@@ -5,53 +5,92 @@ import TaskVisualPreviews from './TaskVisualPreviews';
55const asset = ( id : string ) => `https://github.com/user-attachments/assets/${ id } ` ;
66
77describe ( 'TaskVisualPreviews' , ( ) => {
8- it ( 'frames one capture at a time beside its title, description and viewport ' , ( ) => {
8+ it ( 'stacks the context strip over a full-width stage that fits the whole capture ' , ( ) => {
99 const { container } = render ( < TaskVisualPreviews previews = { [
1010 { type : 'image' , title : 'Task list at Desktop 1920px' , description : 'Row keeps its full title' , url : asset ( 'image' ) } ,
1111 { type : 'video' , title : 'Checkout flow' , url : asset ( 'video' ) } ,
1212 { type : 'image' , title : 'Untrusted' , url : 'https://evil.test/image.png' } ,
1313 ] } /> ) ;
1414 expect ( screen . getByRole ( 'heading' , { name : 'Visual evidence (2 captures)' } ) ) . toBeInTheDocument ( ) ;
15- const canvas = screen . getByTestId ( 'visual-evidence-canvas' ) ;
16- expect ( canvas ) . toHaveClass ( 'sm:h-56' , 'sm:flex-row' ) ;
15+ const stage = screen . getByTestId ( 'visual-evidence-canvas' ) ;
16+ expect ( stage ) . toHaveClass ( 'w-full' , 'sm:h-[440px]' , 'items-center' , 'justify-center' ) ;
17+ expect ( stage ) . toHaveAttribute ( 'data-zoom' , 'fit' ) ;
1718 const image = screen . getByAltText ( 'Task list at Desktop 1920px' ) ;
1819 expect ( image ) . toHaveAttribute ( 'src' , asset ( 'image' ) ) ;
19- expect ( image ) . toHaveClass ( 'h-full' , 'w-full' , 'object-cover' , 'object-left-top' ) ;
20- expect ( within ( canvas ) . getByText ( 'Row keeps its full title' ) ) . toBeInTheDocument ( ) ;
21- expect ( within ( canvas ) . getByText ( 'Desktop 1920px' ) ) . toBeInTheDocument ( ) ;
22- // A light browser window frames the capture, sized like one, with no black bezel around it.
23- const frame = within ( canvas ) . getByTestId ( 'visual-evidence-window' ) ;
24- expect ( frame ) . toHaveClass ( 'border-slate-200' , 'bg-white' ) ;
25- expect ( within ( frame ) . getByTestId ( 'visual-evidence-window-size' ) ) . toHaveTextContent ( / ^ 1 9 2 0 p x $ / ) ;
26- expect ( canvas . innerHTML ) . not . toMatch ( / b g - s l a t e - 9 5 0 p - | b g - s l a t e - 9 0 0 / ) ;
27- expect ( screen . getByText ( 'Click to inspect full resolution' ) ) . toBeInTheDocument ( ) ;
20+ // Fit never clips: the whole capture is scaled into the stage.
21+ expect ( image ) . toHaveClass ( 'max-h-full' , 'max-w-full' , 'object-contain' ) ;
22+ expect ( image ) . not . toHaveClass ( 'object-cover' ) ;
23+ const context = screen . getByTestId ( 'visual-evidence-context' ) ;
24+ expect ( within ( context ) . getByText ( 'Row keeps its full title' ) ) . toBeInTheDocument ( ) ;
25+ expect ( within ( context ) . getByText ( 'Desktop 1920px' ) ) . toBeInTheDocument ( ) ;
26+ expect ( within ( context ) . getByText ( 'Screenshot' ) ) . toBeInTheDocument ( ) ;
27+ // No faux browser window around the capture.
28+ expect ( screen . queryByTestId ( 'visual-evidence-window' ) ) . toBeNull ( ) ;
29+ expect ( container . querySelectorAll ( '.rounded-full' ) ) . toHaveLength ( 0 ) ;
2830 expect ( screen . getByRole ( 'link' , { name : 'Open original: Task list at Desktop 1920px' } ) ) . toHaveAttribute ( 'href' , asset ( 'image' ) ) ;
2931 // Only the selected capture is on screen: the video waits behind the switcher.
3032 expect ( container . querySelector ( 'video' ) ) . toBeNull ( ) ;
3133 expect ( screen . queryByText ( 'Untrusted' ) ) . toBeNull ( ) ;
3234 expect ( container . querySelector ( 'img[src="https://evil.test/image.png"]' ) ) . toBeNull ( ) ;
3335 } ) ;
3436
37+ it ( 'shows the capture at its real pixel size in a scrollable stage at 100%' , ( ) => {
38+ render ( < TaskVisualPreviews previews = { [ { type : 'image' , title : 'Tall mobile page' , url : asset ( 'tall' ) } ] } /> ) ;
39+ const zoom = screen . getByRole ( 'group' , { name : 'Zoom' } ) ;
40+ expect ( within ( zoom ) . getByRole ( 'button' , { name : 'Fit' } ) ) . toHaveAttribute ( 'aria-pressed' , 'true' ) ;
41+ fireEvent . click ( within ( zoom ) . getByRole ( 'button' , { name : '100%' } ) ) ;
42+ expect ( within ( zoom ) . getByRole ( 'button' , { name : '100%' } ) ) . toHaveAttribute ( 'aria-pressed' , 'true' ) ;
43+ const stage = screen . getByTestId ( 'visual-evidence-canvas' ) ;
44+ expect ( stage ) . toHaveAttribute ( 'data-zoom' , 'actual' ) ;
45+ expect ( stage ) . toHaveClass ( 'overflow-auto' ) ;
46+ expect ( screen . getByAltText ( 'Tall mobile page' ) ) . toHaveClass ( 'max-w-none' , 'h-auto' , 'w-auto' ) ;
47+ } ) ;
48+
49+ it ( 'reports the captured resolution once the image loads' , ( ) => {
50+ render ( < TaskVisualPreviews previews = { [ { type : 'image' , title : 'Dialog' , url : asset ( 'dialog' ) } ] } /> ) ;
51+ const image = screen . getByAltText ( 'Dialog' ) ;
52+ Object . defineProperty ( image , 'naturalWidth' , { value : 1440 } ) ;
53+ Object . defineProperty ( image , 'naturalHeight' , { value : 900 } ) ;
54+ fireEvent . load ( image ) ;
55+ expect ( screen . getByTestId ( 'visual-evidence-dimensions' ) ) . toHaveTextContent ( '1440×900' ) ;
56+ } ) ;
57+
58+ it ( 'numbers captures in the switcher, marks their device and flags failed ones' , ( ) => {
59+ render ( < TaskVisualPreviews previews = { [
60+ { type : 'image' , title : 'Chart at desktop 1440px' , url : asset ( 'a' ) } ,
61+ { type : 'image' , title : 'Assertion failed at 390px' , url : asset ( 'b' ) } ,
62+ { type : 'image' , title : 'Checkout assertion passed' , url : asset ( 'c' ) } ,
63+ ] } /> ) ;
64+ const switcher = screen . getByRole ( 'group' , { name : 'Captures' } ) ;
65+ expect ( within ( switcher ) . getAllByRole ( 'button' ) . map ( button => button . textContent ) ) . toEqual ( [ '01Desktop 1440px' , '02390px' , '03Checkout assertion passed' ] ) ;
66+ expect ( within ( switcher ) . getByRole ( 'button' , { name : '390px' } ) ) . toHaveClass ( 'border-red-300' , 'text-red-700' , 'bg-red-50' ) ;
67+ expect ( within ( switcher ) . getByRole ( 'button' , { name : 'Desktop 1440px' } ) ) . not . toHaveClass ( 'border-red-300' ) ;
68+ const passed = within ( switcher ) . getByRole ( 'button' , { name : 'Checkout assertion passed' } ) ;
69+ expect ( passed ) . not . toHaveClass ( 'border-red-300' , 'text-red-700' , 'bg-red-50' ) ;
70+ expect ( passed ) . toHaveClass ( 'border-transparent' , 'text-slate-600' ) ;
71+ } ) ;
72+
3573 it ( 'switches captures in place and plays videos with their controls' , ( ) => {
3674 const { container } = render ( < TaskVisualPreviews previews = { [
3775 { type : 'image' , title : 'Before the fix' , url : asset ( 'before' ) } ,
3876 { type : 'image' , title : 'After the fix on mobile 390px' , url : asset ( 'after' ) } ,
3977 { type : 'video' , title : 'Walkthrough' , url : asset ( 'video' ) } ,
4078 ] } /> ) ;
4179 const switcher = screen . getByRole ( 'group' , { name : 'Captures' } ) ;
42- expect ( within ( switcher ) . getAllByRole ( 'button' ) . map ( button => button . textContent ) ) . toEqual ( [ 'Before ' , 'After ' , 'Capture 3 ' ] ) ;
80+ expect ( within ( switcher ) . getAllByRole ( 'button' ) . map ( button => button . textContent ) ) . toEqual ( [ '01Before ' , '02After ' , '03Walkthrough ' ] ) ;
4381 expect ( within ( switcher ) . getByRole ( 'button' , { name : 'Before' } ) ) . toHaveAttribute ( 'aria-pressed' , 'true' ) ;
4482
4583 fireEvent . click ( within ( switcher ) . getByRole ( 'button' , { name : 'After' } ) ) ;
4684 expect ( screen . getByAltText ( 'After the fix on mobile 390px' ) ) . toBeInTheDocument ( ) ;
4785 expect ( screen . queryByAltText ( 'Before the fix' ) ) . toBeNull ( ) ;
4886 expect ( screen . getByText ( 'Mobile 390px' ) ) . toBeInTheDocument ( ) ;
4987
50- fireEvent . click ( within ( switcher ) . getByRole ( 'button' , { name : 'Capture 3 ' } ) ) ;
88+ fireEvent . click ( within ( switcher ) . getByRole ( 'button' , { name : 'Walkthrough ' } ) ) ;
5189 const video = container . querySelector ( 'video' ) ;
5290 expect ( video ) . toHaveAttribute ( 'src' , asset ( 'video' ) ) ;
5391 expect ( video ) . toHaveAttribute ( 'controls' ) ;
5492 expect ( screen . queryByRole ( 'button' , { name : 'Lightbox' } ) ) . toBeNull ( ) ;
93+ expect ( screen . queryByRole ( 'group' , { name : 'Zoom' } ) ) . toBeNull ( ) ;
5594 } ) ;
5695
5796 it ( 'opens images in an in-app lightbox, skips videos, and restores focus on close' , ( ) => {
@@ -60,7 +99,7 @@ describe('TaskVisualPreviews', () => {
6099 { type : 'video' , title : 'Walkthrough' , url : asset ( 'video' ) } ,
61100 { type : 'image' , title : 'Second' , url : asset ( 'second' ) } ,
62101 ] } /> ) ;
63- fireEvent . click ( screen . getByRole ( 'button' , { name : 'Capture 3 ' } ) ) ;
102+ fireEvent . click ( screen . getByRole ( 'button' , { name : 'Second ' } ) ) ;
64103 const trigger = screen . getByRole ( 'button' , { name : 'Open full-size preview: Second' } ) ;
65104 expect ( trigger ) . toHaveAttribute ( 'aria-haspopup' , 'dialog' ) ;
66105 trigger . focus ( ) ;
@@ -76,7 +115,7 @@ describe('TaskVisualPreviews', () => {
76115 expect ( screen . queryByRole ( 'dialog' ) ) . toBeNull ( ) ;
77116 expect ( trigger ) . toHaveFocus ( ) ;
78117 // The canvas follows the capture the lightbox was left on.
79- expect ( screen . getByRole ( 'button' , { name : 'Capture 1 ' } ) ) . toHaveAttribute ( 'aria-pressed' , 'true' ) ;
118+ expect ( screen . getByRole ( 'button' , { name : 'First ' } ) ) . toHaveAttribute ( 'aria-pressed' , 'true' ) ;
80119 } ) ;
81120
82121 it ( 'opens the lightbox from the header button too' , ( ) => {
0 commit comments