/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this file, * You can obtain one at http://mozilla.org/MPL/2.0/. */ "use strict"; const {utils: Cu} = Components; Cu.import("chrome://marionette/content/action.js"); Cu.import("chrome://marionette/content/element.js"); Cu.import("chrome://marionette/content/error.js"); action.inputStateMap = new Map(); add_test(function test_createAction() { Assert.throws(() => new action.Action(), InvalidArgumentError, "Missing Action constructor args"); Assert.throws(() => new action.Action(1, 2), InvalidArgumentError, "Missing Action constructor args"); Assert.throws( () => new action.Action(1, 2, "sometype"), /Expected string/, "Non-string arguments."); ok(new action.Action("id", "sometype", "sometype")); run_next_test(); }); add_test(function test_defaultPointerParameters() { let defaultParameters = {pointerType: action.PointerType.Mouse}; deepEqual(action.PointerParameters.fromJson(), defaultParameters); run_next_test(); }); add_test(function test_processPointerParameters() { let check = (regex, message, arg) => checkErrors( regex, action.PointerParameters.fromJson, [arg], message); let parametersData; for (let d of ["foo", "", "get", "Get"]) { parametersData = {pointerType: d}; let message = `parametersData: [pointerType: ${parametersData.pointerType}]`; check(/Unknown pointerType/, message, parametersData); } parametersData.pointerType = "mouse"; //TODO "pen"; deepEqual(action.PointerParameters.fromJson(parametersData), {pointerType: "mouse"}); //TODO action.PointerType.Pen}); run_next_test(); }); add_test(function test_processPointerUpDownAction() { let actionItem = {type: "pointerDown"}; let actionSequence = {type: "pointer", id: "some_id"}; for (let d of [-1, "a"]) { actionItem.button = d; checkErrors( /Expected 'button' \(.*\) to be >= 0/, action.Action.fromJson, [actionSequence, actionItem], `button: ${actionItem.button}`); } actionItem.button = 5; let act = action.Action.fromJson(actionSequence, actionItem); equal(act.button, actionItem.button); run_next_test(); }); add_test(function test_validateActionDurationAndCoordinates() { let actionItem = {}; let actionSequence = {id: "some_id"}; let check = function (type, subtype, message = undefined) { message = message || `duration: ${actionItem.duration}, subtype: ${subtype}`; actionItem.type = subtype; actionSequence.type = type; checkErrors(/Expected '.*' \(.*\) to be >= 0/, action.Action.fromJson, [actionSequence, actionItem], message); }; for (let d of [-1, "a"]) { actionItem.duration = d; check("none", "pause"); check("pointer", "pointerMove"); } actionItem.duration = 5000; for (let name of ["x", "y"]) { actionItem[name] = "a"; actionItem.type = "pointerMove"; actionSequence.type = "pointer"; checkErrors(/Expected '.*' \(.*\) to be an Integer/, action.Action.fromJson, [actionSequence, actionItem], `duration: ${actionItem.duration}, subtype: pointerMove`); } run_next_test(); }); add_test(function test_processPointerMoveActionOriginValidation() { let actionSequence = {type: "pointer", id: "some_id"}; let actionItem = {duration: 5000, type: "pointerMove"}; for (let d of [-1, {a: "blah"}, []]) { actionItem.origin = d; checkErrors(/Expected \'origin\' to be a string or a web element reference/, action.Action.fromJson, [actionSequence, actionItem], `actionItem.origin: (${getTypeString(d)})`); } run_next_test(); }); add_test(function test_processPointerMoveActionOriginStringValidation() { let actionSequence = {type: "pointer", id: "some_id"}; let actionItem = {duration: 5000, type: "pointerMove"}; for (let d of ["a", "", "get", "Get"]) { actionItem.origin = d; checkErrors(/Unknown pointer-move origin/, action.Action.fromJson, [actionSequence, actionItem], `actionItem.origin: ${d}`); } run_next_test(); }); add_test(function test_processPointerMoveActionElementOrigin() { let actionSequence = {type: "pointer", id: "some_id"}; let actionItem = {duration: 5000, type: "pointerMove"}; actionItem.origin = {[element.Key]: "something"}; let a = action.Action.fromJson(actionSequence, actionItem); deepEqual(a.origin, actionItem.origin); run_next_test(); }); add_test(function test_processPointerMoveActionDefaultOrigin() { let actionSequence = {type: "pointer", id: "some_id"}; // origin left undefined let actionItem = {duration: 5000, type: "pointerMove"}; let a = action.Action.fromJson(actionSequence, actionItem); deepEqual(a.origin, action.PointerOrigin.Viewport); run_next_test(); }); add_test(function test_processPointerMoveAction() { let actionSequence = {id: "some_id", type: "pointer"}; let actionItems = [ { duration: 5000, type: "pointerMove", origin: undefined, x: undefined, y: undefined, }, { duration: undefined, type: "pointerMove", origin: {[element.Key]: "id", [element.LegacyKey]: "id"}, x: undefined, y: undefined, }, { duration: 5000, type: "pointerMove", x: 0, y: undefined, origin: undefined, }, { duration: 5000, type: "pointerMove", x: 1, y: 2, origin: undefined, }, ]; for (let expected of actionItems) { let actual = action.Action.fromJson(actionSequence, expected); ok(actual instanceof action.Action); equal(actual.duration, expected.duration); equal(actual.x, expected.x); equal(actual.y, expected.y); let origin = expected.origin; if (typeof origin == "undefined") { origin = action.PointerOrigin.Viewport; } deepEqual(actual.origin, origin); } run_next_test(); }); add_test(function test_computePointerDestinationViewport() { let act = { type: "pointerMove", x: 100, y: 200, origin: "viewport"}; let inputState = new action.InputState.Pointer(action.PointerType.Mouse); // these values should not affect the outcome inputState.x = "99"; inputState.y = "10"; let target = action.computePointerDestination(act, inputState); equal(act.x, target.x); equal(act.y, target.y); run_next_test(); }); add_test(function test_computePointerDestinationPointer() { let act = { type: "pointerMove", x: 100, y: 200, origin: "pointer"}; let inputState = new action.InputState.Pointer(action.PointerType.Mouse); inputState.x = 10; inputState.y = 99; let target = action.computePointerDestination(act, inputState); equal(act.x + inputState.x, target.x); equal(act.y + inputState.y, target.y); run_next_test(); }); add_test(function test_computePointerDestinationElement() { // origin represents a web element // using an object literal instead to test default case in computePointerDestination let act = {type: "pointerMove", x: 100, y: 200, origin: {}}; let inputState = new action.InputState.Pointer(action.PointerType.Mouse); let elementCenter = {x: 10, y: 99}; let target = action.computePointerDestination(act, inputState, elementCenter); equal(act.x + elementCenter.x, target.x); equal(act.y + elementCenter.y, target.y); Assert.throws( () => action.computePointerDestination(act, inputState, {a: 1}), InvalidArgumentError, "Invalid element center coordinates."); Assert.throws( () => action.computePointerDestination(act, inputState, undefined), InvalidArgumentError, "Undefined element center coordinates."); run_next_test(); }); add_test(function test_processPointerAction() { let actionSequence = { type: "pointer", id: "some_id", parameters: { pointerType: "mouse" //TODO "touch" }, }; let actionItems = [ { duration: 2000, type: "pause", }, { type: "pointerMove", duration: 2000, }, { type: "pointerUp", button: 1, } ]; for (let expected of actionItems) { let actual = action.Action.fromJson(actionSequence, expected); equal(actual.type, actionSequence.type); equal(actual.subtype, expected.type); equal(actual.id, actionSequence.id); if (expected.type === "pointerUp") { equal(actual.button, expected.button); } else { equal(actual.duration, expected.duration); } if (expected.type !== "pause") { equal(actual.pointerType, actionSequence.parameters.pointerType); } } run_next_test(); }); add_test(function test_processPauseAction() { let actionItem = {type: "pause", duration: 5000}; let actionSequence = {id: "some_id"}; for (let type of ["none", "key", "pointer"]) { actionSequence.type = type; let act = action.Action.fromJson(actionSequence, actionItem); ok(act instanceof action.Action); equal(act.type, type); equal(act.subtype, actionItem.type); equal(act.id, actionSequence.id); equal(act.duration, actionItem.duration); } actionItem.duration = undefined; let act = action.Action.fromJson(actionSequence, actionItem); equal(act.duration, actionItem.duration); run_next_test(); }); add_test(function test_processActionSubtypeValidation() { let actionItem = {type: "dancing"}; let actionSequence = {id: "some_id"}; let check = function (regex) { let message = `type: ${actionSequence.type}, subtype: ${actionItem.type}`; checkErrors(regex, action.Action.fromJson, [actionSequence, actionItem], message); }; for (let type of ["none", "key", "pointer"]) { actionSequence.type = type; check(new RegExp(`Unknown subtype for ${type} action`)); } run_next_test(); }); add_test(function test_processKeyActionUpDown() { let actionSequence = {type: "key", id: "some_id"}; let actionItem = {type: "keyDown"}; for (let v of [-1, undefined, [], ["a"], {length: 1}, null]) { actionItem.value = v; let message = `actionItem.value: (${getTypeString(v)})`; Assert.throws(() => action.Action.fromJson(actionSequence, actionItem), InvalidArgumentError, message); Assert.throws(() => action.Action.fromJson(actionSequence, actionItem), /Expected 'value' to be a string that represents single code point/, message); } actionItem.value = "\uE004"; let act = action.Action.fromJson(actionSequence, actionItem); ok(act instanceof action.Action); equal(act.type, actionSequence.type); equal(act.subtype, actionItem.type); equal(act.id, actionSequence.id); equal(act.value, actionItem.value); run_next_test(); }); add_test(function test_processInputSourceActionSequenceValidation() { let actionSequence = {type: "swim", id: "some id"}; let check = (message, regex) => checkErrors( regex, action.Sequence.fromJson, [actionSequence], message); check(`actionSequence.type: ${actionSequence.type}`, /Unknown action type/); action.inputStateMap.clear(); actionSequence.type = "none"; actionSequence.id = -1; check(`actionSequence.id: ${getTypeString(actionSequence.id)}`, /Expected 'id' to be a string/); action.inputStateMap.clear(); actionSequence.id = undefined; check(`actionSequence.id: ${getTypeString(actionSequence.id)}`, /Expected 'id' to be defined/); action.inputStateMap.clear(); actionSequence.id = "some_id"; actionSequence.actions = -1; check(`actionSequence.actions: ${getTypeString(actionSequence.actions)}`, /Expected 'actionSequence.actions' to be an Array/); action.inputStateMap.clear(); run_next_test(); }); add_test(function test_processInputSourceActionSequence() { let actionItem = { type: "pause", duration: 5}; let actionSequence = { type: "none", id: "some id", actions: [actionItem], }; let expectedAction = new action.Action(actionSequence.id, "none", actionItem.type); expectedAction.duration = actionItem.duration; let actions = action.Sequence.fromJson(actionSequence); equal(actions.length, 1); deepEqual(actions[0], expectedAction); action.inputStateMap.clear(); run_next_test(); }); add_test(function test_processInputSourceActionSequencePointer() { let actionItem = {type: "pointerDown", button: 1}; let actionSequence = { type: "pointer", id: "9", actions: [actionItem], parameters: { pointerType: "mouse" // TODO "pen" }, }; let expectedAction = new action.Action( actionSequence.id, actionSequence.type, actionItem.type); expectedAction.pointerType = actionSequence.parameters.pointerType; expectedAction.button = actionItem.button; let actions = action.Sequence.fromJson(actionSequence); equal(actions.length, 1); deepEqual(actions[0], expectedAction); action.inputStateMap.clear(); run_next_test(); }); add_test(function test_processInputSourceActionSequenceKey() { let actionItem = {type: "keyUp", value: "a"}; let actionSequence = { type: "key", id: "9", actions: [actionItem], }; let expectedAction = new action.Action( actionSequence.id, actionSequence.type, actionItem.type); expectedAction.value = actionItem.value; let actions = action.Sequence.fromJson(actionSequence); equal(actions.length, 1); deepEqual(actions[0], expectedAction); action.inputStateMap.clear(); run_next_test(); }); add_test(function test_processInputSourceActionSequenceInputStateMap() { let id = "1"; let actionItem = {type: "pause", duration: 5000}; let actionSequence = { type: "key", id: id, actions: [actionItem], }; let wrongInputState = new action.InputState.Null(); action.inputStateMap.set(actionSequence.id, wrongInputState); checkErrors(/to be mapped to/, action.Sequence.fromJson, [actionSequence], `${actionSequence.type} using ${wrongInputState}`); action.inputStateMap.clear(); let rightInputState = new action.InputState.Key(); action.inputStateMap.set(id, rightInputState); let acts = action.Sequence.fromJson(actionSequence); equal(acts.length, 1); action.inputStateMap.clear(); run_next_test(); }); add_test(function test_processPointerActionInputStateMap() { let actionItem = {type: "pointerDown"}; let id = "1"; let parameters = {pointerType: "mouse"}; let a = new action.Action(id, "pointer", actionItem.type); let wrongInputState = new action.InputState.Key(); action.inputStateMap.set(id, wrongInputState); checkErrors( /to be mapped to InputState whose type is/, action.processPointerAction, [id, parameters, a], `type "pointer" with ${wrongInputState.type} in inputState`); action.inputStateMap.clear(); // TODO - uncomment once pen is supported //wrongInputState = new action.InputState.Pointer("pen"); //action.inputStateMap.set(id, wrongInputState); //checkErrors( // /to be mapped to InputState whose subtype is/, action.processPointerAction, // [id, parameters, a], // `subtype ${parameters.pointerType} with ${wrongInputState.subtype} in inputState`); //action.inputStateMap.clear(); let rightInputState = new action.InputState.Pointer("mouse"); action.inputStateMap.set(id, rightInputState); action.processPointerAction(id, parameters, a); action.inputStateMap.clear(); run_next_test(); }); add_test(function test_createInputState() { for (let kind in action.InputState) { let state; if (kind == "Pointer") { state = new action.InputState[kind]("mouse"); } else { state = new action.InputState[kind](); } ok(state); if (kind === "Null") { equal(state.type, "none"); } else { equal(state.type, kind.toLowerCase()); } } Assert.throws(() => new action.InputState.Pointer(), InvalidArgumentError, "Missing InputState.Pointer constructor arg"); Assert.throws(() => new action.InputState.Pointer("foo"), InvalidArgumentError, "Invalid InputState.Pointer constructor arg"); run_next_test(); }); add_test(function test_extractActionChainValidation() { for (let actions of [-1, "a", undefined, null]) { let message = `actions: ${getTypeString(actions)}`; Assert.throws(() => action.Chain.fromJson(actions), InvalidArgumentError, message); Assert.throws(() => action.Chain.fromJson(actions), /Expected 'actions' to be an Array/, message); } run_next_test(); }); add_test(function test_extractActionChainEmpty() { deepEqual(action.Chain.fromJson([]), []); run_next_test(); }); add_test(function test_extractActionChain_oneTickOneInput() { let actionItem = {type: "pause", duration: 5000}; let actionSequence = { type: "none", id: "some id", actions: [actionItem], }; let expectedAction = new action.Action(actionSequence.id, "none", actionItem.type); expectedAction.duration = actionItem.duration; let actionsByTick = action.Chain.fromJson([actionSequence]); equal(1, actionsByTick.length); equal(1, actionsByTick[0].length); deepEqual(actionsByTick, [[expectedAction]]); action.inputStateMap.clear(); run_next_test(); }); add_test(function test_extractActionChain_twoAndThreeTicks() { let mouseActionItems = [ { type: "pointerDown", button: 2, }, { type: "pointerUp", button: 2, }, ]; let mouseActionSequence = { type: "pointer", id: "7", actions: mouseActionItems, parameters: { pointerType: "mouse" //TODO "touch" }, }; let keyActionItems = [ { type: "keyDown", value: "a", }, { type: "pause", duration: 4, }, { type: "keyUp", value: "a", }, ]; let keyActionSequence = { type: "key", id: "1", actions: keyActionItems, }; let actionsByTick = action.Chain.fromJson([keyActionSequence, mouseActionSequence]); // number of ticks is same as longest action sequence equal(keyActionItems.length, actionsByTick.length); equal(2, actionsByTick[0].length); equal(2, actionsByTick[1].length); equal(1, actionsByTick[2].length); let expectedAction = new action.Action(keyActionSequence.id, "key", keyActionItems[2].type); expectedAction.value = keyActionItems[2].value; deepEqual(actionsByTick[2][0], expectedAction); action.inputStateMap.clear(); // one empty action sequence actionsByTick = action.Chain.fromJson( [keyActionSequence, {type: "none", id: "some", actions: []}]); equal(keyActionItems.length, actionsByTick.length); equal(1, actionsByTick[0].length); action.inputStateMap.clear(); run_next_test(); }); add_test(function test_computeTickDuration() { let expected = 8000; let tickActions = [ {type: "none", subtype: "pause", duration: 5000}, {type: "key", subtype: "pause", duration: 1000}, {type: "pointer", subtype: "pointerMove", duration: 6000}, // invalid because keyDown should not have duration, so duration should be ignored. {type: "key", subtype: "keyDown", duration: 100000}, {type: "pointer", subtype: "pause", duration: expected}, {type: "pointer", subtype: "pointerUp"}, ]; equal(expected, action.computeTickDuration(tickActions)); run_next_test(); }); add_test(function test_computeTickDuration_empty() { equal(0, action.computeTickDuration([])); run_next_test(); }); add_test(function test_computeTickDuration_noDurations() { let tickActions = [ // invalid because keyDown should not have duration, so duration should be ignored. {type: "key", subtype: "keyDown", duration: 100000}, // undefined duration permitted {type: "none", subtype: "pause"}, {type: "pointer", subtype: "pointerMove"}, {type: "pointer", subtype: "pointerDown"}, {type: "key", subtype: "keyUp"}, ]; equal(0, action.computeTickDuration(tickActions)); run_next_test(); }); // helpers function getTypeString(obj) { return Object.prototype.toString.call(obj); }; function checkErrors(regex, func, args, message) { if (typeof message == "undefined") { message = `actionFunc: ${func.name}; args: ${args}`; } Assert.throws(() => func.apply(this, args), InvalidArgumentError, message); Assert.throws(() => func.apply(this, args), regex, message); };