test_action.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628
  1. /* This Source Code Form is subject to the terms of the Mozilla Public
  2. * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  3. * You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. const {utils: Cu} = Components;
  6. Cu.import("chrome://marionette/content/action.js");
  7. Cu.import("chrome://marionette/content/element.js");
  8. Cu.import("chrome://marionette/content/error.js");
  9. action.inputStateMap = new Map();
  10. add_test(function test_createAction() {
  11. Assert.throws(() => new action.Action(), InvalidArgumentError,
  12. "Missing Action constructor args");
  13. Assert.throws(() => new action.Action(1, 2), InvalidArgumentError,
  14. "Missing Action constructor args");
  15. Assert.throws(
  16. () => new action.Action(1, 2, "sometype"), /Expected string/, "Non-string arguments.");
  17. ok(new action.Action("id", "sometype", "sometype"));
  18. run_next_test();
  19. });
  20. add_test(function test_defaultPointerParameters() {
  21. let defaultParameters = {pointerType: action.PointerType.Mouse};
  22. deepEqual(action.PointerParameters.fromJson(), defaultParameters);
  23. run_next_test();
  24. });
  25. add_test(function test_processPointerParameters() {
  26. let check = (regex, message, arg) => checkErrors(
  27. regex, action.PointerParameters.fromJson, [arg], message);
  28. let parametersData;
  29. for (let d of ["foo", "", "get", "Get"]) {
  30. parametersData = {pointerType: d};
  31. let message = `parametersData: [pointerType: ${parametersData.pointerType}]`;
  32. check(/Unknown pointerType/, message, parametersData);
  33. }
  34. parametersData.pointerType = "mouse"; //TODO "pen";
  35. deepEqual(action.PointerParameters.fromJson(parametersData),
  36. {pointerType: "mouse"}); //TODO action.PointerType.Pen});
  37. run_next_test();
  38. });
  39. add_test(function test_processPointerUpDownAction() {
  40. let actionItem = {type: "pointerDown"};
  41. let actionSequence = {type: "pointer", id: "some_id"};
  42. for (let d of [-1, "a"]) {
  43. actionItem.button = d;
  44. checkErrors(
  45. /Expected 'button' \(.*\) to be >= 0/, action.Action.fromJson, [actionSequence, actionItem],
  46. `button: ${actionItem.button}`);
  47. }
  48. actionItem.button = 5;
  49. let act = action.Action.fromJson(actionSequence, actionItem);
  50. equal(act.button, actionItem.button);
  51. run_next_test();
  52. });
  53. add_test(function test_validateActionDurationAndCoordinates() {
  54. let actionItem = {};
  55. let actionSequence = {id: "some_id"};
  56. let check = function (type, subtype, message = undefined) {
  57. message = message || `duration: ${actionItem.duration}, subtype: ${subtype}`;
  58. actionItem.type = subtype;
  59. actionSequence.type = type;
  60. checkErrors(/Expected '.*' \(.*\) to be >= 0/,
  61. action.Action.fromJson, [actionSequence, actionItem], message);
  62. };
  63. for (let d of [-1, "a"]) {
  64. actionItem.duration = d;
  65. check("none", "pause");
  66. check("pointer", "pointerMove");
  67. }
  68. actionItem.duration = 5000;
  69. for (let name of ["x", "y"]) {
  70. actionItem[name] = "a";
  71. actionItem.type = "pointerMove";
  72. actionSequence.type = "pointer";
  73. checkErrors(/Expected '.*' \(.*\) to be an Integer/,
  74. action.Action.fromJson, [actionSequence, actionItem],
  75. `duration: ${actionItem.duration}, subtype: pointerMove`);
  76. }
  77. run_next_test();
  78. });
  79. add_test(function test_processPointerMoveActionOriginValidation() {
  80. let actionSequence = {type: "pointer", id: "some_id"};
  81. let actionItem = {duration: 5000, type: "pointerMove"};
  82. for (let d of [-1, {a: "blah"}, []]) {
  83. actionItem.origin = d;
  84. checkErrors(/Expected \'origin\' to be a string or a web element reference/,
  85. action.Action.fromJson,
  86. [actionSequence, actionItem],
  87. `actionItem.origin: (${getTypeString(d)})`);
  88. }
  89. run_next_test();
  90. });
  91. add_test(function test_processPointerMoveActionOriginStringValidation() {
  92. let actionSequence = {type: "pointer", id: "some_id"};
  93. let actionItem = {duration: 5000, type: "pointerMove"};
  94. for (let d of ["a", "", "get", "Get"]) {
  95. actionItem.origin = d;
  96. checkErrors(/Unknown pointer-move origin/,
  97. action.Action.fromJson,
  98. [actionSequence, actionItem],
  99. `actionItem.origin: ${d}`);
  100. }
  101. run_next_test();
  102. });
  103. add_test(function test_processPointerMoveActionElementOrigin() {
  104. let actionSequence = {type: "pointer", id: "some_id"};
  105. let actionItem = {duration: 5000, type: "pointerMove"};
  106. actionItem.origin = {[element.Key]: "something"};
  107. let a = action.Action.fromJson(actionSequence, actionItem);
  108. deepEqual(a.origin, actionItem.origin);
  109. run_next_test();
  110. });
  111. add_test(function test_processPointerMoveActionDefaultOrigin() {
  112. let actionSequence = {type: "pointer", id: "some_id"};
  113. // origin left undefined
  114. let actionItem = {duration: 5000, type: "pointerMove"};
  115. let a = action.Action.fromJson(actionSequence, actionItem);
  116. deepEqual(a.origin, action.PointerOrigin.Viewport);
  117. run_next_test();
  118. });
  119. add_test(function test_processPointerMoveAction() {
  120. let actionSequence = {id: "some_id", type: "pointer"};
  121. let actionItems = [
  122. {
  123. duration: 5000,
  124. type: "pointerMove",
  125. origin: undefined,
  126. x: undefined,
  127. y: undefined,
  128. },
  129. {
  130. duration: undefined,
  131. type: "pointerMove",
  132. origin: {[element.Key]: "id", [element.LegacyKey]: "id"},
  133. x: undefined,
  134. y: undefined,
  135. },
  136. {
  137. duration: 5000,
  138. type: "pointerMove",
  139. x: 0,
  140. y: undefined,
  141. origin: undefined,
  142. },
  143. {
  144. duration: 5000,
  145. type: "pointerMove",
  146. x: 1,
  147. y: 2,
  148. origin: undefined,
  149. },
  150. ];
  151. for (let expected of actionItems) {
  152. let actual = action.Action.fromJson(actionSequence, expected);
  153. ok(actual instanceof action.Action);
  154. equal(actual.duration, expected.duration);
  155. equal(actual.x, expected.x);
  156. equal(actual.y, expected.y);
  157. let origin = expected.origin;
  158. if (typeof origin == "undefined") {
  159. origin = action.PointerOrigin.Viewport;
  160. }
  161. deepEqual(actual.origin, origin);
  162. }
  163. run_next_test();
  164. });
  165. add_test(function test_computePointerDestinationViewport() {
  166. let act = { type: "pointerMove", x: 100, y: 200, origin: "viewport"};
  167. let inputState = new action.InputState.Pointer(action.PointerType.Mouse);
  168. // these values should not affect the outcome
  169. inputState.x = "99";
  170. inputState.y = "10";
  171. let target = action.computePointerDestination(act, inputState);
  172. equal(act.x, target.x);
  173. equal(act.y, target.y);
  174. run_next_test();
  175. });
  176. add_test(function test_computePointerDestinationPointer() {
  177. let act = { type: "pointerMove", x: 100, y: 200, origin: "pointer"};
  178. let inputState = new action.InputState.Pointer(action.PointerType.Mouse);
  179. inputState.x = 10;
  180. inputState.y = 99;
  181. let target = action.computePointerDestination(act, inputState);
  182. equal(act.x + inputState.x, target.x);
  183. equal(act.y + inputState.y, target.y);
  184. run_next_test();
  185. });
  186. add_test(function test_computePointerDestinationElement() {
  187. // origin represents a web element
  188. // using an object literal instead to test default case in computePointerDestination
  189. let act = {type: "pointerMove", x: 100, y: 200, origin: {}};
  190. let inputState = new action.InputState.Pointer(action.PointerType.Mouse);
  191. let elementCenter = {x: 10, y: 99};
  192. let target = action.computePointerDestination(act, inputState, elementCenter);
  193. equal(act.x + elementCenter.x, target.x);
  194. equal(act.y + elementCenter.y, target.y);
  195. Assert.throws(
  196. () => action.computePointerDestination(act, inputState, {a: 1}),
  197. InvalidArgumentError,
  198. "Invalid element center coordinates.");
  199. Assert.throws(
  200. () => action.computePointerDestination(act, inputState, undefined),
  201. InvalidArgumentError,
  202. "Undefined element center coordinates.");
  203. run_next_test();
  204. });
  205. add_test(function test_processPointerAction() {
  206. let actionSequence = {
  207. type: "pointer",
  208. id: "some_id",
  209. parameters: {
  210. pointerType: "mouse" //TODO "touch"
  211. },
  212. };
  213. let actionItems = [
  214. {
  215. duration: 2000,
  216. type: "pause",
  217. },
  218. {
  219. type: "pointerMove",
  220. duration: 2000,
  221. },
  222. {
  223. type: "pointerUp",
  224. button: 1,
  225. }
  226. ];
  227. for (let expected of actionItems) {
  228. let actual = action.Action.fromJson(actionSequence, expected);
  229. equal(actual.type, actionSequence.type);
  230. equal(actual.subtype, expected.type);
  231. equal(actual.id, actionSequence.id);
  232. if (expected.type === "pointerUp") {
  233. equal(actual.button, expected.button);
  234. } else {
  235. equal(actual.duration, expected.duration);
  236. }
  237. if (expected.type !== "pause") {
  238. equal(actual.pointerType, actionSequence.parameters.pointerType);
  239. }
  240. }
  241. run_next_test();
  242. });
  243. add_test(function test_processPauseAction() {
  244. let actionItem = {type: "pause", duration: 5000};
  245. let actionSequence = {id: "some_id"};
  246. for (let type of ["none", "key", "pointer"]) {
  247. actionSequence.type = type;
  248. let act = action.Action.fromJson(actionSequence, actionItem);
  249. ok(act instanceof action.Action);
  250. equal(act.type, type);
  251. equal(act.subtype, actionItem.type);
  252. equal(act.id, actionSequence.id);
  253. equal(act.duration, actionItem.duration);
  254. }
  255. actionItem.duration = undefined;
  256. let act = action.Action.fromJson(actionSequence, actionItem);
  257. equal(act.duration, actionItem.duration);
  258. run_next_test();
  259. });
  260. add_test(function test_processActionSubtypeValidation() {
  261. let actionItem = {type: "dancing"};
  262. let actionSequence = {id: "some_id"};
  263. let check = function (regex) {
  264. let message = `type: ${actionSequence.type}, subtype: ${actionItem.type}`;
  265. checkErrors(regex, action.Action.fromJson, [actionSequence, actionItem], message);
  266. };
  267. for (let type of ["none", "key", "pointer"]) {
  268. actionSequence.type = type;
  269. check(new RegExp(`Unknown subtype for ${type} action`));
  270. }
  271. run_next_test();
  272. });
  273. add_test(function test_processKeyActionUpDown() {
  274. let actionSequence = {type: "key", id: "some_id"};
  275. let actionItem = {type: "keyDown"};
  276. for (let v of [-1, undefined, [], ["a"], {length: 1}, null]) {
  277. actionItem.value = v;
  278. let message = `actionItem.value: (${getTypeString(v)})`;
  279. Assert.throws(() => action.Action.fromJson(actionSequence, actionItem),
  280. InvalidArgumentError, message);
  281. Assert.throws(() => action.Action.fromJson(actionSequence, actionItem),
  282. /Expected 'value' to be a string that represents single code point/, message);
  283. }
  284. actionItem.value = "\uE004";
  285. let act = action.Action.fromJson(actionSequence, actionItem);
  286. ok(act instanceof action.Action);
  287. equal(act.type, actionSequence.type);
  288. equal(act.subtype, actionItem.type);
  289. equal(act.id, actionSequence.id);
  290. equal(act.value, actionItem.value);
  291. run_next_test();
  292. });
  293. add_test(function test_processInputSourceActionSequenceValidation() {
  294. let actionSequence = {type: "swim", id: "some id"};
  295. let check = (message, regex) => checkErrors(
  296. regex, action.Sequence.fromJson, [actionSequence], message);
  297. check(`actionSequence.type: ${actionSequence.type}`, /Unknown action type/);
  298. action.inputStateMap.clear();
  299. actionSequence.type = "none";
  300. actionSequence.id = -1;
  301. check(`actionSequence.id: ${getTypeString(actionSequence.id)}`,
  302. /Expected 'id' to be a string/);
  303. action.inputStateMap.clear();
  304. actionSequence.id = undefined;
  305. check(`actionSequence.id: ${getTypeString(actionSequence.id)}`,
  306. /Expected 'id' to be defined/);
  307. action.inputStateMap.clear();
  308. actionSequence.id = "some_id";
  309. actionSequence.actions = -1;
  310. check(`actionSequence.actions: ${getTypeString(actionSequence.actions)}`,
  311. /Expected 'actionSequence.actions' to be an Array/);
  312. action.inputStateMap.clear();
  313. run_next_test();
  314. });
  315. add_test(function test_processInputSourceActionSequence() {
  316. let actionItem = { type: "pause", duration: 5};
  317. let actionSequence = {
  318. type: "none",
  319. id: "some id",
  320. actions: [actionItem],
  321. };
  322. let expectedAction = new action.Action(actionSequence.id, "none", actionItem.type);
  323. expectedAction.duration = actionItem.duration;
  324. let actions = action.Sequence.fromJson(actionSequence);
  325. equal(actions.length, 1);
  326. deepEqual(actions[0], expectedAction);
  327. action.inputStateMap.clear();
  328. run_next_test();
  329. });
  330. add_test(function test_processInputSourceActionSequencePointer() {
  331. let actionItem = {type: "pointerDown", button: 1};
  332. let actionSequence = {
  333. type: "pointer",
  334. id: "9",
  335. actions: [actionItem],
  336. parameters: {
  337. pointerType: "mouse" // TODO "pen"
  338. },
  339. };
  340. let expectedAction = new action.Action(
  341. actionSequence.id, actionSequence.type, actionItem.type);
  342. expectedAction.pointerType = actionSequence.parameters.pointerType;
  343. expectedAction.button = actionItem.button;
  344. let actions = action.Sequence.fromJson(actionSequence);
  345. equal(actions.length, 1);
  346. deepEqual(actions[0], expectedAction);
  347. action.inputStateMap.clear();
  348. run_next_test();
  349. });
  350. add_test(function test_processInputSourceActionSequenceKey() {
  351. let actionItem = {type: "keyUp", value: "a"};
  352. let actionSequence = {
  353. type: "key",
  354. id: "9",
  355. actions: [actionItem],
  356. };
  357. let expectedAction = new action.Action(
  358. actionSequence.id, actionSequence.type, actionItem.type);
  359. expectedAction.value = actionItem.value;
  360. let actions = action.Sequence.fromJson(actionSequence);
  361. equal(actions.length, 1);
  362. deepEqual(actions[0], expectedAction);
  363. action.inputStateMap.clear();
  364. run_next_test();
  365. });
  366. add_test(function test_processInputSourceActionSequenceInputStateMap() {
  367. let id = "1";
  368. let actionItem = {type: "pause", duration: 5000};
  369. let actionSequence = {
  370. type: "key",
  371. id: id,
  372. actions: [actionItem],
  373. };
  374. let wrongInputState = new action.InputState.Null();
  375. action.inputStateMap.set(actionSequence.id, wrongInputState);
  376. checkErrors(/to be mapped to/, action.Sequence.fromJson, [actionSequence],
  377. `${actionSequence.type} using ${wrongInputState}`);
  378. action.inputStateMap.clear();
  379. let rightInputState = new action.InputState.Key();
  380. action.inputStateMap.set(id, rightInputState);
  381. let acts = action.Sequence.fromJson(actionSequence);
  382. equal(acts.length, 1);
  383. action.inputStateMap.clear();
  384. run_next_test();
  385. });
  386. add_test(function test_processPointerActionInputStateMap() {
  387. let actionItem = {type: "pointerDown"};
  388. let id = "1";
  389. let parameters = {pointerType: "mouse"};
  390. let a = new action.Action(id, "pointer", actionItem.type);
  391. let wrongInputState = new action.InputState.Key();
  392. action.inputStateMap.set(id, wrongInputState);
  393. checkErrors(
  394. /to be mapped to InputState whose type is/, action.processPointerAction,
  395. [id, parameters, a],
  396. `type "pointer" with ${wrongInputState.type} in inputState`);
  397. action.inputStateMap.clear();
  398. // TODO - uncomment once pen is supported
  399. //wrongInputState = new action.InputState.Pointer("pen");
  400. //action.inputStateMap.set(id, wrongInputState);
  401. //checkErrors(
  402. // /to be mapped to InputState whose subtype is/, action.processPointerAction,
  403. // [id, parameters, a],
  404. // `subtype ${parameters.pointerType} with ${wrongInputState.subtype} in inputState`);
  405. //action.inputStateMap.clear();
  406. let rightInputState = new action.InputState.Pointer("mouse");
  407. action.inputStateMap.set(id, rightInputState);
  408. action.processPointerAction(id, parameters, a);
  409. action.inputStateMap.clear();
  410. run_next_test();
  411. });
  412. add_test(function test_createInputState() {
  413. for (let kind in action.InputState) {
  414. let state;
  415. if (kind == "Pointer") {
  416. state = new action.InputState[kind]("mouse");
  417. } else {
  418. state = new action.InputState[kind]();
  419. }
  420. ok(state);
  421. if (kind === "Null") {
  422. equal(state.type, "none");
  423. } else {
  424. equal(state.type, kind.toLowerCase());
  425. }
  426. }
  427. Assert.throws(() => new action.InputState.Pointer(), InvalidArgumentError,
  428. "Missing InputState.Pointer constructor arg");
  429. Assert.throws(() => new action.InputState.Pointer("foo"), InvalidArgumentError,
  430. "Invalid InputState.Pointer constructor arg");
  431. run_next_test();
  432. });
  433. add_test(function test_extractActionChainValidation() {
  434. for (let actions of [-1, "a", undefined, null]) {
  435. let message = `actions: ${getTypeString(actions)}`;
  436. Assert.throws(() => action.Chain.fromJson(actions),
  437. InvalidArgumentError, message);
  438. Assert.throws(() => action.Chain.fromJson(actions),
  439. /Expected 'actions' to be an Array/, message);
  440. }
  441. run_next_test();
  442. });
  443. add_test(function test_extractActionChainEmpty() {
  444. deepEqual(action.Chain.fromJson([]), []);
  445. run_next_test();
  446. });
  447. add_test(function test_extractActionChain_oneTickOneInput() {
  448. let actionItem = {type: "pause", duration: 5000};
  449. let actionSequence = {
  450. type: "none",
  451. id: "some id",
  452. actions: [actionItem],
  453. };
  454. let expectedAction = new action.Action(actionSequence.id, "none", actionItem.type);
  455. expectedAction.duration = actionItem.duration;
  456. let actionsByTick = action.Chain.fromJson([actionSequence]);
  457. equal(1, actionsByTick.length);
  458. equal(1, actionsByTick[0].length);
  459. deepEqual(actionsByTick, [[expectedAction]]);
  460. action.inputStateMap.clear();
  461. run_next_test();
  462. });
  463. add_test(function test_extractActionChain_twoAndThreeTicks() {
  464. let mouseActionItems = [
  465. {
  466. type: "pointerDown",
  467. button: 2,
  468. },
  469. {
  470. type: "pointerUp",
  471. button: 2,
  472. },
  473. ];
  474. let mouseActionSequence = {
  475. type: "pointer",
  476. id: "7",
  477. actions: mouseActionItems,
  478. parameters: {
  479. pointerType: "mouse" //TODO "touch"
  480. },
  481. };
  482. let keyActionItems = [
  483. {
  484. type: "keyDown",
  485. value: "a",
  486. },
  487. {
  488. type: "pause",
  489. duration: 4,
  490. },
  491. {
  492. type: "keyUp",
  493. value: "a",
  494. },
  495. ];
  496. let keyActionSequence = {
  497. type: "key",
  498. id: "1",
  499. actions: keyActionItems,
  500. };
  501. let actionsByTick = action.Chain.fromJson([keyActionSequence, mouseActionSequence]);
  502. // number of ticks is same as longest action sequence
  503. equal(keyActionItems.length, actionsByTick.length);
  504. equal(2, actionsByTick[0].length);
  505. equal(2, actionsByTick[1].length);
  506. equal(1, actionsByTick[2].length);
  507. let expectedAction = new action.Action(keyActionSequence.id, "key", keyActionItems[2].type);
  508. expectedAction.value = keyActionItems[2].value;
  509. deepEqual(actionsByTick[2][0], expectedAction);
  510. action.inputStateMap.clear();
  511. // one empty action sequence
  512. actionsByTick = action.Chain.fromJson(
  513. [keyActionSequence, {type: "none", id: "some", actions: []}]);
  514. equal(keyActionItems.length, actionsByTick.length);
  515. equal(1, actionsByTick[0].length);
  516. action.inputStateMap.clear();
  517. run_next_test();
  518. });
  519. add_test(function test_computeTickDuration() {
  520. let expected = 8000;
  521. let tickActions = [
  522. {type: "none", subtype: "pause", duration: 5000},
  523. {type: "key", subtype: "pause", duration: 1000},
  524. {type: "pointer", subtype: "pointerMove", duration: 6000},
  525. // invalid because keyDown should not have duration, so duration should be ignored.
  526. {type: "key", subtype: "keyDown", duration: 100000},
  527. {type: "pointer", subtype: "pause", duration: expected},
  528. {type: "pointer", subtype: "pointerUp"},
  529. ];
  530. equal(expected, action.computeTickDuration(tickActions));
  531. run_next_test();
  532. });
  533. add_test(function test_computeTickDuration_empty() {
  534. equal(0, action.computeTickDuration([]));
  535. run_next_test();
  536. });
  537. add_test(function test_computeTickDuration_noDurations() {
  538. let tickActions = [
  539. // invalid because keyDown should not have duration, so duration should be ignored.
  540. {type: "key", subtype: "keyDown", duration: 100000},
  541. // undefined duration permitted
  542. {type: "none", subtype: "pause"},
  543. {type: "pointer", subtype: "pointerMove"},
  544. {type: "pointer", subtype: "pointerDown"},
  545. {type: "key", subtype: "keyUp"},
  546. ];
  547. equal(0, action.computeTickDuration(tickActions));
  548. run_next_test();
  549. });
  550. // helpers
  551. function getTypeString(obj) {
  552. return Object.prototype.toString.call(obj);
  553. };
  554. function checkErrors(regex, func, args, message) {
  555. if (typeof message == "undefined") {
  556. message = `actionFunc: ${func.name}; args: ${args}`;
  557. }
  558. Assert.throws(() => func.apply(this, args), InvalidArgumentError, message);
  559. Assert.throws(() => func.apply(this, args), regex, message);
  560. };