Skip to content

Commit

Permalink
Merge pull request #10475 from keymanapp/feat/web/key-event-rule-proc…
Browse files Browse the repository at this point in the history
…essing-tests

feat(web): key-event rule processing unit tests for mnemonic, KMW 1.0 keyboards 🧓
  • Loading branch information
jahorton authored Jan 26, 2024
2 parents a0adacf + dbae839 commit 7d999ee
Show file tree
Hide file tree
Showing 10 changed files with 243 additions and 1 deletion.
10 changes: 10 additions & 0 deletions common/test/resources/json/keyboards/armenian.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": "armenian",
"name":"Armenian Unicode",
"languages":{
"id":"hy",
"name": "Armenian",
"region": "Asia"
},
"filename":"resources/keyboards/armenian.js"
}
10 changes: 10 additions & 0 deletions common/test/resources/json/keyboards/sil_ipa.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"id": "sil_ipa",
"name":"IPA (SIL)",
"languages":{
"id":"und-fonipa",
"name": "Undetermined",
"region": "World"
},
"filename":"resources/keyboards/sil_ipa.js"
}
1 change: 1 addition & 0 deletions common/test/resources/keyboards/armenian.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions common/test/resources/keyboards/armenian.readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
From the legacy section of the keyboards repo - is a KMW 1.0 keyboard, thus is neither positional nor mnemonic.
1 change: 1 addition & 0 deletions common/test/resources/keyboards/sil_ipa.js

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions common/test/resources/keyboards/sil_ipa.readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
From the keyboards repo, release section - version 1.8.6, before any KMW 17.0 gestures were added.
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,12 @@ export class KeyboardHarness {
this.loadedKeyboard = new Keyboard(scriptObject);
}

// Is evaluated on script-load for some keyboards using variable stores.
// Example: sil_ipa - store(option_key)
public KLOAD(kbdName: string, storeName: string, dfltValue: string) {
return dfltValue;
}

/**
* Installs this harness instance into the object that the keyboard script will perceive
* as the top-level global with the name `KeymanWeb`.
Expand Down
12 changes: 12 additions & 0 deletions common/web/keyboard-processor/tests/node/keyboard-loading.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ describe('Headless keyboard loading', function() {
const laoPath = require.resolve('@keymanapp/common-test-resources/keyboards/lao_2008_basic.js');
const khmerPath = require.resolve('@keymanapp/common-test-resources/keyboards/khmer_angkor.js');
const nonKeyboardPath = require.resolve('@keymanapp/common-test-resources/index.mjs');
const ipaPath = require.resolve('@keymanapp/common-test-resources/keyboards/sil_ipa.js');
// Common test suite setup.

let device = {
Expand All @@ -37,6 +38,17 @@ describe('Headless keyboard loading', function() {
assert.equal(keyboard.id, "Keyboard_lao_2008_basic");
});

it('successfully loads (has variable stores)', async () => {
// -- START: Standard Recorder-based unit test loading boilerplate --
let harness = new KeyboardInterface({}, MinimalKeymanGlobal);
let keyboardLoader = new NodeKeyboardLoader(harness);
let keyboard = await keyboardLoader.loadKeyboardFromPath(ipaPath);
// -- END: Standard Recorder-based unit test loading boilerplate --

// This part provides extra assurance that the keyboard properly loaded.
assert.equal(keyboard.id, "Keyboard_sil_ipa");
});

it('cannot evaluate rules', async function() {
// -- START: Standard Recorder-based unit test loading boilerplate --
let harness = new KeyboardHarness({}, MinimalKeymanGlobal);
Expand Down
198 changes: 198 additions & 0 deletions common/web/keyboard-processor/tests/node/non-positional-rules.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
import { assert } from 'chai';
import fs from 'fs';

import { createRequire } from 'module';
const require = createRequire(import.meta.url);

import { Codes, KeyboardInterface, KeyEvent, MinimalKeymanGlobal, Mock } from '@keymanapp/keyboard-processor';
import { NodeKeyboardLoader } from '@keymanapp/keyboard-processor/node-keyboard-loader';

// Compare and contrast the unit tests here with those for app/browser key-event unit testing
// in the hardware-event-processing set; the output objects there should have the same format
// as the event object inputs used here.

describe('Engine - rule processing', function() {
const ipaPath = require.resolve('@keymanapp/common-test-resources/keyboards/sil_ipa.js');
const armenianPath = require.resolve('@keymanapp/common-test-resources/keyboards/armenian.js');

let device = {
formFactor: 'desktop',
OS: 'windows',
browser: 'native'
}

describe('for mnemonic keyboards (via `sil_ipa`)', function () {
let keyboardWithHarness;

before(async () => {
// -- START: Standard keyboard unit test loading boilerplate --
let harness = new KeyboardInterface({}, MinimalKeymanGlobal);
let keyboardLoader = new NodeKeyboardLoader(harness);
let keyboard = await keyboardLoader.loadKeyboardFromPath(ipaPath);
// -- END: Standard keyboard unit test loading boilerplate --

// This part provides extra assurance that the keyboard properly loaded.
assert.equal(keyboard.id, "Keyboard_sil_ipa");

harness.activeKeyboard = keyboard;
keyboardWithHarness = harness;
});

it('matches rules with mnemonic-specced KeyEvents', () => {
// Note: plain 'n' is produced from default key outputs for sil_ipa, not a keyboard rule.
let mockMnemonic = new Mock('n');
let mnemonicEvent = new KeyEvent({
// sil_ipa is a mnenomic keyboard: it expects codes based on the key's standard character output.
Lcode: '>'.charCodeAt(0), // 62
Lmodifiers: Codes.modifierCodes.SHIFT, // '>' is shift-layer.
Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK,
LisVirtualKey: true,
kName: '',
vkCode: Codes.keyCodes.K_N,
device: device
});

const mnemonicResult = keyboardWithHarness.processKeystroke(mockMnemonic, mnemonicEvent);

assert.isOk(mnemonicResult);
assert.isFalse(mnemonicResult.triggerKeyDefault);
assert.equal(mockMnemonic.getText(), 'ŋ');
});

it('requires correct modifiers', () => {
// Note: plain 'n' is produced from default key outputs for sil_ipa, not a keyboard rule.
let mockMnemonic = new Mock('n');
let mnemonicEvent = new KeyEvent({
// sil_ipa is a mnenomic keyboard: it expects codes based on the key's standard character output.
Lcode: '>'.charCodeAt(0), // 62
Lmodifiers: 0, // '>' is shift-layer, not default - and this matters for context matching.
Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK,
LisVirtualKey: true,
kName: '',
vkCode: Codes.keyCodes.K_N,
device: device
});

const mnemonicResult = keyboardWithHarness.processKeystroke(mockMnemonic, mnemonicEvent);

assert.isOk(mnemonicResult);
assert.isTrue(mnemonicResult.triggerKeyDefault);
});

it('does not match rules with positional-specced KeyEvents', () => {
let mockPositional = new Mock('n');
let positionalEvent = new KeyEvent({
// If it were positional, we'd use this instead:
Lcode: Codes.keyCodes.K_COMMA, // 188
Lmodifiers: Codes.modifierCodes.SHIFT, // '>' is shift-layer.
Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK,
LisVirtualKey: true,
kName: '',
vkCode: Codes.keyCodes.K_N,
device: device
});

const positionalResult = keyboardWithHarness.processKeystroke(mockPositional, positionalEvent);

assert.isOk(positionalResult);
assert.isTrue(positionalResult.triggerKeyDefault);
assert.notEqual(mockPositional.getText(), 'ŋ');
});
});

describe('for KMW 1.0 legacy keyboards (via `armenian`)', function () {
let keyboardWithHarness;

before(async () => {
// -- START: Standard keyboard unit test loading boilerplate --
let harness = new KeyboardInterface({}, MinimalKeymanGlobal);
let keyboardLoader = new NodeKeyboardLoader(harness);
let keyboard = await keyboardLoader.loadKeyboardFromPath(armenianPath);
// -- END: Standard keyboard unit test loading boilerplate --

// This part provides extra assurance that the keyboard properly loaded.
assert.equal(keyboard.id, "Keyboard_armenian");

harness.activeKeyboard = keyboard;
keyboardWithHarness = harness;
});

it('matches rules with legacy-specced KeyEvents', () => {
let mockLegacy = new Mock('');
let legacyEvent = new KeyEvent({
// armenian is a KMW 1.0 keyboard: it expects codes based on the key's standard character output.
Lcode: 'a'.charCodeAt(0),
Lmodifiers: 0,
Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK,
LisVirtualKey: false,
kName: '',
vkCode: Codes.keyCodes.K_N,
device: device
});

const legacyResult = keyboardWithHarness.processKeystroke(mockLegacy, legacyEvent);

assert.isOk(legacyResult);
assert.isFalse(legacyResult.triggerKeyDefault);
assert.equal(mockLegacy.getText(), 'ա');
});

it('ignores current modifiers and states', () => {
let mockLegacy = new Mock('');
let legacyEvent = new KeyEvent({
// armenian is a KMW 1.0 keyboard: it expects codes based on the key's standard character output.
Lcode: 'a'.charCodeAt(0),
Lmodifiers: 27,
Lstates: Codes.modifierCodes.CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.SCROLL_LOCK | 0x5C00,
LisVirtualKey: false,
kName: '',
vkCode: Codes.keyCodes.K_N,
device: device
});

const legacyResult = keyboardWithHarness.processKeystroke(mockLegacy, legacyEvent);

assert.isOk(legacyResult);
assert.isFalse(legacyResult.triggerKeyDefault);
assert.equal(mockLegacy.getText(), 'ա');
});

it('does not match rules with mnemonic-specced KeyEvents', () => {
let mockMnemonic = new Mock('');
let mnemonicEvent = new KeyEvent({
// armenian is a KMW 1.0 keyboard: it expects codes based on the key's standard character output.
Lcode: 'a'.charCodeAt(0),
Lmodifiers: 0,
Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK,
LisVirtualKey: true,
kName: '',
vkCode: Codes.keyCodes.K_N,
device: device
});

const mnemonicResult = keyboardWithHarness.processKeystroke(mockMnemonic, mnemonicEvent);

assert.isOk(mnemonicResult);
assert.isTrue(mnemonicResult.triggerKeyDefault);
});

it('does not match rules with positional-specced KeyEvents', () => {
let mockPositional = new Mock('');
let positionalEvent = new KeyEvent({
// If it were positional, we'd use this instead:
Lcode: Codes.keyCodes.K_A,
Lmodifiers: 0,
Lstates: Codes.modifierCodes.NO_CAPS | Codes.modifierCodes.NO_NUM_LOCK | Codes.modifierCodes.NO_SCROLL_LOCK,
LisVirtualKey: true,
kName: '',
vkCode: Codes.keyCodes.K_N,
device: device
});

const positionalResult = keyboardWithHarness.processKeystroke(mockPositional, positionalEvent);

assert.isOk(positionalResult);
assert.isTrue(positionalResult.triggerKeyDefault);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ const KeyCodes = Codes.keyCodes;

const DUMMY_DEVICE = new DeviceSpec('chrome', 'desktop', 'windows', false);

// console.log(JSON.stringify(processedEvent));
// Compare and contrast the unit tests here with those for keyboard-processor unit testing
// in the non-positional-rules set; the output objects here should have the same format
// as the inputs for rules as used there.

describe("app/browser: hardware event processing", () => {
describe('preprocessKeyboardEvent', () => {
Expand Down

0 comments on commit 7d999ee

Please sign in to comment.