diff --git a/detox/src/devices/IosDriver.js b/detox/src/devices/IosDriver.js index ab61713429..75fe6ce9fc 100644 --- a/detox/src/devices/IosDriver.js +++ b/detox/src/devices/IosDriver.js @@ -5,6 +5,7 @@ const InvocationManager = require('../invoke').InvocationManager; const invoke = require('../invoke'); const GREYConfigurationApi = require('./../ios/earlgreyapi/GREYConfiguration'); const GREYConfigurationDetox = require('./../ios/earlgreyapi/GREYConfigurationDetox'); +const EarlyGrey = require('./../ios/earlgreyapi/EarlGrey'); class IosDriver extends DeviceDriverBase { constructor(client) { @@ -55,19 +56,8 @@ class IosDriver extends DeviceDriverBase { } async setOrientation(deviceId, orientation) { - // keys are possible orientations - const orientationMapping = { - landscape: 3, // top at left side landscape - portrait: 1 // non-reversed portrait - }; - if (!Object.keys(orientationMapping).includes(orientation)) { - throw new Error(`setOrientation failed: provided orientation ${orientation} is not part of supported orientations: ${Object.keys(orientationMapping)}`); - } + const call = EarlyGrey.rotateDeviceToOrientationErrorOrNil(invoke.EarlGrey.instance,orientation); - const call = invoke.call(invoke.EarlGrey.instance, - 'rotateDeviceToOrientation:errorOrNil:', - invoke.IOS.NSInteger(orientationMapping[orientation]) - ); await this.client.execute(call); } diff --git a/detox/src/ios/earlgreyapi/EarlGrey.js b/detox/src/ios/earlgreyapi/EarlGrey.js new file mode 100644 index 0000000000..217f3c917e --- /dev/null +++ b/detox/src/ios/earlgreyapi/EarlGrey.js @@ -0,0 +1,83 @@ +/** + + This code is generated. + For more information see generation/README.md. +*/ + + +function sanitize_uiDeviceOrientation(value) { + const orientationMapping = { + landscape: 3, // top at left side landscape + portrait: 1 // non-reversed portrait + }; + + return orientationMapping[value]; +} +class EarlGreyImpl { + /*Provides the file name and line number of the code that is calling into EarlGrey. +In case of a failure, the information is used to tell XCTest the exact line which caused +the failure so it can be highlighted in the IDE. + +@param fileName The name of the file where the failing code exists. +@param lineNumber The line number of the failing code. + +@return An EarlGreyImpl instance, with details of the code invoking EarlGrey. +*/static invokedFromFileLineNumber(fileName, lineNumber) { + if (typeof fileName !== "string") throw new Error("fileName should be a string, but got " + (fileName + (" (" + (typeof fileName + ")")))); + if (typeof lineNumber !== "number") throw new Error("lineNumber should be a number, but got " + (lineNumber + (" (" + (typeof lineNumber + ")")))); + return { + target: { + type: "Class", + value: "EarlGreyImpl" + }, + method: "invokedFromFile:lineNumber:", + args: [{ + type: "NSString", + value: fileName + }, { + type: "NSInteger", + value: lineNumber + }] + }; + } + + /*Rotate the device to a given @c deviceOrientation. All device orientations except for +@c UIDeviceOrientationUnknown are supported. If a non-nil @c errorOrNil is provided, it will +be populated with the failure reason if the orientation change fails, otherwise a test failure +will be registered. + +@param deviceOrientation The desired orientation of the device. +@param[out] errorOrNil Error that will be populated on failure. If @c nil, a test +failure will be reported if the rotation attempt fails. + +@return @c YES if the rotation was successful, @c NO otherwise. +*/static rotateDeviceToOrientationErrorOrNil(element, deviceOrientation) { + if (!["landscape", "portrait"].some(option => option === deviceOrientation)) throw new Error("deviceOrientation should be one of [landscape, portrait], but got " + deviceOrientation); + return { + target: element, + method: "rotateDeviceToOrientation:errorOrNil:", + args: [{ + type: "NSInteger", + value: sanitize_uiDeviceOrientation(deviceOrientation) + }] + }; + } + + /*Dismisses the keyboard by resigning the first responder, if any. Will populate the provided +error if the first responder is not present or if the keyboard is not visible. + +@param[out] errorOrNil Error that will be populated on failure. If @c nil, a test +failure will be reported if the dismissing fails. + +@return @c YES if the dismissing of the keyboard was successful, @c NO otherwise. +*/static dismissKeyboardWithError(element) { + return { + target: element, + method: "dismissKeyboardWithError:", + args: [] + }; + } + +} + +module.exports = EarlGreyImpl; \ No newline at end of file diff --git a/detox/src/ios/earlgreyapi/GREYInteraction.js b/detox/src/ios/earlgreyapi/GREYInteraction.js index 088cbf0b0b..e448a177fd 100644 --- a/detox/src/ios/earlgreyapi/GREYInteraction.js +++ b/detox/src/ios/earlgreyapi/GREYInteraction.js @@ -81,6 +81,26 @@ performAction:grey_tap()] // This should be separately called for the action. }; } + /*Performs an @c action on the selected UI element with an error set on failure. + +@param action The action to be performed on the @c element. +@param[out] errorOrNil Error populated on failure. +@throws NSException on action failure if @c errorOrNil is not set. + +@return The provided GREYInteraction instance, with an action and an error that will be +populated on failure. +*/static performActionError(element, action) { + if (typeof action !== "object" || action.type !== "Invocation" || typeof action.value !== "object" || typeof action.value.target !== "object" || action.value.target.value !== "GREYActions") { + throw new Error('action should be a GREYAction, but got ' + JSON.stringify(action)); + } + + return { + target: element, + method: "performAction:error:", + args: [action] + }; + } + /*Performs an assertion that evaluates @c matcher on the selected UI element. @param matcher The matcher to be evaluated on the @c element. @@ -98,6 +118,26 @@ performAction:grey_tap()] // This should be separately called for the action. }; } + /*Performs an assertion that evaluates @c matcher on the selected UI element. + +@param matcher The matcher to be evaluated on the @c element. +@param[out] errorOrNil Error populated on failure. +@throws NSException on assertion failure if @c errorOrNil is not set. + +@return The provided GREYInteraction instance, with a matcher to be evaluated on an element and +an error that will be populated on failure. +*/static assertWithMatcherError(element, 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: element, + method: "assertWithMatcher:error:", + args: [matcher] + }; + } + /*In case of multiple matches, selects the element at the specified index. In case of the index being over the number of matched elements, it throws an exception. Please make sure that this is used after you've created the matcher. For example, in case three elements are diff --git a/generation/adapters/ios.js b/generation/adapters/ios.js index 30df668659..3167a60b91 100644 --- a/generation/adapters/ios.js +++ b/generation/adapters/ios.js @@ -31,7 +31,8 @@ const typeCheckInterfaces = { 'id': isGreyMatcher, 'GREYElementInteraction*': isGreyElementInteraction, UIAccessibilityTraits: isArray, - id: isDefined + id: isDefined, + UIDeviceOrientation: isOneOf(["landscape", "portrait"]) }; const contentSanitizersForType = { @@ -50,10 +51,15 @@ const contentSanitizersForType = { name: 'sanitize_uiAccessibilityTraits', value: callGlobal('sanitize_uiAccessibilityTraits') }, - 'GREYElementInteraction*': { - type: 'Invocation', - name: 'sanitize_greyElementInteraction', - value: callGlobal('sanitize_greyElementInteraction') + "GREYElementInteraction*": { + type: "Invocation", + name: "sanitize_greyElementInteraction", + value: callGlobal("sanitize_greyElementInteraction") + }, + UIDeviceOrientation: { + type: "NSInteger", + name: "sanitize_uiDeviceOrientation", + value: callGlobal("sanitize_uiDeviceOrientation") } }; @@ -62,20 +68,36 @@ module.exports = generator({ contentSanitizersForFunction: {}, contentSanitizersForType, supportedTypes: [ + 'CFTimeInterval', 'CGFloat', 'CGPoint', 'GREYContentEdge', 'GREYDirection', 'GREYElementInteraction*', + 'id', + 'id', + 'id', 'NSInteger', 'NSString *', 'NSString', 'NSUInteger', - 'id', - 'id', - 'CFTimeInterval', 'UIAccessibilityTraits', - 'id' + "__strong NSError **", + "CFTimeInterval", + "CGFloat", + "CGPoint", + "GREYContentEdge", + "GREYDirection", + "GREYElementInteraction*", + "id", + "id", + "id", + "NSInteger", + "NSString *", + "NSString", + "NSUInteger", + "UIAccessibilityTraits", + "UIDeviceOrientation", ], renameTypesMap: { NSUInteger: 'NSInteger', diff --git a/generation/core/global-functions.js b/generation/core/global-functions.js index 8ac4a2dc22..28cc27a12b 100644 --- a/generation/core/global-functions.js +++ b/generation/core/global-functions.js @@ -138,6 +138,15 @@ function sanitize_greyElementInteraction(value) { }; } // END sanitize_greyElementInteraction +function sanitize_uiDeviceOrientation(value) { + const orientationMapping = { + landscape: 3, // top at left side landscape + portrait: 1 // non-reversed portrait + }; + + return orientationMapping[value]; +} // END sanitize_uiDeviceOrientation + module.exports = { sanitize_greyDirection, sanitize_greyContentEdge, @@ -145,5 +154,6 @@ module.exports = { sanitize_android_direction, sanitize_android_edge, sanitize_matcher, - sanitize_greyElementInteraction + sanitize_greyElementInteraction, + sanitize_uiDeviceOrientation }; diff --git a/generation/index.js b/generation/index.js index b44e41b7cb..5d292c1e2e 100755 --- a/generation/index.js +++ b/generation/index.js @@ -16,7 +16,9 @@ const iosFiles = { "../detox/ios/Detox/GREYConfiguration+Detox.h": "../detox/src/ios/earlgreyapi/GREYConfigurationDetox.js", "../detox/ios/EarlGrey/EarlGrey/Common/GREYConfiguration.h": - "../detox/src/ios/earlgreyapi/GREYConfiguration.js" + "../detox/src/ios/earlgreyapi/GREYConfiguration.js", + "../detox/ios/EarlGrey/EarlGrey/EarlGrey.h": + "../detox/src/ios/earlgreyapi/EarlGrey.js" }; generateIOSAdapters(iosFiles);