Merge pull request #6970 from keymanapp/feat/web/recognizer-input-unit-testing-2

feat(web): Gesture Recognizer unit testing 🐵
This commit is contained in:
Joshua Horton 2022-08-16 10:20:03 +07:00 • committed by GitHub
commit ff76082c19
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 4034 additions and 183 deletions

View file

@ -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"
},

View file

@ -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);

View file

@ -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

View file

@ -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"
}
}

View file

@ -9,7 +9,7 @@ namespace com.keyman.osk {
}
export class GestureRecognizer extends EventEmitter<EventMap> {
protected readonly config: Nonoptional<GestureRecognizerConfiguration>;
public readonly config: Nonoptional<GestureRecognizerConfiguration>;
private readonly mouseEngine: MouseEventEngine;
private readonly touchEngine: TouchEventEngine;

View file

@ -15,9 +15,11 @@ namespace com.keyman.osk {
public constructor(config: Nonoptional<GestureRecognizerConfiguration>) {
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.

View file

@ -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");

View file

@ -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,
}

View file

@ -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;
}
});
});
});

View file

@ -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!");
});
});
});

View file

@ -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);
});
}
});
});

View file

@ -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));
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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"
}
}

View file

@ -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

View file

@ -1,3 +1,5 @@
/// <reference path="inputRecording.ts" />
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<FixtureLayoutConfiguration>)
constructor(deviceStyle?: DeviceLayoutClass,
roamingStyle?: RoamingLayoutClass,
receiverStyle?: ReceiverLayoutClass,
safeZoneStyle?: SafeLayoutClass);
constructor(deviceStyle?: DeviceLayoutClass,
constructor(param1?: DeviceLayoutClass | JSONObject<FixtureLayoutConfiguration>,
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;

View file

@ -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<com.keyman.osk.InputSample>): {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<com.keyman.osk.InputSample>,
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<com.keyman.osk.InputSample>, 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<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 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);
});
}
}
}

View file

@ -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;

View file

@ -12,9 +12,11 @@ namespace com.keyman.osk {
public constructor(config: Nonoptional<GestureRecognizerConfiguration>) {
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 {

View file

@ -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"
},

290
package-lock.json generated
View file

@ -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": {

View file

@ -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