From a404c20ddf12ffbe8dcd36bd51ad6343ec37cd9d Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Mon, 22 Jan 2024 14:08:10 +0700 Subject: [PATCH] feat(web): mnemonic + legacy keystroke pre-proc unit tests --- .../web/keyboard-processor/src/text/codes.ts | 4 +- .../app/browser/src/hardwareEventKeyboard.ts | 4 + web/src/engine/main/src/hardKeyboard.ts | 4 +- .../hardware-event-processing.tests.ts | 590 +++++++++++++++++- 4 files changed, 586 insertions(+), 16 deletions(-) diff --git a/common/web/keyboard-processor/src/text/codes.ts b/common/web/keyboard-processor/src/text/codes.ts index 5bbd26f512..11490f32f2 100644 --- a/common/web/keyboard-processor/src/text/codes.ts +++ b/common/web/keyboard-processor/src/text/codes.ts @@ -32,7 +32,9 @@ const Codes = { "ALT_GR_SIM": (0x0001 | 0x0004), "CHIRAL":0x001F, // The base bitmask for chiral keyboards. Includes SHIFT, which is non-chiral. "IS_CHIRAL":0x000F, // Used to test if a bitmask uses a chiral modifier. - "NON_CHIRAL":0x0070 // The default bitmask, for non-chiral keyboards + "NON_CHIRAL":0x0070, // The default bitmask, for non-chiral keyboards, + // Represents all modifier codes not supported by KMW 1.0 legacy keyboards. + "NON_LEGACY": 0x006F // ALL, but without the SHIFT bit }, stateBitmasks: { diff --git a/web/src/app/browser/src/hardwareEventKeyboard.ts b/web/src/app/browser/src/hardwareEventKeyboard.ts index d66f69bf4b..eea822d1cb 100644 --- a/web/src/app/browser/src/hardwareEventKeyboard.ts +++ b/web/src/app/browser/src/hardwareEventKeyboard.ts @@ -121,6 +121,10 @@ export function preprocessKeyboardEvent(e: KeyboardEvent, keyboardState: Keyboar // Stage 3 - Set our modifier state tracking variable and perform basic AltGr-related management. const LmodifierChange = keyboardState.modStateFlags != curModState; + + // KeyboardState update: save our known modifier/state analysis bits. + // Note: `keyboardState` is typically the full-fledged KeyboardProcessor instance. As a result, + // changes here persist across calls (as we only ever make the one instance). keyboardState.modStateFlags = curModState; // For European keyboards, not all browsers properly send both key-up events for the AltGr combo. diff --git a/web/src/engine/main/src/hardKeyboard.ts b/web/src/engine/main/src/hardKeyboard.ts index 786377fc05..0c92affd52 100644 --- a/web/src/engine/main/src/hardKeyboard.ts +++ b/web/src/engine/main/src/hardKeyboard.ts @@ -39,7 +39,9 @@ export function processForMnemonicsAndLegacy(s: KeyEvent, activeKeyboard: Keyboa // // Third: DO, however, track direct presses of any main modifier key. The OSK should // reflect the current modifier state even for legacy keyboards. - if(!activeKeyboard.definesPositionalOrMnemonic && !(s.Lmodifiers & 0x60) && !s.isModifier) { + if(!activeKeyboard.definesPositionalOrMnemonic && + !(s.Lmodifiers & Codes.modifierBitmasks.NON_LEGACY) && + !s.isModifier) { // Support version 1.0 KeymanWeb keyboards that do not define positional vs mnemonic s = new KeyEvent({ Lcode: KeyMapping._USKeyCodeToCharCode(s), diff --git a/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts b/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts index 1764eb0cf6..2efcbdf70d 100644 --- a/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts +++ b/web/src/test/auto/headless/app/browser/hardware-event-processing.tests.ts @@ -5,24 +5,586 @@ import { preprocessKeyboardEvent } from 'keyman/app/browser'; import { processForMnemonicsAndLegacy } from 'keyman/engine/main'; import { PhysicalInputEventSpec } from '@keymanapp/recorder-core'; import { DeviceSpec } from '@keymanapp/web-utils'; +import { Codes, Keyboard, KeyEvent } from '@keymanapp/keyboard-processor'; + +const ModifierCodes = Codes.modifierCodes; +const KeyCodes = Codes.keyCodes; + +const DUMMY_DEVICE = new DeviceSpec('chrome', 'desktop', 'windows', false); + +// console.log(JSON.stringify(processedEvent)); describe("app/browser: hardware event processing", () => { describe('preprocessKeyboardEvent', () => { - it('dummy', () => { - let test = preprocessKeyboardEvent(new PhysicalInputEventSpec({ - keyCode: 20, - modifierSet: PhysicalInputEventSpec.modifierCodes.Shift, - location: 1 - }) as any as KeyboardEvent, { - // Only needs to be non-null for chiral-keystroke detection and for mnemonic detection. - activeKeyboard: null, - modStateFlags: 0, - baseLayout: 'us' - }, - new DeviceSpec('chrome', 'desktop', 'windows', false) - ); + describe('positional keyboards', () => { + it("simple shifted 'a'", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_A, + modifierSet: PhysicalInputEventSpec.modifierCodes.Shift, + location: 1 + }) as any as KeyboardEvent, { + activeKeyboard: new Keyboard({ KM: 0 }), + modStateFlags: 0, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); - console.log(JSON.stringify(test)); + assert.isTrue(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, KeyCodes.K_A); + assert.equal(processedEvent.Lmodifiers, ModifierCodes.SHIFT); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + + // Note: Codes.modifierCodes.VIRTUAL_KEY is NOT applied here. It's applied by the + // compiler & filtered out directly within the `keyMatch` keyboard-API func. It's + // never part of the actual processed event object within KMW. + }); + + it("simple unshifted 'a'", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_A, + modifierSet: 0 + }) as any as KeyboardEvent, { + activeKeyboard: new Keyboard({ KM: 0 }), + modStateFlags: 0, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + assert.isTrue(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, KeyCodes.K_A); + assert.equal(processedEvent.Lmodifiers, 0); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + + it("left-alt press", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_ALT, + modifierSet: PhysicalInputEventSpec.modifierCodes.Alt, + location: 1 + }) as any as KeyboardEvent, { + // KMBM: Keyman Modifier BitMask + activeKeyboard: new Keyboard({KM: 0}), + modStateFlags: 0, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + assert.isTrue(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, KeyCodes.K_ALT); + assert.equal(processedEvent.Lmodifiers, ModifierCodes.ALT); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + + it("left-alt release", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_ALT, + modifierSet: 0, + location: 1 + }) as any as KeyboardEvent, { + // KMBM: Keyman Modifier BitMask + activeKeyboard: new Keyboard({KM: 0}), + modStateFlags: ModifierCodes.ALT, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + assert.isTrue(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, KeyCodes.K_ALT); + assert.equal(processedEvent.Lmodifiers, 0); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + }); + + describe('positional+chiral keyboards', () => { + it("simple shifted 'a'", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_A, + modifierSet: PhysicalInputEventSpec.modifierCodes.Shift, + location: 1 + }) as any as KeyboardEvent, { + // KMBM: Keyman Modifier BitMask + activeKeyboard: new Keyboard({KM:0, KMBM: Codes.modifierBitmasks.CHIRAL}), + modStateFlags: 0, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + assert.isTrue(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, KeyCodes.K_A); + assert.equal(processedEvent.Lmodifiers, ModifierCodes.SHIFT); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + + it("simple unshifted 'a'", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_A, + modifierSet: 0 + }) as any as KeyboardEvent, { + // KMBM: Keyman Modifier BitMask + activeKeyboard: new Keyboard({KM: 0, KMBM: Codes.modifierBitmasks.CHIRAL}), + modStateFlags: 0, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + assert.isTrue(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, KeyCodes.K_A); + assert.equal(processedEvent.Lmodifiers, 0); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + + it("left-alt press", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_ALT, + modifierSet: PhysicalInputEventSpec.modifierCodes.Alt, + location: 1 + }) as any as KeyboardEvent, { + // KMBM: Keyman Modifier BitMask + activeKeyboard: new Keyboard({KM: 0, KMBM: Codes.modifierBitmasks.CHIRAL}), + modStateFlags: 0, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + assert.isTrue(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, KeyCodes.K_ALT); + assert.equal(processedEvent.Lmodifiers, ModifierCodes.LALT); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + + it("left-alt release", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_ALT, + modifierSet: 0, + location: 1 + }) as any as KeyboardEvent, { + // KMBM: Keyman Modifier BitMask + activeKeyboard: new Keyboard({KM: 0, KMBM: Codes.modifierBitmasks.CHIRAL}), + modStateFlags: ModifierCodes.LALT, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + assert.isTrue(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, KeyCodes.K_ALT); + assert.equal(processedEvent.Lmodifiers, 0); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + }); + + describe('mnemonic keyboards', () => { + it("simple shifted 'a'", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_A, + modifierSet: PhysicalInputEventSpec.modifierCodes.Shift, + location: 1 + }) as any as KeyboardEvent, { + activeKeyboard: new Keyboard({ KM: 1 }), + modStateFlags: 0, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + assert.isTrue(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, 'A'.charCodeAt(0)); + assert.equal(processedEvent.Lmodifiers, ModifierCodes.SHIFT); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + + it("simple unshifted 'a'", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_A, + modifierSet: 0 + }) as any as KeyboardEvent, { + activeKeyboard: new Keyboard({ KM: 1 }), + modStateFlags: 0, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + assert.isTrue(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, 'a'.charCodeAt(0)); + assert.equal(processedEvent.Lmodifiers, 0); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + }); + + describe('legacy keyboards', () => { + it("simple shifted 'a'", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_A, + modifierSet: PhysicalInputEventSpec.modifierCodes.Shift, + location: 1 + }) as any as KeyboardEvent, { + activeKeyboard: new Keyboard({ KM: undefined }), + modStateFlags: 0, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + assert.isFalse(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, 'A'.charCodeAt(0)); + assert.equal(processedEvent.Lmodifiers, 0); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + + it("simple unshifted 'a'", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_A, + modifierSet: 0 + }) as any as KeyboardEvent, { + activeKeyboard: new Keyboard({ KM: undefined }), + modStateFlags: 0, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + assert.isFalse(processedEvent.LisVirtualKey); + assert.equal(processedEvent.Lcode, 'a'.charCodeAt(0)); + assert.equal(processedEvent.Lmodifiers, 0); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + + it("'a' during left-alt", () => { + const processedEvent = preprocessKeyboardEvent(new PhysicalInputEventSpec({ + keyCode: KeyCodes.K_A, + modifierSet: PhysicalInputEventSpec.modifierCodes.Alt, + location: 0 + }) as any as KeyboardEvent, { + activeKeyboard: new Keyboard({ KM: undefined }), + // Internally assumes that the ALT-press event was previously handled, with this as + // the resulting modifier state at the time of key-press. + modStateFlags: ModifierCodes.LALT, + baseLayout: 'us' + }, + DUMMY_DEVICE + ); + + // ... which is used to disable the key within the rule-processing `keyMatch` function, as + // the keyboard's rules weren't compiled with the Codes.VIRTUAL_KEY bit set. + assert.isTrue(processedEvent.LisVirtualKey); + + assert.equal(processedEvent.Lcode, KeyCodes.K_A); + assert.equal(processedEvent.Lmodifiers, ModifierCodes.ALT); + assert.equal(processedEvent.Lstates, ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK); + }); + }); + }); + + describe('processForMnemonicsAndLegacy', () => { + describe('positional keyboards', () => { + it("simple shifted 'a'", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_A, + Lmodifiers: ModifierCodes.SHIFT, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM: 0}), + 'us' + ); + + assert.strictEqual(finalKeyEvent, baseKeyEvent); + }); + + it("simple unshifted 'a'", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_A, + Lmodifiers: 0, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM: 0}), + 'us' + ); + + assert.strictEqual(finalKeyEvent, baseKeyEvent); + }); + + it("left-alt press", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_ALT, + Lmodifiers: ModifierCodes.ALT, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_ALT" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM: 0}), + 'us' + ); + + assert.strictEqual(finalKeyEvent, baseKeyEvent); + }); + + it("left-alt release", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_ALT, + Lmodifiers: 0, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM: 0}), + 'us' + ); + + assert.strictEqual(finalKeyEvent, baseKeyEvent); + }); + }); + + describe('positional+chiral keyboards', () => { + it("simple shifted 'a'", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_A, + Lmodifiers: ModifierCodes.SHIFT, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM:0, KMBM: Codes.modifierBitmasks.CHIRAL}), + 'us' + ); + + assert.strictEqual(finalKeyEvent, baseKeyEvent); + }); + + it("simple unshifted 'a'", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_A, + Lmodifiers: 0, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM:0, KMBM: Codes.modifierBitmasks.CHIRAL}), + 'us' + ); + + assert.strictEqual(finalKeyEvent, baseKeyEvent); + }); + + it("left-alt press", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_ALT, + Lmodifiers: ModifierCodes.LALT, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_ALT" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM:0, KMBM: Codes.modifierBitmasks.CHIRAL}), + 'us' + ); + + assert.strictEqual(finalKeyEvent, baseKeyEvent); + }); + + it("left-alt release", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_LALT, + Lmodifiers: 0, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM:0, KMBM: Codes.modifierBitmasks.CHIRAL}), + 'us' + ); + + assert.strictEqual(finalKeyEvent, baseKeyEvent); + }); + }); + + describe('mnemonic keyboards', () => { + it("simple shifted 'a'", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_A, + Lmodifiers: ModifierCodes.SHIFT, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM:1}), + 'us' + ); + + // May be the same instance, but the instance gets mutated. Compare against literals + // for expected values. + assert.equal(finalKeyEvent.Lcode, 'A'.charCodeAt(0)); + assert.equal(finalKeyEvent.Lmodifiers, ModifierCodes.SHIFT); + assert.isTrue(finalKeyEvent.LisVirtualKey); + }); + + it("simple unshifted 'a'", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_A, + Lmodifiers: 0, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM:1}), + 'us' + ); + + // May be the same instance, but the instance gets mutated. Compare against literals + // for expected values. + assert.equal(finalKeyEvent.Lcode, 'a'.charCodeAt(0)); + assert.equal(finalKeyEvent.Lmodifiers, 0); + assert.isTrue(finalKeyEvent.LisVirtualKey); + }); + }); + + describe('legacy keyboards', () => { + + it("simple shifted 'a'", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_A, + Lmodifiers: ModifierCodes.SHIFT, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM:undefined}), + 'us' + ); + + // The event object gets wholesale-replaced. + assert.notStrictEqual(finalKeyEvent, baseKeyEvent); + + assert.isFalse(finalKeyEvent.LisVirtualKey); + assert.equal(finalKeyEvent.Lcode, 'A'.charCodeAt(0)); + assert.equal(finalKeyEvent.Lmodifiers, 0); + }); + + it("simple unshifted 'a'", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_A, + Lmodifiers: 0, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM:undefined}), + 'us' + ); + + // The event object gets wholesale-replaced. + assert.notStrictEqual(finalKeyEvent, baseKeyEvent); + + assert.isFalse(finalKeyEvent.LisVirtualKey); + assert.equal(finalKeyEvent.Lcode, 'a'.charCodeAt(0)); + assert.equal(finalKeyEvent.Lmodifiers, 0); + }); + + it("'a' during alt", () => { + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_A, + Lmodifiers: ModifierCodes.ALT, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM:undefined}), + 'us' + ); + + // Legacy keyboards expect this to be false; this acts as a filter for legacy keyboards + // to prevent key processing with held CTRL / ALT. + assert.isTrue(finalKeyEvent.LisVirtualKey); + assert.equal(finalKeyEvent.Lcode, KeyCodes.K_A); + assert.equal(finalKeyEvent.Lmodifiers, ModifierCodes.ALT); + }); + + it("'a' during left-alt (invalid state)", () => { + // Not that such a state should ever occur, but if it did... we can ensure it's + // handled robustly. + const baseKeyEvent = new KeyEvent({ + Lcode: KeyCodes.K_A, + Lmodifiers: ModifierCodes.LALT, + Lstates: ModifierCodes.NO_CAPS | ModifierCodes.NO_NUM_LOCK | ModifierCodes.NO_SCROLL_LOCK, + LisVirtualKey: true, + device: DUMMY_DEVICE, + kName: "K_A" + }); + + const finalKeyEvent = processForMnemonicsAndLegacy( + baseKeyEvent, + new Keyboard({KM:undefined}), + 'us' + ); + + // Legacy keyboards expect this to be false; this acts as a filter for legacy keyboards + // to prevent key processing with held CTRL / ALT. + assert.isTrue(finalKeyEvent.LisVirtualKey); + assert.equal(finalKeyEvent.Lcode, KeyCodes.K_A); + assert.equal(finalKeyEvent.Lmodifiers, ModifierCodes.LALT); + }); }); }); }); \ No newline at end of file