1+ import { fireEvent , render , screen , waitFor } from "@testing-library/react" ;
2+ import { beforeEach , describe , expect , it , vi } from "vitest" ;
3+ import { SecureImageComponent } from "./SecureImageComponent" ;
4+
5+ describe ( "SecureImageComponent" , ( ) => {
6+ beforeEach ( ( ) => {
7+ vi . clearAllMocks ( ) ;
8+ } ) ;
9+
10+ describe ( "Default blocking behavior" , ( ) => {
11+ it ( "should block images by default and show warning message" , ( ) => {
12+ render ( < SecureImageComponent src = "https://example.com/image.jpg" /> ) ;
13+
14+ expect ( screen . getByText ( / I m a g e b l o c k e d f o r s e c u r i t y / ) ) . toBeInTheDocument ( ) ;
15+ expect ( screen . getByText ( "Load Image" ) ) . toBeInTheDocument ( ) ;
16+ expect ( screen . queryByRole ( "img" ) ) . not . toBeInTheDocument ( ) ;
17+ } ) ;
18+
19+ it ( "should display the image URL" , ( ) => {
20+ const testUrl = "https://example.com/test-image.png" ;
21+ render ( < SecureImageComponent src = { testUrl } /> ) ;
22+
23+ expect ( screen . getByText ( testUrl ) ) . toBeInTheDocument ( ) ;
24+ } ) ;
25+
26+ it ( "should handle invalid src prop" , ( ) => {
27+ render ( < SecureImageComponent src = { undefined } /> ) ;
28+
29+ expect ( screen . getByText ( "[Invalid image: no source]" ) ) . toBeInTheDocument ( ) ;
30+ expect ( screen . queryByText ( "Load Image" ) ) . not . toBeInTheDocument ( ) ;
31+ } ) ;
32+ } ) ;
33+
34+ describe ( "Query parameter detection" , ( ) => {
35+ it ( "should detect and display query parameters" , ( ) => {
36+ render ( < SecureImageComponent src = "https://example.com/image.jpg?user=123& token = abc " /> ) ;
37+
38+ expect ( screen . getByText ( / W a r n i n g : U R L c o n t a i n s q u e r y p a r a m e t e r s / ) ) . toBeInTheDocument ( ) ;
39+ expect ( screen . getByText ( / " u s e r " : " 1 2 3 " / ) ) . toBeInTheDocument ( ) ;
40+ expect ( screen . getByText ( / " t o k e n " : " a b c " / ) ) . toBeInTheDocument ( ) ;
41+ } ) ;
42+
43+ it ( "should not show query parameter warning for URLs without parameters" , ( ) => {
44+ render ( < SecureImageComponent src = "https://example.com/image.jpg" /> ) ;
45+
46+ expect ( screen . queryByText ( / W a r n i n g : U R L c o n t a i n s q u e r y p a r a m e t e r s / ) ) . not . toBeInTheDocument ( ) ;
47+ } ) ;
48+
49+ it ( "should handle relative URLs with query parameters" , ( ) => {
50+ render ( < SecureImageComponent src = "/images/test.jpg?id=456& session = xyz " /> ) ;
51+
52+ expect ( screen . getByText ( / W a r n i n g : U R L c o n t a i n s q u e r y p a r a m e t e r s / ) ) . toBeInTheDocument ( ) ;
53+ expect ( screen . getByText ( / " i d " : " 4 5 6 " / ) ) . toBeInTheDocument ( ) ;
54+ expect ( screen . getByText ( / " s e s s i o n " : " x y z " / ) ) . toBeInTheDocument ( ) ;
55+ } ) ;
56+
57+ it ( "should handle malformed URLs gracefully" , ( ) => {
58+ render ( < SecureImageComponent src = "not-a-valid-url://image" /> ) ;
59+
60+ // Should still display the URL even if it can't be parsed
61+ expect ( screen . getByText ( "not-a-valid-url://image" ) ) . toBeInTheDocument ( ) ;
62+ expect ( screen . getByText ( "Load Image" ) ) . toBeInTheDocument ( ) ;
63+ } ) ;
64+ } ) ;
65+
66+ describe ( "User interaction" , ( ) => {
67+ it ( "should show image when Load Image button is clicked" , ( ) => {
68+ const testUrl = "https://example.com/image.jpg" ;
69+ render ( < SecureImageComponent src = { testUrl } alt = "Test image" /> ) ;
70+
71+ // Initially no image
72+ expect ( screen . queryByRole ( "img" ) ) . not . toBeInTheDocument ( ) ;
73+
74+ // Click load button
75+ const loadButton = screen . getByText ( "Load Image" ) ;
76+ fireEvent . click ( loadButton ) ;
77+
78+ // Image should now be displayed (query by tag since it might be role="presentation")
79+ const image = screen . getByAltText ( "Test image" ) ;
80+ expect ( image ) . toBeInTheDocument ( ) ;
81+ expect ( image ) . toHaveAttribute ( "src" , testUrl ) ;
82+ expect ( image ) . toHaveAttribute ( "alt" , "Test image" ) ;
83+
84+ // Warning message should be gone
85+ expect ( screen . queryByText ( / I m a g e b l o c k e d f o r s e c u r i t y / ) ) . not . toBeInTheDocument ( ) ;
86+ } ) ;
87+
88+ it ( "should handle image load errors" , async ( ) => {
89+ const testUrl = "https://example.com/broken-image.jpg" ;
90+ render ( < SecureImageComponent src = { testUrl } alt = "broken image" /> ) ;
91+
92+ // Click load button
93+ const loadButton = screen . getByText ( "Load Image" ) ;
94+ fireEvent . click ( loadButton ) ;
95+
96+ // Simulate image error (query by alt text since role might be presentation)
97+ const image = screen . getByAltText ( "broken image" ) ;
98+ fireEvent . error ( image ) ;
99+
100+ // Should show error message and hide image
101+ await waitFor ( ( ) => {
102+ expect ( screen . getByText ( / F a i l e d t o l o a d i m a g e / ) ) . toBeInTheDocument ( ) ;
103+ expect ( screen . queryByAltText ( "broken image" ) ) . not . toBeInTheDocument ( ) ;
104+ } ) ;
105+
106+ // Load button should be available again
107+ expect ( screen . getByText ( "Load Image" ) ) . toBeInTheDocument ( ) ;
108+ } ) ;
109+
110+ it ( "should pass through title and className props" , ( ) => {
111+ render (
112+ < SecureImageComponent
113+ src = "https://example.com/image.jpg"
114+ alt = "test image"
115+ title = "Image title"
116+ className = "custom-class"
117+ />
118+ ) ;
119+
120+ // Click load button
121+ fireEvent . click ( screen . getByText ( "Load Image" ) ) ;
122+
123+ // Check image has title (query by alt text)
124+ const image = screen . getByAltText ( "test image" ) ;
125+ expect ( image ) . toHaveAttribute ( "title" , "Image title" ) ;
126+
127+ // Check container has className
128+ const container = image . parentElement ;
129+ expect ( container ) . toHaveClass ( "custom-class" ) ;
130+ } ) ;
131+ } ) ;
132+
133+ describe ( "Security features" , ( ) => {
134+ it ( "should display query parameters as JSON for transparency" , ( ) => {
135+ render (
136+ < SecureImageComponent
137+ src = "https://malicious.com/[email protected] & id = 12345 & action = view " 138+ />
139+ ) ;
140+
141+ // Should show all parameters clearly
142+ expect ( screen . getByText ( / W a r n i n g : U R L c o n t a i n s q u e r y p a r a m e t e r s / ) ) . toBeInTheDocument ( ) ;
143+
144+ // Check JSON is properly formatted
145+ const preElement = screen . getByText ( / " e m a i l " : " u s e r @ e x a m p l e .c o m " / ) ;
146+ expect ( preElement ) . toBeInTheDocument ( ) ;
147+ expect ( screen . getByText ( / " i d " : " 1 2 3 4 5 " / ) ) . toBeInTheDocument ( ) ;
148+ expect ( screen . getByText ( / " a c t i o n " : " v i e w " / ) ) . toBeInTheDocument ( ) ;
149+ } ) ;
150+
151+ it ( "should handle encoded query parameters" , ( ) => {
152+ render (
153+ < SecureImageComponent
154+ src = "https://example.com/img.png?data=%7B%22user%22%3A%22test%22%7D"
155+ />
156+ ) ;
157+
158+ // Should decode and display the parameter
159+ expect ( screen . getByText ( / W a r n i n g : U R L c o n t a i n s q u e r y p a r a m e t e r s / ) ) . toBeInTheDocument ( ) ;
160+ // The decoded value should be shown in the pre element
161+ const preElement = document . querySelector ( 'pre' ) ;
162+ expect ( preElement ) . toBeTruthy ( ) ;
163+ // Check that the JSON contains the decoded data
164+ expect ( preElement ?. textContent ) . toContain ( '"data"' ) ;
165+ // The value is decoded as a string containing JSON
166+ expect ( preElement ?. textContent ) . toContain ( '"{' ) ;
167+ expect ( preElement ?. textContent ) . toContain ( 'user' ) ;
168+ expect ( preElement ?. textContent ) . toContain ( 'test' ) ;
169+ } ) ;
170+ } ) ;
171+
172+ describe ( "Alt text handling" , ( ) => {
173+ it ( "should use empty string for alt when not provided" , ( ) => {
174+ render ( < SecureImageComponent src = "https://example.com/image.jpg" /> ) ;
175+
176+ fireEvent . click ( screen . getByText ( "Load Image" ) ) ;
177+
178+ // Query by tag name since empty alt makes it role="presentation"
179+ const images = document . querySelectorAll ( 'img' ) ;
180+ expect ( images . length ) . toBe ( 1 ) ;
181+ expect ( images [ 0 ] ) . toHaveAttribute ( "alt" , "" ) ;
182+ } ) ;
183+
184+ it ( "should use provided alt text" , ( ) => {
185+ render (
186+ < SecureImageComponent
187+ src = "https://example.com/image.jpg"
188+ alt = "Description of image"
189+ />
190+ ) ;
191+
192+ fireEvent . click ( screen . getByText ( "Load Image" ) ) ;
193+
194+ const image = screen . getByAltText ( "Description of image" ) ;
195+ expect ( image ) . toHaveAttribute ( "alt" , "Description of image" ) ;
196+ } ) ;
197+ } ) ;
198+ } ) ;
0 commit comments