1- import { describe , it , expect , vi } from "vitest" ;
1+ import { describe , it , expect , vi , beforeEach } from "vitest" ;
22import { render , screen } from "@testing-library/react" ;
33import {
44 Dialog ,
@@ -7,7 +7,30 @@ import {
77 DialogTitle ,
88} from "./dialog" ;
99
10+ function withMobileViewport ( fn : ( ) => void ) {
11+ const originalWidth = window . innerWidth
12+ Object . defineProperty ( window , 'innerWidth' , {
13+ writable : true ,
14+ configurable : true ,
15+ value : 375 ,
16+ } )
17+ fn ( )
18+ Object . defineProperty ( window , 'innerWidth' , {
19+ writable : true ,
20+ configurable : true ,
21+ value : originalWidth ,
22+ } )
23+ }
24+
1025describe ( "DialogContent" , ( ) => {
26+ beforeEach ( ( ) => {
27+ Object . defineProperty ( window , 'innerWidth' , {
28+ writable : true ,
29+ configurable : true ,
30+ value : 375 ,
31+ } )
32+ } )
33+
1134 it ( "applies safe-area padding when fullscreen prop is true" , ( ) => {
1235 render (
1336 < Dialog open >
@@ -162,64 +185,225 @@ describe("DialogContent", () => {
162185 expect ( content ) . toHaveStyle ( { paddingTop : "env(safe-area-inset-top, 0px)" } ) ;
163186 } ) ;
164187
165- it ( 'renders hidden close trigger when mobileSwipeToClose is enabled' , ( ) => {
166- const onOpenChange = vi . fn ( ) ;
167- render (
168- < Dialog open onOpenChange = { onOpenChange } >
169- < DialogContent mobileFullscreen mobileSwipeToClose >
170- < DialogHeader >
171- < DialogTitle > Swipe Dialog</ DialogTitle >
172- </ DialogHeader >
173- </ DialogContent >
174- </ Dialog >
175- ) ;
176- const closeTrigger = document . querySelector ( '[data-swipe-close-trigger]' ) ;
177- expect ( closeTrigger ) . toBeInTheDocument ( ) ;
188+ it ( 'renders hidden close trigger by default on mobile' , ( ) => {
189+ withMobileViewport ( ( ) => {
190+ const onOpenChange = vi . fn ( ) ;
191+ render (
192+ < Dialog open onOpenChange = { onOpenChange } >
193+ < DialogContent >
194+ < DialogHeader >
195+ < DialogTitle > Swipe Dialog</ DialogTitle >
196+ </ DialogHeader >
197+ </ DialogContent >
198+ </ Dialog >
199+ ) ;
200+ const closeTrigger = document . querySelector ( '[data-swipe-close-trigger]' ) ;
201+ expect ( closeTrigger ) . toBeInTheDocument ( ) ;
202+ } ) ;
203+ } ) ;
204+
205+ it ( 'does not render hidden close trigger when mobileSwipeToClose is false' , ( ) => {
206+ withMobileViewport ( ( ) => {
207+ render (
208+ < Dialog open >
209+ < DialogContent mobileSwipeToClose = { false } >
210+ < DialogHeader >
211+ < DialogTitle > Swipe Dialog</ DialogTitle >
212+ </ DialogHeader >
213+ </ DialogContent >
214+ </ Dialog >
215+ ) ;
216+ const closeTrigger = document . querySelector ( '[data-swipe-close-trigger]' ) ;
217+ expect ( closeTrigger ) . not . toBeInTheDocument ( ) ;
218+ } ) ;
178219 } ) ;
179220
180221 it ( 'closes dialog when hidden close trigger is activated' , ( ) => {
181- const onOpenChange = vi . fn ( ) ;
182- render (
183- < Dialog open onOpenChange = { onOpenChange } >
184- < DialogContent mobileFullscreen mobileSwipeToClose >
185- < DialogHeader >
186- < DialogTitle > Swipe Dialog</ DialogTitle >
187- </ DialogHeader >
188- </ DialogContent >
189- </ Dialog >
190- ) ;
191-
192- const closeTrigger = document . querySelector ( '[data-swipe-close-trigger]' ) as HTMLButtonElement | null ;
193- expect ( closeTrigger ) . toBeInTheDocument ( ) ;
194-
195- if ( closeTrigger ) {
196- closeTrigger . click ( ) ;
222+ withMobileViewport ( ( ) => {
223+ const onOpenChange = vi . fn ( ) ;
224+ render (
225+ < Dialog open onOpenChange = { onOpenChange } >
226+ < DialogContent >
227+ < DialogHeader >
228+ < DialogTitle > Swipe Dialog</ DialogTitle >
229+ </ DialogHeader >
230+ </ DialogContent >
231+ </ Dialog >
232+ ) ;
233+
234+ const closeTrigger = document . querySelector ( '[data-swipe-close-trigger]' ) as HTMLButtonElement | null ;
235+ expect ( closeTrigger ) . toBeInTheDocument ( ) ;
236+
237+ if ( closeTrigger ) {
238+ closeTrigger . click ( ) ;
239+ expect ( onOpenChange ) . toHaveBeenCalledWith ( false ) ;
240+ }
241+ } ) ;
242+ } ) ;
243+
244+ it ( 'calls onSwipeBack when canSwipeBack is true and swipe completes' , ( ) => {
245+ withMobileViewport ( ( ) => {
246+ const mockOnSwipeBack = vi . fn ( ) ;
247+ const onOpenChange = vi . fn ( ) ;
248+ render (
249+ < Dialog open onOpenChange = { onOpenChange } >
250+ < DialogContent
251+ canSwipeBack = { ( ) => true }
252+ onSwipeBack = { mockOnSwipeBack }
253+ data-testid = "swipe-dialog"
254+ >
255+ Content
256+ </ DialogContent >
257+ </ Dialog >
258+ ) ;
259+
260+ const content = screen . getByTestId ( 'swipe-dialog' ) ;
261+
262+ content . dispatchEvent ( new TouchEvent ( 'touchstart' , {
263+ touches : [ { clientX : 10 , clientY : 100 } ] as any ,
264+ } ) ) ;
265+ content . dispatchEvent ( new TouchEvent ( 'touchmove' , {
266+ touches : [ { clientX : 100 , clientY : 100 } ] as any ,
267+ } ) ) ;
268+ content . dispatchEvent ( new TouchEvent ( 'touchend' , {
269+ changedTouches : [ { clientX : 100 , clientY : 100 } ] as any ,
270+ } ) ) ;
271+
272+ expect ( mockOnSwipeBack ) . toHaveBeenCalled ( ) ;
273+ expect ( onOpenChange ) . not . toHaveBeenCalled ( ) ;
274+ } ) ;
275+ } ) ;
276+
277+ it ( 'attempts close when canSwipeBack is false and swipe completes' , ( ) => {
278+ withMobileViewport ( ( ) => {
279+ const mockOnSwipeBack = vi . fn ( ) ;
280+ const onOpenChange = vi . fn ( ) ;
281+ render (
282+ < Dialog open onOpenChange = { onOpenChange } >
283+ < DialogContent
284+ canSwipeBack = { ( ) => false }
285+ onSwipeBack = { mockOnSwipeBack }
286+ data-testid = "swipe-dialog"
287+ >
288+ Content
289+ </ DialogContent >
290+ </ Dialog >
291+ ) ;
292+
293+ const content = screen . getByTestId ( 'swipe-dialog' ) ;
294+ content . dispatchEvent ( new TouchEvent ( 'touchstart' , {
295+ touches : [ { clientX : 10 , clientY : 100 } ] as any ,
296+ } ) ) ;
297+ content . dispatchEvent ( new TouchEvent ( 'touchmove' , {
298+ touches : [ { clientX : 100 , clientY : 100 } ] as any ,
299+ } ) ) ;
300+ content . dispatchEvent ( new TouchEvent ( 'touchend' , {
301+ changedTouches : [ { clientX : 100 , clientY : 100 } ] as any ,
302+ } ) ) ;
303+
304+ expect ( mockOnSwipeBack ) . not . toHaveBeenCalled ( ) ;
197305 expect ( onOpenChange ) . toHaveBeenCalledWith ( false ) ;
198- }
306+ } ) ;
307+ } ) ;
308+
309+ it ( 'applies safe-area style and hidden trigger for mobileFullscreen' , ( ) => {
310+ withMobileViewport ( ( ) => {
311+ render (
312+ < Dialog open >
313+ < DialogContent mobileFullscreen data-testid = "dialog-content" >
314+ Content
315+ </ DialogContent >
316+ </ Dialog >
317+ ) ;
318+ const content = screen . getByTestId ( "dialog-content" ) ;
319+ expect ( content ) . toHaveStyle ( { paddingTop : "env(safe-area-inset-top, 0px)" } ) ;
320+ expect ( document . querySelector ( '[data-swipe-close-trigger]' ) ) . toBeInTheDocument ( ) ;
321+ } ) ;
322+ } ) ;
323+
324+ it ( 'does not apply transform styles to non-fullscreen dialogs' , ( ) => {
325+ withMobileViewport ( ( ) => {
326+ render (
327+ < Dialog open >
328+ < DialogContent data-testid = "dialog-content" >
329+ Content
330+ </ DialogContent >
331+ </ Dialog >
332+ ) ;
333+ const content = screen . getByTestId ( "dialog-content" ) ;
334+ const style = content . getAttribute ( "style" ) || "" ;
335+ expect ( style ) . not . toMatch ( / t r a n s f o r m / ) ;
336+ } ) ;
337+ } ) ;
338+
339+ it ( 'applies swipe transform to fullscreen dialogs during touchmove' , ( ) => {
340+ withMobileViewport ( ( ) => {
341+ render (
342+ < Dialog open >
343+ < DialogContent fullscreen data-testid = "dialog-content" >
344+ Content
345+ </ DialogContent >
346+ </ Dialog >
347+ ) ;
348+ const content = screen . getByTestId ( "dialog-content" ) ;
349+ content . dispatchEvent ( new TouchEvent ( 'touchstart' , {
350+ touches : [ { clientX : 10 , clientY : 100 } ] as any ,
351+ } ) ) ;
352+ content . dispatchEvent ( new TouchEvent ( 'touchmove' , {
353+ touches : [ { clientX : 50 , clientY : 100 } ] as any ,
354+ } ) ) ;
355+
356+ const style = content . getAttribute ( "style" ) || "" ;
357+ expect ( style ) . toMatch ( / t r a n s f o r m / ) ;
358+ } ) ;
359+ } ) ;
360+
361+ it ( 'does not apply swipe transform to non-fullscreen dialogs during touchmove' , ( ) => {
362+ withMobileViewport ( ( ) => {
363+ render (
364+ < Dialog open >
365+ < DialogContent data-testid = "dialog-content" >
366+ Content
367+ </ DialogContent >
368+ </ Dialog >
369+ ) ;
370+ const content = screen . getByTestId ( "dialog-content" ) ;
371+ content . dispatchEvent ( new TouchEvent ( 'touchstart' , {
372+ touches : [ { clientX : 10 , clientY : 100 } ] as any ,
373+ } ) ) ;
374+ content . dispatchEvent ( new TouchEvent ( 'touchmove' , {
375+ touches : [ { clientX : 50 , clientY : 100 } ] as any ,
376+ } ) ) ;
377+
378+ const style = content . getAttribute ( "style" ) || "" ;
379+ expect ( style ) . not . toMatch ( / t r a n s f o r m / ) ;
380+ } ) ;
199381 } ) ;
200382
201383 it ( 'binds swipe handler when mobileSwipeToClose and mobileFullscreen are enabled' , ( ) => {
202- const onOpenChange = vi . fn ( ) ;
203- render (
204- < Dialog open onOpenChange = { onOpenChange } >
205- < DialogContent mobileFullscreen mobileSwipeToClose data-testid = "swipe-dialog" >
206- < DialogHeader >
207- < DialogTitle > Swipe Dialog</ DialogTitle >
208- </ DialogHeader >
209- </ DialogContent >
210- </ Dialog >
211- ) ;
212-
213- const content = screen . getByTestId ( 'swipe-dialog' ) ;
214- const closeTrigger = document . querySelector ( '[data-swipe-close-trigger]' ) as HTMLButtonElement | null ;
215-
216- expect ( content ) . toBeInTheDocument ( ) ;
217- expect ( closeTrigger ) . toBeInTheDocument ( ) ;
218-
219- const clickSpy = vi . spyOn ( closeTrigger as HTMLButtonElement , 'click' ) ;
220- closeTrigger ?. click ( ) ;
221-
222- expect ( clickSpy ) . toHaveBeenCalled ( ) ;
223- expect ( onOpenChange ) . toHaveBeenCalledWith ( false ) ;
384+ withMobileViewport ( ( ) => {
385+ const onOpenChange = vi . fn ( ) ;
386+ render (
387+ < Dialog open onOpenChange = { onOpenChange } >
388+ < DialogContent mobileFullscreen mobileSwipeToClose data-testid = "swipe-dialog" >
389+ < DialogHeader >
390+ < DialogTitle > Swipe Dialog</ DialogTitle >
391+ </ DialogHeader >
392+ </ DialogContent >
393+ </ Dialog >
394+ ) ;
395+
396+ const content = screen . getByTestId ( 'swipe-dialog' ) ;
397+ const closeTrigger = document . querySelector ( '[data-swipe-close-trigger]' ) as HTMLButtonElement | null ;
398+
399+ expect ( content ) . toBeInTheDocument ( ) ;
400+ expect ( closeTrigger ) . toBeInTheDocument ( ) ;
401+
402+ const clickSpy = vi . spyOn ( closeTrigger as HTMLButtonElement , 'click' ) ;
403+ closeTrigger ?. click ( ) ;
404+
405+ expect ( clickSpy ) . toHaveBeenCalled ( ) ;
406+ expect ( onOpenChange ) . toHaveBeenCalledWith ( false ) ;
407+ } ) ;
224408 } ) ;
225409} ) ;
0 commit comments