Skip to content

Commit ea8a22d

Browse files
committed
chore(angular): reproducing issue where modals with showBackdrop=false aren't allowing clicks through the background
1 parent 3709bba commit ea8a22d

File tree

4 files changed

+175
-0
lines changed

4 files changed

+175
-0
lines changed
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
/**
4+
* Tests for INLINE sheet modals in child routes with showBackdrop=false.
5+
*
6+
* Related issue: https://github.com/ionic-team/ionic-framework/issues/30700
7+
*
8+
* This test mimics the EXACT structure from the issue reproduction:
9+
* - PARENT component has interactive content (buttons) AND a nested ion-router-outlet
10+
* - CHILD route (rendered in that nested outlet) contains ONLY the modal
11+
* - The modal has showBackdrop=false
12+
*
13+
* The bug: when the modal opens in the child route, the buttons in the PARENT
14+
* become non-interactive even though showBackdrop=false should allow interaction.
15+
*
16+
* DOM structure:
17+
* - ion-app > ion-router-outlet (root) > PARENT (buttons + nested outlet) > ion-router-outlet > CHILD (modal only)
18+
*/
19+
test.describe('Modals: Inline Sheet in Child Route (standalone)', () => {
20+
test.beforeEach(async ({ page }) => {
21+
// Navigate to the child route - this will:
22+
// 1. Render the parent with buttons
23+
// 2. Render the child in the nested outlet, which immediately opens the modal
24+
await page.goto('/standalone/modal-child-route/child');
25+
});
26+
27+
test('should render parent content and child modal', async ({ page }) => {
28+
// Verify the PARENT content is visible (buttons are in parent, not child)
29+
await expect(page.locator('#increment-btn')).toBeVisible();
30+
await expect(page.locator('#decrement-btn')).toBeVisible();
31+
await expect(page.locator('#background-action-count')).toHaveText('0');
32+
33+
// Verify the modal from CHILD route is visible (opens immediately with isOpen=true)
34+
await expect(page.locator('ion-modal.show-modal')).toBeVisible();
35+
await expect(page.locator('#modal-content-loaded')).toBeVisible();
36+
});
37+
38+
test('should allow interacting with PARENT content while modal (showBackdrop: false) is open in CHILD route', async ({ page }) => {
39+
// Modal should already be open (isOpen=true in child component)
40+
await expect(page.locator('ion-modal.show-modal')).toBeVisible();
41+
await expect(page.locator('#modal-content-loaded')).toBeVisible();
42+
43+
// Click the increment button in the PARENT - this should work with showBackdrop=false
44+
// This is the KEY test - it FAILS due to issue #30700
45+
await page.locator('#increment-btn').click();
46+
47+
// Verify the click was registered
48+
await expect(page.locator('#background-action-count')).toHaveText('1');
49+
});
50+
51+
test('should allow multiple interactions with PARENT content while modal is open', async ({ page }) => {
52+
// Modal should already be open
53+
await expect(page.locator('ion-modal.show-modal')).toBeVisible();
54+
55+
// Click increment multiple times
56+
await page.locator('#increment-btn').click();
57+
await page.locator('#increment-btn').click();
58+
await expect(page.locator('#background-action-count')).toHaveText('2');
59+
60+
// Click decrement
61+
await page.locator('#decrement-btn').click();
62+
await expect(page.locator('#background-action-count')).toHaveText('1');
63+
});
64+
});

packages/angular/test/base/src/app/standalone/app-standalone/app.routes.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,14 @@ export const routes: Routes = [
1313
{ path: 'modal', loadComponent: () => import('../modal/modal.component').then(c => c.ModalComponent) },
1414
{ path: 'modal-sheet-inline', loadComponent: () => import('../modal-sheet-inline/modal-sheet-inline.component').then(c => c.ModalSheetInlineComponent) },
1515
{ path: 'modal-dynamic-wrapper', loadComponent: () => import('../modal-dynamic-wrapper/modal-dynamic-wrapper.component').then(c => c.ModalDynamicWrapperComponent) },
16+
{ path: 'modal-child-route', redirectTo: '/standalone/modal-child-route/child', pathMatch: 'full' },
17+
{
18+
path: 'modal-child-route',
19+
loadComponent: () => import('../modal-child-route/modal-child-route-parent.component').then(c => c.ModalChildRouteParentComponent),
20+
children: [
21+
{ path: 'child', loadComponent: () => import('../modal-child-route/modal-child-route-child.component').then(c => c.ModalChildRouteChildComponent) },
22+
]
23+
},
1624
{ path: 'programmatic-modal', loadComponent: () => import('../programmatic-modal/programmatic-modal.component').then(c => c.ProgrammaticModalComponent) },
1725
{ path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(c => c.RouterOutletComponent) },
1826
{ path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(c => c.BackButtonComponent) },
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
import { CommonModule } from '@angular/common';
2+
import { Component } from '@angular/core';
3+
import { IonButton, IonContent, IonHeader, IonModal, IonTitle, IonToolbar } from '@ionic/angular/standalone';
4+
5+
/**
6+
* Child component that ONLY contains the modal.
7+
*
8+
* This mimics the EXACT structure from issue #30700 reproduction where:
9+
* - The PARENT page has the interactive content (buttons)
10+
* - The CHILD route (this component) contains ONLY the modal
11+
*
12+
* The structure is:
13+
* - ion-app > ion-router-outlet (root) > PARENT (has buttons + nested outlet) > ion-router-outlet (nested) > THIS COMPONENT (has modal)
14+
*
15+
* The bug is: when this modal opens, the buttons in the PARENT become non-interactive
16+
* even though showBackdrop=false should allow background interaction.
17+
*
18+
* Related issue: https://github.com/ionic-team/ionic-framework/issues/30700
19+
*/
20+
@Component({
21+
selector: 'app-modal-child-route-child',
22+
template: `
23+
<!-- This child route component contains ONLY the modal, which opens immediately -->
24+
<!-- The interactive content (buttons) are in the PARENT component -->
25+
26+
<!-- INLINE modal with showBackdrop=false - parent content should be interactive -->
27+
<ion-modal
28+
[isOpen]="true"
29+
[breakpoints]="[0.2, 0.5, 0.7]"
30+
[initialBreakpoint]="0.5"
31+
[showBackdrop]="false"
32+
>
33+
<ng-template>
34+
<ion-header>
35+
<ion-toolbar>
36+
<ion-title>Modal in Child Route</ion-title>
37+
</ion-toolbar>
38+
</ion-header>
39+
<ion-content class="ion-padding">
40+
<p id="modal-content-loaded">Modal content loaded in child route</p>
41+
<p>The +/- buttons in the parent should be clickable!</p>
42+
</ion-content>
43+
</ng-template>
44+
</ion-modal>
45+
`,
46+
standalone: true,
47+
imports: [CommonModule, IonButton, IonContent, IonHeader, IonModal, IonTitle, IonToolbar],
48+
})
49+
export class ModalChildRouteChildComponent {}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
import { Component } from '@angular/core';
2+
import { IonButton, IonContent, IonHeader, IonRouterOutlet, IonTitle, IonToolbar } from '@ionic/angular/standalone';
3+
4+
/**
5+
* Parent component that contains:
6+
* 1. Interactive content (buttons) that should remain clickable
7+
* 2. A nested ion-router-outlet where the child route with the modal will render
8+
*
9+
* This mimics the EXACT structure from issue #30700 reproduction:
10+
* - Parent page has buttons (+/-)
11+
* - Parent page has a nested IonRouterOutlet
12+
* - Child route (rendered in that outlet) contains ONLY the modal
13+
*
14+
* The bug is: when the modal opens in the child route, the buttons in THIS
15+
* parent component become non-interactive even with showBackdrop=false.
16+
*
17+
* Related issue: https://github.com/ionic-team/ionic-framework/issues/30700
18+
*/
19+
@Component({
20+
selector: 'app-modal-child-route-parent',
21+
template: `
22+
<ion-header>
23+
<ion-toolbar>
24+
<ion-title>Parent Page with Nested Route</ion-title>
25+
</ion-toolbar>
26+
</ion-header>
27+
<ion-content class="ion-padding">
28+
<!-- These buttons are in the PARENT and should be clickable when modal is open in CHILD -->
29+
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 20px;">
30+
<ion-button id="decrement-btn" (click)="decrement()">-</ion-button>
31+
<p id="background-action-count">{{ count }}</p>
32+
<ion-button id="increment-btn" (click)="increment()">+</ion-button>
33+
</div>
34+
35+
<p>The modal will be rendered from a child route below:</p>
36+
37+
<!-- Nested router outlet - child route with modal renders here -->
38+
<ion-router-outlet id="child-router-outlet"></ion-router-outlet>
39+
</ion-content>
40+
`,
41+
standalone: true,
42+
imports: [IonButton, IonContent, IonHeader, IonRouterOutlet, IonTitle, IonToolbar],
43+
})
44+
export class ModalChildRouteParentComponent {
45+
count = 0;
46+
47+
increment() {
48+
this.count++;
49+
}
50+
51+
decrement() {
52+
this.count--;
53+
}
54+
}

0 commit comments

Comments
 (0)