Skip to content
Open
Show file tree
Hide file tree
Changes from 20 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
4c3e912
refactor(test): override-scroll-view-content-inset
LKuchno Apr 29, 2026
4736878
refactor(test): override-scroll-view-content-inset
LKuchno Apr 29, 2026
a5655b6
Merge branch '@lkuchno/test-tab-screen-options-ios' of github.com:sof…
LKuchno Apr 30, 2026
96fb36d
remove e2e test from this branch
LKuchno Apr 30, 2026
8df7d76
e2e section update
LKuchno Apr 30, 2026
8f6aa6b
change scroll order first scroll down then to top - make more sense, …
LKuchno Apr 30, 2026
7b5bd7a
chore(test) test-tabs-override-scroll-view-content-inset-ios
LKuchno Apr 30, 2026
09b8e90
test changes in index
LKuchno Apr 30, 2026
2dd112f
Merge branch 'main' of github.com:software-mansion/react-native-scree…
LKuchno Apr 30, 2026
2a098f8
update e2e section in scenario
LKuchno Apr 30, 2026
b12dbf0
Update FabricExample/e2e/single-feature-tests/tabs/test-tabs-override…
LKuchno May 4, 2026
6c6b186
refactor helpers functions
LKuchno May 4, 2026
3dce72e
rename test
LKuchno May 4, 2026
6b08be7
Update FabricExample/e2e/single-feature-tests/tabs/test-tabs-override…
LKuchno May 4, 2026
68eafa2
Merge branch '@lkuchno/e2e-test-tabs-override-scroll-view-content-ins…
LKuchno May 4, 2026
140ba20
adding testID for items on list modify test to use it
LKuchno May 4, 2026
84c6faa
changing titles of corss tab comparision test
LKuchno May 4, 2026
813ef15
removing testID from item element as its not necessary
LKuchno May 4, 2026
6346b42
readded testID for items
LKuchno May 4, 2026
2af7750
refector test code
LKuchno May 4, 2026
ed68f9d
Potential fix for pull request finding
LKuchno May 4, 2026
00ed7ec
Potential fix for pull request finding
LKuchno May 4, 2026
bc4a3b6
function name change
LKuchno May 4, 2026
0ada243
tMerge branch '@lkuchno/e2e-test-tabs-override-scroll-view-content-in…
LKuchno May 4, 2026
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,192 @@
import { expect as jestExpect } from '@jest/globals';
import { device, expect, element, by } from 'detox';
import { IosElementAttributes } from 'detox/detox';
import {
describeIfiOS,
forceTapByLabeliOS,
selectSingleFeatureTestsScreen,
} from '../../e2e-utils';

async function getScrollViewSafeAreaInsetsTop(testID: string): Promise<{
top: number;
}> {
const attrs = (await element(
by.id(testID),
).getAttributes()) as IosElementAttributes;
return { top: attrs.safeAreaInsets.top };
}

function isBelowStatusBar(
itemFrame: { y: number; height: number },
scrollViewSAVInsetTop: number,
): boolean {
return itemFrame.y + itemFrame.height <= scrollViewSAVInsetTop;
}

async function getTabBarFrame(): Promise<{
x: number;
y: number;
width: number;
height: number;
}> {
const attrs = await element(by.type('UITabBar')).getAttributes();
return (attrs as IosElementAttributes).frame;
}

async function getElementFrame(testID: string): Promise<{
x: number;
y: number;
width: number;
height: number;
}> {
const attrs = await element(by.id(testID)).getAttributes();

if ('elements' in attrs) {
throw new Error(
`Multiple elements (${attrs.elements.length}) found for label: "${testID}". `,
Comment thread
LKuchno marked this conversation as resolved.
Outdated
);
}
return (attrs as IosElementAttributes).frame;
}

function isAboveTabBar(
itemFrame: { y: number; height: number },
tabBarFrame: { y: number },
): boolean {
return itemFrame.y + itemFrame.height <= tabBarFrame.y;
}

async function assertLastItemAboveTabBar(tabPrefix: string, expected: boolean) {
const lastItemFrame = await getElementFrame(`${tabPrefix}-item-30`);
const tabFrame = await getTabBarFrame();
jestExpect(isAboveTabBar(lastItemFrame, tabFrame)).toBe(expected);
}

async function assertHeaderBehindStatusBar(
tabPrefix: string,
expected: boolean,
) {
const informationFrame = await getElementFrame(`${tabPrefix}-header`);
const scrollViewSAVInsetTop = await getScrollViewSafeAreaInsetsTop(
`${tabPrefix}-scrollview`,
);
jestExpect(
isBelowStatusBar(informationFrame, scrollViewSAVInsetTop.top),
).toBe(expected);
}

async function scrollToMaxBottom(scrollViewId: string) {
await element(by.id(scrollViewId)).scrollTo('bottom', NaN, 0.5);
}
async function scrollToMaxTop(scrollViewId: string) {
await element(by.id(scrollViewId)).scrollTo('top', NaN, 0.5);
}

describeIfiOS('Override Scroll View Content Inset (iOS)', () => {
beforeAll(async () => {
await device.reloadReactNative();
await selectSingleFeatureTestsScreen(
'Tabs',
'test-tabs-override-scroll-view-content-inset-ios',
);
});

describe('False tab (overrideScrollViewContentInsetAdjustmentBehavior: false)', () => {
beforeAll(async () => {
await forceTapByLabeliOS('override-inset-tab-false');
});

it('should display the false tab scrollview with the tab bar visible', async () => {
await expect(
element(by.id('override-inset-false-scrollview')),
).toBeVisible();
await expect(element(by.type('UITabBar'))).toBeVisible();
});

it('should render the last item overlapping or behind the tab bar (not fully above it)', async () => {
await scrollToMaxBottom('override-inset-false-scrollview');
await assertLastItemAboveTabBar('override-inset-false', false);
});

it('should render the information text clipped behind the status bar ', async () => {
Comment thread
LKuchno marked this conversation as resolved.
Outdated
await scrollToMaxTop('override-inset-false-scrollview');
await assertHeaderBehindStatusBar('override-inset-false', true);
});
});

describe('True tab (overrideScrollViewContentInsetAdjustmentBehavior: true)', () => {
beforeAll(async () => {
await forceTapByLabeliOS('override-inset-tab-true');
});

it('should display the true tab scrollview with the tab bar visible', async () => {
await expect(
element(by.id('override-inset-true-scrollview')),
).toBeVisible();
await expect(element(by.type('UITabBar'))).toBeVisible();
});

it('should render the last item fully above the tab bar (proper inset applied)', async () => {
await scrollToMaxBottom('override-inset-true-scrollview');
await assertLastItemAboveTabBar('override-inset-true', true);
});

it('should show the information text visible (not hidden behind inset)', async () => {
await scrollToMaxTop('override-inset-true-scrollview');
await assertHeaderBehindStatusBar('override-inset-true', false);
});
});
describe('Default tab (prop omitted)', () => {
beforeAll(async () => {
await forceTapByLabeliOS('override-inset-tab-default');
});
Comment on lines +140 to +142
Copy link

Copilot AI Apr 30, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above: use forceTapByLabeliOS (or a shared forceSelectTabByLabel wrapper) for tab selection on iOS to avoid flakiness from obstructed tab bar items.

Copilot uses AI. Check for mistakes.

it('should display the default tab scrollview with the tab bar visible', async () => {
await expect(
element(by.id('override-inset-default-scrollview')),
).toBeVisible();
await expect(element(by.type('UITabBar'))).toBeVisible();
});

it('should render the last item fully above the tab bar (proper inset applied)', async () => {
await scrollToMaxBottom('override-inset-default-scrollview');
await assertLastItemAboveTabBar('override-inset-default', true);
});

it('should show the header label visible (not hidden behind inset)', async () => {
await scrollToMaxTop('override-inset-default-scrollview');
await assertHeaderBehindStatusBar('override-inset-default', false);
});
});

describe('Cross-tab comparison', () => {
beforeAll(async () => {
await forceTapByLabeliOS('override-inset-tab-default');
});

it('should show the information text visible between True and Default tabs', async () => {
await element(by.label('override-inset-tab-true')).tap();
await expect(element(by.id('override-inset-true-item-1'))).toBeVisible();
await assertHeaderBehindStatusBar('override-inset-true', false);

await element(by.label('override-inset-tab-default')).tap();
await expect(
element(by.id('override-inset-default-item-1')),
).toBeVisible();
await assertHeaderBehindStatusBar('override-inset-default', false);

await element(by.label('override-inset-tab-true')).tap();
await assertHeaderBehindStatusBar('override-inset-true', false);
});

it('should render the information text correctly between False and True tabs', async () => {
await element(by.label('override-inset-tab-false')).tap();
await expect(element(by.id('override-inset-false-item-1'))).toBeVisible();
await assertHeaderBehindStatusBar('override-inset-false', true);

await element(by.label('override-inset-tab-true')).tap();
Comment on lines +168 to +187
Comment on lines +168 to +187
await expect(element(by.id('override-inset-true-item-1'))).toBeVisible();
await assertHeaderBehindStatusBar('override-inset-true', false);
});
});
});
4 changes: 2 additions & 2 deletions apps/src/tests/single-feature-tests/tabs/index.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ScenarioGroup } from '@apps/tests/shared/helpers';

import BottomAccessoryScenario from './bottom-accessory-layout';
import OverrideScrollViewContentInsetScenario from './override-scroll-view-content-inset';
import TestTabsOverrideScrollViewContentInset from './test-tabs-override-scroll-view-content-inset-ios';
import TestTabsTabBarHidden from './test-tabs-tab-bar-hidden';
import TabsScreenOrientationScenario from './tabs-screen-orientation';
import TabBarAppearanceDefinedBySelectedTabScenario from './test-tabs-appearance-defined-by-selected-tab';
Expand All @@ -18,7 +18,7 @@ import TestTabsSpecialEffectsScrollToTop from './test-tabs-special-effects-scrol

const scenarios = {
BottomAccessoryScenario,
OverrideScrollViewContentInsetScenario,
TestTabsOverrideScrollViewContentInset,
TabBarAppearanceDefinedBySelectedTabScenario,
TestTabsTabBarHidden,
TabsScreenOrientationScenario,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { createScenario } from '@apps/tests/shared/helpers';

const scenarioDescription: ScenarioDescription = {
name: 'Override ScrollView Content Inset',
key: 'override-scroll-view-content-inset',
key: 'test-tabs-override-scroll-view-content-inset-ios',
details:
'Tests overrideScrollViewContentInsetAdjustmentBehavior with different static values per tab. ' +
'False: content scrolls behind bars. True/Default: content is inset from bars.',
Expand All @@ -19,16 +19,22 @@ const scenarioDescription: ScenarioDescription = {

const ITEM_COUNT = 30;

function ScrollContent({ label }: { label: string }) {
function ScrollContent({
label,
testID,
}: {
label: string;
testID: string;
}) {
return (
<ScrollView style={styles.scrollView}>
<ScrollView style={styles.scrollView} testID={`${testID}-scrollview`}>
<View style={styles.header}>
<Text style={styles.headerText}>
<Text style={styles.headerText} testID={`${testID}-header`}>
overrideScrollViewContentInsetAdjustmentBehavior: {label}
</Text>
</View>
{Array.from({ length: ITEM_COUNT }, (_, i) => (
<View key={i} style={styles.item}>
<View key={i} style={styles.item} testID={`${testID}-item-${i + 1}`}>
<Text style={styles.itemText}>Item {i + 1}</Text>
</View>
))}
Expand All @@ -37,15 +43,30 @@ function ScrollContent({ label }: { label: string }) {
}

function FalseTab() {
return <ScrollContent label="false" />;
return (
<ScrollContent
label="false"
testID="override-inset-false"
/>
);
}

function TrueTab() {
return <ScrollContent label="true" />;
return (
<ScrollContent
label="true"
testID="override-inset-true"
/>
);
}

function DefaultTab() {
return <ScrollContent label="(not set, defaults to true)" />;
return (
<ScrollContent
label="(not set, defaults to true)"
testID="override-inset-default"
/>
);
}

export function App() {
Expand All @@ -59,6 +80,7 @@ export function App() {
Component: FalseTab,
options: {
title: 'False',
tabBarItemAccessibilityLabel: 'override-inset-tab-false',
ios: {
overrideScrollViewContentInsetAdjustmentBehavior: false,
icon: { type: 'sfSymbol', name: 'xmark.circle' },
Expand All @@ -70,6 +92,7 @@ export function App() {
Component: TrueTab,
options: {
title: 'True',
tabBarItemAccessibilityLabel: 'override-inset-tab-true',
ios: {
overrideScrollViewContentInsetAdjustmentBehavior: true,
icon: { type: 'sfSymbol', name: 'checkmark.circle' },
Expand All @@ -81,6 +104,7 @@ export function App() {
Component: DefaultTab,
options: {
title: 'Default',
tabBarItemAccessibilityLabel: 'override-inset-tab-default',
ios: { icon: { type: 'sfSymbol', name: 'circle.dashed' } },
},
},
Expand Down
Loading
Loading