From ffb614f52cd4c0efcc4daed77a8d60665dc2ec1b Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Wed, 25 Jan 2023 09:14:23 +0700 Subject: [PATCH] chore(web): post-refactor fixes to keyboard-processor, input-processor unit tests --- .../tests/cases/inputProcessor.js | 8 +- .../src/keyboards/activeLayout.ts | 9 +- .../keyboard-processor/src/text/keyEvent.ts | 64 +++++++++-- common/web/recorder/src/nodeProctor.ts | 8 +- common/web/recorder/tsconfig.json | 1 + package-lock.json | 100 ++++++++++++++++++ 6 files changed, 170 insertions(+), 20 deletions(-) diff --git a/common/web/input-processor/tests/cases/inputProcessor.js b/common/web/input-processor/tests/cases/inputProcessor.js index cff915f12b..d51c01ca89 100644 --- a/common/web/input-processor/tests/cases/inputProcessor.js +++ b/common/web/input-processor/tests/cases/inputProcessor.js @@ -96,7 +96,7 @@ describe('InputProcessor', function() { core.activeKeyboard = keyboard; let layout = keyboard.layout(utils.DeviceSpec.FormFactor.Phone); let key = layout.getLayer('default').getKey('K_A'); - let event = key.constructKeyEvent(core.keyboardProcessor, device); + let event = keyboard.constructKeyEvent(key, device, core.keyboardProcessor.stateKeys); let behavior = core.processKeyEvent(event, context); assert.isNotNull(behavior); @@ -115,7 +115,7 @@ describe('InputProcessor', function() { core.activeKeyboard = keyboard; let layout = keyboard.layout(utils.DeviceSpec.FormFactor.Phone); let key = layout.getLayer('default').getKey('K_A'); - let event = key.constructKeyEvent(core.keyboardProcessor, device); + let event = keyboard.constructKeyEvent(key, device, core.keyboardProcessor.stateKeys); let behavior = core.processKeyEvent(event, context); assert.isNotNull(behavior); @@ -132,7 +132,7 @@ describe('InputProcessor', function() { let layout = keyboard.layout(utils.DeviceSpec.FormFactor.Phone); let key = layout.getLayer('default').getKey('K_A'); key.keyDistribution = testDistribution; - let event = key.constructKeyEvent(core.keyboardProcessor, device); + let event = keyboard.constructKeyEvent(key, device, core.keyboardProcessor.stateKeys); let behavior = core.processKeyEvent(event, context); assert.isNotNull(behavior); @@ -155,7 +155,7 @@ describe('InputProcessor', function() { let layout = keyboard.layout(utils.DeviceSpec.FormFactor.Phone); let key = layout.getLayer('default').getKey('K_A'); key.keyDistribution = testDistribution; - let event = key.constructKeyEvent(core.keyboardProcessor, device); + let event = keyboard.constructKeyEvent(key, device, core.keyboardProcessor.stateKeys); let behavior = core.processKeyEvent(event, context); assert.isNotNull(behavior); diff --git a/common/web/keyboard-processor/src/keyboards/activeLayout.ts b/common/web/keyboard-processor/src/keyboards/activeLayout.ts index 7be6882181..8d4b57b6a5 100644 --- a/common/web/keyboard-processor/src/keyboards/activeLayout.ts +++ b/common/web/keyboard-processor/src/keyboards/activeLayout.ts @@ -1,5 +1,5 @@ import Codes from "../text/codes.js"; -import KeyEvent from "../text/keyEvent.js"; +import KeyEvent, { KeyEventSpec } from "../text/keyEvent.js"; import KeyMapping from "../text/keyMapping.js"; import type { KeyDistribution } from "../text/keyEvent.js"; import type { LayoutKey, LayoutRow, LayoutLayer, LayoutFormFactor, ButtonClass } from "./defaultLayouts.js"; @@ -247,8 +247,7 @@ export class ActiveKey implements LayoutKey { // Start: mirrors _GetKeyEventProperties // First check the virtual key, and process shift, control, alt or function keys - let Lkc: KeyEvent = new KeyEvent(); - let props = { + let props: KeyEventSpec = { // Override key shift state if specified for key in layout (corrected for popup keys KMEW-93) Lmodifiers: Codes.getModifierState(layer), Lstates: Codes.getStateFromLayer(layer), @@ -263,9 +262,7 @@ export class ActiveKey implements LayoutKey { isSynthetic: true }; - for(let key in props) { - Lkc[key] = props[key]; - } + let Lkc: KeyEvent = new KeyEvent(props); if(layout.keyboard) { let keyboard = layout.keyboard; diff --git a/common/web/keyboard-processor/src/text/keyEvent.ts b/common/web/keyboard-processor/src/text/keyEvent.ts index bfc32575df..17599e0ad7 100644 --- a/common/web/keyboard-processor/src/text/keyEvent.ts +++ b/common/web/keyboard-processor/src/text/keyEvent.ts @@ -16,11 +16,50 @@ import DefaultOutput from './defaultOutput.js'; // Defined here to avoid compilation issues. export type KeyDistribution = {keyId: string, p: number}[]; +export interface KeyEventSpec { + + Lcode: number; + Lstates: number; + LmodifierChange?: boolean; + Lmodifiers: number; + LisVirtualKey: boolean; + vkCode: number; + kName: string; + kLayer?: string; // The key's layer property + kbdLayer?: string; // The virtual keyboard's active layer + kNextLayer?: string; + + /** + * Marks the active keyboard at the time that this KeyEvent was generated by the user. + * + * Note: this is NOT equivalent to the active keyboard at the time that the event handler begins + * processing! It should be set via closure (or similar) on the event handler that can 100% + * guarantee that the keyboard instance known to the handler has not changed during JS execution + * since the user's interaction that raised the event. + */ + srcKeyboard?: Keyboard; + + // Holds relevant event properties leading to construction of this KeyEvent. + source?: any; // Technically, KeyEvent|MouseEvent|Touch - but those are DOM types that must be kept out of headless mode. + // Holds a generated fat-finger distribution (when appropriate) + keyDistribution?: KeyDistribution; + + /** + * The device model for web-core to follow when processing the keystroke. + */ + device: DeviceSpec; + + /** + * `true` if this event was produced by sources other than a DOM-based KeyboardEvent. + */ + isSynthetic?: boolean; +} + /** * This class is defined within its own file so that it can be loaded by code outside of KMW without * having to actually load the entirety of KMW. */ -export default class KeyEvent { +export default class KeyEvent implements KeyEventSpec { Lcode: number; Lstates: number; LmodifierChange?: boolean; @@ -57,11 +96,24 @@ export default class KeyEvent { */ isSynthetic: boolean = true; + public constructor(keyEventSpec: KeyEventSpec) { + for(let key in keyEventSpec) { + if(keyEventSpec[key] !== undefined) { + this[key] = keyEventSpec[key]; + } + } + } + public static constructNullKeyEvent(device: DeviceSpec): KeyEvent { - const keyEvent = new KeyEvent(); - keyEvent.Lcode = 0; - keyEvent.kName = ''; - keyEvent.device = device; + const keyEvent = new KeyEvent({ + Lcode: 0, + kName: '', + device: device, + Lstates: undefined, + Lmodifiers: undefined, + vkCode: undefined, + LisVirtualKey: undefined + }); return keyEvent; } @@ -86,7 +138,7 @@ export default class KeyEvent { if(this.Lcode != Codes.keyCodes['K_SPACE']) { // So long as the key name isn't prefixed with 'U_', we'll get a default mapping based on the Lcode value. // We need to determine the mnemonic base character - for example, SHIFT + K_PERIOD needs to map to '>'. - let mappingEvent: KeyEvent = new KeyEvent(); + let mappingEvent: KeyEvent = new KeyEvent(this); for(let key in (this as KeyEvent)) { mappingEvent[key] = this[key]; } diff --git a/common/web/recorder/src/nodeProctor.ts b/common/web/recorder/src/nodeProctor.ts index 6650f48d61..aaa4d30aa3 100644 --- a/common/web/recorder/src/nodeProctor.ts +++ b/common/web/recorder/src/nodeProctor.ts @@ -9,7 +9,7 @@ import { } from "./index.js"; import Keyboard from "@keymanapp/keyboard-processor/build/obj/keyboards/keyboard.js"; -import type KeyEvent from "@keymanapp/keyboard-processor/build/obj/text/keyEvent.js"; +import KeyEvent, { KeyEventSpec } from "@keymanapp/keyboard-processor/build/obj/text/keyEvent.js"; import KeyboardProcessor from "@keymanapp/keyboard-processor/build/obj/text/keyboardProcessor.js"; import type OutputTarget from "@keymanapp/keyboard-processor/build/obj/text/outputTarget.js"; import { Mock } from "@keymanapp/keyboard-processor/build/obj/text/outputTarget.js"; @@ -64,7 +64,7 @@ export default class NodeProctor extends Proctor { if(sequence instanceof RecordedKeystrokeSequence) { for(let keystroke of sequence.inputs) { - let keyEvent: KeyEvent; + let keyEvent: KeyEventSpec; if(keystroke instanceof RecordedPhysicalKeystroke) { // Use the keystroke's stored data to reconstruct the KeyEvent. keyEvent = { @@ -80,7 +80,7 @@ export default class NodeProctor extends Proctor { } } else if(keystroke instanceof RecordedSyntheticKeystroke) { let key = this.keyboard.layout(this.device.formFactor).getLayer(keystroke.layer).getKey(keystroke.keyName); - keyEvent = key.constructKeyEvent(processor, this.device); + keyEvent = this.keyboard.constructKeyEvent(key, this.device, processor.stateKeys); } // Fill in the final details of the KeyEvent... @@ -90,7 +90,7 @@ export default class NodeProctor extends Proctor { // We don't care too much about particularities of per-keystroke behavior yet. // ... we _could_ if we wanted to, though. The framework is mostly in place; // it's a matter of actually adding the feature. - let ruleBehavior = processor.processKeystroke(keyEvent, target); + let ruleBehavior = processor.processKeystroke(new KeyEvent(keyEvent), target); if(this.debugMode) { console.log(JSON.stringify(target, null, ' ')); diff --git a/common/web/recorder/tsconfig.json b/common/web/recorder/tsconfig.json index 6bd3ce6364..3e2b801a83 100644 --- a/common/web/recorder/tsconfig.json +++ b/common/web/recorder/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "allowJs": true, + "allowSyntheticDefaultImports": true, "module": "es6", "moduleResolution": "Node", "declaration": true, diff --git a/package-lock.json b/package-lock.json index 069ce484a2..952e6fc9f3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2247,6 +2247,33 @@ "color-support": "bin.js" } }, + "node_modules/combine-source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "integrity": "sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==", + "dev": true, + "dependencies": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + } + }, + "node_modules/combine-source-map/node_modules/convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==", + "dev": true + }, + "node_modules/combine-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -3980,6 +4007,24 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "node_modules/inline-source-map": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha512-0mVWSSbNDvedDWIN4wxLsdPM4a7cIPcpyMxj3QZ406QRwQ6ePGB1YIHxVPjqpcUGbWQ5C+nHTwGNWAGvt7ggVA==", + "dev": true, + "dependencies": { + "source-map": "~0.5.3" + } + }, + "node_modules/inline-source-map/node_modules/source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -4580,6 +4625,12 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, + "node_modules/lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==", + "dev": true + }, "node_modules/lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz", @@ -9309,6 +9360,32 @@ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", "dev": true }, + "combine-source-map": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/combine-source-map/-/combine-source-map-0.8.0.tgz", + "integrity": "sha512-UlxQ9Vw0b/Bt/KYwCFqdEwsQ1eL8d1gibiFb7lxQJFdvTgc2hIZi6ugsg+kyhzhPV+QEpUiEIwInIAIrgoEkrg==", + "dev": true, + "requires": { + "convert-source-map": "~1.1.0", + "inline-source-map": "~0.6.0", + "lodash.memoize": "~3.0.3", + "source-map": "~0.5.3" + }, + "dependencies": { + "convert-source-map": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.1.3.tgz", + "integrity": "sha512-Y8L5rp6jo+g9VEPgvqNfEopjTR4OTYct8lXlS8iVQdmnjDvbdbzYe9rjtFCB9egC86JoNCU61WRY+ScjkZpnIg==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + } + } + }, "combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -10556,6 +10633,23 @@ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, + "inline-source-map": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", + "integrity": "sha512-0mVWSSbNDvedDWIN4wxLsdPM4a7cIPcpyMxj3QZ406QRwQ6ePGB1YIHxVPjqpcUGbWQ5C+nHTwGNWAGvt7ggVA==", + "dev": true, + "requires": { + "source-map": "~0.5.3" + }, + "dependencies": { + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", + "dev": true + } + } + }, "ipaddr.js": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", @@ -11073,6 +11167,12 @@ "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" }, + "lodash.memoize": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", + "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==", + "dev": true + }, "lodash.set": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/lodash.set/-/lodash.set-4.3.2.tgz",