diff --git a/common/web/recorder/build.sh b/common/web/recorder/build.sh
index fb566a18c8..3bee5dc024 100755
--- a/common/web/recorder/build.sh
+++ b/common/web/recorder/build.sh
@@ -22,16 +22,11 @@ builder_describe \
"@../keyman-version" \
configure \
clean \
- build \
- ":module Builds recorder-core module" \
- ":proctor Builds headless-testing, node-oriented 'proctor' component"
+ build
builder_describe_outputs \
- configure "/node_modules" \
- configure:module "/node_modules" \
- configure:proctor "/node_modules" \
- build:module "build/index.js" \
- build:proctor "build/nodeProctor/index.js"
+ configure "/node_modules" \
+ build "build/index.js"
builder_parse "$@"
@@ -40,22 +35,12 @@ if builder_start_action configure; then
builder_finish_action success configure
fi
-if builder_start_action clean:module; then
- npm run tsc -- -b --clean "$THIS_SCRIPT_PATH/src/tsconfig.json"
- builder_finish_action success clean:module
+if builder_start_action clean; then
+ npm run tsc -- -b --clean "$THIS_SCRIPT_PATH/tsconfig.json"
+ builder_finish_action success clean
fi
-if builder_start_action clean:proctor; then
- npm run tsc -- -b --clean "$THIS_SCRIPT_PATH/src/nodeProctor.tsconfig.json"
- builder_finish_action success clean:proctor
-fi
-
-if builder_start_action build:module; then
- npm run tsc -- --build "$THIS_SCRIPT_PATH/src/tsconfig.json"
- builder_finish_action success build:module
-fi
-
-if builder_start_action build:proctor; then
- npm run tsc -- --build "$THIS_SCRIPT_PATH/src/nodeProctor.tsconfig.json"
- builder_finish_action success build:proctor
+if builder_start_action build; then
+ npm run tsc -- --build "$THIS_SCRIPT_PATH/tsconfig.json"
+ builder_finish_action success build
fi
\ No newline at end of file
diff --git a/common/web/recorder/src/index.ts b/common/web/recorder/src/index.ts
index d01dcad71b..f845891df9 100644
--- a/common/web/recorder/src/index.ts
+++ b/common/web/recorder/src/index.ts
@@ -1,774 +1,780 @@
///
-namespace KMWRecorder {
- //#region Defines the InputEventSpec set, used to reconstruct DOM-based events for browser-based simulation
- export abstract class InputEventSpec {
- type: "key" | "osk";
- static fromJSONObject(obj: any): InputEventSpec {
- if(obj && obj.type) {
- if(obj.type == "key") {
- return new PhysicalInputEventSpec(obj);
- } else if(obj.type == "osk") {
- return new OSKInputEventSpec(obj);
- }
- } else {
- throw new SyntaxError("Error in JSON format corresponding to an InputEventSpec!");
- }
- }
+import KeyEvent, { KeyDistribution } from "keyboard-processor/build/modules/text/keyEvent.js";
+import type OutputTarget from "keyboard-processor/build/modules/text/outputTarget.js";
+import { Mock } from "keyboard-processor/build/modules/text/outputTarget.js";
- toPrettyJSON(): string {
- // We want the default, non-spaced JSON for this class, even when otherwise adding whitespace.
- var str = JSON.stringify(this);
- return str;
+import Proctor from "./proctor.js";
+
+import * as utils from "utils/build/modules/index.js";
+
+//#region Defines the InputEventSpec set, used to reconstruct DOM-based events for browser-based simulation
+export abstract class InputEventSpec {
+ type: "key" | "osk";
+ static fromJSONObject(obj: any): InputEventSpec {
+ if(obj && obj.type) {
+ if(obj.type == "key") {
+ return new PhysicalInputEventSpec(obj);
+ } else if(obj.type == "osk") {
+ return new OSKInputEventSpec(obj);
+ }
+ } else {
+ throw new SyntaxError("Error in JSON format corresponding to an InputEventSpec!");
}
}
- export class PhysicalInputEventSpec extends InputEventSpec {
- static readonly modifierCodes: { [mod:string]: number } = {
- "Shift":0x0001,
- "Control":0x0002,
- "Alt":0x0004,
- "Meta":0x0008,
- "CapsLock":0x0010,
- "NumLock":0x0020,
- "ScrollLock":0x0040
- };
+ toPrettyJSON(): string {
+ // We want the default, non-spaced JSON for this class, even when otherwise adding whitespace.
+ var str = JSON.stringify(this);
+ return str;
+ }
+}
- // KeyboardEvent properties
- type: "key" = "key";
- key: string;
- code: string;
- keyCode: number;
- modifierSet: number;
- location: number;
+export class PhysicalInputEventSpec extends InputEventSpec {
+ static readonly modifierCodes: { [mod:string]: number } = {
+ "Shift":0x0001,
+ "Control":0x0002,
+ "Alt":0x0004,
+ "Meta":0x0008,
+ "CapsLock":0x0010,
+ "NumLock":0x0020,
+ "ScrollLock":0x0040
+ };
- constructor(e?: PhysicalInputEventSpec) { // parameter is used to reconstruct from JSON.
- super();
+ // KeyboardEvent properties
+ type: "key" = "key";
+ key: string;
+ code: string;
+ keyCode: number;
+ modifierSet: number;
+ location: number;
- if(e) {
- this.key = e.key;
- this.code = e.code;
- this.keyCode = e.keyCode;
- this.modifierSet = e.modifierSet;
- this.location = e.location;
- }
- }
+ constructor(e?: PhysicalInputEventSpec) { // parameter is used to reconstruct from JSON.
+ super();
- getModifierState(key: string): boolean {
- return (PhysicalInputEventSpec.modifierCodes[key] & this.modifierSet) != 0;
- }
-
- generateModifierString(): string {
- var list: string = "";
-
- for(var key in PhysicalInputEventSpec.modifierCodes) {
- if(this.getModifierState(key)) {
- list += ((list != "" ? " " : "") + key);
- }
- }
-
- return list;
+ if(e) {
+ this.key = e.key;
+ this.code = e.code;
+ this.keyCode = e.keyCode;
+ this.modifierSet = e.modifierSet;
+ this.location = e.location;
}
}
- export class OSKInputEventSpec extends InputEventSpec {
- type: "osk" = "osk";
- keyID: string;
-
- // The parameter may be used to reconstruct the item from raw JSON.
- constructor(e?: OSKInputEventSpec) {
- super();
- if(e) {
- this.keyID = e.keyID;
- }
- }
- }
- //#endregion
-
- export abstract class RecordedKeystroke {
- type: "key" | "osk";
-
- static fromJSONObject(obj: any): RecordedKeystroke {
- if(obj && obj.type) {
- if(obj.type == "key") {
- return new RecordedPhysicalKeystroke(obj as RecordedPhysicalKeystroke);
- } else if(obj && obj.type) {
- return new RecordedSyntheticKeystroke(obj as RecordedSyntheticKeystroke);
- }
- } else {
- throw new SyntaxError("Error in JSON format corresponding to a RecordedKeystroke!");
- }
- }
-
- toPrettyJSON(): string {
- // We want the default, non-spaced JSON for this class, even when otherwise adding whitespace.
- var str = JSON.stringify(this);
- return str;
- }
-
- /**
- * Returns an InputEventSpec that may be used to simulate the keystroke within a browser-based environment.
- */
- abstract get inputEventSpec(): InputEventSpec;
+ getModifierState(key: string): boolean {
+ return (PhysicalInputEventSpec.modifierCodes[key] & this.modifierSet) != 0;
}
- export class RecordedPhysicalKeystroke extends RecordedKeystroke {
- // KeyboardEvent properties
- type: "key" = "key";
+ generateModifierString(): string {
+ var list: string = "";
- keyCode: number; // may be different from eventSpec's value b/c keymapping
- states: number;
- modifiers: number;
- modifierChanged: boolean;
- isVirtualKey: boolean;
- vkCode: number; // may be possible to eliminate; differences arise from mnemonics.
-
- eventSpec: PhysicalInputEventSpec;
-
- constructor(keystroke: RecordedPhysicalKeystroke)
- constructor(keystroke: com.keyman.text.KeyEvent, eventSpec: PhysicalInputEventSpec)
- constructor(keystroke: RecordedPhysicalKeystroke|com.keyman.text.KeyEvent, eventSpec?: PhysicalInputEventSpec) {
- super();
-
- if(keystroke instanceof com.keyman.text.KeyEvent || typeof keystroke.type === 'undefined') {
- // Store what is necessary for headless event reconstruction.
- keystroke = keystroke as com.keyman.text.KeyEvent;
- this.keyCode = keystroke.Lcode;
- this.states = keystroke.Lstates;
- this.modifiers = keystroke.Lmodifiers;
- this.modifierChanged = !!keystroke.LmodifierChange;
- this.isVirtualKey = keystroke.LisVirtualKey;
- this.vkCode = keystroke.vkCode;
-
- // Also store the DOM-based event spec for use in integrated testing.
- this.eventSpec = eventSpec;
- } else {
- // It might be a raw object, from JSON.
- this.keyCode = keystroke.keyCode;
- this.states = keystroke.states;
- this.modifiers = keystroke.modifiers;
- this.modifierChanged = keystroke.modifierChanged;
- this.isVirtualKey = keystroke.isVirtualKey;
- this.vkCode = keystroke.vkCode;
-
- this.eventSpec = new PhysicalInputEventSpec(keystroke.eventSpec); // must also be reconstructed.
+ for(var key in PhysicalInputEventSpec.modifierCodes) {
+ if(this.getModifierState(key)) {
+ list += ((list != "" ? " " : "") + key);
}
}
- get inputEventSpec(): InputEventSpec {
- return this.eventSpec;
+ return list;
+ }
+}
+
+export class OSKInputEventSpec extends InputEventSpec {
+ type: "osk" = "osk";
+ keyID: string;
+
+ // The parameter may be used to reconstruct the item from raw JSON.
+ constructor(e?: OSKInputEventSpec) {
+ super();
+ if(e) {
+ this.keyID = e.keyID;
+ }
+ }
+}
+//#endregion
+
+export abstract class RecordedKeystroke {
+ type: "key" | "osk";
+
+ static fromJSONObject(obj: any): RecordedKeystroke {
+ if(obj && obj.type) {
+ if(obj.type == "key") {
+ return new RecordedPhysicalKeystroke(obj as RecordedPhysicalKeystroke);
+ } else if(obj && obj.type) {
+ return new RecordedSyntheticKeystroke(obj as RecordedSyntheticKeystroke);
+ }
+ } else {
+ throw new SyntaxError("Error in JSON format corresponding to a RecordedKeystroke!");
}
}
- export class RecordedSyntheticKeystroke extends RecordedKeystroke {
- // KeyboardEvent properties
- type: "osk" = "osk";
-
- keyName: string;
- layer: string;
-
- keyDistribution?: com.keyman.text.KeyDistribution;
-
- constructor(keystroke: RecordedSyntheticKeystroke)
- constructor(keystroke: com.keyman.text.KeyEvent)
- constructor(keystroke: RecordedSyntheticKeystroke|com.keyman.text.KeyEvent) {
- super();
-
- if(keystroke instanceof com.keyman.text.KeyEvent || typeof keystroke.type === 'undefined') {
- keystroke = keystroke as com.keyman.text.KeyEvent;
- // Store what is necessary for headless event reconstruction.
-
- // Also store the DOM-based event spec for use in integrated testing.
- this.layer = keystroke.kbdLayer;
- this.keyName = keystroke.kName;
- this.keyDistribution = keystroke.keyDistribution;
- } else {
- // It might be a raw object, from JSON.
- this.layer = keystroke.layer;
- this.keyName = keystroke.keyName;
- this.keyDistribution = keystroke.keyDistribution;
- }
- }
-
- get inputEventSpec(): InputEventSpec {
- let eventSpec = new OSKInputEventSpec();
- eventSpec.keyID = this.layer + '-' + this.keyName;
-
- return eventSpec;
- }
- }
-
- export abstract class TestSequence {
- inputs: KeyRecord[];
- output: string;
- msg?: string;
-
- abstract hasOSKInteraction(): boolean;
-
- test(proctor: Proctor, target?: com.keyman.text.OutputTarget): {success: boolean, result: string} {
- // Start with an empty OutputTarget and a fresh KeyboardProcessor.
- if(!target) {
- target = new com.keyman.text.Mock();
- }
-
- proctor.before();
-
- let result = proctor.simulateSequence(this, target);
- proctor.assertEquals(result, this.output, this.msg);
-
- return {success: (result == this.output), result: result};
- }
-
- toPrettyJSON(): string {
- var str = "{ ";
- if(this.output) {
- str += "\"output\": \"" + this.output + "\", ";
- }
- str += "\"inputs\": [\n";
- for(var i = 0; i < this.inputs.length; i++) {
- str += " " + this.inputs[i].toPrettyJSON() + ((i == this.inputs.length-1) ? "\n" : ",\n");
- }
- if(this.msg) {
- str += "], \"message\": \"" + this.msg + "\" }";
- } else {
- str += "]}";
- }
- return str;
- }
- }
-
- export class InputEventSpecSequence extends TestSequence {
- inputs: InputEventSpec[];
- output: string;
- msg?: string;
-
- constructor(ins?: InputEventSpec[] | InputEventSpecSequence, outs?: string, msg?: string) {
- super();
-
- if(ins) {
- if(ins instanceof Array) {
- this.inputs = [].concat(ins);
- } else {
- // We're constructing from existing JSON.
- this.inputs = [];
-
- for(var ie=0; ie < ins.inputs.length; ie++) {
- this.inputs.push(InputEventSpec.fromJSONObject(ins.inputs[ie]));
- }
-
- this.output = ins.output;
- this.msg = ins.msg;
- return;
- }
- } else {
- this.inputs = [];
- }
-
- if(outs) {
- this.output = outs;
- }
-
- if(msg) {
- this.msg = msg;
- }
- }
-
- addInput(event: InputEventSpec, output: string) {
- this.inputs.push(event);
- this.output = output;
- }
-
- hasOSKInteraction(): boolean {
- for(var i=0; i < this.inputs.length; i++) {
- if(this.inputs[i] instanceof OSKInputEventSpec) {
- return true;
- }
- }
-
- return false;
- }
- }
-
- export class RecordedKeystrokeSequence extends TestSequence {
- inputs: RecordedKeystroke[];
- output: string;
- msg?: string;
-
- constructor(ins?: RecordedKeystroke[], outs?: string, msg?: string)
- constructor(sequence: RecordedKeystrokeSequence)
- constructor(ins?: RecordedKeystroke[] | RecordedKeystrokeSequence, outs?: string, msg?: string) {
- super();
-
- if(ins) {
- if(ins instanceof Array) {
- this.inputs = [].concat(ins);
- } else {
- // We're constructing from existing JSON.
- this.inputs = [];
-
- for(var ie=0; ie < ins.inputs.length; ie++) {
- this.inputs.push(RecordedKeystroke.fromJSONObject(ins.inputs[ie]));
- }
-
- this.output = ins.output;
- this.msg = ins.msg;
- return;
- }
- } else {
- this.inputs = [];
- }
-
- if(outs) {
- this.output = outs;
- }
-
- if(msg) {
- this.msg = msg;
- }
- }
-
- addInput(event: RecordedKeystroke, output: string) {
- this.inputs.push(event);
- this.output = output;
- }
-
- hasOSKInteraction(): boolean {
- for(var i=0; i < this.inputs.length; i++) {
- if(this.inputs[i] instanceof RecordedSyntheticKeystroke) {
- return true;
- }
- }
-
- return false;
- }
- }
-
- class FontStubForLanguage {
- family: string;
- source: string[];
-
- constructor(activeStubEntry: any) {
- this.family = activeStubEntry.family;
-
- var src = activeStubEntry.files;
- if(!(src instanceof Array)) {
- src = [ src ];
- }
-
- this.source = [];
- for(var i=0; i < src.length; i++) {
- this.source.push(activeStubEntry.path + src[i]);
- }
- }
- }
-
- export class LanguageStubForKeyboard {
- id: string;
- name: string;
- region: string;
- font?: FontStubForLanguage;
- oskFont?: FontStubForLanguage;
-
- constructor(activeStub: any) {
- if(activeStub.KLC) {
- this.id = activeStub.KLC;
- this.name = activeStub.KL;
- this.region = activeStub.KR;
-
- // Fonts.
- if(activeStub.KFont) {
- this.font = new FontStubForLanguage(activeStub.KFont);
- }
- if(activeStub.KOskFont) {
- this.oskFont = new FontStubForLanguage(activeStub.KOskFont);
- }
- } else {
- this.id = activeStub.id;
- this.name = activeStub.name;
- this.region = activeStub.region;
-
- // If we end up adding functionality to FontStubForLanguage, we'll need to properly reconstruct these.
- this.font = activeStub.font;
- this.oskFont = activeStub.oskFont;
- }
- }
- }
-
- export class KeyboardStub {
- id: string;
- name: string;
- filename: string;
- languages: LanguageStubForKeyboard | LanguageStubForKeyboard[];
-
- // Constructs a stub usable with KeymanWeb's addKeyboards() API function from
- // the internally-tracked ActiveStub value for that keyboard.
- constructor(json?: KeyboardStub) {
- if(json) {
- this.id = json.id;
- this.name = json.name;
- this.filename = json.filename;
-
- if(!Array.isArray(json.languages)) {
- this.languages = new LanguageStubForKeyboard(json.languages);
- } else {
- this.languages = [];
- for(var i=0; i < json.languages.length; i++) {
- this.languages.push(new LanguageStubForKeyboard(json.languages[i]));
- }
- }
- }
- }
-
- getFirstLanguage() {
- if(this.languages instanceof LanguageStubForKeyboard) {
- return this.languages.id;
- } else {
- return this.languages[0].id;
- }
- }
- }
-
- type TARGET = 'hardware'|'desktop'|'phone'|'tablet';
- type OS = 'windows'|'android'|'ios'|'macosx'|'linux';
- type BROWSER = 'chrome'|'firefox'|'safari'|'opera'; // ! no 'edge' detection in KMW!
-
- export class Constraint {
- target: TARGET;
- validOSList?: OS[];
- validBrowsers?: BROWSER[];
-
- constructor(target: TARGET|Constraint, validOSList?: OS[], validBrowsers?: BROWSER[]) {
- if(typeof(target) == 'string') {
- this.target = target;
- this.validOSList = validOSList;
- this.validBrowsers = validBrowsers;
- } else {
- var json = target;
- this.target = json.target;
- this.validOSList = json.validOSList;
- this.validBrowsers = json.validBrowsers;
- }
- }
-
- matchesClient(device: com.keyman.utils.DeviceSpec, usingOSK?: boolean) {
- // #1: Platform check.
- if(usingOSK === true) {
- if(this.target != device.formFactor) {
- return false;
- }
- } else if(usingOSK === false) {
- if(this.target != 'hardware') {
- return false;
- }
- } else if(this.target != device.formFactor && this.target != 'hardware') {
- return false;
- }
-
- if(this.validOSList) {
- if(this.validOSList.indexOf(device.OS as OS) == -1) {
- return false;
- }
- }
-
- if(this.validBrowsers) {
- if(this.validBrowsers.indexOf(device.browser as BROWSER) == -1) {
- return false;
- }
- }
-
- return true;
- }
-
- // Checks if another Constraint instance is functionally identical to this one.
- equals(other: Constraint) {
- if(this.target != other.target) {
- return false;
- }
-
- var list1 = this.validOSList ? this.validOSList : ['any'];
- var list2 = other.validOSList ? other.validOSList : ['any'];
-
- if(list1.sort().join(',') != list2.sort().join(',')) {
- return false;
- }
-
- list1 = this.validBrowsers ? this.validBrowsers : ['web'];
- list2 = other.validBrowsers ? other.validBrowsers : ['web'];
-
- if(list1.sort().join(',') != list2.sort().join(',')) {
- return false;
- }
-
- return true;
- }
- }
-
- export class TestFailure {
- constraint: Constraint;
- test: InputEventSpecSequence;
- result: string;
-
- constructor(constraint: Constraint, test: InputEventSpecSequence, output: string) {
- this.constraint = constraint;
- this.test = test;
- this.result = output;
- }
- }
-
- export interface TestSet> {
- constraint: Constraint;
-
- addTest(seq: Sequence): void;
- isValidForDevice(device: com.keyman.utils.DeviceSpec, usingOSK?: boolean): boolean;
- test(proctor: Proctor): TestFailure[];
+ toPrettyJSON(): string {
+ // We want the default, non-spaced JSON for this class, even when otherwise adding whitespace.
+ var str = JSON.stringify(this);
+ return str;
}
/**
- * The core constraint-specific test set definition used for testing versions 10.0 to 13.0.
+ * Returns an InputEventSpec that may be used to simulate the keystroke within a browser-based environment.
*/
- export class EventSpecTestSet implements TestSet {
- constraint: Constraint;
- testSet: InputEventSpecSequence[];
+ abstract get inputEventSpec(): InputEventSpec;
+}
- constructor(constraint: Constraint|EventSpecTestSet) {
- if("target" in constraint) {
- this.constraint = constraint as Constraint;
- this.testSet = [];
- } else {
- var json = constraint as EventSpecTestSet;
- this.constraint = new Constraint(json.constraint);
- this.testSet = [];
+export class RecordedPhysicalKeystroke extends RecordedKeystroke {
+ // KeyboardEvent properties
+ type: "key" = "key";
- // Clone each test sequence / reconstruct from methodless JSON object.
- for(var i=0; i < json.testSet.length; i++) {
- this.testSet.push(new InputEventSpecSequence(json.testSet[i]));
- }
- }
- }
+ keyCode: number; // may be different from eventSpec's value b/c keymapping
+ states: number;
+ modifiers: number;
+ modifierChanged: boolean;
+ isVirtualKey: boolean;
+ vkCode: number; // may be possible to eliminate; differences arise from mnemonics.
- addTest(seq: InputEventSpecSequence) {
- this.testSet.push(seq);
- }
+ eventSpec: PhysicalInputEventSpec;
- // Used to determine if the current EventSpecTestSet is applicable to be run on a device.
- isValidForDevice(device: com.keyman.utils.DeviceSpec, usingOSK?: boolean) {
- return this.constraint.matchesClient(device, usingOSK);
- }
+ constructor(keystroke: RecordedPhysicalKeystroke)
+ constructor(keystroke: KeyEvent, eventSpec: PhysicalInputEventSpec)
+ constructor(keystroke: RecordedPhysicalKeystroke|KeyEvent, eventSpec?: PhysicalInputEventSpec) {
+ super();
- // Validity should be checked before calling this method.
- test(proctor: Proctor): TestFailure[] {
- var failures: TestFailure[] = [];
- let testSet = this.testSet;
+ if(keystroke instanceof KeyEvent || typeof keystroke.type === 'undefined') {
+ // Store what is necessary for headless event reconstruction.
+ keystroke = keystroke as KeyEvent;
+ this.keyCode = keystroke.Lcode;
+ this.states = keystroke.Lstates;
+ this.modifiers = keystroke.Lmodifiers;
+ this.modifierChanged = !!keystroke.LmodifierChange;
+ this.isVirtualKey = keystroke.LisVirtualKey;
+ this.vkCode = keystroke.vkCode;
- for(var i=0; i < testSet.length; i++) {
- var testSeq = this[i];
- var simResult = testSet[i].test(proctor);
- if(!simResult.success) {
- // Failed test!
- failures.push(new TestFailure(this.constraint, testSeq, simResult.result));
- }
- }
+ // Also store the DOM-based event spec for use in integrated testing.
+ this.eventSpec = eventSpec;
+ } else {
+ // It might be a raw object, from JSON.
+ this.keyCode = keystroke.keyCode;
+ this.states = keystroke.states;
+ this.modifiers = keystroke.modifiers;
+ this.modifierChanged = keystroke.modifierChanged;
+ this.isVirtualKey = keystroke.isVirtualKey;
+ this.vkCode = keystroke.vkCode;
- return failures.length > 0 ? failures : null;
+ this.eventSpec = new PhysicalInputEventSpec(keystroke.eventSpec); // must also be reconstructed.
}
}
+ get inputEventSpec(): InputEventSpec {
+ return this.eventSpec;
+ }
+}
+
+export class RecordedSyntheticKeystroke extends RecordedKeystroke {
+ // KeyboardEvent properties
+ type: "osk" = "osk";
+
+ keyName: string;
+ layer: string;
+
+ keyDistribution?: KeyDistribution;
+
+ constructor(keystroke: RecordedSyntheticKeystroke)
+ constructor(keystroke: KeyEvent)
+ constructor(keystroke: RecordedSyntheticKeystroke|KeyEvent) {
+ super();
+
+ if(keystroke instanceof KeyEvent || typeof keystroke.type === 'undefined') {
+ keystroke = keystroke as KeyEvent;
+ // Store what is necessary for headless event reconstruction.
+
+ // Also store the DOM-based event spec for use in integrated testing.
+ this.layer = keystroke.kbdLayer;
+ this.keyName = keystroke.kName;
+ this.keyDistribution = keystroke.keyDistribution;
+ } else {
+ // It might be a raw object, from JSON.
+ this.layer = keystroke.layer;
+ this.keyName = keystroke.keyName;
+ this.keyDistribution = keystroke.keyDistribution;
+ }
+ }
+
+ get inputEventSpec(): InputEventSpec {
+ let eventSpec = new OSKInputEventSpec();
+ eventSpec.keyID = this.layer + '-' + this.keyName;
+
+ return eventSpec;
+ }
+}
+
+export abstract class TestSequence {
+ inputs: KeyRecord[];
+ output: string;
+ msg?: string;
+
+ abstract hasOSKInteraction(): boolean;
+
+ test(proctor: Proctor, target?: OutputTarget): {success: boolean, result: string} {
+ // Start with an empty OutputTarget and a fresh KeyboardProcessor.
+ if(!target) {
+ target = new Mock();
+ }
+
+ proctor.before();
+
+ let result = proctor.simulateSequence(this, target);
+ proctor.assertEquals(result, this.output, this.msg);
+
+ return {success: (result == this.output), result: result};
+ }
+
+ toPrettyJSON(): string {
+ var str = "{ ";
+ if(this.output) {
+ str += "\"output\": \"" + this.output + "\", ";
+ }
+ str += "\"inputs\": [\n";
+ for(var i = 0; i < this.inputs.length; i++) {
+ str += " " + this.inputs[i].toPrettyJSON() + ((i == this.inputs.length-1) ? "\n" : ",\n");
+ }
+ if(this.msg) {
+ str += "], \"message\": \"" + this.msg + "\" }";
+ } else {
+ str += "]}";
+ }
+ return str;
+ }
+}
+
+export class InputEventSpecSequence extends TestSequence {
+ inputs: InputEventSpec[];
+ output: string;
+ msg?: string;
+
+ constructor(ins?: InputEventSpec[] | InputEventSpecSequence, outs?: string, msg?: string) {
+ super();
+
+ if(ins) {
+ if(ins instanceof Array) {
+ this.inputs = [].concat(ins);
+ } else {
+ // We're constructing from existing JSON.
+ this.inputs = [];
+
+ for(var ie=0; ie < ins.inputs.length; ie++) {
+ this.inputs.push(InputEventSpec.fromJSONObject(ins.inputs[ie]));
+ }
+
+ this.output = ins.output;
+ this.msg = ins.msg;
+ return;
+ }
+ } else {
+ this.inputs = [];
+ }
+
+ if(outs) {
+ this.output = outs;
+ }
+
+ if(msg) {
+ this.msg = msg;
+ }
+ }
+
+ addInput(event: InputEventSpec, output: string) {
+ this.inputs.push(event);
+ this.output = output;
+ }
+
+ hasOSKInteraction(): boolean {
+ for(var i=0; i < this.inputs.length; i++) {
+ if(this.inputs[i] instanceof OSKInputEventSpec) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
+
+export class RecordedKeystrokeSequence extends TestSequence {
+ inputs: RecordedKeystroke[];
+ output: string;
+ msg?: string;
+
+ constructor(ins?: RecordedKeystroke[], outs?: string, msg?: string)
+ constructor(sequence: RecordedKeystrokeSequence)
+ constructor(ins?: RecordedKeystroke[] | RecordedKeystrokeSequence, outs?: string, msg?: string) {
+ super();
+
+ if(ins) {
+ if(ins instanceof Array) {
+ this.inputs = [].concat(ins);
+ } else {
+ // We're constructing from existing JSON.
+ this.inputs = [];
+
+ for(var ie=0; ie < ins.inputs.length; ie++) {
+ this.inputs.push(RecordedKeystroke.fromJSONObject(ins.inputs[ie]));
+ }
+
+ this.output = ins.output;
+ this.msg = ins.msg;
+ return;
+ }
+ } else {
+ this.inputs = [];
+ }
+
+ if(outs) {
+ this.output = outs;
+ }
+
+ if(msg) {
+ this.msg = msg;
+ }
+ }
+
+ addInput(event: RecordedKeystroke, output: string) {
+ this.inputs.push(event);
+ this.output = output;
+ }
+
+ hasOSKInteraction(): boolean {
+ for(var i=0; i < this.inputs.length; i++) {
+ if(this.inputs[i] instanceof RecordedSyntheticKeystroke) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+}
+
+class FontStubForLanguage {
+ family: string;
+ source: string[];
+
+ constructor(activeStubEntry: any) {
+ this.family = activeStubEntry.family;
+
+ var src = activeStubEntry.files;
+ if(!(src instanceof Array)) {
+ src = [ src ];
+ }
+
+ this.source = [];
+ for(var i=0; i < src.length; i++) {
+ this.source.push(activeStubEntry.path + src[i]);
+ }
+ }
+}
+
+export class LanguageStubForKeyboard {
+ id: string;
+ name: string;
+ region: string;
+ font?: FontStubForLanguage;
+ oskFont?: FontStubForLanguage;
+
+ constructor(activeStub: any) {
+ if(activeStub.KLC) {
+ this.id = activeStub.KLC;
+ this.name = activeStub.KL;
+ this.region = activeStub.KR;
+
+ // Fonts.
+ if(activeStub.KFont) {
+ this.font = new FontStubForLanguage(activeStub.KFont);
+ }
+ if(activeStub.KOskFont) {
+ this.oskFont = new FontStubForLanguage(activeStub.KOskFont);
+ }
+ } else {
+ this.id = activeStub.id;
+ this.name = activeStub.name;
+ this.region = activeStub.region;
+
+ // If we end up adding functionality to FontStubForLanguage, we'll need to properly reconstruct these.
+ this.font = activeStub.font;
+ this.oskFont = activeStub.oskFont;
+ }
+ }
+}
+
+export class KeyboardStub {
+ id: string;
+ name: string;
+ filename: string;
+ languages: LanguageStubForKeyboard | LanguageStubForKeyboard[];
+
+ // Constructs a stub usable with KeymanWeb's addKeyboards() API function from
+ // the internally-tracked ActiveStub value for that keyboard.
+ constructor(json?: KeyboardStub) {
+ if(json) {
+ this.id = json.id;
+ this.name = json.name;
+ this.filename = json.filename;
+
+ if(!Array.isArray(json.languages)) {
+ this.languages = new LanguageStubForKeyboard(json.languages);
+ } else {
+ this.languages = [];
+ for(var i=0; i < json.languages.length; i++) {
+ this.languages.push(new LanguageStubForKeyboard(json.languages[i]));
+ }
+ }
+ }
+ }
+
+ getFirstLanguage() {
+ if(this.languages instanceof LanguageStubForKeyboard) {
+ return this.languages.id;
+ } else {
+ return this.languages[0].id;
+ }
+ }
+}
+
+type TARGET = 'hardware'|'desktop'|'phone'|'tablet';
+type OS = 'windows'|'android'|'ios'|'macosx'|'linux';
+type BROWSER = 'chrome'|'firefox'|'safari'|'opera'; // ! no 'edge' detection in KMW!
+
+export class Constraint {
+ target: TARGET;
+ validOSList?: OS[];
+ validBrowsers?: BROWSER[];
+
+ constructor(target: TARGET|Constraint, validOSList?: OS[], validBrowsers?: BROWSER[]) {
+ if(typeof(target) == 'string') {
+ this.target = target;
+ this.validOSList = validOSList;
+ this.validBrowsers = validBrowsers;
+ } else {
+ var json = target;
+ this.target = json.target;
+ this.validOSList = json.validOSList;
+ this.validBrowsers = json.validBrowsers;
+ }
+ }
+
+ matchesClient(device: utils.DeviceSpec, usingOSK?: boolean) {
+ // #1: Platform check.
+ if(usingOSK === true) {
+ if(this.target != device.formFactor) {
+ return false;
+ }
+ } else if(usingOSK === false) {
+ if(this.target != 'hardware') {
+ return false;
+ }
+ } else if(this.target != device.formFactor && this.target != 'hardware') {
+ return false;
+ }
+
+ if(this.validOSList) {
+ if(this.validOSList.indexOf(device.OS as OS) == -1) {
+ return false;
+ }
+ }
+
+ if(this.validBrowsers) {
+ if(this.validBrowsers.indexOf(device.browser as BROWSER) == -1) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ // Checks if another Constraint instance is functionally identical to this one.
+ equals(other: Constraint) {
+ if(this.target != other.target) {
+ return false;
+ }
+
+ var list1 = this.validOSList ? this.validOSList : ['any'];
+ var list2 = other.validOSList ? other.validOSList : ['any'];
+
+ if(list1.sort().join(',') != list2.sort().join(',')) {
+ return false;
+ }
+
+ list1 = this.validBrowsers ? this.validBrowsers : ['web'];
+ list2 = other.validBrowsers ? other.validBrowsers : ['web'];
+
+ if(list1.sort().join(',') != list2.sort().join(',')) {
+ return false;
+ }
+
+ return true;
+ }
+}
+
+export class TestFailure {
+ constraint: Constraint;
+ test: InputEventSpecSequence;
+ result: string;
+
+ constructor(constraint: Constraint, test: InputEventSpecSequence, output: string) {
+ this.constraint = constraint;
+ this.test = test;
+ this.result = output;
+ }
+}
+
+export interface TestSet> {
+ constraint: Constraint;
+
+ addTest(seq: Sequence): void;
+ isValidForDevice(device: utils.DeviceSpec, usingOSK?: boolean): boolean;
+ test(proctor: Proctor): TestFailure[];
+}
+
+/**
+ * The core constraint-specific test set definition used for testing versions 10.0 to 13.0.
+ */
+export class EventSpecTestSet implements TestSet {
+ constraint: Constraint;
+ testSet: InputEventSpecSequence[];
+
+ constructor(constraint: Constraint|EventSpecTestSet) {
+ if("target" in constraint) {
+ this.constraint = constraint as Constraint;
+ this.testSet = [];
+ } else {
+ var json = constraint as EventSpecTestSet;
+ this.constraint = new Constraint(json.constraint);
+ this.testSet = [];
+
+ // Clone each test sequence / reconstruct from methodless JSON object.
+ for(var i=0; i < json.testSet.length; i++) {
+ this.testSet.push(new InputEventSpecSequence(json.testSet[i]));
+ }
+ }
+ }
+
+ addTest(seq: InputEventSpecSequence) {
+ this.testSet.push(seq);
+ }
+
+ // Used to determine if the current EventSpecTestSet is applicable to be run on a device.
+ isValidForDevice(device: utils.DeviceSpec, usingOSK?: boolean) {
+ return this.constraint.matchesClient(device, usingOSK);
+ }
+
+ // Validity should be checked before calling this method.
+ test(proctor: Proctor): TestFailure[] {
+ var failures: TestFailure[] = [];
+ let testSet = this.testSet;
+
+ for(var i=0; i < testSet.length; i++) {
+ var testSeq = this[i];
+ var simResult = testSet[i].test(proctor);
+ if(!simResult.success) {
+ // Failed test!
+ failures.push(new TestFailure(this.constraint, testSeq, simResult.result));
+ }
+ }
+
+ return failures.length > 0 ? failures : null;
+ }
+}
+
+/**
+ * The core constraint-specific test set definition used for testing versions 10.0 to 13.0.
+ */
+export class RecordedSequenceTestSet implements TestSet {
+ constraint: Constraint;
+ testSet: RecordedKeystrokeSequence[];
+
+ constructor(constraint: Constraint|RecordedSequenceTestSet) {
+ if("target" in constraint) {
+ this.constraint = constraint as Constraint;
+ this.testSet = [];
+ } else {
+ var json = constraint as RecordedSequenceTestSet;
+ this.constraint = new Constraint(json.constraint);
+ this.testSet = [];
+
+ // Clone each test sequence / reconstruct from methodless JSON object.
+ for(var i=0; i < json.testSet.length; i++) {
+ this.testSet.push(new RecordedKeystrokeSequence(json.testSet[i]));
+ }
+ }
+ }
+
+ addTest(seq: RecordedKeystrokeSequence) {
+ this.testSet.push(seq);
+ }
+
+ // Used to determine if the current EventSpecTestSet is applicable to be run on a device.
+ isValidForDevice(device: utils.DeviceSpec, usingOSK?: boolean) {
+ return this.constraint.matchesClient(device, usingOSK);
+ }
+
+ // Validity should be checked before calling this method.
+ test(proctor: Proctor): TestFailure[] {
+ var failures: TestFailure[] = [];
+ let testSet = this.testSet;
+
+ for(var i=0; i < testSet.length; i++) {
+ var testSeq = this[i];
+ var simResult = testSet[i].test(proctor);
+ if(!simResult.success) {
+ // Failed test!
+ failures.push(new TestFailure(this.constraint, testSeq, simResult.result));
+ }
+ }
+
+ return failures.length > 0 ? failures : null;
+ }
+
+ toTestName(): string {
+ let name = "constraint: for " + this.constraint.target;
+
+ if(this.constraint.target == 'hardware') {
+ name += " keyboard";
+ } else {
+ name += " OSK";
+ }
+ if(this.constraint.validOSList) {
+ name += " on OS of " + JSON.stringify(this.constraint.validOSList);
+ }
+ if(this.constraint.validBrowsers) {
+ name += " in browser of " + JSON.stringify(this.constraint.validBrowsers);
+ }
+
+ return name;
+ }
+}
+
+export class KeyboardTest {
+ /**
+ * Indicates what version of KMW's recorder the spec conforms to.
+ */
+ public specVersion: utils.Version = KeyboardTest.CURRENT_VERSION;
+
/**
- * The core constraint-specific test set definition used for testing versions 10.0 to 13.0.
+ * The version of KMW in which the Recorder was first written. Worked from 10.0 to 13.0 with
+ * only backward-compatible changes and minor tweaks to conform to internal API shifts.
*/
- export class RecordedSequenceTestSet implements TestSet {
- constraint: Constraint;
- testSet: RecordedKeystrokeSequence[];
+ public static readonly FALLBACK_VERSION = new utils.Version("10.0");
+ public static readonly CURRENT_VERSION = new utils.Version("14.0");
- constructor(constraint: Constraint|RecordedSequenceTestSet) {
- if("target" in constraint) {
- this.constraint = constraint as Constraint;
- this.testSet = [];
- } else {
- var json = constraint as RecordedSequenceTestSet;
- this.constraint = new Constraint(json.constraint);
- this.testSet = [];
+ /**
+ * The stub information to be passed into keyman.addKeyboards() in order to run the test.
+ */
+ keyboard: KeyboardStub;
- // Clone each test sequence / reconstruct from methodless JSON object.
- for(var i=0; i < json.testSet.length; i++) {
- this.testSet.push(new RecordedKeystrokeSequence(json.testSet[i]));
- }
- }
- }
+ /**
+ * The master array of test sets, each of which specifies constraints a client must fulfill for
+ * the tests contained therein to be valid.
+ */
+ inputTestSets: TestSet[];
- addTest(seq: RecordedKeystrokeSequence) {
- this.testSet.push(seq);
- }
-
- // Used to determine if the current EventSpecTestSet is applicable to be run on a device.
- isValidForDevice(device: com.keyman.utils.DeviceSpec, usingOSK?: boolean) {
- return this.constraint.matchesClient(device, usingOSK);
- }
-
- // Validity should be checked before calling this method.
- test(proctor: Proctor): TestFailure[] {
- var failures: TestFailure[] = [];
- let testSet = this.testSet;
-
- for(var i=0; i < testSet.length; i++) {
- var testSeq = this[i];
- var simResult = testSet[i].test(proctor);
- if(!simResult.success) {
- // Failed test!
- failures.push(new TestFailure(this.constraint, testSeq, simResult.result));
- }
- }
-
- return failures.length > 0 ? failures : null;
- }
-
- toTestName(): string {
- let name = "constraint: for " + this.constraint.target;
-
- if(this.constraint.target == 'hardware') {
- name += " keyboard";
- } else {
- name += " OSK";
- }
- if(this.constraint.validOSList) {
- name += " on OS of " + JSON.stringify(this.constraint.validOSList);
- }
- if(this.constraint.validBrowsers) {
- name += " in browser of " + JSON.stringify(this.constraint.validBrowsers);
- }
-
- return name;
- }
- }
-
- export class KeyboardTest {
- /**
- * Indicates what version of KMW's recorder the spec conforms to.
- */
- public specVersion: com.keyman.utils.Version = KeyboardTest.CURRENT_VERSION;
-
- /**
- * The version of KMW in which the Recorder was first written. Worked from 10.0 to 13.0 with
- * only backward-compatible changes and minor tweaks to conform to internal API shifts.
- */
- public static readonly FALLBACK_VERSION = new com.keyman.utils.Version("10.0");
- public static readonly CURRENT_VERSION = new com.keyman.utils.Version("14.0");
-
- /**
- * The stub information to be passed into keyman.addKeyboards() in order to run the test.
- */
- keyboard: KeyboardStub;
-
- /**
- * The master array of test sets, each of which specifies constraints a client must fulfill for
- * the tests contained therein to be valid.
- */
- inputTestSets: TestSet[];
-
- /**
- * Reconstructs a KeyboardTest object from its JSON representation, restoring its methods.
- * @param fromJSON
- */
- constructor(fromJSON?: string|KeyboardStub|KeyboardTest) {
- if(!fromJSON) {
- this.keyboard = null;
- this.inputTestSets = [];
- return;
- } else if(typeof(fromJSON) == 'string') {
- fromJSON = JSON.parse(fromJSON) as KeyboardTest;
- } else if(fromJSON instanceof KeyboardStub) {
- this.keyboard = fromJSON;
- this.inputTestSets = [];
- return;
- }
-
- if(!fromJSON.specVersion) {
- fromJSON.specVersion = KeyboardTest.FALLBACK_VERSION;
- } else {
- // Is serialized to a String when saved.
- fromJSON.specVersion = new com.keyman.utils.Version(fromJSON.specVersion as unknown as string);
- }
-
- this.keyboard = new KeyboardStub(fromJSON.keyboard);
+ /**
+ * Reconstructs a KeyboardTest object from its JSON representation, restoring its methods.
+ * @param fromJSON
+ */
+ constructor(fromJSON?: string|KeyboardStub|KeyboardTest) {
+ if(!fromJSON) {
+ this.keyboard = null;
this.inputTestSets = [];
- this.specVersion = fromJSON.specVersion;
+ return;
+ } else if(typeof(fromJSON) == 'string') {
+ fromJSON = JSON.parse(fromJSON) as KeyboardTest;
+ } else if(fromJSON instanceof KeyboardStub) {
+ this.keyboard = fromJSON;
+ this.inputTestSets = [];
+ return;
+ }
- if(this.specVersion.equals(KeyboardTest.FALLBACK_VERSION)) {
- // Top-level test spec: EventSpecTestSet, based entirely on browser events.
- for(var i=0; i < fromJSON.inputTestSets.length; i++) {
- this.inputTestSets[i] = new EventSpecTestSet(fromJSON.inputTestSets[i] as EventSpecTestSet);
- }
- } else {
- for(var i=0; i < fromJSON.inputTestSets.length; i++) {
- this.inputTestSets[i] = new RecordedSequenceTestSet(fromJSON.inputTestSets[i] as RecordedSequenceTestSet);
+ if(!fromJSON.specVersion) {
+ fromJSON.specVersion = KeyboardTest.FALLBACK_VERSION;
+ } else {
+ // Is serialized to a String when saved.
+ fromJSON.specVersion = new utils.Version(fromJSON.specVersion as unknown as string);
+ }
+
+ this.keyboard = new KeyboardStub(fromJSON.keyboard);
+ this.inputTestSets = [];
+ this.specVersion = fromJSON.specVersion;
+
+ if(this.specVersion.equals(KeyboardTest.FALLBACK_VERSION)) {
+ // Top-level test spec: EventSpecTestSet, based entirely on browser events.
+ for(var i=0; i < fromJSON.inputTestSets.length; i++) {
+ this.inputTestSets[i] = new EventSpecTestSet(fromJSON.inputTestSets[i] as EventSpecTestSet);
+ }
+ } else {
+ for(var i=0; i < fromJSON.inputTestSets.length; i++) {
+ this.inputTestSets[i] = new RecordedSequenceTestSet(fromJSON.inputTestSets[i] as RecordedSequenceTestSet);
+ }
+ }
+ }
+
+ addTest(constraint: Constraint, seq: RecordedKeystrokeSequence) {
+ if(!this.specVersion.equals(KeyboardTest.CURRENT_VERSION)) {
+ throw new Error("The currently-loaded test was built to an outdated specification and may not be altered.");
+ }
+
+ for(var i=0; i < this.inputTestSets.length; i++) {
+ if(this.inputTestSets[i].constraint.equals(constraint)) {
+ this.inputTestSets[i].addTest(seq);
+ return;
+ }
+ }
+
+ var newSet = new RecordedSequenceTestSet(new Constraint(constraint));
+ this.inputTestSets.push(newSet);
+ newSet.addTest(seq);
+ }
+
+ test(proctor: Proctor) {
+ var setHasRun = false;
+ var failures: TestFailure[] = [];
+
+ proctor.beforeAll();
+
+ // The original test spec requires a browser environment and thus requires its own `.run` implementation.
+ if(!(proctor.compatibleWithSuite(this))) {
+ throw Error("Cannot perform version " + KeyboardTest.FALLBACK_VERSION + "-based testing outside of browser-based environments.");
+ }
+
+ // Otherwise, the test spec instances will know how to run in any currently-supported environment.
+ for(var i = 0; i < this.inputTestSets.length; i++) {
+ var testSet = this.inputTestSets[i];
+
+ if(proctor.matchesTestSet(testSet)) {
+ var testFailures = testSet.test(proctor);
+ if(testFailures) {
+ failures = failures.concat(testFailures);
}
+ setHasRun = true;
}
}
- addTest(constraint: Constraint, seq: RecordedKeystrokeSequence) {
- if(!this.specVersion.equals(KeyboardTest.CURRENT_VERSION)) {
- throw new Error("The currently-loaded test was built to an outdated specification and may not be altered.");
- }
-
- for(var i=0; i < this.inputTestSets.length; i++) {
- if(this.inputTestSets[i].constraint.equals(constraint)) {
- this.inputTestSets[i].addTest(seq);
- return;
- }
- }
-
- var newSet = new RecordedSequenceTestSet(new Constraint(constraint));
- this.inputTestSets.push(newSet);
- newSet.addTest(seq);
+ if(!setHasRun) {
+ // The sets CAN be empty, allowing silent failure if/when we actually want that.
+ console.warn("No test sets for this keyboard were applicable for this device!");
}
- test(proctor: Proctor) {
- var setHasRun = false;
- var failures: TestFailure[] = [];
-
- proctor.beforeAll();
-
- // The original test spec requires a browser environment and thus requires its own `.run` implementation.
- if(!(proctor.compatibleWithSuite(this))) {
- throw Error("Cannot perform version " + KeyboardTest.FALLBACK_VERSION + "-based testing outside of browser-based environments.");
- }
-
- // Otherwise, the test spec instances will know how to run in any currently-supported environment.
- for(var i = 0; i < this.inputTestSets.length; i++) {
- var testSet = this.inputTestSets[i];
-
- if(proctor.matchesTestSet(testSet)) {
- var testFailures = testSet.test(proctor);
- if(testFailures) {
- failures = failures.concat(testFailures);
- }
- setHasRun = true;
- }
- }
-
- if(!setHasRun) {
- // The sets CAN be empty, allowing silent failure if/when we actually want that.
- console.warn("No test sets for this keyboard were applicable for this device!");
- }
-
- // Allow the method's caller to trigger a 'fail'.
- if(failures.length > 0) {
- return failures;
- } else {
- return null;
- }
+ // Allow the method's caller to trigger a 'fail'.
+ if(failures.length > 0) {
+ return failures;
+ } else {
+ return null;
}
+ }
- isEmpty() {
- return this.inputTestSets.length == 0;
- }
+ isEmpty() {
+ return this.inputTestSets.length == 0;
+ }
- toPrettyJSON() {
- return JSON.stringify(this, null, ' ');
- }
+ toPrettyJSON() {
+ return JSON.stringify(this, null, ' ');
+ }
- get isLegacy(): boolean {
- return !this.specVersion.equals(KeyboardTest.CURRENT_VERSION);
- }
+ get isLegacy(): boolean {
+ return !this.specVersion.equals(KeyboardTest.CURRENT_VERSION);
}
}
\ No newline at end of file
diff --git a/common/web/recorder/src/nodeProctor.ts b/common/web/recorder/src/nodeProctor.ts
index 0ae4b5adcc..2031e450e4 100644
--- a/common/web/recorder/src/nodeProctor.ts
+++ b/common/web/recorder/src/nodeProctor.ts
@@ -1,94 +1,105 @@
+import Proctor, { AssertCallback } from "./proctor.js";
+import {
+ KeyboardTest,
+ TestSet,
+ TestSequence,
+ RecordedKeystrokeSequence,
+ RecordedPhysicalKeystroke,
+ RecordedSyntheticKeystroke
+} from "./index.js";
-namespace KMWRecorder {
- export class NodeProctor extends Proctor {
- private keyboard: com.keyman.keyboards.Keyboard;
- public __debug = false;
+import Keyboard from "keyboard-processor/build/modules/keyboards/keyboard.js";
+import type KeyEvent from "keyboard-processor/build/modules/text/keyEvent.js";
+import KeyboardProcessor from "keyboard-processor/build/modules/text/keyboardProcessor.js";
+import type OutputTarget from "keyboard-processor/build/modules/text/outputTarget.js";
+import { Mock } from "keyboard-processor/build/modules/text/outputTarget.js";
- constructor(keyboard: com.keyman.keyboards.Keyboard, device: com.keyman.utils.DeviceSpec, assert: AssertCallback) {
- super(device, assert);
+import DeviceSpec from "utils/build/modules/deviceSpec.js";
- this.keyboard = keyboard;
- }
+export default class NodeProctor extends Proctor {
+ private keyboard: Keyboard;
+ public __debug = false;
- beforeAll() {
- //
- }
-
- before() {
- //
- }
-
- compatibleWithSuite(testSuite: KeyboardTest): boolean {
- // Original-version tests did not supply core-compatible KeyEvent data.
- return !testSuite.specVersion.equals(KeyboardTest.FALLBACK_VERSION);
- }
-
- get debugMode(): boolean {
- return this.__debug;
- }
-
- set debugMode(value: boolean) {
- this.__debug = value;
- }
-
- matchesTestSet(testSet: TestSet) {
- // KeyboardProcessor is abstract enough to run tests aimed at any platform.
- return true;
- }
-
- simulateSequence(sequence: TestSequence, target?: com.keyman.text.OutputTarget): string {
- // Start with an empty OutputTarget and a fresh KeyboardProcessor.
- if(!target) {
- target = new com.keyman.text.Mock();
- }
-
- // Establish a fresh processor, setting its keyboard appropriately for the test.
- let processor = new com.keyman.text.KeyboardProcessor(this.device);
- processor.activeKeyboard = this.keyboard;
-
- if(sequence instanceof RecordedKeystrokeSequence) {
- for(let keystroke of sequence.inputs) {
- let keyEvent: com.keyman.text.KeyEvent;
- if(keystroke instanceof RecordedPhysicalKeystroke) {
- // Use the keystroke's stored data to reconstruct the KeyEvent.
- keyEvent = {
- Lcode: keystroke.keyCode,
- Lmodifiers: keystroke.modifiers,
- LmodifierChange: keystroke.modifierChanged,
- vkCode: keystroke.vkCode,
- Lstates: keystroke.states,
- kName: '',
- device: this.device,
- isSynthetic: false,
- LisVirtualKey: this.keyboard.definesPositionalOrMnemonic // Only false for 1.0 keyboards.
- }
- } 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);
- }
-
- // Fill in the final details of the KeyEvent...
- keyEvent.device = this.device;
-
- // And now, execute the keystroke!
- // 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);
-
- if(this.debugMode) {
- console.log(JSON.stringify(target, null, ' '));
- console.log(JSON.stringify(ruleBehavior, null, ' '));
- }
- }
- } else {
- throw new Error("NodeProctor only supports RecordedKeystrokeSequences for testing at present.");
- }
- return target.getText();
- }
+ constructor(keyboard: Keyboard, device: DeviceSpec, assert: AssertCallback) {
+ super(device, assert);
+ this.keyboard = keyboard;
}
-}
-// Export the namespace itself, giving access to all contained classes.
-module.exports = KMWRecorder;
\ No newline at end of file
+ beforeAll() {
+ //
+ }
+
+ before() {
+ //
+ }
+
+ compatibleWithSuite(testSuite: KeyboardTest): boolean {
+ // Original-version tests did not supply core-compatible KeyEvent data.
+ return !testSuite.specVersion.equals(KeyboardTest.FALLBACK_VERSION);
+ }
+
+ get debugMode(): boolean {
+ return this.__debug;
+ }
+
+ set debugMode(value: boolean) {
+ this.__debug = value;
+ }
+
+ matchesTestSet(testSet: TestSet) {
+ // KeyboardProcessor is abstract enough to run tests aimed at any platform.
+ return true;
+ }
+
+ simulateSequence(sequence: TestSequence, target?: OutputTarget): string {
+ // Start with an empty OutputTarget and a fresh KeyboardProcessor.
+ if(!target) {
+ target = new Mock();
+ }
+
+ // Establish a fresh processor, setting its keyboard appropriately for the test.
+ let processor = new KeyboardProcessor(this.device);
+ processor.activeKeyboard = this.keyboard;
+
+ if(sequence instanceof RecordedKeystrokeSequence) {
+ for(let keystroke of sequence.inputs) {
+ let keyEvent: KeyEvent;
+ if(keystroke instanceof RecordedPhysicalKeystroke) {
+ // Use the keystroke's stored data to reconstruct the KeyEvent.
+ keyEvent = {
+ Lcode: keystroke.keyCode,
+ Lmodifiers: keystroke.modifiers,
+ LmodifierChange: keystroke.modifierChanged,
+ vkCode: keystroke.vkCode,
+ Lstates: keystroke.states,
+ kName: '',
+ device: this.device,
+ isSynthetic: false,
+ LisVirtualKey: this.keyboard.definesPositionalOrMnemonic // Only false for 1.0 keyboards.
+ }
+ } 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);
+ }
+
+ // Fill in the final details of the KeyEvent...
+ keyEvent.device = this.device;
+
+ // And now, execute the keystroke!
+ // 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);
+
+ if(this.debugMode) {
+ console.log(JSON.stringify(target, null, ' '));
+ console.log(JSON.stringify(ruleBehavior, null, ' '));
+ }
+ }
+ } else {
+ throw new Error("NodeProctor only supports RecordedKeystrokeSequences for testing at present.");
+ }
+ return target.getText();
+ }
+}
\ No newline at end of file
diff --git a/common/web/recorder/src/nodeProctor.tsconfig.json b/common/web/recorder/src/nodeProctor.tsconfig.json
deleted file mode 100644
index 5b80993dc6..0000000000
--- a/common/web/recorder/src/nodeProctor.tsconfig.json
+++ /dev/null
@@ -1,28 +0,0 @@
-{
- "extends": "../../../../tsconfig-base.json",
-
- "compilerOptions": {
- "allowJs": true,
- "module": "none",
- "outDir": "../build/nodeProctor/",
- "outFile": "../build/nodeProctor/index.js",
- "inlineSources": true,
- "inlineSourceMap": true,
- "target": "es5",
- "types": ["node"],
- "lib": ["es6"]
- },
-
- "files": [
- "index.ts",
- "proctor.ts",
- "nodeProctor.ts"
- ],
-
- "references": [
- { "path": "../../keyman-version" },
- { "path": "../../utils" },
- { "path": "../../keyboard-processor/src" },
- { "path": "../../lm-message-types" }
- ]
-}
diff --git a/common/web/recorder/src/proctor.ts b/common/web/recorder/src/proctor.ts
index 38ffd9997a..bedc461237 100644
--- a/common/web/recorder/src/proctor.ts
+++ b/common/web/recorder/src/proctor.ts
@@ -1,50 +1,53 @@
-namespace KMWRecorder {
- export type AssertCallback = (s1: any, s2: any, msg?: string) => void;
+import { type DeviceSpec } from "utils/build/modules/index.js";
+import type OutputTarget from "keyboard-processor/build/modules/text/outputTarget.js";
+
+import type { KeyboardTest, TestSet, TestSequence } from "./index.js";
+
+export type AssertCallback = (s1: any, s2: any, msg?: string) => void;
+
+/**
+ * Facilitates running Recorder-generated tests on various platforms.
+ *
+ * Note that DOM-aware KeymanWeb will implement a Browser-based version, while
+ * keyboard-processor and input-processor will use a Node-based version instead.
+ */
+export default abstract class Proctor {
+ device: DeviceSpec;
+
+ _assert: AssertCallback;
+
+ constructor(device: DeviceSpec, assert: AssertCallback) {
+ this.device = device;
+
+ this._assert = assert;
+ }
+
+ assertEquals(s1: unknown, s2: unknown, msg?: string) {
+ if(this._assert) {
+ this._assert(s1, s2, msg);
+ }
+ }
+
+ // Performs global test prep.
+ abstract beforeAll();
+
+ // Performs per-test setup
+ abstract before();
/**
- * Facilitates running Recorder-generated tests on various platforms.
- *
- * Note that DOM-aware KeymanWeb will implement a Browser-based version, while
- * keyboard-processor and input-processor will use a Node-based version instead.
+ * Allows the proctor to indicate if is capable of executing a suite of tests or not.
+ * @param testSuite
*/
- export abstract class Proctor {
- device: com.keyman.utils.DeviceSpec;
+ abstract compatibleWithSuite(testSuite: KeyboardTest): boolean;
- _assert: AssertCallback;
+ /**
+ * Indicates whether or not this Proctor is capable of running the specified set of tests.
+ */
+ abstract matchesTestSet(testSet: TestSet);
- constructor(device: com.keyman.utils.DeviceSpec, assert: AssertCallback) {
- this.device = device;
-
- this._assert = assert;
- }
-
- assertEquals(s1: unknown, s2: unknown, msg?: string) {
- if(this._assert) {
- this._assert(s1, s2, msg);
- }
- }
-
- // Performs global test prep.
- abstract beforeAll();
-
- // Performs per-test setup
- abstract before();
-
- /**
- * Allows the proctor to indicate if is capable of executing a suite of tests or not.
- * @param testSuite
- */
- abstract compatibleWithSuite(testSuite: KeyboardTest): boolean;
-
- /**
- * Indicates whether or not this Proctor is capable of running the specified set of tests.
- */
- abstract matchesTestSet(testSet: TestSet);
-
- /**
- * Simulates the specified test sequence for use in testing.
- * @param sequence The recorded sequence, generally provided by a test set.
- */
- abstract simulateSequence(sequence: TestSequence, target?: com.keyman.text.OutputTarget);
- }
+ /**
+ * Simulates the specified test sequence for use in testing.
+ * @param sequence The recorded sequence, generally provided by a test set.
+ */
+ abstract simulateSequence(sequence: TestSequence, target?: OutputTarget);
}
\ No newline at end of file
diff --git a/common/web/recorder/src/tsconfig.json b/common/web/recorder/src/tsconfig.json
deleted file mode 100644
index 00cb00a482..0000000000
--- a/common/web/recorder/src/tsconfig.json
+++ /dev/null
@@ -1,27 +0,0 @@
-{
- "extends": "../../../../tsconfig-base.json",
-
- "compilerOptions": {
- "allowJs": true,
- "module": "none",
- "outDir": "../build/",
- "inlineSources": true,
- "inlineSourceMap": true,
- "target": "es5",
- "types": ["node"],
- "lib": ["es6"],
- "outFile": "../build/index.js"
- },
-
- "files": [
- "index.ts",
- "proctor.ts"
- ],
-
- "references": [
- { "path": "../../keyman-version" },
- { "path": "../../utils" },
- { "path": "../../keyboard-processor/src" },
- { "path": "../../lm-message-types" }
- ]
-}
diff --git a/common/web/recorder/tsconfig.json b/common/web/recorder/tsconfig.json
new file mode 100644
index 0000000000..c490c2280a
--- /dev/null
+++ b/common/web/recorder/tsconfig.json
@@ -0,0 +1,35 @@
+{
+ "extends": "../../../tsconfig-base.json",
+
+ "compilerOptions": {
+ "allowJs": true,
+ "module": "es6",
+ "declaration": true,
+ "inlineSources": true,
+ "inlineSourceMap": true,
+ "target": "es5",
+ "types": ["node"],
+ "lib": ["es6"],
+ "baseUrl": "./",
+ "outDir": "build/modules/",
+ "tsBuildInfoFile": "build/modules/tsconfig.tsbuildinfo",
+ "rootDir": "./src"
+ },
+
+ "include": [
+ "src/**/*.ts"
+ ],
+
+ "references": [
+ { "path": "../keyman-version" },
+ { "path": "../utils/" },
+ { "path": "../keyboard-processor/" },
+ { "path": "../lm-message-types" }
+ ],
+
+ "paths": {
+ "keyboard-processor": ["../keyboard-processor/build" ],
+ "keyman-version": ["../keyman-version/build" ],
+ "utils": ["../utils/build" ]
+ }
+}