Merge branch 'epic/web-core' into test/web/problem

This commit is contained in:
Eberhard Beilharz 2026-01-07 11:37:49 +01:00 committed by GitHub
commit afe6f467e3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
28 changed files with 139 additions and 90 deletions

View file

@ -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+')');

View file

@ -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 |

View file

@ -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()) ?? ''}'` };
}
}

View file

@ -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',
]

View file

@ -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;

View file

@ -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

View file

@ -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:

View file

@ -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';

View file

@ -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';

View file

@ -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';

View file

@ -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<CloudQueryEngine['_registerCore']>) => void
}
export default class CloudQueryEngine extends EventEmitter<EventMap> {
export class CloudQueryEngine extends EventEmitter<EventMap> {
private cloudResolutionPromises: Map<number, ManagedPromise<KeyboardStub[] | LanguageAPIPropertySpec[]>> = new Map();
private _languageListPromise: ManagedPromise<LanguageAPIPropertySpec[]>;

View file

@ -1,6 +1,6 @@
import { ManagedPromise } from 'keyman/engine/keyboard';
export default interface CloudRequesterInterface {
export interface CloudRequesterInterface {
request<T>(query: string): {
promise: ManagedPromise<T>,
queryId: number

View file

@ -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) {

View file

@ -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';

View file

@ -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;

View file

@ -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;

View file

@ -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} = {};

View file

@ -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<EventMap> {
export class StubAndKeyboardCache extends EventEmitter<EventMap> {
private stubSetTable: Record<string, Record<string, KeyboardStub>> = {};
private keyboardTable: Record<string, Keyboard | Promise<Keyboard>> = {};
@ -70,7 +66,7 @@ export default class StubAndKeyboardCache extends EventEmitter<EventMap> {
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<EventMap> {
}
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<EventMap> {
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<EventMap> {
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<EventMap> {
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<EventMap> {
}
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<EventMap> {
}
if(keyboardID) {
keyboardID = prefixed(keyboardID);
keyboardID = toPrefixedKeyboardId(keyboardID);
}
const stubTable = this.stubSetTable[keyboardID] ?? {};
@ -238,7 +234,7 @@ export default class StubAndKeyboardCache extends EventEmitter<EventMap> {
* 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];

View file

@ -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 {

View file

@ -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);

View file

@ -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;

View file

@ -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)
}

View file

@ -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.

View file

@ -360,7 +360,7 @@ export class LanguageProcessor extends EventEmitter<LanguageProcessorEventMap> {
/**
* 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.
*/

View file

@ -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
]
};

View file

@ -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`.

View file

@ -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);
};

View file

@ -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';