Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { device, expect, element, by } from 'detox';
import {
forceTapByLabeliOS,
selectSingleFeatureTestsScreen,
} from '../../e2e-utils';

describe('@smoke Tabs: simple navigation', () => {
beforeAll(async () => {
await device.reloadReactNative();
await selectSingleFeatureTestsScreen('Tabs', 'test-tabs-simple-nav');
});

it('should display First tab as active by default', async () => {
await expect(element(by.id('route-key-label'))).toHaveLabel('First');
});

it('should navigate to Second tab via tab bar', async () => {
await element(by.id('tab-bar-item-second')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('Second');
});

it('should navigate to Third tab via tab bar', async () => {
await element(by.id('tab-bar-item-third')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('Third');
});

it('should navigate back to First tab via tab bar', async () => {
await element(by.id('tab-bar-item-first')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('First');
});

it('should navigate to Second tab programmatically via Select Second button', async () => {
await element(by.id('select-second-button')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('Second');
});

it('should navigate to Third tab programmatically via Select Third button', async () => {
await element(by.id('select-third-button')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('Third');
});

it('should navigate to First tab programmatically via Select First button', async () => {
await element(by.id('select-first-button')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('First');
});

it('should skip Second tab when navigating directly from First to Third programmatically', async () => {
await expect(element(by.id('route-key-label'))).toHaveLabel('First');
await element(by.id('select-third-button')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('Third');
});

it('should skip Second tab when navigating directly from Third to First via tab bar', async () => {
await expect(element(by.id('route-key-label'))).toHaveLabel('Third');
await element(by.id('tab-bar-item-first')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('First');
});

it('should navigate correctly after mixing tab-bar and programmatic navigation', async () => {
await element(by.id('tab-bar-item-second')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('Second');
await element(by.id('select-first-button')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('First');
});

it('should stay on First tab when re-tapping the active First tab bar item', async () => {
await expect(element(by.id('route-key-label'))).toHaveLabel('First');
device.getPlatform() === 'ios'
? await forceTapByLabeliOS('FirstTab')
: await element(by.id('tab-bar-item-first')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('First');
});

it('should stay on First tab when calling selectTab on the already-active tab programmatically', async () => {
await expect(element(by.id('route-key-label'))).toHaveLabel('First');
await element(by.id('select-first-button')).tap();
await expect(element(by.id('route-key-label'))).toHaveLabel('First');
});
});
2 changes: 2 additions & 0 deletions apps/src/tests/single-feature-tests/tabs/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ import TestTabsSystemItem from './test-tabs-system-item-ios';
import TestTabsGeneralAppearanceNoLiquidGlass from './test-tabs-general-appearance-no-liquid-glass-ios';
import TestTabsNativeContainerStyle from './test-tabs-native-container-style';

export { TestTabsSimpleNav } from './test-tabs-simple-nav';

const scenarios = {
TestTabBottomAccessory,
TestTabsOverrideScrollViewContentInset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,40 @@ import {
} from '@apps/shared/gamma/containers/tabs';
import { CenteredLayoutView } from '@apps/shared/CenteredLayoutView';

export function ContentView() {
function ContentView() {
const { routeKey } = useTabsNavigationContext();
return (
<CenteredLayoutView>
<Text style={{ fontWeight: 'bold', textAlign: 'center' }}>
<Text
style={{ fontWeight: 'bold', textAlign: 'center' }}
testID="route-key-label">
{routeKey}
</Text>
<TabsNavigationButtons />
</CenteredLayoutView>
);
}

export function TabsNavigationButtons() {
function TabsNavigationButtons() {
const nav = useTabsNavigationContext();

return (
<View>
<Button title="Select First" onPress={() => nav.selectTab('First')} />
<Button title="Select Second" onPress={() => nav.selectTab('Second')} />
<Button title="Select Third" onPress={() => nav.selectTab('Third')} />
<Button
title="Select First"
onPress={() => nav.selectTab('First')}
testID="select-first-button"
/>
<Button
title="Select Second"
onPress={() => nav.selectTab('Second')}
testID="select-second-button"
/>
<Button
title="Select Third"
onPress={() => nav.selectTab('Third')}
testID="select-third-button"
/>
</View>
);
}
Expand All @@ -38,22 +52,35 @@ const ROUTE_CONFIGS: TabRouteConfig[] = [
{
name: 'First',
Component: ContentView,
options: { ...DEFAULT_TAB_ROUTE_OPTIONS, title: 'First' },
options: {
...DEFAULT_TAB_ROUTE_OPTIONS,
title: 'First',
tabBarItemTestID: 'tab-bar-item-first',
tabBarItemAccessibilityLabel: 'FirstTab',
},
},
{
name: 'Second',
Component: ContentView,
options: { ...DEFAULT_TAB_ROUTE_OPTIONS, title: 'Second' },
options: {
...DEFAULT_TAB_ROUTE_OPTIONS,
title: 'Second',
tabBarItemTestID: 'tab-bar-item-second',
},
},
{
name: 'Third',
Component: ContentView,
options: { ...DEFAULT_TAB_ROUTE_OPTIONS, title: 'Third' },
options: {
...DEFAULT_TAB_ROUTE_OPTIONS,
title: 'Third',
tabBarItemTestID: 'tab-bar-item-third',
},
},
];

export function App() {
export function TestTabsSimpleNav() {
return <TabsContainer routeConfigs={ROUTE_CONFIGS} />;
}

export default createScenario(App, scenarioDescription);
export default createScenario(TestTabsSimpleNav, scenarioDescription);
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ export const scenarioDescription: ScenarioDescription = {
key: 'test-tabs-simple-nav',
details: 'Test basic navigation scenarios',
platforms: ['android', 'ios'],
e2eCoverage: 'tbd',
e2eCoverage: 'incomplete',
smokeTest: true,
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# Test Scenario: Simple navigation

## Details

**Description:** Verifies that programmatic tab navigation via the
`selectTab` method on `useTabsNavigationContext` works correctly.
Each tab renders its own `routeKey` as a label and exposes three
buttons - **Select First**, **Select Second**, **Select Third** -
that call `selectTab` with the corresponding route key. The test
validates that tapping the tab bar items navigates normally, that
the programmatic `selectTab` buttons switch to the correct tab
from any starting tab, that the displayed `routeKey` label always
matches the active tab, and that both interaction paths are
consistent across platforms.

**OS test creation version:** iOS: 18.6 and 26.5, Android: API Level 36.

## E2E test

Incomplete: The E2E test covers steps 1-13. Step 14 is omitted because Detox
automatically waits for the UI layer to become idle. Due to this synchronization
model, it cannot execute rapid tab switching sequentially before the previous
transition animation finishes.

## Prerequisites

- iOS device or simulator
- Android emulator or device

## Steps

### Baseline

1. Launch the app and navigate to **Test simple navigation**.

- [ ] Expected: Three tabs are visible in the tab bar -
**First**, **Second**, and **Third**. The **First** tab is
active. The content area displays the label `First`.

---

### Native tab-bar navigation

2. Tap the **Second** tab in the tab bar.

- [ ] Expected: The **Second** tab becomes active. The content
area displays the label `Second`.

3. Tap the **Third** tab in the tab bar.

- [ ] Expected: The **Third** tab becomes active. The content
area displays the label `Third`.

4. Tap the **First** tab in the tab bar.

- [ ] Expected: The **First** tab becomes active. The content
area displays the label `First`.

---

### Programmatic navigation via selectTab

5. While on the **First** tab, tap **Select Second**.

- [ ] Expected: The **Second** tab becomes active. The content
area displays the label `Second`.

6. While on the **Second** tab, tap **Select Third**.

- [ ] Expected: The **Third** tab becomes active. The content
area displays the label `Third`.

7. While on the **Third** tab, tap **Select First**.

- [ ] Expected: The **First** tab becomes active. The content
area displays the label `First`.

---

### Skipping tabs

8. While on the **First** tab, tap **Select Third**.

- [ ] Expected: The **Third** tab becomes active directly,
skipping **Second**. The content area displays the label
`Third`.

9. While on the **Third** tab, tap **First** tab in the tab bar.

- [ ] Expected: The **First** tab becomes active directly,
skipping **Second**. The content area displays the label
`First`.

---

### Mixed navigation (tab-bar then programmatic)

10. Tap the **Second** tab in the tab bar.

- [ ] Expected: The **Second** tab is active, label shows
`Second`.

11. Tap **Select First** in the content area.

- [ ] Expected: The **First** tab becomes active. The content
area displays the label `First`. The tab-bar item for
**First** is visually selected.

---

### Re-selecting the active tab (edge case)

12. While on the **First** tab, tap the **First** tab bar item
again.

- [ ] Expected: The **First** tab remains selected. The content
area still displays `First`. No crash or unexpected transition
occurs.

13. While on the **First** tab, tap **Select First**.

- [ ] Expected: The **First** tab remains selected. The content
area still displays `First`. No crash or unexpected state
change occurs.

---

### Rapid programmatic switching (edge case)

14. From the **First** tab, tap **Select Second** and immediately
tap **Select Third** before the transition completes.

- [ ] Expected: The final active tab is **Third**. The content
area displays the label `Third`. No crash or inconsistent
label is shown.
Loading