@@ -191,5 +191,83 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
191
191
// Verify the container was actually removed
192
192
await expect ( modalContainer ) . not . toBeAttached ( ) ;
193
193
} ) ;
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
+ } ) ;
194
272
} ) ;
195
273
} ) ;
0 commit comments