Merge pull request #8132 from keymanapp/chore/developer/8069-todocleanup-epic-ldml

fix(developer): todo cleanup, common code, tests  🙀
This commit is contained in:
Steven R. Loomis 2023-02-01 12:31:23 -06:00 • committed by GitHub
commit 031998f0a1
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 153 additions and 58 deletions

View file

@ -6,6 +6,7 @@ import GlobalSections = KMXPlus.GlobalSections;
import Key2 = KMXPlus.Key2;
import ListItem = KMXPlus.ListItem;
import Key2Flicks = KMXPlus.Key2Flicks;
import { allUsedKeyIdsInLayers, calculateUniqueKeys } from '../util/util.js';
export class Key2Compiler extends SectionCompiler {
@ -20,28 +21,6 @@ export class Key2Compiler extends SectionCompiler {
return valid;
}
/**
* Return a set of all used keys
* public for test
*/
public allUsedKeyIds(): Set<string> {
const s = new Set<string>();
if (this.keyboard?.layers) {
for (const layers of this.keyboard.layers || []) {
for (const layer of layers.layer || []) {
for (const row of layer.row || []) {
if (row.keys) {
for (const k of row.keys.split(" ")) {
s.add(k);
}
}
}
}
}
}
return s;
}
public compile(sections: GlobalSections): Key2 {
if (!this.keyboard?.keys?.key && !this.keyboard?.keys?.flicks) {
// short-circuit if no keys or flicks
@ -81,20 +60,8 @@ export class Key2Compiler extends SectionCompiler {
}
public loadKeys(sections: GlobalSections, sect: Key2) {
const usedKeys = this.allUsedKeyIds();
// TODO-LDML factor common code
// Need 'newer' (later) keys to override older ones.
const reverseKeys = [...this.keyboard.keys.key].reverse(); // newest to oldest
const alreadySeen = new Set<string>();
// filter out only the keys that haven't already been seen
const uniqueKeys = reverseKeys.filter(({id}) => {
if (!alreadySeen.has(id)) {
alreadySeen.add(id);
return true;
}
return false;
});
const usedKeys = allUsedKeyIdsInLayers(this.keyboard?.layers);
const uniqueKeys = calculateUniqueKeys([...this.keyboard.keys?.key]);
for (let key of uniqueKeys) {
if (!usedKeys.has(key.id)) {

View file

@ -6,6 +6,7 @@ import { SectionCompiler } from "./section-compiler.js";
import GlobalSections = KMXPlus.GlobalSections;
import Keys = KMXPlus.Keys;
import USVirtualKeyMap = Constants.USVirtualKeyMap;
import { calculateUniqueKeys } from '../util/util.js';
export class KeysCompiler extends SectionCompiler {
@ -14,19 +15,7 @@ export class KeysCompiler extends SectionCompiler {
}
private validateHardwareLayer(layer: LDMLKeyboard.LKLayer) {
// TODO-LDML factor common code
// Need 'newer' (later) keys to override older ones.
const reverseKeys = [...this.keyboard.keys?.key].reverse(); // newest to oldest
const alreadySeen = new Set<string>();
// filter out only the keys that haven't already been seen
const uniqueKeys = reverseKeys.filter(({id}) => {
if (!alreadySeen.has(id)) {
alreadySeen.add(id);
return true;
}
return false;
});
const uniqueKeys = calculateUniqueKeys([...this.keyboard.keys?.key]);
let valid = true;
if(layer.row.length > USVirtualKeyMap.length) {
this.callbacks.reportMessage(CompilerMessages.Error_HardwareLayerHasTooManyRows());

View file

@ -1,3 +1,4 @@
import { LKKey, LKLayers } from "@keymanapp/common-types/src/ldml-keyboard/ldml-keyboard-xml";
/**
* Verifies that value is an item in the enumeration.
@ -5,3 +6,51 @@
export function isValidEnumValue<T extends {[key: number]: string | number}>(enu: T, value: string) {
return (Object.values(enu) as string[]).includes(value);
}
/**
* Returns unique LKKeys only, preserving later ones
* in case of conflict. (i.e. later overrides)
* @param keys list of keys to consider. (mutated)
* @returns Array of unique keys. Order is not specified.
*/
export function calculateUniqueKeys(keys?: LKKey[]): LKKey[] {
if (!keys) {
return [];
}
// Need 'newer' (later) keys to override older ones.
const reverseKeys = keys.reverse(); // newest to oldest
const alreadySeen = new Set<string>();
// filter out only the keys that haven't already been seen
const uniqueKeys = reverseKeys.filter(({ id }) => {
if (!alreadySeen.has(id)) {
alreadySeen.add(id);
return true;
}
return false;
});
return uniqueKeys;
}
/**
*
* @param layersList list of layers elements, from `keyboard?.layers`
* @returns set of key IDs
*/
export function allUsedKeyIdsInLayers(layersList : LKLayers[] | null): Set<string> {
const s = new Set<string>();
if (layersList) {
for (const layers of layersList || []) {
for (const layer of layers.layer || []) {
for (const row of layer.row || []) {
if (row.keys) {
for (const k of row.keys.split(" ")) {
s.add(k);
}
}
}
}
}
}
return s;
}

View file

@ -1,9 +0,0 @@
import 'mocha';
import {assert} from 'chai';
describe('null-test', function () {
it('should-pass-null-test', function() {
assert.isTrue(true);
});
});

View file

@ -0,0 +1,99 @@
import 'mocha';
import {assert} from 'chai';
import { isValidEnumValue, calculateUniqueKeys, allUsedKeyIdsInLayers } from '../src/util/util.js';
describe('test of util/util.ts', () => {
describe('isValidEnumValue()', () => {
enum MyFruit {
apple, peach, pear, plum
};
enum MyNumbers {
wieħed=1, tnejn=2, tlieta=3
};
enum MyLetters {
akka='h', magħtugha='ħ'
};
it('should handle a bare enum', ()=> {
assert.ok(isValidEnumValue(MyFruit, 'apple'));
assert.notOk(isValidEnumValue(MyFruit, 'banana'));
assert.notOk(isValidEnumValue(MyFruit, ''));
assert.notOk(isValidEnumValue(MyFruit, undefined));
assert.notOk(isValidEnumValue(MyFruit, null));
assert.notOk(isValidEnumValue(MyFruit, 'unknown'));
});
it('should handle a numerical enum', ()=> {
assert.ok(isValidEnumValue(MyNumbers, 'tlieta'));
assert.notOk(isValidEnumValue(MyNumbers, '3'));
assert.notOk(isValidEnumValue(MyNumbers, 'seventy-six'));
assert.notOk(isValidEnumValue(MyNumbers, '76'));
});
it('should handle a string enum', ()=> {
assert.ok(isValidEnumValue(MyLetters, 'h'));
assert.ok(isValidEnumValue(MyLetters, 'ħ'));
assert.notOk(isValidEnumValue(MyLetters, 'akka'));
assert.notOk(isValidEnumValue(MyLetters, 'z'));
assert.notOk(isValidEnumValue(MyLetters, 'zed'));
});
});
describe('calculateUniqueKeys()', () => {
it('should handle some null cases', () => {
assert.sameDeepMembers(calculateUniqueKeys(null),
[]);
assert.sameDeepMembers(calculateUniqueKeys([]),
[]);
});
it('should handle some real cases', () => {
assert.sameDeepMembers(calculateUniqueKeys([
{ id: 'a', to: 'a' },
{ id: 'a', to: 'a' }, // dup
{ id: 'b', to: 'b' },
{ id: 'a', to: 'å' }, // override
]),[
{ id: 'b', to: 'b' },
{ id: 'a', to: 'å' },
]);
});
});
describe('allUsedKeyIdsInLayers()', () => {
it('should handle a null case', () => {
assert.sameDeepMembers(Array.from(allUsedKeyIdsInLayers(null).values()),
[]);
});
it('should handle a real case', () => {
assert.sameDeepMembers(Array.from(allUsedKeyIdsInLayers([
{
layer: [
{
row: [
{ keys: 'q w e r t y' },
{ keys: 'a s d f' },
]
},
{
row: [
{ keys: 'Q W E R T Y' },
{ keys: 'A S D F' },
]
}
]
},
{
layer: [
{
row: [
{ keys: 'q w e r t y' },
{ keys: '0 1 2 3' }
]
},
]
}
]).values()),
'q w e r t y Q W E R T Y a s d f A S D F 0 1 2 3'.split(' '));
});
});
});