diff --git a/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts new file mode 100644 index 0000000000..132a916572 --- /dev/null +++ b/FabricExample/e2e/single-feature-tests/tabs/test-tabs-ime-insets-android.e2e.ts @@ -0,0 +1,140 @@ +import { expect as jestExpect } from '@jest/globals'; +import { device, expect, element, by } from 'detox'; +import { AndroidElementAttributes } from 'detox/detox'; +import { + describeIfAndroid, + selectSingleFeatureTestsScreen, +} from '../../e2e-utils'; + +async function getTabBarItemAttrs(): Promise { + const attrs = (await element( + by.id('ime-insets-config-tab-item'), + ).getAttributes()) as AndroidElementAttributes; + return attrs; +} +async function getTextAttrs(): Promise { + const attrs = (await element( + by.id('tabs-screen-bottom-text'), + ).getAttributes()) as AndroidElementAttributes; + return attrs; +} + +describeIfAndroid('Tabs: tabBarRespectsIMEInsets', () => { + beforeAll(async () => { + await device.reloadReactNative(); + await selectSingleFeatureTestsScreen( + 'Tabs', + 'test-tabs-ime-insets-android', + ); + }); + + it('should display default switch states and bottom text on load', async () => { + await expect(element(by.id('safe-area-bottom-edge-switch'))).toBeVisible(); + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toBeVisible(); + await expect(element(by.id('ime-insets-text-input'))).toBeVisible(); + + await expect(element(by.id('safe-area-bottom-edge-switch'))).toHaveLabel( + 'safeAreaViewBottomEdgeEnabled: true', + ); + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toHaveLabel('tabBarRespectsIMEInsets: false'); + + await expect(element(by.id('tabs-screen-bottom-text'))).toBeVisible(); + }); + + it('tabBarRespectsIMEInsets: false — tab bar Y stays constant when keyboard opens', async () => { + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toHaveLabel('tabBarRespectsIMEInsets: false'); + + const yTabBefore = (await getTabBarItemAttrs()).frame.y; + const yTextBefore = (await getTextAttrs()).frame.y; + + await element(by.id('ime-insets-text-input')).tap(); + + const yTabAfter = (await getTabBarItemAttrs()).frame.y; + const yTextAfter = (await getTextAttrs()).frame.y; + + jestExpect(yTabAfter).toEqual(yTabBefore); + jestExpect(yTextAfter).toEqual(yTextBefore); + + await device.pressBack(); + }); + + it('tabBarRespectsIMEInsets: true — tab bar shifts above keyboard when keyboard opens', async () => { + await element(by.id('tab-bar-respects-ime-insets-switch')).tap(); + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toHaveLabel('tabBarRespectsIMEInsets: true'); + + const yTabBefore = (await getTabBarItemAttrs()).frame.y; + const yTextBefore = (await getTextAttrs()).frame.y; + + await element(by.id('ime-insets-text-input')).tap(); + + const yTabAfter = (await getTabBarItemAttrs()).frame.y; + const yTextAfter = (await getTextAttrs()).frame.y; + + jestExpect(yTabAfter).toBeLessThan(yTabBefore); + jestExpect(yTextAfter).toBeLessThan(yTextBefore); + jestExpect(yTextAfter).toBeLessThan(yTabAfter); + + await device.pressBack(); + + const yTabRestored = (await getTabBarItemAttrs()).frame.y; + + jestExpect(yTabRestored).toEqual(yTabBefore); + }); + + it('safeAreaViewBottomEdgeEnabled: false + tabBarRespectsIMEInsets: true — tab bar still shifts above keyboard', async () => { + await element(by.id('safe-area-bottom-edge-switch')).tap(); + await expect(element(by.id('safe-area-bottom-edge-switch'))).toHaveLabel( + 'safeAreaViewBottomEdgeEnabled: false', + ); + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toHaveLabel('tabBarRespectsIMEInsets: true'); + + const yTabBefore = (await getTabBarItemAttrs()).frame.y; + const yTextBefore = (await getTextAttrs()).frame.y; + + jestExpect(yTextBefore).toBeGreaterThan(yTabBefore); + + await element(by.id('ime-insets-text-input')).tap(); + + const yTabAfter = (await getTabBarItemAttrs()).frame.y; + const yTextAfter = (await getTextAttrs()).frame.y; + + jestExpect(yTabAfter).toBeLessThan(yTabBefore); + jestExpect(yTextAfter).toEqual(yTextBefore); + + await device.pressBack(); + }); + + it('both props false — tab bar stays at bottom when keyboard opens', async () => { + await element(by.id('tab-bar-respects-ime-insets-switch')).tap(); + await expect(element(by.id('safe-area-bottom-edge-switch'))).toHaveLabel( + 'safeAreaViewBottomEdgeEnabled: false', + ); + await expect( + element(by.id('tab-bar-respects-ime-insets-switch')), + ).toHaveLabel('tabBarRespectsIMEInsets: false'); + + const yTabBefore = (await getTabBarItemAttrs()).frame.y; + const yTextBefore = (await getTextAttrs()).frame.y; + jestExpect(yTextBefore).toBeGreaterThan(yTabBefore); + + await element(by.id('ime-insets-text-input')).tap(); + + const yTabAfter = (await getTabBarItemAttrs()).frame.y; + const yTextAfter = (await getTextAttrs()).frame.y; + + jestExpect(yTabAfter).toEqual(yTabBefore); + jestExpect(yTextAfter).toEqual(yTextBefore); + + await device.pressBack(); + }); +}); diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/index.tsx b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/index.tsx index dc0dabc98e..2be4f4c4dc 100644 --- a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/index.tsx +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/index.tsx @@ -39,6 +39,7 @@ function ConfigScreen() { onValueChange={function (value: boolean): void { setSafeAreaViewBottomEdgeEnabled(value); }} + testID="safe-area-bottom-edge-switch" /> @@ -49,6 +50,7 @@ function ConfigScreen() { onValueChange={function (value: boolean): void { updateHostConfig({ android: { tabBarRespectsIMEInsets: value } }); }} + testID="tab-bar-respects-ime-insets-switch" /> @@ -56,10 +58,11 @@ function ConfigScreen() { - TabsScreen bottom + TabsScreen bottom ); @@ -72,6 +75,7 @@ const ROUTE_CONFIGS: TabRouteConfig[] = [ options: { ...DEFAULT_TAB_ROUTE_OPTIONS, title: 'Config', + tabBarItemTestID: 'ime-insets-config-tab-item', safeAreaConfiguration: { edges: { bottom: true, @@ -85,6 +89,7 @@ const ROUTE_CONFIGS: TabRouteConfig[] = [ options: { ...DEFAULT_TAB_ROUTE_OPTIONS, title: 'Tab2', + tabBarItemTestID: 'ime-insets-tab2-tab-item', }, }, ]; diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts index 5283e267ea..d82ca9df99 100644 --- a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario-description.ts @@ -6,6 +6,6 @@ export const scenarioDescription: ScenarioDescription = { details: 'Tests prop that determines whether BottomNavigationView respects IME insets.', platforms: ['android'], - e2eCoverage: 'tbd', + e2eCoverage: 'full', smokeTest: false, }; diff --git a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md index f6bae73da6..36e9702975 100644 --- a/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md +++ b/apps/src/tests/single-feature-tests/tabs/test-tabs-ime-insets-android/scenario.md @@ -13,7 +13,8 @@ the layout content relative to the tab bar to prevent keyboard overlapping. ## E2E test -TBD: Planned, but will be implemented separately. +Full: All manual steps are covered by an E2E test based on changes to the tab bar +and text frame Y-positions. ## Prerequisites