Skip to content

Commit c188d35

Browse files
authored
fix(iOS): fix detents not changing if sheetInitialDetent > 0 (#2935)
## Description Changing detents on iOS form sheets would not work correctly if `sheetInitialDetent` was set to value other than `0`. For some reason, initial detent was set on **every** update instead of being set only once. Fixes #2543. ## Changes - add `_hasInitialDetentSet` flag and use it to determine whether to set initial detent - add `Test2543` test screen and e2e test ## Screenshots / GIFs | before | after | | --- | --- | | <video src="https://github.com/user-attachments/assets/626b4dc8-52ac-454d-a223-d311c0a7a08d" /> | <video src="https://github.com/user-attachments/assets/f7ca38ca-c0b5-4455-b04e-384621bfecbd" /> | First example works on both videos, second one only after fix. ## Test code and steps to reproduce Run example app, open `Test2543`. ## Checklist - [x] Included code example that can be used to test this change - [x] Ensured that CI passes
1 parent f79fcae commit c188d35

4 files changed

Lines changed: 371 additions & 13 deletions

File tree

Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { device, expect, element, by } from 'detox';
2+
import { describeIfiOS, selectTestScreen } from '../e2e-utils';
3+
4+
async function testDetentsVisibility(
5+
testCaseName: string,
6+
firstVisible: boolean,
7+
secondVisible: boolean,
8+
thirdVisible: boolean,
9+
) {
10+
const firstDetent = element(by.id(`${testCaseName}-text-first-detent`));
11+
const secondDetent = element(by.id(`${testCaseName}-text-second-detent`));
12+
const thirdDetent = element(by.id(`${testCaseName}-text-third-detent`));
13+
14+
if (firstVisible) {
15+
await expect(firstDetent).toBeVisible();
16+
} else {
17+
await expect(firstDetent).not.toBeVisible();
18+
}
19+
20+
if (secondVisible) {
21+
await expect(secondDetent).toBeVisible();
22+
} else {
23+
await expect(secondDetent).not.toBeVisible();
24+
}
25+
26+
if (thirdVisible) {
27+
await expect(thirdDetent).toBeVisible();
28+
} else {
29+
await expect(thirdDetent).not.toBeVisible();
30+
}
31+
}
32+
33+
// issue related to iOS formSheet initial detent
34+
describeIfiOS('Test2543', () => {
35+
beforeAll(async () => {
36+
await device.reloadReactNative();
37+
});
38+
39+
it('Test2543 should exist', async () => {
40+
await selectTestScreen('Test2543');
41+
});
42+
43+
it('formSheet with 2 detents, initial first, should allow changing detents', async () => {
44+
await testDetentsVisibility('TwoDetentsInitialFirst', false, false, false);
45+
46+
await element(by.id('home-button-open-TwoDetentsInitialFirst')).tap();
47+
await testDetentsVisibility('TwoDetentsInitialFirst', true, false, false);
48+
49+
const header = element(by.id('TwoDetentsInitialFirst-text-header'));
50+
await header.swipe('up', 'fast', 0.25);
51+
await testDetentsVisibility('TwoDetentsInitialFirst', true, true, false);
52+
53+
await header.swipe('down', 'fast', 0.25);
54+
await testDetentsVisibility('TwoDetentsInitialFirst', true, false, false);
55+
56+
await header.swipe('down', 'fast', 0.25);
57+
await testDetentsVisibility('TwoDetentsInitialFirst', false, false, false);
58+
});
59+
60+
it('formSheet with 2 detents, initial second, should allow changing detents', async () => {
61+
await testDetentsVisibility('TwoDetentsInitialSecond', false, false, false);
62+
63+
await element(by.id('home-button-open-TwoDetentsInitialSecond')).tap();
64+
await testDetentsVisibility('TwoDetentsInitialSecond', true, true, false);
65+
66+
const header = element(by.id('TwoDetentsInitialSecond-text-header'));
67+
await header.swipe('down', 'fast', 0.25);
68+
await testDetentsVisibility('TwoDetentsInitialSecond', true, false, false);
69+
70+
await header.swipe('up', 'fast', 0.25);
71+
await testDetentsVisibility('TwoDetentsInitialSecond', true, true, false);
72+
73+
await header.swipe('down', 'fast', 0.5);
74+
await testDetentsVisibility('TwoDetentsInitialSecond', false, false, false);
75+
});
76+
77+
it('formSheet with 3 detents, initial first, should allow changing detents', async () => {
78+
await testDetentsVisibility(
79+
'ThreeDetentsInitialFirst',
80+
false,
81+
false,
82+
false,
83+
);
84+
85+
await element(by.id('home-button-open-ThreeDetentsInitialFirst')).tap();
86+
await testDetentsVisibility('ThreeDetentsInitialFirst', true, false, false);
87+
88+
const header = element(by.id('ThreeDetentsInitialFirst-text-header'));
89+
await header.swipe('up', 'fast', 0.25);
90+
await testDetentsVisibility('ThreeDetentsInitialFirst', true, true, false);
91+
92+
await header.swipe('up', 'fast', 0.25);
93+
await testDetentsVisibility('ThreeDetentsInitialFirst', true, true, true);
94+
95+
await header.swipe('down', 'fast', 0.25);
96+
await testDetentsVisibility('ThreeDetentsInitialFirst', true, true, false);
97+
98+
await header.swipe('down', 'fast', 0.25);
99+
await testDetentsVisibility('ThreeDetentsInitialFirst', true, false, false);
100+
101+
await header.swipe('up', 'fast', 0.5);
102+
await testDetentsVisibility('ThreeDetentsInitialFirst', true, true, true);
103+
104+
await header.swipe('down', 'fast', 0.5);
105+
await testDetentsVisibility(
106+
'ThreeDetentsInitialFirst',
107+
false,
108+
false,
109+
false,
110+
);
111+
});
112+
113+
it('formSheet with 3 detents, initial second, should allow changing detents', async () => {
114+
await testDetentsVisibility(
115+
'ThreeDetentsInitialSecond',
116+
false,
117+
false,
118+
false,
119+
);
120+
121+
await element(by.id('home-button-open-ThreeDetentsInitialSecond')).tap();
122+
await testDetentsVisibility('ThreeDetentsInitialSecond', true, true, false);
123+
124+
const header = element(by.id('ThreeDetentsInitialSecond-text-header'));
125+
await header.swipe('down', 'fast', 0.25);
126+
await testDetentsVisibility(
127+
'ThreeDetentsInitialSecond',
128+
true,
129+
false,
130+
false,
131+
);
132+
133+
await header.swipe('up', 'fast', 0.25);
134+
await testDetentsVisibility('ThreeDetentsInitialSecond', true, true, false);
135+
136+
await header.swipe('up', 'fast', 0.25);
137+
await testDetentsVisibility('ThreeDetentsInitialSecond', true, true, true);
138+
139+
await header.swipe('down', 'fast', 0.25);
140+
await testDetentsVisibility('ThreeDetentsInitialSecond', true, true, false);
141+
142+
await header.swipe('down', 'fast', 0.25);
143+
await testDetentsVisibility(
144+
'ThreeDetentsInitialSecond',
145+
true,
146+
false,
147+
false,
148+
);
149+
150+
await header.swipe('up', 'fast', 0.5);
151+
await testDetentsVisibility('ThreeDetentsInitialSecond', true, true, true);
152+
153+
await header.swipe('down', 'fast', 0.5);
154+
await testDetentsVisibility(
155+
'ThreeDetentsInitialSecond',
156+
false,
157+
false,
158+
false,
159+
);
160+
});
161+
162+
it('formSheet with 3 detents, initial third, should allow changing detents', async () => {
163+
await testDetentsVisibility(
164+
'ThreeDetentsInitialThird',
165+
false,
166+
false,
167+
false,
168+
);
169+
170+
await element(by.id('home-button-open-ThreeDetentsInitialThird')).tap();
171+
await testDetentsVisibility('ThreeDetentsInitialThird', true, true, true);
172+
173+
const header = element(by.id('ThreeDetentsInitialThird-text-header'));
174+
await header.swipe('down', 'fast', 0.25);
175+
await testDetentsVisibility('ThreeDetentsInitialThird', true, true, false);
176+
177+
await header.swipe('down', 'fast', 0.25);
178+
await testDetentsVisibility('ThreeDetentsInitialThird', true, false, false);
179+
180+
await header.swipe('up', 'fast', 0.25);
181+
await testDetentsVisibility('ThreeDetentsInitialThird', true, true, false);
182+
183+
await header.swipe('up', 'fast', 0.25);
184+
await testDetentsVisibility('ThreeDetentsInitialThird', true, true, true);
185+
186+
await header.swipe('down', 'fast', 0.4);
187+
await testDetentsVisibility('ThreeDetentsInitialThird', true, false, false);
188+
189+
await header.swipe('up', 'fast', 0.5);
190+
await testDetentsVisibility('ThreeDetentsInitialThird', true, true, true);
191+
192+
await header.swipe('down', 'fast', 0.75);
193+
await testDetentsVisibility(
194+
'ThreeDetentsInitialThird',
195+
false,
196+
false,
197+
false,
198+
);
199+
});
200+
});

apps/src/tests/Test2543.tsx

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
import React from 'react';
2+
import {
3+
NavigationContainer,
4+
ParamListBase,
5+
RouteProp,
6+
} from '@react-navigation/native';
7+
import {
8+
NativeStackNavigationOptions,
9+
NativeStackNavigationProp,
10+
createNativeStackNavigator,
11+
} from '@react-navigation/native-stack';
12+
import { Button, Text, View } from 'react-native';
13+
import Colors from '../shared/styling/Colors';
14+
15+
const FORM_SHEET_CONFIGURATIONS: Record<string, NativeStackNavigationOptions> =
16+
{
17+
TwoDetentsInitialFirst: {
18+
sheetAllowedDetents: [0.3, 0.55],
19+
},
20+
TwoDetentsInitialSecond: {
21+
sheetAllowedDetents: [0.3, 0.55],
22+
sheetInitialDetentIndex: 1,
23+
},
24+
ThreeDetentsInitialFirst: {
25+
sheetAllowedDetents: [0.3, 0.55, 0.8],
26+
},
27+
ThreeDetentsInitialSecond: {
28+
sheetAllowedDetents: [0.3, 0.55, 0.8],
29+
sheetInitialDetentIndex: 1,
30+
},
31+
ThreeDetentsInitialThird: {
32+
sheetAllowedDetents: [0.3, 0.55, 0.8],
33+
sheetInitialDetentIndex: 2,
34+
},
35+
};
36+
37+
type StackRouteParamList = {
38+
Home: undefined;
39+
} & {
40+
[P in keyof typeof FORM_SHEET_CONFIGURATIONS]: undefined;
41+
};
42+
43+
type NavigationProp<ParamList extends ParamListBase> = {
44+
navigation: NativeStackNavigationProp<ParamList>;
45+
route: RouteProp<ParamList>;
46+
};
47+
48+
type StackNavigationProp = NavigationProp<StackRouteParamList>;
49+
50+
const Stack = createNativeStackNavigator<StackRouteParamList>();
51+
52+
function Home({ navigation }: StackNavigationProp) {
53+
return (
54+
<View
55+
style={{
56+
backgroundColor: Colors.GreenLight40,
57+
flex: 1,
58+
justifyContent: 'center',
59+
alignItems: 'center',
60+
gap: 15,
61+
}}>
62+
{Object.keys(FORM_SHEET_CONFIGURATIONS).map(key => (
63+
<Button
64+
title={key}
65+
key={key}
66+
onPress={() => navigation.navigate(key)}
67+
testID={`home-button-open-${key}`}
68+
/>
69+
))}
70+
</View>
71+
);
72+
}
73+
74+
function FormSheet({ route }: StackNavigationProp) {
75+
return (
76+
<View style={{ paddingTop: 20 }}>
77+
<Text
78+
style={{
79+
fontSize: 24,
80+
fontWeight: 'bold',
81+
marginBottom: 16,
82+
textAlign: 'center',
83+
}}
84+
testID={`${route.name}-text-header`}>
85+
{route.name}
86+
</Text>
87+
<Text style={{ textAlign: 'center', marginBottom: 16 }}>
88+
You should be able to easily switch between detents.
89+
</Text>
90+
<View
91+
style={{
92+
flex: 1,
93+
alignItems: 'center',
94+
justifyContent: 'center',
95+
height: 180,
96+
}}>
97+
<Text testID={`${route.name}-text-first-detent`}>
98+
This should be visible on first detent.
99+
</Text>
100+
</View>
101+
<View
102+
style={{
103+
flex: 1,
104+
alignItems: 'center',
105+
justifyContent: 'center',
106+
height: 180,
107+
backgroundColor: Colors.PurpleLight40,
108+
}}>
109+
<Text testID={`${route.name}-text-second-detent`}>
110+
This should be visible on second detent.
111+
</Text>
112+
</View>
113+
<View
114+
style={{
115+
flex: 1,
116+
alignItems: 'center',
117+
justifyContent: 'center',
118+
height: 200,
119+
backgroundColor: Colors.NavyLight40,
120+
}}>
121+
<Text testID={`${route.name}-text-third-detent`}>
122+
This should be visible on third detent.
123+
</Text>
124+
</View>
125+
</View>
126+
);
127+
}
128+
129+
export default function App() {
130+
return (
131+
<NavigationContainer>
132+
<Stack.Navigator
133+
screenOptions={{
134+
headerShown: false,
135+
}}>
136+
<Stack.Screen name="Home" component={Home} />
137+
{Object.keys(FORM_SHEET_CONFIGURATIONS).map(key => (
138+
<Stack.Screen
139+
name={key}
140+
key={key}
141+
component={FormSheet}
142+
options={{
143+
presentation: 'formSheet',
144+
sheetGrabberVisible: true,
145+
...FORM_SHEET_CONFIGURATIONS[key],
146+
}}
147+
/>
148+
))}
149+
</Stack.Navigator>
150+
</NavigationContainer>
151+
);
152+
}

apps/src/tests/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ export { default as Test2379 } from './Test2379';
119119
export { default as Test2395 } from './Test2395';
120120
export { default as Test2466 } from './Test2466';
121121
export { default as Test2538 } from './Test2538';
122+
export { default as Test2543 } from './Test2543'; // [E2E created](iOS): issue related to iOS formSheet initial detent
122123
export { default as Test2552 } from './Test2552';
123124
export { default as Test2611 } from './Test2611';
124125
export { default as Test2631 } from './Test2631';

0 commit comments

Comments
 (0)