mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-14 01:47:30 +00:00
change(web): core prediction method now internally async
This commit is contained in:
parent
c25ad48d74
commit
70ffb14c8b
5 changed files with 81 additions and 73 deletions
|
|
@ -325,12 +325,12 @@ export default class LMLayerWorker {
|
|||
switch(payload.message) {
|
||||
case 'predict':
|
||||
var {transform, context} = payload;
|
||||
var suggestions = compositor.predict(transform, context);
|
||||
|
||||
// Now that the suggestions are ready, send them out!
|
||||
this.cast('suggestions', {
|
||||
token: payload.token,
|
||||
suggestions: suggestions
|
||||
compositor.predict(transform, context).then((suggestions) => {
|
||||
// Now that the suggestions are ready, send them out!
|
||||
this.cast('suggestions', {
|
||||
token: payload.token,
|
||||
suggestions: suggestions
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'wordbreak':
|
||||
|
|
@ -355,11 +355,12 @@ export default class LMLayerWorker {
|
|||
break;
|
||||
case 'revert':
|
||||
var {reversion, context} = payload;
|
||||
var suggestions: Suggestion[] = compositor.applyReversion(reversion, context);
|
||||
|
||||
this.cast('postrevert', {
|
||||
token: payload.token,
|
||||
suggestions: suggestions
|
||||
compositor.applyReversion(reversion, context).then((suggestions) => {
|
||||
this.cast('postrevert', {
|
||||
token: payload.token,
|
||||
suggestions: suggestions
|
||||
});
|
||||
});
|
||||
break;
|
||||
case 'reset-context':
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@ export default class ModelCompositor {
|
|||
return returnedPredictions;
|
||||
}
|
||||
|
||||
predict(transformDistribution: Transform | Distribution<Transform>, context: Context): Suggestion[] {
|
||||
async predict(transformDistribution: Transform | Distribution<Transform>, context: Context): Promise<Suggestion[]> {
|
||||
let suggestionDistribution: Distribution<Suggestion> = [];
|
||||
let lexicalModel = this.lexicalModel;
|
||||
let punctuation = this.punctuation;
|
||||
|
|
@ -651,19 +651,21 @@ export default class ModelCompositor {
|
|||
return reversion;
|
||||
}
|
||||
|
||||
applyReversion(reversion: Reversion, context: Context): Suggestion[] {
|
||||
async applyReversion(reversion: Reversion, context: Context): Promise<Suggestion[]> {
|
||||
// If we are unable to track context (because the model does not support LexiconTraversal),
|
||||
// we need a "fallback" strategy.
|
||||
let compositor = this;
|
||||
let fallbackSuggestions = function() {
|
||||
let revertedContext = models.applyTransform(reversion.transform, context);
|
||||
let suggestions = compositor.predict({insert: '', deleteLeft: 0}, revertedContext);
|
||||
suggestions.forEach(function(suggestion) {
|
||||
// A reversion's transform ID is the additive inverse of its original suggestion;
|
||||
// we revert to the state of said original suggestion.
|
||||
suggestion.transformId = -reversion.transformId;
|
||||
return compositor.predict({insert: '', deleteLeft: 0}, revertedContext).then((suggestions) => {
|
||||
suggestions.forEach(function(suggestion) {
|
||||
// A reversion's transform ID is the additive inverse of its original suggestion;
|
||||
// we revert to the state of said original suggestion.
|
||||
suggestion.transformId = -reversion.transformId;
|
||||
});
|
||||
|
||||
return suggestions;
|
||||
});
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
if(!this.contextTracker) {
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import DummyModel from '#./models/dummy-model.js';
|
|||
import ModelCompositor from '#./model-compositor.js';
|
||||
|
||||
describe('Custom Punctuation', function () {
|
||||
it('appears in the keep suggestion', function () {
|
||||
it('appears in the keep suggestion', async function () {
|
||||
let dummySuggestions = [{
|
||||
transform: {
|
||||
insert: 'Hello',
|
||||
|
|
@ -37,7 +37,7 @@ describe('Custom Punctuation', function () {
|
|||
// The model compositor is responsible for adding this to the display as
|
||||
// string.
|
||||
var composite = new ModelCompositor(model, true);
|
||||
var suggestions = composite.predict([{ sample: { insert: 'o', deleteLeft: 0 }, p: 1.00 }], {
|
||||
var suggestions = await composite.predict([{ sample: { insert: 'o', deleteLeft: 0 }, p: 1.00 }], {
|
||||
left: 'Hrll', startOfBuffer: false, endOfBuffer: true
|
||||
});
|
||||
assert.lengthOf(suggestions, 3);
|
||||
|
|
@ -54,7 +54,7 @@ describe('Custom Punctuation', function () {
|
|||
});
|
||||
|
||||
describe("insertAfterWord", function () {
|
||||
it('appears after "word" suggestion', function () {
|
||||
it('appears after "word" suggestion', async function () {
|
||||
let dummySuggestions = [
|
||||
{
|
||||
transform: { insert: 'ᚈᚑᚋ', deleteLeft: 0, },
|
||||
|
|
@ -82,7 +82,7 @@ describe('Custom Punctuation', function () {
|
|||
// The model compositor is responsible for adding this to the display as
|
||||
// string.
|
||||
var composite = new ModelCompositor(model, true);
|
||||
var suggestions = composite.predict([{ sample: { insert: 'ᚋ', deleteLeft: 0 }, p: 1.00 }], {
|
||||
var suggestions = await composite.predict([{ sample: { insert: 'ᚋ', deleteLeft: 0 }, p: 1.00 }], {
|
||||
left: '᚛ᚈᚑ', startOfBuffer: false, endOfBuffer: true
|
||||
});
|
||||
assert.lengthOf(suggestions, dummySuggestions.length);
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ describe('ModelCompositor', function() {
|
|||
{wordBreaker: wordBreakers.default}
|
||||
);
|
||||
|
||||
it('generates suggestions with expected properties', function() {
|
||||
it('generates suggestions with expected properties', async function() {
|
||||
let compositor = new ModelCompositor(plainModel, true);
|
||||
let context = {
|
||||
left: 'th', startOfBuffer: true, endOfBuffer: true,
|
||||
|
|
@ -30,7 +30,7 @@ describe('ModelCompositor', function() {
|
|||
deleteLeft: 0
|
||||
};
|
||||
|
||||
let suggestions = compositor.predict(inputTransform, context);
|
||||
let suggestions = await compositor.predict(inputTransform, context);
|
||||
suggestions.forEach(function(suggestion) {
|
||||
// Suggstions are built based on the context state BEFORE the triggering
|
||||
// input, replacing the prediction's root with the complete word.
|
||||
|
|
@ -55,7 +55,7 @@ describe('ModelCompositor', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('strongly avoids corrections for single-character roots', function() {
|
||||
it('strongly avoids corrections for single-character roots', async function() {
|
||||
let compositor = new ModelCompositor(plainModel, true);
|
||||
let context = {
|
||||
left: '', startOfBuffer: true, endOfBuffer: true,
|
||||
|
|
@ -68,8 +68,8 @@ describe('ModelCompositor', function() {
|
|||
{sample: {insert: 'a', deleteLeft: 0}, p: 0.4} // but at lower weight than 'and' (998).
|
||||
];
|
||||
|
||||
compositor.predict({insert: '', deleteLeft: 0}, context); // Initialize context tracking first!
|
||||
let suggestions = compositor.predict(inputDistribution, context);
|
||||
await compositor.predict({insert: '', deleteLeft: 0}, context); // Initialize context tracking first!
|
||||
let suggestions = await compositor.predict(inputDistribution, context);
|
||||
|
||||
// remove the keep suggestion; we're not testing that here.
|
||||
suggestions = suggestions.filter((suggestion) => suggestion.tag != 'keep');
|
||||
|
|
@ -94,7 +94,7 @@ describe('ModelCompositor', function() {
|
|||
assert.isUndefined(aRange.find((suggestion) => suggestion.transform.insert.charAt(0) == 'q'));
|
||||
});
|
||||
|
||||
it('properly handles suggestions after a backspace', function() {
|
||||
it('properly handles suggestions after a backspace', async function() {
|
||||
let compositor = new ModelCompositor(plainModel, true);
|
||||
let context = {
|
||||
left: 'the ', startOfBuffer: true, endOfBuffer: true,
|
||||
|
|
@ -105,7 +105,7 @@ describe('ModelCompositor', function() {
|
|||
deleteLeft: 1
|
||||
};
|
||||
|
||||
let suggestions = compositor.predict(inputTransform, context);
|
||||
let suggestions = await compositor.predict(inputTransform, context);
|
||||
suggestions.forEach(function(suggestion) {
|
||||
// Suggestions always delete the full root of the suggestion.
|
||||
//
|
||||
|
|
@ -117,7 +117,7 @@ describe('ModelCompositor', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('properly handles suggestions for the first letter after a ` `', function() {
|
||||
it('properly handles suggestions for the first letter after a ` `', async function() {
|
||||
let compositor = new ModelCompositor(plainModel, true);
|
||||
let context = {
|
||||
left: 'the', startOfBuffer: true, endOfBuffer: true,
|
||||
|
|
@ -128,7 +128,7 @@ describe('ModelCompositor', function() {
|
|||
deleteLeft: 0
|
||||
};
|
||||
|
||||
let suggestions = compositor.predict(inputTransform, context);
|
||||
let suggestions = await compositor.predict(inputTransform, context);
|
||||
suggestions.forEach(function(suggestion) {
|
||||
// After a space, predictions are based on a new, zero-length root.
|
||||
// With nothing to replace, .deleteLeft should be zero.
|
||||
|
|
@ -136,7 +136,7 @@ describe('ModelCompositor', function() {
|
|||
});
|
||||
});
|
||||
|
||||
it('properly handles suggestions for the first letter after a `\'`', function() {
|
||||
it('properly handles suggestions for the first letter after a `\'`', async function() {
|
||||
let compositor = new ModelCompositor(plainModel, true);
|
||||
let context = {
|
||||
left: "the '", startOfBuffer: true, endOfBuffer: true,
|
||||
|
|
@ -149,7 +149,7 @@ describe('ModelCompositor', function() {
|
|||
deleteLeft: 0
|
||||
};
|
||||
|
||||
let suggestions = compositor.predict(inputTransform, context);
|
||||
let suggestions = await compositor.predict(inputTransform, context);
|
||||
suggestions.forEach(function(suggestion) {
|
||||
// Suggestions always delete the full root of the suggestion.
|
||||
// Which, here, didn't exist before the input. Nothing to
|
||||
|
|
@ -320,20 +320,20 @@ describe('ModelCompositor', function() {
|
|||
}
|
||||
);
|
||||
|
||||
it('should produce suggestions from uncased input', function() {
|
||||
it('should produce suggestions from uncased input', async function() {
|
||||
let model = uncasedModel;
|
||||
var composite = new ModelCompositor(model, true);
|
||||
var compositor = new ModelCompositor(model, true);
|
||||
|
||||
// Initialize context
|
||||
let context = {
|
||||
left: 'th', startOfBuffer: false, endOfBuffer: true,
|
||||
};
|
||||
composite.predict({insert: '', deleteLeft: 0}, context);
|
||||
await compositor.predict({insert: '', deleteLeft: 0}, context);
|
||||
|
||||
// Pretend to fat finger "the" as "thr"
|
||||
var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 };
|
||||
var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 };
|
||||
var suggestions = composite.predict([thr, the], context);
|
||||
var suggestions = await compositor.predict([thr, the], context);
|
||||
|
||||
// Get the top suggest for 'the' and 'thr*'.
|
||||
var theSuggestion = suggestions.filter(function (s) { return s.displayAs === 'the' || s.displayAs === '“the”'; })[0];
|
||||
|
|
@ -348,20 +348,20 @@ describe('ModelCompositor', function() {
|
|||
assert.isAbove(theSuggestion.p, thrSuggestion.p);
|
||||
});
|
||||
|
||||
it('should not produce suggestions from cased input', function() {
|
||||
it('should not produce suggestions from cased input', async function() {
|
||||
let model = uncasedModel;
|
||||
var composite = new ModelCompositor(model, true);
|
||||
var compositor = new ModelCompositor(model, true);
|
||||
|
||||
// Initialize context
|
||||
let context = {
|
||||
left: 'TH', startOfBuffer: false, endOfBuffer: true,
|
||||
};
|
||||
composite.predict({insert: '', deleteLeft: 0}, context);
|
||||
await compositor.predict({insert: '', deleteLeft: 0}, context);
|
||||
|
||||
// Pretend to fat finger "the" as "thr"
|
||||
var the = { sample: { insert: 'R', deleteLeft: 0}, p: 0.45 };
|
||||
var thr = { sample: { insert: 'E', deleteLeft: 0}, p: 0.55 };
|
||||
var suggestions = composite.predict([thr, the], context);
|
||||
var suggestions = await compositor.predict([thr, the], context);
|
||||
|
||||
// We should only receive a 'keep' suggestion.
|
||||
assert.equal(suggestions.length, 1);
|
||||
|
|
@ -395,20 +395,20 @@ describe('ModelCompositor', function() {
|
|||
}
|
||||
);
|
||||
|
||||
it('should produce suggestions from uncased input', function() {
|
||||
it('should produce suggestions from uncased input', async function() {
|
||||
let model = casedModel;
|
||||
var composite = new ModelCompositor(model, true);
|
||||
let compositor = new ModelCompositor(model, true);
|
||||
|
||||
// Initialize context
|
||||
let context = {
|
||||
left: 'th', startOfBuffer: false, endOfBuffer: true,
|
||||
};
|
||||
composite.predict({insert: '', deleteLeft: 0}, context);
|
||||
await compositor.predict({insert: '', deleteLeft: 0}, context);
|
||||
|
||||
// Pretend to fat finger "the" as "thr"
|
||||
var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 };
|
||||
var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 };
|
||||
var suggestions = composite.predict([thr, the], context);
|
||||
var suggestions = await compositor.predict([thr, the], context);
|
||||
|
||||
// Get the top suggest for 'the' and 'thr*'.
|
||||
var theSuggestion = suggestions.filter(function (s) { return s.displayAs === 'the' || s.displayAs === '“the”'; })[0];
|
||||
|
|
@ -423,20 +423,20 @@ describe('ModelCompositor', function() {
|
|||
assert.isAbove(theSuggestion.p, thrSuggestion.p);
|
||||
});
|
||||
|
||||
it('should produce capitalized suggestions from fully-uppercased input', function() {
|
||||
it('should produce capitalized suggestions from fully-uppercased input', async function() {
|
||||
let model = casedModel;
|
||||
var composite = new ModelCompositor(model, true);
|
||||
let compositor = new ModelCompositor(model, true);
|
||||
|
||||
// Initialize context
|
||||
let context = {
|
||||
left: 'TH', startOfBuffer: false, endOfBuffer: true,
|
||||
};
|
||||
composite.predict({insert: '', deleteLeft: 0}, context);
|
||||
await compositor.predict({insert: '', deleteLeft: 0}, context);
|
||||
|
||||
// Pretend to fat finger "the" as "thr"
|
||||
var the = { sample: { insert: 'R', deleteLeft: 0}, p: 0.45 };
|
||||
var thr = { sample: { insert: 'E', deleteLeft: 0}, p: 0.55 };
|
||||
var suggestions = composite.predict([thr, the], context);
|
||||
var suggestions = await compositor.predict([thr, the], context);
|
||||
|
||||
// Get the top suggest for 'the' and 'thr*'.
|
||||
var theSuggestion = suggestions.filter(function (s) { return s.displayAs === 'THE' || s.displayAs === '“THE”'; })[0];
|
||||
|
|
@ -451,20 +451,20 @@ describe('ModelCompositor', function() {
|
|||
assert.isAbove(theSuggestion.p, thrSuggestion.p);
|
||||
});
|
||||
|
||||
it('should produce "initial-case" suggestions from input with an initial capital', function() {
|
||||
it('should produce "initial-case" suggestions from input with an initial capital', async function() {
|
||||
let model = casedModel;
|
||||
var composite = new ModelCompositor(model, true);
|
||||
let compositor = new ModelCompositor(model, true);
|
||||
|
||||
// Initialize context
|
||||
let context = {
|
||||
left: 'Th', startOfBuffer: false, endOfBuffer: true,
|
||||
};
|
||||
composite.predict({insert: '', deleteLeft: 0}, context);
|
||||
await compositor.predict({insert: '', deleteLeft: 0}, context);
|
||||
|
||||
// Pretend to fat finger "the" as "thr"
|
||||
var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 };
|
||||
var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 };
|
||||
var suggestions = composite.predict([thr, the], context);
|
||||
var suggestions = await compositor.predict([thr, the], context);
|
||||
|
||||
// Get the top suggest for 'the' and 'thr*'.
|
||||
var theSuggestion = suggestions.filter(function (s) { return s.displayAs === 'The' || s.displayAs === '“The”'; })[0];
|
||||
|
|
@ -479,20 +479,20 @@ describe('ModelCompositor', function() {
|
|||
assert.isAbove(theSuggestion.p, thrSuggestion.p);
|
||||
});
|
||||
|
||||
it('also from input with partial capitalization when including an initial capital', function() {
|
||||
it('also from input with partial capitalization when including an initial capital', async function() {
|
||||
let model = casedModel;
|
||||
var composite = new ModelCompositor(model, true);
|
||||
let compositor = new ModelCompositor(model, true);
|
||||
|
||||
// Initialize context
|
||||
let context = {
|
||||
left: 'TH', startOfBuffer: false, endOfBuffer: true,
|
||||
};
|
||||
composite.predict({insert: '', deleteLeft: 0}, context);
|
||||
await compositor.predict({insert: '', deleteLeft: 0}, context);
|
||||
|
||||
// Pretend to fat finger "the" as "thr"
|
||||
var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 };
|
||||
var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 };
|
||||
var suggestions = composite.predict([thr, the], context);
|
||||
var suggestions = await compositor.predict([thr, the], context);
|
||||
|
||||
// Get the top suggest for 'the' and 'thr*'.
|
||||
var theSuggestion = suggestions.filter(function (s) { return s.displayAs.startsWith('The'); })[0];
|
||||
|
|
@ -508,22 +508,22 @@ describe('ModelCompositor', function() {
|
|||
});
|
||||
|
||||
describe('Prediction with legacy Models (12.0 / 13.0)', function() {
|
||||
it('should compose suggestions from a fat-fingered keypress (no keying needed)', function () {
|
||||
it('should compose suggestions from a fat-fingered keypress (no keying needed)', async function () {
|
||||
var model = new TrieModel(
|
||||
jsonFixture('models/tries/english-1000')
|
||||
);
|
||||
var composite = new ModelCompositor(model, true);
|
||||
let compositor = new ModelCompositor(model, true);
|
||||
|
||||
// Initialize context
|
||||
let context = {
|
||||
left: 'th', startOfBuffer: false, endOfBuffer: true,
|
||||
};
|
||||
composite.predict({insert: '', deleteLeft: 0}, context);
|
||||
await compositor.predict({insert: '', deleteLeft: 0}, context);
|
||||
|
||||
// Pretend to fat finger "the" as "thr"
|
||||
var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 };
|
||||
var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 };
|
||||
var suggestions = composite.predict([thr, the], context);
|
||||
var suggestions = await compositor.predict([thr, the], context);
|
||||
|
||||
// Get the top suggest for 'the' and 'thr*'.
|
||||
var theSuggestion = suggestions.filter(function (s) { return s.displayAs === 'the' || s.displayAs === '“the”'; })[0];
|
||||
|
|
@ -538,22 +538,22 @@ describe('ModelCompositor', function() {
|
|||
assert.isAbove(theSuggestion.p, thrSuggestion.p);
|
||||
});
|
||||
|
||||
it('should compose suggestions from a fat-fingered keypress (keying needed)', function () {
|
||||
it('should compose suggestions from a fat-fingered keypress (keying needed)', async function () {
|
||||
var model = new TrieModel(
|
||||
jsonFixture('models/tries/english-1000')
|
||||
);
|
||||
var composite = new ModelCompositor(model, true);
|
||||
let compositor = new ModelCompositor(model, true);
|
||||
|
||||
// Initialize context
|
||||
let context = {
|
||||
left: 'Th', startOfBuffer: false, endOfBuffer: true,
|
||||
};
|
||||
composite.predict({insert: '', deleteLeft: 0}, context);
|
||||
await compositor.predict({insert: '', deleteLeft: 0}, context);
|
||||
|
||||
// Pretend to fat finger "the" as "thr"
|
||||
var the = { sample: { insert: 'r', deleteLeft: 0}, p: 0.45 };
|
||||
var thr = { sample: { insert: 'e', deleteLeft: 0}, p: 0.55 };
|
||||
var suggestions = composite.predict([thr, the], context);
|
||||
var suggestions = await compositor.predict([thr, the], context);
|
||||
|
||||
// Get the top suggest for 'the' and 'thr*'.
|
||||
// As of 15.0+, because of #5429, the keep suggestion `"The"` will not be merged
|
||||
|
|
@ -841,7 +841,7 @@ describe('ModelCompositor', function() {
|
|||
|
||||
// While this isn't a state the LMLayer should ever operate within, this provides
|
||||
// a useful base state for developing further tests against the method.
|
||||
it('model without traversals: returns appropriate suggestions upon reversion', function() {
|
||||
it('model without traversals: returns appropriate suggestions upon reversion', async function() {
|
||||
// This setup matches 'acceptSuggestion' the test case
|
||||
// it('first word of context + postTransform provided, .deleteLeft > 0')
|
||||
// seen earlier in the file.
|
||||
|
|
@ -878,7 +878,7 @@ describe('ModelCompositor', function() {
|
|||
let appliedContext = models.applyTransform(baseSuggestion.transform, baseContext);
|
||||
assert.equal(appliedContext.left, "hello ");
|
||||
|
||||
let suggestions = compositor.applyReversion(reversion, appliedContext);
|
||||
let suggestions = await compositor.applyReversion(reversion, appliedContext);
|
||||
|
||||
// As this test is a bit... 'hard-wired', we only get the 'keep' suggestion.
|
||||
// It should still be accurate, though.
|
||||
|
|
@ -891,7 +891,7 @@ describe('ModelCompositor', function() {
|
|||
assert.deepEqual(suggestions[0].transform, expectedTransform);
|
||||
});
|
||||
|
||||
it('model with traversals: returns appropriate suggestions upon reversion', function() {
|
||||
it('model with traversals: returns appropriate suggestions upon reversion', async function() {
|
||||
// This setup matches 'acceptSuggestion' the test case
|
||||
// it('first word of context + postTransform provided, .deleteLeft > 0')
|
||||
// seen earlier in the file.
|
||||
|
|
@ -911,7 +911,7 @@ describe('ModelCompositor', function() {
|
|||
let model = new models.TrieModel(jsonFixture('models/tries/english-1000'), {punctuation: englishPunctuation});
|
||||
let compositor = new ModelCompositor(model, true);
|
||||
|
||||
let initialSuggestions = compositor.predict(postTransform, baseContext);
|
||||
let initialSuggestions = await compositor.predict(postTransform, baseContext);
|
||||
let keepSuggestion = initialSuggestions[0];
|
||||
assert.equal(keepSuggestion.tag, 'keep'); // corresponds to `postTransform`, but the transform isn't equal.
|
||||
|
||||
|
|
@ -921,13 +921,13 @@ describe('ModelCompositor', function() {
|
|||
assert.equal(reversion.id, -baseSuggestion.id);
|
||||
|
||||
let appliedContext = models.applyTransform(baseSuggestion.transform, baseContext);
|
||||
let reversionSuggestions = compositor.applyReversion(reversion, appliedContext);
|
||||
let reversionSuggestions = await compositor.applyReversion(reversion, appliedContext);
|
||||
|
||||
// The returned suggestion list should match the original suggestion list.
|
||||
assert.deepEqual(reversionSuggestions, initialSuggestions);
|
||||
});
|
||||
|
||||
it('model with traversals: properly tracks context state', function() {
|
||||
it('model with traversals: properly tracks context state', async function() {
|
||||
// Could be merged with the previous test case, but I think it's good to have the error
|
||||
// sets flagged separately.
|
||||
|
||||
|
|
@ -946,7 +946,7 @@ describe('ModelCompositor', function() {
|
|||
let model = new models.TrieModel(jsonFixture('models/tries/english-1000'), {punctuation: englishPunctuation});
|
||||
let compositor = new ModelCompositor(model, true);
|
||||
|
||||
let initialSuggestions = compositor.predict(postTransform, baseContext);
|
||||
let initialSuggestions = await compositor.predict(postTransform, baseContext);
|
||||
const suggestionContextState = compositor.contextTracker.newest;
|
||||
|
||||
let keepSuggestion = initialSuggestions[0];
|
||||
|
|
|
|||
|
|
@ -8,10 +8,11 @@ import LMLayerWorker from '#./index.js';
|
|||
|
||||
import { configWorker, createMessageEventWithData, emptyContext, iGotDistractedByHazel,
|
||||
importScriptsWith, randomToken, zeroTransform } from '@keymanapp/common-test-resources/model-helpers.mjs';
|
||||
import { timedPromise } from '@keymanapp/web-utils';
|
||||
|
||||
describe('LMLayerWorker', function () {
|
||||
describe('#predict()', function () {
|
||||
it('should send back suggestions', function () {
|
||||
it('should send back suggestions', async function () {
|
||||
var suggestion = {
|
||||
transform: {
|
||||
insert: 'I ',
|
||||
|
|
@ -63,6 +64,10 @@ describe('LMLayerWorker', function () {
|
|||
context: emptyContext()
|
||||
}));
|
||||
|
||||
// predict() is async, so we need to relinquish control flow temporarily
|
||||
// in order for a return message to become available.
|
||||
await timedPromise(500);
|
||||
|
||||
// Retrieve the internal 'dummy' suggestions for comparison.
|
||||
var hazel = iGotDistractedByHazel();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue