Skip to content

Commit

Permalink
Fix several picker woes
Browse files Browse the repository at this point in the history
Closes #1051
Also updated docs
  • Loading branch information
LeoNatan committed Jul 2, 2019
1 parent 3110d32 commit b83a798
Show file tree
Hide file tree
Showing 14 changed files with 197 additions and 43 deletions.
2 changes: 2 additions & 0 deletions detox/ios/Detox/GREYMatchers+Detox.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,4 +28,6 @@

+ (id<GREYMatcher>)detoxMatcherForClass:(NSString *)aClassName;

+ (id<GREYMatcher>)detoxMatcherForPickerViewChildOfMatcher:(id<GREYMatcher>)matcher;

@end
25 changes: 25 additions & 0 deletions detox/ios/Detox/GREYMatchers+Detox.m
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,29 @@ @implementation GREYMatchers (Detox)
return grey_kindOfClass(klass);
}

+ (id<GREYMatcher>)detoxMatcherForPickerViewChildOfMatcher:(id<GREYMatcher>)matcher
{
//No RN—Life is always good.
Class RN_RCTDatePicker = NSClassFromString(@"RCTDatePicker");
if (!RN_RCTDatePicker)
{
return matcher;
}

//Either take picker view or the pickerview that is a child of RCTDatePicker.
return grey_anyOf(grey_allOf(
grey_kindOfClass([UIPickerView class]),
matcher,
nil),
grey_allOf(grey_kindOfClass([UIPickerView class]),
grey_ancestor(
grey_allOf(
matcher,
grey_kindOfClass(RN_RCTDatePicker),
nil)
),
nil),
nil);
}

@end
21 changes: 21 additions & 0 deletions detox/ios/Detox/ReactNativeSupport.m
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,27 @@ static void __setupRNSupport()

[[GREYUIThreadExecutor sharedInstance] registerIdlingResource:[WXAnimatedDisplayLinkIdlingResource new]];
}

//🤦‍♂️ RN doesn't set the data source and relies on undocumented behavior.
cls = NSClassFromString(@"RCTPicker");
if(cls != nil)
{
SEL sel = @selector(initWithFrame:);
Method m = class_getInstanceMethod(cls, sel);

if(m == nil)
{
return;
}

id (*orig)(id, SEL, CGRect) = (void*)method_getImplementation(m);
method_setImplementation(m, imp_implementationWithBlock(^ (UIPickerView<UIPickerViewDataSource>* _self, CGRect frame) {
_self = orig(_self, sel, frame);
_self.dataSource = _self;

return _self;
}));
}
}

@implementation ReactNativeSupport
Expand Down
3 changes: 3 additions & 0 deletions detox/src/android/matcher.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ class Matcher {
return this;
}

_extendPickerViewMatching() {
return this;
}
}

class LabelMatcher extends Matcher {
Expand Down
15 changes: 15 additions & 0 deletions detox/src/ios/earlgreyapi/GREYMatchers+Detox.js
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,21 @@ class GREYMatchers {
};
}

static detoxMatcherForPickerViewChildOfMatcher(matcher) {
if (typeof matcher !== "object" || matcher.type !== "Invocation" || typeof matcher.value !== "object" || typeof matcher.value.target !== "object" || matcher.value.target.value !== "GREYMatchers") {
throw new Error('matcher should be a GREYMatcher, but got ' + JSON.stringify(matcher));
}

return {
target: {
type: "Class",
value: "GREYMatchers"
},
method: "detoxMatcherForPickerViewChildOfMatcher:",
args: [matcher]
};
}

}

module.exports = GREYMatchers;
2 changes: 2 additions & 0 deletions detox/src/ios/expect.js
Original file line number Diff line number Diff line change
Expand Up @@ -339,6 +339,8 @@ class Element {
return await new ActionInteraction(this._invocationManager, this, new SwipeAction(direction, speed, percentage)).execute();
}
async setColumnToValue(column,value) {
// override the user's element selection with an extended matcher that supports RN's date picker
this._selectElementWithMatcher(this._originalMatcher._extendPickerViewMatching());
return await new ActionInteraction(this._invocationManager, this, new ScrollColumnToValue(column, value)).execute();
}
async setDatePickerDate(dateString, dateFormat) {
Expand Down
10 changes: 10 additions & 0 deletions detox/src/ios/matchers.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ class Matcher {
this._call = invoke.callDirectly(GreyMatchersDetox.detoxMatcherForScrollChildOfMatcher(_originalMatcherCall));
return this;
}
_extendToDescendantScrollViews() {
const _originalMatcherCall = this._call;
this._call = invoke.callDirectly(GreyMatchersDetox.detoxMatcherForScrollChildOfMatcher(_originalMatcherCall));
return this;
}
_extendPickerViewMatching() {
const _originalMatcherCall = this._call;
this._call = invoke.callDirectly(GreyMatchersDetox.detoxMatcherForPickerViewChildOfMatcher(_originalMatcherCall));
return this;
}
}

class LabelMatcher extends Matcher {
Expand Down
6 changes: 3 additions & 3 deletions detox/test/e2e/17.datePicker.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,13 @@ describe(':ios: DatePicker', () => {
});

it('datePicker should trigger change handler correctly', async () => {
await element(by.type('UIPickerView')).setColumnToValue(1, "6");
await element(by.type('UIPickerView')).setColumnToValue(2, "34");
await element(by.id('datePicker')).setColumnToValue(1, "6");
await element(by.id('datePicker')).setColumnToValue(2, "34");
await expect(element(by.id('localTimeLabel'))).toHaveText('Time: 06:34');
});

it('can select dates on a UIDatePicker', async () => {
await element(by.type('UIDatePicker')).setDatePickerDate('2019-02-06T05:10:00-08:00', "yyyy-MM-dd'T'HH:mm:ssZZZZZ");
await element(by.id('datePicker')).setDatePickerDate('2019-02-06T05:10:00-08:00', "yyyy-MM-dd'T'HH:mm:ssZZZZZ");
await expect(element(by.id('utcDateLabel'))).toHaveText('Date (UTC): Feb 6th, 2019');
await expect(element(by.id('utcTimeLabel'))).toHaveText('Time (UTC): 1:10 PM');
});
Expand Down
11 changes: 11 additions & 0 deletions detox/test/e2e/17.picker.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
describe(":ios: Picker", () => {
beforeEach(async () => {
await device.reloadReactNative();
await element(by.text("Picker")).tap();
});

it("picker should select value correctly", async () => {
await element(by.id("pickerView")).setColumnToValue(0, "c");
await expect(element(by.id("valueLabel"))).toHaveText("com.wix.detox.c");
});
});
5 changes: 2 additions & 3 deletions detox/test/src/Screens/DatePickerScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export default class DatePickerScreen extends Component {
<Text style={styles.dateText} testID='localTimeLabel'>
{"Time: " + this.getTimeLocal()}
</Text>
<DatePickerIOS style={styles.datePicker} date={this.state.chosenDate} onDateChange={this.setDate} />
<DatePickerIOS testID="datePicker" style={styles.datePicker} date={this.state.chosenDate} onDateChange={this.setDate} />
</View>
);
}
Expand All @@ -58,8 +58,7 @@ const styles = StyleSheet.create({
},
datePicker: {
width:'100%',
height:200,
backgroundColor:'green'
height:200
},
dateText: {
textAlign:'center'
Expand Down
58 changes: 58 additions & 0 deletions detox/test/src/Screens/PickerViewScreen.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import moment from "moment";
import React, { Component } from "react";
import { Text, View, StyleSheet, Picker } from "react-native";

export default class PickerViewScreen extends Component {
constructor(props) {
super(props);

this.state = {
chosenValue: "com.wix.detox.a"
};

this.setValue = this.setValue.bind(this);
}

setValue(newValue) {
this.setState({
chosenValue: newValue
});
}

render() {
return (
<View style={styles.container}>
<Text style={styles.dateText} testID="valueLabel">
{this.state.chosenValue}
</Text>
<Picker
testID="pickerView"
selectedValue={this.state.chosenValue}
style={{width:"100%", height:200}}
onValueChange={this.setValue}>

<Picker.Item label="a" value="com.wix.detox.a" />
<Picker.Item label="b" value="com.wix.detox.b" />
<Picker.Item label="c" value="com.wix.detox.c" />
<Picker.Item label="d" value="com.wix.detox.d" />
</Picker>
</View>
);
}
}

const styles = StyleSheet.create({
container: {
flex: 1,
paddingTop: 20,
justifyContent: "center",
alignItems: "center"
},
datePicker: {
width:"100%",
height:200
},
dateText: {
textAlign:"center"
}
});
4 changes: 3 additions & 1 deletion detox/test/src/Screens/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import ShakeScreen from './ShakeScreen';
import DatePickerScreen from './DatePickerScreen';
import LanguageScreen from './LanguageScreen';
import LaunchArgsScreen from './LaunchArgsScreen';
import PickerViewScreen from './PickerViewScreen';

export {
SanityScreen,
Expand All @@ -34,6 +35,7 @@ export {
LocationScreen,
ShakeScreen,
DatePickerScreen,
PickerViewScreen,
LanguageScreen,
LaunchArgsScreen,
LaunchArgsScreen
};
7 changes: 6 additions & 1 deletion detox/test/src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ class example extends Component {
}

renderScreenButton(title, component) {
if(component == null) {
throw new Error("Got no component for " + title);
}

return this.renderButton(title, () => {
this.setState({screen: component});
});
Expand Down Expand Up @@ -100,7 +104,8 @@ class example extends Component {
{this.renderScreenButton('Network', Screens.NetworkScreen)}
{this.renderScreenButton('Animations', Screens.AnimationsScreen)}
{this.renderScreenButton('Location', Screens.LocationScreen)}
{this.renderScreenButton('DatePicker', Screens.DatePickerScreen)}
{!isAndroid && this.renderScreenButton('DatePicker', Screens.DatePickerScreen)}
{!isAndroid && this.renderScreenButton('Picker', Screens.PickerViewScreen)}
{this.renderButton('Crash', () => {
throw new Error('Simulated Crash')
})}
Expand Down
Loading

0 comments on commit b83a798

Please sign in to comment.