Skip to content

Commit

Permalink
chore(web): prepping for gesture-sequence tests
Browse files Browse the repository at this point in the history
  • Loading branch information
jahorton committed Aug 30, 2023
1 parent 6ef9236 commit 654d561
Show file tree
Hide file tree
Showing 8 changed files with 137 additions and 40 deletions.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import EventEmitter from "eventemitter3";

import { GestureModelDefs } from "../../../configuration/gestureConfiguration.js";
import { GestureModelDefs, getGestureModel, getGestureModelSet } from "../specs/gestureModelDefs.js";
import { GestureSource, GestureSourceSubview } from "../../gestureSource.js";
import { GestureMatcher, MatchResult, PredecessorMatch } from "./gestureMatcher.js";
import { GestureModel } from "../specs/gestureModel.js";
Expand Down Expand Up @@ -55,7 +55,6 @@ interface EventMap<Type> {
stageReport: GestureStageReport<Type>,
changeConfiguration: (configStackCommand: PushConfig<Type> | PopConfig) => void
) => void;
cancel: () => void;
complete: () => void;
}

Expand Down Expand Up @@ -106,6 +105,11 @@ export class GestureSequence<Type> extends EventEmitter<EventMap<Type>> {
const matchReport = new GestureStageReport<Type>(selection);
this.stageReports.push(matchReport);

if(!selection.result.matched && selection.result.action.type == 'complete') {
this.emit('complete');
return;
}

// Raise the event, providing a functor that allows the listener to specify an alt config for the next stage.
// Example case: longpress => subkey selection - the subkey menu has different boundary conditions.
this.emit('stage', matchReport, (command) => {
Expand Down Expand Up @@ -220,31 +224,6 @@ export class GestureSequence<Type> extends EventEmitter<EventMap<Type>> {
};
}

export function getGestureModel<Type>(defs: GestureModelDefs<Type>, id: string): GestureModel<Type> {
const result = defs.gestures.find((spec) => spec.id == id);
if(!result) {
throw new Error(`Could not find spec for gesture with id '${id}'`);
}

return result;
}

export function getGestureModelSet<Type>(defs: GestureModelDefs<Type>, id: string): GestureModel<Type>[] {
let idSet = defs.sets[id];
if(!idSet) {
throw new Error(`Could not find a defined gesture-set with id '${id}'`);
}

const set = defs.gestures.filter((spec) => !!idSet.find((id) => spec.id == id));
const missing = idSet.filter((id) => !set.find((spec) => spec.id == id));

if(missing.length > 0) {
throw new Error(`Set '${id}' cannot find definitions for gestures with ids ${missing}`);
}

return set;
}

interface ProcessedGestureAction<Type> {
nextModels: GestureModel<Type>[],
sources: GestureSource<Type>[],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { GestureMatcher } from './gestureMatcher.js';
export { GestureSequence } from './gestureSequence.js';
export { MatcherSelection, MatcherSelector } from './matcherSelector.js';
export { PathMatcher } from './pathMatcher.js';
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import * as gestures from "../index.js";

// Prototype spec for the main gesture & gesture-set definitions.
// A work in-progress. Should probably land somewhere within headless/gestures/specs/.
// ... with the following two functions, as well.
export interface GestureModelDefs<Type> {
gestures: gestures.specs.GestureModel<Type>[],
sets: {
default: string[],
} & Record<string, string[]>;
}


export function getGestureModel<Type>(defs: GestureModelDefs<Type>, id: string): gestures.specs.GestureModel<Type> {
const result = defs.gestures.find((spec) => spec.id == id);
if(!result) {
throw new Error(`Could not find spec for gesture with id '${id}'`);
}

return result;
}

export function getGestureModelSet<Type>(defs: GestureModelDefs<Type>, id: string): gestures.specs.GestureModel<Type>[] {
let idSet = defs.sets[id];
if(!idSet) {
throw new Error(`Could not find a defined gesture-set with id '${id}'`);
}

const set = defs.gestures.filter((spec) => !!idSet.find((id) => spec.id == id));
const missing = idSet.filter((id) => !set.find((spec) => spec.id == id));

if(missing.length > 0) {
throw new Error(`Set '${id}' cannot find definitions for gestures with ids ${missing}`);
}

return set;
}
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './contactModel.js';
export * from './gestureModel.js';
export * from './gestureModelDefs.js';
export * from './pathModel.js';
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ import EventEmitter from "eventemitter3";
import { InputEngineBase } from "./inputEngineBase.js";
import { GestureSource, GestureSourceSubview } from "./gestureSource.js";
import { MatcherSelector } from "./gestures/matchers/matcherSelector.js";
import { GestureSequence, getGestureModelSet } from "./gestures/matchers/gestureSequence.js";
import { GestureModelDefs } from "../configuration/gestureConfiguration.js";
import { GestureSequence } from "./gestures/matchers/gestureSequence.js";
import { GestureModelDefs, getGestureModelSet } from "./gestures/specs/gestureModelDefs.js";

interface EventMap<HoveredItemType> {
/**
Expand Down
1 change: 1 addition & 0 deletions common/web/gesture-recognizer/src/engine/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export { ConstructingSegment } from './headless/subsegmentation/constructingSegment.js';
export { CumulativePathStats } from './headless/cumulativePathStats.js';
export { GestureModelDefs } from './headless/gestures/specs/gestureModelDefs.js';
export { GestureRecognizer } from "./gestureRecognizer.js";
export { GestureRecognizerConfiguration } from "./configuration/gestureRecognizerConfiguration.js";
export { InputEngineBase } from "./headless/inputEngineBase.js";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { assert } from 'chai'
import sinon from 'sinon';

import * as PromiseStatusModule from 'promise-status-async';
const PromiseStatuses = PromiseStatusModule.PromiseStatuses;
import { assertingPromiseStatus as promiseStatus } from '../../../resources/assertingPromiseStatus.js';

import { GestureModelDefs, GestureSource, gestures, InputSample } from '@keymanapp/gesture-recognizer';

import { TouchpathTurtle } from '#tools';
import { ManagedPromise, timedPromise } from '@keymanapp/web-utils';

import { simulateMultiSourceMatcherInput } from "../../../resources/simulateMultiSourceInput.js";

import {
LongpressModel,
MultitapModel,
SimpleTapModel,
SubkeySelectModel
} from './isolatedGestureSpecs.js';

import {
LongpressDistanceThreshold,
MainLongpressSourceModel
} from './isolatedPathSpecs.js';

const TestGestureModelDefinitions: GestureModelDefs<string> = {
gestures: [
LongpressModel,
MultitapModel,
SimpleTapModel,
SubkeySelectModel
],
// TODO: modipress mode
sets: {
default: [LongpressModel.id, SimpleTapModel.id]
}
}

// TODO(?): right, simulation. Again. Yaaaaay.
// - Fortunately, the _start_ can just use selection-sim semantics; the GestureSequence
// constructor takes in an existing Selector & its selection, after all.
// - in fact... that should be 100% fine, right? There's only ever the one selector!
// - the issue: we do want to 'select' early, before later-stage timers are all run.

// Also to-test: some of the component functions outside of the GestureSequence class.
// They're fairly simple to do, right?
// - processGestureAction, in particular.

// ALSO possible to test: GestureSource (with some of the 'fun', new wrinkles.)

// Possible to test, though separate file - the gesture-model / gesture-model-set
// retrieval funcs.

// Later, in a different file: testing TouchpointCoordinator's integration with this.

describe("GestureSequence", function() {
beforeEach(function() {
this.fakeClock = sinon.useFakeTimers();
});

afterEach(function() {
this.fakeClock.restore();
});

// TODO: author tests for (at least) the following
// A single, simple-tap
// Two+ separate simple-taps (overlapping time interval)
// - could be mitigated with a special 'flag' on the gesture-model, perhaps?
// - would be 'nice', conceptually, for consumers... but I don't think it's worth prioritizing
// at the moment. Got enough else to deal with for now.
// - but the Sequence DOES hold a ref to the coordinator object, so... it technically _could_
// spin off a separate sequence?
// Longpress: hold -> subkey-select
// simple-tap => multi-tap (x2)
// Modipress
// Android longpress delegation
//
// Defer: flick test - confirm => execute
//
// .on('complete') may be checked to validate if any further match attempts will be possible
// based on the condition; it may be worth 'extending' tests (via mocked timer) to
// double-check such scenarios.

it('first sequence test', () => {
// simulateSelectorInput should be sufficient for sequence emulation; just capture
// the first selection once kick-started, then add the "fun" hooks for the rest of the test.
});
});

0 comments on commit 654d561

Please sign in to comment.