From 64791e1277ba3616e83d1a07741c856dcda6aae6 Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Thu, 22 Jan 2026 08:40:41 -0600 Subject: [PATCH 1/2] fix(web): adjust formatting nit re: missing else --- .../src/main/correction/search-quotient-spur.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts index b877aaf286..5d00809df6 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts @@ -115,11 +115,14 @@ export class SearchQuotientSpur implements SearchQuotientNode { // 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 + } + + 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; }); From 42fcbc390dd038587af83f1291608192bf89c95f Mon Sep 17 00:00:00 2001 From: Joshua Horton Date: Thu, 22 Jan 2026 09:21:05 -0600 Subject: [PATCH 2/2] refactor(web): move unit-testing .hasInputs to standalone, tree-shakable method --- .../main/correction/search-quotient-node.ts | 83 +++++-- .../main/correction/search-quotient-spur.ts | 48 ---- .../worker-thread/src/main/test-index.ts | 1 + .../context/context-token.tests.ts | 205 ++++++++++-------- .../search-quotient-node.tests.ts | 52 +++++ .../search-quotient-spur.tests.ts | 45 +--- 6 files changed, 236 insertions(+), 198 deletions(-) create mode 100644 web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-node.tests.ts diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-node.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-node.ts index 153edbe02c..22e0b0b22a 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-node.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-node.ts @@ -13,6 +13,7 @@ import { SearchNode, SearchResult } from "./distance-modeler.js"; import Distribution = LexicalModelTypes.Distribution; import Transform = LexicalModelTypes.Transform; +import { SearchQuotientSpur } from "./search-quotient-spur.js"; let SPACE_ID_SEED = 0; @@ -63,19 +64,6 @@ export interface SearchQuotientNode { */ handleNextNode(): PathResult; - /** - * Denotes whether or not the represented search space 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 - */ - hasInputs(keystrokeDistributions: Distribution[]): boolean; - /** * Increases the editing range that will be considered for determining * correction distances. @@ -133,4 +121,73 @@ export interface SearchQuotientNode { * the correction-search graph and its paths. */ readonly bestExample: { text: string, p: number }; +} + +/** + * 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[]): boolean { + if(!(node instanceof SearchQuotientSpur)) { + for(const p of node.parents) { + if(quotientPathHasInputs(p, keystrokeDistributions)) { + return true; + } + } + return false; + } + + 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(); + } } \ No newline at end of file diff --git a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts index 5d00809df6..4eeed530e7 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/correction/search-quotient-spur.ts @@ -89,54 +89,6 @@ export class SearchQuotientSpur implements SearchQuotientNode { } } - public hasInputs(keystrokeDistributions: Distribution[]): boolean { - if(this.inputCount == 0) { - return keystrokeDistributions.length == 0; - } else if(keystrokeDistributions.length != this.inputCount) { - return false; - } - - const tailInput = [...keystrokeDistributions[keystrokeDistributions.length - 1]]; - keystrokeDistributions = keystrokeDistributions.slice(0, keystrokeDistributions.length - 1); - const localInput = this.lastInput; - - const parentHasInput = () => !!this.parents.find(p => p.hasInputs(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(); - } - } - public get lastInput(): Distribution> { // Shallow-copies the array to prevent external modification; the Transforms // are marked Readonly to prevent their modification as well. diff --git a/web/src/engine/predictive-text/worker-thread/src/main/test-index.ts b/web/src/engine/predictive-text/worker-thread/src/main/test-index.ts index 2c5c0214ad..231cc84815 100644 --- a/web/src/engine/predictive-text/worker-thread/src/main/test-index.ts +++ b/web/src/engine/predictive-text/worker-thread/src/main/test-index.ts @@ -5,6 +5,7 @@ export * from './correction/context-tokenization.js'; export { ContextTracker } from './correction/context-tracker.js'; export { ContextTransition } from './correction/context-transition.js'; export * from './correction/distance-modeler.js'; +export * from './correction/search-quotient-node.js'; export * from './correction/search-quotient-spur.js'; export { ExtendedEditOperation, SegmentableDistanceCalculation } from './correction/segmentable-calculation.js'; export * from './correction/tokenization-subsets.js'; diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/context-token.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/context-token.tests.ts index 06dc12a0d7..013a1a85ce 100644 --- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/context-token.tests.ts +++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/context/context-token.tests.ts @@ -14,7 +14,7 @@ import { default as defaultBreaker } from '@keymanapp/models-wordbreakers'; import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs'; import { LexicalModelTypes } from '@keymanapp/common-types'; -import { ContextToken, correction, getBestMatches, models, preprocessInputSources, SearchQuotientSpur } from '@keymanapp/lm-worker/test-index'; +import { ContextToken, correction, getBestMatches, models, preprocessInputSources, quotientPathHasInputs, SearchQuotientSpur } from '@keymanapp/lm-worker/test-index'; import Distribution = LexicalModelTypes.Distribution; import ExecutionTimer = correction.ExecutionTimer; @@ -71,7 +71,8 @@ describe('ContextToken', function() { assert.equal(token.exampleInput, 'and'); assert.equal(token.searchModule.inputCount, 3); - assert.isTrue(token.searchModule.hasInputs([ + assert.isTrue(quotientPathHasInputs( + token.searchModule, [ [{sample: { insert: 'a', deleteLeft: 0 }, p: 1}], [{sample: { insert: 'n', deleteLeft: 0 }, p: 1}], [{sample: { insert: 'd', deleteLeft: 0 }, p: 1}] @@ -108,7 +109,8 @@ describe('ContextToken', function() { token2.inputRange.forEach((entry) => assert.isTrue(merged.inputRange.indexOf(entry) > -1)); token3.inputRange.forEach((entry) => assert.isTrue(merged.inputRange.indexOf(entry) > -1)); - assert.isTrue(merged.searchModule.hasInputs([ + assert.isTrue(quotientPathHasInputs( + merged.searchModule, [ [{sample: { insert: 'c', deleteLeft: 0 }, p: 1}], [{sample: { insert: 'a', deleteLeft: 0 }, p: 1}], [{sample: { insert: 'n', deleteLeft: 0 }, p: 1}], @@ -205,7 +207,8 @@ describe('ContextToken', function() { const merged = ContextToken.merge(tokensToMerge, plainModel); assert.equal(merged.exampleInput, "applesandsourgrapes"); assert.deepEqual(merged.inputRange, srcTransforms.map((t) => ({ trueTransform: t, inputStartIndex: 0, bestProbFromSet: 1 }) )); - assert.isTrue(merged.searchModule.hasInputs( + assert.isTrue(quotientPathHasInputs( + merged.searchModule, srcTransforms.map((t) => ([{sample: t, p: 1}])) )); }); @@ -266,7 +269,8 @@ describe('ContextToken', function() { const merged = ContextToken.merge(tokensToMerge, plainModel); assert.equal(merged.exampleInput, toMathematicalSMP("applesandsourgrapes")); assert.deepEqual(merged.inputRange, srcTransforms.map((t) => ({ trueTransform: t, inputStartIndex: 0, bestProbFromSet: 1 }) )); - assert.isTrue(merged.searchModule.hasInputs( + assert.isTrue(quotientPathHasInputs( + merged.searchModule, srcTransforms.map((t) => ([{sample: t, p: 1}])) )); }); @@ -300,7 +304,7 @@ describe('ContextToken', function() { }; assert.equal(tokenToSplit.sourceText, 'can\''); - tokenToSplit.searchModule.hasInputs(keystrokeDistributions); + assert.isTrue(quotientPathHasInputs(tokenToSplit.searchModule, keystrokeDistributions)); // And now for the "fun" part. const resultsOfSplit = tokenToSplit.split({ @@ -317,8 +321,8 @@ describe('ContextToken', function() { assert.equal(resultsOfSplit.length, 2); assert.sameOrderedMembers(resultsOfSplit.map(t => t.exampleInput), ['can', '\'']); - assert.isTrue(resultsOfSplit[0].searchModule.hasInputs(keystrokeDistributions.slice(0, 3))); - assert.isTrue(resultsOfSplit[1].searchModule.hasInputs([keystrokeDistributions[3]])); + assert.isTrue(quotientPathHasInputs(resultsOfSplit[0].searchModule, keystrokeDistributions.slice(0, 3))); + assert.isTrue(quotientPathHasInputs(resultsOfSplit[1].searchModule, [keystrokeDistributions[3]])); }); it("handles mid-transform splits correctly", () => { @@ -336,7 +340,7 @@ describe('ContextToken', function() { }; assert.equal(tokenToSplit.sourceText, 'biglargetransform'); - assert.isTrue(tokenToSplit.searchModule.hasInputs(keystrokeDistributions)); + assert.isTrue(quotientPathHasInputs(tokenToSplit.searchModule, keystrokeDistributions)); // And now for the "fun" part. const resultsOfSplit = tokenToSplit.split({ @@ -365,7 +369,8 @@ describe('ContextToken', function() { }))); for(let i = 0; i < resultsOfSplit.length; i++) { - assert.isTrue(resultsOfSplit[i].searchModule.hasInputs([ + assert.isTrue(quotientPathHasInputs( + resultsOfSplit[i].searchModule, [ [{sample: { insert: splitTextArray[i], deleteLeft: 0, deleteRight: 0 }, p: 1}] ])); } @@ -390,7 +395,7 @@ describe('ContextToken', function() { }; assert.equal(tokenToSplit.exampleInput, 'largelongtransforms'); - tokenToSplit.searchModule.hasInputs(keystrokeDistributions); + assert.isTrue(quotientPathHasInputs(tokenToSplit.searchModule, keystrokeDistributions)); // And now for the "fun" part. const resultsOfSplit = tokenToSplit.split({ @@ -420,49 +425,55 @@ describe('ContextToken', function() { { trueTransform: keystrokeDistributions[2][0].sample, inputStartIndex: 'ng'.length, bestProbFromSet: 1 } ]); - assert.isTrue(resultsOfSplit[0].searchModule.hasInputs([ - keystrokeDistributions[0], - keystrokeDistributions[1].map((entry) => { - return { - sample: { - ...entry.sample, - insert: entry.sample.insert.slice(0, 4) // gets the 'arge' portion & the deleteLefts. - }, p: entry.p - } - }), - ])); + assert.isTrue(quotientPathHasInputs( + resultsOfSplit[0].searchModule,[ + keystrokeDistributions[0], + keystrokeDistributions[1].map((entry) => { + return { + sample: { + ...entry.sample, + insert: entry.sample.insert.slice(0, 4) // gets the 'arge' portion & the deleteLefts. + }, p: entry.p + } + }) + ] + )); - assert.isTrue(resultsOfSplit[1].searchModule.hasInputs([ - keystrokeDistributions[1].map((entry) => { - return { - sample: { - ...entry.sample, - insert: entry.sample.insert.slice('arge'.length), - deleteLeft: 0 - }, p: entry.p - } - }), - keystrokeDistributions[2].map((entry) => { - return { - sample: { - ...entry.sample, - insert: entry.sample.insert.slice(0, 'ng'.length), // gets the 'ng' portion. - }, p: entry.p - } - }), - ])); + assert.isTrue(quotientPathHasInputs( + resultsOfSplit[1].searchModule, [ + keystrokeDistributions[1].map((entry) => { + return { + sample: { + ...entry.sample, + insert: entry.sample.insert.slice('arge'.length), + deleteLeft: 0 + }, p: entry.p + } + }), + keystrokeDistributions[2].map((entry) => { + return { + sample: { + ...entry.sample, + insert: entry.sample.insert.slice(0, 'ng'.length), // gets the 'ng' portion. + }, p: entry.p + } + }) + ] + )); - assert.isTrue(resultsOfSplit[2].searchModule.hasInputs([ - keystrokeDistributions[2].map((entry) => { - return { - sample: { - ...entry.sample, - insert: entry.sample.insert.slice('ng'.length), // drops the 'ng' portion. - deleteLeft: 0 - }, p: entry.p - } - }), - ])); + assert.isTrue(quotientPathHasInputs( + resultsOfSplit[2].searchModule, [ + keystrokeDistributions[2].map((entry) => { + return { + sample: { + ...entry.sample, + insert: entry.sample.insert.slice('ng'.length), // drops the 'ng' portion. + deleteLeft: 0 + }, p: entry.p + } + }), + ] + )); }); it("handles messy mid-transform splits correctly - non-BMP text", () => { @@ -484,7 +495,7 @@ describe('ContextToken', function() { }; assert.equal(tokenToSplit.exampleInput, toMathematicalSMP('largelongtransforms')); - tokenToSplit.searchModule.hasInputs(keystrokeDistributions); + assert.isTrue(quotientPathHasInputs(tokenToSplit.searchModule, keystrokeDistributions)); // And now for the "fun" part. const resultsOfSplit = tokenToSplit.split({ @@ -514,49 +525,55 @@ describe('ContextToken', function() { { trueTransform: keystrokeDistributions[2][0].sample, inputStartIndex: 'ng'.length, bestProbFromSet: 1 } ]); - assert.isTrue(resultsOfSplit[0].searchModule.hasInputs([ - keystrokeDistributions[0], - keystrokeDistributions[1].map((entry) => { - return { - sample: { - ...entry.sample, - insert: KMWString.substring(entry.sample.insert, 0, 4) // gets the 'arge' portion & the deleteLefts. - }, p: entry.p - } - }), - ])); + assert.isTrue(quotientPathHasInputs( + resultsOfSplit[0].searchModule, [ + keystrokeDistributions[0], + keystrokeDistributions[1].map((entry) => { + return { + sample: { + ...entry.sample, + insert: KMWString.substring(entry.sample.insert, 0, 4) // gets the 'arge' portion & the deleteLefts. + }, p: entry.p + } + }) + ] + )); - assert.isTrue(resultsOfSplit[1].searchModule.hasInputs([ - keystrokeDistributions[1].map((entry) => { - return { - sample: { - ...entry.sample, - insert: KMWString.substring(entry.sample.insert, 'arge'.length), - deleteLeft: 0 - }, p: entry.p - } - }), - keystrokeDistributions[2].map((entry) => { - return { - sample: { - ...entry.sample, - insert: KMWString.substring(entry.sample.insert, 0, 'ng'.length), // gets the 'ng' portion. - }, p: entry.p - } - }), - ])); + assert.isTrue(quotientPathHasInputs( + resultsOfSplit[1].searchModule, [ + keystrokeDistributions[1].map((entry) => { + return { + sample: { + ...entry.sample, + insert: KMWString.substring(entry.sample.insert, 'arge'.length), + deleteLeft: 0 + }, p: entry.p + } + }), + keystrokeDistributions[2].map((entry) => { + return { + sample: { + ...entry.sample, + insert: KMWString.substring(entry.sample.insert, 0, 'ng'.length), // gets the 'ng' portion. + }, p: entry.p + } + }) + ] + )); - assert.isTrue(resultsOfSplit[2].searchModule.hasInputs([ - keystrokeDistributions[2].map((entry) => { - return { - sample: { - ...entry.sample, - insert: KMWString.substring(entry.sample.insert, 'ng'.length), // drops the 'ng' portion. - deleteLeft: 0 - }, p: entry.p - } - }), - ])); + assert.isTrue(quotientPathHasInputs( + resultsOfSplit[2].searchModule, [ + keystrokeDistributions[2].map((entry) => { + return { + sample: { + ...entry.sample, + insert: KMWString.substring(entry.sample.insert, 'ng'.length), // drops the 'ng' portion. + deleteLeft: 0 + }, p: entry.p + } + }) + ] + )); }); }); }); diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-node.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-node.tests.ts new file mode 100644 index 0000000000..fb7e3471bd --- /dev/null +++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-node.tests.ts @@ -0,0 +1,52 @@ +import { assert } from 'chai'; + +import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs'; +import { models, quotientPathHasInputs, SearchQuotientSpur } from '@keymanapp/lm-worker/test-index'; + +import { buildSimplePathSplitFixture } from './search-quotient-spur.tests.js'; + +import TrieModel = models.TrieModel; + +const testModel = new TrieModel(jsonFixture('models/tries/english-1000')); + +describe('quotientNodeHasParents()', () => { + it('matches an empty array on root SearchPaths', () => { + assert.isTrue(quotientPathHasInputs(new SearchQuotientSpur(testModel), [])); + }); + + it('matches all path inputs when provided in proper order', () => { + const { paths, distributions } = buildSimplePathSplitFixture(); + assert.isTrue(quotientPathHasInputs(paths[4], distributions)); + }); + + it('does not match when any path input component is missing', () => { + const { paths, distributions } = buildSimplePathSplitFixture(); + assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice(1))); + assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice(2))); + assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice(3))); + assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice(0, 3))); + assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice(0, 1).concat(distributions.slice(2)))); + }); + + it('does not match when path inputs are not in proper order', () => { + const { paths, distributions } = buildSimplePathSplitFixture(); + assert.isFalse(quotientPathHasInputs(paths[4], distributions.slice().reverse())); + + // Random shuffle. + let shuffled: typeof distributions; + let isShuffled: boolean; + do { + shuffled = distributions.slice().sort(() => Math.random() * 2 - 1); + // Validate that we actually shuffled - that we didn't land on the original order! + isShuffled = false; + for(let i = 0; i < distributions.length; i++) { + if(distributions[i] != shuffled[i]) { + isShuffled = true; + break; + } + } + } while(!isShuffled); + assert.isFalse(quotientPathHasInputs(paths[4], shuffled)); + }); +}); + diff --git a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-spur.tests.ts b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-spur.tests.ts index 55babb6765..fd2ac8a15c 100644 --- a/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-spur.tests.ts +++ b/web/src/test/auto/headless/engine/predictive-text/worker-thread/correction-search/search-quotient-spur.tests.ts @@ -10,7 +10,7 @@ import { assert } from 'chai'; import { jsonFixture } from '@keymanapp/common-test-resources/model-helpers.mjs'; -import { models, SearchQuotientSpur } from '@keymanapp/lm-worker/test-index'; +import { models, quotientPathHasInputs, SearchQuotientSpur } from '@keymanapp/lm-worker/test-index'; import TrieModel = models.TrieModel; @@ -195,48 +195,7 @@ describe('SearchQuotientSpur', () => { (constructing, current) => ({text: constructing.text + current[0].sample.insert, p: constructing.p * current[0].p}), {text: '', p: 1}) ); - assert.isTrue(pathToSplit.hasInputs(distributions)); - }); - }); - - describe('hasInputs()', () => { - it('matches an empty array on root SearchPaths', () => { - assert.isTrue(new SearchQuotientSpur(testModel).hasInputs([])); - }); - - it('matches all path inputs when provided in proper order', () => { - const { paths, distributions } = buildSimplePathSplitFixture(); - assert.isTrue(paths[4].hasInputs(distributions)); - }); - - it('does not match when any path input component is missing', () => { - const { paths, distributions } = buildSimplePathSplitFixture(); - assert.isFalse(paths[4].hasInputs(distributions.slice(1))); - assert.isFalse(paths[4].hasInputs(distributions.slice(2))); - assert.isFalse(paths[4].hasInputs(distributions.slice(3))); - assert.isFalse(paths[4].hasInputs(distributions.slice(0, 3))); - assert.isFalse(paths[4].hasInputs(distributions.slice(0, 1).concat(distributions.slice(2)))); - }); - - it('does not match when path inputs are not in proper order', () => { - const { paths, distributions } = buildSimplePathSplitFixture(); - assert.isFalse(paths[4].hasInputs(distributions.slice().reverse())); - - // Random shuffle. - let shuffled: typeof distributions; - let isShuffled: boolean; - do { - shuffled = distributions.slice().sort(() => Math.random() * 2 - 1); - // Validate that we actually shuffled - that we didn't land on the original order! - isShuffled = false; - for(let i = 0; i < distributions.length; i++) { - if(distributions[i] != shuffled[i]) { - isShuffled = true; - break; - } - } - } while(!isShuffled); - assert.isFalse(paths[4].hasInputs(shuffled)); + assert.isTrue(quotientPathHasInputs(pathToSplit, distributions)); }); }); }); \ No newline at end of file