Skip to content

Commit

Permalink
swipe action, closes #29
Browse files Browse the repository at this point in the history
  • Loading branch information
talkol committed Oct 12, 2016
1 parent 05f5616 commit a0965d6
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 2 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 @@ -14,6 +14,8 @@

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

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

+ (id<GREYMatcher>)detoxMatcherForBoth:(id<GREYMatcher>)firstMatcher and:(id<GREYMatcher>)secondMatcher;

+ (id<GREYMatcher>)detoxMatcherForBoth:(id<GREYMatcher>)firstMatcher andAncestorMatcher:(id<GREYMatcher>)ancestorMatcher;
Expand Down
17 changes: 17 additions & 0 deletions detox/ios/Detox/GREYMatchers+Detox.m
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,23 @@ @implementation GREYMatchers (Detox)
grey_ancestor(matcher), nil), nil);
}

+ (id<GREYMatcher>)detoxMatcherAvoidingProblematicReactNativeElements:(id<GREYMatcher>)matcher
{
Class RN_RCTScrollView = NSClassFromString(@"RCTScrollView");
if (!RN_RCTScrollView)
{
return matcher;
}

// RCTScrollView is problematic because EarlGrey's visibility matcher adds a subview and this causes a RN assertion
// solution: if we match RCTScrollView, switch over to matching its contained UIScrollView

return grey_anyOf(grey_allOf(grey_kindOfClass([UIScrollView class]),
grey_ancestor(grey_allOf(matcher, grey_kindOfClass(RN_RCTScrollView), nil)), nil),
grey_allOf(matcher,
grey_not(grey_kindOfClass(RN_RCTScrollView)), nil), nil);
}

+ (id<GREYMatcher>)detoxMatcherForBoth:(id<GREYMatcher>)firstMatcher and:(id<GREYMatcher>)secondMatcher
{
return grey_allOf(firstMatcher, secondMatcher, nil);
Expand Down
32 changes: 32 additions & 0 deletions detox/src/ios/expect.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,11 @@ class Matcher {
this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'detoxMatcherForBoth:andDescendantMatcher:', _originalMatcherCall, matcher._call);
return this;
}
_avoidProblematicReactNativeElements() {
const _originalMatcherCall = this._call;
this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'detoxMatcherAvoidingProblematicReactNativeElements:', _originalMatcherCall);
return this;
}
}

class LabelMatcher extends Matcher {
Expand Down Expand Up @@ -235,6 +240,28 @@ class ScrollEdgeAction extends Action {
}
}

class SwipeAction extends Action {
constructor(direction, speed) {
super();
if (typeof direction !== 'string') throw new Error(`SwipeAction ctor 1st argument must be a string, got ${typeof direction}`);
if (typeof speed !== 'string') throw new Error(`SwipeAction ctor 2nd argument must be a string, got ${typeof speed}`);
switch (direction) {
case 'left': direction = 1; break;
case 'right': direction = 2; break;
case 'up': direction = 3; break;
case 'down': direction = 4; break;
default: throw new Error(`SwipeAction direction must be a 'left'/'right'/'up'/'down', got ${direction}`);
}
if (speed == 'fast') {
this._call = invoke.call(invoke.IOS.Class('GREYActions'), 'actionForSwipeFastInDirection:', invoke.IOS.NSInteger(direction));
} else if (speed == 'slow') {
this._call = invoke.call(invoke.IOS.Class('GREYActions'), 'actionForSwipeSlowInDirection:', invoke.IOS.NSInteger(direction));
} else {
throw new Error(`SwipeAction speed must be a 'fast'/'slow', got ${speed}`);
}
}
}

class Interaction {
execute() {
if (!this._call) throw new Error(`Interaction.execute cannot find a valid _call, got ${typeof this._call}`);
Expand Down Expand Up @@ -353,6 +380,11 @@ class Element {
this._selectElementWithMatcher(new ExtendedScrollMatcher(this._originalMatcher));
return new ActionInteraction(this, new ScrollEdgeAction(edge)).execute();
}
swipe(direction, speed = 'fast') {
// override the user's element selection with an extended matcher that avoids RN issues with RCTScrollView
this._selectElementWithMatcher(this._originalMatcher._avoidProblematicReactNativeElements());
return new ActionInteraction(this, new SwipeAction(direction, speed)).execute();
}
}

class Expect {}
Expand Down
5 changes: 5 additions & 0 deletions detox/test/e2e/c-actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,4 +63,9 @@ describe('Actions', function () {
expect(element(by.label('Text1'))).toBeVisible();
});

it('should swipe down until pull to reload is triggered', function () {
element(by.id('ScrollView799')).swipe('down', 'fast');
expect(element(by.label('PullToReload Working!!!'))).toBeVisible();
});

});
24 changes: 22 additions & 2 deletions detox/test/src/Screens/ActionsScreen.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ import {
View,
TouchableOpacity,
TextInput,
ScrollView
ScrollView,
RefreshControl
} from 'react-native';

export default class ActionsScreen extends Component {
Expand All @@ -15,7 +16,8 @@ export default class ActionsScreen extends Component {
greeting: undefined,
typeText: '',
clearText: 'some stuff here..',
numTaps: 0
numTaps: 0,
isRefreshing: false
};
}

Expand Down Expand Up @@ -60,6 +62,13 @@ export default class ActionsScreen extends Component {
</ScrollView>
</View>

<View style={{height: 100, borderColor: '#c0c0c0', borderWidth: 1, backgroundColor: '#f8f8ff', marginBottom: 20}}>
<ScrollView testID='ScrollView799' refreshControl={
<RefreshControl refreshing={this.state.isRefreshing} onRefresh={this.onRefresh.bind(this)} title="Loading..." />
}>
</ScrollView>
</View>

</View>
);
}
Expand Down Expand Up @@ -108,4 +117,15 @@ export default class ActionsScreen extends Component {
}
}

onRefresh() {
this.setState({
isRefreshing: true
});
setTimeout(() => {
this.setState({
greeting: 'PullToReload Working'
});
}, 500);
}

}

0 comments on commit a0965d6

Please sign in to comment.