mirror of
https://github.com/keymanapp/keyman.git
synced 2026-09-04 06:37:41 +00:00
feat(web): first segmentation test based on test-recording
This commit is contained in:
parent
c2dcffc043
commit
ba568bef40
3 changed files with 397 additions and 5 deletions
|
|
@ -1,8 +1,6 @@
|
|||
const assert = require('chai').assert;
|
||||
const sinon = require('sinon');
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const PromiseStatusModule = require('promise-status-async');
|
||||
const promiseStatus = PromiseStatusModule.promiseStatus;
|
||||
const PromiseStatuses = PromiseStatusModule.PromiseStatuses;
|
||||
|
|
@ -14,9 +12,6 @@ const PathSegmenter = com.keyman.osk.PathSegmenter;
|
|||
const timedPromise = require('../../resources/timedPromise.js');
|
||||
|
||||
describe("Segmentation", function() {
|
||||
// // File paths need to be from the package's / module's root folder
|
||||
// let testJSONtext = fs.readFileSync('src/test/resources/json/canaryRecording.json');
|
||||
|
||||
describe("Single-sample 'sequence'", function() {
|
||||
it("expected segment types", function() {
|
||||
let spy = sinon.fake();
|
||||
|
|
@ -0,0 +1,117 @@
|
|||
const assert = require('chai').assert;
|
||||
const sinon = require('sinon');
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const PromiseStatusModule = require('promise-status-async');
|
||||
const promiseStatus = PromiseStatusModule.promiseStatus;
|
||||
const PromiseStatuses = PromiseStatusModule.PromiseStatuses;
|
||||
|
||||
const GestureRecognizer = require('../../../../build/index.js');
|
||||
const com = GestureRecognizer.com;
|
||||
const PathSegmenter = com.keyman.osk.PathSegmenter;
|
||||
|
||||
const timedPromise = require('../../resources/timedPromise.js');
|
||||
|
||||
const SEGMENT_TEST_JSON_FOLDER = 'src/test/resources/json/segmentation';
|
||||
|
||||
describe("Segmentation", function() {
|
||||
describe("Recorded sequence tests", function() {
|
||||
beforeEach(function() {
|
||||
this.fakeClock = sinon.useFakeTimers();
|
||||
})
|
||||
|
||||
afterEach(function() {
|
||||
// NOTE: for debugging investigations, it may be necessary to use .only on
|
||||
// the test under investigation and to disable the `this.fakeClock.restore()` line.
|
||||
//
|
||||
// Tests tend to timeout when interactively debugging, and having unmocked timers
|
||||
// suddenly restored during investigation can cause some very confusing behavior.
|
||||
this.fakeClock.restore();
|
||||
})
|
||||
|
||||
it("simple_ne_move.json", async function() {
|
||||
// NOTE: this recording's final 'hold' segment is somewhat tightly attuned to the DEFAULT_CONFIG
|
||||
// hold time setting. Changing default values there may necessitate a hand-edit tweak to the
|
||||
// test recording's data in order for this test to continue passing as-is.
|
||||
|
||||
let testJSONtext = fs.readFileSync(`${SEGMENT_TEST_JSON_FOLDER}/simple_ne_move.json`);
|
||||
let jsonObj = JSON.parse(testJSONtext);
|
||||
|
||||
// Prepare some of the basic setup.
|
||||
let spy = sinon.fake();
|
||||
|
||||
const segmenter = new PathSegmenter(PathSegmenter.DEFAULT_CONFIG, spy);
|
||||
|
||||
// Next: drill down to the relevant part(s).
|
||||
const sourceTrackedPath = jsonObj.inputs[0].touchpoints[0].path;
|
||||
|
||||
const sourceSamples = sourceTrackedPath.coords;
|
||||
const originalSegments = sourceTrackedPath.segments;
|
||||
const lastSegment = originalSegments[originalSegments.length-1];
|
||||
|
||||
// Build promises designed to reproduce the events at the correct times.
|
||||
const samplePromises = sourceSamples.map((sample) => {
|
||||
return timedPromise(() => {
|
||||
segmenter.add(sample);
|
||||
}, sample.t - sourceSamples[0].t);
|
||||
});
|
||||
|
||||
// Extend any Promises here if relevant
|
||||
|
||||
// Segmentation is then ended via a followup event.
|
||||
// The source recording does not save an input for the 'touchend' event - but
|
||||
// we can infer the correct time from the final segment.
|
||||
|
||||
const segmentationEndPromise = timedPromise(() => {
|
||||
segmenter.close();
|
||||
}, lastSegment.lastCoord.t - sourceSamples[0].t);
|
||||
|
||||
// Wrap it all together with a nice little bow.
|
||||
const finalPromise = Promise.all([...samplePromises, segmentationEndPromise]).catch((reason) => {
|
||||
// Because we use a `setInterval` internally, we need cleanup if things go wrong.
|
||||
segmenter.close();
|
||||
throw reason;
|
||||
});
|
||||
|
||||
await this.fakeClock.runAllAsync();
|
||||
|
||||
// Make sure this Promise also gets to complete.
|
||||
await finalPromise;
|
||||
|
||||
// Any post-sequence tests to run.
|
||||
const originalSegmentTypeSequence = originalSegments.map((segment) => segment.type);
|
||||
|
||||
const reproedSegments = spy.getCalls().map((call) => call.args[0]);
|
||||
const reproedSegmentTypeSequence = reproedSegments.map((segment) => segment.type);
|
||||
|
||||
assert.sameOrderedMembers(reproedSegmentTypeSequence, originalSegmentTypeSequence);
|
||||
|
||||
for(let segment of reproedSegments) {
|
||||
assert.isTrue(await promiseStatus(segment.whenRecognized) == PromiseStatuses.PROMISE_RESOLVED);
|
||||
assert.isTrue(await promiseStatus(segment.whenResolved) == PromiseStatuses.PROMISE_RESOLVED);
|
||||
}
|
||||
|
||||
// Complete our deserialization of the original segment in question - needed for
|
||||
// clearer, more direct comparisons.
|
||||
const holdSegmentOriginal = new com.keyman.osk.Segment(originalSegments[1]);
|
||||
const holdSegmentRepro = reproedSegments[1];
|
||||
|
||||
assert.equal(holdSegmentRepro.type, 'hold');
|
||||
assert.isAtLeast(holdSegmentRepro.duration, holdSegmentOriginal.duration - PathSegmenter.DEFAULT_CONFIG.holdMinimumDuration);
|
||||
|
||||
// Complete our deserialization of the original segment in question - needed for
|
||||
// clearer, more direct comparisons.
|
||||
const moveSegmentOriginal = new com.keyman.osk.Segment(originalSegments[2]);
|
||||
const moveSegmentRepro = reproedSegments[2];
|
||||
|
||||
assert.equal(moveSegmentRepro.type, 'move');
|
||||
assert.isAtLeast(moveSegmentRepro.duration, moveSegmentOriginal.duration - PathSegmenter.DEFAULT_CONFIG.holdMinimumDuration);
|
||||
|
||||
// Move-specific checks
|
||||
assert.equal(moveSegmentRepro.direction, moveSegmentOriginal.direction);
|
||||
assert.isAtLeast(moveSegmentRepro.distance, moveSegmentOriginal.distance - 2 * PathSegmenter.DEFAULT_CONFIG.holdMoveTolerance);
|
||||
assert.isAtLeast(moveSegmentRepro.peakSpeed, 0.9 * moveSegmentOriginal.peakSpeed);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,280 @@
|
|||
{
|
||||
"inputs": [
|
||||
{
|
||||
"touchpoints": [
|
||||
{
|
||||
"isFromTouch": false,
|
||||
"path": {
|
||||
"coords": [
|
||||
{
|
||||
"targetX": 52,
|
||||
"targetY": 134,
|
||||
"t": 10174
|
||||
},
|
||||
{
|
||||
"targetX": 52,
|
||||
"targetY": 133,
|
||||
"t": 10373
|
||||
},
|
||||
{
|
||||
"targetX": 52,
|
||||
"targetY": 132,
|
||||
"t": 10386
|
||||
},
|
||||
{
|
||||
"targetX": 53,
|
||||
"targetY": 131,
|
||||
"t": 10403
|
||||
},
|
||||
{
|
||||
"targetX": 53,
|
||||
"targetY": 130,
|
||||
"t": 10417
|
||||
},
|
||||
{
|
||||
"targetX": 56,
|
||||
"targetY": 126,
|
||||
"t": 10433
|
||||
},
|
||||
{
|
||||
"targetX": 59,
|
||||
"targetY": 121,
|
||||
"t": 10450
|
||||
},
|
||||
{
|
||||
"targetX": 61,
|
||||
"targetY": 118,
|
||||
"t": 10467
|
||||
},
|
||||
{
|
||||
"targetX": 64,
|
||||
"targetY": 114,
|
||||
"t": 10484
|
||||
},
|
||||
{
|
||||
"targetX": 68,
|
||||
"targetY": 108,
|
||||
"t": 10500
|
||||
},
|
||||
{
|
||||
"targetX": 73,
|
||||
"targetY": 103,
|
||||
"t": 10517
|
||||
},
|
||||
{
|
||||
"targetX": 77,
|
||||
"targetY": 98,
|
||||
"t": 10534
|
||||
},
|
||||
{
|
||||
"targetX": 80,
|
||||
"targetY": 95,
|
||||
"t": 10550
|
||||
},
|
||||
{
|
||||
"targetX": 82,
|
||||
"targetY": 91,
|
||||
"t": 10567
|
||||
},
|
||||
{
|
||||
"targetX": 85,
|
||||
"targetY": 89,
|
||||
"t": 10583
|
||||
},
|
||||
{
|
||||
"targetX": 87,
|
||||
"targetY": 86,
|
||||
"t": 10601
|
||||
},
|
||||
{
|
||||
"targetX": 89,
|
||||
"targetY": 84,
|
||||
"t": 10617
|
||||
},
|
||||
{
|
||||
"targetX": 91,
|
||||
"targetY": 82,
|
||||
"t": 10633
|
||||
},
|
||||
{
|
||||
"targetX": 93,
|
||||
"targetY": 80,
|
||||
"t": 10650
|
||||
},
|
||||
{
|
||||
"targetX": 94,
|
||||
"targetY": 77,
|
||||
"t": 10667
|
||||
},
|
||||
{
|
||||
"targetX": 97,
|
||||
"targetY": 73,
|
||||
"t": 10683
|
||||
},
|
||||
{
|
||||
"targetX": 99,
|
||||
"targetY": 70,
|
||||
"t": 10700
|
||||
},
|
||||
{
|
||||
"targetX": 101,
|
||||
"targetY": 68,
|
||||
"t": 10717
|
||||
},
|
||||
{
|
||||
"targetX": 102,
|
||||
"targetY": 65,
|
||||
"t": 10734
|
||||
},
|
||||
{
|
||||
"targetX": 104,
|
||||
"targetY": 63,
|
||||
"t": 10750
|
||||
},
|
||||
{
|
||||
"targetX": 106,
|
||||
"targetY": 60,
|
||||
"t": 10766
|
||||
},
|
||||
{
|
||||
"targetX": 107,
|
||||
"targetY": 58,
|
||||
"t": 10784
|
||||
},
|
||||
{
|
||||
"targetX": 108,
|
||||
"targetY": 57,
|
||||
"t": 10800
|
||||
},
|
||||
{
|
||||
"targetX": 109,
|
||||
"targetY": 55,
|
||||
"t": 10816
|
||||
},
|
||||
{
|
||||
"targetX": 110,
|
||||
"targetY": 53,
|
||||
"t": 10833
|
||||
},
|
||||
{
|
||||
"targetX": 110,
|
||||
"targetY": 52,
|
||||
"t": 10851
|
||||
},
|
||||
{
|
||||
"targetX": 111,
|
||||
"targetY": 52,
|
||||
"t": 10869
|
||||
},
|
||||
{
|
||||
"targetX": 111,
|
||||
"targetY": 52,
|
||||
"t": 10951
|
||||
}
|
||||
],
|
||||
"segments": [
|
||||
{
|
||||
"type": "start",
|
||||
"duration": 0,
|
||||
"speed": 0,
|
||||
"distance": 0,
|
||||
"peakSpeed": 0,
|
||||
"initialCoord": {
|
||||
"targetX": 52,
|
||||
"targetY": 134,
|
||||
"t": 10174
|
||||
},
|
||||
"lastCoord": {
|
||||
"targetX": 52,
|
||||
"targetY": 134,
|
||||
"t": 10174
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "hold",
|
||||
"duration": 203,
|
||||
"cardinalDirection": "n",
|
||||
"speed": 0.01696751286262412,
|
||||
"distance": 4.123105625617661,
|
||||
"angle": 0.24497866312686423,
|
||||
"peakSpeed": 0,
|
||||
"initialCoord": {
|
||||
"targetX": 52,
|
||||
"targetY": 134,
|
||||
"t": 10174
|
||||
},
|
||||
"lastCoord": {
|
||||
"targetX": 53,
|
||||
"targetY": 130,
|
||||
"t": 10377
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "move",
|
||||
"duration": 574,
|
||||
"cardinalDirection": "ne",
|
||||
"speed": 0.17599268299546728,
|
||||
"distance": 101.01980003939822,
|
||||
"angle": 0.6237017162268916,
|
||||
"peakSpeed": 0.5202346361292709,
|
||||
"initialCoord": {
|
||||
"targetX": 52,
|
||||
"targetY": 134,
|
||||
"t": 10337
|
||||
},
|
||||
"lastCoord": {
|
||||
"targetX": 111,
|
||||
"targetY": 52,
|
||||
"t": 10911
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "hold",
|
||||
"duration": 102,
|
||||
"cardinalDirection": "e",
|
||||
"speed": 0.00980392156862745,
|
||||
"distance": 1,
|
||||
"angle": 1.5707963267948966,
|
||||
"peakSpeed": 0.041666666666666796,
|
||||
"initialCoord": {
|
||||
"targetX": 110,
|
||||
"targetY": 52,
|
||||
"t": 10851
|
||||
},
|
||||
"lastCoord": {
|
||||
"targetX": 111,
|
||||
"targetY": 52,
|
||||
"t": 10953
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "end",
|
||||
"duration": 0,
|
||||
"speed": 0,
|
||||
"distance": 0,
|
||||
"peakSpeed": 0,
|
||||
"initialCoord": {
|
||||
"targetX": 111,
|
||||
"targetY": 52,
|
||||
"t": 10953
|
||||
},
|
||||
"lastCoord": {
|
||||
"targetX": 111,
|
||||
"targetY": 52,
|
||||
"t": 10953
|
||||
}
|
||||
}
|
||||
],
|
||||
"wasCancelled": false
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"config": {
|
||||
"deviceStyle": "screen2",
|
||||
"roamingStyle": "bounds1",
|
||||
"receiverStyle": "full",
|
||||
"safeZoneStyle": "safe-loose"
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue