diff --git a/common/predictive-text/package.json b/common/predictive-text/package.json index 81a92a4fa9..d7c12767ad 100644 --- a/common/predictive-text/package.json +++ b/common/predictive-text/package.json @@ -39,7 +39,7 @@ "karma-teamcity-reporter": "^1.1.0", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", - "sinon": "^7.1.1", + "sinon": "^14.0.0", "ts-node": "^9.1.1", "typescript": "^4.5.4" }, diff --git a/common/test/resources/karma-browserstack-config.js b/common/test/resources/karma-browserstack-config.js index a6b3c95662..b65b46d104 100644 --- a/common/test/resources/karma-browserstack-config.js +++ b/common/test/resources/karma-browserstack-config.js @@ -107,7 +107,10 @@ module.exports = function(baseConfigParams /* the project's base configuration FINAL_LAUNCHER_DEFS = mergeLaunchers( PRIMARY_LAUNCHERS, SECONDARY_LAUNCHERS); } else { - FINAL_LAUNCHER_DEFS = PRIMARY_LAUNCHERS; + // The function also does some necessary bookkeeping that makes for + // cleaner definitions at the file's top, so we call it even if we + // aren't merging lists. + FINAL_LAUNCHER_DEFS = mergeLaunchers(PRIMARY_LAUNCHERS); } var FINAL_BROWSER_LIST = toBrowserList(FINAL_LAUNCHER_DEFS); diff --git a/common/web/gesture-recognizer/build.sh b/common/web/gesture-recognizer/build.sh index 41f94271a0..f41a266751 100755 --- a/common/web/gesture-recognizer/build.sh +++ b/common/web/gesture-recognizer/build.sh @@ -25,7 +25,8 @@ builder_describe "Builds the gesture-recognition model for Web-based on-screen k "build" \ "test" \ ":module" \ - ":tools tools for testing & developing test resources for this module" + ":tools tools for testing & developing test resources for this module" \ + "--ci sets the --ci option for child scripts (i.e, the `test` action)" builder_parse "$@" @@ -60,6 +61,10 @@ if builder_has_action build :tools; then fi if builder_has_action test :module; then - npm test + if builder_has_option --ci; then + npm test -- --ci + else + npm test + fi builder_report success test :module fi \ No newline at end of file diff --git a/common/web/gesture-recognizer/package.json b/common/web/gesture-recognizer/package.json index 60620aeb7c..e00cbd7dc3 100644 --- a/common/web/gesture-recognizer/package.json +++ b/common/web/gesture-recognizer/package.json @@ -4,21 +4,36 @@ "devDependencies": { "@keymanapp/resources-gosh": "*", "@types/node": "^11.9.4", + "@types/sinon": "^10.0.13", "chai": "^4.3.4", + "karma": "^6.3.4", + "karma-browserstack-launcher": "^1.6.0", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^2.2.0", + "karma-firefox-launcher": "^1.1.0", + "karma-fixture": "^0.2.6", + "karma-html2js-preprocessor": "^1.1.0", + "karma-ie-launcher": "^1.0.0", + "karma-json-fixtures-preprocessor": "0.0.6", + "karma-mocha": "^2.0.1", + "karma-mocha-reporter": "^2.2.5", + "karma-safari-launcher": "^1.0.0", + "karma-teamcity-reporter": "^1.1.0", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", + "sinon": "^14.0.0", "ts-node": "^9.1.1", "typescript": "^4.5.4" }, "scripts": { "build": "echo 'Building @keymanapp/gesture-recognizer' && tsc -b ./tsconfig.json", "clean": "tsc -b --clean", - "tsc": "tsc", - "mocha": "mocha" + "karma": "karma", + "mocha": "mocha", + "test": "gosh ./src/test/test.sh", + "tsc": "tsc" }, "dependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/web-utils": "*", "eventemitter3": "^4.0.0" } } diff --git a/common/web/gesture-recognizer/src/gestureRecognizer.ts b/common/web/gesture-recognizer/src/gestureRecognizer.ts index 6ef4a146bd..58c78ab0c3 100644 --- a/common/web/gesture-recognizer/src/gestureRecognizer.ts +++ b/common/web/gesture-recognizer/src/gestureRecognizer.ts @@ -9,7 +9,7 @@ namespace com.keyman.osk { } export class GestureRecognizer extends EventEmitter { - protected readonly config: Nonoptional; + public readonly config: Nonoptional; private readonly mouseEngine: MouseEventEngine; private readonly touchEngine: TouchEventEngine; diff --git a/common/web/gesture-recognizer/src/mouseEventEngine.ts b/common/web/gesture-recognizer/src/mouseEventEngine.ts index d4cea43ee8..2b92cdbb6a 100644 --- a/common/web/gesture-recognizer/src/mouseEventEngine.ts +++ b/common/web/gesture-recognizer/src/mouseEventEngine.ts @@ -15,9 +15,11 @@ namespace com.keyman.osk { public constructor(config: Nonoptional) { super(config); - this._mouseStart = this.onMouseStart.bind(this); - this._mouseMove = this.onMouseMove.bind(this); - this._mouseEnd = this.onMouseEnd.bind(this); + // We use this approach, rather than .bind, because _this_ version allows hook + // insertion for unit tests via prototype manipulation. The .bind version doesn't. + this._mouseStart = (event: MouseEvent) => this.onMouseStart(event); + this._mouseMove = (event: MouseEvent) => this.onMouseMove(event); + this._mouseEnd = (event: MouseEvent) => this.onMouseEnd(event); // IDs should be unique. Fortunately, they're disambiguated by their corresponding TrackedPoint, // which has gives a globally-unique string-based identifier based partly on the numeric ID set here. diff --git a/common/web/gesture-recognizer/src/test/auto/browser/CI.conf.js b/common/web/gesture-recognizer/src/test/auto/browser/CI.conf.js new file mode 100644 index 0000000000..dcb282763e --- /dev/null +++ b/common/web/gesture-recognizer/src/test/auto/browser/CI.conf.js @@ -0,0 +1,4 @@ +var BASE_CONFIG = require("./base.conf.js"); +var ci_config_adapter = require("../../../../../../../common/test/resources/karma-browserstack-config.js"); + +module.exports = ci_config_adapter(BASE_CONFIG, "partial"); \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/auto/browser/base.conf.js b/common/web/gesture-recognizer/src/test/auto/browser/base.conf.js new file mode 100644 index 0000000000..b1c9bf1d6f --- /dev/null +++ b/common/web/gesture-recognizer/src/test/auto/browser/base.conf.js @@ -0,0 +1,95 @@ +// Karma configuration +// Generated on Mon Jan 29 2018 11:58:53 GMT+0700 (SE Asia Standard Time) + +// Super-useful! http://www.bradoncode.com/blog/2015/02/27/karma-tutorial/#handling-html-fixtures + +module.exports = { + // base path that will be used to resolve all patterns (eg. files, exclude), + // set to this module's base folder. + basePath: '../../../..', + + // Safeguards against disconnecting after starting tests. + browserNoActivityTimeout: 60000, + + client: { + /* `client.args` here is passed to the test runner page as `__karma__.config.args`. + * + * Karma doc type spec says "array", so we use an array. It also gives us room to add alternate + * configuration details later if we need to, though on a CI vs local basis only. + * + * Timeouts below are in milliseconds + */ + args: [{ + type: "timeouts", // This base is designed for local machine testing. + eventDelay: 60, // Designed for small delays to allow time for event handling to occur before proceeding. + // Make sure this stays under 1/4 of 'standard', as multiple eventDelays may occur within a test. + standard: 5000, + scriptLoad: 8000, + uiLoad: 30000, // Loads two scripts + includes internal setup/timeout time requirements. + // At this time of writing this, UI script loading is one of the longest checks. + }] + }, + + // frameworks to use + // available frameworks: https://npmjs.org/browse/keyword/karma-adapter + frameworks: ['mocha', 'chai', 'fixture'], + + // list of files / patterns to load in the browser + files: [ + 'build/index.js', // The primary gesture-recognizer script. + {pattern: 'build/index.js.map', watched: true, served: true, included: false}, + 'build/tools/unit-test-resources.js', // The primary unit-test-resources script. + {pattern: 'build/tools/unit-test-resources.js.map', watched: true, served: true, included: false}, + + '../../../node_modules/sinon/pkg/sinon.js', + + '../../../common/test/resources/timeout-adapter.js', // Handles timeout configuration for local vs BrowserStack-based testing + + 'src/test/auto/browser/cases/**/*.js', // Where the tests actually reside. + + 'build/tools/host-fixture.html', // The primary test fixture's build output location + 'build/tools/gestureHost.css', // The primary test fixture's backing CSS stylesheet. Note: Karma will auto-link it! + 'src/test/resources/json/**/*.json', // Where pre-loaded JSON resides. + ], + + proxies: { + "/resources/": "/src/test/resources/", + }, + + + // list of files / patterns to exclude + exclude: [ + ], + + // preprocess matching files before serving them to the browser + // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor + preprocessors: { + 'build/tools/host-fixture.html': ['html2js'], + 'src/test/resources/fixtures/**/*.html' : ['html2js'], + 'src/test/resources/json/**/*.json' : ['json_fixtures'] + }, + + html2JsPreprocessor: { + stripPrefix: 'build/tools' + }, + + // Settings to properly configure how JSON fixtures are automatically loaded by Karma. + jsonFixturesPreprocessor: { + stripPrefix: 'src/test/resources/json/', + variableName: '__json__' + }, + + // web server port + port: 9876, + + // enable / disable colors in the output (reporters and logs) + colors: true, + + // enable / disable watching file and executing tests whenever any file changes + autoWatch: true, + + // Continuous Integration mode + // if true, Karma captures browsers, runs the tests and exits + // if false, it generates the pages and acts as a persistent server. + singleRun: true, +} diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.js b/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.js new file mode 100644 index 0000000000..91ca0e4edd --- /dev/null +++ b/common/web/gesture-recognizer/src/test/auto/browser/cases/canary.js @@ -0,0 +1,111 @@ +var assert = chai.assert; + +describe("'Canary' checks", function() { + this.timeout(testconfig.timeouts.standard); + + before(function() { + fixture.setBase(''); + }) + + it('host-fixture.html + gestureHost.css', function() { + let element = fixture.load('host-fixture.html')[0]; + + // Ensure that not only did we get an element, we got the expected element. + assert.isNotNull(element); + assert.isDefined(element); + assert.equal(element.id, 'host-fixture'); + // If the CSS is missing, the element will default to zero height. + assert.notEqual(element.getBoundingClientRect().height, 0); + }); + + it('canaryRecording.json', function() { + let jsonObject = window['__json__'].canaryRecording; + + assert.isNotNull(jsonObject); + assert.isDefined(jsonObject); + + let config = new Testing.FixtureLayoutConfiguration(jsonObject.config); + assert.equal(config.deviceStyle, 'screen4'); + }); + + it('Testing.HostFixtureLayoutController', function(done) { + let targetRoot = fixture.load('host-fixture.html')[0]; + let jsonObject = window['__json__'].canaryRecording; + + let controller = new Testing.HostFixtureLayoutController(); + // Note: this is set BEFORE the controller is configured (in the following line). + // The class is designed to support this. + controller.layoutConfiguration = new Testing.FixtureLayoutConfiguration(jsonObject.config); + controller.connect().then(() => { + assert.isTrue(targetRoot.className.indexOf('screen4') > -1, "Could not apply configuration spec from recorded JSON!"); + done(); + }).finally(() => controller.destroy()); + }) + + afterEach(function() { + fixture.cleanup(); + }) + + describe('event simulation', function() { + beforeEach(function(done) { + fixture.load('host-fixture.html'); + this.controller = new Testing.HostFixtureLayoutController(); + this.controller.connect().then(() => done()); + }); + + afterEach(function() { + this.controller.destroy(); + fixture.cleanup(); + }); + + it("InputSequenceSimulator.replayTouchSample", function() { + let playbackEngine = new Testing.InputSequenceSimulator(this.controller); + let layout = new Testing.FixtureLayoutConfiguration("screen2", "bounds1", "full", "safe-loose"); + this.controller.layoutConfiguration = layout; + + let fireEvent = () => { + playbackEngine.replayTouchSample(/*relative coord:*/ {targetX: 10, targetY: 10}, + /*state:*/ "start", + /*identifier:*/ 1, + /*otherTouches:*/ [], + ); + } + + // Ensure that the expected handler is called. + let proto = com.keyman.osk.TouchEventEngine.prototype; + let trueHandler = proto.onTouchStart; + let fakeHandler = proto.onTouchStart = sinon.fake(); + fireEvent(); + try { + assert.isTrue(fakeHandler.called, "Unit test attempt failed: handler was not called successfully."); + } finally { + // Restore the true implementation. + proto.onTouchStart = trueHandler; + } + }); + + it("InputSequenceSimulator.replayMouseSample", function() { + let playbackEngine = new Testing.InputSequenceSimulator(this.controller); + let layout = new Testing.FixtureLayoutConfiguration("screen2", "bounds1", "full", "safe-loose"); + this.controller.layoutConfiguration = layout; + + let fireEvent = () => { + playbackEngine.replayMouseSample(/*relative coord:*/ {targetX: 15, targetY: 15}, + /*state:*/ "start" + ); + } + + // Ensure that the expected handler is called. + let proto = com.keyman.osk.MouseEventEngine.prototype; + let trueHandler = proto.onMouseStart; + let fakeHandler = proto.onMouseStart = sinon.fake(); + fireEvent(); + try { + assert.isTrue(fakeHandler.called, "Unit test attempt failed: handler was not called successfully."); + } finally { + // Restore the true implementation. + proto.onMouseStart = trueHandler; + } + }); + }); +}); \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/other.js b/common/web/gesture-recognizer/src/test/auto/browser/cases/other.js new file mode 100644 index 0000000000..318473ff3e --- /dev/null +++ b/common/web/gesture-recognizer/src/test/auto/browser/cases/other.js @@ -0,0 +1,85 @@ +var assert = chai.assert; +var expect = chai.expect; + +describe("Layer one - DOM -> InputSequence", function() { + this.timeout(testconfig.timeouts.standard); + + before(function() { + fixture.setBase(''); + }); + + beforeEach(function(done) { + fixture.load('host-fixture.html'); + this.controller = new Testing.HostFixtureLayoutController(); + this.controller.connect().then(() => done()); + }); + + afterEach(function() { + this.controller.destroy(); + fixture.cleanup(); + }); + + describe('other tests', function() { + it("starts in roaming zone are ignored", function() { + let playbackEngine = new Testing.InputSequenceSimulator(this.controller); + let recorder = new Testing.SequenceRecorder(this.controller); + let layout = new Testing.FixtureLayoutConfiguration("screen2", "bounds1", "full", "safe-loose"); + this.controller.layoutConfiguration = layout; + + let fireEvent = () => { + playbackEngine.replayTouchSample(/*relative coord:*/ {targetX: 10, targetY: -5}, + /*state:*/ "start", + /*identifier:*/ 1, + /*otherTouches:*/ [], + /*targetElement:*/ this.controller.recognizer.config.maxRoamingBounds + ); + } + + // This test is invalidated if the handler itself isn't called. So... let's verify that! + // Not quite covered by the canary cases b/c of the distinct targetElement. + let proto = com.keyman.osk.TouchEventEngine.prototype; + let trueHandler = proto.onTouchStart; + let fakeHandler = proto.onTouchStart = sinon.fake(); + fireEvent(); + try { + assert.isTrue(fakeHandler.called, "Unit test attempt failed: handler was not called successfully."); + } finally { + // Restore the true implementation. + proto.onTouchStart = trueHandler; + } + + fireEvent(); + assert.equal(recorder.count, 0, "Input starting in roaming area was not ignored!"); + }); + + it("ignores target-external events", function() { + let playbackEngine = new Testing.InputSequenceSimulator(this.controller); + let recorder = new Testing.SequenceRecorder(this.controller); + let layout = new Testing.FixtureLayoutConfiguration("screen2", "bounds1", "full", "safe-loose"); + this.controller.layoutConfiguration = layout; + + let fireEvent = () => { + playbackEngine.replayMouseSample(/*relative coord:*/ {targetX: -5, targetY: 15}, + /*state:*/ "start", + /*targetElement:*/ document.body + ); + } + + // This test is invalidated if the handler itself isn't called. So... let's verify that! + // Not quite covered by the canary cases b/c of the distinct targetElement. + let proto = com.keyman.osk.MouseEventEngine.prototype; + let trueHandler = proto.onMouseStart; + let fakeHandler = proto.onMouseStart = sinon.fake(); + fireEvent(); + try { + assert.isTrue(fakeHandler.called, "Unit test attempt failed: handler was not called successfully."); + } finally { + // Restore the true implementation. + proto.onMouseStart = trueHandler; + } + + fireEvent(); + assert.equal(recorder.count, 0, "Input starting outside the main receiver element's hierarchy was not ignored!"); + }); + }); +}); \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/auto/browser/cases/recordedCoordSequences.js b/common/web/gesture-recognizer/src/test/auto/browser/cases/recordedCoordSequences.js new file mode 100644 index 0000000000..6b964ff419 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/auto/browser/cases/recordedCoordSequences.js @@ -0,0 +1,112 @@ +var assert = chai.assert; +var expect = chai.expect; + +describe("Layer one - DOM -> InputSequence", function() { + this.timeout(testconfig.timeouts.standard); + + before(function() { + fixture.setBase(''); + }); + + beforeEach(function(done) { + fixture.load('host-fixture.html'); + this.controller = new Testing.HostFixtureLayoutController(); + this.controller.connect().then(() => done()); + }); + + afterEach(function() { + this.controller.destroy(); + fixture.cleanup(); + fixture.cleanup(); + }); + + describe('recorded input sequences', function() { + beforeEach(function() { + this.clock = sinon.useFakeTimers(); + }); + + afterEach(function() { + this.clock.restore(); // restores the default implementations. + }); + + // We rely on this function to have the same context as `it` - the test-definition function. + let replayAndCompare = function(testObj) { + let resultPromise; + + let playbackEngine = new Testing.InputSequenceSimulator(this.controller); + resultPromise = playbackEngine.replayAsync(testObj); + + // replayAsync sets up timeouts against the `clock` object. + // This will run through the simulated timeout queue asynchronously. + this.clock.runAllAsync(); + + // resultPromise resolves on the final timeout in the queue. + return resultPromise.then((result) => { + assert.equal(result.inputs.length, testObj.inputs.length); + + // Removes the timestamp element; we know that this component may not match perfectly. + let sampleCleaner = (sample) => { return {targetX: sample.targetX, targetY: sample.targetY} }; + + // Returns just the observed, cleaned samples for a sequence object. The recorded coordinates + // should match perfectly. + let seqCleaner = (input) => { + return input.touchpoints[0].path.coords.map(sampleCleaner); + }; + + let cleanOriginalSet = testObj.inputs.map(seqCleaner); + let cleanResultSet = result .inputs.map(seqCleaner); + + expect(cleanResultSet).to.deep.equal(cleanOriginalSet); + + // Now to compare just the timestamp elements. We'll tolerate a difference of up to 1. + // Note: if using the `replaySync` function instead, disable this section! + // (Through to the nested for-loop `assert.closeTo`) + let sampleTimeExtractor = (sample) => sample.t; + let inputTimeExtractor = (input) => { + return input.touchpoints[0].path.coords.map(sampleTimeExtractor); + } + + let originalTimeSet = testObj.inputs.map(inputTimeExtractor); + let resultTimeSet = result .inputs.map(inputTimeExtractor); + + for(let i = 0; i < originalTimeSet.length; i++) { + for(let j = 0; j < originalTimeSet[i].length; j++) { + assert.closeTo(resultTimeSet[i][j], originalTimeSet[i][j], 1); + } + } + + // The 'terminationEvent' property should match. Any sequence that was "canceled" should still + // cancel; that's a pretty critical detail! + let terminationEventMapper = (seq) => { + return seq.touchpoints[0].path.wasCancelled; + } + + expect(result.inputs.map(terminationEventMapper)).to.deep.equal(testObj.inputs.map(terminationEventMapper)); + }); + } + + // List all relevant fixtures in src/test/resources/json. + let testRecordings = [ + 'canaryRecording', + 'desktopRoamAndReturn', + 'mobileSafeZoneCancel', + 'mobileProximityApproach', + 'embeddedBorderCancel', + 'hardBorderCancel', + 'popupLongRoamingEnd', + 'popupShimCancel', + 'popupSafePersistence', + 'basicMultitouch' + ]; + + for(let recordingID of testRecordings) { + it(`${recordingID}.json`, function() { + let testObj = __json__[recordingID]; + + // 'describe' has a notably different `this` reference than `it`, `before`, etc, + // hence the `.call` construction. + return replayAndCompare.call(this, testObj); + }); + } + }); +}); \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/auto/browser/manual.conf.js b/common/web/gesture-recognizer/src/test/auto/browser/manual.conf.js new file mode 100644 index 0000000000..f903999d15 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/auto/browser/manual.conf.js @@ -0,0 +1,26 @@ +module.exports = function(config) { + var base = require("./base.conf.js"); + + var specifics = { + // test results reporter to use + // possible values: 'dots', 'progress' + // available reporters: https://npmjs.org/browse/keyword/karma-reporter + reporters: ['mocha'], + + // start these browsers + // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher + browsers: ['Firefox', 'Chrome'], // Can be specified at run-time instead! + // Future note for us: https://www.npmjs.com/package/karma-browserstack-launcher + + // Concurrency level + // how many browser should be started simultaneous + concurrency: Infinity, + + // level of logging + // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG + // Can't set in base.conf.js b/c of constant's definition style. + logLevel: config.LOG_INFO + }; + + config.set(Object.assign(specifics, base)); +} diff --git a/common/web/gesture-recognizer/src/test/resources/json/basicMultitouch.json b/common/web/gesture-recognizer/src/test/resources/json/basicMultitouch.json new file mode 100644 index 0000000000..ad9e84cc37 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/resources/json/basicMultitouch.json @@ -0,0 +1,449 @@ +{ + "inputs": [ + { + "touchpoints": [ + { + "isFromTouch": true, + "path": { + "coords": [ + { + "targetX": 46, + "targetY": 66, + "t": 6679 + }, + { + "targetX": 46, + "targetY": 59, + "t": 6935.000000000001 + }, + { + "targetX": 46, + "targetY": 58, + "t": 6949 + }, + { + "targetX": 46, + "targetY": 57, + "t": 6983.000000000001 + }, + { + "targetX": 46, + "targetY": 56, + "t": 7016 + }, + { + "targetX": 45, + "targetY": 55, + "t": 7049 + }, + { + "targetX": 45, + "targetY": 54, + "t": 7083 + }, + { + "targetX": 44, + "targetY": 54, + "t": 7099 + }, + { + "targetX": 44, + "targetY": 53, + "t": 7115 + }, + { + "targetX": 43, + "targetY": 52, + "t": 7166 + }, + { + "targetX": 42, + "targetY": 51, + "t": 7199 + }, + { + "targetX": 42, + "targetY": 50, + "t": 7232 + }, + { + "targetX": 41, + "targetY": 49, + "t": 7249.000000000001 + }, + { + "targetX": 41, + "targetY": 48, + "t": 7265.000000000001 + }, + { + "targetX": 41, + "targetY": 47, + "t": 7282 + }, + { + "targetX": 41, + "targetY": 46, + "t": 7298 + }, + { + "targetX": 41, + "targetY": 45, + "t": 7332 + }, + { + "targetX": 41, + "targetY": 44, + "t": 7348 + }, + { + "targetX": 41, + "targetY": 43, + "t": 7399 + }, + { + "targetX": 40, + "targetY": 43, + "t": 7483.000000000001 + }, + { + "targetX": 40, + "targetY": 42, + "t": 7917 + }, + { + "targetX": 41, + "targetY": 42, + "t": 8406 + }, + { + "targetX": 42, + "targetY": 42, + "t": 8589 + }, + { + "targetX": 43, + "targetY": 42, + "t": 8765 + }, + { + "targetX": 42, + "targetY": 41, + "t": 9183 + }, + { + "targetX": 41, + "targetY": 40, + "t": 9231 + }, + { + "targetX": 40, + "targetY": 40, + "t": 9250 + }, + { + "targetX": 40, + "targetY": 39, + "t": 9265 + }, + { + "targetX": 38, + "targetY": 38, + "t": 9282 + }, + { + "targetX": 38, + "targetY": 37, + "t": 9299 + }, + { + "targetX": 37, + "targetY": 36, + "t": 9315 + }, + { + "targetX": 36, + "targetY": 35, + "t": 9331 + }, + { + "targetX": 35, + "targetY": 34, + "t": 9348 + }, + { + "targetX": 35, + "targetY": 33, + "t": 9365 + }, + { + "targetX": 34, + "targetY": 33, + "t": 9382 + }, + { + "targetX": 34, + "targetY": 32, + "t": 9414 + }, + { + "targetX": 33, + "targetY": 31, + "t": 9515 + }, + { + "targetX": 32, + "targetY": 30, + "t": 9548 + }, + { + "targetX": 32, + "targetY": 29, + "t": 9564 + }, + { + "targetX": 31, + "targetY": 29, + "t": 9581 + }, + { + "targetX": 31, + "targetY": 28, + "t": 9597 + }, + { + "targetX": 30, + "targetY": 27, + "t": 9615 + }, + { + "targetX": 30, + "targetY": 26, + "t": 9632 + }, + { + "targetX": 29, + "targetY": 25, + "t": 9648 + }, + { + "targetX": 29, + "targetY": 24, + "t": 9682 + }, + { + "targetX": 29, + "targetY": 23, + "t": 9732 + } + ], + "wasCancelled": false + } + } + ] + }, + { + "touchpoints": [ + { + "isFromTouch": true, + "path": { + "coords": [ + { + "targetX": 172, + "targetY": 95, + "t": 7871 + }, + { + "targetX": 172, + "targetY": 85, + "t": 8151 + }, + { + "targetX": 172, + "targetY": 83, + "t": 8173 + }, + { + "targetX": 172, + "targetY": 80, + "t": 8183 + }, + { + "targetX": 172, + "targetY": 77, + "t": 8199 + }, + { + "targetX": 172, + "targetY": 74, + "t": 8215 + }, + { + "targetX": 172, + "targetY": 71, + "t": 8233 + }, + { + "targetX": 172, + "targetY": 68, + "t": 8249 + }, + { + "targetX": 172, + "targetY": 64, + "t": 8266 + }, + { + "targetX": 172, + "targetY": 61, + "t": 8283 + }, + { + "targetX": 172, + "targetY": 57, + "t": 8299 + }, + { + "targetX": 173, + "targetY": 54, + "t": 8315 + }, + { + "targetX": 173, + "targetY": 51, + "t": 8331 + }, + { + "targetX": 173, + "targetY": 48, + "t": 8348 + }, + { + "targetX": 173, + "targetY": 45, + "t": 8366 + }, + { + "targetX": 174, + "targetY": 42, + "t": 8383 + }, + { + "targetX": 174, + "targetY": 39, + "t": 8399 + }, + { + "targetX": 174, + "targetY": 36, + "t": 8415.000000000002 + }, + { + "targetX": 174, + "targetY": 33, + "t": 8431.000000000002 + }, + { + "targetX": 175, + "targetY": 30, + "t": 8449 + }, + { + "targetX": 175, + "targetY": 27, + "t": 8466 + }, + { + "targetX": 175, + "targetY": 24, + "t": 8483 + }, + { + "targetX": 175, + "targetY": 21, + "t": 8499 + }, + { + "targetX": 176, + "targetY": 18, + "t": 8515 + }, + { + "targetX": 176, + "targetY": 14, + "t": 8531 + }, + { + "targetX": 177, + "targetY": 10, + "t": 8548 + }, + { + "targetX": 177, + "targetY": 6, + "t": 8566 + }, + { + "targetX": 177, + "targetY": 3, + "t": 8582 + }, + { + "targetX": 178, + "targetY": 0, + "t": 8598 + }, + { + "targetX": 178, + "targetY": -3, + "t": 8615 + }, + { + "targetX": 179, + "targetY": -6, + "t": 8631 + }, + { + "targetX": 179, + "targetY": -9, + "t": 8649 + }, + { + "targetX": 180, + "targetY": -12, + "t": 8666 + }, + { + "targetX": 181, + "targetY": -15, + "t": 8682 + }, + { + "targetX": 182, + "targetY": -18, + "t": 8698 + }, + { + "targetX": 182, + "targetY": -22, + "t": 8715 + }, + { + "targetX": 183, + "targetY": -26, + "t": 8731 + } + ], + "wasCancelled": true + } + } + ] + } + ], + "config": { + "deviceStyle": "screen2", + "roamingStyle": "bounds1", + "receiverStyle": "full", + "safeZoneStyle": "safe-loose" + } +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/resources/json/canaryRecording.json b/common/web/gesture-recognizer/src/test/resources/json/canaryRecording.json new file mode 100644 index 0000000000..8d0cfd4fb3 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/resources/json/canaryRecording.json @@ -0,0 +1,167 @@ +{ + "inputs": [ + { + "touchpoints": [ + { + "isFromTouch": false, + "path": { + "coords": [ + { + "targetX": 231, + "targetY": 32, + "t": 388913 + }, + { + "targetX": 230, + "targetY": 32, + "t": 389101 + }, + { + "targetX": 229, + "targetY": 32, + "t": 389131 + }, + { + "targetX": 228, + "targetY": 32, + "t": 389164 + }, + { + "targetX": 228, + "targetY": 33, + "t": 389173 + }, + { + "targetX": 227, + "targetY": 33, + "t": 389206 + }, + { + "targetX": 226, + "targetY": 34, + "t": 389240 + }, + { + "targetX": 225, + "targetY": 34, + "t": 389275 + }, + { + "targetX": 225, + "targetY": 35, + "t": 389306 + }, + { + "targetX": 225, + "targetY": 36, + "t": 389390 + }, + { + "targetX": 225, + "targetY": 37, + "t": 389479 + }, + { + "targetX": 225, + "targetY": 38, + "t": 389609 + }, + { + "targetX": 226, + "targetY": 38, + "t": 389851 + }, + { + "targetX": 226, + "targetY": 39, + "t": 389856 + }, + { + "targetX": 227, + "targetY": 39, + "t": 389872 + }, + { + "targetX": 228, + "targetY": 39, + "t": 389906 + }, + { + "targetX": 228, + "targetY": 40, + "t": 389923 + }, + { + "targetX": 229, + "targetY": 40, + "t": 389957 + }, + { + "targetX": 230, + "targetY": 40, + "t": 389993 + }, + { + "targetX": 231, + "targetY": 40, + "t": 390025 + }, + { + "targetX": 232, + "targetY": 40, + "t": 390056 + }, + { + "targetX": 233, + "targetY": 39, + "t": 390098 + }, + { + "targetX": 234, + "targetY": 39, + "t": 390164 + }, + { + "targetX": 234, + "targetY": 38, + "t": 390213 + }, + { + "targetX": 235, + "targetY": 38, + "t": 390223 + }, + { + "targetX": 236, + "targetY": 38, + "t": 390257 + }, + { + "targetX": 236, + "targetY": 37, + "t": 390325 + }, + { + "targetX": 237, + "targetY": 37, + "t": 390654 + }, + { + "targetX": 237, + "targetY": 36, + "t": 390776 + } + ], + "wasCancelled": false + } + } + ] + } + ], + "config": { + "deviceStyle": "screen4", + "roamingStyle": "bounds1", + "receiverStyle": "full", + "safeZoneStyle": "safe-loose" + } +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/resources/json/desktopRoamAndReturn.json b/common/web/gesture-recognizer/src/test/resources/json/desktopRoamAndReturn.json new file mode 100644 index 0000000000..3684c797e8 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/resources/json/desktopRoamAndReturn.json @@ -0,0 +1,292 @@ +{ + "inputs": [ + { + "touchpoints": [ + { + "isFromTouch": false, + "path": { + "coords": [ + { + "targetX": 119, + "targetY": 27, + "t": 670652.6999999881 + }, + { + "targetX": 119, + "targetY": 26, + "t": 670770.8999999762 + }, + { + "targetX": 119, + "targetY": 25, + "t": 670783.5 + }, + { + "targetX": 119, + "targetY": 24, + "t": 670792.5 + }, + { + "targetX": 119, + "targetY": 24, + "t": 670815.2999999523 + }, + { + "targetX": 119, + "targetY": 23, + "t": 670825.8999999762 + }, + { + "targetX": 119, + "targetY": 22, + "t": 670848.1999999881 + }, + { + "targetX": 119, + "targetY": 21, + "t": 670859.0999999642 + }, + { + "targetX": 119, + "targetY": 20, + "t": 670875.8999999762 + }, + { + "targetX": 119, + "targetY": 20, + "t": 670892.6999999881 + }, + { + "targetX": 119, + "targetY": 19, + "t": 670909.1999999881 + }, + { + "targetX": 119, + "targetY": 18, + "t": 670926.5 + }, + { + "targetX": 119, + "targetY": 16, + "t": 670942.5 + }, + { + "targetX": 118, + "targetY": 15, + "t": 670959.1999999881 + }, + { + "targetX": 118, + "targetY": 12, + "t": 670975.7999999523 + }, + { + "targetX": 118, + "targetY": 12, + "t": 670993.1999999881 + }, + { + "targetX": 118, + "targetY": 10, + "t": 671009.1999999881 + }, + { + "targetX": 118, + "targetY": 8, + "t": 671025.8999999762 + }, + { + "targetX": 118, + "targetY": 6, + "t": 671042.5999999642 + }, + { + "targetX": 118, + "targetY": 5, + "t": 671059.2999999523 + }, + { + "targetX": 118, + "targetY": 4, + "t": 671075.8999999762 + }, + { + "targetX": 118, + "targetY": 2, + "t": 671092.5999999642 + }, + { + "targetX": 118, + "targetY": 0, + "t": 671109.1999999881 + }, + { + "targetX": 118, + "targetY": 0, + "t": 671125.8999999762 + }, + { + "targetX": 118, + "targetY": -2, + "t": 671142.5999999642 + }, + { + "targetX": 118, + "targetY": -3, + "t": 671159.0999999642 + }, + { + "targetX": 118, + "targetY": -4, + "t": 671175.7999999523 + }, + { + "targetX": 118, + "targetY": -4, + "t": 671192.5 + }, + { + "targetX": 118, + "targetY": -5, + "t": 671244.3999999762 + }, + { + "targetX": 118, + "targetY": -6, + "t": 671260 + }, + { + "targetX": 119, + "targetY": -7, + "t": 671275.8999999762 + }, + { + "targetX": 120, + "targetY": -7, + "t": 671326.1999999881 + }, + { + "targetX": 120, + "targetY": -8, + "t": 671342.5 + }, + { + "targetX": 120, + "targetY": -8, + "t": 671359.8999999762 + }, + { + "targetX": 120, + "targetY": -8, + "t": 671376 + }, + { + "targetX": 121, + "targetY": -8, + "t": 671395.1999999881 + }, + { + "targetX": 121, + "targetY": -9, + "t": 671420.5999999642 + }, + { + "targetX": 122, + "targetY": -9, + "t": 671460 + }, + { + "targetX": 123, + "targetY": -9, + "t": 671475.8999999762 + }, + { + "targetX": 124, + "targetY": -9, + "t": 671525.8999999762 + }, + { + "targetX": 125, + "targetY": -8, + "t": 671542.5999999642 + }, + { + "targetX": 126, + "targetY": -8, + "t": 671559.2999999523 + }, + { + "targetX": 127, + "targetY": -6, + "t": 671575.7999999523 + }, + { + "targetX": 128, + "targetY": -4, + "t": 671592.5999999642 + }, + { + "targetX": 128, + "targetY": -3, + "t": 671609.2999999523 + }, + { + "targetX": 129, + "targetY": -1, + "t": 671625.8999999762 + }, + { + "targetX": 129, + "targetY": 0, + "t": 671642.5999999642 + }, + { + "targetX": 129, + "targetY": 0, + "t": 671659.1999999881 + }, + { + "targetX": 129, + "targetY": 1, + "t": 671675.8999999762 + }, + { + "targetX": 130, + "targetY": 1, + "t": 671692.8999999762 + }, + { + "targetX": 130, + "targetY": 3, + "t": 671709.2999999523 + }, + { + "targetX": 130, + "targetY": 4, + "t": 671734.8999999762 + }, + { + "targetX": 130, + "targetY": 4, + "t": 671749.5 + }, + { + "targetX": 130, + "targetY": 5, + "t": 671793 + } + ], + "wasCancelled": false + } + } + ] + } + ], + "config": { + "deviceStyle": "screen3", + "roamingStyle": "bounds1", + "receiverStyle": "full", + "safeZoneStyle": "safe-loose" + } +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/resources/json/embeddedBorderCancel.json b/common/web/gesture-recognizer/src/test/resources/json/embeddedBorderCancel.json new file mode 100644 index 0000000000..2da4e5114e --- /dev/null +++ b/common/web/gesture-recognizer/src/test/resources/json/embeddedBorderCancel.json @@ -0,0 +1,167 @@ +{ + "inputs": [ + { + "touchpoints": [ + { + "isFromTouch": true, + "path": { + "coords": [ + { + "targetX": 118, + "targetY": 79, + "t": 110727 + }, + { + "targetX": 118, + "targetY": 71, + "t": 110877 + }, + { + "targetX": 118, + "targetY": 68, + "t": 110909 + }, + { + "targetX": 118, + "targetY": 65, + "t": 110926 + }, + { + "targetX": 118, + "targetY": 62, + "t": 110943 + }, + { + "targetX": 118, + "targetY": 58, + "t": 110959 + }, + { + "targetX": 118, + "targetY": 55, + "t": 110976 + }, + { + "targetX": 118, + "targetY": 54, + "t": 110993 + }, + { + "targetX": 118, + "targetY": 52, + "t": 111009 + }, + { + "targetX": 118, + "targetY": 50, + "t": 111026 + }, + { + "targetX": 118, + "targetY": 45, + "t": 111043 + }, + { + "targetX": 118, + "targetY": 44, + "t": 111060 + }, + { + "targetX": 118, + "targetY": 42, + "t": 111076 + }, + { + "targetX": 118, + "targetY": 38, + "t": 111093 + }, + { + "targetX": 118, + "targetY": 34, + "t": 111109 + }, + { + "targetX": 118, + "targetY": 31, + "t": 111126 + }, + { + "targetX": 118, + "targetY": 28, + "t": 111143 + }, + { + "targetX": 118, + "targetY": 25, + "t": 111160 + }, + { + "targetX": 118, + "targetY": 22, + "t": 111176 + }, + { + "targetX": 118, + "targetY": 19, + "t": 111193 + }, + { + "targetX": 118, + "targetY": 17, + "t": 111209 + }, + { + "targetX": 118, + "targetY": 16, + "t": 111226 + }, + { + "targetX": 118, + "targetY": 15, + "t": 111243 + }, + { + "targetX": 118, + "targetY": 13, + "t": 111259 + }, + { + "targetX": 118, + "targetY": 10, + "t": 111276 + }, + { + "targetX": 118, + "targetY": 8, + "t": 111293 + }, + { + "targetX": 118, + "targetY": 6, + "t": 111309 + }, + { + "targetX": 118, + "targetY": 4, + "t": 111325 + }, + { + "targetX": 118, + "targetY": 2, + "t": 111343 + } + ], + "wasCancelled": true + } + } + ] + } + ], + "config": { + "deviceStyle": "screen4", + "roamingStyle": "bounds1", + "receiverStyle": "full", + "safeZoneStyle": "safe-loose" + } +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/resources/json/hardBorderCancel.json b/common/web/gesture-recognizer/src/test/resources/json/hardBorderCancel.json new file mode 100644 index 0000000000..56c4caa88b --- /dev/null +++ b/common/web/gesture-recognizer/src/test/resources/json/hardBorderCancel.json @@ -0,0 +1,32 @@ +{ + "inputs": [ + { + "touchpoints": [ + { + "isFromTouch": true, + "path": { + "coords": [ + { + "targetX": 231, + "targetY": 82, + "t": 131762 + }, + { + "targetX": 242, + "targetY": 81, + "t": 131976 + } + ], + "wasCancelled": true + } + } + ] + } + ], + "config": { + "deviceStyle": "screen4", + "roamingStyle": "bounds1", + "receiverStyle": "full", + "safeZoneStyle": "safe-loose" + } +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/resources/json/mobileProximityApproach.json b/common/web/gesture-recognizer/src/test/resources/json/mobileProximityApproach.json new file mode 100644 index 0000000000..a3ca094a65 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/resources/json/mobileProximityApproach.json @@ -0,0 +1,282 @@ +{ + "inputs": [ + { + "touchpoints": [ + { + "isFromTouch": true, + "path": { + "coords": [ + { + "targetX": 115, + "targetY": 79, + "t": 14808 + }, + { + "targetX": 124, + "targetY": 76, + "t": 14945 + }, + { + "targetX": 128, + "targetY": 76, + "t": 14977 + }, + { + "targetX": 132, + "targetY": 75, + "t": 14995 + }, + { + "targetX": 136, + "targetY": 75, + "t": 15012 + }, + { + "targetX": 140, + "targetY": 75, + "t": 15028 + }, + { + "targetX": 146, + "targetY": 75, + "t": 15045 + }, + { + "targetX": 150, + "targetY": 75, + "t": 15061 + }, + { + "targetX": 154, + "targetY": 75, + "t": 15078 + }, + { + "targetX": 158, + "targetY": 75, + "t": 15095 + }, + { + "targetX": 160, + "targetY": 75, + "t": 15111 + }, + { + "targetX": 162, + "targetY": 75, + "t": 15128 + }, + { + "targetX": 164, + "targetY": 75, + "t": 15144 + }, + { + "targetX": 166, + "targetY": 75, + "t": 15162 + }, + { + "targetX": 167, + "targetY": 75, + "t": 15177 + }, + { + "targetX": 170, + "targetY": 75, + "t": 15195 + }, + { + "targetX": 172, + "targetY": 75, + "t": 15211 + }, + { + "targetX": 175, + "targetY": 75, + "t": 15227 + }, + { + "targetX": 178, + "targetY": 75, + "t": 15244 + }, + { + "targetX": 180, + "targetY": 75, + "t": 15261 + }, + { + "targetX": 183, + "targetY": 75, + "t": 15278 + }, + { + "targetX": 187, + "targetY": 75, + "t": 15294 + }, + { + "targetX": 190, + "targetY": 75, + "t": 15311 + }, + { + "targetX": 192, + "targetY": 75, + "t": 15328 + }, + { + "targetX": 195, + "targetY": 75, + "t": 15344 + }, + { + "targetX": 198, + "targetY": 75, + "t": 15361 + }, + { + "targetX": 198, + "targetY": 75, + "t": 15378 + }, + { + "targetX": 199, + "targetY": 75, + "t": 15394 + }, + { + "targetX": 203, + "targetY": 75, + "t": 15411 + }, + { + "targetX": 207, + "targetY": 75, + "t": 15427 + }, + { + "targetX": 210, + "targetY": 75, + "t": 15444 + }, + { + "targetX": 212, + "targetY": 75, + "t": 15461 + }, + { + "targetX": 214, + "targetY": 75, + "t": 15477 + }, + { + "targetX": 215, + "targetY": 75, + "t": 15494 + }, + { + "targetX": 216, + "targetY": 75, + "t": 15511 + }, + { + "targetX": 218, + "targetY": 75, + "t": 15544 + }, + { + "targetX": 219, + "targetY": 75, + "t": 15561 + }, + { + "targetX": 220, + "targetY": 75, + "t": 15578 + }, + { + "targetX": 222, + "targetY": 75, + "t": 15594 + }, + { + "targetX": 222, + "targetY": 75, + "t": 15611 + }, + { + "targetX": 223, + "targetY": 75, + "t": 15628 + }, + { + "targetX": 223, + "targetY": 74, + "t": 15744 + }, + { + "targetX": 223, + "targetY": 73, + "t": 15845 + }, + { + "targetX": 224, + "targetY": 73, + "t": 15861 + }, + { + "targetX": 226, + "targetY": 73, + "t": 15894 + }, + { + "targetX": 226, + "targetY": 72, + "t": 15911 + }, + { + "targetX": 228, + "targetY": 71, + "t": 15927 + }, + { + "targetX": 230, + "targetY": 71, + "t": 15961 + }, + { + "targetX": 230, + "targetY": 71, + "t": 15978 + }, + { + "targetX": 231, + "targetY": 71, + "t": 15995 + }, + { + "targetX": 232, + "targetY": 71, + "t": 16044 + }, + { + "targetX": 232, + "targetY": 70, + "t": 16078 + } + ], + "wasCancelled": false + } + } + ] + } + ], + "config": { + "deviceStyle": "screen2", + "roamingStyle": "bounds1", + "receiverStyle": "full", + "safeZoneStyle": "safe-loose" + } +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/resources/json/mobileSafeZoneCancel.json b/common/web/gesture-recognizer/src/test/resources/json/mobileSafeZoneCancel.json new file mode 100644 index 0000000000..cba5a30a7b --- /dev/null +++ b/common/web/gesture-recognizer/src/test/resources/json/mobileSafeZoneCancel.json @@ -0,0 +1,42 @@ +{ + "inputs": [ + { + "touchpoints": [ + { + "isFromTouch": true, + "path": { + "coords": [ + { + "targetX": 226, + "targetY": 38, + "t": 61290 + }, + { + "targetX": 234, + "targetY": 38, + "t": 61676 + }, + { + "targetX": 235, + "targetY": 37, + "t": 61729 + }, + { + "targetX": 237, + "targetY": 37, + "t": 61829 + } + ], + "wasCancelled": true + } + } + ] + } + ], + "config": { + "deviceStyle": "screen2", + "roamingStyle": "bounds1", + "receiverStyle": "full", + "safeZoneStyle": "safe-loose" + } +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/resources/json/popupLongRoamingEnd.json b/common/web/gesture-recognizer/src/test/resources/json/popupLongRoamingEnd.json new file mode 100644 index 0000000000..cefbf00fb4 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/resources/json/popupLongRoamingEnd.json @@ -0,0 +1,812 @@ +{ + "inputs": [ + { + "touchpoints": [ + { + "isFromTouch": true, + "path": { + "coords": [ + { + "targetX": 83, + "targetY": 25, + "t": 512159 + }, + { + "targetX": 83, + "targetY": 33, + "t": 512303 + }, + { + "targetX": 83, + "targetY": 34, + "t": 512335 + }, + { + "targetX": 83, + "targetY": 37, + "t": 512351 + }, + { + "targetX": 82, + "targetY": 38, + "t": 512368 + }, + { + "targetX": 82, + "targetY": 40, + "t": 512385 + }, + { + "targetX": 82, + "targetY": 41, + "t": 512401 + }, + { + "targetX": 82, + "targetY": 44, + "t": 512418 + }, + { + "targetX": 82, + "targetY": 45, + "t": 512435 + }, + { + "targetX": 82, + "targetY": 46, + "t": 512451 + }, + { + "targetX": 80, + "targetY": 46, + "t": 512468 + }, + { + "targetX": 79, + "targetY": 46, + "t": 512484 + }, + { + "targetX": 79, + "targetY": 48, + "t": 512501 + }, + { + "targetX": 79, + "targetY": 48, + "t": 512518 + }, + { + "targetX": 79, + "targetY": 49, + "t": 512535 + }, + { + "targetX": 78, + "targetY": 49, + "t": 512551 + }, + { + "targetX": 78, + "targetY": 50, + "t": 512568 + }, + { + "targetX": 78, + "targetY": 52, + "t": 512584 + }, + { + "targetX": 75, + "targetY": 52, + "t": 512601 + }, + { + "targetX": 75, + "targetY": 53, + "t": 512618 + }, + { + "targetX": 74, + "targetY": 53, + "t": 512635 + }, + { + "targetX": 74, + "targetY": 54, + "t": 512651 + }, + { + "targetX": 71, + "targetY": 54, + "t": 512668 + }, + { + "targetX": 70, + "targetY": 54, + "t": 512685 + }, + { + "targetX": 70, + "targetY": 53, + "t": 512702 + }, + { + "targetX": 68, + "targetY": 53, + "t": 512718 + }, + { + "targetX": 66, + "targetY": 54, + "t": 512735 + }, + { + "targetX": 63, + "targetY": 53, + "t": 512751 + }, + { + "targetX": 62, + "targetY": 53, + "t": 512768 + }, + { + "targetX": 62, + "targetY": 53, + "t": 512785 + }, + { + "targetX": 59, + "targetY": 53, + "t": 512802 + }, + { + "targetX": 56, + "targetY": 53, + "t": 512818 + }, + { + "targetX": 54, + "targetY": 53, + "t": 512835 + }, + { + "targetX": 51, + "targetY": 53, + "t": 512851 + }, + { + "targetX": 48, + "targetY": 53, + "t": 512868 + }, + { + "targetX": 46, + "targetY": 53, + "t": 512885 + }, + { + "targetX": 42, + "targetY": 53, + "t": 512902 + }, + { + "targetX": 39, + "targetY": 53, + "t": 512918 + }, + { + "targetX": 38, + "targetY": 53, + "t": 512935 + }, + { + "targetX": 35, + "targetY": 53, + "t": 512951 + }, + { + "targetX": 32, + "targetY": 53, + "t": 512968 + }, + { + "targetX": 30, + "targetY": 53, + "t": 512984 + }, + { + "targetX": 30, + "targetY": 53, + "t": 513002 + }, + { + "targetX": 28, + "targetY": 53, + "t": 513018 + }, + { + "targetX": 27, + "targetY": 53, + "t": 513035 + }, + { + "targetX": 24, + "targetY": 53, + "t": 513051 + }, + { + "targetX": 23, + "targetY": 53, + "t": 513068 + }, + { + "targetX": 22, + "targetY": 53, + "t": 513084 + }, + { + "targetX": 20, + "targetY": 53, + "t": 513102 + }, + { + "targetX": 19, + "targetY": 53, + "t": 513118 + }, + { + "targetX": 16, + "targetY": 53, + "t": 513135 + }, + { + "targetX": 14, + "targetY": 53, + "t": 513152 + }, + { + "targetX": 12, + "targetY": 53, + "t": 513168 + }, + { + "targetX": 10, + "targetY": 53, + "t": 513184 + }, + { + "targetX": 10, + "targetY": 53, + "t": 513201 + }, + { + "targetX": 8, + "targetY": 53, + "t": 513218 + }, + { + "targetX": 7, + "targetY": 53, + "t": 513234 + }, + { + "targetX": 6, + "targetY": 53, + "t": 513251 + }, + { + "targetX": 6, + "targetY": 53, + "t": 513285 + }, + { + "targetX": 4, + "targetY": 53, + "t": 513319 + }, + { + "targetX": 3, + "targetY": 53, + "t": 513368 + }, + { + "targetX": 2, + "targetY": 52, + "t": 513385 + }, + { + "targetX": -1, + "targetY": 52, + "t": 513401 + }, + { + "targetX": -2, + "targetY": 50, + "t": 513418 + }, + { + "targetX": -5, + "targetY": 50, + "t": 513434 + }, + { + "targetX": -6, + "targetY": 49, + "t": 513452 + }, + { + "targetX": -6, + "targetY": 48, + "t": 513468 + }, + { + "targetX": -8, + "targetY": 48, + "t": 513485 + }, + { + "targetX": -10, + "targetY": 46, + "t": 513518 + }, + { + "targetX": -10, + "targetY": 45, + "t": 513535 + }, + { + "targetX": -12, + "targetY": 44, + "t": 513551 + }, + { + "targetX": -13, + "targetY": 44, + "t": 513568 + }, + { + "targetX": -13, + "targetY": 44, + "t": 513585 + }, + { + "targetX": -13, + "targetY": 42, + "t": 513618 + }, + { + "targetX": -13, + "targetY": 41, + "t": 513635 + }, + { + "targetX": -14, + "targetY": 40, + "t": 513652 + }, + { + "targetX": -14, + "targetY": 40, + "t": 513668 + }, + { + "targetX": -14, + "targetY": 38, + "t": 513685 + }, + { + "targetX": -14, + "targetY": 37, + "t": 513701 + }, + { + "targetX": -14, + "targetY": 36, + "t": 513719 + }, + { + "targetX": -14, + "targetY": 36, + "t": 513735 + }, + { + "targetX": -14, + "targetY": 34, + "t": 513752 + }, + { + "targetX": -14, + "targetY": 32, + "t": 513768 + }, + { + "targetX": -14, + "targetY": 29, + "t": 513802 + }, + { + "targetX": -14, + "targetY": 28, + "t": 513835 + }, + { + "targetX": -14, + "targetY": 26, + "t": 513851 + }, + { + "targetX": -14, + "targetY": 24, + "t": 513868 + }, + { + "targetX": -14, + "targetY": 22, + "t": 513885 + }, + { + "targetX": -14, + "targetY": 21, + "t": 513901 + }, + { + "targetX": -14, + "targetY": 20, + "t": 513919 + }, + { + "targetX": -14, + "targetY": 20, + "t": 513934 + }, + { + "targetX": -14, + "targetY": 18, + "t": 513952 + }, + { + "targetX": -14, + "targetY": 17, + "t": 513968 + }, + { + "targetX": -14, + "targetY": 16, + "t": 513985 + }, + { + "targetX": -14, + "targetY": 14, + "t": 514001 + }, + { + "targetX": -16, + "targetY": 13, + "t": 514018 + }, + { + "targetX": -17, + "targetY": 12, + "t": 514035 + }, + { + "targetX": -17, + "targetY": 10, + "t": 514051 + }, + { + "targetX": -17, + "targetY": 8, + "t": 514068 + }, + { + "targetX": -17, + "targetY": 8, + "t": 514085 + }, + { + "targetX": -17, + "targetY": 5, + "t": 514101 + }, + { + "targetX": -17, + "targetY": 4, + "t": 514118 + }, + { + "targetX": -17, + "targetY": 2, + "t": 514135 + }, + { + "targetX": -17, + "targetY": 0, + "t": 514151 + }, + { + "targetX": -17, + "targetY": 0, + "t": 514168 + }, + { + "targetX": -17, + "targetY": -2, + "t": 514184 + }, + { + "targetX": -17, + "targetY": -3, + "t": 514201 + }, + { + "targetX": -17, + "targetY": -4, + "t": 514219 + }, + { + "targetX": -17, + "targetY": -4, + "t": 514235 + }, + { + "targetX": -17, + "targetY": -6, + "t": 514251 + }, + { + "targetX": -17, + "targetY": -7, + "t": 514268 + }, + { + "targetX": -17, + "targetY": -8, + "t": 514284 + }, + { + "targetX": -17, + "targetY": -10, + "t": 514335 + }, + { + "targetX": -17, + "targetY": -11, + "t": 514351 + }, + { + "targetX": -17, + "targetY": -12, + "t": 514368 + }, + { + "targetX": -17, + "targetY": -12, + "t": 514485 + }, + { + "targetX": -16, + "targetY": -12, + "t": 514518 + }, + { + "targetX": -13, + "targetY": -14, + "t": 514535 + }, + { + "targetX": -13, + "targetY": -15, + "t": 514551 + }, + { + "targetX": -12, + "targetY": -15, + "t": 514602 + }, + { + "targetX": -10, + "targetY": -15, + "t": 514618 + }, + { + "targetX": -6, + "targetY": -15, + "t": 514635 + }, + { + "targetX": -5, + "targetY": -15, + "t": 514652 + }, + { + "targetX": -2, + "targetY": -15, + "t": 514701 + }, + { + "targetX": -1, + "targetY": -15, + "t": 514718 + }, + { + "targetX": 0, + "targetY": -15, + "t": 514735 + }, + { + "targetX": 2, + "targetY": -15, + "t": 514751 + }, + { + "targetX": 3, + "targetY": -15, + "t": 514768 + }, + { + "targetX": 6, + "targetY": -15, + "t": 514785 + }, + { + "targetX": 7, + "targetY": -15, + "t": 514801 + }, + { + "targetX": 10, + "targetY": -15, + "t": 514818 + }, + { + "targetX": 11, + "targetY": -15, + "t": 514834 + }, + { + "targetX": 14, + "targetY": -15, + "t": 514869 + }, + { + "targetX": 15, + "targetY": -15, + "t": 514885 + }, + { + "targetX": 16, + "targetY": -15, + "t": 514918 + }, + { + "targetX": 18, + "targetY": -15, + "t": 514934 + }, + { + "targetX": 19, + "targetY": -15, + "t": 514952 + }, + { + "targetX": 20, + "targetY": -15, + "t": 514968 + }, + { + "targetX": 23, + "targetY": -15, + "t": 515018 + }, + { + "targetX": 27, + "targetY": -15, + "t": 515034 + }, + { + "targetX": 31, + "targetY": -15, + "t": 515052 + }, + { + "targetX": 35, + "targetY": -14, + "t": 515068 + }, + { + "targetX": 36, + "targetY": -15, + "t": 515085 + }, + { + "targetX": 38, + "targetY": -15, + "t": 515101 + }, + { + "targetX": 39, + "targetY": -15, + "t": 515118 + }, + { + "targetX": 40, + "targetY": -15, + "t": 515134 + }, + { + "targetX": 42, + "targetY": -15, + "t": 515168 + }, + { + "targetX": 43, + "targetY": -15, + "t": 515185 + }, + { + "targetX": 44, + "targetY": -15, + "t": 515251 + }, + { + "targetX": 46, + "targetY": -15, + "t": 515268 + }, + { + "targetX": 46, + "targetY": -15, + "t": 515285 + }, + { + "targetX": 47, + "targetY": -15, + "t": 515301 + }, + { + "targetX": 48, + "targetY": -15, + "t": 515335 + }, + { + "targetX": 50, + "targetY": -15, + "t": 515352 + }, + { + "targetX": 50, + "targetY": -15, + "t": 515368 + }, + { + "targetX": 51, + "targetY": -15, + "t": 515385 + }, + { + "targetX": 52, + "targetY": -15, + "t": 515535 + }, + { + "targetX": 51, + "targetY": -15, + "t": 515552 + } + ], + "wasCancelled": false + } + } + ] + } + ], + "config": { + "deviceStyle": "screen4", + "roamingStyle": "bounds4", + "receiverStyle": "popup", + "safeZoneStyle": "safe-default" + } +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/resources/json/popupSafePersistence.json b/common/web/gesture-recognizer/src/test/resources/json/popupSafePersistence.json new file mode 100644 index 0000000000..01c108c530 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/resources/json/popupSafePersistence.json @@ -0,0 +1,602 @@ +{ + "inputs": [ + { + "touchpoints": [ + { + "isFromTouch": false, + "path": { + "coords": [ + { + "targetX": 133, + "targetY": 19, + "t": 601934 + }, + { + "targetX": 134, + "targetY": 19, + "t": 603068 + }, + { + "targetX": 135, + "targetY": 19, + "t": 603093 + }, + { + "targetX": 135, + "targetY": 19, + "t": 603109 + }, + { + "targetX": 136, + "targetY": 19, + "t": 603172 + }, + { + "targetX": 136, + "targetY": 18, + "t": 603193 + }, + { + "targetX": 137, + "targetY": 18, + "t": 603226 + }, + { + "targetX": 137, + "targetY": 18, + "t": 603278 + }, + { + "targetX": 138, + "targetY": 18, + "t": 603309 + }, + { + "targetX": 138, + "targetY": 17, + "t": 603326 + }, + { + "targetX": 139, + "targetY": 17, + "t": 603360 + }, + { + "targetX": 139, + "targetY": 16, + "t": 603376 + }, + { + "targetX": 140, + "targetY": 16, + "t": 603410 + }, + { + "targetX": 141, + "targetY": 16, + "t": 603426 + }, + { + "targetX": 141, + "targetY": 16, + "t": 603486 + }, + { + "targetX": 142, + "targetY": 16, + "t": 603509 + }, + { + "targetX": 143, + "targetY": 16, + "t": 603543 + }, + { + "targetX": 144, + "targetY": 16, + "t": 603584 + }, + { + "targetX": 145, + "targetY": 16, + "t": 603610 + }, + { + "targetX": 145, + "targetY": 16, + "t": 603646 + }, + { + "targetX": 146, + "targetY": 16, + "t": 603679 + }, + { + "targetX": 147, + "targetY": 16, + "t": 603692 + }, + { + "targetX": 148, + "targetY": 16, + "t": 603735 + }, + { + "targetX": 149, + "targetY": 16, + "t": 603759 + }, + { + "targetX": 149, + "targetY": 17, + "t": 603776 + }, + { + "targetX": 150, + "targetY": 17, + "t": 603809 + }, + { + "targetX": 151, + "targetY": 18, + "t": 603826 + }, + { + "targetX": 151, + "targetY": 19, + "t": 603873 + }, + { + "targetX": 152, + "targetY": 19, + "t": 603897 + }, + { + "targetX": 153, + "targetY": 19, + "t": 603954 + }, + { + "targetX": 153, + "targetY": 19, + "t": 603994 + }, + { + "targetX": 153, + "targetY": 20, + "t": 604076 + }, + { + "targetX": 153, + "targetY": 21, + "t": 604558 + }, + { + "targetX": 153, + "targetY": 22, + "t": 607143 + }, + { + "targetX": 153, + "targetY": 23, + "t": 607250 + }, + { + "targetX": 153, + "targetY": 23, + "t": 607276 + }, + { + "targetX": 153, + "targetY": 25, + "t": 607292 + }, + { + "targetX": 153, + "targetY": 26, + "t": 609118 + }, + { + "targetX": 153, + "targetY": 27, + "t": 609148 + }, + { + "targetX": 152, + "targetY": 27, + "t": 609176 + }, + { + "targetX": 152, + "targetY": 27, + "t": 609193 + }, + { + "targetX": 152, + "targetY": 28, + "t": 610651 + }, + { + "targetX": 152, + "targetY": 29, + "t": 610659 + }, + { + "targetX": 152, + "targetY": 30, + "t": 610676 + }, + { + "targetX": 152, + "targetY": 31, + "t": 610709 + }, + { + "targetX": 152, + "targetY": 31, + "t": 611036 + }, + { + "targetX": 152, + "targetY": 32, + "t": 611042 + }, + { + "targetX": 152, + "targetY": 33, + "t": 611059 + }, + { + "targetX": 152, + "targetY": 34, + "t": 611076 + }, + { + "targetX": 152, + "targetY": 35, + "t": 611092 + }, + { + "targetX": 152, + "targetY": 35, + "t": 611126 + }, + { + "targetX": 152, + "targetY": 36, + "t": 611142 + }, + { + "targetX": 152, + "targetY": 37, + "t": 611159 + }, + { + "targetX": 152, + "targetY": 38, + "t": 611192 + }, + { + "targetX": 151, + "targetY": 39, + "t": 611208 + }, + { + "targetX": 151, + "targetY": 40, + "t": 611226 + }, + { + "targetX": 151, + "targetY": 41, + "t": 611242 + }, + { + "targetX": 150, + "targetY": 43, + "t": 611275 + }, + { + "targetX": 150, + "targetY": 43, + "t": 611292 + }, + { + "targetX": 150, + "targetY": 45, + "t": 611309 + }, + { + "targetX": 149, + "targetY": 47, + "t": 611342 + }, + { + "targetX": 149, + "targetY": 47, + "t": 611359 + }, + { + "targetX": 149, + "targetY": 49, + "t": 611376 + }, + { + "targetX": 149, + "targetY": 51, + "t": 611409 + }, + { + "targetX": 149, + "targetY": 51, + "t": 611426 + }, + { + "targetX": 149, + "targetY": 52, + "t": 611442 + }, + { + "targetX": 149, + "targetY": 53, + "t": 611459 + }, + { + "targetX": 148, + "targetY": 55, + "t": 611475 + }, + { + "targetX": 148, + "targetY": 55, + "t": 611493 + }, + { + "targetX": 148, + "targetY": 56, + "t": 611509 + }, + { + "targetX": 147, + "targetY": 57, + "t": 611526 + }, + { + "targetX": 147, + "targetY": 59, + "t": 611543 + }, + { + "targetX": 146, + "targetY": 59, + "t": 611559 + }, + { + "targetX": 145, + "targetY": 60, + "t": 611576 + }, + { + "targetX": 145, + "targetY": 61, + "t": 611592 + }, + { + "targetX": 145, + "targetY": 63, + "t": 611609 + }, + { + "targetX": 144, + "targetY": 64, + "t": 611626 + }, + { + "targetX": 143, + "targetY": 65, + "t": 611642 + }, + { + "targetX": 141, + "targetY": 67, + "t": 611659 + }, + { + "targetX": 141, + "targetY": 68, + "t": 611676 + }, + { + "targetX": 139, + "targetY": 70, + "t": 611693 + }, + { + "targetX": 137, + "targetY": 71, + "t": 611709 + }, + { + "targetX": 136, + "targetY": 72, + "t": 611726 + }, + { + "targetX": 134, + "targetY": 74, + "t": 611743 + }, + { + "targetX": 133, + "targetY": 75, + "t": 611759 + }, + { + "targetX": 131, + "targetY": 76, + "t": 611776 + }, + { + "targetX": 130, + "targetY": 77, + "t": 611793 + }, + { + "targetX": 129, + "targetY": 78, + "t": 611809 + }, + { + "targetX": 127, + "targetY": 79, + "t": 611825 + }, + { + "targetX": 125, + "targetY": 79, + "t": 611842 + }, + { + "targetX": 123, + "targetY": 80, + "t": 611859 + }, + { + "targetX": 121, + "targetY": 81, + "t": 611876 + }, + { + "targetX": 121, + "targetY": 82, + "t": 611892 + }, + { + "targetX": 118, + "targetY": 82, + "t": 611909 + }, + { + "targetX": 117, + "targetY": 82, + "t": 611926 + }, + { + "targetX": 115, + "targetY": 82, + "t": 611942 + }, + { + "targetX": 113, + "targetY": 82, + "t": 611959 + }, + { + "targetX": 111, + "targetY": 82, + "t": 611975 + }, + { + "targetX": 109, + "targetY": 82, + "t": 611992 + }, + { + "targetX": 106, + "targetY": 81, + "t": 612009 + }, + { + "targetX": 102, + "targetY": 79, + "t": 612025 + }, + { + "targetX": 97, + "targetY": 78, + "t": 612042 + }, + { + "targetX": 92, + "targetY": 76, + "t": 612058 + }, + { + "targetX": 87, + "targetY": 75, + "t": 612076 + }, + { + "targetX": 83, + "targetY": 73, + "t": 612092 + }, + { + "targetX": 79, + "targetY": 72, + "t": 612109 + }, + { + "targetX": 75, + "targetY": 71, + "t": 612125 + }, + { + "targetX": 73, + "targetY": 71, + "t": 612142 + }, + { + "targetX": 71, + "targetY": 70, + "t": 612158 + }, + { + "targetX": 70, + "targetY": 70, + "t": 612175 + }, + { + "targetX": 69, + "targetY": 69, + "t": 612192 + }, + { + "targetX": 69, + "targetY": 68, + "t": 612244 + }, + { + "targetX": 68, + "targetY": 68, + "t": 612258 + }, + { + "targetX": 67, + "targetY": 68, + "t": 612276 + }, + { + "targetX": 66, + "targetY": 67, + "t": 612292 + }, + { + "targetX": 65, + "targetY": 67, + "t": 612309 + } + ], + "wasCancelled": false + } + } + ] + } + ], + "config": { + "deviceStyle": "screen1", + "roamingStyle": "bounds4", + "receiverStyle": "popup", + "safeZoneStyle": "safe-generous" + } +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/resources/json/popupShimCancel.json b/common/web/gesture-recognizer/src/test/resources/json/popupShimCancel.json new file mode 100644 index 0000000000..8e90d8c142 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/resources/json/popupShimCancel.json @@ -0,0 +1,167 @@ +{ + "inputs": [ + { + "touchpoints": [ + { + "isFromTouch": false, + "path": { + "coords": [ + { + "targetX": 68, + "targetY": 22, + "t": 511067 + }, + { + "targetX": 67, + "targetY": 22, + "t": 511180 + }, + { + "targetX": 65, + "targetY": 24, + "t": 511211 + }, + { + "targetX": 63, + "targetY": 25, + "t": 511228 + }, + { + "targetX": 62, + "targetY": 25, + "t": 511244 + }, + { + "targetX": 58, + "targetY": 26, + "t": 511261 + }, + { + "targetX": 55, + "targetY": 26, + "t": 511277 + }, + { + "targetX": 51, + "targetY": 27, + "t": 511294 + }, + { + "targetX": 46, + "targetY": 28, + "t": 511310 + }, + { + "targetX": 42, + "targetY": 29, + "t": 511327 + }, + { + "targetX": 38, + "targetY": 29, + "t": 511345 + }, + { + "targetX": 34, + "targetY": 29, + "t": 511361 + }, + { + "targetX": 30, + "targetY": 29, + "t": 511377 + }, + { + "targetX": 25, + "targetY": 29, + "t": 511394 + }, + { + "targetX": 20, + "targetY": 29, + "t": 511411 + }, + { + "targetX": 15, + "targetY": 29, + "t": 511427 + }, + { + "targetX": 10, + "targetY": 29, + "t": 511444 + }, + { + "targetX": 5, + "targetY": 29, + "t": 511460 + }, + { + "targetX": 2, + "targetY": 28, + "t": 511478 + }, + { + "targetX": -2, + "targetY": 28, + "t": 511494 + }, + { + "targetX": -5, + "targetY": 28, + "t": 511511 + }, + { + "targetX": -7, + "targetY": 28, + "t": 511527 + }, + { + "targetX": -9, + "targetY": 28, + "t": 511544 + }, + { + "targetX": -11, + "targetY": 28, + "t": 511561 + }, + { + "targetX": -14, + "targetY": 28, + "t": 511578 + }, + { + "targetX": -18, + "targetY": 28, + "t": 511595 + }, + { + "targetX": -21, + "targetY": 28, + "t": 511611 + }, + { + "targetX": -25, + "targetY": 28, + "t": 511628 + }, + { + "targetX": -26, + "targetY": 28, + "t": 511644 + } + ], + "wasCancelled": true + } + } + ] + } + ], + "config": { + "deviceStyle": "screen4", + "roamingStyle": "bounds4", + "receiverStyle": "popup", + "safeZoneStyle": "safe-default" + } +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/test/test.sh b/common/web/gesture-recognizer/src/test/test.sh new file mode 100755 index 0000000000..d923098928 --- /dev/null +++ b/common/web/gesture-recognizer/src/test/test.sh @@ -0,0 +1,75 @@ +#!/usr/bin/env bash +# +# Builds the include script for the current Keyman version. +# + +# Exit on command failure and when using unset variables: +set -eu + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(greadlink -f "${BASH_SOURCE[0]}" 2>/dev/null || readlink -f "${BASH_SOURCE[0]}")" +. "$(dirname "$THIS_SCRIPT")/../../../../../resources/build/build-utils.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +. $KEYMAN_ROOT/resources/shellHelperFunctions.sh + +# This script runs from its own folder +cd "$(dirname $THIS_SCRIPT)" + +################################ Main script ################################ + +builder_describe "Runs all tests for the gesture-recognizer module" \ + "test+" \ + ":headless Runs headless user tests" \ + ":browser Runs browser-based user tests" \ + "--ci Uses CI-based test configurations & emits CI-friendly test reports" \ + "--debug,-d Activates developer-friendly debug mode for unit tests where applicable" + +builder_parse "$@" + +# TODO: build if out-of-date if test is specified +# TODO: configure if npm has not been run, and build is specified + +# START - Script parameter configuration +REPORT_STYLE="local" # Default setting. + +if builder_has_option --ci; then + REPORT_STYLE="ci" + + echo "Replacing user-friendly test reports & configurations with CI-friendly versions." +fi + +# END - Script parameter configuration + +test-browser ( ) { + KARMA_FLAGS= + if [[ $# -eq 1 && $1 == "debug" ]]; then + KARMA_CONFIG="manual.conf.js" + KARMA_FLAGS="--no-single-run" + elif [ $REPORT_STYLE == "local" ]; then + KARMA_CONFIG="manual.conf.js" + else + KARMA_CONFIG="CI.conf.js" + fi + + npm run karma -- start src/test/auto/browser/$KARMA_CONFIG "$KARMA_FLAGS" +} + +if builder_has_action test :headless; then + echo "Headless unit tests for this module have not yet been defined." + builder_report success test :headless +fi + +if builder_has_action test :browser; then + if builder_has_option --debug; then + echo "Running browser-based unit tests in debug-mode configuration..." + echo + echo "${COLOR_YELLOW}You must manually terminate this mode (CTRL-C) for the script to exit.${COLOR_RESET}" + sleep 2 + test-browser debug + else + test-browser + fi + builder_report success test :browser +fi \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/fixtureLayoutConfiguration.ts b/common/web/gesture-recognizer/src/tools/unit-test-resources/src/fixtureLayoutConfiguration.ts index fb6921a4c2..22f1548522 100644 --- a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/fixtureLayoutConfiguration.ts +++ b/common/web/gesture-recognizer/src/tools/unit-test-resources/src/fixtureLayoutConfiguration.ts @@ -1,3 +1,5 @@ +/// + namespace Testing { /* * Cross-reference all enum definitions in this class with those of host-fixture/gestureHost.css. @@ -35,14 +37,29 @@ namespace Testing { public safeZoneStyle: SafeLayoutClass; constructor() + constructor(recordedConfig: JSONObject) constructor(deviceStyle?: DeviceLayoutClass, roamingStyle?: RoamingLayoutClass, receiverStyle?: ReceiverLayoutClass, safeZoneStyle?: SafeLayoutClass); - constructor(deviceStyle?: DeviceLayoutClass, + constructor(param1?: DeviceLayoutClass | JSONObject, roamingStyle?: RoamingLayoutClass, receiverStyle?: ReceiverLayoutClass, safeZoneStyle?: SafeLayoutClass) { + let deviceStyle: DeviceLayoutClass; + + // Determine constructor form utilized & disambiguate. + if(param1 && typeof param1 != "string") { + const recordedConfig = param1; + + deviceStyle = recordedConfig.deviceStyle; + roamingStyle = recordedConfig.roamingStyle; + receiverStyle = recordedConfig.receiverStyle; + safeZoneStyle = recordedConfig.safeZoneStyle; + } else { + deviceStyle = param1 as DeviceLayoutClass; + } + this.deviceStyle = deviceStyle ?? DeviceLayoutClass.MOBILE_PHONE_PAGE; this.roamingStyle = roamingStyle ?? RoamingLayoutClass.TOP_OVERFLOW; this.receiverStyle = receiverStyle ?? ReceiverLayoutClass.KEYBOARD_STYLE; diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/inputSequenceSimulator.ts b/common/web/gesture-recognizer/src/tools/unit-test-resources/src/inputSequenceSimulator.ts new file mode 100644 index 0000000000..8f0a0e8294 --- /dev/null +++ b/common/web/gesture-recognizer/src/tools/unit-test-resources/src/inputSequenceSimulator.ts @@ -0,0 +1,315 @@ +namespace Testing { + /** + * This class is designed to 'replay' a recorded InputSequence's DOM events to ensure + * that the recognizer's DOM layer is working correctly. + */ + export class InputSequenceSimulator { + private controller: HostFixtureLayoutController; + + constructor(controller: HostFixtureLayoutController) { + this.controller = controller; + } + + getSampleClientPos(sample: JSONObject): {clientX: number, clientY: number} { + let targetRoot = this.controller.recognizer.config.targetRoot; + let targetRect = targetRoot.getBoundingClientRect(); + + return { + clientX: sample.targetX + targetRect.left, + clientY: sample.targetY + targetRect.top + } + } + + private buildSyntheticTouchEvent(name: string, dict: TouchEventInit): TouchEvent { + let config = this.controller.recognizer.config; + + let baseDict = { + srcElement: config.touchEventRoot, + view: window + } as any; + + if(dict.bubbles !== undefined) { + baseDict.bubbles = dict.bubbles; // is an event read-only property; can't assign it later! + delete dict.bubbles; + } + + // Produces a hacky-but-sufficient implementation of TouchList for our purposes + // in environments that avoid direct touch-support. + const arrToTouchList = (arr: Touch[]): TouchList => { + return { + //@ts-ignore + _arr: arr, // Obviously, this isn't a standard member of TouchList. + length: arr.length, + item: function(i: number) { return this._arr[i]; } + } + } + + //@ts-ignore + dict.touches = arrToTouchList(dict.touches); + //@ts-ignore + dict.changedTouches = arrToTouchList(dict.changedTouches); + + let event = new Event(name, baseDict) as TouchEvent; + + // Since touch-related properties aren't expected by some browsers' Event constructors, + // we must add them "in post" when testing in some non-touch environments. For those, + // the Event constructor will neglect to copy them from `baseDict`. + Object.assign(event, dict); + return event; + } + + replayTouchSample(sample: JSONObject, + state: string, + identifier: number, + otherTouches: Touch[], + targetElement?: HTMLElement): Touch { + let config = this.controller.recognizer.config; + + let event: TouchEvent; + const mappedSample = this.getSampleClientPos(sample); + + let touch: Touch; + let touchDict = {identifier: identifier, + target: targetElement || config.targetRoot, + ...mappedSample}; + if(window['Touch'] !== undefined) { + touch = new Touch(touchDict); + } else { + // When not performing touch-emulation, some desktop browsers will leave `Touch` undefined. + touch = touchDict as any; + } + + otherTouches = otherTouches || []; + + let touchEventDict: TouchEventInit = { + bubbles: true, + touches: [touch, ...otherTouches], + changedTouches: [touch], + } + + let buildEvent = (type: string, dict: TouchEventInit) => { + if(window['TouchEvent'] !== undefined) { + // Nothing beats the real thing. + return new TouchEvent(type, dict); + } else { + // Effectively, an internal polyfill. But, if at ALL possible, use a real version instead! + return this.buildSyntheticTouchEvent(type, dict); + } + } + + switch(state) { + case 'start': + event = buildEvent('touchstart', touchEventDict); + break; + case 'move': + event = buildEvent('touchmove', touchEventDict); + break; + case 'end': + event = buildEvent('touchend', touchEventDict); + break; + } + + config.targetRoot.dispatchEvent(event); + + return touch; + } + + replayMouseSample(sample: JSONObject, state: string, targetElement?: HTMLElement) { + let config = this.controller.recognizer.config; + + let event: MouseEvent; + let eventDict: MouseEventInit = { + bubbles: true, + buttons: state == 'end' ? 0 : 1, + ...this.getSampleClientPos(sample) + }; + + switch(state) { + case 'start': + event = new MouseEvent('mousedown', eventDict); + break; + case 'move': + event = new MouseEvent('mousemove', eventDict); + break; + case 'end': + event = new MouseEvent('mouseup', eventDict); + break; + } + + targetElement = targetElement || config.targetRoot; + + // Implies that `targetElement` is the target element; .target cannot be set separately + // for mouse events, so we can't use config.mouseEventRoot. Don't worry, the + // listener will still receive the event b/c bubbling. + targetElement.dispatchEvent(event); + } + + /** + * Given a previously-recorded sequence of sampled input coordinates, this function + * reproduces their corresponding events and runs them through this instance's linked + * gesture-recognizer instance. + * + * It will re-use the original fixture-layout configuration. + * @param sequenceTestSpec A previously-recorded sequence of sampled input coordinates. + * @param replayExecutor A middleman function to use when simulating the actual events. + * @returns The final sample to be simulated. + */ + private replayCore(sequenceTestSpec: RecordedCoordSequenceSet, + replayExecutor: (func: () => void, sample?: com.keyman.osk.InputSample) => void): com.keyman.osk.InputSample { + let inputs = sequenceTestSpec.inputs; + const config = sequenceTestSpec.config; + + this.controller.layoutConfiguration = new FixtureLayoutConfiguration(config); + + /** + * For each corresponding recorded sequence, notes the index of the sequence's + * next sample to be 'replayed'. If the full sequence has already been + * replayed, its corresponding entry will be set to Number.MAX_VALUE instead. + */ + let sequenceProgress: number[] = new Array(inputs.length).fill(0); + let sequenceTouches: Touch[] = new Array(inputs.length).fill(null); + + let lastSample = null; + + while(sequenceProgress.find((number) => number != Number.MAX_VALUE) !== undefined) { + // Determine the sequence that has the chronologically next-in-line sample + // to reproduce. + let minTimestamp = Number.MAX_VALUE; + + /** + * Pulls double-duty as the index of the sequence owning said sample & as + * the sequence's common 'identifier' value for emulated Touches. + */ + let selectedSequence = -1; + + for(let index=0; index < inputs.length; index++) { + // TODO: does not iterate over all touchpoints. Not that we can have more than one at present... + const touchpoint = new com.keyman.osk.TrackedPoint(index, inputs[index].touchpoints[0]); + const indexInSequence = sequenceProgress[index]; + + if(indexInSequence == Number.MAX_VALUE) { + continue; + } + + if(touchpoint.path.coords[indexInSequence].t < minTimestamp) { + minTimestamp = touchpoint.path.coords[indexInSequence].t; + selectedSequence = index; + } + } + + const touchpoint = new com.keyman.osk.TrackedPoint(selectedSequence, inputs[selectedSequence].touchpoints[0]); + const indexInSequence = sequenceProgress[selectedSequence]; + let state: string = "move"; + + let appendEndEvent = false; + if(indexInSequence + 1 >= touchpoint.path.coords.length) { + sequenceProgress[selectedSequence] = Number.MAX_VALUE; + appendEndEvent = true; + } else { + sequenceProgress[selectedSequence]++; + } + + if(indexInSequence == 0) { + state = "start"; + } + + const sample = touchpoint.path.coords[indexInSequence]; + if(touchpoint.isFromTouch) { + let otherTouches = [...sequenceTouches]; + otherTouches.splice(selectedSequence, 1); + // Now that we've removed the entry for the current touchpoint, filter out any null entries. + otherTouches = otherTouches.filter((val) => !!val); + + replayExecutor(() => { + sequenceTouches[selectedSequence] = this.replayTouchSample(sample, state, selectedSequence, otherTouches); + }, sample); + if(appendEndEvent) { + // Synchronous in sync mode, but set with a timeout in async mode. + replayExecutor(() => { + this.replayTouchSample(sample, "end", selectedSequence, otherTouches); + }, sample); + sequenceTouches[selectedSequence] = null; + } + } else { + replayExecutor(() => { + this.replayMouseSample(sample, state); + }, sample); + if(appendEndEvent) { + replayExecutor(() => { + this.replayMouseSample(sample, "end"); + }, sample); + } + } + + lastSample = sample; + } + + return lastSample; + } + + /** + * Given a previously-recorded sequence of sampled input coordinates, this function + * reproduces their corresponding events and runs them through this instance's linked + * gesture-recognizer instance. The original timestamps are ignored; only the relative + * order of events will be enforced. + * + * It will re-use the original fixture-layout configuration. + * @param sequenceTestSpec A previously-recorded sequence of sampled input coordinates. + * @returns A recording of the reproduced version of that sequence. + */ + replaySync(sequenceTestSpec: RecordedCoordSequenceSet): RecordedCoordSequenceSet { + // We're going to record the test spec as we replay it, eventually returning the + // final result. + const recorder = new Testing.SequenceRecorder(this.controller); + + // We're operating synchronously, so just simulate the events on the spot. + this.replayCore(sequenceTestSpec, (func) => { func() }); + + // We technically don't have access to 'samples' for the actual recorded object. + // Obtaining a 'deep copy' (though methodless) works around this nicely and + // matches the original `sequenceTestSpec`'s format to boot. + let recording = JSON.parse(recorder.recordingsToJSON()) as RecordedCoordSequenceSet; + recorder.clear(); + return recording; + } + + + /** + * Given a previously-recorded sequence of sampled input coordinates, this function + * reproduces their corresponding events and runs them through this instance's linked + * gesture-recognizer instance. The original timestamps will be used; if a timer-stubbing + * package (like SinonJS) is used, they should be reproduced to ~1ms precision. + * + * (Experimentally, the actual value tends to be Math.floor() - not Math.round().) + * + * It will re-use the original fixture-layout configuration. + * @param sequenceTestSpec A previously-recorded sequence of sampled input coordinates. + * @returns A Promise for a recording of the reproduced version of that sequence. + */ + replayAsync(sequenceTestSpec: RecordedCoordSequenceSet): Promise { + // We're going to record the test spec as we replay it, eventually returning the + // final result. + const recorder = new Testing.SequenceRecorder(this.controller); + + // We're operating asynchronously, so we wrap each simulated event in a timeout. + // NOTE: this will be much less precise without stubbed timeout systems like the one + // included within SinonJS. + const finalSample = this.replayCore(sequenceTestSpec, (func, sample) => { + window.setTimeout(() => { + func(); + }, sample.t); + }); + + return new Promise((resolve) => { + window.setTimeout(() => { + // We technically don't have access to 'samples' for the actual recorded object. + // Obtaining a 'deep copy' (though methodless) works around this nicely and + // matches the original `sequenceTestSpec`'s format to boot. + let recording = JSON.parse(recorder.recordingsToJSON()) as RecordedCoordSequenceSet; + recorder.clear(); + resolve(recording); + }, finalSample.t + 30); + }); + } + } +} \ No newline at end of file diff --git a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/sequenceRecorder.ts b/common/web/gesture-recognizer/src/tools/unit-test-resources/src/sequenceRecorder.ts index 37ca5fab8d..7936158e13 100644 --- a/common/web/gesture-recognizer/src/tools/unit-test-resources/src/sequenceRecorder.ts +++ b/common/web/gesture-recognizer/src/tools/unit-test-resources/src/sequenceRecorder.ts @@ -24,6 +24,10 @@ namespace Testing { this._attachRecognizerHooks(); } + public get count(): number { + return this.startOrder.length; + } + private _attachRecognizerHooks() { this.controller.recognizer.on('inputstart', (wrappedSequence: WrappedInputSequence) => { const id = wrappedSequence.touchpoints[0].identifier; diff --git a/common/web/gesture-recognizer/src/touchEventEngine.ts b/common/web/gesture-recognizer/src/touchEventEngine.ts index bbb8a7cf54..6e4960fc2a 100644 --- a/common/web/gesture-recognizer/src/touchEventEngine.ts +++ b/common/web/gesture-recognizer/src/touchEventEngine.ts @@ -12,9 +12,11 @@ namespace com.keyman.osk { public constructor(config: Nonoptional) { super(config); - this._touchStart = this.onTouchStart.bind(this); - this._touchMove = this.onTouchMove.bind(this); - this._touchEnd = this.onTouchEnd.bind(this); + // We use this approach, rather than .bind, because _this_ version allows hook + // insertion for unit tests via prototype manipulation. The .bind version doesn't. + this._touchStart = (event: TouchEvent) => this.onTouchStart(event); + this._touchMove = (event: TouchEvent) => this.onTouchMove(event); + this._touchEnd = (event: TouchEvent) => this.onTouchEnd(event); } private get eventRoot(): HTMLElement { diff --git a/common/web/lm-worker/package.json b/common/web/lm-worker/package.json index f0ebe95036..753948495a 100644 --- a/common/web/lm-worker/package.json +++ b/common/web/lm-worker/package.json @@ -31,7 +31,7 @@ "karma-teamcity-reporter": "^1.1.0", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", - "sinon": "^7.1.1", + "sinon": "^14.0.0", "ts-node": "^9.1.1", "typescript": "^4.5.4" }, diff --git a/package-lock.json b/package-lock.json index 8975479db6..0149000800 100644 --- a/package-lock.json +++ b/package-lock.json @@ -199,7 +199,7 @@ "karma-teamcity-reporter": "^1.1.0", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", - "sinon": "^7.1.1", + "sinon": "^14.0.0", "ts-node": "^9.1.1", "typescript": "^4.5.4" } @@ -221,18 +221,30 @@ }, "common/web/gesture-recognizer": { "name": "@keymanapp/gesture-recognizer", - "license": "MIT", "dependencies": { - "@keymanapp/keyman-version": "*", - "@keymanapp/web-utils": "*", "eventemitter3": "^4.0.0" }, "devDependencies": { "@keymanapp/resources-gosh": "*", "@types/node": "^11.9.4", + "@types/sinon": "^10.0.13", "chai": "^4.3.4", + "karma": "^6.3.4", + "karma-browserstack-launcher": "^1.6.0", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^2.2.0", + "karma-firefox-launcher": "^1.1.0", + "karma-fixture": "^0.2.6", + "karma-html2js-preprocessor": "^1.1.0", + "karma-ie-launcher": "^1.0.0", + "karma-json-fixtures-preprocessor": "0.0.6", + "karma-mocha": "^2.0.1", + "karma-mocha-reporter": "^2.2.5", + "karma-safari-launcher": "^1.0.0", + "karma-teamcity-reporter": "^1.1.0", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", + "sinon": "^14.0.0", "ts-node": "^9.1.1", "typescript": "^4.5.4" } @@ -341,7 +353,7 @@ "karma-teamcity-reporter": "^1.1.0", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", - "sinon": "^7.1.1", + "sinon": "^14.0.0", "ts-node": "^9.1.1", "typescript": "^4.5.4" } @@ -1496,31 +1508,30 @@ "type-detect": "4.0.8" } }, - "node_modules/@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "node_modules/@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" + "@sinonjs/commons": "^1.7.0" } }, "node_modules/@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz", + "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" } }, "node_modules/@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, "node_modules/@socket.io/base64-arraybuffer": { @@ -1724,6 +1735,21 @@ "@types/node": "*" } }, + "node_modules/@types/sinon": { + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", + "integrity": "sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==", + "dev": true, + "dependencies": { + "@types/sinonjs__fake-timers": "*" + } + }, + "node_modules/@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -1916,12 +1942,6 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "node_modules/array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -4393,12 +4413,6 @@ } } }, - "node_modules/lolex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", - "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", - "dev": true - }, "node_modules/lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -4952,33 +4966,24 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "node_modules/nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz", + "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==", "dev": true, "dependencies": { - "@sinonjs/formatio": "^3.2.1", + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": ">=5", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", - "lolex": "^5.0.1", "path-to-regexp": "^1.7.0" } }, "node_modules/nise/node_modules/isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "dev": true }, - "node_modules/nise/node_modules/lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "dependencies": { - "@sinonjs/commons": "^1.7.0" - } - }, "node_modules/nise/node_modules/path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -5842,48 +5847,21 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "node_modules/sinon": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", - "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.0.tgz", + "integrity": "sha512-ugA6BFmE+WrJdh0owRZHToLd32Uw3Lxq6E6LtNRU+xTVBefx632h03Q7apXWRsRdZAJ41LB8aUfn2+O4jsDNMw==", "dev": true, "dependencies": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.3", - "diff": "^3.5.0", - "lolex": "^4.2.0", - "nise": "^1.5.2", - "supports-color": "^5.5.0" - } - }, - "node_modules/sinon/node_modules/diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true, - "engines": { - "node": ">=0.3.1" - } - }, - "node_modules/sinon/node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true, - "engines": { - "node": ">=4" - } - }, - "node_modules/sinon/node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^6.1.1", + "diff": "^5.0.0", + "nise": "^5.1.1", + "supports-color": "^7.2.0" }, - "engines": { - "node": ">=4" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/sinon" } }, "node_modules/socket.io": { @@ -7587,14 +7565,27 @@ "@keymanapp/gesture-recognizer": { "version": "file:common/web/gesture-recognizer", "requires": { - "@keymanapp/keyman-version": "*", "@keymanapp/resources-gosh": "*", - "@keymanapp/web-utils": "*", "@types/node": "^11.9.4", + "@types/sinon": "^10.0.13", "chai": "^4.3.4", "eventemitter3": "^4.0.0", + "karma": "^6.3.4", + "karma-browserstack-launcher": "^1.6.0", + "karma-chai": "^0.1.0", + "karma-chrome-launcher": "^2.2.0", + "karma-firefox-launcher": "^1.1.0", + "karma-fixture": "^0.2.6", + "karma-html2js-preprocessor": "^1.1.0", + "karma-ie-launcher": "^1.0.0", + "karma-json-fixtures-preprocessor": "0.0.6", + "karma-mocha": "^2.0.1", + "karma-mocha-reporter": "^2.2.5", + "karma-safari-launcher": "^1.0.0", + "karma-teamcity-reporter": "^1.1.0", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", + "sinon": "^14.0.0", "ts-node": "^9.1.1", "typescript": "^4.5.4" } @@ -7752,7 +7743,7 @@ "karma-teamcity-reporter": "^1.1.0", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", - "sinon": "^7.1.1", + "sinon": "^14.0.0", "string.prototype.codepointat": "^0.2.1", "ts-node": "^9.1.1", "typescript": "^4.5.4" @@ -7800,7 +7791,7 @@ "karma-teamcity-reporter": "^1.1.0", "mocha": "^8.4.0", "mocha-teamcity-reporter": "^4.0.0", - "sinon": "^7.1.1", + "sinon": "^14.0.0", "string.prototype.codepointat": "^0.2.1", "string.prototype.startswith": "^0.2.0", "ts-node": "^9.1.1", @@ -8321,31 +8312,30 @@ "type-detect": "4.0.8" } }, - "@sinonjs/formatio": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@sinonjs/formatio/-/formatio-3.2.2.tgz", - "integrity": "sha512-B8SEsgd8gArBLMD6zpRw3juQ2FVSsmdd7qlevyDqzS9WTCtvF55/gAL+h6gue8ZvPYcdiPdvueM/qm//9XzyTQ==", + "@sinonjs/fake-timers": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, "requires": { - "@sinonjs/commons": "^1", - "@sinonjs/samsam": "^3.1.0" + "@sinonjs/commons": "^1.7.0" } }, "@sinonjs/samsam": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-3.3.3.tgz", - "integrity": "sha512-bKCMKZvWIjYD0BLGnNrxVuw4dkWCYsLqFOUWw8VgKF/+5Y+mE7LfHWPIYoDXowH+3a9LsWDMo0uAP8YDosPvHQ==", + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.1.tgz", + "integrity": "sha512-cZ7rKJTLiE7u7Wi/v9Hc2fs3Ucc3jrWeMgPHbbTCeVAB2S0wOBbYlkJVeNSL04i7fdhT8wIbDq1zhC/PXTD2SA==", "dev": true, "requires": { - "@sinonjs/commons": "^1.3.0", - "array-from": "^2.1.1", - "lodash": "^4.17.15" + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" } }, "@sinonjs/text-encoding": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.1.tgz", - "integrity": "sha512-+iTbntw2IZPb/anVDbypzfQa+ay64MW0Zo8aJ8gZPWMMK6/OubMVb6lUPMagqjOPnmtauXnFCACVl3O7ogjeqQ==", + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", "dev": true }, "@socket.io/base64-arraybuffer": { @@ -8543,6 +8533,21 @@ "@types/node": "*" } }, + "@types/sinon": { + "version": "10.0.13", + "resolved": "https://registry.npmjs.org/@types/sinon/-/sinon-10.0.13.tgz", + "integrity": "sha512-UVjDqJblVNQYvVNUsj0PuYYw0ELRmgt1Nt5Vk0pT5f16ROGfcKJY8o1HVuMOJOpD727RrGB9EGvoaTQE5tgxZQ==", + "dev": true, + "requires": { + "@types/sinonjs__fake-timers": "*" + } + }, + "@types/sinonjs__fake-timers": { + "version": "8.1.2", + "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-8.1.2.tgz", + "integrity": "sha512-9GcLXF0/v3t80caGs5p2rRfkB+a8VBGLJZVih6CNFkx8IZ994wiKKLSRs9nuFwk1HevWs/1mnUmkApGrSGsShA==", + "dev": true + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -8697,12 +8702,6 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, - "array-from": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/array-from/-/array-from-2.1.1.tgz", - "integrity": "sha1-z+nYwmYoudxa7MYqn12PHzUsEZU=", - "dev": true - }, "asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", @@ -10719,12 +10718,6 @@ } } }, - "lolex": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-4.2.0.tgz", - "integrity": "sha512-gKO5uExCXvSm6zbF562EvM+rd1kQDnB9AZBbiQVzf1ZmdDpxUSvpnAaVOP83N/31mRK8Ml8/VE8DMvsAZQ+7wg==", - "dev": true - }, "lowercase-keys": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", @@ -11139,33 +11132,24 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==" }, "nise": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/nise/-/nise-1.5.3.tgz", - "integrity": "sha512-Ymbac/94xeIrMf59REBPOv0thr+CJVFMhrlAkW/gjCIE58BGQdCj0x7KRCb3yz+Ga2Rz3E9XXSvUyyxqqhjQAQ==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.1.tgz", + "integrity": "sha512-yr5kW2THW1AkxVmCnKEh4nbYkJdB3I7LUkiUgOvEkOp414mc2UMaHMA7pjq1nYowhdoJZGwEKGaQVbxfpWj10A==", "dev": true, "requires": { - "@sinonjs/formatio": "^3.2.1", + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": ">=5", "@sinonjs/text-encoding": "^0.7.1", "just-extend": "^4.0.2", - "lolex": "^5.0.1", "path-to-regexp": "^1.7.0" }, "dependencies": { "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", "dev": true }, - "lolex": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/lolex/-/lolex-5.1.2.tgz", - "integrity": "sha512-h4hmjAvHTmd+25JSwrtTIuwbKdwg5NzZVRMLn9saij4SZaepCrTCxPr35H/3bjwfMJtN+t3CX8672UIkglz28A==", - "dev": true, - "requires": { - "@sinonjs/commons": "^1.7.0" - } - }, "path-to-regexp": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.8.0.tgz", @@ -11822,41 +11806,17 @@ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "sinon": { - "version": "7.5.0", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-7.5.0.tgz", - "integrity": "sha512-AoD0oJWerp0/rY9czP/D6hDTTUYGpObhZjMpd7Cl/A6+j0xBE+ayL/ldfggkBXUs0IkvIiM1ljM8+WkOc5k78Q==", + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-14.0.0.tgz", + "integrity": "sha512-ugA6BFmE+WrJdh0owRZHToLd32Uw3Lxq6E6LtNRU+xTVBefx632h03Q7apXWRsRdZAJ41LB8aUfn2+O4jsDNMw==", "dev": true, "requires": { - "@sinonjs/commons": "^1.4.0", - "@sinonjs/formatio": "^3.2.1", - "@sinonjs/samsam": "^3.3.3", - "diff": "^3.5.0", - "lolex": "^4.2.0", - "nise": "^1.5.2", - "supports-color": "^5.5.0" - }, - "dependencies": { - "diff": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", - "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - } + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^6.1.1", + "diff": "^5.0.0", + "nise": "^5.1.1", + "supports-color": "^7.2.0" } }, "socket.io": { diff --git a/web/unit_tests/test.sh b/web/unit_tests/test.sh index e3f4edc1d9..c10ea639a8 100755 --- a/web/unit_tests/test.sh +++ b/web/unit_tests/test.sh @@ -70,6 +70,7 @@ get_OS get_browser_set_for_OS CONFIG=manual.conf.js # TODO - get/make OS-specific version +SH_FLAGS= DEBUG=false FLAGS= HEADLESS_FLAGS=-skip-package-install @@ -81,6 +82,7 @@ while [[ $# -gt 0 ]] ; do -CI) CONFIG=CI.conf.js HEADLESS_FLAGS="$HEADLESS_FLAGS -CI" + SH_FLAGS="--ci" ;; -log-level) shift @@ -131,9 +133,15 @@ cd ../tools/recorder # First: Web-core tests. pushd "$KEYMAN_ROOT/common/web/input-processor" ./test.sh $HEADLESS_FLAGS || fail "Tests failed by dependencies; aborting integration tests." -# Once done, now we run the integrated (KeymanWeb) tests. popd +# For now, we'll also link in the gesture-recognizer unit tests here. +echo_heading "Running gesture-recognizer test suite" +pushd "$KEYMAN_ROOT/common/web/gesture-recognizer" +npm run test -- $SH_FLAGS +popd + +# Once done, now we run the integrated (KeymanWeb) tests. echo_heading "Running KeymanWeb integration test suite" npm --no-color run modernizr -- -c unit_tests/modernizr.config.json -d unit_tests/modernizr.js npm --no-color run karma -- start $FLAGS $BROWSERS unit_tests/$CONFIG