diff --git a/detox/src/android/espressoapi/DetoxAction.js b/detox/src/android/espressoapi/DetoxAction.js index 758ba5f533..c0aabe94f8 100644 --- a/detox/src/android/espressoapi/DetoxAction.js +++ b/detox/src/android/espressoapi/DetoxAction.js @@ -5,7 +5,38 @@ */ - +function sanitize_android_edge(edge) { + switch (edge) { + case "left": + return 1; + case "right": + return 2; + case "top": + return 3; + case "bottom": + return 4; + default: + throw new Error( + `edge must be a 'left'/'right'/'top'/'bottom', got ${edge}` + ); + } +} +function sanitize_android_direction(direction) { + switch (direction) { + case "left": + return 1; + case "right": + return 2; + case "up": + return 3; + case "down": + return 4; + default: + throw new Error( + `direction must be a 'left'/'right'/'up'/'down', got ${direction}` + ); + } +} class DetoxAction { static multiClick(times) { if (typeof times !== "number") throw new Error("times should be a number, but got " + (times + (" (" + (typeof times + ")")))); @@ -42,7 +73,7 @@ class DetoxAction { } static scrollToEdge(edge) { - if (typeof edge !== "number") throw new Error("edge should be a number, but got " + (edge + (" (" + (typeof edge + ")")))); + if (typeof edge !== "string") throw new Error("edge should be a string, but got " + (edge + (" (" + (typeof edge + ")")))); return { target: { type: "Class", @@ -51,13 +82,13 @@ class DetoxAction { method: "scrollToEdge", args: [{ type: "Integer", - value: edge + value: sanitize_android_edge(edge) }] }; } static scrollInDirection(direction, amountInDP) { - if (typeof direction !== "number") throw new Error("direction should be a number, but got " + (direction + (" (" + (typeof direction + ")")))); + if (typeof direction !== "string") throw new Error("direction should be a string, but got " + (direction + (" (" + (typeof direction + ")")))); if (typeof amountInDP !== "number") throw new Error("amountInDP should be a number, but got " + (amountInDP + (" (" + (typeof amountInDP + ")")))); return { target: { @@ -67,14 +98,33 @@ class DetoxAction { method: "scrollInDirection", args: [{ type: "Integer", - value: direction + value: sanitize_android_direction(direction) }, { - type: "double", + type: "Double", value: amountInDP }] }; } + static swipeInDirection(direction, fast) { + if (typeof direction !== "string") throw new Error("direction should be a string, but got " + (direction + (" (" + (typeof direction + ")")))); + if (typeof fast !== "boolean") throw new Error("fast should be a boolean, but got " + (fast + (" (" + (typeof fast + ")")))); + return { + target: { + type: "Class", + value: "com.wix.detox.espresso.DetoxAction" + }, + method: "swipeInDirection", + args: [{ + type: "Integer", + value: sanitize_android_direction(direction) + }, { + type: "boolean", + value: fast + }] + }; + } + } module.exports = DetoxAction; \ No newline at end of file diff --git a/detox/src/android/expect.js b/detox/src/android/expect.js index 20c1baa105..9c3a91e006 100644 --- a/detox/src/android/expect.js +++ b/detox/src/android/expect.js @@ -22,7 +22,6 @@ function setInvocationManager(im) { const ViewActions = 'android.support.test.espresso.action.ViewActions'; const ViewAssertions = 'android.support.test.espresso.assertion.ViewAssertions'; const DetoxMatcher = 'com.wix.detox.espresso.DetoxMatcher'; -const DetoxAction = 'com.wix.detox.espresso.DetoxAction'; const DetoxAssertion = 'com.wix.detox.espresso.DetoxAssertion'; const EspressoDetox = 'com.wix.detox.espresso.EspressoDetox'; @@ -82,31 +81,15 @@ class ClearTextAction extends Action { class ScrollAmountAction extends Action { constructor(direction, amount) { super(); - if (typeof direction !== 'string') throw new Error(`ScrollAmountAction ctor 1st argument must be a string, got ${typeof direction}`); - 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(`ScrollAmountAction direction must be a 'left'/'right'/'up'/'down', got ${direction}`); - } - if (typeof amount !== 'number') throw new Error(`ScrollAmountAction ctor 2nd argument must be a number, got ${typeof amount}`); - this._call = invoke.call(invoke.Android.Class(DetoxAction), 'scrollInDirection', invoke.Android.Integer(direction), invoke.Android.Double(amount)); + this._call = invoke.callDirectly(DetoxActionApi.scrollInDirection(direction, amount)); } } class ScrollEdgeAction extends Action { constructor(edge) { super(); - if (typeof edge !== 'string') throw new Error(`ScrollEdgeAction ctor 1st argument must be a string, got ${typeof edge}`); - switch (edge) { - case 'left': edge = 1; break; - case 'right': edge = 2; break; - case 'top': edge = 3; break; - case 'bottom': edge = 4; break; - default: throw new Error(`ScrollEdgeAction edge must be a 'left'/'right'/'top'/'bottom', got ${edge}`); - } - this._call = invoke.call(invoke.Android.Class(DetoxAction), 'scrollToEdge', invoke.Android.Integer(edge)); + + this._call = invoke.callDirectly(DetoxActionApi.scrollToEdge(edge)); } } @@ -114,19 +97,10 @@ class SwipeAction extends Action { // This implementation ignores the percentage parameter constructor(direction, speed, percentage) { 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.Android.Class(DetoxAction), 'swipeInDirection', invoke.Android.Integer(direction), invoke.Android.Boolean(true)); + this._call = invoke.callDirectly(DetoxActionApi.swipeInDirection(direction, true)); } else if (speed === 'slow') { - this._call = invoke.call(invoke.Android.Class(DetoxAction), 'swipeInDirection', invoke.Android.Integer(direction), invoke.Android.Boolean(false)); + this._call = invoke.callDirectly(DetoxActionApi.swipeInDirection(direction, false)); } else { throw new Error(`SwipeAction speed must be a 'fast'/'slow', got ${speed}`); } diff --git a/generation/__tests__/__snapshots__/android.js.snap b/generation/__tests__/__snapshots__/android.js.snap index 5107f02726..6ff5be163f 100644 --- a/generation/__tests__/__snapshots__/android.js.snap +++ b/generation/__tests__/__snapshots__/android.js.snap @@ -1,5 +1,25 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`Android generation methods should add a sanitizer for the function with the correct name 1`] = ` +Object { + "args": Array [ + Object { + "type": "Integer", + "value": 4, + }, + Object { + "type": "Double", + "value": 42, + }, + ], + "method": "scrollInDirection", + "target": Object { + "type": "Class", + "value": "com.wix.detox.espresso.DetoxAction", + }, +} +`; + exports[`Android generation methods should generate type checks 1`] = `"times should be a number, but got FOO (string)"`; exports[`Android generation methods should return adapter calls 1`] = ` diff --git a/generation/__tests__/__snapshots__/global-functions.js.snap b/generation/__tests__/__snapshots__/global-functions.js.snap index 2c8fd64d54..67cf432741 100644 --- a/generation/__tests__/__snapshots__/global-functions.js.snap +++ b/generation/__tests__/__snapshots__/global-functions.js.snap @@ -1,5 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`globals sanitize_android_direction should fail with unknown value 1`] = `"direction must be a 'left'/'right'/'up'/'down', got kittens"`; + +exports[`globals sanitize_android_edge should fail with unknown value 1`] = `"edge must be a 'left'/'right'/'top'/'bottom', got kittens"`; + exports[`globals sanitize_greyContentEdge should fail with unknown value 1`] = `"GREYAction.GREYContentEdge must be a 'left'/'right'/'top'/'bottom', got kittens"`; exports[`globals sanitize_greyDirection should fail with unknown value 1`] = `"GREYAction.GREYDirection must be a 'left'/'right'/'up'/'down', got kittens"`; diff --git a/generation/__tests__/android.js b/generation/__tests__/android.js index 5718c723d9..9ff7a99a7b 100644 --- a/generation/__tests__/android.js +++ b/generation/__tests__/android.js @@ -50,5 +50,19 @@ describe("Android generation", () => { expect(result).toMatchSnapshot(); }); + + it("should add a sanitizer for the function with the correct name", () => { + const fn = ExampleClass.scrollInDirection; + + expect(() => { + fn(3, 42); + }).toThrowError(); + + expect(() => { + fn("down", 42); + }).not.toThrowError(); + + expect(fn("down", 42)).toMatchSnapshot(); + }); }); }); diff --git a/generation/__tests__/global-functions.js b/generation/__tests__/global-functions.js index f03882d676..088cf49eba 100644 --- a/generation/__tests__/global-functions.js +++ b/generation/__tests__/global-functions.js @@ -1,6 +1,36 @@ const globals = require("../core/global-functions"); describe("globals", () => { + describe("sanitize_android_direction", () => { + it("should return numbers for strings", () => { + expect(globals.sanitize_android_direction("left")).toBe(1); + expect(globals.sanitize_android_direction("right")).toBe(2); + expect(globals.sanitize_android_direction("up")).toBe(3); + expect(globals.sanitize_android_direction("down")).toBe(4); + }); + + it("should fail with unknown value", () => { + expect(() => { + globals.sanitize_android_direction("kittens"); + }).toThrowErrorMatchingSnapshot(); + }); + }); + + describe("sanitize_android_edge", () => { + it("should return numbers for strings", () => { + expect(globals.sanitize_android_edge("left")).toBe(1); + expect(globals.sanitize_android_edge("right")).toBe(2); + expect(globals.sanitize_android_edge("top")).toBe(3); + expect(globals.sanitize_android_edge("bottom")).toBe(4); + }); + + it("should fail with unknown value", () => { + expect(() => { + globals.sanitize_android_edge("kittens"); + }).toThrowErrorMatchingSnapshot(); + }); + }); + describe("sanitize_greyDirection", () => { it("should return numbers for strings", () => { expect(globals.sanitize_greyDirection("left")).toBe(1); @@ -33,9 +63,32 @@ describe("globals", () => { describe("sanitize_uiAccessibilityTraits", () => { it("should return numbers for traits", () => { - expect(globals.sanitize_uiAccessibilityTraits(["button", "link"])).toBe( - 3 - ); + expect(globals.sanitize_uiAccessibilityTraits(["button"])).toBe(1); + + [ + "button", + "link", + "header", + "search", + "image", + "selected", + "plays", + "key", + "text", + "summary", + "disabled", + "frequentUpdates", + "startsMedia", + "adjustable", + "allowsDirectInteraction", + "pageTurn" + ].forEach(trait => { + expect(typeof globals.sanitize_uiAccessibilityTraits([trait])).toBe( + "number" + ); + }); + }); + it("should combine the traits", () => { expect( globals.sanitize_uiAccessibilityTraits([ "summary", diff --git a/generation/adapters/android.js b/generation/adapters/android.js index b3413b3750..d0d7c60ca1 100644 --- a/generation/adapters/android.js +++ b/generation/adapters/android.js @@ -1,19 +1,45 @@ const t = require("babel-types"); const generator = require("../core/generator"); -const { isNumber } = require("../core/type-checks"); +const { isNumber, isString, isBoolean } = require("../core/type-checks"); +const { callGlobal } = require("../helpers"); const typeCheckInterfaces = { Integer: isNumber, - double: isNumber + Double: isNumber, + String: isString, + boolean: isBoolean +}; + +const contentSanitizersForFunction = { + scrollInDirection: { + argumentName: "direction", + newType: "String", + name: "sanitize_android_direction", + value: callGlobal("sanitize_android_direction") + }, + swipeInDirection: { + argumentName: "direction", + newType: "String", + name: "sanitize_android_direction", + value: callGlobal("sanitize_android_direction") + }, + scrollToEdge: { + argumentName: "edge", + newType: "String", + name: "sanitize_android_edge", + value: callGlobal("sanitize_android_edge") + } }; module.exports = generator({ typeCheckInterfaces, - supportedContentSanitizersMap: {}, - supportedTypes: ["Integer", "int", "double"], + contentSanitizersForFunction, + contentSanitizersForType: {}, + supportedTypes: ["Integer", "int", "double", "Double", "boolean"], renameTypesMap: { - int: "Integer" // TODO: add test + int: "Integer", // TODO: add test + double: "Double" }, classValue: ({ package: pkg, name }) => `${pkg}.${name}` }); diff --git a/generation/adapters/ios.js b/generation/adapters/ios.js index d8d36482ca..3a44ffd7f5 100644 --- a/generation/adapters/ios.js +++ b/generation/adapters/ios.js @@ -28,7 +28,7 @@ const typeCheckInterfaces = { UIAccessibilityTraits: isArray }; -const supportedContentSanitizersMap = { +const contentSanitizersForType = { GREYDirection: { type: "NSInteger", name: "sanitize_greyDirection", @@ -48,7 +48,8 @@ const supportedContentSanitizersMap = { module.exports = generator({ typeCheckInterfaces, - supportedContentSanitizersMap, + contentSanitizersForFunction: {}, + contentSanitizersForType, supportedTypes: [ "CGFloat", "CGPoint", diff --git a/generation/core/generator.js b/generation/core/generator.js index 07f172b28e..54902acbb5 100644 --- a/generation/core/generator.js +++ b/generation/core/generator.js @@ -12,7 +12,8 @@ module.exports = function({ renameTypesMap, supportedTypes, classValue, - supportedContentSanitizersMap + contentSanitizersForFunction, + contentSanitizersForType }) { /** * the input provided by objective-c-parser looks like this: @@ -114,7 +115,10 @@ module.exports = function({ args: json.args.map(argJson => sanitizeArgumentType(argJson)) }); - const allTypeChecks = createTypeChecks(sanitizedJson).reduce( + const allTypeChecks = createTypeChecks( + sanitizedJson, + sanitizedJson.name + ).reduce( (carry, item) => item instanceof Array ? [...carry, ...item] : [...carry, item], [] @@ -124,23 +128,33 @@ module.exports = function({ return [...typeChecks, returnStatement]; } - function createTypeChecks(json) { - const checks = json.args.map(createTypeCheck); + function createTypeChecks(json, functionName) { + const checks = json.args.map(arg => createTypeCheck(arg, functionName)); checks.filter(check => Boolean(check)); return checks; } - function addArgumentContentSanitizerCall(json) { - if (supportedContentSanitizersMap[json.type]) { - globalFunctionUsage[supportedContentSanitizersMap[json.type].name] = true; - return supportedContentSanitizersMap[json.type].value(json.name); + function addArgumentContentSanitizerCall(json, functionName) { + if (contentSanitizersForType[json.type]) { + globalFunctionUsage[contentSanitizersForType[json.type].name] = true; + return contentSanitizersForType[json.type].value(json.name); + } + + if ( + contentSanitizersForFunction[functionName] && + contentSanitizersForFunction[functionName].argumentName === json.name + ) { + globalFunctionUsage[ + contentSanitizersForFunction[functionName].name + ] = true; + return contentSanitizersForFunction[functionName].value(json.name); } return t.identifier(json.name); } function addArgumentTypeSanitizer(json) { - if (supportedContentSanitizersMap[json.type]) { - return supportedContentSanitizersMap[json.type].type; + if (contentSanitizersForType[json.type]) { + return contentSanitizersForType[json.type].type; } return json.type; @@ -162,10 +176,10 @@ module.exports = function({ ), t.objectProperty( t.identifier("value"), - addArgumentContentSanitizerCall(arg) + addArgumentContentSanitizerCall(arg, json.name) ) ]) - : addArgumentContentSanitizerCall(arg) + : addArgumentContentSanitizerCall(arg, json.name) ); return t.returnStatement( @@ -186,8 +200,13 @@ module.exports = function({ ); } - function createTypeCheck(json) { - const typeCheckCreator = typeCheckInterfaces[json.type]; + function createTypeCheck(json, functionName) { + const optionalSanitizer = contentSanitizersForFunction[functionName]; + const type = + optionalSanitizer && optionalSanitizer.argumentName === json.name + ? optionalSanitizer.newType + : json.type; + const typeCheckCreator = typeCheckInterfaces[type]; const isListOfChecks = typeCheckCreator instanceof Array; return isListOfChecks ? typeCheckCreator.map(singleCheck => singleCheck(json)) diff --git a/generation/core/global-functions.js b/generation/core/global-functions.js index 17ea1d6d5d..44548d46ca 100644 --- a/generation/core/global-functions.js +++ b/generation/core/global-functions.js @@ -2,6 +2,40 @@ // Each function needs to end with }// END function_name so that it can be // dynamically included while generating +function sanitize_android_direction(direction) { + switch (direction) { + case "left": + return 1; + case "right": + return 2; + case "up": + return 3; + case "down": + return 4; + default: + throw new Error( + `direction must be a 'left'/'right'/'up'/'down', got ${direction}` + ); + } +} // END sanitize_android_direction + +function sanitize_android_edge(edge) { + switch (edge) { + case "left": + return 1; + case "right": + return 2; + case "top": + return 3; + case "bottom": + return 4; + default: + throw new Error( + `edge must be a 'left'/'right'/'top'/'bottom', got ${edge}` + ); + } +} // END sanitize_android_edge + function sanitize_greyDirection(action) { switch (action) { case "left": @@ -105,5 +139,7 @@ function sanitize_uiAccessibilityTraits(value) { module.exports = { sanitize_greyDirection, sanitize_greyContentEdge, - sanitize_uiAccessibilityTraits + sanitize_uiAccessibilityTraits, + sanitize_android_direction, + sanitize_android_edge };