diff --git a/detox/src/ios/earlgreyapi/GREYMatchers+Detox.js b/detox/src/ios/earlgreyapi/GREYMatchers+Detox.js
new file mode 100644
index 0000000000..9bb25fc868
--- /dev/null
+++ b/detox/src/ios/earlgreyapi/GREYMatchers+Detox.js
@@ -0,0 +1,179 @@
+/**
+
+ This code is generated.
+ For more information see generation/README.md.
+*/
+
+
+// Globally declared helpers
+
+function sanitize_greyDirection(action) {
+ switch (action) {
+ case "left":
+ return 1;
+ case "right":
+ return 2;
+ case "up":
+ return 3;
+ case "down":
+ return 4;
+
+ default:
+ throw new Error(`GREYAction.GREYDirection must be a 'left'/'right'/'up'/'down', got ${action}`);
+ }
+}
+
+function sanitize_greyContentEdge(action) {
+ switch (action) {
+ case "left":
+ return 0;
+ case "right":
+ return 1;
+ case "top":
+ return 2;
+ case "bottom":
+ return 3;
+
+ default:
+ throw new Error(`GREYAction.GREYContentEdge must be a 'left'/'right'/'top'/'bottom', got ${action}`);
+ }
+}
+
+
+
+class GREYMatchers {
+ static detoxMatcherForText(text) {
+ if (typeof text !== "string") throw new Error("text should be a string, but got " + (text + (" (" + (typeof text + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "detoxMatcherForText:",
+ args: [{
+ type: "NSString",
+ value: text
+ }]
+ };
+ }
+
+ static detoxMatcherForScrollChildOfMatcher(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: "detoxMatcherForScrollChildOfMatcher:",
+ args: [matcher]
+ };
+ }
+
+ static detoxMatcherAvoidingProblematicReactNativeElements(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: "detoxMatcherAvoidingProblematicReactNativeElements:",
+ args: [matcher]
+ };
+ }
+
+ static detoxMatcherForBothAnd(firstMatcher, secondMatcher) {
+ if (typeof firstMatcher !== "object" || firstMatcher.type !== "Invocation" || typeof firstMatcher.value !== "object" || typeof firstMatcher.value.target !== "object" || firstMatcher.value.target.value !== "GREYMatchers") {
+ throw new Error('firstMatcher should be a GREYMatcher, but got ' + JSON.stringify(firstMatcher));
+ }
+
+ if (typeof secondMatcher !== "object" || secondMatcher.type !== "Invocation" || typeof secondMatcher.value !== "object" || typeof secondMatcher.value.target !== "object" || secondMatcher.value.target.value !== "GREYMatchers") {
+ throw new Error('secondMatcher should be a GREYMatcher, but got ' + JSON.stringify(secondMatcher));
+ }
+
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "detoxMatcherForBoth:and:",
+ args: [firstMatcher, secondMatcher]
+ };
+ }
+
+ static detoxMatcherForBothAndAncestorMatcher(firstMatcher, ancestorMatcher) {
+ if (typeof firstMatcher !== "object" || firstMatcher.type !== "Invocation" || typeof firstMatcher.value !== "object" || typeof firstMatcher.value.target !== "object" || firstMatcher.value.target.value !== "GREYMatchers") {
+ throw new Error('firstMatcher should be a GREYMatcher, but got ' + JSON.stringify(firstMatcher));
+ }
+
+ if (typeof ancestorMatcher !== "object" || ancestorMatcher.type !== "Invocation" || typeof ancestorMatcher.value !== "object" || typeof ancestorMatcher.value.target !== "object" || ancestorMatcher.value.target.value !== "GREYMatchers") {
+ throw new Error('ancestorMatcher should be a GREYMatcher, but got ' + JSON.stringify(ancestorMatcher));
+ }
+
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "detoxMatcherForBoth:andAncestorMatcher:",
+ args: [firstMatcher, ancestorMatcher]
+ };
+ }
+
+ static detoxMatcherForBothAndDescendantMatcher(firstMatcher, descendantMatcher) {
+ if (typeof firstMatcher !== "object" || firstMatcher.type !== "Invocation" || typeof firstMatcher.value !== "object" || typeof firstMatcher.value.target !== "object" || firstMatcher.value.target.value !== "GREYMatchers") {
+ throw new Error('firstMatcher should be a GREYMatcher, but got ' + JSON.stringify(firstMatcher));
+ }
+
+ if (typeof descendantMatcher !== "object" || descendantMatcher.type !== "Invocation" || typeof descendantMatcher.value !== "object" || typeof descendantMatcher.value.target !== "object" || descendantMatcher.value.target.value !== "GREYMatchers") {
+ throw new Error('descendantMatcher should be a GREYMatcher, but got ' + JSON.stringify(descendantMatcher));
+ }
+
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "detoxMatcherForBoth:andDescendantMatcher:",
+ args: [firstMatcher, descendantMatcher]
+ };
+ }
+
+ static detoxMatcherForNot(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: "detoxMatcherForNot:",
+ args: [matcher]
+ };
+ }
+
+ static detoxMatcherForClass(aClassName) {
+ if (typeof aClassName !== "string") throw new Error("aClassName should be a string, but got " + (aClassName + (" (" + (typeof aClassName + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "detoxMatcherForClass:",
+ args: [{
+ type: "NSString",
+ value: aClassName
+ }]
+ };
+ }
+
+}
+
+module.exports = GREYMatchers;
\ No newline at end of file
diff --git a/detox/src/ios/earlgreyapi/GREYMatchers.js b/detox/src/ios/earlgreyapi/GREYMatchers.js
new file mode 100644
index 0000000000..17dbebb7ef
--- /dev/null
+++ b/detox/src/ios/earlgreyapi/GREYMatchers.js
@@ -0,0 +1,577 @@
+/**
+
+ This code is generated.
+ For more information see generation/README.md.
+*/
+
+
+// Globally declared helpers
+
+function sanitize_greyDirection(action) {
+ switch (action) {
+ case "left":
+ return 1;
+ case "right":
+ return 2;
+ case "up":
+ return 3;
+ case "down":
+ return 4;
+
+ default:
+ throw new Error(`GREYAction.GREYDirection must be a 'left'/'right'/'up'/'down', got ${action}`);
+ }
+}
+
+function sanitize_greyContentEdge(action) {
+ switch (action) {
+ case "left":
+ return 0;
+ case "right":
+ return 1;
+ case "top":
+ return 2;
+ case "bottom":
+ return 3;
+
+ default:
+ throw new Error(`GREYAction.GREYContentEdge must be a 'left'/'right'/'top'/'bottom', got ${action}`);
+ }
+}
+
+
+
+class GREYMatchers {
+ /*Matcher for application's key window.
+
+@return A matcher for the application's key window.
+*/static matcherForKeyWindow() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForKeyWindow",
+ args: []
+ };
+ }
+
+ /*Matcher for UI element with the provided accessibility @c label.
+
+@param label The accessibility label to be matched.
+
+@return A matcher for the accessibility label of an accessible element.
+*/static matcherForAccessibilityLabel(label) {
+ if (typeof label !== "string") throw new Error("label should be a string, but got " + (label + (" (" + (typeof label + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForAccessibilityLabel:",
+ args: [{
+ type: "NSString",
+ value: label
+ }]
+ };
+ }
+
+ /*Matcher for UI element with the provided accessibility ID @c accessibilityID.
+
+@param accessibilityID The accessibility ID to be matched.
+
+@return A matcher for the accessibility ID of an accessible element.
+*/static matcherForAccessibilityID(accessibilityID) {
+ if (typeof accessibilityID !== "string") throw new Error("accessibilityID should be a string, but got " + (accessibilityID + (" (" + (typeof accessibilityID + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForAccessibilityID:",
+ args: [{
+ type: "NSString",
+ value: accessibilityID
+ }]
+ };
+ }
+
+ /*Matcher for UI element with the provided accessibility @c value.
+
+@param value The accessibility value to be matched.
+
+@return A matcher for the accessibility value of an accessible element.
+*/static matcherForAccessibilityValue(value) {
+ if (typeof value !== "string") throw new Error("value should be a string, but got " + (value + (" (" + (typeof value + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForAccessibilityValue:",
+ args: [{
+ type: "NSString",
+ value: value
+ }]
+ };
+ }
+
+ /*Matcher for UI element with the provided accessiblity @c hint.
+
+@param hint The accessibility hint to be matched.
+
+@return A matcher for the accessibility hint of an accessible element.
+*/static matcherForAccessibilityHint(hint) {
+ if (typeof hint !== "string") throw new Error("hint should be a string, but got " + (hint + (" (" + (typeof hint + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForAccessibilityHint:",
+ args: [{
+ type: "NSString",
+ value: hint
+ }]
+ };
+ }
+
+ /*Matcher for UI element with accessiblity focus.
+
+@return A matcher for the accessibility focus of an accessible element.
+*/static matcherForAccessibilityElementIsFocused() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForAccessibilityElementIsFocused",
+ args: []
+ };
+ }
+
+ /*Matcher for UI elements of type UIButton, UILabel, UITextField or UITextView displaying the
+provided @c inputText.
+
+@param text The text to be matched in the UI elements.
+
+@return A matcher to check for any UI elements with a text field containing the given text.
+*/static matcherForText(text) {
+ if (typeof text !== "string") throw new Error("text should be a string, but got " + (text + (" (" + (typeof text + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForText:",
+ args: [{
+ type: "NSString",
+ value: text
+ }]
+ };
+ }
+
+ /*Matcher for element that is the first responder.
+
+@return A matcher that verifies if a UI element is the first responder.
+*/static matcherForFirstResponder() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForFirstResponder",
+ args: []
+ };
+ }
+
+ /*Matcher to check if system alert view is shown.
+
+@return A matcher to check if a system alert view is being shown.
+*/static matcherForSystemAlertViewShown() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForSystemAlertViewShown",
+ args: []
+ };
+ }
+
+ /*Matcher for UI element whose percent visible area (of its accessibility frame) exceeds the
+given @c percent.
+
+@param percent The percent visible area that the UI element being matched has to be visible.
+Allowed values for @c percent are [0,1] inclusive.
+
+@return A matcher that checks if a UI element has a visible area at least equal
+to a minimum value.
+*/static matcherForMinimumVisiblePercent(percent) {
+ if (typeof percent !== "number") throw new Error("percent should be a number, but got " + (percent + (" (" + (typeof percent + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForMinimumVisiblePercent:",
+ args: [{
+ type: "CGFloat",
+ value: percent
+ }]
+ };
+ }
+
+ /*Matcher for UI element that is sufficiently visible to the user. EarlGrey considers elements
+with visible area percentage greater than @c kElementSufficientlyVisiblePercentage (0.75)
+to be sufficiently visible.
+
+@return A matcher intialized with a visibility percentage that confirms an element is
+sufficiently visible.
+*/static matcherForSufficientlyVisible() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForSufficientlyVisible",
+ args: []
+ };
+ }
+
+ /*Matcher for UI element that are not visible to the user i.e. has a zero visible area.
+
+@return A matcher for verifying if an element is not visible.
+*/static matcherForNotVisible() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForNotVisible",
+ args: []
+ };
+ }
+
+ /*Matcher for UI element that matches EarlGrey's criteria for user interaction currently it must
+satisfy at least the following criteria:
+
+- At least a few pixels of the element's UI are visible.
+- The element's accessibility activation point OR the center of the element's visible area
+is visible.
+
+
+@return A matcher that checks if a UI element is interactable.
+*/static matcherForInteractable() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForInteractable",
+ args: []
+ };
+ }
+
+ /*Matcher to check if a UI element is accessible.
+
+@return A matcher that checks if a UI element is accessible.
+*/static matcherForAccessibilityElement() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForAccessibilityElement",
+ args: []
+ };
+ }
+
+ /*Matcher for matching UIProgressView's values. Use greaterThan, greaterThanOrEqualTo,
+lessThan etc to create @c comparisonMatcher. For example, to match the UIProgressView
+elements that have progress value greater than 50.2, use
+@code [GREYMatchers matcherForProgress:grey_greaterThan(@(50.2))] @endcode. In case if an
+unimplemented matcher is required, please implement it similar to @c grey_closeTo.
+
+@param comparisonMatcher The matcher with the value to check the progress against.
+
+@return A matcher for checking a UIProgessView's value.
+*/static matcherForProgress(comparisonMatcher) {
+ if (typeof comparisonMatcher !== "object" || comparisonMatcher.type !== "Invocation" || typeof comparisonMatcher.value !== "object" || typeof comparisonMatcher.value.target !== "object" || comparisonMatcher.value.target.value !== "GREYMatchers") {
+ throw new Error('comparisonMatcher should be a GREYMatcher, but got ' + JSON.stringify(comparisonMatcher));
+ }
+
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForProgress:",
+ args: [comparisonMatcher]
+ };
+ }
+
+ /*Matcher that matches UI element based on the presence of an ancestor in its hierarchy.
+The given matcher is used to match decendants.
+
+@param ancestorMatcher The ancestor UI element whose descendants are to be matched.
+
+@return A matcher to check if a UI element is the descendant of another.
+*/static matcherForAncestor(ancestorMatcher) {
+ if (typeof ancestorMatcher !== "object" || ancestorMatcher.type !== "Invocation" || typeof ancestorMatcher.value !== "object" || typeof ancestorMatcher.value.target !== "object" || ancestorMatcher.value.target.value !== "GREYMatchers") {
+ throw new Error('ancestorMatcher should be a GREYMatcher, but got ' + JSON.stringify(ancestorMatcher));
+ }
+
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForAncestor:",
+ args: [ancestorMatcher]
+ };
+ }
+
+ /*Matcher that matches any UI element with a descendant matching the given matcher.
+
+@param descendantMatcher A matcher being checked to be a descendant
+of the UI element being checked.
+
+@return A matcher to check if a the specified element is in a descendant of another UI element.
+*/static matcherForDescendant(descendantMatcher) {
+ if (typeof descendantMatcher !== "object" || descendantMatcher.type !== "Invocation" || typeof descendantMatcher.value !== "object" || typeof descendantMatcher.value.target !== "object" || descendantMatcher.value.target.value !== "GREYMatchers") {
+ throw new Error('descendantMatcher should be a GREYMatcher, but got ' + JSON.stringify(descendantMatcher));
+ }
+
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForDescendant:",
+ args: [descendantMatcher]
+ };
+ }
+
+ /*Matcher that matches UIButton that has title label as @c text.
+
+@param title The title to be checked on the UIButtons being matched.
+
+@return A matcher to confirm UIButton titles.
+*/static matcherForButtonTitle(title) {
+ if (typeof title !== "string") throw new Error("title should be a string, but got " + (title + (" (" + (typeof title + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForButtonTitle:",
+ args: [{
+ type: "NSString",
+ value: title
+ }]
+ };
+ }
+
+ /*Matcher that matches UIScrollView that has contentOffset as @c offset.
+
+@param offset The content offset to be checked on the UIScrollView being
+matched.
+
+@return A matcher to confirm UIScrollView content offset.
+*/static matcherForScrollViewContentOffset(offset) {
+ if (typeof offset !== "object") throw new Error("offset should be a object, but got " + (offset + (" (" + (typeof offset + ")"))));
+ if (typeof offset.x !== "number") throw new Error("offset.x should be a number, but got " + (offset.x + (" (" + (typeof offset.x + ")"))));
+ if (typeof offset.y !== "number") throw new Error("offset.y should be a number, but got " + (offset.y + (" (" + (typeof offset.y + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForScrollViewContentOffset:",
+ args: [{
+ type: "CGPoint",
+ value: offset
+ }]
+ };
+ }
+
+ /*Matcher that matches a UISlider's value.
+
+@param valueMatcher A matcher for the UISlider's value. You must provide a valid
+@c valueMatcher for the floating point value comparison. The
+@c valueMatcher should be of the type @c closeTo, @c greaterThan,
+@c lessThan, @c lessThanOrEqualTo, @c greaterThanOrEqualTo. The
+value matchers should account for any loss in precision for the given
+floating point value. If you are using @c grey_closeTo, use delta diff as
+@c kGREYAcceptableFloatDifference. In case if an unimplemented matcher
+is required, please implement it similar to @c grey_closeTo.
+
+@return A matcher for checking a UISlider's value.
+*/static matcherForSliderValueMatcher(valueMatcher) {
+ if (typeof valueMatcher !== "object" || valueMatcher.type !== "Invocation" || typeof valueMatcher.value !== "object" || typeof valueMatcher.value.target !== "object" || valueMatcher.value.target.value !== "GREYMatchers") {
+ throw new Error('valueMatcher should be a GREYMatcher, but got ' + JSON.stringify(valueMatcher));
+ }
+
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForSliderValueMatcher:",
+ args: [valueMatcher]
+ };
+ }
+
+ /*Matcher that matches UIPickerView that has a column set to @c value.
+
+@param column The column of the UIPickerView to be matched.
+@param value The value that should be set in the column of the UIPickerView.
+
+@return A matcher to check the value in a particular column of a UIPickerView.
+*/static matcherForPickerColumnSetToValue(column, value) {
+ if (typeof column !== "number") throw new Error("column should be a number, but got " + (column + (" (" + (typeof column + ")"))));
+ if (typeof value !== "string") throw new Error("value should be a string, but got " + (value + (" (" + (typeof value + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForPickerColumn:setToValue:",
+ args: [{
+ type: "NSInteger",
+ value: column
+ }, {
+ type: "NSString",
+ value: value
+ }]
+ };
+ }
+
+ /*Matcher that verifies whether an element, that is a UIControl, is enabled.
+
+@return A matcher for checking whether a UI element is an enabled UIControl.
+*/static matcherForEnabledElement() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForEnabledElement",
+ args: []
+ };
+ }
+
+ /*Matcher that verifies whether an element, that is a UIControl, is selected.
+
+@return A matcher for checking whether a UI element is a selected UIControl.
+*/static matcherForSelectedElement() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForSelectedElement",
+ args: []
+ };
+ }
+
+ /*Matcher that verifies whether a view has its userInteractionEnabled property set to @c YES.
+
+@return A matcher for checking whether a view' userInteractionEnabled property is set to @c YES.
+*/static matcherForUserInteractionEnabled() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForUserInteractionEnabled",
+ args: []
+ };
+ }
+
+ /*Matcher primarily for asserting that the element is @c nil or not found.
+
+@return A matcher to check if a specified element is @c nil or not found.
+*/static matcherForNil() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForNil",
+ args: []
+ };
+ }
+
+ /*Matcher for asserting that the element exists in the UI hierarchy (i.e. not @c nil).
+
+@return A matcher to check if a specified element is not @c nil.
+*/static matcherForNotNil() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForNotNil",
+ args: []
+ };
+ }
+
+ /*A Matcher that matches against any object, including @c nils.
+
+@return A matcher that matches any object.
+*/static matcherForAnything() {
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForAnything",
+ args: []
+ };
+ }
+
+ /*Matcher that matches a UIScrollView scrolled to content @c edge.
+
+@param edge The content edge UIScrollView should be scrolled to.
+
+@return A matcher that matches a UIScrollView scrolled to content @c edge.
+*/static matcherForScrolledToContentEdge(edge) {
+ if (!["left", "right", "top", "bottom"].some(option => option === edge)) throw new Error("edge should be one of [left, right, top, bottom], but got " + edge);
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForScrolledToContentEdge:",
+ args: [{
+ type: "NSInteger",
+ value: sanitize_greyContentEdge(edge)
+ }]
+ };
+ }
+
+ /*Matcher that matches a UITextField's content.
+
+@param value The text string contained inside the UITextField.
+
+@return A matcher that matches the value inside a UITextField.
+*/static matcherForTextFieldValue(value) {
+ if (typeof value !== "string") throw new Error("value should be a string, but got " + (value + (" (" + (typeof value + ")"))));
+ return {
+ target: {
+ type: "Class",
+ value: "GREYMatchers"
+ },
+ method: "matcherForTextFieldValue:",
+ args: [{
+ type: "NSString",
+ value: value
+ }]
+ };
+ }
+
+}
+
+module.exports = GREYMatchers;
\ No newline at end of file
diff --git a/detox/src/ios/matchers.js b/detox/src/ios/matchers.js
index badfc113f6..16753d52ef 100644
--- a/detox/src/ios/matchers.js
+++ b/detox/src/ios/matchers.js
@@ -1,37 +1,36 @@
const invoke = require('../invoke');
+const GreyMatchers = require('./earlgreyapi/GREYMatchers');
+const GreyMatchersDetox = require('./earlgreyapi/GREYMatchers+Detox');
class Matcher {
withAncestor(matcher) {
- if (!(matcher instanceof Matcher)) throw new Error(`Matcher withAncestor argument must be a valid Matcher, got ${typeof matcher}`);
const _originalMatcherCall = this._call;
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'detoxMatcherForBoth:andAncestorMatcher:', _originalMatcherCall, matcher._call);
+ this._call = invoke.callDirectly(GreyMatchersDetox.detoxMatcherForBothAndAncestorMatcher(_originalMatcherCall, matcher._call));
return this;
}
withDescendant(matcher) {
- if (!(matcher instanceof Matcher)) throw new Error(`Matcher withDescendant argument must be a valid Matcher, got ${typeof matcher}`);
const _originalMatcherCall = this._call;
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'detoxMatcherForBoth:andDescendantMatcher:', _originalMatcherCall, matcher._call);
+ this._call = invoke.callDirectly(GreyMatchersDetox.detoxMatcherForBothAndDescendantMatcher(_originalMatcherCall, matcher._call));
return this;
}
and(matcher) {
- if (!(matcher instanceof Matcher)) throw new Error(`Matcher and argument must be a valid Matcher, got ${typeof matcher}`);
const _originalMatcherCall = this._call;
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'detoxMatcherForBoth:and:', _originalMatcherCall, matcher._call);
+ this._call = invoke.callDirectly(GreyMatchersDetox.detoxMatcherForBothAnd(_originalMatcherCall, matcher._call));
return this;
}
not() {
const _originalMatcherCall = this._call;
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'detoxMatcherForNot:', _originalMatcherCall);
+ this._call = invoke.callDirectly(GreyMatchersDetox.detoxMatcherForNot(_originalMatcherCall));
return this;
}
_avoidProblematicReactNativeElements() {
const _originalMatcherCall = this._call;
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'detoxMatcherAvoidingProblematicReactNativeElements:', _originalMatcherCall);
+ this._call = invoke.callDirectly(GreyMatchersDetox.detoxMatcherAvoidingProblematicReactNativeElements(_originalMatcherCall));
return this;
}
_extendToDescendantScrollViews() {
const _originalMatcherCall = this._call;
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'detoxMatcherForScrollChildOfMatcher:', _originalMatcherCall);
+ this._call = invoke.callDirectly(GreyMatchersDetox.detoxMatcherForScrollChildOfMatcher(_originalMatcherCall));
return this;
}
}
@@ -47,16 +46,14 @@ class LabelMatcher extends Matcher {
class IdMatcher extends Matcher {
constructor(value) {
super();
- if (typeof value !== 'string') throw new Error(`IdMatcher ctor argument must be a string, got ${typeof value}`);
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'matcherForAccessibilityID:', value);
+ this._call = invoke.callDirectly(GreyMatchers.matcherForAccessibilityID(value));
}
}
class TypeMatcher extends Matcher {
constructor(value) {
super();
- if (typeof value !== 'string') throw new Error(`TypeMatcher ctor argument must be a string, got ${typeof value}`);
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'detoxMatcherForClass:', value);
+ this._call = invoke.callDirectly(GreyMatchersDetox.detoxMatcherForClass(value));
}
}
@@ -93,44 +90,42 @@ class TraitsMatcher extends Matcher {
class VisibleMatcher extends Matcher {
constructor() {
super();
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'matcherForSufficientlyVisible');
+ this._call = invoke.callDirectly(GreyMatchers.matcherForSufficientlyVisible());
}
}
class NotVisibleMatcher extends Matcher {
constructor() {
super();
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'matcherForNotVisible');
+ this._call = invoke.callDirectly(GreyMatchers.matcherForNotVisible());
}
}
class ExistsMatcher extends Matcher {
constructor() {
super();
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'matcherForNotNil');
+ this._call = invoke.callDirectly(GreyMatchers.matcherForNotNil());
}
}
class NotExistsMatcher extends Matcher {
constructor() {
super();
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'matcherForNil');
+ this._call = invoke.callDirectly(GreyMatchers.matcherForNil());
}
}
class TextMatcher extends Matcher {
constructor(value) {
super();
- if (typeof value !== 'string') throw new Error(`TextMatcher ctor argument must be a string, got ${typeof value}`);
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'detoxMatcherForText:', value);
+ this._call = invoke.callDirectly(GreyMatchersDetox.detoxMatcherForText(value));
}
}
class ValueMatcher extends Matcher {
constructor(value) {
super();
- if (typeof value !== 'string') throw new Error(`ValueMatcher ctor argument must be a string, got ${typeof value}`);
- this._call = invoke.call(invoke.IOS.Class('GREYMatchers'), 'matcherForAccessibilityValue:', value);
+ this._call = invoke.callDirectly(GreyMatchers.matcherForAccessibilityValue(value));
}
}
diff --git a/generation/__tests__/__snapshots__/earl-grey.js.snap b/generation/__tests__/__snapshots__/earl-grey.js.snap
index a44881bf56..b5f323fff1 100644
--- a/generation/__tests__/__snapshots__/earl-grey.js.snap
+++ b/generation/__tests__/__snapshots__/earl-grey.js.snap
@@ -92,3 +92,9 @@ Object {
},
}
`;
+
+exports[`earl-grey generation special case: id should fail with wrongly formatted matchers 1`] = `"firstMatcher should be a GREYMatcher, but got {\\"type\\":\\"Invocation\\",\\"value\\":{\\"target\\":{\\"type\\":\\"Class\\",\\"value\\":\\"GREYAction\\"},\\"method\\":\\"matcherForAccessibilityID:\\",\\"args\\":[\\"Grandfather883\\"]}}"`;
+
+exports[`earl-grey generation special case: id should fail with wrongly formatted matchers 2`] = `"ancestorMatcher should be a GREYMatcher, but got {\\"type\\":\\"Invocation\\",\\"value\\":{\\"target\\":{\\"type\\":\\"Class\\",\\"value\\":\\"GREYAction\\"},\\"method\\":\\"matcherForAccessibilityID:\\",\\"args\\":[\\"Grandson883\\"]}}"`;
+
+exports[`earl-grey generation special case: id should fail with wrongly formatted matchers 3`] = `"firstMatcher should be a GREYMatcher, but got {\\"type\\":\\"Invocation\\",\\"value\\":{\\"method\\":\\"matcherForAccessibilityID:\\",\\"args\\":[\\"Grandfather883\\"]}}"`;
diff --git a/generation/__tests__/__snapshots__/helpers.js.snap b/generation/__tests__/__snapshots__/helpers.js.snap
new file mode 100644
index 0000000000..41542647ab
--- /dev/null
+++ b/generation/__tests__/__snapshots__/helpers.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`helpers methodNameToSnakeCase should return the correct snake case method name 1`] = `"actionForScrollInDirectionAmountXOriginStartPercentageYOriginStartPercentage"`;
diff --git a/generation/__tests__/earl-grey.js b/generation/__tests__/earl-grey.js
index 7428516cd0..50f11fdbfe 100644
--- a/generation/__tests__/earl-grey.js
+++ b/generation/__tests__/earl-grey.js
@@ -166,6 +166,162 @@ describe("earl-grey generation", () => {
});
});
+ describe("special case: id", () => {
+ it("should not wrap the args in type id, but pass them in as they are", () => {
+ const ancestorMatcher = {
+ "type": "Invocation",
+ "value": {
+ "target": {
+ "type": "Class",
+ "value": "GREYMatchers"
+ },
+ "method": "matcherForAccessibilityID:",
+ "args": [
+ "Grandson883"
+ ]
+ }
+ };
+ const currentMatcher = {
+ "type": "Invocation",
+ "value": {
+ "target": {
+ "type": "Class",
+ "value": "GREYMatchers"
+ },
+ "method": "matcherForAccessibilityID:",
+ "args": [
+ "Grandfather883"
+ ]
+ }
+ };
+ const result = ExampleClass.detoxMatcherForBothAndAncestorMatcher(currentMatcher, ancestorMatcher);
+
+ expect(result).toEqual({
+ "target": {
+ "type": "Class",
+ "value": "GREYActions"
+ },
+ "method": "detoxMatcherForBoth:andAncestorMatcher:",
+ "args": [
+ {
+ "type": "Invocation",
+ "value": {
+ "target": {
+ "type": "Class",
+ "value": "GREYMatchers"
+ },
+ "method": "matcherForAccessibilityID:",
+ "args": [
+ "Grandfather883"
+ ]
+ }
+ },
+ {
+ "type": "Invocation",
+ "value": {
+ "target": {
+ "type": "Class",
+ "value": "GREYMatchers"
+ },
+ "method": "matcherForAccessibilityID:",
+ "args": [
+ "Grandson883"
+ ]
+ }
+ }
+ ]
+ });
+ });
+
+ it("should fail with wrongly formatted matchers", () => {
+ expect(() => {
+ const ancestorMatcher = {
+ "type": "Invocation",
+ "value": {
+ "target": {
+ "type": "Class",
+ "value": "GREYMatchers"
+ },
+ "method": "matcherForAccessibilityID:",
+ "args": [
+ "Grandson883"
+ ]
+ }
+ };
+ const currentAction = {
+ "type": "Invocation",
+ "value": {
+ "target": {
+ "type": "Class",
+ "value": "GREYAction"
+ },
+ "method": "matcherForAccessibilityID:",
+ "args": [
+ "Grandfather883"
+ ]
+ }
+ };
+ ExampleClass.detoxMatcherForBothAndAncestorMatcher(currentAction, ancestorMatcher);
+ }).toThrowErrorMatchingSnapshot();
+
+ expect(() => {
+ const ancestorAction = {
+ "type": "Invocation",
+ "value": {
+ "target": {
+ "type": "Class",
+ "value": "GREYAction"
+ },
+ "method": "matcherForAccessibilityID:",
+ "args": [
+ "Grandson883"
+ ]
+ }
+ };
+ const currentMatcher = {
+ "type": "Invocation",
+ "value": {
+ "target": {
+ "type": "Class",
+ "value": "GREYMatchers"
+ },
+ "method": "matcherForAccessibilityID:",
+ "args": [
+ "Grandfather883"
+ ]
+ }
+ };
+ ExampleClass.detoxMatcherForBothAndAncestorMatcher(currentMatcher, ancestorAction);
+ }).toThrowErrorMatchingSnapshot();
+
+ expect(() => {
+ const ancestorMatcher = {
+ "type": "Invocation",
+ "value": {
+ "target": {
+ "type": "Class",
+ "value": "GREYAction"
+ },
+ "method": "matcherForAccessibilityID:",
+ "args": [
+ "Grandson883"
+ ]
+ }
+ };
+ const currentMatcher = {
+ "type": "Invocation",
+ "value": {
+ "method": "matcherForAccessibilityID:",
+ "args": [
+ "Grandfather883"
+ ]
+ }
+ };
+ ExampleClass.detoxMatcherForBothAndAncestorMatcher(currentMatcher, ancestorMatcher);
+ }).toThrowErrorMatchingSnapshot();
+ });
+ });
+
afterAll(() => {
// Clean up
remove.removeSync("./__tests__/generated");
diff --git a/generation/__tests__/helpers.js b/generation/__tests__/helpers.js
new file mode 100644
index 0000000000..04e5757829
--- /dev/null
+++ b/generation/__tests__/helpers.js
@@ -0,0 +1,13 @@
+const { methodNameToSnakeCase } = require('../helpers');
+
+describe("helpers", () => {
+ describe("methodNameToSnakeCase", () => {
+ it("should not fail with empty string", () => {
+ expect(() => methodNameToSnakeCase("")).not.toThrow();
+ });
+
+ it("should return the correct snake case method name", () => {
+ expect(methodNameToSnakeCase("actionForScrollInDirection:amount:xOriginStartPercentage:yOriginStartPercentage:")).toMatchSnapshot();
+ });
+ });
+});
diff --git a/generation/earl-grey/index.js b/generation/earl-grey/index.js
index 5d31be4cfe..0f27be9e96 100644
--- a/generation/earl-grey/index.js
+++ b/generation/earl-grey/index.js
@@ -1,4 +1,5 @@
const t = require("babel-types");
+const template = require("babel-template");
const objectiveCParser = require("objective-c-parser");
const generate = require("babel-generator").default;
const fs = require("fs");
@@ -19,6 +20,19 @@ const isPoint = [
generateTypeCheck("number", { selector: "y" })
];
const isOneOf = generateIsOneOfCheck;
+const isGreyMatcher = ({ name }) => template(`
+ if (
+ typeof ARG !== "object" ||
+ ARG.type !== "Invocation" ||
+ typeof ARG.value !== "object" ||
+ typeof ARG.value.target !== "object" ||
+ ARG.value.target.value !== "GREYMatchers"
+ ) {
+ throw new Error('${name} should be a GREYMatcher, but got ' + JSON.stringify(ARG));
+ }
+`)({
+ ARG: t.identifier(name)
+ })
// Constants
const SUPPORTED_TYPES = [
@@ -30,6 +44,7 @@ const SUPPORTED_TYPES = [
"NSString *",
"NSString",
"NSUInteger",
+ "id"
];
/**
@@ -172,8 +187,13 @@ function addArgumentTypeSanitizer(json) {
return json.type;
}
+// These types need no wrapping with {type: ..., value: }
+const plainArgumentTypes = ["id"];
+function shouldBeWrapped({ type }) {
+ return !plainArgumentTypes.includes(type);
+}
function createReturnStatement(className, json) {
- const args = json.args.map(arg =>
+ const args = json.args.map(arg => shouldBeWrapped(arg) ?
t.objectExpression([
t.objectProperty(
t.identifier("type"),
@@ -183,7 +203,7 @@ function createReturnStatement(className, json) {
t.identifier("value"),
addArgumentContentSanitizerCall(arg)
)
- ])
+ ]) : addArgumentContentSanitizerCall(arg)
);
return t.returnStatement(
@@ -214,17 +234,13 @@ function createTypeCheck(json) {
"NSDate *": isNumber,
GREYDirection: isOneOf(["left", "right", "up", "down"]),
GREYContentEdge: isOneOf(["left", "right", "top", "bottom"]),
- GREYPinchDirection: isOneOf(["outward", "inward"])
+ GREYPinchDirection: isOneOf(["outward", "inward"]),
+ "id": isGreyMatcher,
};
const typeCheckCreator = typeInterfaces[json.type];
const isListOfChecks = typeCheckCreator instanceof Array;
- if (typeof typeCheckCreator !== "function" && !isListOfChecks) {
- console.info("Could not find ", json);
- return;
- }
-
return isListOfChecks
? typeCheckCreator.map(singleCheck => singleCheck(json))
: typeCheckCreator(json);
@@ -248,10 +264,13 @@ module.exports = function(files) {
fs.writeFileSync(outputFile, code, "utf8");
// Output methods that were not created due to missing argument support
- console.log(`Could not generate the following methods for ${json.name}`);
- const unsupportedMethods = json.methods.filter(x => !filterMethodsWithUnsupportedParams(x)).forEach(method => {
- const methodArgs = method.args.filter(methodArg => !SUPPORTED_TYPES.includes(methodArg.type)).map(methodArg => methodArg.type);
- console.log(`\t ${method.name} misses ${methodArgs}`);
- });
+ const unsupportedMethods = json.methods.filter(x => !filterMethodsWithUnsupportedParams(x));
+ if (unsupportedMethods.length) {
+ console.log(`Could not generate the following methods for ${json.name}`);
+ unsupportedMethods.forEach(method => {
+ const methodArgs = method.args.filter(methodArg => !SUPPORTED_TYPES.includes(methodArg.type)).map(methodArg => methodArg.type);
+ console.log(`\t ${method.name} misses ${methodArgs}`);
+ });
+ }
});
};
diff --git a/generation/fixtures/example.h b/generation/fixtures/example.h
index 80b007678b..1ea0b904da 100644
--- a/generation/fixtures/example.h
+++ b/generation/fixtures/example.h
@@ -60,4 +60,5 @@
+ (id)actionForScrollToContentEdge:(GREYContentEdge)edge;
+ (id)actionWithUnknownType:(WTFType *)wat;
-+ (id)actionWithKnown:(NSUInteger)iknowdis andUnknownType:(WTFTypalike *)wat;
\ No newline at end of file
++ (id)actionWithKnown:(NSUInteger)iknowdis andUnknownType:(WTFTypalike *)wat;
++ (id)detoxMatcherForBoth:(id)firstMatcher andAncestorMatcher:(id)ancestorMatcher;
\ No newline at end of file
diff --git a/generation/index.js b/generation/index.js
index 404627dd50..16c8c46a61 100755
--- a/generation/index.js
+++ b/generation/index.js
@@ -1,7 +1,9 @@
#!/usr/bin/env node
const generateEarlGreyAdapters = require("./earl-grey");
const files = {
- "../detox/ios/EarlGrey/EarlGrey/Action/GREYActions.h": "../detox/src/ios/earlgreyapi/GREYActions.js"
+ "../detox/ios/EarlGrey/EarlGrey/Action/GREYActions.h": "../detox/src/ios/earlgreyapi/GREYActions.js",
+ "../detox/ios/Detox/GREYMatchers+Detox.h": "../detox/src/ios/earlgreyapi/GREYMatchers+Detox.js",
+ "../detox/ios/EarlGrey/EarlGrey/Matcher/GREYMatchers.h": "../detox/src/ios/earlgreyapi/GREYMatchers.js",
};
generateEarlGreyAdapters(files);
diff --git a/generation/package.json b/generation/package.json
index 89b3dc777b..465365c106 100644
--- a/generation/package.json
+++ b/generation/package.json
@@ -15,7 +15,7 @@
"babel-types": "^6.25.0",
"jest": "^20.0.4",
"lerna": "2.0.0-rc.4",
- "objective-c-parser": "1.0.4",
+ "objective-c-parser": "1.1.0",
"remove": "^0.1.5"
},
"jest": {
@@ -34,6 +34,7 @@
}
},
"dependencies": {
- "babel-generate-guard-clauses": "^0.1.0"
+ "babel-generate-guard-clauses": "^2.0.0",
+ "babel-template": "^6.26.0"
}
-}
+}
\ No newline at end of file