import { assert } from 'chai'; import fs from 'fs'; import { createRequire } from 'module'; const require = createRequire(import.meta.url); import { InputProcessor } from '@keymanapp/input-processor'; import { KeyboardInterface, MinimalKeymanGlobal, Mock } from '@keymanapp/keyboard-processor'; import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader'; import { Worker } from '@keymanapp/lexical-model-layer/node'; import * as utils from '@keymanapp/web-utils'; // Required initialization setup. global.keyman = {}; // So that keyboard-based checks against the global `keyman` succeed. // 10.0+ dependent keyboards, like khmer_angkor, will otherwise fail to load. let device = { formFactor: 'phone', OS: 'ios', browser: 'safari' }; // Initialize supplementary plane string extensions String.kmwEnableSupplementaryPlane(false); // Test the KeyboardProcessor interface. describe('InputProcessor', function() { describe('[[constructor]]', function () { it('should initialize without errors', function () { let core = new InputProcessor(device); assert.isNotNull(core); }); it('has expected default values after initialization', function () { // Can construct without the second parameter; if so, the final assertion - .mayPredict // will be invalidated. (No worker, no ability to predict.) let worker = Worker.constructInstance(); try { let core = new InputProcessor(device, worker); assert.isOk(core.keyboardProcessor); assert.isDefined(core.keyboardProcessor.contextDevice); assert.isOk(core.languageProcessor); assert.isOk(core.keyboardInterface); assert.isUndefined(core.activeKeyboard); // No keyboard should be loaded yet. assert.isUndefined(core.activeModel); // Same for the model. // These checks are lifted from the keyboard-processor init checks found in // common/web/keyboard-processor/tests/cases/basic-init.js. assert.equal('us', core.keyboardProcessor.baseLayout, 'KeyboardProcessor has unexpected base layout') assert.isNotNull(global.KeymanWeb, 'KeymanWeb global was not automatically installed'); assert.equal('default', core.keyboardProcessor.layerId, 'Default layer is not set to "default"'); assert.isUndefined(core.keyboardProcessor.activeKeyboard, 'Initialized with already-active keyboard'); // Lifted from languageProcessor.js - the core should not be changing these with its init. assert.isUndefined(core.languageProcessor.activeModel); assert.isFalse(core.languageProcessor.isActive); assert.isTrue(core.languageProcessor.mayPredict); } finally { worker.terminate(); } }); }); describe('efficiency tests', function() { let testDistribution = []; let keyboardWithHarness; // Easy peasy long context: use the input processor's full source! let coreSourceCode = fs.readFileSync('build/lib/index.mjs', 'utf-8'); // At the time this test block was written... 810485 chars. // Let's force it to the same order of magnitude, even if the codebase grows. if(coreSourceCode.length > 1000000) { coreSourceCode = coreSourceCode.substring(0, 1000000); } this.beforeAll(async function() { testDistribution = []; for(let c = 'A'.charCodeAt(0); c <= 'Z'.charCodeAt(0); c++) { let char = String.fromCharCode(c); testDistribution.push({ keyId: "K_" + char, p: 1 / 26 }); } // Load the keyboard. let keyboardLoader = new NodeKeyboardLoader(new KeyboardInterface({}, MinimalKeymanGlobal)); const keyboard = await keyboardLoader.loadKeyboardFromPath(require.resolve('@keymanapp/common-test-resources/keyboards/test_chirality.js')); keyboardWithHarness = keyboardLoader.harness; keyboardWithHarness.activeKeyboard = keyboard; }); describe('without fat-fingering', function() { it('with minimal context (no fat-fingers)', function() { this.timeout(32); // ms let core = new InputProcessor(device); let context = new Mock("", 0); core.keyboardProcessor.keyboardInterface = keyboardWithHarness; let keyboard = keyboardWithHarness.activeKeyboard; let layout = keyboard.layout(utils.DeviceSpec.FormFactor.Phone); let key = layout.getLayer('default').getKey('K_A'); let event = keyboard.constructKeyEvent(key, device, core.keyboardProcessor.stateKeys); let behavior = core.processKeyEvent(event, context); assert.isNotNull(behavior); }); it('with extremely long context (' + coreSourceCode._kmwLength() + ' chars, no fat-fingers)', function() { // Assumes no SMP chars in the source, which is fine. let context = new Mock(coreSourceCode, coreSourceCode._kmwLength()); this.timeout(500); // 500 ms, excluding text import. // These often run on VMs, so we'll be a bit generous. let core = new InputProcessor(device); // I mean, it IS long context, and time // thresholding is disabled within Node. core.keyboardProcessor.keyboardInterface = keyboardWithHarness; let keyboard = keyboardWithHarness.activeKeyboard; let layout = keyboard.layout(utils.DeviceSpec.FormFactor.Phone); let key = layout.getLayer('default').getKey('K_A'); let event = keyboard.constructKeyEvent(key, device, core.keyboardProcessor.stateKeys); let behavior = core.processKeyEvent(event, context); assert.isNotNull(behavior); }); }); describe('with fat-fingering', function() { it('with minimal context (with fat-fingers)', function() { this.timeout(32); // ms let core = new InputProcessor(device); let context = new Mock("", 0); core.keyboardProcessor.keyboardInterface = keyboardWithHarness; let keyboard = keyboardWithHarness.activeKeyboard; let layout = keyboard.layout(utils.DeviceSpec.FormFactor.Phone); let key = layout.getLayer('default').getKey('K_A'); key.keyDistribution = testDistribution; let event = keyboard.constructKeyEvent(key, device, core.keyboardProcessor.stateKeys); let behavior = core.processKeyEvent(event, context); assert.isNotNull(behavior); }); it('with extremely long context (' + coreSourceCode._kmwLength() + ' chars, with fat-fingers)', function() { // Assumes no SMP chars in the source, which is fine. let context = new Mock(coreSourceCode, coreSourceCode._kmwLength()); this.timeout(500); // 500 ms, excluding text import. // These often run on VMs, so we'll be a bit generous. // // Keep at the same 'order of magnitude' as the // 'without fat-fingers' test. let core = new InputProcessor(device); // It IS long context, and time // thresholding is disabled within Node. core.keyboardProcessor.keyboardInterface = keyboardWithHarness; let keyboard = keyboardWithHarness.activeKeyboard; let layout = keyboard.layout(utils.DeviceSpec.FormFactor.Phone); let key = layout.getLayer('default').getKey('K_A'); key.keyDistribution = testDistribution; let event = keyboard.constructKeyEvent(key, device, core.keyboardProcessor.stateKeys); let behavior = core.processKeyEvent(event, context); assert.isNotNull(behavior); }); }); }); });