Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
import { device, expect, element, by } from 'detox';
import { selectSingleFeatureTestsScreen } from '../../e2e-utils';

describe('Tab Bar preventNativeSelection', () => {
beforeAll(async () => {
await device.reloadReactNative();
await selectSingleFeatureTestsScreen(
'Tabs',
'test-tabs-prevent-native-selection',
);
});

it('preventNativeSelection should be set to false for First tab', async () => {
await expect(
element(by.id('tab-bar-prevent-native-selection-view')),
).toBeVisible();
if (device.getPlatform() === 'ios') {
await expect(element(by.label('More'))).toExist();
}
await expect(element(by.id('screen-name-label'))).toHaveLabel('First');
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: false',
);
});

it('native selection of first tab should be blocked', async () => {
await expect(element(by.id('screen-name-label'))).toHaveLabel('First');
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: false',
);
await element(by.id('prevent-native-selection-button')).tap();
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: true',
);
await element(by.id('Second')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('Second');
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: false',
);
await element(by.id('First')).tap();
await expect(
element(by.label('1. onTabSelectionPrevented: First')),
).toBeVisible();
await element(by.label('1. onTabSelectionPrevented: First')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('Second');
});

it('programmatic navigation to first tab should not be blocked', async () => {
await expect(element(by.id('screen-name-label'))).toHaveLabel('Second');
await element(by.id('first-button')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('First');
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: true',
);
});

it('native selection should be possible after disabling preventNativeSelection', async () => {
await expect(element(by.id('screen-name-label'))).toHaveLabel('First');
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: true',
);
await element(by.id('prevent-native-selection-button')).tap();
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: false',
);
await element(by.id('Fourth')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('Fourth');
await element(by.id('First')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('First');
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: false',
);
});

it('native selection should be possible after disabling preventNativeSelection', async () => {
await expect(element(by.id('screen-name-label'))).toHaveLabel('First');
await element(by.id('Third')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('Third');
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: false',
);
await element(by.id('prevent-native-selection-button')).tap();
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: true',
);
await element(by.id('Fourth')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('Fourth');
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: false',
);
await element(by.id('prevent-native-selection-button')).tap();
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: true',
);
await element(by.id('Third')).tap();
await expect(
element(by.label('1. onTabSelectionPrevented: Third')),
).toBeVisible();
await element(by.label('1. onTabSelectionPrevented: Third')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('Fourth');
await element(by.id('First')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('First');
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: false',
);
});
it('iOS only: preventNativeSelection for tabs hidden under More tab blocks native selection', async () => {
if (device.getPlatform() !== 'ios') {
return;
}
await element(by.id('fifth-button')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('Fifth');
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: false',
);
await element(by.id('prevent-native-selection-button')).tap();
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: true',
);

await element(by.id('sixth-button')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('Sixth');
await element(by.id('prevent-native-selection-button')).tap();
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: true',
);

await element(by.id('first-button')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('First');
await element(by.label('More')).atIndex(0).tap();
await expect(
element(by.label('1. onTabSelectionPrevented: Sixth')),
).toBeVisible();
await element(by.label('1. onTabSelectionPrevented: Sixth')).tap();
await expect(element(by.label('Fifth'))).toBeVisible();
await expect(element(by.label('Sixth'))).toBeVisible();

await element(by.label('Fifth')).tap();
await expect(
element(by.label('1. onTabSelectionPrevented: Fifth')),
).toBeVisible();
await element(by.label('1. onTabSelectionPrevented: Fifth')).tap();

await element(by.id('First')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('First');
await element(by.id('fifth-button')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('Fifth');
await element(by.id('prevent-native-selection-button')).tap();
await expect(element(by.id('prevent-native-selection-state'))).toHaveLabel(
'preventNativeSelection: false',
);

await element(by.id('First')).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('First');
await element(by.label('More')).atIndex(0).tap();
await expect(element(by.id('screen-name-label'))).toHaveLabel('Fifth');
await expect(element(by.id('Fifth'))).not.toBeVisible();
await expect(element(by.id('Sixth'))).not.toBeVisible();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,14 @@ function ContentView() {
nav.routeOptions.preventNativeSelection ?? false;

return (
<CenteredLayoutView>
<Text style={{ fontWeight: 'bold', textAlign: 'center' }}>
<CenteredLayoutView
testID='tab-bar-prevent-native-selection-view'>
<Text style={{ fontWeight: 'bold', textAlign: 'center' }}
testID='screen-name-label'>
{nav.routeKey}
</Text>
<Text style={{ textAlign: 'center' }}>
<Text style={{ textAlign: 'center' }}
testID='prevent-native-selection-state'>
preventNativeSelection: {JSON.stringify(preventNativeSelection)}
</Text>
<Button
Expand All @@ -41,6 +44,7 @@ function ContentView() {
preventNativeSelection: !preventNativeSelection,
})
}
testID='prevent-native-selection-button'
/>
<TabsNavigationButtons />
</CenteredLayoutView>
Expand All @@ -52,12 +56,17 @@ function TabsNavigationButtons() {

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 Fourth" onPress={() => nav.selectTab('Fourth')} />
<Button title="Select Fifth" onPress={() => nav.selectTab('Fifth')} />
<Button title="Select Sixth" onPress={() => nav.selectTab('Sixth')} />
<Button title="Select First" onPress={() => nav.selectTab('First')} testID='first-button' />
<Button title="Select Second" onPress={() => nav.selectTab('Second')}
testID='second-button' />
<Button title="Select Third" onPress={() => nav.selectTab('Third')}
testID='third-button' />
<Button title="Select Fourth" onPress={() => nav.selectTab('Fourth')}
testID='fourth-button' />
<Button title="Select Fifth" onPress={() => nav.selectTab('Fifth')}
testID='fifth-button' />
<Button title="Select Sixth" onPress={() => nav.selectTab('Sixth')}
testID='sixth-button' />
</View>
);
}
Expand All @@ -77,32 +86,32 @@ const ROUTE_CONFIGS: TabRouteConfig[] = [
{
name: 'First',
Component: ContentView,
options: { ...ROUTE_OPTIONS, title: 'First' },
options: { ...ROUTE_OPTIONS, title: 'First', tabBarItemTestID: 'First' },
},
{
name: 'Second',
Component: ContentView,
options: { ...ROUTE_OPTIONS, title: 'Second' },
options: { ...ROUTE_OPTIONS, title: 'Second', tabBarItemTestID: 'Second' },
},
{
name: 'Third',
Component: ContentView,
options: { ...ROUTE_OPTIONS, title: 'Third' },
options: { ...ROUTE_OPTIONS, title: 'Third', tabBarItemTestID: 'Third' },
},
{
name: 'Fourth',
Component: ContentView,
options: { ...ROUTE_OPTIONS, title: 'Fourth' },
options: { ...ROUTE_OPTIONS, title: 'Fourth', tabBarItemTestID: 'Fourth' },
},
{
name: 'Fifth',
Component: ContentView,
options: { ...ROUTE_OPTIONS, title: 'Fifth' },
options: { ...ROUTE_OPTIONS, title: 'Fifth', tabBarItemTestID: 'Fifth' },
},
{
name: 'Sixth',
Component: ContentView,
options: { ...ROUTE_OPTIONS, title: 'Sixth' },
options: { ...ROUTE_OPTIONS, title: 'Sixth', tabBarItemTestID: 'Sixth' },
},
];

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,16 @@ subject to prevention.

## E2E test

Other: ongoing research.
Yes: Partially automated. The E2E test covers most steps for iPhone and Android
phone.

- iPad specific steps are not automated, as the resize interactions required to
trigger the More tab (Split View / window resizing between compact and regular
size classes) are not feasible with Detox.
- Testing repeated selection of an already active
More tab is not currently automated (Step 19 from the scenario). When the More tab content is already active
and displayed, Detox is unable to re-select the "More" tab bar item because it
becomes invisible for Detox.

## Prerequisites

Expand All @@ -43,8 +52,8 @@ is blocked.
1. Launch the app and navigate to **Prevent native selection**.

- [ ] Expected: On Android — six tabs visible in the tab bar. On iOS — four tabs
and a **More** item visible.The **First** tab is selected.
Each tab displays its name and `preventNativeSelection: false`.
and a **More** item visible.The **First** tab is selected
and displays `preventNativeSelection: false` under its name on the screen.

---

Expand All @@ -61,7 +70,7 @@ Each tab displays its name and `preventNativeSelection: false`.

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

- [ ] Expected: The tab does not change. A toast appears with
- [ ] Expected: The tab does not change. A toast appears with
`onTabSelectionPrevented: First`.

5. Tap **Select First** button (programmatic navigation).
Expand Down Expand Up @@ -90,11 +99,16 @@ navigation is not blocked by `preventNativeSelection`.

- [ ] Expected: Third tab label shows `preventNativeSelection: true`.

9. Navigate to a different tab, then tap the **Third** tab item in the tab bar.
9. Navigate to the **Fourth** tab and tap **Toggle preventNativeSelection**.

- [ ] Expected: Fourth tab label shows `preventNativeSelection: true`.

10. Tap the **Third** tab item in the tab bar.

- [ ] Expected: Tab does not switch. Toast appears with `onTabSelectionPrevented: Third`.
- [ ] Expected: Tab does not switch. Toast appears with
`onTabSelectionPrevented: Third`.

10. Navigate to the **First** tab and confirm its `preventNativeSelection` is
11. Navigate to the **First** tab and confirm its `preventNativeSelection` is
still `false`.

- [ ] Expected: First tab label shows `preventNativeSelection: false`. Tapping it
Expand All @@ -104,69 +118,69 @@ from another tab works normally.

### iOS only — More navigation controller

11. Navigate to the **Fifth** tab via the **Select Fifth** button (programmatic).
12. Navigate to the **Fifth** tab via the **Select Fifth** button (programmatic).

- [ ] Expected: **Fifth** tab is displayed normally.

12. Tap **Toggle preventNativeSelection** on the **Fifth** tab.
13. Tap **Toggle preventNativeSelection** on the **Fifth** tab.

- [ ] Expected: Label updates to `preventNativeSelection: true`.

13. Tap **Select Sixth**, then tap **Toggle preventNativeSelection**.
14. Tap **Select Sixth**, then tap **Toggle preventNativeSelection**.

- [ ] Expected: Label updates to `preventNativeSelection: true`.

14. Tap **Select First**, then tap **More** in the tab bar.
15. Tap **Select First**, then tap **More** in the tab bar.

- [ ] Expected: Navigation to **Sixth** is blocked. Toast appears with
`onTabSelectionPrevented: Sixth`. The More list is displayed.

15. Tap **Fifth** in the More list.
16. Tap **Fifth** in the More list.

- [ ] Expected: Navigation to **Fifth** is blocked. Toast appears with `onTabSelectionPrevented: Fifth`. The More list remains displayed.

16. Tap **Fourth** tab and navigate to **Fifth** via **Select Fifth**, tap
17. Tap **Fourth** tab and navigate to **Fifth** via **Select Fifth**, tap
**Toggle preventNativeSelection** to disable it.

- [ ] Expected: Fifth tab label shows `preventNativeSelection: false`.

17. Navigate away, then tap **More**.
18. Navigate away, then tap **More**.

- [ ] Expected: Navigation to **Fifth** proceeds normally. No toast appears.

18. Tap **More** again and tap **Fifth** from list.
19. Tap **More** again and tap **Fifth** from list.

- [ ] Expected: Navigation to **Fifth** proceeds normally. No toast appears.

### iPad only - Sidebar behavior

19. Resize app to full screen width.
20. Resize app to full screen width.

- [ ] Expected: More tab disappear. **Fifth** tab is selected.

20. Open Sidebar and select **Sixth** from the list.
21. Open Sidebar and select **Sixth** from the list.

- [ ] Expected: Navigation to **Sixth** is blocked. Toast appears with
`onTabSelectionPrevented: Sixth`.
The **Fifth** tab is selected and its content remains displayed.

21. Open Sidebar.
22. Open Sidebar.

- [ ] Expected: **Fifth** tab is selected.

22. Tap **Second** from Sidebar list and tap **Toggle preventNativeSelection**.
23. Tap **Second** from Sidebar list and tap **Toggle preventNativeSelection**.

- [ ] Expected: Label updates to `preventNativeSelection: true`.

23. Tap **Select Sixth** and tap **Toggle preventNativeSelection**.
24. Tap **Select Sixth** and tap **Toggle preventNativeSelection**.

- [ ] Expected: Label updates to `preventNativeSelection: false`.

24. Tap **Fourth** and then from the Sidebar tap **Sixth**.
25. Tap **Fourth** and then from the Sidebar tap **Sixth**.

- [ ] Expected: Tab switches normally. No toast appears.

25. Tap **Second** from tab bar.
26. Tap **Second** from tab bar.

- [ ] Expected: Navigation to **Second** is blocked. Toast appears with
`onTabSelectionPrevented: Second`.
Expand Down
Loading