Skip to content

Commit a204041

Browse files
committed
fix: add missing testIDs and sync lockfile
- Add testIDs to Program tab bar and tabs for E2E testing - Add testIDPrefix prop to PlaceholderScreen component - Add E2E page object and tests for Program navigation - Sync pnpm-lock.yaml with package.json
1 parent 145b528 commit a204041

6 files changed

Lines changed: 370 additions & 20 deletions

File tree

app/(app)/(tabs)/program/_layout.tsx

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export default function ProgramLayout(): React.ReactElement {
6868
</View>
6969

7070
{/* Top Tab Bar */}
71-
<View style={styles.tabBar}>
71+
<View style={styles.tabBar} testID="program-tab-bar">
7272
{TAB_ITEMS.map((tab) => {
7373
const isActive = activeTab === tab.name;
7474
const Icon = tab.icon;
@@ -77,6 +77,7 @@ export default function ProgramLayout(): React.ReactElement {
7777
key={tab.name}
7878
style={[styles.tab, isActive && styles.tabActive]}
7979
onPress={() => handleTabPress(tab.name)}
80+
testID={`program-tab-${tab.name}`}
8081
>
8182
<Icon size={18} color={isActive ? theme.primary : theme.textSecondary} />
8283
<Text style={[styles.tabLabel, isActive && styles.tabLabelActive]}>

components/program/PlaceholderScreen.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import { useTabBarPadding } from '@/hooks/useTabBarPadding';
1212
interface PlaceholderScreenProps {
1313
title: string;
1414
subtitle?: string;
15+
/** Optional testID prefix for E2E testing. Defaults to lowercase title. */
16+
testIDPrefix?: string;
1517
}
1618

1719
// =============================================================================
@@ -23,16 +25,22 @@ interface PlaceholderScreenProps {
2325
export default function PlaceholderScreen({
2426
title,
2527
subtitle = 'Coming soon',
28+
testIDPrefix,
2629
}: PlaceholderScreenProps) {
2730
const { theme } = useTheme();
2831
const tabBarHeight = useTabBarPadding();
2932
const styles = createStyles(theme);
33+
const prefix = testIDPrefix ?? title.toLowerCase().replace(/\s+/g, '-');
3034

3135
return (
32-
<View style={[styles.container, { paddingBottom: tabBarHeight }]}>
36+
<View style={[styles.container, { paddingBottom: tabBarHeight }]} testID={`${prefix}-screen`}>
3337
<View style={styles.content}>
34-
<Text style={styles.title}>{title}</Text>
35-
<Text style={styles.subtitle}>{subtitle}</Text>
38+
<Text style={styles.title} testID={`${prefix}-title`}>
39+
{title}
40+
</Text>
41+
<Text style={styles.subtitle} testID={`${prefix}-subtitle`}>
42+
{subtitle}
43+
</Text>
3644
</View>
3745
</View>
3846
);

e2e/pages/program.page.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { Page, expect, Locator } from '@playwright/test';
2+
import { BasePage } from './base.page';
3+
4+
export class ProgramPage extends BasePage {
5+
readonly tabBar: Locator;
6+
readonly stepsTab: Locator;
7+
readonly dailyTab: Locator;
8+
readonly prayersTab: Locator;
9+
readonly literatureTab: Locator;
10+
readonly meetingsTab: Locator;
11+
12+
constructor(page: Page) {
13+
super(page);
14+
this.tabBar = page.getByTestId('program-tab-bar');
15+
this.stepsTab = page.getByTestId('program-tab-steps');
16+
this.dailyTab = page.getByTestId('program-tab-daily');
17+
this.prayersTab = page.getByTestId('program-tab-prayers');
18+
this.literatureTab = page.getByTestId('program-tab-literature');
19+
this.meetingsTab = page.getByTestId('program-tab-meetings');
20+
}
21+
22+
async goto(): Promise<void> {
23+
await this.page.goto('/program');
24+
await this.waitForPageLoad();
25+
}
26+
27+
async expectOnProgramPage(): Promise<void> {
28+
await expect(this.tabBar).toBeVisible();
29+
}
30+
31+
async navigateToSteps(): Promise<void> {
32+
await this.stepsTab.click();
33+
}
34+
35+
async navigateToDaily(): Promise<void> {
36+
await this.dailyTab.click();
37+
}
38+
39+
async navigateToPrayers(): Promise<void> {
40+
await this.prayersTab.click();
41+
}
42+
43+
async navigateToLiterature(): Promise<void> {
44+
await this.literatureTab.click();
45+
}
46+
47+
async navigateToMeetings(): Promise<void> {
48+
await this.meetingsTab.click();
49+
}
50+
51+
async navigateToTab(
52+
tabName: 'steps' | 'daily' | 'prayers' | 'literature' | 'meetings'
53+
): Promise<void> {
54+
const tab = this.page.getByTestId(`program-tab-${tabName}`);
55+
await tab.click();
56+
}
57+
}
58+
59+
export class PlaceholderPage extends BasePage {
60+
readonly screen: Locator;
61+
readonly title: Locator;
62+
readonly subtitle: Locator;
63+
64+
constructor(
65+
page: Page,
66+
private readonly screenName: string
67+
) {
68+
super(page);
69+
this.screen = page.getByTestId(`${screenName}-screen`);
70+
this.title = page.getByTestId(`${screenName}-title`);
71+
this.subtitle = page.getByTestId(`${screenName}-subtitle`);
72+
}
73+
74+
async expectOnPlaceholderScreen(): Promise<void> {
75+
await expect(this.screen).toBeVisible();
76+
}
77+
78+
async getTitle(): Promise<string> {
79+
return (await this.title.textContent()) ?? '';
80+
}
81+
82+
async getSubtitle(): Promise<string> {
83+
return (await this.subtitle.textContent()) ?? '';
84+
}
85+
86+
async expectComingSoon(): Promise<void> {
87+
await expect(this.subtitle).toContainText('Coming soon');
88+
}
89+
}
90+
91+
export const createDailyReadingsPage = (page: Page) => new PlaceholderPage(page, 'daily-readings');
92+
export const createPrayersPage = (page: Page) => new PlaceholderPage(page, 'prayers');
93+
export const createLiteraturePage = (page: Page) => new PlaceholderPage(page, 'literature');
94+
export const createMeetingsPage = (page: Page) => new PlaceholderPage(page, 'meetings');
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
import { test, expect } from '@playwright/test';
2+
import { ProgramPage } from '../../pages/program.page';
3+
4+
test.describe('Program Navigation', () => {
5+
let programPage: ProgramPage;
6+
7+
test.beforeEach(async ({ page }) => {
8+
programPage = new ProgramPage(page);
9+
await programPage.goto();
10+
});
11+
12+
test('should display program tab bar', async () => {
13+
await programPage.expectOnProgramPage();
14+
await expect(programPage.tabBar).toBeVisible();
15+
});
16+
17+
test('should display all program tabs', async () => {
18+
await expect(programPage.stepsTab).toBeVisible();
19+
await expect(programPage.dailyTab).toBeVisible();
20+
await expect(programPage.prayersTab).toBeVisible();
21+
await expect(programPage.literatureTab).toBeVisible();
22+
await expect(programPage.meetingsTab).toBeVisible();
23+
});
24+
25+
test('should redirect /program to /program/steps by default', async ({ page }) => {
26+
await expect(page).toHaveURL(/.*program\/steps/);
27+
});
28+
29+
test('should navigate to daily readings tab', async ({ page }) => {
30+
await programPage.navigateToDaily();
31+
await expect(page).toHaveURL(/.*program\/daily/);
32+
});
33+
34+
test('should navigate to prayers tab', async ({ page }) => {
35+
await programPage.navigateToPrayers();
36+
await expect(page).toHaveURL(/.*program\/prayers/);
37+
});
38+
39+
test('should navigate to literature tab', async ({ page }) => {
40+
await programPage.navigateToLiterature();
41+
await expect(page).toHaveURL(/.*program\/literature/);
42+
});
43+
44+
test('should navigate to meetings tab', async ({ page }) => {
45+
await programPage.navigateToMeetings();
46+
await expect(page).toHaveURL(/.*program\/meetings/);
47+
});
48+
49+
test('should navigate back to steps tab', async ({ page }) => {
50+
await programPage.navigateToDaily();
51+
await expect(page).toHaveURL(/.*program\/daily/);
52+
53+
await programPage.navigateToSteps();
54+
await expect(page).toHaveURL(/.*program\/steps/);
55+
});
56+
57+
test('should navigate between all tabs without errors', async ({ page }) => {
58+
const tabs = ['steps', 'daily', 'prayers', 'literature', 'meetings'] as const;
59+
60+
for (const tab of tabs) {
61+
await programPage.navigateToTab(tab);
62+
await expect(page).toHaveURL(new RegExp(`.*program/${tab}`));
63+
await programPage.expectNoErrors();
64+
}
65+
});
66+
});
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import { test, expect } from '@playwright/test';
2+
import {
3+
ProgramPage,
4+
createDailyReadingsPage,
5+
createPrayersPage,
6+
createLiteraturePage,
7+
createMeetingsPage,
8+
} from '../../pages/program.page';
9+
10+
test.describe('Program Placeholder Screens', () => {
11+
let programPage: ProgramPage;
12+
13+
test.beforeEach(async ({ page }) => {
14+
programPage = new ProgramPage(page);
15+
await programPage.goto();
16+
});
17+
18+
test.describe('Daily Readings', () => {
19+
test('should display daily readings placeholder screen', async ({ page }) => {
20+
await programPage.navigateToDaily();
21+
22+
const dailyPage = createDailyReadingsPage(page);
23+
await dailyPage.expectOnPlaceholderScreen();
24+
});
25+
26+
test('should show correct title', async ({ page }) => {
27+
await programPage.navigateToDaily();
28+
29+
const dailyPage = createDailyReadingsPage(page);
30+
const title = await dailyPage.getTitle();
31+
expect(title).toBe('Daily Readings');
32+
});
33+
34+
test('should show coming soon subtitle', async ({ page }) => {
35+
await programPage.navigateToDaily();
36+
37+
const dailyPage = createDailyReadingsPage(page);
38+
await dailyPage.expectComingSoon();
39+
});
40+
});
41+
42+
test.describe('Prayers', () => {
43+
test('should display prayers placeholder screen', async ({ page }) => {
44+
await programPage.navigateToPrayers();
45+
46+
const prayersPage = createPrayersPage(page);
47+
await prayersPage.expectOnPlaceholderScreen();
48+
});
49+
50+
test('should show correct title', async ({ page }) => {
51+
await programPage.navigateToPrayers();
52+
53+
const prayersPage = createPrayersPage(page);
54+
const title = await prayersPage.getTitle();
55+
expect(title).toBe('Prayers');
56+
});
57+
58+
test('should show coming soon subtitle', async ({ page }) => {
59+
await programPage.navigateToPrayers();
60+
61+
const prayersPage = createPrayersPage(page);
62+
await prayersPage.expectComingSoon();
63+
});
64+
});
65+
66+
test.describe('Literature', () => {
67+
test('should display literature placeholder screen', async ({ page }) => {
68+
await programPage.navigateToLiterature();
69+
70+
const literaturePage = createLiteraturePage(page);
71+
await literaturePage.expectOnPlaceholderScreen();
72+
});
73+
74+
test('should show correct title', async ({ page }) => {
75+
await programPage.navigateToLiterature();
76+
77+
const literaturePage = createLiteraturePage(page);
78+
const title = await literaturePage.getTitle();
79+
expect(title).toBe('Literature');
80+
});
81+
82+
test('should show coming soon subtitle', async ({ page }) => {
83+
await programPage.navigateToLiterature();
84+
85+
const literaturePage = createLiteraturePage(page);
86+
await literaturePage.expectComingSoon();
87+
});
88+
});
89+
90+
test.describe('Meetings', () => {
91+
test('should display meetings placeholder screen', async ({ page }) => {
92+
await programPage.navigateToMeetings();
93+
94+
const meetingsPage = createMeetingsPage(page);
95+
await meetingsPage.expectOnPlaceholderScreen();
96+
});
97+
98+
test('should show correct title', async ({ page }) => {
99+
await programPage.navigateToMeetings();
100+
101+
const meetingsPage = createMeetingsPage(page);
102+
const title = await meetingsPage.getTitle();
103+
expect(title).toBe('Meetings');
104+
});
105+
106+
test('should show coming soon subtitle', async ({ page }) => {
107+
await programPage.navigateToMeetings();
108+
109+
const meetingsPage = createMeetingsPage(page);
110+
await meetingsPage.expectComingSoon();
111+
});
112+
});
113+
114+
test('all placeholder screens should show coming soon message', async ({ page }) => {
115+
const screens = [
116+
{ navigate: () => programPage.navigateToDaily(), factory: createDailyReadingsPage },
117+
{ navigate: () => programPage.navigateToPrayers(), factory: createPrayersPage },
118+
{ navigate: () => programPage.navigateToLiterature(), factory: createLiteraturePage },
119+
{ navigate: () => programPage.navigateToMeetings(), factory: createMeetingsPage },
120+
];
121+
122+
for (const { navigate, factory } of screens) {
123+
await navigate();
124+
const placeholderPage = factory(page);
125+
await placeholderPage.expectComingSoon();
126+
}
127+
});
128+
});

0 commit comments

Comments
 (0)