Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions web/common.inc.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
# compile engine/main
# ```
function compile() {
if [ $# -lt 1 ]; then
if [[ $# -lt 1 ]]; then
builder_die "Scripting error: insufficient argument count!"
fi

Expand All @@ -27,10 +27,10 @@ function compile() {
local SRC_DIR=${2:-"${KEYMAN_ROOT}/web/src"}
local BUILD_DIR=${3:-"${KEYMAN_ROOT}/web/build"}

tsc -b "${SRC_DIR}/$COMPILE_TARGET"
tsc -b "${SRC_DIR}/${COMPILE_TARGET}"

# So... tsc does declaration-bundling on its own pretty well, at least for local development.
tsc --emitDeclarationOnly --outFile "${BUILD_DIR}/$COMPILE_TARGET/lib/index.d.ts" -p "${SRC_DIR}/$COMPILE_TARGET"
tsc --emitDeclarationOnly --outFile "${BUILD_DIR}/${COMPILE_TARGET}/lib/index.d.ts" -p "${SRC_DIR}/${COMPILE_TARGET}"
}

function _copy_dir_if_exists() {
Expand Down
4 changes: 4 additions & 0 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,10 @@
"./tools/testing/test-utils": {
"types": "./build/tools/testing/test-utils/obj/index.d.ts",
"import": "./build/tools/testing/test-utils/obj/index.js"
},
"./test/resources": {
"types": "./src/test/auto/resources/build/index.d.ts",
"import": "./src/test/auto/resources/build/index.js"
}
},
"imports": {
Expand Down
28 changes: 26 additions & 2 deletions web/src/engine/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,35 @@ do_build () {
node src/osk/validate-gesture-specs.js
}

builder_run_action clean rm -rf "$KEYMAN_ROOT/web/build/engine"
run_tests() {
local OUTPUT_FILE FAILURE_COUNT
# Remove stale coverage data
rm -rf "${KEYMAN_ROOT}/web/build/coverage/raw/engine"

# Unfortunately we get an error from the coverage report generation:
# "TypeError [ERR_INVALID_URL_SCHEME]: The URL must be of scheme file"
# The following lines ignore the exit code and instead check the number
# of failed tests from the output.
set +e
OUTPUT_FILE=$(mktemp)
test-headless engine "" 2>&1 | tee "${OUTPUT_FILE}"
set -e

FAILURE_COUNT=$(grep ' failing' "${OUTPUT_FILE}" | xargs | cut -f 1 -d' ')
rm "${OUTPUT_FILE}"
builder_echo "(The 'TypeError [ERR_INVALID_URL_SCHEME]: The URL must be of scheme file' is expected)"
if ((FAILURE_COUNT > 0)); then
builder_die "Headless engine tests failed (.js tests)"
fi
Comment on lines +76 to +90
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't seem right. We are running code coverage tests elsewhere just fine.


test-headless-typescript engine
}

builder_run_action clean rm -rf "${KEYMAN_ROOT}/web/build/engine"
builder_run_child_actions clean
builder_run_action configure node_select_version_and_npm_ci
builder_run_child_actions configure
builder_run_child_actions build
builder_run_action build do_build
builder_run_action test test-headless-typescript engine
builder_run_action test run_tests
builder_run_child_actions test
2 changes: 0 additions & 2 deletions web/src/engine/src/keyboard-storage/cloud/index.ts

This file was deleted.

2 changes: 1 addition & 1 deletion web/src/engine/src/keyboard-storage/domCloudRequester.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ManagedPromise } from 'keyman/common/web-utils';
import { CloudRequesterInterface } from './cloud/requesterInterface.js';
import { CLOUD_MALFORMED_OBJECT_ERR, CLOUD_TIMEOUT_ERR, CLOUD_STUB_REGISTRATION_ERR } from './cloud/queryEngine.js';
import { CLOUD_MALFORMED_OBJECT_ERR, CLOUD_TIMEOUT_ERR, CLOUD_STUB_REGISTRATION_ERR } from './cloud/cloudQueryEngine.js';

export class DOMCloudRequester implements CloudRequesterInterface {
private readonly fileLocal: boolean;
Expand Down
11 changes: 9 additions & 2 deletions web/src/engine/src/keyboard-storage/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,15 @@ export {
REGION_CODES
} from './keyboardStub.js';
export { StubAndKeyboardCache, toPrefixedKeyboardId, toUnprefixedKeyboardId } from './stubAndKeyboardCache.js';
export { CloudQueryResult, CloudQueryEngine } from './cloud/queryEngine.js';
export { CloudQueryResult, CloudQueryEngine } from './cloud/cloudQueryEngine.js';
export { CloudRequesterInterface } from './cloud/requesterInterface.js';
export { KeyboardRequisitioner } from './keyboardRequisitioner.js';
export { ModelCache } from './modelCache.js';
export { DOMCloudRequester } from './domCloudRequester.js';
export { DOMCloudRequester } from './domCloudRequester.js';

import { CLOUD_TIMEOUT_ERR, CLOUD_STUB_REGISTRATION_ERR } from './cloud/cloudQueryEngine.js';

export const unitTestEndpoints = {
CLOUD_TIMEOUT_ERR,
CLOUD_STUB_REGISTRATION_ERR
};
Comment on lines +17 to +22
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This doesn't feel right. unitTestEndpoints are intended for targets for unit tests, not for sharing variables. There's no need for these to be exported for their usages in unit tests -- they are both used just one time in nodeCloudeRequester.ts (tests):

// Set callback timer
const timeoutObj = setTimeout(() => {
promise.reject(new Error(CLOUD_TIMEOUT_ERR));
}, 10000);

if(!promise.isResolved) {
promise.reject(new Error(CLOUD_STUB_REGISTRATION_ERR));
}

IMHO, those errors should just be plain strings in the unit testing code -- it's not deployed, there's no localization need, etc, etc. Just KISS. We can then eliminate the messiness of exporting these two constants.

Suggested change
import { CLOUD_TIMEOUT_ERR, CLOUD_STUB_REGISTRATION_ERR } from './cloud/cloudQueryEngine.js';
export const unitTestEndpoints = {
CLOUD_TIMEOUT_ERR,
CLOUD_STUB_REGISTRATION_ERR
};

Copy link
Contributor Author

@ermshiperete ermshiperete Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, inlined those consts.

Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import sinon from 'sinon';

import { LanguageProcessor, TranscriptionCache } from 'keyman/engine/main';
import { PredictionContext } from 'keyman/engine/interfaces';
import { Worker as LMWorker } from "@keymanapp/lexical-model-layer/node";
import { DeviceSpec, SyntheticTextStore } from 'keyman/engine/keyboard';
import { NodeWorker } from "@keymanapp/lexical-model-layer/node";
import { SyntheticTextStore } from 'keyman/engine/keyboard';

function compileDummyModel(suggestionSets) {
return `
Expand Down Expand Up @@ -66,7 +66,7 @@ describe("PredictionContext", () => {
let langProcessor;

beforeEach(function() {
langProcessor = new LanguageProcessor(LMWorker, new TranscriptionCache());
langProcessor = new LanguageProcessor(NodeWorker, new TranscriptionCache());
});

afterEach(function() {
Expand All @@ -83,7 +83,7 @@ describe("PredictionContext", () => {

let textStore = new SyntheticTextStore("appl", 4); // "appl|", with '|' as the caret position.
const initialTextStore = SyntheticTextStore.from(textStore);
const promise = predictiveContext.setCurrentTarget(textStore);
const promise = predictiveContext.setCurrentTextStore(textStore);

// Initial predictive state: no suggestions. context.initializeState() has not yet been called.
assert.equal(updateFake.callCount, 1);
Expand Down Expand Up @@ -122,7 +122,7 @@ describe("PredictionContext", () => {

let textStore = new SyntheticTextStore("appl", 4); // "appl|", with '|' as the caret position.
const initialTextStore = SyntheticTextStore.from(textStore);
const promise = predictiveContext.setCurrentTarget(textStore);
const promise = predictiveContext.setCurrentTextStore(textStore);

// Initial predictive state: no suggestions. context.initializeState() has not yet been called.
assert.equal(updateFake.callCount, 1);
Expand Down Expand Up @@ -181,7 +181,7 @@ describe("PredictionContext", () => {
const predictiveContext = new PredictionContext(langProcessor, dummiedGetLayer);

let textStore = new SyntheticTextStore("appl", 4); // "appl|", with '|' as the caret position.
const initialSuggestions = await predictiveContext.setCurrentTarget(textStore);
const initialSuggestions = await predictiveContext.setCurrentTextStore(textStore);

let updateFake = sinon.fake();
predictiveContext.on('update', updateFake);
Expand All @@ -206,7 +206,7 @@ describe("PredictionContext", () => {

let textState = new SyntheticTextStore("appl", 4); // "appl|", with '|' as the caret position.

await predictiveContext.setCurrentTarget(textState);
await predictiveContext.setCurrentTextStore(textState);

let updateFake = sinon.fake();
predictiveContext.on('update', updateFake);
Expand Down Expand Up @@ -274,7 +274,7 @@ describe("PredictionContext", () => {

// Test setup - return to the state at the end of the prior-defined unit test ('suggestion application...')

await predictiveContext.setCurrentTarget(textState);
await predictiveContext.setCurrentTextStore(textState);

// This is the point in time that a reversion operation will rewind the context to.
const revertBaseTextState = SyntheticTextStore.from(textState);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ const require = createRequire(import.meta.url);

import { MinimalKeymanGlobal } from 'keyman/engine/keyboard';
import { JSKeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from '../../../resources/loader/nodeKeyboardLoader.js';
import { NodeKeyboardLoader } from 'keyman/test/resources';
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';

import { env } from 'node:process';
const KEYMAN_ROOT = env.KEYMAN_ROOT;
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';

const __dirname = dirname(fileURLToPath(import.meta.url));
const KEYMAN_ROOT = env.KEYMAN_ROOT ?? (__dirname + '/../../../../../../../');

describe('Engine - Basic Simulation', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/basic_lao_simulation.json'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import { createRequire } from 'module';
const require = createRequire(import.meta.url);

import { JSKeyboardProcessor } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from '../../../resources/loader/nodeKeyboardLoader.js';
import { DEFAULT_PROCESSOR_INIT_OPTIONS } from '../../../resources/defaultProcessorInitOptions.js';
import { DEFAULT_PROCESSOR_INIT_OPTIONS, NodeKeyboardLoader } from 'keyman/test/resources';

global.keyman = {}; // So that keyboard-based checks against the global `keyman` succeed.
// 10.0+ dependent keyboards, like khmer_angkor, will otherwise fail to load.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { assert } from "chai";
import { DEFAULT_PROCESSOR_INIT_OPTIONS } from '../../../resources/defaultProcessorInitOptions.js';
import * as JSProcessorModule from "keyman/engine/js-processor";
import * as KeyboardModule from "keyman/engine/keyboard";
const KMWString = KeyboardModule.KMWString;
import { DEFAULT_PROCESSOR_INIT_OPTIONS } from 'keyman/test/resources';
import { JSKeyboardProcessor } from "keyman/engine/js-processor";
import { JSKeyboard, SyntheticTextStore } from "keyman/engine/keyboard";
import { KMWString, Version } from 'keyman/common/web-utils';

// A few small tests to ensure that the ES Module bundle was successfully constructed and is usable.

var toSupplementaryPairString = function(code){
var H = Math.floor((code - 0x10000) / 0x400) + 0xD800;
var L = (code - 0x10000) % 0x400 + 0xDC00;
const toSupplementaryPairString = function(code){
const H = Math.floor((code - 0x10000) / 0x400) + 0xD800;
const L = (code - 0x10000) % 0x400 + 0xDC00;

return String.fromCharCode(H, L);
}
Expand All @@ -18,7 +18,7 @@ let u = toSupplementaryPairString;
describe('Bundled ES Module for js-processor', function() {
describe('JSKeyboardProcessor', function () {
it('should initialize without errors', function () {
let kp = new JSProcessorModule.JSKeyboardProcessor(null, DEFAULT_PROCESSOR_INIT_OPTIONS);
let kp = new JSKeyboardProcessor(null, DEFAULT_PROCESSOR_INIT_OPTIONS);
assert.isNotNull(kp);
});
});
Expand All @@ -28,29 +28,29 @@ describe('Bundled ES Module for js-processor', function() {
describe('Bundled ES Module for keyboard', function () {
describe('Keyboard', function () {
it('should initialize without errors', function () {
let kp = new KeyboardModule.JSKeyboard();
let kp = new JSKeyboard();
assert.isNotNull(kp);
});
});

describe("Imported `utils`", function () {
it("should include `utils` package's Version class", () => {
let v16 = new KeyboardModule.Version([16, 1]);
let v16 = new Version([16, 1]);
assert.equal(v16.toString(), "16.1");
});
});

describe('SyntheticTextStore', () => {
it('basic functionality test', () => {
let textStore = new KeyboardModule.SyntheticTextStore("aple", 2); // ap | le
let textStore = new SyntheticTextStore("aple", 2); // ap | le
textStore.insertTextBeforeCaret('p');
assert.equal(textStore.getText(), "apple");
});

it('smp test', () => {
KMWString.enableSupplementaryPlane(true); // Declared & defined in web-utils.
try {
let textStore = new KeyboardModule.SyntheticTextStore(u(0x1d5ba) + u(0x1d5c9) + u(0x1d5c5) + u(0x1d5be), 2); // ap | le
let textStore = new SyntheticTextStore(u(0x1d5ba) + u(0x1d5c9) + u(0x1d5c5) + u(0x1d5be), 2); // ap | le
textStore.insertTextBeforeCaret(u(0x1d5c9));
assert.equal(textStore.getText(), u(0x1d5ba) + u(0x1d5c9) + u(0x1d5c9) + u(0x1d5c5) + u(0x1d5be));
} finally {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,16 @@ const require = createRequire(import.meta.url);

import { MinimalKeymanGlobal } from 'keyman/engine/keyboard';
import { JSKeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from '../../../resources/loader/nodeKeyboardLoader.js';
import { NodeKeyboardLoader } from 'keyman/test/resources';
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';
import { ModifierKeyConstants } from '@keymanapp/common-types';

import { env } from 'node:process';
const KEYMAN_ROOT = env.KEYMAN_ROOT;
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';

const __dirname = dirname(fileURLToPath(import.meta.url));
const KEYMAN_ROOT = env.KEYMAN_ROOT ?? (__dirname + '/../../../../../../../');

describe('Engine - Chirality', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/chirality.json'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,15 @@ const require = createRequire(import.meta.url);

import { MinimalKeymanGlobal } from 'keyman/engine/keyboard';
import { JSKeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from '../../../resources/loader/nodeKeyboardLoader.js';
import { NodeKeyboardLoader } from 'keyman/test/resources';
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';

import { env } from 'node:process';
const KEYMAN_ROOT = env.KEYMAN_ROOT;
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';

const __dirname = dirname(fileURLToPath(import.meta.url));
const KEYMAN_ROOT = env.KEYMAN_ROOT ?? (__dirname + '/../../../../../../../');

describe('Engine - Deadkeys', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/deadkeys.json'));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,7 @@ const require = createRequire(import.meta.url);

import { MinimalKeymanGlobal, SyntheticTextStore } from 'keyman/engine/keyboard';
import { JSKeyboardInterface, JSKeyboardProcessor } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from '../../../resources/loader/nodeKeyboardLoader.js';
import { DEFAULT_PROCESSOR_INIT_OPTIONS } from '../../../resources/defaultProcessorInitOptions.js';
import { DEFAULT_PROCESSOR_INIT_OPTIONS, NodeKeyboardLoader } from 'keyman/test/resources';

import { NodeProctor, RecordedKeystrokeSequence } from '@keymanapp/recorder-core';

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const require = createRequire(import.meta.url);

import { MinimalKeymanGlobal, SyntheticTextStore } from 'keyman/engine/keyboard';
import { JSKeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from '../../../resources/loader/nodeKeyboardLoader.js';
import { NodeKeyboardLoader } from 'keyman/test/resources';
import { NodeProctor, RecordedKeystrokeSequence } from '@keymanapp/recorder-core';

const device = {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { assert } from 'chai';

import { JSKeyboard, KMWString } from 'keyman/engine/keyboard';
import { KMWString } from 'keyman/common/web-utils';
import { JSKeyboard } from 'keyman/engine/keyboard';
import { JSKeyboardProcessor } from 'keyman/engine/js-processor';
import { DEFAULT_PROCESSOR_INIT_OPTIONS } from '../../../resources/defaultProcessorInitOptions.js';
import { DEFAULT_PROCESSOR_INIT_OPTIONS } from 'keyman/test/resources';

let device = {
formFactor: 'desktop',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@ const require = createRequire(import.meta.url);

import { MinimalKeymanGlobal } from 'keyman/engine/keyboard';
import { JSKeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from '../../../resources/loader/nodeKeyboardLoader.js';
import { NodeKeyboardLoader } from 'keyman/test/resources';
import { KeyboardTest, NodeProctor } from '@keymanapp/recorder-core';

import { env } from 'node:process';
const KEYMAN_ROOT = env.KEYMAN_ROOT;
import { fileURLToPath } from 'node:url';
import { dirname } from 'node:path';

const __dirname = dirname(fileURLToPath(import.meta.url));
const KEYMAN_ROOT = env.KEYMAN_ROOT ?? (__dirname + '/../../../../../../../../');
Comment on lines 12 to +17
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put this into a helper module in keyman/test/resources and avoid repeating it?


describe('Engine - Unmatched Final Groups', function() {
let testJSONtext = fs.readFileSync(require.resolve('@keymanapp/common-test-resources/json/engine_tests/ghp_enter.json'));
// Common test suite setup.
let testSuite = new KeyboardTest(JSON.parse(testJSONtext));

var keyboardWithHarness;
let keyboardWithHarness;
let device = {
formFactor: 'desktop',
OS: 'windows',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const require = createRequire(import.meta.url);
import { DeviceSpec } from 'keyman/common/web-utils';
import { JSKeyboard, MinimalKeymanGlobal, SyntheticTextStore } from 'keyman/engine/keyboard';
import { JSKeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from '../../../resources/loader/nodeKeyboardLoader.js';
import { NodeKeyboardLoader } from 'keyman/test/resources';

describe('Headless keyboard loading', function () {
const laoPath = require.resolve('@keymanapp/common-test-resources/keyboards/lao_2008_basic.js');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const require = createRequire(import.meta.url);

import { Codes, KeyEvent, MinimalKeymanGlobal, SyntheticTextStore } from 'keyman/engine/keyboard';
import { JSKeyboardInterface } from 'keyman/engine/js-processor';
import { NodeKeyboardLoader } from '../../../resources/loader/nodeKeyboardLoader.js';
import { NodeKeyboardLoader } from 'keyman/test/resources';

// Compare and contrast the unit tests here with those for app/browser key-event unit testing
// in the hardware-event-processing set; the output objects there should have the same format
Expand Down
Loading