@@ -191,5 +191,83 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
191191 // Verify the container was actually removed
192192 await expect ( modalContainer ) . not . toBeAttached ( ) ;
193193 } ) ;
194+
195+ test ( 'it should dismiss modals when top-level ancestor is removed' , async ( { page } ) => {
196+ // We need to make sure we can close a modal when a much higher
197+ // element is removed from the DOM. This will be a common
198+ // use case in frameworks like Angular and React, where an entire
199+ // page container for much more than the modal might be swapped out.
200+ await page . setContent (
201+ `
202+ <ion-app>
203+ <div class="ion-page">
204+ <ion-header>
205+ <ion-toolbar>
206+ <ion-title>Top Level Removal Test</ion-title>
207+ </ion-toolbar>
208+ </ion-header>
209+ <ion-content class="ion-padding">
210+ <div id="top-level-container">
211+ <div id="nested-container">
212+ <button id="open-nested-modal">Open Nested Modal</button>
213+ <ion-modal id="nested-modal">
214+ <ion-header>
215+ <ion-toolbar>
216+ <ion-title>Nested Modal</ion-title>
217+ </ion-toolbar>
218+ </ion-header>
219+ <ion-content class="ion-padding">
220+ <p>This modal's original parent is deeply nested</p>
221+ <button id="remove-top-level">Remove Top Level Container</button>
222+ </ion-content>
223+ </ion-modal>
224+ </div>
225+ </div>
226+ </ion-content>
227+ </div>
228+ </ion-app>
229+
230+ <script>
231+ const nestedModal = document.querySelector('#nested-modal');
232+ nestedModal.presentingElement = document.querySelector('.ion-page');
233+
234+ document.getElementById('open-nested-modal').addEventListener('click', () => {
235+ nestedModal.isOpen = true;
236+ });
237+
238+ document.getElementById('remove-top-level').addEventListener('click', () => {
239+ document.querySelector('#top-level-container').remove();
240+ });
241+ </script>
242+ ` ,
243+ config
244+ ) ;
245+
246+ const ionModalDidPresent = await page . spyOnEvent ( 'ionModalDidPresent' ) ;
247+ const ionModalDidDismiss = await page . spyOnEvent ( 'ionModalDidDismiss' ) ;
248+
249+ const nestedModal = page . locator ( '#nested-modal' ) ;
250+ const topLevelContainer = page . locator ( '#top-level-container' ) ;
251+
252+ // Open the nested modal
253+ await page . click ( '#open-nested-modal' ) ;
254+ await ionModalDidPresent . next ( ) ;
255+ await expect ( nestedModal ) . toBeVisible ( ) ;
256+
257+ // Remove the top-level container
258+ await page . click ( '#remove-top-level' ) ;
259+
260+ // Wait for modal to be dismissed
261+ const dismissEvent = await ionModalDidDismiss . next ( ) ;
262+
263+ // Verify the modal was dismissed with the correct role
264+ expect ( dismissEvent . detail . role ) . toBe ( 'parent-removed' ) ;
265+
266+ // Verify the modal is no longer visible
267+ await expect ( nestedModal ) . toBeHidden ( ) ;
268+
269+ // Verify the container was actually removed
270+ await expect ( topLevelContainer ) . not . toBeAttached ( ) ;
271+ } ) ;
194272 } ) ;
195273} ) ;
0 commit comments