diff --git a/android/KMEA/app/src/main/assets/android-host.js b/android/KMEA/app/src/main/assets/android-host.js index ad440d493a..48672c0572 100644 --- a/android/KMEA/app/src/main/assets/android-host.js +++ b/android/KMEA/app/src/main/assets/android-host.js @@ -247,7 +247,7 @@ function deregisterModel(modelID) { } function enableSuggestions(model, suggestionType) { - // Set the options first so that KMW's ModelManager can properly handle model enablement states + // Set the options first so that KMW's ModelCache can properly handle model enablement states // the moment we actually register the new model. // Use console_debug console_debug('enableSuggestions(model, maySuggest='+suggestionType+')'); diff --git a/common/test/keyboards/baseline/README.md b/common/test/keyboards/baseline/README.md index 7894490d9b..090fdcbd5b 100644 --- a/common/test/keyboards/baseline/README.md +++ b/common/test/keyboards/baseline/README.md @@ -26,3 +26,21 @@ The keyboards can be built with: This builds the keyboards with debug information and no compiler version embedded. + +## Grouping of the test fixtures + +Fixtures that test similar functionality are roughly grouped together. +There is some overlap between different groups, so this was done +mainly by test name. + +| Name | Test group | +|----------|------------------------------------------| +| k_00xx_* | Tests that didn't fit in any other group | +| k_01xx_* | Basic rules | +| k_02xx_* | RALT | +| k_03xx_* | deadkeys | +| k_04xx_* | Using multiple groups | +| k_05xx_* | Options | +| k_06xx_* | System stores | +| k_07xx_* | Caps related tests | +| k_08xx_* | Context related | diff --git a/common/test/resources/playwright-TC-reporter.ts b/common/test/resources/playwright-TC-reporter.ts index f550a48d2c..8cbc0967e7 100644 --- a/common/test/resources/playwright-TC-reporter.ts +++ b/common/test/resources/playwright-TC-reporter.ts @@ -62,6 +62,15 @@ class TestNode { TestNode.OpenNodes.push(this.id); } + private escape(message: string): string { + // TeamCity escaping rules for: ' | [ ] \n \r \uNNNN + // See: https://www.jetbrains.com/help/teamcity/service-messages.html#Escaped+Values + return message?.replace(/['|\[\]]/g, (matched) => `|${matched}`) + .replace(/\n/g, '|n') + .replace(/\r/g, '|r') + .replace(/[\u0080-\uFFFF]/g, c => `|0x${c.charCodeAt(0).toString(16).padStart(4, '0')}`) ?? ''; + } + private getTestResult(result: TestResult): { msgTitle: string, details: string } { if (!result) { return null; @@ -72,9 +81,9 @@ class TestNode { case 'failed': case 'interrupted': case 'timedOut': - return { msgTitle: 'testFailed', details: `message='${result.error?.message}' details='${result.error?.value ?? result.error?.cause}'` }; + return { msgTitle: 'testFailed', details: `message='${this.escape(result.error?.message)}' details='${this.escape(result.error?.value ?? result.error?.cause)}'` }; case 'skipped': - return { msgTitle: 'testIgnored', details: `message='${result.annotations?.toString() ?? ''}'` }; + return { msgTitle: 'testIgnored', details: `message='${this.escape(result.annotations?.toString()) ?? ''}'` }; } } diff --git a/core/tests/unit/kmx/meson.build b/core/tests/unit/kmx/meson.build index 9acf6cf94f..152c5656cb 100644 --- a/core/tests/unit/kmx/meson.build +++ b/core/tests/unit/kmx/meson.build @@ -87,7 +87,7 @@ tests = [ 'k_0810___nul_and_index', 'k_0811___if_and_index', 'k_0812___nul_and_contextex', - # Skipped: 'k_0813___deadkey_cancelled_by_arrow', + # TODO-web-core: Skipped: 'k_0813___deadkey_cancelled_by_arrow', ] diff --git a/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js b/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js index b390b438de..25af94b5bd 100644 --- a/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js +++ b/ios/engine/KMEI/KeymanEngine/resources/Keyman.bundle/Contents/Resources/ios-host.js @@ -327,7 +327,7 @@ function toHex(theString) { } function enableSuggestions(model, mayPredict, mayCorrect) { - // Set the options first so that KMW's ModelManager can properly handle model enablement states + // Set the options first so that KMW's ModelCache can properly handle model enablement states // the moment we actually register the new model. keyman.core.languageProcessor.mayPredict = mayPredict; keyman.core.languageProcessor.mayCorrect = mayCorrect; diff --git a/web/common.inc.sh b/web/common.inc.sh index b587bf99ab..b4a3c47d7e 100644 --- a/web/common.inc.sh +++ b/web/common.inc.sh @@ -91,7 +91,7 @@ function test-headless() { TEST_FOLDER=$1 TEST_BASE="${KEYMAN_ROOT}/web/src/test/auto/headless/" TEST_EXTENSIONS=${2:-} - if [ ! -z "${2:-}" ]; then + if [[ ! -z "${2:-}" ]]; then TEST_BASE="${KEYMAN_ROOT}/web/build/test/headless/" # Ensure the compiled tests are available. @@ -104,6 +104,7 @@ function test-headless() { echo "##teamcity[flowStarted flowId='unit_tests']" fi if [[ -n "${TEST_EXTENSIONS}" ]]; then + # file extension of test files TEST_OPTS+=(--extension "${TEST_EXTENSIONS}") fi diff --git a/web/src/app/browser/src/hardwareEventKeyboard.ts b/web/src/app/browser/src/hardwareEventKeyboard.ts index 7057bd9df9..22f47544d7 100644 --- a/web/src/app/browser/src/hardwareEventKeyboard.ts +++ b/web/src/app/browser/src/hardwareEventKeyboard.ts @@ -14,6 +14,12 @@ type KeyboardState = { baseLayout: string } +const DOM_KEY_LOCATION = { + STANDARD: 0, + LEFT: 1, + RIGHT: 2, +}; + // Important: the following two lines should not cause a compile error if left uncommented. // let dummy1: KeyboardProcessor; // let dummy2: KeyboardState = dummy1; @@ -51,9 +57,9 @@ export function _GetEventKeyCode(e: KeyboardEvent) { * @param {KeyboardEvent} e Event object * @param {KeyboardState} keyboardState Keyboard state object * @param {DeviceSpec} device Device object - * @return {KeyEvent} KeymanWeb KeyEvent object, or null - * for duplicate/spurious events or if - * there is no key code. + * + * @return {KeyEvent} KeymanWeb KeyEvent object, or null for duplicate/spurious + * events or if there is no key code. */ export function preprocessKeyboardEvent(e: KeyboardEvent, keyboardState: KeyboardState, device: DeviceSpec): KeyEvent { if(e.cancelBubble === true) { @@ -104,17 +110,21 @@ export function preprocessKeyboardEvent(e: KeyboardEvent, keyboardState: Keyboar */ let curModState = 0x0000; - curModState |= (e.getModifierState("Shift") ? 0x10 : 0); + curModState |= (e.getModifierState("Shift") ? ModifierKeyConstants.K_SHIFTFLAG : 0); if(e.getModifierState("Control")) { - curModState |= ((e.location != 0 && ctrlEvent) ? - (e.location == 1 ? ModifierKeyConstants.LCTRLFLAG : ModifierKeyConstants.RCTRLFLAG) : // Condition 1 - prevModState & 0x0003 /* LCTRLFLAG | RCTRLFLAG */); // Condition 2 + curModState |= ((e.location != DOM_KEY_LOCATION.STANDARD && ctrlEvent) + ? (e.location == DOM_KEY_LOCATION.LEFT + ? ModifierKeyConstants.LCTRLFLAG + : ModifierKeyConstants.RCTRLFLAG) // Condition 1 + : prevModState & (ModifierKeyConstants.LCTRLFLAG | ModifierKeyConstants.RCTRLFLAG)); // Condition 2 } if(e.getModifierState("Alt")) { - curModState |= ((e.location != 0 && altEvent) ? - (e.location == 1 ? ModifierKeyConstants.LALTFLAG : ModifierKeyConstants.RALTFLAG) : // Condition 1 - prevModState & 0x000C /* LALTFLAG | RALTFLAG */); // Condition 2 + curModState |= ((e.location != DOM_KEY_LOCATION.STANDARD && altEvent) + ? (e.location == DOM_KEY_LOCATION.LEFT + ? ModifierKeyConstants.LALTFLAG + : ModifierKeyConstants.RALTFLAG) // Condition 1 + : prevModState & (ModifierKeyConstants.LALTFLAG | ModifierKeyConstants.RALTFLAG)); // Condition 2 } // Stage 2 - detect state key information. It can be looked up per keypress with no issue. @@ -143,7 +153,7 @@ export function preprocessKeyboardEvent(e: KeyboardEvent, keyboardState: Keyboar curModState &= ~ altGrMask; } // Perform basic filtering for Windows-based ALT_GR emulation on European keyboards. - if(curModState & ModifierKeyConstants.RALTFLAG) { + if((curModState & ModifierKeyConstants.RALTFLAG) != 0) { curModState &= ~ModifierKeyConstants.LCTRLFLAG; } @@ -151,7 +161,7 @@ export function preprocessKeyboardEvent(e: KeyboardEvent, keyboardState: Keyboar const modifierBitmasks = Codes.modifierBitmasks; const activeKeyboard = keyboardState.activeKeyboard; let Lmodifiers: number; - if(activeKeyboard && activeKeyboard.isChiral) { + if(activeKeyboard?.isChiral) { Lmodifiers = curModState & modifierBitmasks.CHIRAL; // Note for future - embedding a kill switch here would facilitate disabling AltGr / Right-alt simulation. @@ -162,9 +172,9 @@ export function preprocessKeyboardEvent(e: KeyboardEvent, keyboardState: Keyboar } else { // No need to sim AltGr here; we don't need chiral ALTs. Lmodifiers = - (curModState & 0x10) | // SHIFT - ((curModState & (ModifierKeyConstants.LCTRLFLAG | ModifierKeyConstants.RCTRLFLAG)) ? 0x20 : 0) | - ((curModState & (ModifierKeyConstants.LALTFLAG | ModifierKeyConstants.RALTFLAG)) ? 0x40 : 0); + (curModState & ModifierKeyConstants.K_SHIFTFLAG) | + ((curModState & (ModifierKeyConstants.LCTRLFLAG | ModifierKeyConstants.RCTRLFLAG)) != 0 ? ModifierKeyConstants.K_CTRLFLAG : 0) | + ((curModState & (ModifierKeyConstants.LALTFLAG | ModifierKeyConstants.RALTFLAG)) != 0 ? ModifierKeyConstants.K_ALTFLAG : 0); } @@ -210,7 +220,7 @@ export function preprocessKeyboardEvent(e: KeyboardEvent, keyboardState: Keyboar return processedEvent; } -export default class HardwareEventKeyboard extends HardKeyboardBase { +export class HardwareEventKeyboard extends HardKeyboardBase { private readonly hardDevice: DeviceSpec; // Needed properties & methods: diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index c5e44512c5..2695cee82c 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -14,7 +14,7 @@ import * as views from './viewsAnchorpoint.js'; import { BrowserConfiguration, BrowserInitOptionDefaults, BrowserInitOptionSpec } from './configuration.js'; import { ContextManager } from './contextManager.js'; import DefaultBrowserRules from './defaultBrowserRules.js'; -import HardwareEventKeyboard from './hardwareEventKeyboard.js'; +import { HardwareEventKeyboard } from './hardwareEventKeyboard.js'; import { FocusStateAPIObject } from './context/focusAssistant.js'; import { PageIntegrationHandlers } from './context/pageIntegrationHandlers.js'; import { LanguageMenu } from './languageMenu.js'; diff --git a/web/src/app/browser/src/test-index.ts b/web/src/app/browser/src/test-index.ts index d094fe7c25..6f010f494e 100644 --- a/web/src/app/browser/src/test-index.ts +++ b/web/src/app/browser/src/test-index.ts @@ -1,6 +1,6 @@ export { BrowserConfiguration, BrowserInitOptionSpec } from './configuration.js'; export { ContextManager, KeyboardCookie } from "./contextManager.js"; -export { preprocessKeyboardEvent, default as HardwareEventKeyboard } from './hardwareEventKeyboard.js'; +export { preprocessKeyboardEvent, HardwareEventKeyboard } from './hardwareEventKeyboard.js'; export { KeymanEngine } from './keymanEngine.js'; export { KeyboardInterface } from './keyboardInterface.js'; diff --git a/web/src/engine/src/keyboard-storage/cloud/index.ts b/web/src/engine/src/keyboard-storage/cloud/index.ts index eb477cacec..2ab752bcde 100644 --- a/web/src/engine/src/keyboard-storage/cloud/index.ts +++ b/web/src/engine/src/keyboard-storage/cloud/index.ts @@ -1,2 +1,2 @@ -export { CloudQueryResult, default as QueryEngine } from './queryEngine.js'; -export { default as RequesterInterface } from './requesterInterface.js'; +export { CloudQueryResult, CloudQueryEngine as QueryEngine } from './queryEngine.js'; +export { CloudRequesterInterface as RequesterInterface } from './requesterInterface.js'; diff --git a/web/src/engine/src/keyboard-storage/cloud/queryEngine.ts b/web/src/engine/src/keyboard-storage/cloud/queryEngine.ts index e94aaca1bd..fe76a3fcb6 100644 --- a/web/src/engine/src/keyboard-storage/cloud/queryEngine.ts +++ b/web/src/engine/src/keyboard-storage/cloud/queryEngine.ts @@ -2,9 +2,9 @@ import { EventEmitter } from 'eventemitter3'; import { PathConfiguration } from 'keyman/engine/interfaces'; -import { default as KeyboardStub, ErrorStub, KeyboardAPISpec } from '../keyboardStub.js'; +import { KeyboardStub, ErrorStub, KeyboardAPISpec } from '../keyboardStub.js'; import { LanguageAPIPropertySpec, ManagedPromise, Version } from 'keyman/engine/keyboard'; -import CloudRequesterInterface from './requesterInterface.js'; +import { CloudRequesterInterface } from './requesterInterface.js'; // For when the API call straight-up times out. export const CLOUD_TIMEOUT_ERR = "The Cloud API request timed out."; @@ -56,7 +56,7 @@ interface EventMap { 'unboundregister': (registration: ReturnType) => void } -export default class CloudQueryEngine extends EventEmitter { +export class CloudQueryEngine extends EventEmitter { private cloudResolutionPromises: Map> = new Map(); private _languageListPromise: ManagedPromise; diff --git a/web/src/engine/src/keyboard-storage/cloud/requesterInterface.ts b/web/src/engine/src/keyboard-storage/cloud/requesterInterface.ts index 6c612d9cb2..b1c84b6c05 100644 --- a/web/src/engine/src/keyboard-storage/cloud/requesterInterface.ts +++ b/web/src/engine/src/keyboard-storage/cloud/requesterInterface.ts @@ -1,6 +1,6 @@ import { ManagedPromise } from 'keyman/engine/keyboard'; -export default interface CloudRequesterInterface { +export interface CloudRequesterInterface { request(query: string): { promise: ManagedPromise, queryId: number diff --git a/web/src/engine/src/keyboard-storage/domCloudRequester.ts b/web/src/engine/src/keyboard-storage/domCloudRequester.ts index a39310ad8a..4901b4be15 100644 --- a/web/src/engine/src/keyboard-storage/domCloudRequester.ts +++ b/web/src/engine/src/keyboard-storage/domCloudRequester.ts @@ -1,8 +1,8 @@ import { ManagedPromise } from 'keyman/engine/keyboard'; -import CloudRequesterInterface from './cloud/requesterInterface.js'; +import { CloudRequesterInterface } from './cloud/requesterInterface.js'; import { CLOUD_MALFORMED_OBJECT_ERR, CLOUD_TIMEOUT_ERR, CLOUD_STUB_REGISTRATION_ERR } from './cloud/queryEngine.js'; -export default class DOMCloudRequester implements CloudRequesterInterface { +export class DOMCloudRequester implements CloudRequesterInterface { private readonly fileLocal: boolean; constructor(fileLocal: boolean = false) { diff --git a/web/src/engine/src/keyboard-storage/index.ts b/web/src/engine/src/keyboard-storage/index.ts index 80073844db..08f44191c0 100644 --- a/web/src/engine/src/keyboard-storage/index.ts +++ b/web/src/engine/src/keyboard-storage/index.ts @@ -2,15 +2,15 @@ export { ErrorStub, type KeyboardAPISpec, - default as KeyboardStub, + KeyboardStub, mergeAndResolveStubPromises, RawKeyboardStub, REGIONS, REGION_CODES } from './keyboardStub.js'; -export { default as StubAndKeyboardCache, toPrefixedKeyboardId, toUnprefixedKeyboardId } from './stubAndKeyboardCache.js'; -export { CloudQueryResult, default as CloudQueryEngine } from './cloud/queryEngine.js'; -export { default as CloudRequesterInterface } from './cloud/requesterInterface.js'; -export { default as KeyboardRequisitioner } from './keyboardRequisitioner.js'; -export { default as ModelCache } from './modelCache.js'; -export { default as DOMCloudRequester } from './domCloudRequester.js'; +export { StubAndKeyboardCache, toPrefixedKeyboardId, toUnprefixedKeyboardId } from './stubAndKeyboardCache.js'; +export { CloudQueryResult, CloudQueryEngine } from './cloud/queryEngine.js'; +export { CloudRequesterInterface } from './cloud/requesterInterface.js'; +export { KeyboardRequisitioner } from './keyboardRequisitioner.js'; +export { ModelCache } from './modelCache.js'; +export { DOMCloudRequester } from './domCloudRequester.js'; \ No newline at end of file diff --git a/web/src/engine/src/keyboard-storage/keyboardRequisitioner.ts b/web/src/engine/src/keyboard-storage/keyboardRequisitioner.ts index b629d2abbb..d1da9ade89 100644 --- a/web/src/engine/src/keyboard-storage/keyboardRequisitioner.ts +++ b/web/src/engine/src/keyboard-storage/keyboardRequisitioner.ts @@ -17,7 +17,7 @@ import { mergeAndResolveStubPromises, toUnprefixedKeyboardId as unprefixed } from "./index.js"; -import { default as CloudRequesterInterface } from "./cloud/requesterInterface.js"; +import { CloudRequesterInterface } from "./cloud/requesterInterface.js"; import { rejectErrorStubs } from "./keyboardStub.js"; class CloudRequestEntry { @@ -89,7 +89,7 @@ function isUniqueRequest(cache: StubAndKeyboardCache, cloudList: {id: string, la }; // TODO: Move to the keyboard-cache child project - we can test it headlessly there! -export default class KeyboardRequisitioner { +export class KeyboardRequisitioner { readonly cache: StubAndKeyboardCache; readonly cloudQueryEngine: CloudQueryEngine; readonly pathConfig: PathConfiguration; diff --git a/web/src/engine/src/keyboard-storage/keyboardStub.ts b/web/src/engine/src/keyboard-storage/keyboardStub.ts index 5f19d1d783..17877dcfe8 100644 --- a/web/src/engine/src/keyboard-storage/keyboardStub.ts +++ b/web/src/engine/src/keyboard-storage/keyboardStub.ts @@ -48,7 +48,7 @@ function configureFilePathing(path: string, configurationBasePath: string) { } } -export default class KeyboardStub extends KeyboardProperties { +export class KeyboardStub extends KeyboardProperties { KR: string; KRC: string; KF: string; diff --git a/web/src/engine/src/keyboard-storage/modelCache.ts b/web/src/engine/src/keyboard-storage/modelCache.ts index 6f4b39999a..1b01b45d57 100644 --- a/web/src/engine/src/keyboard-storage/modelCache.ts +++ b/web/src/engine/src/keyboard-storage/modelCache.ts @@ -1,6 +1,6 @@ import { ModelSpec } from 'keyman/engine/interfaces'; -export default class ModelManager { +export class ModelCache { // Tracks registered models by ID. private registeredModels: {[id: string]: ModelSpec} = {}; diff --git a/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts b/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts index 1f26759416..1537a8cc52 100644 --- a/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts +++ b/web/src/engine/src/keyboard-storage/stubAndKeyboardCache.ts @@ -1,11 +1,11 @@ import { type Keyboard, JSKeyboard, KeyboardLoaderBase as KeyboardLoader, KMXKeyboard } from "keyman/engine/keyboard"; import { EventEmitter } from "eventemitter3"; -import KeyboardStub from "./keyboardStub.js"; +import { KeyboardStub } from "./keyboardStub.js"; const KEYBOARD_PREFIX = "Keyboard_"; -function prefixed(text: string) { +export function toPrefixedKeyboardId(text: string) { if(!text.startsWith(KEYBOARD_PREFIX)) { return KEYBOARD_PREFIX + text; } else { @@ -13,9 +13,7 @@ function prefixed(text: string) { } } -export {prefixed as toPrefixedKeyboardId}; - -function withoutPrefix(text: string) { +export function toUnprefixedKeyboardId(text: string) { if(text.startsWith(KEYBOARD_PREFIX)) { return text.substring(KEYBOARD_PREFIX.length); } else { @@ -23,8 +21,6 @@ function withoutPrefix(text: string) { } } -export {withoutPrefix as toUnprefixedKeyboardId}; - interface EventMap { /** * Indicates that the specified stub has just been registered within the cache. @@ -41,7 +37,7 @@ interface EventMap { keyboardadded: (keyboard: Keyboard) => void; } -export default class StubAndKeyboardCache extends EventEmitter { +export class StubAndKeyboardCache extends EventEmitter { private stubSetTable: Record> = {}; private keyboardTable: Record> = {}; @@ -70,7 +66,7 @@ export default class StubAndKeyboardCache extends EventEmitter { if(!keyboardID) { return null; } - const entry = this.keyboardTable[prefixed(keyboardID)]; + const entry = this.keyboardTable[toPrefixedKeyboardId(keyboardID)]; // Unit testing may 'trip up' in the DOM, as bundled versions of a class from one bundled // module will fail against an `instanceof` expecting the version bundled in a second. @@ -123,7 +119,7 @@ export default class StubAndKeyboardCache extends EventEmitter { } addKeyboard(keyboard: Keyboard) { - const keyboardID = prefixed(keyboard.id); + const keyboardID = toPrefixedKeyboardId(keyboard.id); this.keyboardTable[keyboardID] = keyboard; this.emit('keyboardadded', keyboard); @@ -138,7 +134,7 @@ export default class StubAndKeyboardCache extends EventEmitter { throw new Error("Keyboard ID must be specified"); } - keyboardID = prefixed(keyboardID); + keyboardID = toPrefixedKeyboardId(keyboardID); const cachedEntry = this.keyboardTable[keyboardID]; return cachedEntry instanceof Promise; @@ -153,7 +149,7 @@ export default class StubAndKeyboardCache extends EventEmitter { throw new Error("Cannot load keyboards; this cache was configured without a loader"); } - keyboardID = prefixed(keyboardID); + keyboardID = toPrefixedKeyboardId(keyboardID); const cachedEntry = this.keyboardTable[keyboardID]; if(cachedEntry instanceof JSKeyboard) { @@ -164,11 +160,11 @@ export default class StubAndKeyboardCache extends EventEmitter { const stub = this.getStub(keyboardID, null); if(!stub) { - throw new Error(`No stub for ${withoutPrefix(keyboardID)} has been registered`); + throw new Error(`No stub for ${toUnprefixedKeyboardId(keyboardID)} has been registered`); } if(!stub.filename) { - throw new Error(`The registered stub for ${withoutPrefix(keyboardID)} lacks a path to the main keyboard file`); + throw new Error(`The registered stub for ${toUnprefixedKeyboardId(keyboardID)} lacks a path to the main keyboard file`); } const promise = this.keyboardLoader.loadKeyboardFromStub(stub); @@ -189,7 +185,7 @@ export default class StubAndKeyboardCache extends EventEmitter { } addStub(stub: KeyboardStub) { - const keyboardID = prefixed(stub.KI); + const keyboardID = toPrefixedKeyboardId(stub.KI); const stubTable = this.stubSetTable[keyboardID] = this.stubSetTable[keyboardID] ?? {}; stubTable[stub.KLC] = stub; @@ -213,7 +209,7 @@ export default class StubAndKeyboardCache extends EventEmitter { } if(keyboardID) { - keyboardID = prefixed(keyboardID); + keyboardID = toPrefixedKeyboardId(keyboardID); } const stubTable = this.stubSetTable[keyboardID] ?? {}; @@ -238,7 +234,7 @@ export default class StubAndKeyboardCache extends EventEmitter { * If `false`, only forgets the metadata (stubs). */ forgetKeyboard(keyboard: string | JSKeyboard, purge: boolean = false) { - const id: string = (keyboard instanceof JSKeyboard) ? keyboard.id : prefixed(keyboard); + const id: string = (keyboard instanceof JSKeyboard) ? keyboard.id : toPrefixedKeyboardId(keyboard); if(this.stubSetTable[id]) { delete this.stubSetTable[id]; diff --git a/web/src/engine/src/keyboard/index.ts b/web/src/engine/src/keyboard/index.ts index c8ea67593f..0e8fc53d3e 100644 --- a/web/src/engine/src/keyboard/index.ts +++ b/web/src/engine/src/keyboard/index.ts @@ -4,7 +4,7 @@ export { JSKeyboard, LayoutState } from "./keyboards/jsKeyboard.js"; export { KeyboardMinimalInterface } from './keyboards/keyboardMinimalInterface.js'; export { KMXKeyboard } from './keyboards/kmxKeyboard.js'; export { KeyboardHarness, KeyboardKeymanGlobal, MinimalCodesInterface, MinimalKeymanGlobal } from "./keyboards/keyboardHarness.js"; -export { Keyboard, KeyboardLoaderBase } from "./keyboards/keyboardLoaderBase.js"; +export { NotifyEventCode, Keyboard, KeyboardLoaderBase } from "./keyboards/keyboardLoaderBase.js"; export { KeyboardLoadErrorBuilder, KeyboardMissingError, KeyboardScriptError, KeyboardDownloadError, InvalidKeyboardError } from './keyboards/keyboardLoadError.js' export { BeepHandler, EventMap, KeyboardProcessor } from "./keyboards/keyboardProcessor.js"; export { diff --git a/web/src/engine/src/keyboard/keyboards/jsKeyboard.ts b/web/src/engine/src/keyboard/keyboards/jsKeyboard.ts index 90b45f3ba5..5fd6e06530 100644 --- a/web/src/engine/src/keyboard/keyboards/jsKeyboard.ts +++ b/web/src/engine/src/keyboard/keyboards/jsKeyboard.ts @@ -14,6 +14,7 @@ type TouchLayoutSpec = TouchLayout.TouchLayoutPlatform & { isDefault?: boolean}; import { Version, DeviceSpec } from "keyman/common/web-utils"; import { StateKeyMap } from "./stateKeyMap.js"; +import { NotifyEventCode } from './keyboardLoaderBase.js'; /** * Stores preprocessed properties of a keyboard for quick retrieval later. @@ -356,11 +357,12 @@ export class JSKeyboard { /** * Notifies keyboard of keystroke or other event * - * @param {number} command event code (16,17,18) or 0 - * @param {TextStore} textStore textStore - * @param {number} data 1 or 0 + * @param {NotifyEventCode} command event code (16,17,18) or 0 + * @param {TextStore} textStore textStore + * @param {number} data 1 for KeyDown or FocusReceived, + * 0 for KeyUp or FocusLost */ - public notify(command: number, textStore: TextStore, data: number): void { // I2187 + public notify(command: NotifyEventCode, textStore: TextStore, data: number): void { // I2187 // Good example use case - the Japanese CJK-picker keyboard if(typeof(this.scriptObject['KNS']) == 'function') { this.scriptObject['KNS'](command, textStore, data); diff --git a/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts b/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts index b7fe16dd1d..2d938e159e 100644 --- a/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts +++ b/web/src/engine/src/keyboard/keyboards/keyboardLoaderBase.ts @@ -4,6 +4,14 @@ import { KMXKeyboard } from './kmxKeyboard.js'; import { KeyboardHarness } from "./keyboardHarness.js"; import KeyboardProperties from "./keyboardProperties.js"; import { KeyboardLoadErrorBuilder, StubBasedErrorBuilder, UriBasedErrorBuilder } from './keyboardLoadError.js'; +import { Codes } from '../codes.js'; + +export enum NotifyEventCode { + FocusEvent = 0, + ShiftKey = Codes.keyCodes.K_SHIFT, + ControlKey = Codes.keyCodes.K_CONTROL, + AltKey = Codes.keyCodes.K_ALT, +}; export type KeyboardStub = KeyboardProperties & { filename: string }; export type Keyboard = JSKeyboard | KMXKeyboard; diff --git a/web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts b/web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts index 73b62558dc..1c8123192f 100644 --- a/web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts +++ b/web/src/engine/src/keyboard/keyboards/kmxKeyboard.ts @@ -7,6 +7,7 @@ import { ActiveKey, ActiveSubKey } from './activeLayout.js'; import { StateKeyMap } from './stateKeyMap.js'; import { KeyEvent } from '../keyEvent.js'; import { TextStore } from '../textStore.js'; +import { NotifyEventCode } from './keyboardLoaderBase.js'; /** * Acts as a wrapper class for KMX(+) Keyman keyboards @@ -99,12 +100,15 @@ export class KMXKeyboard { } /** - * @param {number} eventCode event code (16,17,18) or 0 // TODO-web-core: document meaning of these! (#15290) - * @param {TextStore} textStore textStore - * @param {number} data 1 or 0 * Notifies keyboard of keystroke or other event + * + * @param {NotifyEventCode} eventCode key code (16-18: Shift, Control or Alt), + * or 0 for focus + * @param {TextStore} textStore textStore + * @param {number} data 1 for KeyDown or FocusReceived, + * 0 for KeyUp or FocusLost */ - public notify(eventCode: 16|17|18|0, textStore: TextStore, data: number) { // I2187 + public notify(eventCode: NotifyEventCode, textStore: TextStore, data: number): void { // I2187 // TODO-web-core: do we need to support this? (#15290) } diff --git a/web/src/engine/src/main/headless/inputProcessor.ts b/web/src/engine/src/main/headless/inputProcessor.ts index ffcb282a69..88168a54f4 100644 --- a/web/src/engine/src/main/headless/inputProcessor.ts +++ b/web/src/engine/src/main/headless/inputProcessor.ts @@ -168,26 +168,29 @@ export class InputProcessor { */ private _processKeyEvent(keyEvent: KeyEvent, textStore: TextStore): ProcessorAction { const formFactor = keyEvent.device.formFactor; - const fromOSK = keyEvent.isSynthetic; - // The default OSK layout for desktop devices does not include nextlayer info, relying on modifier detection here. + // The default OSK layout for desktop devices does not include nextlayer info, relying on + // modifier detection here. // It's the OSK equivalent to doModifierPress on 'desktop' form factors. - if((formFactor == DeviceSpec.FormFactor.Desktop || !this.activeKeyboard || (this.activeKeyboard instanceof JSKeyboard && this.activeKeyboard.usesDesktopLayoutOnDevice(keyEvent.device))) && fromOSK) { + if ((formFactor == DeviceSpec.FormFactor.Desktop || !this.activeKeyboard || + (this.activeKeyboard instanceof JSKeyboard && + this.activeKeyboard.usesDesktopLayoutOnDevice(keyEvent.device))) && + keyEvent.isSynthetic + ) { // If it's a desktop OSK style and this triggers a layer change, // a modifier key was clicked. No output expected, so it's safe to instantly exit. - if(this.keyboardProcessor.selectLayer(keyEvent)) { + if (this.keyboardProcessor.selectLayer(keyEvent)) { return new ProcessorAction(); } } - // Will handle keystroke-based non-layer change modifier & state keys, mapping them through the physical keyboard's version - // of state management. `doModifierPress` must always run. - if (this.keyboardProcessor.doModifierPress(keyEvent, textStore, !fromOSK)) { + // Will handle keystroke-based non-layer change modifier & state keys, mapping them through + // the physical keyboard's version of state management. `doModifierPress` must always run. + const wasModifierPress = this.keyboardProcessor.doModifierPress(keyEvent, textStore, !keyEvent.isSynthetic); + if (wasModifierPress && !keyEvent.isSynthetic) { // If run on a desktop platform, we know that modifier & state key presses may not // produce output, so we may make an immediate return safely. - if(!fromOSK) { - return new ProcessorAction(); - } + return new ProcessorAction(); } // If suggestions exist AND space is pressed, accept the suggestion and do not process the keystroke. diff --git a/web/src/engine/src/main/headless/languageProcessor.ts b/web/src/engine/src/main/headless/languageProcessor.ts index bad68b5d2b..589bed6b2e 100644 --- a/web/src/engine/src/main/headless/languageProcessor.ts +++ b/web/src/engine/src/main/headless/languageProcessor.ts @@ -360,7 +360,7 @@ export class LanguageProcessor extends EventEmitter { /** * Retrieves the context and output state of KMW immediately before the prediction with * token `id` was generated. Must correspond to a 'recent' one, as only so many are stored - * in `ModelManager`'s history buffer. + * in `ModelCache`'s history buffer. * @param id A unique identifier corresponding to a recent `Transcription`. * @returns The matching `Transcription`, or `null` none is found. */ diff --git a/web/src/test/auto/e2e/baseline/baseline.tests.ts b/web/src/test/auto/e2e/baseline/baseline.tests.ts index 945d5e6620..0abd219e02 100644 --- a/web/src/test/auto/e2e/baseline/baseline.tests.ts +++ b/web/src/test/auto/e2e/baseline/baseline.tests.ts @@ -59,7 +59,7 @@ const testsToFix = { 'k_0700___caps_lock.kmn', 'k_0701___caps_control.kmn', 'k_0807___enter_invalidates_context.kmn', - 'k_0813___deadkey_cancelled_by_arrow.kmn', + 'k_0813___deadkey_cancelled_by_arrow.kmn', // Keyman Engine for Web does not interpret arrow keys - #15397 ], // TODO: fix these tests (#15342) '.js': [ @@ -84,7 +84,7 @@ const testsToFix = { 'k_0807___enter_invalidates_context.kmn', 'k_0808___nul_and_context.kmn', // js only 'k_0810___nul_and_index.kmn', // js only - 'k_0813___deadkey_cancelled_by_arrow.kmn', + 'k_0813___deadkey_cancelled_by_arrow.kmn', // Keyman Engine for Web does not interpret arrow keys - #15397 ] }; diff --git a/web/src/test/auto/headless/README.md b/web/src/test/auto/headless/README.md index e496cf8a2a..58e1a4107f 100644 --- a/web/src/test/auto/headless/README.md +++ b/web/src/test/auto/headless/README.md @@ -5,4 +5,4 @@ under `src`. For example, `src/test/auto/headless/engine/js-processor` are the tests for `src/engine/js-processor` and will be run from -`src/engine/js-processor/build.sh`. +`src/engine/build.sh`. diff --git a/web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts b/web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts index 42c20db423..f01cba232e 100644 --- a/web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts +++ b/web/src/test/auto/headless/engine/core-processor/coreKeyboardProcessor.tests.ts @@ -30,12 +30,10 @@ describe('CoreKeyboardProcessor', function () { const item = new KM_Core.instance.km_core_context_item(); if (isMarker) { item.marker = c as number; + } else if (typeof c == 'number') { + item.character = c; } else { - if (typeof (c) == 'number') { - item.character = c; - } else { - item.character = c.codePointAt(0); - } + item.character = c.codePointAt(0); } contextItems.push_back(item); }; diff --git a/web/src/test/auto/resources/loader/nodeCloudRequester.ts b/web/src/test/auto/resources/loader/nodeCloudRequester.ts index ae1b57e14d..3b4e923b5c 100644 --- a/web/src/test/auto/resources/loader/nodeCloudRequester.ts +++ b/web/src/test/auto/resources/loader/nodeCloudRequester.ts @@ -1,10 +1,10 @@ import { ManagedPromise } from 'keyman/engine/keyboard'; -import CloudRequesterInterface from '../../../../engine/src/keyboard-storage/cloud/requesterInterface.js'; +import { CloudRequesterInterface } from '../../../../engine/src/keyboard-storage/cloud/requesterInterface.js'; import { CLOUD_TIMEOUT_ERR, CLOUD_STUB_REGISTRATION_ERR, CloudQueryResult, - default as CloudQueryEngine + CloudQueryEngine } from '../../../../engine/src/keyboard-storage/cloud/queryEngine.js'; import fs from 'node:fs';