11# frozen_string_literal: true
22
33require "components/test_helper"
4+ require "webmock/minitest"
45
56class PrimerOpenProjectAvatarWithFallbackTest < Minitest ::Test
67 include Primer ::ComponentTestHelpers
78
8- def setup
9- # Disable URL validation by default in tests to avoid HTTP requests
10- @original_validate_urls = Primer ::OpenProject ::AvatarWithFallback . validate_urls
11- Primer ::OpenProject ::AvatarWithFallback . validate_urls = false
12- end
13-
14- def teardown
15- Primer ::OpenProject ::AvatarWithFallback . validate_urls = @original_validate_urls
16- end
17-
189 def test_renders_image_avatar_with_src
1910 render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : "https://github.com/github.png" , alt : "github" ) )
2011
@@ -41,14 +32,63 @@ def test_image_avatar_error_handling_setup
4132 assert_includes svg_content , ">AJ<" , "Fallback SVG should contain initials 'AJ'"
4233 end
4334
44- def test_falls_back_when_absolute_url_is_inaccessible
35+ def test_skips_validation_for_non_allowlisted_hosts
36+ Primer ::OpenProject ::AvatarWithFallback . validate_urls = true
37+ # url_accessible? should NOT be called for non-allowlisted hosts
38+ Primer ::OpenProject ::AvatarWithFallback . any_instance . expects ( :url_accessible? ) . never
39+
40+ render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : "https://example.com/avatar.png" , alt : "Test User" , unique_id : 43 ) )
41+
42+ # Should render the original URL (no server-side validation for non-allowlisted hosts)
43+ assert_selector ( "avatar-fallback" ) do
44+ assert_selector ( "img.avatar[src='https://example.com/avatar.png']" )
45+ end
46+ end
47+
48+ def test_handles_invalid_uri_in_allowed_host_check
4549 Primer ::OpenProject ::AvatarWithFallback . validate_urls = true
46- Primer ::OpenProject ::AvatarWithFallback . any_instance . stubs ( :url_accessible? ) . returns ( false )
4750
48- render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : "https://broken.example.com/avatar.png" , alt : "Test User" , unique_id : 42 ) )
51+ # Invalid URI should not raise, just skip validation
52+ render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : "not a valid uri %%" , alt : "Test User" , unique_id : 45 ) )
4953
50- # Should render fallback SVG when URL is inaccessible
51- assert_selector ( "avatar-fallback[data-unique-id='42']" ) do
54+ # Should render the original URL (invalid URI treated as non-allowlisted)
55+ assert_selector ( "avatar-fallback" ) do
56+ assert_selector ( "img.avatar[src='not a valid uri %%']" )
57+ end
58+ end
59+
60+ def test_url_accessible_returns_true_for_successful_head_request
61+ Primer ::OpenProject ::AvatarWithFallback . validate_urls = true
62+ stub_request ( :head , "https://gravatar.com/avatar/exists" ) . to_return ( status : 200 )
63+
64+ render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : "https://gravatar.com/avatar/exists" , alt : "Test User" , unique_id : 46 ) )
65+
66+ # Should render original URL when HEAD request succeeds
67+ assert_selector ( "avatar-fallback" ) do
68+ assert_selector ( "img.avatar[src='https://gravatar.com/avatar/exists']" )
69+ end
70+ end
71+
72+ def test_url_accessible_returns_false_for_404_response
73+ Primer ::OpenProject ::AvatarWithFallback . validate_urls = true
74+ stub_request ( :head , "https://gravatar.com/avatar/notfound" ) . to_return ( status : 404 )
75+
76+ render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : "https://gravatar.com/avatar/notfound" , alt : "Test User" , unique_id : 47 ) )
77+
78+ # Should render fallback SVG when HEAD request returns 404
79+ assert_selector ( "avatar-fallback" ) do
80+ assert_selector ( "img.avatar[src^='data:image/svg+xml;base64,']" )
81+ end
82+ end
83+
84+ def test_url_accessible_returns_false_on_network_error
85+ Primer ::OpenProject ::AvatarWithFallback . validate_urls = true
86+ stub_request ( :head , "https://gravatar.com/avatar/timeout" ) . to_timeout
87+
88+ render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : "https://gravatar.com/avatar/timeout" , alt : "Test User" , unique_id : 48 ) )
89+
90+ # Should render fallback SVG when network error occurs
91+ assert_selector ( "avatar-fallback" ) do
5292 assert_selector ( "img.avatar[src^='data:image/svg+xml;base64,']" )
5393 end
5494 end
@@ -76,12 +116,6 @@ def test_defaults_to_size_20
76116 assert_selector ( "img.avatar[size=20][height=20][width=20]" )
77117 end
78118
79- def test_fallback_defaults_to_size_20
80- render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : nil , alt : "Test User" ) )
81-
82- assert_selector ( "img.avatar[src^='data:image/svg+xml;base64,']" )
83- end
84-
85119 def test_falls_back_when_size_isn_t_valid
86120 without_fetch_or_fallback_raises do
87121 render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : "https://github.com/github.png" , alt : "github" , size : 1_000_000_000 ) )
@@ -171,15 +205,6 @@ def test_fallback_with_unique_id_in_data_attribute
171205 end
172206 end
173207
174- def test_fallback_without_unique_id
175- render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : nil , alt : "Test User" ) )
176-
177- # Should still render, just without unique_id data attribute
178- assert_selector ( "avatar-fallback[data-alt-text='Test User']" ) do
179- assert_selector ( "img.avatar[src^='data:image/svg+xml;base64,']" )
180- end
181- end
182-
183208 def test_adds_custom_classes
184209 render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : "https://github.com/github.png" , alt : "github" , classes : "custom-class" ) )
185210
@@ -208,12 +233,14 @@ def test_raises_when_both_src_and_alt_are_blank
208233 assert_includes ( error . message , "`src` or `alt` is required" )
209234 end
210235
211- def test_fallback_with_single_word_name
236+ def test_fallback_extracts_single_initial_from_single_word_name
212237 render_inline ( Primer ::OpenProject ::AvatarWithFallback . new ( src : nil , alt : "Alice" ) )
213238
214- assert_selector ( "avatar-fallback" ) do
215- assert_selector ( "img.avatar[src^='data:image/svg+xml;base64,']" )
216- end
239+ fallback_wrapper = page . find ( "avatar-fallback" )
240+ fallback_src = fallback_wrapper [ "data-fallback-src" ]
241+ svg_content = Base64 . decode64 ( fallback_src . sub ( "data:image/svg+xml;base64," , "" ) )
242+
243+ assert_includes svg_content , ">A<" , "Single word name should produce single initial 'A'"
217244 end
218245
219246 def test_status
0 commit comments