mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-31 04:37:41 +00:00
Merge pull request #15563 from keymanapp/change/web/relocate-search-quotient-test-utils
change(web): relocate search-quotient node testing-utility methods 🚂
This commit is contained in:
commit
c8836c8e98
7 changed files with 119 additions and 119 deletions
|
|
@ -85,7 +85,8 @@
|
|||
},
|
||||
"imports": {
|
||||
"#gesture-tools": "./src/engine/osk/gesture-processor/build/tools/obj/index.js",
|
||||
"#recorder": "./build/tools/testing/recorder/obj/index.js"
|
||||
"#recorder": "./build/tools/testing/recorder/obj/index.js",
|
||||
"#test-resources/*.js": "./build/test/resources/*.js"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
|
|
|
|||
|
|
@ -10,12 +10,8 @@
|
|||
import { LexicalModelTypes } from "@keymanapp/common-types";
|
||||
|
||||
import { SearchNode, SearchResult } from "./distance-modeler.js";
|
||||
import { SearchQuotientSpur } from "./search-quotient-spur.js";
|
||||
import { SearchQuotientRoot } from "./search-quotient-root.js";
|
||||
|
||||
import Distribution = LexicalModelTypes.Distribution;
|
||||
import LexicalModel = LexicalModelTypes.LexicalModel;
|
||||
import Transform = LexicalModelTypes.Transform;
|
||||
|
||||
let SPACE_ID_SEED = 0;
|
||||
|
||||
|
|
@ -211,103 +207,4 @@ export interface SearchQuotientNode {
|
|||
* @param charIndex
|
||||
*/
|
||||
split(charIndex: number): [SearchQuotientNode, SearchQuotientNode];
|
||||
}
|
||||
|
||||
/**
|
||||
* Denotes whether or not the represented search-space quotient path includes
|
||||
* paths built from the specified set of keystroke input distributions. The
|
||||
* distribution count should match .inputCount - no omissions or extras are
|
||||
* permitted.
|
||||
*
|
||||
* Designed explicitly for use in unit testing; it's not super-efficient, so
|
||||
* avoid live use.
|
||||
*
|
||||
* @param keystrokeDistributions
|
||||
* @internal
|
||||
*/
|
||||
function quotientPathHasInputs(node: SearchQuotientNode, keystrokeDistributions: Distribution<Transform>[]): boolean {
|
||||
if(!(node instanceof SearchQuotientSpur)) {
|
||||
for(const p of node.parents) {
|
||||
if(quotientPathHasInputs(p, keystrokeDistributions)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return node.parents.length == 0 && keystrokeDistributions.length == 0;
|
||||
}
|
||||
|
||||
if(node.inputCount == 0) {
|
||||
return keystrokeDistributions.length == 0;
|
||||
} else if(keystrokeDistributions.length != node.inputCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tailInput = [...keystrokeDistributions[keystrokeDistributions.length - 1]];
|
||||
keystrokeDistributions = keystrokeDistributions.slice(0, keystrokeDistributions.length - 1);
|
||||
const localInput = node.lastInput;
|
||||
|
||||
const parentHasInput = () => !!node.parents.find(p => quotientPathHasInputs(p, keystrokeDistributions));
|
||||
|
||||
// Actual reference match? Easy mode.
|
||||
if(localInput == tailInput) {
|
||||
return parentHasInput();
|
||||
} else if(localInput.length != tailInput.length) {
|
||||
return false;
|
||||
} else {
|
||||
for(let entry of tailInput) {
|
||||
const matchIndex = localInput.findIndex((x) => {
|
||||
const s1 = x.sample;
|
||||
const s2 = entry.sample;
|
||||
// Check for equal reference first before the other checks; it makes a nice shortcut.
|
||||
if(x == entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(x.p == entry.p && s1.deleteLeft == s2.deleteLeft
|
||||
&& s1.id == s2.id && ((s1.deleteRight ?? 0) == (s2.deleteRight ?? 0)) && s1.insert == s2.insert
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if(matchIndex == -1) {
|
||||
return false;
|
||||
} else {
|
||||
tailInput.splice(matchIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return parentHasInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates the different potential SearchQuotientSpur sequences that lead
|
||||
* to a SearchQuotientNode.
|
||||
*
|
||||
* Intended only for use during unit testing. Does not include the root node.
|
||||
*/
|
||||
function constituentPaths(node: SearchQuotientNode): SearchQuotientSpur[][] {
|
||||
if(node instanceof SearchQuotientRoot) {
|
||||
return [];
|
||||
} else if(node instanceof SearchQuotientSpur) {
|
||||
const parentPaths = constituentPaths(node.parents[0]);
|
||||
if(parentPaths.length > 0) {
|
||||
return parentPaths.map(p => {
|
||||
p.push(node);
|
||||
return p;
|
||||
});
|
||||
} else {
|
||||
return [[node]];
|
||||
}
|
||||
} else {
|
||||
throw new Error("constituentPaths is unable to handle a new, unexpected SearchQuotientNode type");
|
||||
}
|
||||
}
|
||||
|
||||
export const unitTestEndpoints = {
|
||||
quotientPathHasInputs,
|
||||
constituentPaths
|
||||
}
|
||||
|
|
@ -20,10 +20,4 @@ export { ModelCompositor } from './model-compositor.js';
|
|||
export * from './predict-helpers.js';
|
||||
export { default as TransformUtils } from './transformUtils.js'
|
||||
export { default as LMLayerWorker } from './index.js'
|
||||
export * from './transform-subsets.js';
|
||||
|
||||
// unitTestEndpoints
|
||||
import { unitTestEndpoints as SearchQuotientNodeEndpoints } from './correction/search-quotient-node.js';
|
||||
export const unitTestEndpoints: typeof SearchQuotientNodeEndpoints = {
|
||||
...SearchQuotientNodeEndpoints
|
||||
}
|
||||
export * from './transform-subsets.js';
|
||||
|
|
@ -15,15 +15,15 @@ import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs'
|
|||
import { LexicalModelTypes } from '@keymanapp/common-types';
|
||||
import { KMWString } from '@keymanapp/web-utils';
|
||||
|
||||
import { ContextToken, correction, generateSubsetId, getBestMatches, InputSegment, models, SearchQuotientSpur, unitTestEndpoints } from '@keymanapp/lm-worker/test-index';
|
||||
import { ContextToken, correction, generateSubsetId, getBestMatches, InputSegment, models, SearchQuotientSpur } from '@keymanapp/lm-worker/test-index';
|
||||
|
||||
import { quotientPathHasInputs } from "#test-resources/searchQuotientUtils.js";
|
||||
|
||||
import Distribution = LexicalModelTypes.Distribution;
|
||||
import ExecutionTimer = correction.ExecutionTimer;
|
||||
import Transform = LexicalModelTypes.Transform;
|
||||
import TrieModel = models.TrieModel;
|
||||
|
||||
const { quotientPathHasInputs } = unitTestEndpoints;
|
||||
|
||||
var plainModel = new TrieModel(jsonFixture('models/tries/english-1000'),
|
||||
{wordBreaker: defaultBreaker});
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
import { assert } from 'chai';
|
||||
|
||||
import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs';
|
||||
import { LegacyQuotientRoot, models, unitTestEndpoints } from '@keymanapp/lm-worker/test-index';
|
||||
import { LegacyQuotientRoot, models } from '@keymanapp/lm-worker/test-index';
|
||||
|
||||
import { buildSimplePathSplitFixture } from './search-quotient-spur.tests.js';
|
||||
|
||||
const { quotientPathHasInputs } = unitTestEndpoints;
|
||||
import { quotientPathHasInputs } from '#test-resources/searchQuotientUtils.js';
|
||||
|
||||
import TrieModel = models.TrieModel;
|
||||
|
||||
|
|
|
|||
|
|
@ -20,15 +20,14 @@ import {
|
|||
PathInputProperties,
|
||||
SearchQuotientNode,
|
||||
SearchQuotientRoot,
|
||||
SearchQuotientSpur,
|
||||
unitTestEndpoints
|
||||
SearchQuotientSpur
|
||||
} from '@keymanapp/lm-worker/test-index';
|
||||
|
||||
import Distribution = LexicalModelTypes.Distribution;
|
||||
import Transform = LexicalModelTypes.Transform;
|
||||
import TrieModel = models.TrieModel;
|
||||
|
||||
const { constituentPaths, quotientPathHasInputs } = unitTestEndpoints;
|
||||
import { constituentPaths, quotientPathHasInputs } from '#test-resources/searchQuotientUtils.js';
|
||||
|
||||
const testModel = new TrieModel(jsonFixture('models/tries/english-1000'));
|
||||
|
||||
|
|
|
|||
109
web/src/test/auto/resources/searchQuotientUtils.ts
Normal file
109
web/src/test/auto/resources/searchQuotientUtils.ts
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
/**
|
||||
* Keyman is copyright (C) SIL Global. MIT License.
|
||||
*
|
||||
* Created by jahorton on 2026-02-05
|
||||
*
|
||||
* This file adds helper functions useful for developing test assertions against
|
||||
* different types of SearchQuotientNodes and their properties.
|
||||
*/
|
||||
|
||||
import { LexicalModelTypes } from "@keymanapp/common-types";
|
||||
|
||||
import { SearchQuotientNode, SearchQuotientRoot, SearchQuotientSpur } from "@keymanapp/lm-worker/test-index";
|
||||
|
||||
import Distribution = LexicalModelTypes.Distribution;
|
||||
import Transform = LexicalModelTypes.Transform;
|
||||
|
||||
/**
|
||||
* Denotes whether or not the represented search-space quotient path includes
|
||||
* paths built from the specified set of keystroke input distributions. The
|
||||
* distribution count should match .inputCount - no omissions or extras are
|
||||
* permitted.
|
||||
*
|
||||
* Designed explicitly for use in unit testing; it's not super-efficient, so
|
||||
* avoid live use.
|
||||
*
|
||||
* @param keystrokeDistributions
|
||||
* @internal
|
||||
*/
|
||||
export function quotientPathHasInputs(node: SearchQuotientNode, keystrokeDistributions: Distribution<Transform>[]): boolean {
|
||||
if(!(node instanceof SearchQuotientSpur)) {
|
||||
for(const p of node.parents) {
|
||||
if(quotientPathHasInputs(p, keystrokeDistributions)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return node.parents.length == 0 && keystrokeDistributions.length == 0;
|
||||
}
|
||||
|
||||
if(node.inputCount == 0) {
|
||||
return keystrokeDistributions.length == 0;
|
||||
} else if(keystrokeDistributions.length != node.inputCount) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const tailInput = [...keystrokeDistributions[keystrokeDistributions.length - 1]];
|
||||
keystrokeDistributions = keystrokeDistributions.slice(0, keystrokeDistributions.length - 1);
|
||||
const localInput = node.lastInput;
|
||||
|
||||
const parentHasInput = () => !!node.parents.find(p => quotientPathHasInputs(p, keystrokeDistributions));
|
||||
|
||||
// Actual reference match? Easy mode.
|
||||
if(localInput == tailInput) {
|
||||
return parentHasInput();
|
||||
} else if(localInput.length != tailInput.length) {
|
||||
return false;
|
||||
} else {
|
||||
for(let entry of tailInput) {
|
||||
const matchIndex = localInput.findIndex((x) => {
|
||||
const s1 = x.sample;
|
||||
const s2 = entry.sample;
|
||||
// Check for equal reference first before the other checks; it makes a nice shortcut.
|
||||
if(x == entry) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if(x.p == entry.p && s1.deleteLeft == s2.deleteLeft
|
||||
&& s1.id == s2.id && ((s1.deleteRight ?? 0) == (s2.deleteRight ?? 0)) && s1.insert == s2.insert
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
if(matchIndex == -1) {
|
||||
return false;
|
||||
} else {
|
||||
tailInput.splice(matchIndex, 1);
|
||||
}
|
||||
}
|
||||
|
||||
return parentHasInput();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumerates the different potential SearchQuotientSpur sequences that lead
|
||||
* to a SearchQuotientNode.
|
||||
*
|
||||
* Intended only for use during unit testing. Does not include the root node.
|
||||
*/
|
||||
export function constituentPaths(node: SearchQuotientNode): SearchQuotientSpur[][] {
|
||||
if(node instanceof SearchQuotientRoot) {
|
||||
return [];
|
||||
} else if(node instanceof SearchQuotientSpur) {
|
||||
const parentPaths = constituentPaths(node.parents[0]);
|
||||
if(parentPaths.length > 0) {
|
||||
return parentPaths.map(p => {
|
||||
p.push(node);
|
||||
return p;
|
||||
});
|
||||
} else {
|
||||
return [[node]];
|
||||
}
|
||||
} else {
|
||||
throw new Error("constituentPaths is unable to handle a new, unexpected SearchQuotientNode type");
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue