spiegel-keyman/common/test/resources/model-helpers.mjs
2025-08-12 15:38:53 -05:00

165 lines
4 KiB
JavaScript

import path from 'path';
import { assert } from 'chai';
import fs from 'fs';
import vm from 'vm';
/**
* @file model-helpers.mjs
*
* Defines a common set of helper functions for use with predictive-text-related
* unit tests.
*/
// Ensure that we can successfully load the module & apply kmwLength, as it's
// needed for some of the unit tests.
import { createRequire } from "module";
import { fileURLToPath } from 'url';
/**
* Creates a MessageEvent (for inter-worker communication), with the given data payload.
*
* @template {T}
* @param {T} data
* @returns {T}
*/
export function createMessageEventWithData(data) {
return { data };
}
/**
* Creates a simple, default capabilities object for standard-case LMLayer init.
*/
export function capabilities() {
return {
maxLeftContextCodePoints: 64
}
}
/**
* Mimics a message from the outer LMLayer shell with a simple, default config object.
* Used for Worker tests.
*/
export function configWorker(worker) {
worker.onMessage(createMessageEventWithData({
message: 'config',
capabilities: capabilities()
}));
}
/**
* A valid model that suggests exactly what you want it to suggest.
*
* @returns {ModelDescription}
*/
export function dummyModel(futureSuggestions) {
return {
type: 'dummy',
futureSuggestions: futureSuggestions || []
};
}
/**
* Capabilities of a keyboard that will ONLY send left-sided capabilities.
* The keyboard does not support deleting to the right.
*
* @returns {Capabilities}
*/
export function defaultCapabilities() {
return {
maxLeftContextCodeUnits: 64
};
}
/**
* Returns the Context of an empty buffer; no text, at both the start and
* end of the buffer.
*
* @returns {Context}
*/
export function emptyContext() {
return {
left: '',
startOfBuffer: true,
endOfBuffer: true
};
}
/**
* Returns a Transform that, when applied, makes no changes to the buffer.
*
* @returns {Transform}
*/
export function zeroTransform() {
return {
insert: '',
deleteLeft: 0,
};
}
/**
* Returns a random token. NOT guaranteed to be unique.
*
* @returns {Token}
*/
export function randomToken() {
var range = Number.MAX_SAFE_INTEGER - Number.MIN_SAFE_INTEGER;
return Math.random() * range + Number.MIN_SAFE_INTEGER;
}
export function iGotDistractedByHazel() {
return jsonFixture('models/future_suggestions/i_got_distracted_by_hazel').map((set) => set.map((entry) => {
return {
...entry,
// Dummy-model predictions all claim probability 1; there's no actual probability stuff
// used here.
'lexical-p': 1,
// We're predicting from a single transform, not a distribution, so probability 1.
'correction-p': 1,
// Multiply 'em together.
p: 1,
}
}));
}
export function jsonFixture(name, root, import_root) {
// Assuming this file structure:
// root
// └── json
// ├── future_suggestions
// │ └── ...
// └── wordlists
// └── ...
// Default root: this folder.
if(!root) {
root = 'json';
}
if(!import_root) {
import_root = import.meta.url;
}
// The most straight-forward way... is to use CommonJS-style require to load JSON.
// Fortunately, Node provides the tools needed to recreate it.
const require = createRequire(import_root);
// ES-module mode also leaves out `__dirname`, so we rebuild that too.
const __filename = fileURLToPath(import_root);
const __dirname = path.dirname(__filename);
return require(path.join(__dirname, root, `${name}.json`));
}
export function importScriptsWith(context) {
return function() { // the constructed context's importScripts method.
/* Use of vm.createContext and script.runInContext allow us to avoid
* polluting the global scope with imports. When we throw away the
* context object, imported scripts will be automatically GC'd.
*/
for(var i=0; i < arguments.length; i++) {
context = vm.createContext(context);
var script = new vm.Script(fs.readFileSync(arguments[i]));
script.runInContext(context);
}
}
}