Skip to content

Commit 7e984a2

Browse files
authored
chore: refactor e2e tests (#251)
* chore: improve e2e tests * fix placeholder color * use isIOS() * minor updates to test labels
1 parent d9cc4fb commit 7e984a2

File tree

7 files changed

+138
-63
lines changed

7 files changed

+138
-63
lines changed

example/App.js

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,17 @@ const ThemedText = (props) => {
3434
style: [props.style, textColorByMode],
3535
});
3636
};
37+
const ThemedTextInput = (props) => {
38+
const isDarkMode = useColorScheme() === 'dark';
39+
40+
const textColorByMode = {color: isDarkMode ? Colors.white : Colors.black};
41+
42+
const TextElement = React.createElement(TextInput, props);
43+
return React.cloneElement(TextElement, {
44+
style: [props.style, textColorByMode],
45+
placeholderTextColor: isDarkMode ? Colors.white : Colors.black,
46+
});
47+
};
3748

3849
const MODE_VALUES = Platform.select({
3950
ios: Object.values(IOS_MODE),
@@ -52,6 +63,7 @@ export const App = () => {
5263
const [color, setColor] = useState();
5364
const [display, setDisplay] = useState(DISPLAY_VALUES[0]);
5465
const [interval, setMinInterval] = useState(1);
66+
const [neutralButtonLabel, setNeutralButtonLabel] = useState(undefined);
5567

5668
// Windows-specific
5769
const [time, setTime] = useState(undefined);
@@ -72,7 +84,11 @@ export const App = () => {
7284
const currentDate = selectedDate || date;
7385

7486
setShow(Platform.OS === 'ios');
75-
setDate(currentDate);
87+
if (event.type === 'neutralButtonPressed') {
88+
setDate(new Date(0));
89+
} else {
90+
setDate(currentDate);
91+
}
7692
};
7793

7894
const onTimeChange = (event: any, newTime?: Date) => {
@@ -141,7 +157,7 @@ export const App = () => {
141157
<ThemedText style={{margin: 10, flex: 1}}>
142158
text color (iOS only)
143159
</ThemedText>
144-
<TextInput
160+
<ThemedTextInput
145161
value={color}
146162
style={{height: 60, flex: 1}}
147163
onChangeText={(text) => {
@@ -150,6 +166,18 @@ export const App = () => {
150166
placeholder="color"
151167
/>
152168
</View>
169+
<View style={styles.header}>
170+
<ThemedText style={{margin: 10, flex: 1}}>
171+
neutralButtonLabel (android only)
172+
</ThemedText>
173+
<ThemedTextInput
174+
value={neutralButtonLabel}
175+
style={{height: 60, flex: 1}}
176+
onChangeText={setNeutralButtonLabel}
177+
placeholder="neutralButtonLabel"
178+
testID="neutralButtonLabelTextInput"
179+
/>
180+
</View>
153181
<View style={styles.button}>
154182
<Button
155183
testID="showPickerButton"
@@ -186,6 +214,7 @@ export const App = () => {
186214
onChange={onChange}
187215
style={styles.iOsPicker}
188216
textColor={color || undefined}
217+
neutralButtonLabel={neutralButtonLabel}
189218
/>
190219
)}
191220
</View>

example/e2e/detoxTest.spec.js

Lines changed: 36 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,40 @@
1-
const {getTimeText, getDateText} = require('./utils/matchers');
2-
3-
async function userChangesMinuteValue() {
4-
const keyboardIconButton = element(
5-
by.type('androidx.appcompat.widget.AppCompatImageButton'),
6-
);
7-
8-
await keyboardIconButton.tap();
9-
10-
const minuteTextinput = element(
11-
by.type('androidx.appcompat.widget.AppCompatEditText'),
12-
).atIndex(1);
13-
14-
await minuteTextinput.replaceText('30');
15-
}
16-
17-
async function userOpensPicker({mode, display, interval}) {
18-
await element(by.text(mode)).tap();
19-
await element(by.text(display)).tap();
20-
if (interval) {
21-
await element(by.text(String(interval))).tap();
22-
}
23-
await element(by.id('showPickerButton')).tap();
24-
}
25-
26-
async function userTapsCancelButtonAndroid() {
27-
// selecting element by text does not work consistently :/
28-
const cancelButton = element(by.text('Cancel'));
29-
// const cancelButton = element(
30-
// by
31-
// .type('androidx.appcompat.widget.AppCompatButton')
32-
// .withAncestor(by.type('android.widget.ScrollView')),
33-
// ).atIndex(0);
34-
await cancelButton.tap();
35-
}
36-
async function userTapsOkButtonAndroid() {
37-
// selecting element by text does not work consistently :/
38-
const okButton = element(by.text('OK'));
39-
// const okButton = element(
40-
// by
41-
// .type('androidx.appcompat.widget.AppCompatButton')
42-
// .withAncestor(by.type('android.widget.ScrollView')),
43-
// ).atIndex(1);
44-
await okButton.tap();
45-
}
1+
const {
2+
getTimeText,
3+
getDateText,
4+
elementById,
5+
elementByText,
6+
} = require('./utils/matchers');
7+
const {
8+
userChangesMinuteValue,
9+
userOpensPicker,
10+
userTapsCancelButtonAndroid,
11+
userTapsOkButtonAndroid,
12+
} = require('./utils/actions');
13+
const {isAndroid, isIOS} = require('./utils/utils');
4614

4715
describe('Example', () => {
4816
beforeEach(async () => {
49-
if (global.device.getPlatform() === 'ios') {
17+
if (isIOS()) {
5018
await device.reloadReactNative();
5119
} else {
5220
await device.launchApp({newInstance: true});
5321
}
54-
await waitFor(element(by.text('Example DateTime Picker')))
22+
await waitFor(elementByText('Example DateTime Picker'))
5523
.toBeVisible()
5624
.withTimeout(5000);
5725
});
5826

5927
it('should have title and hermes indicator on android', async () => {
60-
await expect(element(by.text('Example DateTime Picker'))).toBeVisible();
61-
if (device.getPlatform() === 'android') {
62-
await expect(element(by.id('hermesIndicator'))).toExist();
28+
await expect(elementByText('Example DateTime Picker')).toBeVisible();
29+
if (isAndroid()) {
30+
await expect(elementById('hermesIndicator')).toExist();
6331
}
6432
});
6533

6634
it('should show date picker after tapping datePicker button', async () => {
6735
await userOpensPicker({mode: 'date', display: 'default'});
6836

69-
if (global.device.getPlatform() === 'ios') {
37+
if (isIOS()) {
7038
await expect(
7139
element(by.type('UIPickerView').withAncestor(by.id('dateTimePicker'))),
7240
).toBeVisible();
@@ -75,10 +43,10 @@ describe('Example', () => {
7543
}
7644
});
7745

78-
it('Nothing should happen if date does not change', async () => {
46+
it('nothing should happen if date does not change', async () => {
7947
await userOpensPicker({mode: 'date', display: 'default'});
8048

81-
if (global.device.getPlatform() === 'ios') {
49+
if (isIOS()) {
8250
await expect(
8351
element(by.type('UIPickerView').withAncestor(by.id('dateTimePicker'))),
8452
).toBeVisible();
@@ -101,7 +69,7 @@ describe('Example', () => {
10169
await userOpensPicker({mode: 'date', display: 'default'});
10270
const dateText = getDateText();
10371

104-
if (global.device.getPlatform() === 'ios') {
72+
if (isIOS()) {
10573
const testElement = element(
10674
by.type('UIPickerView').withAncestor(by.id('dateTimePicker')),
10775
);
@@ -127,7 +95,7 @@ describe('Example', () => {
12795
it('should show time picker after tapping timePicker button', async () => {
12896
await userOpensPicker({mode: 'time', display: 'default'});
12997

130-
if (global.device.getPlatform() === 'ios') {
98+
if (isIOS()) {
13199
await expect(
132100
element(by.type('UIPickerView').withAncestor(by.id('dateTimePicker'))),
133101
).toBeVisible();
@@ -136,10 +104,10 @@ describe('Example', () => {
136104
}
137105
});
138106

139-
it('Nothing should happen if time does not change', async () => {
107+
it('nothing should happen if time does not change', async () => {
140108
await userOpensPicker({mode: 'time', display: 'default'});
141109

142-
if (global.device.getPlatform() === 'ios') {
110+
if (isIOS()) {
143111
await expect(
144112
element(by.type('UIPickerView').withAncestor(by.id('dateTimePicker'))),
145113
).toBeVisible();
@@ -155,7 +123,7 @@ describe('Example', () => {
155123
await userOpensPicker({mode: 'time', display: 'default'});
156124
const timeText = getTimeText();
157125

158-
if (global.device.getPlatform() === 'ios') {
126+
if (isIOS()) {
159127
const testElement = element(
160128
by.type('UIPickerView').withAncestor(by.id('dateTimePicker')),
161129
);
@@ -172,6 +140,15 @@ describe('Example', () => {
172140
}
173141
});
174142

143+
it(':android: given we specify neutralButtonLabel, tapping the corresponding button sets date to the beginning of the unix time epoch', async () => {
144+
await elementById('neutralButtonLabelTextInput').typeText('clear');
145+
await userOpensPicker({mode: 'time', display: 'default'});
146+
await elementByText('clear').tap();
147+
148+
const dateText = getDateText();
149+
await expect(dateText).toHaveText('01/01/1970');
150+
});
151+
175152
describe('given 5-minute interval', () => {
176153
it(':android: clock picker should correct 18-minute selection to 20-minute one', async () => {
177154
try {
@@ -195,7 +172,7 @@ describe('Example', () => {
195172
}
196173
});
197174

198-
it(':android: given picker is shown as a spinner, swiping it down changes selected time', async () => {
175+
it(':android: when the picker is shown as "spinner", swiping it down changes selected time', async () => {
199176
try {
200177
const timeText = getTimeText();
201178

example/e2e/utils/actions.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
async function userChangesMinuteValue() {
2+
const keyboardIconButton = element(
3+
by.type('androidx.appcompat.widget.AppCompatImageButton'),
4+
);
5+
6+
await keyboardIconButton.tap();
7+
8+
const minuteTextinput = element(
9+
by.type('androidx.appcompat.widget.AppCompatEditText'),
10+
).atIndex(1);
11+
12+
await minuteTextinput.replaceText('30');
13+
}
14+
15+
async function userOpensPicker({mode, display, interval}) {
16+
await element(by.text(mode)).tap();
17+
await element(by.text(display)).tap();
18+
if (interval) {
19+
await element(by.text(String(interval))).tap();
20+
}
21+
await element(by.id('showPickerButton')).tap();
22+
}
23+
24+
async function userTapsCancelButtonAndroid() {
25+
// selecting element by text does not work consistently :/
26+
const cancelButton = element(by.text('Cancel'));
27+
// const cancelButton = element(
28+
// by
29+
// .type('androidx.appcompat.widget.AppCompatButton')
30+
// .withAncestor(by.type('android.widget.ScrollView')),
31+
// ).atIndex(0);
32+
await cancelButton.tap();
33+
}
34+
async function userTapsOkButtonAndroid() {
35+
// selecting element by text does not work consistently :/
36+
const okButton = element(by.text('OK'));
37+
// const okButton = element(
38+
// by
39+
// .type('androidx.appcompat.widget.AppCompatButton')
40+
// .withAncestor(by.type('android.widget.ScrollView')),
41+
// ).atIndex(1);
42+
await okButton.tap();
43+
}
44+
45+
module.exports = {
46+
userChangesMinuteValue,
47+
userOpensPicker,
48+
userTapsCancelButtonAndroid,
49+
userTapsOkButtonAndroid,
50+
};

example/e2e/utils/matchers.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
const getTimeText = () => element(by.id('timeText'));
22
const getDateText = () => element(by.id('dateText'));
3+
const elementById = (id) => element(by.id(id));
4+
const elementByText = (text) => element(by.text(text));
35

46
module.exports = {
57
getTimeText,
68
getDateText,
9+
elementById,
10+
elementByText,
711
};

example/e2e/utils/utils.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
const isAndroid = () => device.getPlatform() === 'android';
2+
const isIOS = () => device.getPlatform() === 'ios';
3+
4+
module.exports = {
5+
isAndroid,
6+
isIOS,
7+
};

example/ios/Podfile.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,7 +294,7 @@ PODS:
294294
- React-cxxreact (= 0.62.2)
295295
- React-jsi (= 0.62.2)
296296
- ReactCommon/callinvoker (= 0.62.2)
297-
- RNDateTimePicker (2.6.1):
297+
- RNDateTimePicker (3.0.0):
298298
- React
299299
- Yoga (1.14.0)
300300
- YogaKit (1.18.1):
@@ -459,7 +459,7 @@ SPEC CHECKSUMS:
459459
React-RCTText: fae545b10cfdb3d247c36c56f61a94cfd6dba41d
460460
React-RCTVibration: 4356114dbcba4ce66991096e51a66e61eda51256
461461
ReactCommon: ed4e11d27609d571e7eee8b65548efc191116eb3
462-
RNDateTimePicker: 83540fb5d71f28bd34990d8fabccb8d91e5d957e
462+
RNDateTimePicker: f47a6309133c6d013ad6942fc83b753b3f6e41d6
463463
Yoga: 3ebccbdd559724312790e7742142d062476b698e
464464
YogaKit: f782866e155069a2cca2517aafea43200b01fd5a
465465

package.json

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,14 @@
113113
"avdName": "TestingAVD"
114114
}
115115
},
116+
"android.device.debug": {
117+
"binaryPath": "example/android/app/build/outputs/apk/debug/app-debug.apk",
118+
"build": "export RCT_NO_LAUNCH_PACKAGER=true && (cd example/android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug)",
119+
"type": "android.attached",
120+
"device": {
121+
"adbName": "34HDU19716000753"
122+
}
123+
},
116124
"android.emu.release": {
117125
"binaryPath": "example/android/app/build/outputs/apk/release/app-release.apk",
118126
"build": "export RCT_NO_LAUNCH_PACKAGER=true && (cd example/android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=release)",

0 commit comments

Comments
 (0)