Skip to content

Commit bbe4154

Browse files
committed
test(angular): copying new lazy tests to standalone, fixing react route tab issue
1 parent 03435a3 commit bbe4154

File tree

11 files changed

+398
-2
lines changed

11 files changed

+398
-2
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
test.describe('Modals: Dynamic Wrapper (standalone)', () => {
4+
test.beforeEach(async ({ page }) => {
5+
await page.goto('/standalone/modal-dynamic-wrapper');
6+
});
7+
8+
test('should render dynamic component inside modal', async ({ page }) => {
9+
await page.locator('#open-dynamic-modal').click();
10+
11+
await expect(page.locator('ion-modal')).toBeVisible();
12+
await expect(page.locator('#dynamic-component-loaded')).toBeVisible();
13+
});
14+
15+
test('should allow interacting with background content while sheet is open', async ({ page }) => {
16+
await page.locator('#open-dynamic-modal').click();
17+
18+
await expect(page.locator('ion-modal')).toBeVisible();
19+
20+
await page.locator('#background-action').click();
21+
22+
await expect(page.locator('#background-action-count')).toHaveText('1');
23+
});
24+
25+
test('should prevent interacting with background content when focus is trapped', async ({ page }) => {
26+
await page.locator('#open-focused-modal').click();
27+
28+
await expect(page.locator('ion-modal')).toBeVisible();
29+
30+
// Attempt to click the background button via coordinates; click should be intercepted by backdrop
31+
const box = await page.locator('#background-action').boundingBox();
32+
if (box) {
33+
await page.mouse.click(box.x + box.width / 2, box.y + box.height / 2);
34+
}
35+
36+
await expect(page.locator('#background-action-count')).toHaveText('0');
37+
});
38+
});
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
import { expect, test } from '@playwright/test';
2+
3+
test.describe('Modals: Inline Sheet (standalone)', () => {
4+
test.beforeEach(async ({ page }) => {
5+
await page.goto('/standalone/modal-sheet-inline');
6+
});
7+
8+
test('should open inline sheet modal', async ({ page }) => {
9+
await page.locator('#present-inline-sheet-modal').click();
10+
11+
await expect(page.locator('ion-modal')).toBeVisible();
12+
await expect(page.locator('#current-breakpoint')).toHaveText('0.2');
13+
await expect(page.locator('ion-modal ion-item')).toHaveCount(4);
14+
});
15+
16+
test('should expand to 0.75 breakpoint when searchbar is clicked', async ({ page }) => {
17+
await page.locator('#present-inline-sheet-modal').click();
18+
await expect(page.locator('#current-breakpoint')).toHaveText('0.2');
19+
20+
await page.locator('ion-modal ion-searchbar').click();
21+
22+
await expect(page.locator('#current-breakpoint')).toHaveText('0.75');
23+
});
24+
25+
test('should allow interacting with background content while sheet is open', async ({ page }) => {
26+
await page.locator('#present-inline-sheet-modal').click();
27+
28+
await expect(page.locator('ion-modal')).toBeVisible();
29+
30+
await page.locator('#background-action').click();
31+
32+
await expect(page.locator('#background-action-count')).toHaveText('1');
33+
});
34+
});

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ export const routes: Routes = [
1111
{ path: 'action-sheet-controller', loadComponent: () => import('../action-sheet-controller/action-sheet-controller.component').then(c => c.ActionSheetControllerComponent) },
1212
{ path: 'popover', loadComponent: () => import('../popover/popover.component').then(c => c.PopoverComponent) },
1313
{ path: 'modal', loadComponent: () => import('../modal/modal.component').then(c => c.ModalComponent) },
14+
{ path: 'modal-sheet-inline', loadComponent: () => import('../modal-sheet-inline/modal-sheet-inline.component').then(c => c.ModalSheetInlineComponent) },
15+
{ path: 'modal-dynamic-wrapper', loadComponent: () => import('../modal-dynamic-wrapper/modal-dynamic-wrapper.component').then(c => c.ModalDynamicWrapperComponent) },
1416
{ path: 'programmatic-modal', loadComponent: () => import('../programmatic-modal/programmatic-modal.component').then(c => c.ProgrammaticModalComponent) },
1517
{ path: 'router-outlet', loadComponent: () => import('../router-outlet/router-outlet.component').then(c => c.RouterOutletComponent) },
1618
{ path: 'back-button', loadComponent: () => import('../back-button/back-button.component').then(c => c.BackButtonComponent) },

packages/angular/test/base/src/app/standalone/home-page/home-page.component.html

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,16 @@
9090
Modal Test
9191
</ion-label>
9292
</ion-item>
93+
<ion-item routerLink="/standalone/modal-sheet-inline">
94+
<ion-label>
95+
Modal Sheet Inline Test
96+
</ion-label>
97+
</ion-item>
98+
<ion-item routerLink="/standalone/modal-dynamic-wrapper">
99+
<ion-label>
100+
Modal Dynamic Wrapper Test
101+
</ion-label>
102+
</ion-item>
93103
<ion-item routerLink="/standalone/programmatic-modal">
94104
<ion-label>
95105
Programmatic Modal Test
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import { Component, ComponentRef, Input, OnDestroy, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
2+
import { IonContent } from '@ionic/angular/standalone';
3+
4+
@Component({
5+
selector: 'app-dynamic-component-wrapper',
6+
template: `
7+
<ion-content>
8+
<ng-container #container></ng-container>
9+
</ion-content>
10+
`,
11+
standalone: true,
12+
imports: [IonContent],
13+
})
14+
export class DynamicComponentWrapperComponent implements OnInit, OnDestroy {
15+
@Input() componentRef?: ComponentRef<unknown>;
16+
@ViewChild('container', { read: ViewContainerRef, static: true }) container!: ViewContainerRef;
17+
18+
ngOnInit(): void {
19+
if (this.componentRef) {
20+
this.container.insert(this.componentRef.hostView);
21+
}
22+
}
23+
24+
ngOnDestroy(): void {
25+
this.componentRef?.destroy();
26+
}
27+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { Component, EventEmitter, Output } from '@angular/core';
2+
import {
3+
IonButton,
4+
IonContent,
5+
IonHeader,
6+
IonTitle,
7+
IonToolbar,
8+
} from '@ionic/angular/standalone';
9+
10+
@Component({
11+
selector: 'app-dynamic-modal-content',
12+
template: `
13+
<ion-header>
14+
<ion-toolbar>
15+
<ion-title>Dynamic Sheet Content</ion-title>
16+
</ion-toolbar>
17+
</ion-header>
18+
<ion-content class="ion-padding">
19+
<p id="dynamic-component-loaded">Dynamic component rendered inside wrapper.</p>
20+
<ion-button id="dismiss-dynamic-modal" (click)="dismiss.emit()">Close</ion-button>
21+
</ion-content>
22+
`,
23+
standalone: true,
24+
imports: [IonButton, IonContent, IonHeader, IonTitle, IonToolbar],
25+
})
26+
export class DynamicModalContentComponent {
27+
@Output() dismiss = new EventEmitter<void>();
28+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<ion-button id="open-dynamic-modal" (click)="openModal()">Open Dynamic Sheet Modal</ion-button>
2+
<ion-button id="open-focused-modal" color="primary" (click)="openFocusedModal()">Open Focus-Trapped Sheet Modal</ion-button>
3+
<ion-button id="background-action" (click)="onBackgroundActionClick()">Background Action</ion-button>
4+
<p>
5+
Background action count: <span id="background-action-count">{{ backgroundActionCount }}</span>
6+
</p>
7+
8+
<ng-template #modalHost></ng-template>
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
import { CommonModule } from '@angular/common';
2+
import { Component, ComponentRef, OnDestroy, ViewChild, ViewContainerRef } from '@angular/core';
3+
import { IonButton, ModalController } from '@ionic/angular/standalone';
4+
5+
import { DynamicComponentWrapperComponent } from './dynamic-component-wrapper.component';
6+
import { DynamicModalContentComponent } from './dynamic-modal-content.component';
7+
8+
@Component({
9+
selector: 'app-modal-dynamic-wrapper',
10+
templateUrl: './modal-dynamic-wrapper.component.html',
11+
standalone: true,
12+
imports: [CommonModule, IonButton],
13+
})
14+
export class ModalDynamicWrapperComponent implements OnDestroy {
15+
@ViewChild('modalHost', { read: ViewContainerRef, static: true }) modalHost!: ViewContainerRef;
16+
17+
backgroundActionCount = 0;
18+
19+
private currentModal?: HTMLIonModalElement;
20+
private currentComponentRef?: ComponentRef<DynamicModalContentComponent>;
21+
22+
constructor(private modalCtrl: ModalController) {}
23+
24+
async openModal() {
25+
await this.closeModal();
26+
27+
const componentRef = this.modalHost.createComponent(DynamicModalContentComponent);
28+
this.modalHost.detach();
29+
componentRef.instance.dismiss.subscribe(() => this.closeModal());
30+
31+
this.currentComponentRef = componentRef;
32+
33+
const modal = await this.modalCtrl.create({
34+
component: DynamicComponentWrapperComponent,
35+
componentProps: {
36+
componentRef,
37+
},
38+
breakpoints: [0, 0.2, 0.75, 1],
39+
initialBreakpoint: 0.2,
40+
backdropDismiss: false,
41+
focusTrap: false,
42+
handleBehavior: 'cycle',
43+
});
44+
45+
this.currentModal = modal;
46+
47+
modal.onWillDismiss().then(() => this.destroyComponent());
48+
49+
await modal.present();
50+
}
51+
52+
async openFocusedModal() {
53+
await this.closeModal();
54+
55+
const componentRef = this.modalHost.createComponent(DynamicModalContentComponent);
56+
this.modalHost.detach();
57+
componentRef.instance.dismiss.subscribe(() => this.closeModal());
58+
59+
this.currentComponentRef = componentRef;
60+
61+
const modal = await this.modalCtrl.create({
62+
component: DynamicComponentWrapperComponent,
63+
componentProps: {
64+
componentRef,
65+
},
66+
breakpoints: [0, 0.25, 0.5, 0.75, 1],
67+
initialBreakpoint: 0.5,
68+
backdropDismiss: false,
69+
focusTrap: true,
70+
handleBehavior: 'cycle',
71+
});
72+
73+
this.currentModal = modal;
74+
75+
modal.onWillDismiss().then(() => this.destroyComponent());
76+
77+
await modal.present();
78+
}
79+
80+
async closeModal() {
81+
if (this.currentModal) {
82+
await this.currentModal.dismiss();
83+
this.currentModal = undefined;
84+
}
85+
86+
this.destroyComponent();
87+
}
88+
89+
private destroyComponent() {
90+
if (this.currentComponentRef) {
91+
this.currentComponentRef.destroy();
92+
this.currentComponentRef = undefined;
93+
}
94+
}
95+
96+
onBackgroundActionClick() {
97+
this.backgroundActionCount++;
98+
}
99+
100+
ngOnDestroy(): void {
101+
this.destroyComponent();
102+
}
103+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
<ion-button id="present-inline-sheet-modal" (click)="presentInlineSheetModal()">
2+
Present Inline Sheet Modal
3+
</ion-button>
4+
5+
<p>
6+
Current breakpoint: <span id="current-breakpoint">{{ currentBreakpoint }}</span>
7+
</p>
8+
9+
<ion-button id="background-action" (click)="onBackgroundActionClick()">
10+
Background Action
11+
</ion-button>
12+
13+
<p>
14+
Background action count: <span id="background-action-count">{{ backgroundActionCount }}</span>
15+
</p>
16+
17+
<ion-modal
18+
#inlineSheetModal
19+
mode="md"
20+
[isOpen]="isSheetOpen"
21+
[initialBreakpoint]="0.2"
22+
[breakpoints]="breakpoints"
23+
[backdropDismiss]="false"
24+
[backdropBreakpoint]="0.5"
25+
[focusTrap]="false"
26+
handleBehavior="cycle"
27+
(ionBreakpointDidChange)="onSheetBreakpointDidChange($event)"
28+
(didDismiss)="onSheetDidDismiss()"
29+
>
30+
<ng-template>
31+
<ion-content>
32+
<ion-searchbar placeholder="Search" (click)="expandInlineSheet()"></ion-searchbar>
33+
<ion-list>
34+
<ion-item *ngFor="let contact of contacts">
35+
<ion-avatar slot="start">
36+
<ion-img [src]="contact.avatar"></ion-img>
37+
</ion-avatar>
38+
<ion-label>
39+
<h2>{{ contact.name }}</h2>
40+
<p>{{ contact.title }}</p>
41+
</ion-label>
42+
</ion-item>
43+
</ion-list>
44+
</ion-content>
45+
</ng-template>
46+
</ion-modal>

0 commit comments

Comments
 (0)