11import { expect } from '@playwright/test' ;
2- import { configs , test , dragElementBy } from '@utils/test/playwright' ;
2+ import { configs , dragElementBy , test } from '@utils/test/playwright' ;
33
44configs ( { directions : [ 'ltr' ] } ) . forEach ( ( { title, screenshot, config } ) => {
55 test . describe ( title ( 'sheet modal: rendering' ) , ( ) => {
@@ -30,6 +30,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
3030 test . beforeEach ( async ( { page } ) => {
3131 await page . goto ( '/src/components/modal/test/sheet' , config ) ;
3232 } ) ;
33+
3334 test ( 'should dismiss the sheet modal when clicking the active backdrop' , async ( { page } ) => {
3435 const ionModalDidPresent = await page . spyOnEvent ( 'ionModalDidPresent' ) ;
3536 const ionModalDidDismiss = await page . spyOnEvent ( 'ionModalDidDismiss' ) ;
@@ -42,6 +43,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
4243
4344 await ionModalDidDismiss . next ( ) ;
4445 } ) ;
46+
4547 test ( 'should present another sheet modal when clicking an inactive backdrop' , async ( { page } ) => {
4648 const ionModalDidPresent = await page . spyOnEvent ( 'ionModalDidPresent' ) ;
4749 const modal = page . locator ( '.custom-height' ) ;
@@ -54,6 +56,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
5456
5557 await expect ( modal ) . toBeVisible ( ) ;
5658 } ) ;
59+
5760 test ( 'input outside sheet modal should be focusable when backdrop is inactive' , async ( { page } ) => {
5861 const ionModalDidPresent = await page . spyOnEvent ( 'ionModalDidPresent' ) ;
5962
@@ -66,6 +69,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
6669 await expect ( input ) . toBeFocused ( ) ;
6770 } ) ;
6871 } ) ;
72+
6973 test . describe ( title ( 'sheet modal: setting the breakpoint' ) , ( ) => {
7074 test . describe ( 'sheet modal: invalid values' , ( ) => {
7175 let warnings : string [ ] = [ ] ;
@@ -88,18 +92,21 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
8892 const modal = page . locator ( 'ion-modal' ) ;
8993 await modal . evaluate ( ( el : HTMLIonModalElement ) => el . setCurrentBreakpoint ( 0.01 ) ) ;
9094 } ) ;
95+
9196 test ( 'it should not change the breakpoint when setting to an invalid value' , async ( { page } ) => {
9297 const modal = page . locator ( 'ion-modal' ) ;
9398 const breakpoint = await modal . evaluate ( ( el : HTMLIonModalElement ) => el . getCurrentBreakpoint ( ) ) ;
9499 expect ( breakpoint ) . toBe ( 0.25 ) ;
95100 } ) ;
101+
96102 test ( 'it should warn when setting an invalid breakpoint' , async ( ) => {
97103 expect ( warnings . length ) . toBe ( 1 ) ;
98104 expect ( warnings [ 0 ] ) . toBe (
99105 '[Ionic Warning]: [ion-modal] - Attempted to set invalid breakpoint value 0.01. Please double check that the breakpoint value is part of your defined breakpoints.'
100106 ) ;
101107 } ) ;
102108 } ) ;
109+
103110 test . describe ( 'sheet modal: valid values' , ( ) => {
104111 test . beforeEach ( async ( { page } ) => {
105112 await page . goto ( '/src/components/modal/test/sheet' , config ) ;
@@ -108,6 +115,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
108115 await page . click ( '#sheet-modal' ) ;
109116 await ionModalDidPresent . next ( ) ;
110117 } ) ;
118+
111119 test ( 'should update the current breakpoint' , async ( { page } ) => {
112120 const ionBreakpointDidChange = await page . spyOnEvent ( 'ionBreakpointDidChange' ) ;
113121 const modal = page . locator ( '.modal-sheet' ) ;
@@ -118,6 +126,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
118126 const breakpoint = await modal . evaluate ( ( el : HTMLIonModalElement ) => el . getCurrentBreakpoint ( ) ) ;
119127 expect ( breakpoint ) . toBe ( 0.5 ) ;
120128 } ) ;
129+
121130 test ( 'should emit ionBreakpointDidChange' , async ( { page } ) => {
122131 const ionBreakpointDidChange = await page . spyOnEvent ( 'ionBreakpointDidChange' ) ;
123132 const modal = page . locator ( '.modal-sheet' ) ;
@@ -126,6 +135,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
126135 await ionBreakpointDidChange . next ( ) ;
127136 expect ( ionBreakpointDidChange . events . length ) . toBe ( 1 ) ;
128137 } ) ;
138+
129139 test ( 'should emit ionBreakpointDidChange when breakpoint is set to 0' , async ( { page } ) => {
130140 const ionBreakpointDidChange = await page . spyOnEvent ( 'ionBreakpointDidChange' ) ;
131141 const modal = page . locator ( '.modal-sheet' ) ;
@@ -134,6 +144,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
134144 await ionBreakpointDidChange . next ( ) ;
135145 expect ( ionBreakpointDidChange . events . length ) . toBe ( 1 ) ;
136146 } ) ;
147+
137148 test ( 'should emit ionBreakpointDidChange when the sheet is swiped to breakpoint 0' , async ( { page } ) => {
138149 const ionBreakpointDidChange = await page . spyOnEvent ( 'ionBreakpointDidChange' ) ;
139150 const header = page . locator ( '.modal-sheet ion-header' ) ;
@@ -211,6 +222,7 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
211222 expect ( updatedBreakpoint ) . toBe ( 0.5 ) ;
212223 } ) ;
213224 } ) ;
225+
214226 test . describe ( title ( 'sheet modal: clicking the handle' ) , ( ) => {
215227 test . beforeEach ( async ( { page } ) => {
216228 await page . goto ( '/src/components/modal/test/sheet' , config ) ;
@@ -285,4 +297,60 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
285297 await expect ( await modal . evaluate ( ( el : HTMLIonModalElement ) => el . getCurrentBreakpoint ( ) ) ) . toBe ( 0.75 ) ;
286298 } ) ;
287299 } ) ;
300+
301+ test . describe ( title ( 'sheet modal: accessibility' ) , ( ) => {
302+ test ( 'it should allow focus on the drag handle from outside of the modal' , async ( { page } ) => {
303+ // In this scenario, the modal is opened and has no backdrop, allowing
304+ // the background content to be focused. We need to ensure that we can
305+ // navigate to the drag handle using the keyboard and voiceover/talkback.
306+ await page . goto ( '/src/components/modal/test/sheet' , config ) ;
307+
308+ await page . setContent (
309+ `
310+ <ion-content>
311+ <button id="open-modal">Open</button>
312+ <ion-modal trigger="open-modal" initial-breakpoint="0.25">
313+ <ion-content>
314+ <ion-button id="dismiss" onclick="modal.dismiss();">Dismiss</ion-button>
315+ <ion-button id="set-breakpoint">Set breakpoint</ion-button>
316+ </ion-content>
317+ </ion-modal>
318+ </ion-content>
319+ <script>
320+ const modal = document.querySelector('ion-modal');
321+ const setBreakpointButton = document.querySelector('#set-breakpoint');
322+
323+ modal.breakpoints = [0.25, 0.5, 1];
324+ modal.handleBehavior = 'cycle';
325+ modal.backdropBreakpoint = 1;
326+ modal.backdropDismiss = false;
327+ modal.expandToScroll = false;
328+
329+ setBreakpointButton.addEventListener('click', () => {
330+ modal.setCurrentBreakpoint(0.5);
331+ });
332+ </script>
333+ ` ,
334+ config
335+ ) ;
336+
337+ const openButton = page . locator ( '#open-modal' ) ;
338+
339+ const ionModalDidPresent = await page . spyOnEvent ( 'ionModalDidPresent' ) ;
340+
341+ await openButton . click ( ) ;
342+ await ionModalDidPresent . next ( ) ;
343+
344+ const dragHandle = page . locator ( 'ion-modal .modal-handle' ) ;
345+ await expect ( dragHandle ) . toBeVisible ( ) ;
346+
347+ openButton . focus ( ) ;
348+ await expect ( openButton ) . toBeFocused ( ) ;
349+
350+ // Tab should now bring us to the drag handle
351+ await page . keyboard . press ( 'Tab' ) ;
352+
353+ await expect ( dragHandle ) . toBeFocused ( ) ;
354+ } ) ;
355+ } ) ;
288356} ) ;
0 commit comments