Skip to content

Commit 8d743ff

Browse files
committed
Merge branch 'main' into test/migrate-angular-playwright
2 parents 5d8f733 + a2e803a commit 8d743ff

9 files changed

Lines changed: 196 additions & 12 deletions

File tree

core/package-lock.json

Lines changed: 6 additions & 6 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

core/src/components/datetime/test/custom/datetime.e2e.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
55
test.describe(title('datetime: custom'), () => {
66
test.beforeEach(async ({ page }) => {
77
await page.goto(`/src/components/datetime/test/custom`, config);
8+
9+
await page.locator('.datetime-ready').last().waitFor();
810
});
911

1012
test('should allow styling wheel style datetimes', async ({ page }) => {
@@ -30,6 +32,13 @@ configs({ directions: ['ltr'] }).forEach(({ title, screenshot, config }) => {
3032
test('should allow styling calendar days in grid style datetimes', async ({ page }) => {
3133
const datetime = page.locator('#custom-calendar-days');
3234

35+
// Wait for calendar days to be rendered
36+
await page.waitForFunction(() => {
37+
const datetime = document.querySelector('#custom-calendar-days');
38+
const calendarDays = datetime?.shadowRoot?.querySelectorAll('.calendar-day');
39+
return calendarDays && calendarDays.length > 0;
40+
});
41+
3342
await expect(datetime).toHaveScreenshot(screenshot(`datetime-custom-calendar-days`));
3443
});
3544
});

core/src/components/datetime/test/custom/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ <h2>Grid Style</h2>
164164
const customDatetime = document.querySelector('#custom-calendar-days');
165165

166166
// Mock the current day to always have the same screenshots
167-
const mockToday = '2023-06-10T16:22';
167+
const mockToday = '2023-06-10T16:22:00.000Z';
168168
Date = class extends Date {
169169
constructor(...args) {
170170
if (args.length === 0) {

core/src/components/datetime/test/datetime.e2e.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,23 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
2222

2323
await expect(monthYearToggle).toContainText('January 2022');
2424

25+
// Click to open the picker
2526
await monthYearToggle.click();
2627
await page.waitForChanges();
2728

28-
// February
29-
await monthColumnItems.nth(1).click();
29+
// Wait for the picker to be open
30+
await page.locator('.month-year-picker-open').waitFor();
31+
32+
// Wait a bit for the picker to fully load
33+
await page.waitForTimeout(200);
34+
35+
const ionChange = await page.spyOnEvent('ionChange');
36+
37+
// Click on February
38+
await monthColumnItems.filter({ hasText: 'February' }).click();
39+
40+
// Wait for changes
41+
await ionChange.next();
3042
await page.waitForChanges();
3143

3244
await expect(monthYearToggle).toContainText('February 2022');
@@ -38,13 +50,23 @@ configs({ modes: ['md'], directions: ['ltr'] }).forEach(({ title, config }) => {
3850
const datetime = page.locator('ion-datetime');
3951
const ionChange = await page.spyOnEvent('ionChange');
4052

53+
// Click to open the picker
4154
await monthYearToggle.click();
4255
await page.waitForChanges();
4356

44-
// February
45-
await monthColumnItems.nth(1).click();
57+
// Wait for the picker to be open
58+
await page.locator('.month-year-picker-open').waitFor();
59+
60+
// Wait a bit for the picker to fully load
61+
await page.waitForTimeout(200);
62+
63+
// Click on February
64+
await monthColumnItems.filter({ hasText: 'February' }).click();
4665

66+
// Wait for changes
4767
await ionChange.next();
68+
await page.waitForChanges();
69+
4870
await expect(ionChange).toHaveReceivedEventTimes(1);
4971
await expect(datetime).toHaveJSProperty('value', '2022-02-28');
5072
});

core/src/components/input-otp/test/separators/input-otp.e2e.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,8 @@ configs({ modes: ['ios'], directions: ['ltr'] }).forEach(({ title, config }) =>
9595
el.separators = [2, 3];
9696
});
9797

98+
await page.waitForChanges();
99+
98100
await expect(await hasSeparatorAfter(page, 0)).toBe(false);
99101
await expect(await hasSeparatorAfter(page, 1)).toBe(true);
100102
await expect(await hasSeparatorAfter(page, 2)).toBe(true);

core/src/components/tabs/tabs.tsx

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,27 @@ export class Tabs implements NavOutlet {
6868
componentWillRender() {
6969
const tabBar = this.el.querySelector('ion-tab-bar');
7070
if (tabBar) {
71-
const tab = this.selectedTab ? this.selectedTab.tab : undefined;
71+
let tab = this.selectedTab ? this.selectedTab.tab : undefined;
72+
73+
// Fallback: if no selectedTab is set but we're using router mode,
74+
// determine the active tab from the current URL. This works around
75+
// timing issues in React Router integration where setRouteId may not
76+
// be called in time for the initial render.
77+
// TODO(FW-6724): Remove this with React Router upgrade
78+
if (!tab && this.useRouter && typeof window !== 'undefined') {
79+
const currentPath = window.location.pathname;
80+
const tabButtons = this.el.querySelectorAll('ion-tab-button');
81+
82+
// Look for a tab button that matches the current path pattern
83+
for (const tabButton of tabButtons) {
84+
const tabId = tabButton.getAttribute('tab');
85+
if (tabId && currentPath.includes(tabId)) {
86+
tab = tabId;
87+
break;
88+
}
89+
}
90+
}
91+
7292
tabBar.selectedTab = tab;
7393
}
7494
}

packages/react/test/base/src/App.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import Main from './pages/Main';
2727
import Tabs from './pages/Tabs';
2828
import TabsBasic from './pages/TabsBasic';
2929
import NavComponent from './pages/navigation/NavComponent';
30+
import TabsDirectNavigation from './pages/TabsDirectNavigation';
3031
import IonModalConditional from './pages/overlay-components/IonModalConditional';
3132
import IonModalConditionalSibling from './pages/overlay-components/IonModalConditionalSibling';
3233
import IonModalDatetimeButton from './pages/overlay-components/IonModalDatetimeButton';
@@ -63,6 +64,7 @@ const App: React.FC = () => (
6364
<Route path="/navigation" component={NavComponent} />
6465
<Route path="/tabs" component={Tabs} />
6566
<Route path="/tabs-basic" component={TabsBasic} />
67+
<Route path="/tabs-direct-navigation" component={TabsDirectNavigation} />
6668
<Route path="/icons" component={Icons} />
6769
<Route path="/inputs" component={Inputs} />
6870
</IonRouterOutlet>
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { IonContent, IonHeader, IonIcon, IonLabel, IonPage, IonRouterOutlet, IonTabBar, IonTabButton, IonTabs, IonTitle, IonToolbar } from '@ionic/react';
2+
import { homeOutline, radioOutline, libraryOutline, searchOutline } from 'ionicons/icons';
3+
import React from 'react';
4+
import { Route, Redirect } from 'react-router-dom';
5+
6+
const HomePage: React.FC = () => (
7+
<IonPage>
8+
<IonHeader>
9+
<IonToolbar>
10+
<IonTitle>Home</IonTitle>
11+
</IonToolbar>
12+
</IonHeader>
13+
<IonContent>
14+
<div data-testid="home-content">Home Content</div>
15+
</IonContent>
16+
</IonPage>
17+
);
18+
19+
const RadioPage: React.FC = () => (
20+
<IonPage>
21+
<IonHeader>
22+
<IonToolbar>
23+
<IonTitle>Radio</IonTitle>
24+
</IonToolbar>
25+
</IonHeader>
26+
<IonContent>
27+
<div data-testid="radio-content">Radio Content</div>
28+
</IonContent>
29+
</IonPage>
30+
);
31+
32+
const LibraryPage: React.FC = () => (
33+
<IonPage>
34+
<IonHeader>
35+
<IonToolbar>
36+
<IonTitle>Library</IonTitle>
37+
</IonToolbar>
38+
</IonHeader>
39+
<IonContent>
40+
<div data-testid="library-content">Library Content</div>
41+
</IonContent>
42+
</IonPage>
43+
);
44+
45+
const SearchPage: React.FC = () => (
46+
<IonPage>
47+
<IonHeader>
48+
<IonToolbar>
49+
<IonTitle>Search</IonTitle>
50+
</IonToolbar>
51+
</IonHeader>
52+
<IonContent>
53+
<div data-testid="search-content">Search Content</div>
54+
</IonContent>
55+
</IonPage>
56+
);
57+
58+
const TabsDirectNavigation: React.FC = () => {
59+
return (
60+
<IonTabs data-testid="tabs-direct-navigation">
61+
<IonRouterOutlet>
62+
<Redirect exact path="/tabs-direct-navigation" to="/tabs-direct-navigation/home" />
63+
<Route path="/tabs-direct-navigation/home" render={() => <HomePage />} exact={true} />
64+
<Route path="/tabs-direct-navigation/radio" render={() => <RadioPage />} exact={true} />
65+
<Route path="/tabs-direct-navigation/library" render={() => <LibraryPage />} exact={true} />
66+
<Route path="/tabs-direct-navigation/search" render={() => <SearchPage />} exact={true} />
67+
</IonRouterOutlet>
68+
69+
<IonTabBar slot="bottom" data-testid="tab-bar">
70+
<IonTabButton tab="home" href="/tabs-direct-navigation/home" data-testid="home-tab">
71+
<IonIcon icon={homeOutline}></IonIcon>
72+
<IonLabel>Home</IonLabel>
73+
</IonTabButton>
74+
75+
<IonTabButton tab="radio" href="/tabs-direct-navigation/radio" data-testid="radio-tab">
76+
<IonIcon icon={radioOutline}></IonIcon>
77+
<IonLabel>Radio</IonLabel>
78+
</IonTabButton>
79+
80+
<IonTabButton tab="library" href="/tabs-direct-navigation/library" data-testid="library-tab">
81+
<IonIcon icon={libraryOutline}></IonIcon>
82+
<IonLabel>Library</IonLabel>
83+
</IonTabButton>
84+
85+
<IonTabButton tab="search" href="/tabs-direct-navigation/search" data-testid="search-tab">
86+
<IonIcon icon={searchOutline}></IonIcon>
87+
<IonLabel>Search</IonLabel>
88+
</IonTabButton>
89+
</IonTabBar>
90+
</IonTabs>
91+
);
92+
};
93+
94+
export default TabsDirectNavigation;
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
describe('Tabs Direct Navigation', () => {
2+
it('should select the correct tab when navigating directly to home route', () => {
3+
cy.visit('/tabs-direct-navigation/home');
4+
cy.get('[data-testid="home-tab"]').should('have.class', 'tab-selected');
5+
cy.get('[data-testid="home-content"]').should('be.visible');
6+
});
7+
8+
it('should select the correct tab when navigating directly to radio route', () => {
9+
cy.visit('/tabs-direct-navigation/radio');
10+
cy.get('[data-testid="radio-tab"]').should('have.class', 'tab-selected');
11+
cy.get('[data-testid="radio-content"]').should('be.visible');
12+
});
13+
14+
it('should select the correct tab when navigating directly to library route', () => {
15+
cy.visit('/tabs-direct-navigation/library');
16+
cy.get('[data-testid="library-tab"]').should('have.class', 'tab-selected');
17+
cy.get('[data-testid="library-content"]').should('be.visible');
18+
});
19+
20+
it('should select the correct tab when navigating directly to search route', () => {
21+
cy.visit('/tabs-direct-navigation/search');
22+
cy.get('[data-testid="search-tab"]').should('have.class', 'tab-selected');
23+
cy.get('[data-testid="search-content"]').should('be.visible');
24+
});
25+
26+
it('should update tab selection when navigating between tabs', () => {
27+
cy.visit('/tabs-direct-navigation/home');
28+
cy.get('[data-testid="home-tab"]').should('have.class', 'tab-selected');
29+
30+
cy.get('[data-testid="radio-tab"]').click();
31+
cy.get('[data-testid="radio-tab"]').should('have.class', 'tab-selected');
32+
cy.get('[data-testid="home-tab"]').should('not.have.class', 'tab-selected');
33+
cy.get('[data-testid="radio-content"]').should('be.visible');
34+
});
35+
});

0 commit comments

Comments
 (0)