feat(web): first segmentation test based on test-recording

This commit is contained in:
Joshua A. Horton 2022-09-01 12:36:22 +07:00
parent c2dcffc043
commit ba568bef40
3 changed files with 397 additions and 5 deletions

View file

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

View file

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

View file

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