From 19ebc627b3ddf8f39df561080cafb4a1ed0bbaae Mon Sep 17 00:00:00 2001 From: Sabine Date: Mon, 20 Apr 2026 17:24:49 +0200 Subject: [PATCH 01/12] feat(developer): ensure typesafety for 'return null' and others --- .../src/kmc-convert/src/converter-messages.ts | 2 +- developer/src/kmc-convert/src/converter.ts | 4 +- .../keylayout-to-kmn-converter.ts | 52 ++++++------ .../src/keylayout-to-kmn/kmn-file-writer.ts | 67 ++++++++------- .../test/keylayout-to-kmn-converter.tests.ts | 51 ++++++------ .../kmc-convert/test/kmn-file-writer.tests.ts | 83 ++++++++++++++++++- 6 files changed, 166 insertions(+), 93 deletions(-) diff --git a/developer/src/kmc-convert/src/converter-messages.ts b/developer/src/kmc-convert/src/converter-messages.ts index 42bfff90578..ea5e9b84cfe 100644 --- a/developer/src/kmc-convert/src/converter-messages.ts +++ b/developer/src/kmc-convert/src/converter-messages.ts @@ -30,7 +30,7 @@ export class ConverterMessages { ); static ERROR_FileNotFound = SevError | 0x0003; - static Error_FileNotFound = (o: { inputFilename: string; }) => m( + static Error_FileNotFound = (o: { inputFilename: string | null; }) => m( this.ERROR_FileNotFound, `Input filename '${def(o.inputFilename)}' does not exist or could not be loaded.` ); diff --git a/developer/src/kmc-convert/src/converter.ts b/developer/src/kmc-convert/src/converter.ts index 6c32603fac9..f1858fa5b5d 100644 --- a/developer/src/kmc-convert/src/converter.ts +++ b/developer/src/kmc-convert/src/converter.ts @@ -68,9 +68,9 @@ export class Converter implements KeymanCompiler { return null; } - const ConverterClass = ConverterClassFactory.find(inputFilename, outputFilename); + const ConverterClass = ConverterClassFactory.find(inputFilename, outputFilename ??''); if (!ConverterClass) { - this.callbacks.reportMessage(ConverterMessages.Error_NoConverterFound({ inputFilename, outputFilename })); + this.callbacks.reportMessage(ConverterMessages.Error_NoConverterFound({ inputFilename, outputFilename: outputFilename ?? '' })); return null; } diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts index caf939851c3..49696712b20 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts @@ -45,12 +45,12 @@ export interface KeylayoutFileData { * Interface for storing data read from a .keylayout file and used for processing rules. * These are used for obtaining one entity form the other (e.g. from action id to output, from keycode to modifier, etc.) */ - actionId?: string; - keyCode?: string; - key?: string; - behavior?: string; + actionId?: string | undefined; + keyCode?: string | undefined; + key?: string | undefined; + behavior?: string | undefined; modifier?: string; - outchar?: string; + outchar?: string | undefined; }; export interface ActionStateOutput { @@ -117,7 +117,7 @@ export class KeylayoutToKmnConverter { constructor(private callbacks: CompilerCallbacks, options: CompilerOptions) { this.options = { ...options }; - }; + }; /** * @brief member function to run read/convert/write @@ -125,7 +125,7 @@ export class KeylayoutToKmnConverter { * @param outputFilename the resulting keyman .kmn-file * @return null on success */ - async run(inputFilename: string, outputFilename?: string): Promise { + async run(inputFilename: string, outputFilename?: string): Promise { if (!inputFilename) { this.callbacks.reportMessage(ConverterMessages.Error_FileNotFound({ inputFilename })); @@ -145,7 +145,7 @@ export class KeylayoutToKmnConverter { if (!KeylayoutReader.validate(jsonO)) { return null; } - } catch (e) { + } catch (e: any) { this.callbacks.reportMessage(ConverterMessages.Error_InvalidFile({ errorText: e.toString() })); return null; } @@ -154,10 +154,13 @@ export class KeylayoutToKmnConverter { const kmnFileWriter = new KmnFileWriter(this.callbacks, this.options); // write to object/ConverterToKmnResult - const outputKmn = kmnFileWriter.write(processedData); + const outputKmn = processedData ? kmnFileWriter.write(processedData) : null; const result: ConverterToKmnResult = { artifacts: { - kmn: { data: outputKmn, filename: processedData.kmnFilename } + kmn: { + data: outputKmn ?? new Uint8Array(0), + filename: processedData?.kmnFilename ?? "" + } } }; return result; @@ -168,7 +171,7 @@ export class KeylayoutToKmnConverter { * @param jsonObj containing filename, behaviorand rules of a json object * @return an ProcessedData containing all data ready to print out */ - private convert(jsonObj: any, inputfilename: string, outputFilename?: string): ProcessedData { + private convert(jsonObj: any, inputfilename: string, outputFilename?: string): ProcessedData | null { // modifiers for each behavior const modifierBehavior: string[][] = []; @@ -218,7 +221,7 @@ export class KeylayoutToKmnConverter { * @param jsonObj: json Object containing all data read from a keylayout file * @return an object containing the name of the input file, an array of behaviors and a populated array of Rules[] */ - public createRuleData(dataUkelele: ProcessedData, jsonObj: any): ProcessedData { + public createRuleData(dataUkelele: ProcessedData, jsonObj: any): ProcessedData | null { const rules: Rule[] = []; let dkCounterC3: number = 0; @@ -322,8 +325,8 @@ export class KeylayoutToKmnConverter { /* dk for C2*/ 0, /* unique B */ 0, - /* modifierKey*/ b1ModifierKeyObj[m].modifier, - /* key */ b1ModifierKeyObj[m].key, + /* modifierKey*/ b1ModifierKeyObj[m].modifier ?? "", + /* key */ b1ModifierKeyObj[m].key ?? "", /* output */ new TextEncoder().encode(outputchar) ); if ((outputchar !== undefined) && (outputchar !== "undefined") && (outputchar !== "")) { @@ -402,8 +405,8 @@ export class KeylayoutToKmnConverter { /* dk for C2*/ dkCounterC2++, /* unique B */ 0, - /* modifierKey*/ b1ModifierKeyObj[n4].modifier, - /* key */ b1ModifierKeyObj[n4].key, + /* modifierKey*/ b1ModifierKeyObj[n4].modifier ?? "", + /* key */ b1ModifierKeyObj[n4].key ?? "", /* output */ new TextEncoder().encode(b1ModifierKeyObj[n4].outchar), ); if ((b1ModifierKeyObj[n4].outchar !== undefined) @@ -490,8 +493,8 @@ export class KeylayoutToKmnConverter { /* dk for C2*/ 0, /* unique B */ 0, - /* modifierKey*/ b1ModifierKeyObj[n7].modifier, - /* key */ b1ModifierKeyObj[n7].key, + /* modifierKey*/ b1ModifierKeyObj[n7].modifier ?? "", + /* key */ b1ModifierKeyObj[n7].key ?? "", /* output */ new TextEncoder().encode(b1ModifierKeyObj[n7].outchar), ); if ((b1ModifierKeyObj[n7].outchar !== undefined) @@ -851,7 +854,7 @@ export class KeylayoutToKmnConverter { public getModifierArrayFromKeyModifierArray(data: any, search: KeylayoutFileData[]): string[] { const returnString1D: string[] = []; for (let i = 0; i < search.length; i++) { - returnString1D.push(data[search[i].behavior]); + returnString1D.push(data[search[i].behavior ?? ""]); } return returnString1D; } @@ -946,8 +949,7 @@ export class KeylayoutToKmnConverter { * @return an array: KeylayoutFileData[] containing [{KeyName,actionId,behavior,modifier,output}] */ public getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(data: any, search: KeylayoutFileData[], isCAPSused: boolean): KeylayoutFileData[] { - const keyBehaviorModOutput = []; - + const keyBehaviorModOutput: KeylayoutFileData[] = []; if (!((search === undefined) || (search === null) || (search.length === 0))) { for (let i = 0; i < search.length; i++) { const behaviorIdx: number = Number(search[i].behavior); @@ -964,7 +966,7 @@ export class KeylayoutToKmnConverter { } } // remove duplicates - const uniquekeyBehaviorModOutput = keyBehaviorModOutput.reduce((unique, o) => { + const uniquekeyBehaviorModOutput = keyBehaviorModOutput.reduce((unique, o) => { if (!unique.some(obj => obj.actionId === o.actionId && obj.key === o.key && @@ -988,10 +990,10 @@ export class KeylayoutToKmnConverter { * @param isCAPSused : boolean - flag to indicate if CAPS is used in a keylayout file or not * @return an array: KeylayoutFileData[] containing [{actionID,output, behavior,keyname,modifier}] */ - public getActionOutputBehaviorKeyModiFromActionIDStateOutput(data: any, modi: string[][], search: string, outchar: string, isCapsused: boolean): KeylayoutFileData[] { + public getActionOutputBehaviorKeyModiFromActionIDStateOutput(data: any, modi: string[][] | null, search: string, outchar: string, isCapsused: boolean): KeylayoutFileData[] { const actionOutputBehaviorKeyModi = []; - if ((search === "") || (search === undefined) || !((isCapsused === true) || (isCapsused === false))) { + if ((!modi)||(search === "") || (search === undefined) || !((isCapsused === true) || (isCapsused === false)|| (!modi))) { return []; } // loop behaviors (in ukelele it is possible to define multiple modifier combinations that behave in the same way) @@ -1016,7 +1018,7 @@ export class KeylayoutToKmnConverter { //............................................................................. // remove duplicates - const uniqueactionOutputBehaviorKey = actionOutputBehaviorKeyModi.reduce((unique, o) => { + const uniqueactionOutputBehaviorKey = actionOutputBehaviorKeyModi.reduce((unique, o) => { if (!unique.some(obj => obj.outchar === o.outchar && obj.actionId === o.actionId && diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts index 5f850735d71..5c01977f509 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts @@ -9,7 +9,6 @@ import { CompilerCallbacks, CompilerOptions } from "@keymanapp/developer-utils"; import { KeylayoutToKmnConverter, ProcessedData, Rule } from './keylayout-to-kmn-converter.js'; -import { ConverterMessages } from '../converter-messages.js'; import KEYMAN_VERSION from "@keymanapp/keyman-version"; export interface messageCharacter { @@ -38,12 +37,7 @@ export class KmnFileWriter { if (dataRules) data += dataStores + dataRules; - try { - return new TextEncoder().encode(data); - } catch (err) { - this.callbacks.reportMessage(ConverterMessages.Error_UnableToWrite({ outputFilename: dataUkelele.kmnFilename, errorText: err })); - return null; - } + return new TextEncoder().encode(data); } /** @@ -51,7 +45,10 @@ export class KmnFileWriter { * @param dataUkelele an object containing all data read from a .keylayout file * @return string - all stores to be printed */ - public writeKmnFileHeader(dataUkelele: ProcessedData): string { + public writeKmnFileHeader(dataUkelele: ProcessedData | null): string { + if (!dataUkelele) { + return ""; + } let data: string = ""; @@ -78,8 +75,10 @@ export class KmnFileWriter { * @param dataUkelele an object containing all data read from a .keylayout file * @return string - all rules to be printed */ - public writeDataRules(dataUkelele: ProcessedData): string { - + public writeDataRules(dataUkelele: ProcessedData | null): string { + if (!dataUkelele) { + return ""; + } const keylayoutKmnConverter = new KeylayoutToKmnConverter(this.callbacks, this.options); let data: string = ""; @@ -95,7 +94,7 @@ export class KmnFileWriter { || (curr.ruleType === "C2" && (curr.deadkey !== "")) || (curr.ruleType === "C3" && (curr.deadkey !== "") && (curr.prevDeadkey !== ""))) ); - }).reduce((unique, o) => { + }).reduce((unique, o) => { if (!unique.some((obj: Rule) => new TextDecoder().decode(obj.output) === new TextDecoder().decode(o.output) @@ -112,7 +111,7 @@ export class KmnFileWriter { unique.push(o); } return unique; - }, []); + }, [] as Rule[]); //................................................ C0 C1 ................................................................ @@ -149,10 +148,10 @@ export class KmnFileWriter { // const outputUnicodeCharacter = util.convertToUnicodeCharacter(outputCharacter); // const outputUnicodeCodePoint = util.convertToUnicodeCodePoint(outputCharacter); - if ((outputCharacter !== undefined) || (outputCharacter !== "")) { + if ((outputCharacter !== undefined) && (outputCharacter !== "")) { const characterMessage = this.writeCharacterOrUnicode(outputCharacter, warnText[2]); - versionOutputCharacter = characterMessage.character; - warnText[2] = characterMessage.message; + versionOutputCharacter = characterMessage?.character ?? ""; + warnText[2] = characterMessage?.message ?? ""; } // add a warning in front of rules in case unavailable modifiers or ambiguous rules are used @@ -202,10 +201,10 @@ export class KmnFileWriter { // const outputUnicodeCharacter = util.convertToUnicodeCharacter(outputCharacter); // const outputUnicodeCodePoint = util.convertToUnicodeCodePoint(outputCharacter); - if ((outputCharacter !== undefined) || (outputCharacter !== "")) { + if ((outputCharacter !== undefined) && (outputCharacter !== "")) { const characterMessage = this.writeCharacterOrUnicode(outputCharacter, warnText[2]); - versionOutputCharacter = characterMessage.character; - warnText[2] = characterMessage.message; + versionOutputCharacter = characterMessage?.character ?? ""; + warnText[2] = characterMessage?.message ?? ""; } // add a warning in front of rules in case unavailable modifiers or ambiguous rules are used @@ -276,10 +275,10 @@ export class KmnFileWriter { const outputCharacter = new TextDecoder().decode(uniqueDataRules[k].output); // TODO-kmc-convert: after merge of PR 14564 use functions from util instead of the ones in this class - if ((outputCharacter !== undefined) || (outputCharacter !== "")) { + if ((outputCharacter !== undefined) && (outputCharacter !== "")) { const characterMessage = this.writeCharacterOrUnicode(outputCharacter, warnText[2]); - versionOutputCharacter = characterMessage.character; - warnText[2] = characterMessage.message; + versionOutputCharacter = characterMessage?.character ?? ""; + warnText[2] = characterMessage?.message ?? ""; } // add a warning in front of rules in case unavailable modifiers or ambiguous rules are used @@ -486,7 +485,7 @@ export class KmnFileWriter { + " " + amb_1_1[0].key + "] > \'" - + this.writeCharacterOrUnicode(new TextDecoder().decode(amb_1_1[0].output)).character + + (this.writeCharacterOrUnicode(new TextDecoder().decode(amb_1_1[0].output))?.character ?? "") + "\' "); } @@ -497,7 +496,7 @@ export class KmnFileWriter { + " " + dup_1_1[0].key + "] > \'" - + this.writeCharacterOrUnicode(new TextDecoder().decode(dup_1_1[0].output)).character + + (this.writeCharacterOrUnicode(new TextDecoder().decode(dup_1_1[0].output))?.character ?? "") + "\' "); } } @@ -582,7 +581,7 @@ export class KmnFileWriter { + " " + amb_3_3[0].key + "] > \'" - + this.writeCharacterOrUnicode(new TextDecoder().decode(amb_3_3[0].output)).character + + (this.writeCharacterOrUnicode(new TextDecoder().decode(amb_3_3[0].output))?.character ?? "") + "\' "); } @@ -595,7 +594,7 @@ export class KmnFileWriter { + " " + dup_3_3[0].key + "] > \'" - + this.writeCharacterOrUnicode(new TextDecoder().decode(dup_3_3[0].output)).character + + (this.writeCharacterOrUnicode(new TextDecoder().decode(dup_3_3[0].output))?.character ?? "") + "\' "); } @@ -723,7 +722,7 @@ export class KmnFileWriter { + " " + amb_6_3[0].key + "] > \'" - + this.writeCharacterOrUnicode(new TextDecoder().decode(amb_6_3[0].output)).character + + (this.writeCharacterOrUnicode(new TextDecoder().decode(amb_6_3[0].output))?.character ?? "") + "\' "); } @@ -736,7 +735,7 @@ export class KmnFileWriter { + " " + dup_6_3[0].key + "] > \'" - + this.writeCharacterOrUnicode(new TextDecoder().decode(dup_6_3[0].output)).character + + (this.writeCharacterOrUnicode(new TextDecoder().decode(dup_6_3[0].output))?.character ?? "") + "\' "); } @@ -797,7 +796,7 @@ export class KmnFileWriter { + " " + amb_6_6[0].key + "] > \'" - + this.writeCharacterOrUnicode(new TextDecoder().decode(amb_6_6[0].output)).character + + (this.writeCharacterOrUnicode(new TextDecoder().decode(amb_6_6[0].output))?.character ?? "") + "\' "); } @@ -810,7 +809,7 @@ export class KmnFileWriter { + " " + dup_6_6[0].key + "] > \'" - + this.writeCharacterOrUnicode(new TextDecoder().decode(dup_6_6[0].output)).character + + (this.writeCharacterOrUnicode(new TextDecoder().decode(dup_6_6[0].output))?.character ?? "") + "\' "); } } @@ -856,7 +855,7 @@ export class KmnFileWriter { * a non-control character will be written as itself ( 'A', '1', '፩', '😎') * null in case of an empty string or null or undefined input */ - public writeCharacterOrUnicode(ctr: string, msg: string = ""): messageCharacter { + public writeCharacterOrUnicode(ctr: string, msg: string = ""): messageCharacter | null { if ((ctr === null) || (ctr === undefined) || (ctr.length === 0)) { return null; @@ -874,10 +873,10 @@ export class KmnFileWriter { // find the value of output character which may be specified in unicode, html hex or html dec format ( e.g. U+1234 -> 1234; ሴ -> 1234; ሴ -> 1234) const ctr_val = ((m_uni || m_hex || m_dec) ? - m_uni ? parseInt(m_uni[1], 16) : m_hex ? parseInt(m_hex[1], 16) : parseInt(m_dec[1], 10) : KeylayoutToKmnConverter.MAX_CTRL_CHARACTER + m_uni ? parseInt(m_uni[1], 16) : m_hex ? parseInt(m_hex[1], 16) : m_dec ? parseInt(m_dec[1], 10) : KeylayoutToKmnConverter.MAX_CTRL_CHARACTER : KeylayoutToKmnConverter.MAX_CTRL_CHARACTER ); - // for control charactersin 'U+...', '&#x...' or '&#...' format as well as in "" format + // for control characters in 'U+...', '&#x...' or '&#...' format as well as in "" format if ((ctr_val < KeylayoutToKmnConverter.MAX_CTRL_CHARACTER) || (ctr.charCodeAt(0) < KeylayoutToKmnConverter.MAX_CTRL_CHARACTER)) { // for control characters in 'U+...', '&#x...' or '&#...' format @@ -900,7 +899,7 @@ export class KmnFileWriter { } } else { - out.character = this.convertToUnicodeCharacter(ctr);; + out.character = this.convertToUnicodeCharacter(ctr) ?? ""; } return out; } @@ -911,7 +910,7 @@ export class KmnFileWriter { * @param inputString the value that will converted * @return a unicode character like 'c', 'ሴ', '😎' or undefined if inputString is not recognized */ - public convertToUnicodeCharacter(inputString: string): string { + public convertToUnicodeCharacter(inputString: string): string | undefined { // null, undefined will later be refused for conversion diff --git a/developer/src/kmc-convert/test/keylayout-to-kmn-converter.tests.ts b/developer/src/kmc-convert/test/keylayout-to-kmn-converter.tests.ts index bff598a3905..434f6285ae8 100644 --- a/developer/src/kmc-convert/test/keylayout-to-kmn-converter.tests.ts +++ b/developer/src/kmc-convert/test/keylayout-to-kmn-converter.tests.ts @@ -125,31 +125,24 @@ describe('KeylayoutToKmnConverter', function () { describe('run() ', function () { const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions); - it('run() should throw on null input file name and null output file name', async function () { + it('run() should throw on empty input file name and empty output file name', async function () { // note, could use 'chai as promised' library to make this more fluent: - const result = sut.run(null, null); + const result = sut.run('', ''); assert.isNotNull(result); assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(compilerTestCallbacks.messages[0], ConverterMessages.Error_FileNotFound({ inputFilename: null })); + assert.deepEqual(compilerTestCallbacks.messages[0], ConverterMessages.Error_FileNotFound({ inputFilename: '' })); }); - it('run() should throw on null input file name and empty output file name', async function () { - const result = sut.run(null, ''); + it('run() should throw on empty input file name and unknown output file name', async function () { + const result = sut.run('', 'X'); assert.isNotNull(result); assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(compilerTestCallbacks.messages[0], ConverterMessages.Error_FileNotFound({ inputFilename: null })); - }); - - it('run() should throw on null input file name and unknown output file name', async function () { - const result = sut.run(null, 'X'); - assert.isNotNull(result); - assert.equal(compilerTestCallbacks.messages.length, 1); - assert.deepEqual(compilerTestCallbacks.messages[0], ConverterMessages.Error_FileNotFound({ inputFilename: null })); + assert.deepEqual(compilerTestCallbacks.messages[0], ConverterMessages.Error_FileNotFound({ inputFilename: '' })); }); it('run() should throw on unavailable input file name and null output file name', async function () { const inputFilename = makePathToFixture('../data/Unavailable.keylayout'); - const result = sut.run(inputFilename, null); + const result = sut.run(inputFilename, undefined); assert.isNotNull(result); assert.equal(compilerTestCallbacks.messages.length, 2); assert.deepEqual(compilerTestCallbacks.messages[0], ConverterMessages.Error_UnableToRead()); @@ -170,7 +163,7 @@ describe('KeylayoutToKmnConverter', function () { [makePathToFixture('../data/OutputXName.bb')], ].forEach(function (files) { it(infile + " should run ", async function () { - await NodeAssert.doesNotReject(async () => sut.run(makePathToFixture(infile), files[0])); + await NodeAssert.doesNotReject(async () => sut.run(makePathToFixture(infile), files[0] ?? undefined)); assert.equal(compilerTestCallbacks.messages.length, 0); }); }); @@ -196,7 +189,7 @@ describe('KeylayoutToKmnConverter', function () { const convertedEmpty = sut.convertBound.convert(readEmpty, inputFilenameEmpty); it('should return converted array on correct input', async function () { - assert.isTrue(converted.rules.length !== 0); + assert.isTrue(converted?.rules.length !== 0); }); it('should return empty on empty name as input', async function () { @@ -377,7 +370,7 @@ describe('KeylayoutToKmnConverter', function () { it((values[1] !== null) ? ("getModifierArrayFromKeyModifierArray('" + JSON.stringify(values[0]) + "')").padEnd(68, " ") + " should return '" + JSON.stringify(values[1]) + "'" : ("getModifierArrayFromKeyModifierArray('" + JSON.stringify(values[0]) + "')").padEnd(68, " ") + " should return '" + "null" + "'", async function () { - const result = sut.getModifierArrayFromKeyModifierArray(converted.modifiers, values[0] as KeylayoutFileData[]); + const result = sut.getModifierArrayFromKeyModifierArray(converted?.modifiers, values[0] as KeylayoutFileData[]); assert.deepStrictEqual(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -399,8 +392,10 @@ describe('KeylayoutToKmnConverter', function () { ['', []], ].forEach(function (values) { let outstring = '[ '; - for (let i = 0; i < values[1].length; i++) { - outstring = outstring + "[ " + JSON.stringify(values[1][i]) + "], "; + if (values[1]) { + for (let i = 0; i < values[1].length; i++) { + outstring = outstring + "[ " + JSON.stringify(values[1]?.[i]) + "], "; + } } it(("getKeyModifierArrayFromActionID('" + values[0] + "')").padEnd(57, " ") + ' should return ' + outstring.substring(0, outstring.lastIndexOf(']') + 2) + " ]", async function () { const result = sut.getKeyModifierArrayFromActionID(read, String(values[0])); @@ -591,7 +586,7 @@ describe('KeylayoutToKmnConverter', function () { ].forEach(function (values) { const isCaps = true; it(("getKeybehaviorModOutputArrayFromKeyActionbehaviorOutputArray([" + values[0] + "])").padEnd(74, " ") + ' should return ' + "[" + values[1] + "]", async function () { - const result = sut.getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(read, values[0], isCaps); + const result = sut.getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(read, values[0] ?? [], isCaps); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -670,12 +665,14 @@ describe('KeylayoutToKmnConverter', function () { ['', 'a', false, []], ['', '', , []], ].forEach(function (values) { - it((JSON.stringify(values[3]).length > 35) ? - ("getActionOutputbehaviorKeyModiFromActionIDStateOutput('" + values[0] + "', '" + values[1] + "', " + values[2] + ")").padEnd(67, " ") + ' should return an array of objects' : - ("getActionOutputbehaviorKeyModiFromActionIDStateOutput('" + values[0] + "', '" + values[1] + "', " + values[2] + ")").padEnd(67, " ") + ' should return ' + "'" + JSON.stringify(values[3]) + "'", async function () { - const result = sut.getActionOutputBehaviorKeyModiFromActionIDStateOutput(read, converted.modifiers, String(values[0]), String(values[1]), Boolean(values[2])); - assert.equal(JSON.stringify(result), JSON.stringify(values[3])); - }); + if (converted) { + it((JSON.stringify(values[3]).length > 35) ? + ("getActionOutputbehaviorKeyModiFromActionIDStateOutput('" + values[0] + "', '" + values[1] + "', " + values[2] + ")").padEnd(67, " ") + ' should return an array of objects' : + ("getActionOutputbehaviorKeyModiFromActionIDStateOutput('" + values[0] + "', '" + values[1] + "', " + values[2] + ")").padEnd(67, " ") + ' should return ' + "'" + JSON.stringify(values[3]) + "'", async function () { + const result = sut.getActionOutputBehaviorKeyModiFromActionIDStateOutput(read, converted.modifiers, String(values[0]), String(values[1]), Boolean(values[2])); + assert.equal(JSON.stringify(result), JSON.stringify(values[3])); + }); + } }); }); @@ -839,7 +836,7 @@ describe('KeylayoutToKmnConverter', function () { const inputFilename = makePathToFixture(values[0][0]); const read = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); const processedData = sut.convertBound.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); - assert.deepEqual(processedData.rules[0], values[1][0]); + assert.deepEqual(processedData?.rules[0], values[1][0]); }); }); }); diff --git a/developer/src/kmc-convert/test/kmn-file-writer.tests.ts b/developer/src/kmc-convert/test/kmn-file-writer.tests.ts index 0eb23c88b46..fe234367847 100644 --- a/developer/src/kmc-convert/test/kmn-file-writer.tests.ts +++ b/developer/src/kmc-convert/test/kmn-file-writer.tests.ts @@ -21,6 +21,41 @@ describe('KmnFileWriter', function () { compilerTestCallbacks.clear(); }); + describe('RunONE', function () { + const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions); + [ + [makePathToFixture('../data/Test_mixedEncodings.keylayout')], + ].forEach(function (files) { + it(files + " should give no errors ", async function () { + sut.run(files[0]); + assert.isTrue(compilerTestCallbacks.messages.length === 0); + }); + }); + }); + + describe('RunFILES', function () { + this.timeout(10000); // allow longer time for these tests + const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions); + [ + [makePathToFixture('../data/Polish.keylayout')], + [makePathToFixture('../data/Spanish.keylayout')], + [makePathToFixture('../data/French.keylayout')], + [makePathToFixture('../data/German_complete_reduced.keylayout')], + // [makePathToFixture('../data/German_complete.keylayout')], + // [makePathToFixture('../data/German_standard.keylayout')], + [makePathToFixture('../data/Italian_command.keylayout')], + [makePathToFixture('../data/Italian.keylayout')], + [makePathToFixture('../data/Latin_American.keylayout')], + [makePathToFixture('../data/Swiss_French.keylayout')], + [makePathToFixture('../data/Swiss_German.keylayout')], + [makePathToFixture('../data/US.keylayout')], + ].forEach(function (files) { + it(files + " should give no errors ", async function () { + sut.run(files[0]); + assert.isTrue(compilerTestCallbacks.messages.length === 0); + }); + }); + }); describe("writeDataRules() ", function () { const inputFilename = makePathToFixture('../data/Test.keylayout'); const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions); @@ -63,7 +98,7 @@ describe('KmnFileWriter', function () { it(('writeKmnFileHeader should return store text with filename ').padEnd(62, " ") + 'on correct input', async function () { const writtenCorrectName = sutW.writeKmnFileHeader(converted); - assert.equal(writtenCorrectName, (outExpectedFirst + converted.keylayoutFilename + outExpectedLast)); + assert.equal(writtenCorrectName, (outExpectedFirst + (converted?.keylayoutFilename ?? "") + outExpectedLast)); }); }); @@ -74,7 +109,7 @@ describe('KmnFileWriter', function () { ["ሴ", 'ሴ'], ["😎", '😎'], ["", '\u0002'], - ["�",undefined ], + ["�", undefined], ["a", 'a'], ["ሴ", 'ሴ'], ["😆", 'πŸ˜†'], @@ -95,8 +130,16 @@ describe('KmnFileWriter', function () { ["␀", '␀'], ["␕", '␕'], ["", ''], - [undefined, undefined], - [null, undefined] + [null, undefined], + ["<", '<'], + ["&Gt", undefined], + ["U+D801", undefined], + ["�", undefined], + ["�", undefined], + ["�", undefined], + ["U+D801", undefined], + ["&#xmmm;", undefined], + ["�", undefined], ].forEach(function (values) { it(('should convert "' + values[0] + '"').padEnd(25, " ") + 'to "' + values[1] + '"', async function () { const result = sutW.convertToUnicodeCharacter(values[0] as string); @@ -105,6 +148,38 @@ describe('KmnFileWriter', function () { }); }); + describe('writeCharacterOrUnicode ', function () { + const sutW = new KmnFileWriter(compilerTestCallbacks, compilerTestOptions); + [ + ["A", "Msg", "A", "Msg"], + ["ሴ", "Msg", "ሴ", "Msg"], + ["πŸ˜€", "Msg", "πŸ˜€", "Msg"], + ["ẘ", "Msg", "ẘ", "Msg"], + ["U+0001", "Msg", "U+0001", "Msg; Use of a control character "], + ["U+0061", "Msg", "a", "Msg"], + ["", "Msg", "U+0002", "Msg; Use of a control character "], + ["ሴ", "Msg", 'ሴ', "Msg",], + ["", "Msg", "U+0003", "Msg; Use of a control character "], + ["ሺ", "Msg", "ሺ", "Msg",], + [null, "Msg", null, null], + [undefined, "Msg", null, null], + ["", "Msg", null, null], + ["", "Msg", "U+0006", "Msg; Use of a control character "], + ].forEach(function (values) { + it(('should convert "' + values[0] + '"').padEnd(25, " ") + 'to "' + values[2] + '"', async function () { + const result = sutW.writeCharacterOrUnicode(values[0] as string, values[1] as string); + if (result) { + assert.equal(result.character, values[2]); + assert.equal(result.message, values[3]); + } + else { + assert.isNull(values[2]); + assert.isNull(values[3]); + } + }); + }); + }); + describe('reviewRules messages', function () { const sutW = new KmnFileWriter(compilerTestCallbacks, compilerTestOptions); [ From 30ea90484ef2c25d57ad55fd6be9b24783c6e406 Mon Sep 17 00:00:00 2001 From: Sabine Date: Mon, 20 Apr 2026 17:47:37 +0200 Subject: [PATCH 02/12] feat(developer): remove tests for entire keyboards --- .../kmc-convert/test/kmn-file-writer.tests.ts | 35 ------------------- 1 file changed, 35 deletions(-) diff --git a/developer/src/kmc-convert/test/kmn-file-writer.tests.ts b/developer/src/kmc-convert/test/kmn-file-writer.tests.ts index fe234367847..89d70d83f3e 100644 --- a/developer/src/kmc-convert/test/kmn-file-writer.tests.ts +++ b/developer/src/kmc-convert/test/kmn-file-writer.tests.ts @@ -21,41 +21,6 @@ describe('KmnFileWriter', function () { compilerTestCallbacks.clear(); }); - describe('RunONE', function () { - const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions); - [ - [makePathToFixture('../data/Test_mixedEncodings.keylayout')], - ].forEach(function (files) { - it(files + " should give no errors ", async function () { - sut.run(files[0]); - assert.isTrue(compilerTestCallbacks.messages.length === 0); - }); - }); - }); - - describe('RunFILES', function () { - this.timeout(10000); // allow longer time for these tests - const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions); - [ - [makePathToFixture('../data/Polish.keylayout')], - [makePathToFixture('../data/Spanish.keylayout')], - [makePathToFixture('../data/French.keylayout')], - [makePathToFixture('../data/German_complete_reduced.keylayout')], - // [makePathToFixture('../data/German_complete.keylayout')], - // [makePathToFixture('../data/German_standard.keylayout')], - [makePathToFixture('../data/Italian_command.keylayout')], - [makePathToFixture('../data/Italian.keylayout')], - [makePathToFixture('../data/Latin_American.keylayout')], - [makePathToFixture('../data/Swiss_French.keylayout')], - [makePathToFixture('../data/Swiss_German.keylayout')], - [makePathToFixture('../data/US.keylayout')], - ].forEach(function (files) { - it(files + " should give no errors ", async function () { - sut.run(files[0]); - assert.isTrue(compilerTestCallbacks.messages.length === 0); - }); - }); - }); describe("writeDataRules() ", function () { const inputFilename = makePathToFixture('../data/Test.keylayout'); const sut = new KeylayoutToKmnConverter(compilerTestCallbacks, compilerTestOptions); From 6802ec4da7cf3365fc763e0283a0c09b288c7595 Mon Sep 17 00:00:00 2001 From: Sabine Date: Mon, 20 Apr 2026 22:38:53 +0200 Subject: [PATCH 03/12] feat(developer): read() throw error instead of return null --- .../kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts index 2dc98fdb3c5..30c8ea6051a 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts @@ -85,7 +85,7 @@ export class KeylayoutFileReader { } catch (err) { this.callbacks.reportMessage(ConverterMessages.Error_UnableToRead()); - return null; + throw new Error('Failed to parse keylayout file'); } } } From 9b9cafe720383455e0c4f5cc112ec78a278288b0 Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 21 Apr 2026 10:08:11 +0200 Subject: [PATCH 04/12] feat(developer): more typesafety + add tests for validate() --- developer/src/kmc-convert/src/converter.ts | 2 +- .../keylayout-to-kmn/keylayout-file-reader.ts | 10 +++-- .../keylayout-to-kmn-converter.ts | 7 +++- .../src/keylayout-to-kmn/kmn-file-writer.ts | 9 ++++ .../kmc-convert/test/kmn-file-reader.tests.ts | 42 +++++++++++++++++++ 5 files changed, 64 insertions(+), 6 deletions(-) diff --git a/developer/src/kmc-convert/src/converter.ts b/developer/src/kmc-convert/src/converter.ts index f1858fa5b5d..a1e696e7b8e 100644 --- a/developer/src/kmc-convert/src/converter.ts +++ b/developer/src/kmc-convert/src/converter.ts @@ -68,7 +68,7 @@ export class Converter implements KeymanCompiler { return null; } - const ConverterClass = ConverterClassFactory.find(inputFilename, outputFilename ??''); + const ConverterClass = ConverterClassFactory.find(inputFilename, outputFilename ?? '.kmn'); if (!ConverterClass) { this.callbacks.reportMessage(ConverterMessages.Error_NoConverterFound({ inputFilename, outputFilename: outputFilename ?? '' })); return null; diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts index 30c8ea6051a..aeb0d7737b0 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts @@ -19,7 +19,11 @@ export class KeylayoutFileReader { /** * @returns true if valid, false if invalid */ - public validate(source: Keylayout.KeylayoutXMLSourceFile): boolean { + public validate(source: Keylayout.KeylayoutXMLSourceFile|null): boolean { + if (!source) { + this.callbacks.reportMessage(ConverterMessages.Error_UnableToRead()); + return false; + } if (!SchemaValidators.default.keylayout(source)) { for (const err of (SchemaValidators.default.keylayout).errors) { this.callbacks.reportMessage(DeveloperUtilsMessages.Error_InvalidXml({ @@ -75,7 +79,7 @@ export class KeylayoutFileReader { * @param inputFilename the ukelele .keylayout-file to be parsed * @return in case of success: json object containing data of the .keylayout file; else null */ - public read(source: Uint8Array): Keylayout.KeylayoutXMLSourceFile { + public read(source: Uint8Array): Keylayout.KeylayoutXMLSourceFile | null { try { const data = new TextDecoder().decode(source); @@ -85,7 +89,7 @@ export class KeylayoutFileReader { } catch (err) { this.callbacks.reportMessage(ConverterMessages.Error_UnableToRead()); - throw new Error('Failed to parse keylayout file'); + return null; } } } diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts index 49696712b20..2a887f2798e 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts @@ -155,6 +155,10 @@ export class KeylayoutToKmnConverter { // write to object/ConverterToKmnResult const outputKmn = processedData ? kmnFileWriter.write(processedData) : null; + + if (!processedData || !outputKmn) { + return null; + } const result: ConverterToKmnResult = { artifacts: { kmn: { @@ -992,8 +996,7 @@ export class KeylayoutToKmnConverter { */ public getActionOutputBehaviorKeyModiFromActionIDStateOutput(data: any, modi: string[][] | null, search: string, outchar: string, isCapsused: boolean): KeylayoutFileData[] { const actionOutputBehaviorKeyModi = []; - - if ((!modi)||(search === "") || (search === undefined) || !((isCapsused === true) || (isCapsused === false)|| (!modi))) { + if ((!modi) || (search === "") || (search === undefined) ) { return []; } // loop behaviors (in ukelele it is possible to define multiple modifier combinations that behave in the same way) diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts index 5c01977f509..94daec484cd 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts @@ -148,6 +148,9 @@ export class KmnFileWriter { // const outputUnicodeCharacter = util.convertToUnicodeCharacter(outputCharacter); // const outputUnicodeCodePoint = util.convertToUnicodeCodePoint(outputCharacter); + // in case writeCharacterOrUnicode() returns null, the fallback is empty strings for characterMessage.character + // and characterMessage.message. Then versionOutputCharacter could be "" and would be written into the kmn file + // as ... > '', producing an invalid kmn rule. if ((outputCharacter !== undefined) && (outputCharacter !== "")) { const characterMessage = this.writeCharacterOrUnicode(outputCharacter, warnText[2]); versionOutputCharacter = characterMessage?.character ?? ""; @@ -201,6 +204,9 @@ export class KmnFileWriter { // const outputUnicodeCharacter = util.convertToUnicodeCharacter(outputCharacter); // const outputUnicodeCodePoint = util.convertToUnicodeCodePoint(outputCharacter); + // in case writeCharacterOrUnicode() returns null, the fallback is empty strings for characterMessage.character + // and characterMessage.message. Then versionOutputCharacter could be "" and would be written into the kmn file + // as ... > '', producing an invalid kmn rule. if ((outputCharacter !== undefined) && (outputCharacter !== "")) { const characterMessage = this.writeCharacterOrUnicode(outputCharacter, warnText[2]); versionOutputCharacter = characterMessage?.character ?? ""; @@ -275,6 +281,9 @@ export class KmnFileWriter { const outputCharacter = new TextDecoder().decode(uniqueDataRules[k].output); // TODO-kmc-convert: after merge of PR 14564 use functions from util instead of the ones in this class + // in case writeCharacterOrUnicode() returns null, the fallback is empty strings for characterMessage.character + // and characterMessage.message. Then versionOutputCharacter could be "" and would be written into the kmn file + // as ... > '', producing an invalid kmn rule. if ((outputCharacter !== undefined) && (outputCharacter !== "")) { const characterMessage = this.writeCharacterOrUnicode(outputCharacter, warnText[2]); versionOutputCharacter = characterMessage?.character ?? ""; diff --git a/developer/src/kmc-convert/test/kmn-file-reader.tests.ts b/developer/src/kmc-convert/test/kmn-file-reader.tests.ts index c68e0e52415..35da499d44d 100644 --- a/developer/src/kmc-convert/test/kmn-file-reader.tests.ts +++ b/developer/src/kmc-convert/test/kmn-file-reader.tests.ts @@ -9,6 +9,7 @@ import 'mocha'; import { assert } from 'chai'; +import { Keylayout } from "@keymanapp/developer-utils"; import { compilerTestCallbacks, makePathToFixture } from './helpers/index.js'; import { KeylayoutFileReader } from '../src/keylayout-to-kmn/keylayout-file-reader.js'; @@ -18,6 +19,47 @@ describe('KeylayoutFileReader', function () { compilerTestCallbacks.clear(); }); + describe("validate() ", function () { + + it('validate() should return true on correct inputfile', async function () { + const sutR = new KeylayoutFileReader(compilerTestCallbacks); + const inputFilename = makePathToFixture('../data/Test.keylayout'); + const result: Keylayout.KeylayoutXMLSourceFile|null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(result); + assert.isTrue(validated); + }); + + it('validate() should return false on inputfile with unknown tags', async function () { + const sutR = new KeylayoutFileReader(compilerTestCallbacks); + const inputFilename = makePathToFixture('../data/Test_unknownTags.keylayout'); + const result: Keylayout.KeylayoutXMLSourceFile|null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(result); + assert.isFalse(validated); + }); + + it('validate() should return false on inputfile with additional tags', async function () { + const sutR = new KeylayoutFileReader(compilerTestCallbacks); + const inputFilename = makePathToFixture('../data/Test_additionalTags.keylayout'); + const result: Keylayout.KeylayoutXMLSourceFile|null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(result); + assert.isFalse(validated); + }); + it('validate() should return false on inputfile with missing tags', async function () { + const sutR = new KeylayoutFileReader(compilerTestCallbacks); + const inputFilename = makePathToFixture('../data/Test_missingTags.keylayout'); + const result: Keylayout.KeylayoutXMLSourceFile|null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(result); + assert.isFalse(validated); + }); + it('validate() should return false on no entries in action-when', async function () { + const sutR = new KeylayoutFileReader(compilerTestCallbacks); + const inputFilename = makePathToFixture('../data/Test_noActionWhen.keylayout'); + const result: Keylayout.KeylayoutXMLSourceFile|null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(result); + assert.isFalse(validated); + }); + }); + describe("read() ", function () { const sutR = new KeylayoutFileReader(compilerTestCallbacks); From ef1cf18746f2ce8f9d32638f96ac7731bf1e2020 Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 21 Apr 2026 10:50:24 +0200 Subject: [PATCH 05/12] feat(developer): more typesafety --- .../src/keylayout-to-kmn/keylayout-to-kmn-converter.ts | 5 ++--- .../src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts | 3 +-- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts index 2a887f2798e..28321f6bfc7 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts @@ -162,8 +162,7 @@ export class KeylayoutToKmnConverter { const result: ConverterToKmnResult = { artifacts: { kmn: { - data: outputKmn ?? new Uint8Array(0), - filename: processedData?.kmnFilename ?? "" + data: outputKmn, filename: processedData.kmnFilename } } }; @@ -996,7 +995,7 @@ export class KeylayoutToKmnConverter { */ public getActionOutputBehaviorKeyModiFromActionIDStateOutput(data: any, modi: string[][] | null, search: string, outchar: string, isCapsused: boolean): KeylayoutFileData[] { const actionOutputBehaviorKeyModi = []; - if ((!modi) || (search === "") || (search === undefined) ) { + if ((!modi) || (search === "") || (search === undefined)) { return []; } // loop behaviors (in ukelele it is possible to define multiple modifier combinations that behave in the same way) diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts index 94daec484cd..f1388e935c6 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/kmn-file-writer.ts @@ -87,8 +87,7 @@ export class KmnFileWriter { // (e.g. when in a keylayout file the same modifiers occur in several behaviors thus producing the same rules). // This is to filter out those duplicate Rule objects const uniqueDataRules: Rule[] = dataUkelele.rules.filter((curr) => { - return (!(curr.output === new TextEncoder().encode("") || curr.output === undefined) - && (curr.key !== "") + return ((curr.key !== "") && ((curr.ruleType === "C0") || (curr.ruleType === "C1") || (curr.ruleType === "C2" && (curr.deadkey !== "")) From 417c044d9048127465910bcda86d6b4996f87151 Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 21 Apr 2026 16:44:34 +0200 Subject: [PATCH 06/12] feat(developer): baseUrl problem of tsconfig.json, errorMsg typo --- developer/src/kmc-convert/src/converter-messages.ts | 2 +- .../src/keylayout-to-kmn/keylayout-to-kmn-converter.ts | 2 +- developer/src/kmc-convert/test/tsconfig.json | 1 + developer/src/kmc-convert/tsconfig.json | 1 + 4 files changed, 4 insertions(+), 2 deletions(-) diff --git a/developer/src/kmc-convert/src/converter-messages.ts b/developer/src/kmc-convert/src/converter-messages.ts index ea5e9b84cfe..f95e497b869 100644 --- a/developer/src/kmc-convert/src/converter-messages.ts +++ b/developer/src/kmc-convert/src/converter-messages.ts @@ -26,7 +26,7 @@ export class ConverterMessages { static ERROR_IntputFilenameIsRequired = SevError | 0x0002; static Error_IntputFilenameIsRequired = () => m( this.ERROR_IntputFilenameIsRequired, - `An output filename is required for keyboard conversion.` + `An Input filename is required for keyboard conversion.` ); static ERROR_FileNotFound = SevError | 0x0003; diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts index 28321f6bfc7..743bf7d1876 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts @@ -135,7 +135,7 @@ export class KeylayoutToKmnConverter { const KeylayoutReader = new KeylayoutFileReader(this.callbacks/*, this.options*/); const binaryData = this.callbacks.loadFile(inputFilename); - const jsonO: Keylayout.KeylayoutXMLSourceFile = KeylayoutReader.read(binaryData); + const jsonO: Keylayout.KeylayoutXMLSourceFile|null = KeylayoutReader.read(binaryData); if (!jsonO) { this.callbacks.reportMessage(ConverterMessages.Error_UnableToReadFile({ inputFilename: inputFilename })); diff --git a/developer/src/kmc-convert/test/tsconfig.json b/developer/src/kmc-convert/test/tsconfig.json index 78127d66db9..9c543db4b18 100644 --- a/developer/src/kmc-convert/test/tsconfig.json +++ b/developer/src/kmc-convert/test/tsconfig.json @@ -6,6 +6,7 @@ "rootDirs": ["./", "../src/"], "outDir": "../build/test", "baseUrl": ".", + "ignoreDeprecations": "6.0", }, "include": [ "**/*.tests.ts", diff --git a/developer/src/kmc-convert/tsconfig.json b/developer/src/kmc-convert/tsconfig.json index 32235e24687..72a38d4a96e 100644 --- a/developer/src/kmc-convert/tsconfig.json +++ b/developer/src/kmc-convert/tsconfig.json @@ -5,6 +5,7 @@ "outDir": "build/src/", "rootDir": "src/", "baseUrl": ".", + "ignoreDeprecations": "6.0", }, "include": [ "src/**/*.ts" From 47168873df32f7dbc01ecc895150c0b24551eaa8 Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 21 Apr 2026 17:02:33 +0200 Subject: [PATCH 07/12] feat(developer): restore tsconfig.json --- developer/src/kmc-convert/test/tsconfig.json | 1 - developer/src/kmc-convert/tsconfig.json | 1 - 2 files changed, 2 deletions(-) diff --git a/developer/src/kmc-convert/test/tsconfig.json b/developer/src/kmc-convert/test/tsconfig.json index 9c543db4b18..78127d66db9 100644 --- a/developer/src/kmc-convert/test/tsconfig.json +++ b/developer/src/kmc-convert/test/tsconfig.json @@ -6,7 +6,6 @@ "rootDirs": ["./", "../src/"], "outDir": "../build/test", "baseUrl": ".", - "ignoreDeprecations": "6.0", }, "include": [ "**/*.tests.ts", diff --git a/developer/src/kmc-convert/tsconfig.json b/developer/src/kmc-convert/tsconfig.json index 72a38d4a96e..32235e24687 100644 --- a/developer/src/kmc-convert/tsconfig.json +++ b/developer/src/kmc-convert/tsconfig.json @@ -5,7 +5,6 @@ "outDir": "build/src/", "rootDir": "src/", "baseUrl": ".", - "ignoreDeprecations": "6.0", }, "include": [ "src/**/*.ts" From 1d3d53e2e7897eb44ae09f22647b95d503546e07 Mon Sep 17 00:00:00 2001 From: Sabine Date: Mon, 4 May 2026 20:04:43 +0200 Subject: [PATCH 08/12] feat(developer): add returntypes --- .../keylayout-to-kmn/keylayout-file-reader.ts | 2 +- .../keylayout-to-kmn-converter.ts | 54 ++++++++++--------- .../test/keylayout-to-kmn-converter.tests.ts | 45 ++++++++-------- .../kmc-convert/test/kmn-file-reader.tests.ts | 20 +++---- 4 files changed, 62 insertions(+), 59 deletions(-) diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts index ec5fd3bea45..0be7550ef83 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts @@ -81,7 +81,7 @@ export class KeylayoutFileReader { /** * @returns true if valid, false if invalid */ - public validate(source: Keylayout.KeylayoutXMLSourceFile|null): boolean { + public validate(source: Keylayout.KeylayoutXMLSourceFile, inputFilename: string): boolean { if (!source) { this.callbacks.reportMessage(ConverterMessages.Error_UnableToRead()); return false; diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts index 609df132747..e3c8175c524 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-to-kmn-converter.ts @@ -118,7 +118,7 @@ export class KeylayoutToKmnConverter { const KeylayoutReader = new KeylayoutFileReader(this.callbacks/*, this.options*/); const binaryData = this.callbacks.loadFile(inputFilename); - const jsonO: Keylayout.KeylayoutXMLSourceFile|null = KeylayoutReader.read(binaryData); + const jsonO: Keylayout.KeylayoutXMLSourceFile | null = KeylayoutReader.read(binaryData); if (!jsonO) { this.callbacks.reportMessage(ConverterMessages.Error_UnableToReadFile({ inputFilename: inputFilename })); @@ -157,7 +157,7 @@ export class KeylayoutToKmnConverter { * @param jsonObj containing filename, behaviorand rules of a json object * @return an ProcessedData containing all data ready to print out */ - private convert(jsonObj: Keylayout.KeylayoutXMLSourceFile, inputfilename: string, outputFilename?: string): ProcessedData { + private convert(jsonObj: Keylayout.KeylayoutXMLSourceFile, inputfilename: string, outputFilename?: string): ProcessedData | null { // modifiers for each behavior const modifierBehavior: string[][] = []; @@ -203,7 +203,7 @@ export class KeylayoutToKmnConverter { * @param jsonObj: json Object containing all data read from a keylayout file * @return an object containing the name of the input file, an array of behaviors and a populated array of Rules[] */ - public createRuleData(dataUkelele: ProcessedData, jsonObj: Keylayout.KeylayoutXMLSourceFile): ProcessedData { + public createRuleData(dataUkelele: ProcessedData, jsonObj: Keylayout.KeylayoutXMLSourceFile): ProcessedData | null { const rules: Rule[] = []; let dkCounterC3: number = 0; @@ -346,7 +346,7 @@ export class KeylayoutToKmnConverter { // with present actionId (a18) find all keycode-behavior-pairs that use this action (a18) => (keymapIndex 0/keycode 24 and keymapIndex 3/keycode 24) .................................... // from these create an array of modifier combinations e.g. [['','caps?'], ['Caps']] ..................................................................................................... /* eg: [['24', 0], ['24', 3]] */ const b4DeadkeyObj: KeylayoutFileData[] = this.getKeyModifierArrayFromActionID(jsonObj, actionId); - /* e.g. [['','caps?'], ['Caps']]*/ const b4DeadkeyModifierObj: string[][] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b4DeadkeyObj); + /* e.g. [['','caps?'], ['Caps']]*/ const b4DeadkeyModifierObj: string[][] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b4DeadkeyObj) as string[][]; // ........................................................................................................................................................................................ @@ -410,21 +410,22 @@ export class KeylayoutToKmnConverter { // with actionId from above loop all 'action' and search for a state-next-pair ................................................................................................................... // e.g. in Block 5: find for action id a16 ............................................................................................................................. - for (let l = 0; l < jsonObj.keyboard.actions.action[b1ActionIndex].when.length; l++) { - if ((jsonObj.keyboard.actions.action[b1ActionIndex].when[l]['state'] !== "none") - && (jsonObj.keyboard.actions.action[b1ActionIndex].when[l]['next'] !== undefined)) { + if (jsonObj.keyboard.actions?.action?.[b1ActionIndex]?.when) { + for (const when of jsonObj.keyboard.actions.action[b1ActionIndex].when) { + if ((when['state'] !== "none") + && (when['next'] !== undefined)) { // Data of Block Nr 5 ........................................................................................................................................................................ // of this state-next-pair get value of next (next="1") and state="3" ........................................................................................................................ - /* e.g. state = 3 */ const b5ValueState: string = jsonObj.keyboard.actions.action[b1ActionIndex].when[l]['state']; - /* e.g. next = 1 */ const b5ValueNext: string = jsonObj.keyboard.actions.action[b1ActionIndex].when[l]['next']; + /* e.g. state = 3 */ const b5ValueState: string = when['state'] as string; + /* e.g. next = 1 */ const b5ValueNext: string = when['next']; // ........................................................................................................................................................................................... // Data of Block Nr 4 ........................................................................................................................................................................ // with present actionId (a16) find all keycode-behavior-pairs that use this action (a16) => (keymapIndex 3/keycode 32) .................................................................... // from these create an array of modifier combinations e.g. [ [ 'anyOption', 'Caps' ] ] ..................................................................................................... /* e.g. [['32', 3]] */ const b4DeadkeyObj: KeylayoutFileData[] = this.getKeyModifierArrayFromActionID(jsonObj, actionId); - /* e.g. [ [ 'anyOption', 'Caps' ] ]*/ const b4DeadkeyModifierObj: string[][] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b4DeadkeyObj); + /* e.g. [ [ 'anyOption', 'Caps' ] ]*/ const b4DeadkeyModifierObj: string[][] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b4DeadkeyObj) as string[][]; // ........................................................................................................................................................................................... // Data of Block Nr 3 ........................................................................................................................................................................ @@ -436,7 +437,7 @@ export class KeylayoutToKmnConverter { // with present actionId (a17) find all key names and behaviors that use this action (a17) => (keymapIndex 3/keycode 28) .................................................................... // from these create an array of modifier combinations e.g. [ [ 'anyOption', 'Caps' ] ] ..................................................................................................... /* eg: index=3 */ const b2PrevDeadkeyObj: KeylayoutFileData[] = this.getKeyModifierArrayFromActionID(jsonObj, b3ActionId); - /* e.g. [ [ 'anyOption', 'Caps' ] ] */ const b2PrevDeadkeyModifierObj: string[][] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b2PrevDeadkeyObj); + /* e.g. [ [ 'anyOption', 'Caps' ] ] */ const b2PrevDeadkeyModifierObj: string[][] = this.getModifierArrayFromKeyModifierArray(dataUkelele.modifiers, b2PrevDeadkeyObj) as string[][]; // ........................................................................................................................................................................................... // Data of Block Nr 6 ........................................................................................................................................................................ // create an array[action id,state,output] from all state-output-pairs that use state = b5ValueNext (e.g. use 1 in ) ......................................... @@ -447,17 +448,17 @@ export class KeylayoutToKmnConverter { // create array[Keycode,Keyname,action id,actionIndex,output] and array[Keyname,action id,behavior,modifier,output] ......................................................................... /* eg: ['49','K_SPACE','a0','0','Γ‚'] */ const b1KeycodeObj: KeylayoutFileData[] = this.getKeyActionOutputArrayFromActionStateOutputArray(jsonObj, b6ActionIdObj); /* eg: ['K_SPACE','a0','0','NCAPS','Γ‚'] */ const b1ModifierKeyObj: KeylayoutFileData[] = this.getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(jsonObj, b1KeycodeObj, isCapsused); - // ........................................................................................................................................................................................... + // ........................................................................................................................................................................................... - for (let n1 = 0; n1 < b2PrevDeadkeyModifierObj.length; n1++) { - for (let n2 = 0; n2 < b2PrevDeadkeyModifierObj[n1].length; n2++) { - for (let n3 = 0; n3 < b2PrevDeadkeyObj.length; n3++) { - for (let n4 = 0; n4 < b4DeadkeyModifierObj.length; n4++) { - for (let n5 = 0; n5 < b4DeadkeyModifierObj[n4].length; n5++) { - for (let n6 = 0; n6 < b4DeadkeyObj.length; n6++) { - for (let n7 = 0; n7 < b1ModifierKeyObj.length; n7++) { + for (let n1 = 0; n1 < b2PrevDeadkeyModifierObj.length; n1++) { + for (let n2 = 0; n2 < b2PrevDeadkeyModifierObj[n1].length; n2++) { + for (let n3 = 0; n3 < b2PrevDeadkeyObj.length; n3++) { + for (let n4 = 0; n4 < b4DeadkeyModifierObj.length; n4++) { + for (let n5 = 0; n5 < b4DeadkeyModifierObj[n4].length; n5++) { + for (let n6 = 0; n6 < b4DeadkeyObj.length; n6++) { + for (let n7 = 0; n7 < b1ModifierKeyObj.length; n7++) { - ruleObj = new Rule( + ruleObj = new Rule( /* ruleType */ "C3", /* modifierPrevDeadkey*/ this.createKmnModifier(b2PrevDeadkeyModifierObj[n1][n2], isCapsused), /* prevDeadkey */ this.mapUkeleleKeycodeToVK(Number(b2PrevDeadkeyObj[n3].key)), @@ -472,11 +473,12 @@ export class KeylayoutToKmnConverter { /* modifierKey*/ b1ModifierKeyObj[n7].modifier ?? "", /* key */ b1ModifierKeyObj[n7].key ?? "", /* output */ new TextEncoder().encode(b1ModifierKeyObj[n7].outchar), - ); - if ((b1ModifierKeyObj[n7].outchar !== undefined) - && (b1ModifierKeyObj[n7].outchar !== "undefined") - && (b1ModifierKeyObj[n7].outchar !== "")) { - rules.push(ruleObj); + ); + if ((b1ModifierKeyObj[n7].outchar !== undefined) + && (b1ModifierKeyObj[n7].outchar !== "undefined") + && (b1ModifierKeyObj[n7].outchar !== "")) { + rules.push(ruleObj); + } } } } @@ -492,7 +494,7 @@ export class KeylayoutToKmnConverter { this.callbacks.reportMessage(ConverterMessages.Error_UnsupportedCharactersDetected({ inputFilename: jsonObj.keyboard['name'] + ".keylayout", keymapIndex: jsonObj.keyboard.keyMapSet[0].keyMap[i]['index'], - output: jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['output'], + output: jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['output'] as string, key: jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['code'], KeyName: this.mapUkeleleKeycodeToVK(Number(jsonObj.keyboard.keyMapSet[0].keyMap[i].key[j]['code'])) })); diff --git a/developer/src/kmc-convert/test/keylayout-to-kmn-converter.tests.ts b/developer/src/kmc-convert/test/keylayout-to-kmn-converter.tests.ts index 97f478f0ccc..99d6e1fc272 100644 --- a/developer/src/kmc-convert/test/keylayout-to-kmn-converter.tests.ts +++ b/developer/src/kmc-convert/test/keylayout-to-kmn-converter.tests.ts @@ -13,6 +13,7 @@ import { compilerTestCallbacks, compilerTestOptions, makePathToFixture } from '. import { ActionStateOutput, KeylayoutFileData, KeylayoutToKmnConverter, Rule } from '../src/keylayout-to-kmn/keylayout-to-kmn-converter.js'; import { KeylayoutFileReader } from '../src/keylayout-to-kmn/keylayout-file-reader.js'; import { ConverterMessages } from '../src/converter-messages.js'; +import { KeylayoutXMLSourceFile } from '../../common/web/utils/src/types/keylayout/keylayout-xml.js'; describe('KeylayoutToKmnConverter', function () { @@ -155,17 +156,17 @@ describe('KeylayoutToKmnConverter', function () { // ProcessedData from usable file const inputFilename = makePathToFixture('../data/Test.keylayout'); const read = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const converted = sut.convertBound.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); + const converted = sut.convertBound.convert(read as KeylayoutXMLSourceFile, inputFilename.replace(/\.keylayout$/, '.kmn')); // ProcessedData from unavailable file const inputFilenameUnavailable = makePathToFixture('../data/X.keylayout'); const readUnavailable = sutR.read(compilerTestCallbacks.loadFile(inputFilenameUnavailable)); - const convertedUnavailable = sut.convertBound.convert(readUnavailable, inputFilenameUnavailable.replace(/\.keylayout$/, '.kmn')); + const convertedUnavailable = sut.convertBound.convert(readUnavailable as KeylayoutXMLSourceFile, inputFilenameUnavailable.replace(/\.keylayout$/, '.kmn')); // ProcessedData from empty file const inputFilenameEmpty = makePathToFixture(''); const readEmpty = sutR.read(compilerTestCallbacks.loadFile(inputFilenameEmpty)); - const convertedEmpty = sut.convertBound.convert(readEmpty, inputFilenameEmpty); + const convertedEmpty = sut.convertBound.convert(readEmpty as KeylayoutXMLSourceFile, inputFilenameEmpty); it('should return converted array on correct input', async function () { assert.isTrue(converted?.rules.length !== 0); @@ -180,7 +181,7 @@ describe('KeylayoutToKmnConverter', function () { }); it('should return empty array of rules on null input', async function () { - const convertedRule = sut.convertBound.convert(null, 'ABC.kmn'); + const convertedRule = sut.convertBound.convert(null , 'ABC.kmn'); assert.isNull(convertedRule); }); }); @@ -317,7 +318,7 @@ describe('KeylayoutToKmnConverter', function () { const sutR = new KeylayoutFileReader(compilerTestCallbacks); const inputFilename = makePathToFixture('../data/Test.keylayout'); const read = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const converted = sut.convertBound.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); + const converted = sut.convertBound.convert(read as KeylayoutXMLSourceFile, inputFilename.replace(/\.keylayout$/, '.kmn')); [ [[{ key: '0', behavior: 0 }], [['', 'shift? caps? ']]], [[{ key: '0', behavior: 2 }], [['shift? leftShift caps? ', 'anyShift caps?', 'shift leftShift caps ', 'shift? rightShift caps? ']]], @@ -332,7 +333,7 @@ describe('KeylayoutToKmnConverter', function () { it((values[1] !== null) ? ("getModifierArrayFromKeyModifierArray('" + JSON.stringify(values[0]) + "')").padEnd(68, " ") + " should return '" + JSON.stringify(values[1]) + "'" : ("getModifierArrayFromKeyModifierArray('" + JSON.stringify(values[0]) + "')").padEnd(68, " ") + " should return '" + "null" + "'", async function () { - const result = sut.getModifierArrayFromKeyModifierArray(converted?.modifiers, values[0] as KeylayoutFileData[]); + const result = sut.getModifierArrayFromKeyModifierArray(converted?.modifiers as string[][], values[0] as unknown as KeylayoutFileData[]); assert.deepStrictEqual(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -360,7 +361,7 @@ describe('KeylayoutToKmnConverter', function () { } } it(("getKeyModifierArrayFromActionID('" + values[0] + "')").padEnd(57, " ") + ' should return ' + outstring.substring(0, outstring.lastIndexOf(']') + 2) + " ]", async function () { - const result = sut.getKeyModifierArrayFromActionID(read, String(values[0])); + const result = sut.getKeyModifierArrayFromActionID(read as KeylayoutXMLSourceFile, String(values[0])); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -386,7 +387,7 @@ describe('KeylayoutToKmnConverter', function () { ['unknown', ''], ].forEach(function (values) { it(("getActionIdFromActionNext('" + values[0] + "')").padEnd(49, " ") + ' should return ' + "'" + values[1] + "'", async function () { - const result = sut.getActionIdFromActionNext(read, String(values[0])); + const result = sut.getActionIdFromActionNext(read as KeylayoutXMLSourceFile, String(values[0])); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -410,7 +411,7 @@ describe('KeylayoutToKmnConverter', function () { ['unknown', -1], ].forEach(function (values) { it(("getActionIndexFromActionId('" + values[0] + "')").padEnd(50, " ") + ' should return ' + values[1], async function () { - const result = sut.getActionIndexFromActionId(read, String(values[0])); + const result = sut.getActionIndexFromActionId(read as KeylayoutXMLSourceFile, String(values[0])); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -430,7 +431,7 @@ describe('KeylayoutToKmnConverter', function () { ].forEach(function (values) { it( ("getOutputFromActionIdNone('" + values[0] + "')").padEnd(56, " ") + ' should return ' + "'" + values[1] + "'", async function () { - const result = sut.getOutputFromActionIdNone(read, String(values[0])); + const result = sut.getOutputFromActionIdNone(read as KeylayoutXMLSourceFile, String(values[0])); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -440,7 +441,7 @@ describe('KeylayoutToKmnConverter', function () { [99, ''], ].forEach(function (values) { it(("getOutputFromActionIdNone('" + values[0] + "')").padEnd(56, " ") + ' should return ' + values[1], async function () { - const result = sut.getOutputFromActionIdNone(read, String(values[0])); + const result = sut.getOutputFromActionIdNone(read as KeylayoutXMLSourceFile, String(values[0])); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -523,7 +524,7 @@ describe('KeylayoutToKmnConverter', function () { it((JSON.stringify(values[1]).length > 60) ? 'an array of objects should return an array of objects' : stringIn.padEnd(74, " ") + ' should return ' + stringOut, async function () { - const result = sut.getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(read, values[0], isCapsUsed); + const result = sut.getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(read as KeylayoutXMLSourceFile, values[0], isCapsUsed); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -537,7 +538,7 @@ describe('KeylayoutToKmnConverter', function () { const stringOut = "['" + values[1].actionId + "', '" + "', '" + values[1].modifier + "', '" + values[1].key + "', '" + values[1].outchar + "']"; it(stringIn.padEnd(74, " ") + ' should return ' + stringOut, async function () { - const result = sut.getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(read, [values[0]], isCapsUsed); + const result = sut.getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(read as KeylayoutXMLSourceFile, [values[0]], isCapsUsed); assert.equal(JSON.stringify(result), JSON.stringify([values[1]])); }); }); @@ -548,7 +549,7 @@ describe('KeylayoutToKmnConverter', function () { ].forEach(function (values) { const isCaps = true; it(("getKeybehaviorModOutputArrayFromKeyActionbehaviorOutputArray([" + values[0] + "])").padEnd(74, " ") + ' should return ' + "[" + values[1] + "]", async function () { - const result = sut.getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(read, values[0] ?? [], isCaps); + const result = sut.getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(read as KeylayoutXMLSourceFile, values[0] ?? [], isCaps); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -595,7 +596,7 @@ describe('KeylayoutToKmnConverter', function () { it((JSON.stringify(values[1]).length > 30) ? ("getActionStateOutputArrayFromActionState('" + values[0] + "')").padEnd(60, " ") + ' should return an array of objects' : ("getActionStateOutputArrayFromActionState('" + values[0] + "')").padEnd(60, " ") + ' should return ' + "'" + JSON.stringify(values[1]) + "'", async function () { - const result = sut.getActionStateOutputArrayFromActionState(read, String(values[0])); + const result = sut.getActionStateOutputArrayFromActionState(read as KeylayoutXMLSourceFile, String(values[0])); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -606,7 +607,7 @@ describe('KeylayoutToKmnConverter', function () { const sutR = new KeylayoutFileReader(compilerTestCallbacks); const inputFilename = makePathToFixture('../data/Test.keylayout'); const read = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const converted = sut.convertBound.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); + const converted = sut.convertBound.convert(read as KeylayoutXMLSourceFile, inputFilename.replace(/\.keylayout$/, '.kmn')); [ ['A_1', 'A', true, [{ "outchar": "A", "actionId": "A_1", "behavior": "1", "key": "K_A", "modifier": "CAPS" }, @@ -631,7 +632,7 @@ describe('KeylayoutToKmnConverter', function () { it((JSON.stringify(values[3]).length > 35) ? ("getActionOutputbehaviorKeyModiFromActionIDStateOutput('" + values[0] + "', '" + values[1] + "', " + values[2] + ")").padEnd(67, " ") + ' should return an array of objects' : ("getActionOutputbehaviorKeyModiFromActionIDStateOutput('" + values[0] + "', '" + values[1] + "', " + values[2] + ")").padEnd(67, " ") + ' should return ' + "'" + JSON.stringify(values[3]) + "'", async function () { - const result = sut.getActionOutputBehaviorKeyModiFromActionIDStateOutput(read, converted.modifiers, String(values[0]), String(values[1]), Boolean(values[2])); + const result = sut.getActionOutputBehaviorKeyModiFromActionIDStateOutput(read as KeylayoutXMLSourceFile, converted.modifiers, String(values[0]), String(values[1]), Boolean(values[2])); assert.equal(JSON.stringify(result), JSON.stringify(values[3])); }); } @@ -686,7 +687,7 @@ describe('KeylayoutToKmnConverter', function () { [[b6ActionIdArr, b1KeycodeArr], ].forEach(function (values) { it(("getKeyActionOutputArrayFromActionStateOutputArray([['" + JSON.stringify(values[0]) + "'],..])").padEnd(73, " ") + '1 should return an array of objects', async function () { - const result = sut.getKeyActionOutputArrayFromActionStateOutputArray(read, values[0] as ActionStateOutput[]); + const result = sut.getKeyActionOutputArrayFromActionStateOutputArray(read as KeylayoutXMLSourceFile, values[0] as ActionStateOutput[]); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -716,7 +717,7 @@ describe('KeylayoutToKmnConverter', function () { [[{ "id": "A_0", "state": "", "output": "Λ†" }], oneEntryResult], ].forEach(function (values) { it(("getKeyActionOutputArrayFromActionStateOutputArray(['" + JSON.stringify(values[0]) + "'])").padEnd(73, " ") + ' should return an array of objects', async function () { - const result = sut.getKeyActionOutputArrayFromActionStateOutputArray(read, values[0] as ActionStateOutput[]); + const result = sut.getKeyActionOutputArrayFromActionStateOutputArray(read as KeylayoutXMLSourceFile, values[0] as ActionStateOutput[]); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -727,7 +728,7 @@ describe('KeylayoutToKmnConverter', function () { ].forEach(function (values) { it(("getKeyActionOutputArrayFromActionStateOutputArray(" + JSON.stringify(values[0]) + ")").padEnd(73, " ") + ' should return ' + "'[" + JSON.stringify(values[1]) + "]'", async function () { - const result = sut.getKeyActionOutputArrayFromActionStateOutputArray(read, values[0] as ActionStateOutput[]); + const result = sut.getKeyActionOutputArrayFromActionStateOutputArray(read as KeylayoutXMLSourceFile, values[0] as ActionStateOutput[]); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -737,7 +738,7 @@ describe('KeylayoutToKmnConverter', function () { [null, []], ].forEach(function (values) { it(("getKeyActionOutputArrayFromActionStateOutputArray(" + JSON.stringify(values[0]) + ")").padEnd(73, " ") + ' should return ' + "'[" + JSON.stringify(values[1]) + "]'", async function () { - const result = sut.getKeyActionOutputArrayFromActionStateOutputArray(read, values[0] as ActionStateOutput[]); + const result = sut.getKeyActionOutputArrayFromActionStateOutputArray(read as KeylayoutXMLSourceFile, values[0] as ActionStateOutput[]); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -797,7 +798,7 @@ describe('KeylayoutToKmnConverter', function () { it('data of \'' + values[0] + "' passed into createRuleData() " + 'should create an array of rules', async function () { const inputFilename = makePathToFixture(values[0][0]); const read = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const processedData = sut.convertBound.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); + const processedData = sut.convertBound.convert(read as KeylayoutXMLSourceFile, inputFilename.replace(/\.keylayout$/, '.kmn')); assert.deepEqual(processedData?.rules[0], values[1][0]); }); }); diff --git a/developer/src/kmc-convert/test/kmn-file-reader.tests.ts b/developer/src/kmc-convert/test/kmn-file-reader.tests.ts index 35da499d44d..c3509a5dd49 100644 --- a/developer/src/kmc-convert/test/kmn-file-reader.tests.ts +++ b/developer/src/kmc-convert/test/kmn-file-reader.tests.ts @@ -24,38 +24,38 @@ describe('KeylayoutFileReader', function () { it('validate() should return true on correct inputfile', async function () { const sutR = new KeylayoutFileReader(compilerTestCallbacks); const inputFilename = makePathToFixture('../data/Test.keylayout'); - const result: Keylayout.KeylayoutXMLSourceFile|null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const validated = sutR.validate(result); + const result: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(result as Keylayout.KeylayoutXMLSourceFile, inputFilename); assert.isTrue(validated); }); it('validate() should return false on inputfile with unknown tags', async function () { const sutR = new KeylayoutFileReader(compilerTestCallbacks); const inputFilename = makePathToFixture('../data/Test_unknownTags.keylayout'); - const result: Keylayout.KeylayoutXMLSourceFile|null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const validated = sutR.validate(result); + const result: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(result as Keylayout.KeylayoutXMLSourceFile, inputFilename); assert.isFalse(validated); }); it('validate() should return false on inputfile with additional tags', async function () { const sutR = new KeylayoutFileReader(compilerTestCallbacks); const inputFilename = makePathToFixture('../data/Test_additionalTags.keylayout'); - const result: Keylayout.KeylayoutXMLSourceFile|null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const validated = sutR.validate(result); + const result: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(result as Keylayout.KeylayoutXMLSourceFile, inputFilename); assert.isFalse(validated); }); it('validate() should return false on inputfile with missing tags', async function () { const sutR = new KeylayoutFileReader(compilerTestCallbacks); const inputFilename = makePathToFixture('../data/Test_missingTags.keylayout'); - const result: Keylayout.KeylayoutXMLSourceFile|null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const validated = sutR.validate(result); + const result: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(result as Keylayout.KeylayoutXMLSourceFile, inputFilename); assert.isFalse(validated); }); it('validate() should return false on no entries in action-when', async function () { const sutR = new KeylayoutFileReader(compilerTestCallbacks); const inputFilename = makePathToFixture('../data/Test_noActionWhen.keylayout'); - const result: Keylayout.KeylayoutXMLSourceFile|null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const validated = sutR.validate(result); + const result: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(result as Keylayout.KeylayoutXMLSourceFile, inputFilename); assert.isFalse(validated); }); }); From 9675611e89ea93ebb2539ae20e3bdb3cbf410e02 Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 5 May 2026 13:10:21 +0200 Subject: [PATCH 09/12] feat(developer): add tests and comments --- .../keylayout-to-kmn/keylayout-file-reader.ts | 12 ++- .../kmc-convert/test/kmn-file-reader.tests.ts | 93 +++++++++++++++++++ .../kmc-convert/test/kmn-file-writer.tests.ts | 11 +++ 3 files changed, 112 insertions(+), 4 deletions(-) diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts index 0be7550ef83..d8443e369bd 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts @@ -19,7 +19,9 @@ export class KeylayoutFileReader { /** - * @brief helper function to find a specific keyMap index in a keyMapSet + * @brief helper function to check if a specific keyMap index exists in a keyMapSet + * neccessary because the amount of must correspond to + * the amount of * @param jsonObj the read keylayout data to be checked * @param keyMapSelect the keyMapSelect element to find in keyMapSet * @return true if the keyMapSet element is found, false if not @@ -36,7 +38,9 @@ export class KeylayoutFileReader { } /** - * @brief helper function to find a specific keyMapSelect index in a modifierMap + * @brief helper function to check if a specific keyMapSelect index exists in a modifierMap + * neccessary because the amount of must correspond to + * the amount of * @param jsonObj the read keylayout data to be checked * @param keyMap the keyMap element to find in modifierMap * @return true if the keyMap element is found, false if not @@ -53,8 +57,8 @@ export class KeylayoutFileReader { } /** - * @brief member function checking if all keyMapSelect elements have a corresponding keyMap - * element in the .keylayout file (if not, the .keylayout file is invalid and will not be converted) + * @brief member function checking if all keyMapSelect elements have exact one corresponding keyMap element (per keyMapSet) + * in the .keylayout file (if not, the .keylayout file is invalid and will not be converted) * see TN2056 (https://developer.apple.com/library/archive/technotes/tn2056/_index.html#//apple_ref/doc/uid/DTS10003085-CH1-SUBSECTION7) * @param jsonObj the read keylayout data to be checked * @return true if all keyMapSelect elements have a corresponding keyMap element, false if not diff --git a/developer/src/kmc-convert/test/kmn-file-reader.tests.ts b/developer/src/kmc-convert/test/kmn-file-reader.tests.ts index c3509a5dd49..5ea9f9e244a 100644 --- a/developer/src/kmc-convert/test/kmn-file-reader.tests.ts +++ b/developer/src/kmc-convert/test/kmn-file-reader.tests.ts @@ -12,6 +12,7 @@ import { assert } from 'chai'; import { Keylayout } from "@keymanapp/developer-utils"; import { compilerTestCallbacks, makePathToFixture } from './helpers/index.js'; import { KeylayoutFileReader } from '../src/keylayout-to-kmn/keylayout-file-reader.js'; +import { KL_KeyMap, KL_KeyMapSelect } from "../../common/web/utils/src/types/keylayout/keylayout-xml.js"; describe('KeylayoutFileReader', function () { @@ -58,6 +59,20 @@ describe('KeylayoutFileReader', function () { const validated = sutR.validate(result as Keylayout.KeylayoutXMLSourceFile, inputFilename); assert.isFalse(validated); }); + it('validate() should return false on null as input', async function () { + const sutR = new KeylayoutFileReader(compilerTestCallbacks); + const inputFilename = makePathToFixture('../data/Test_noActionWhen.keylayout'); + //const result: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(null, inputFilename); + assert.isFalse(validated); + }); + it('validate() should return false on undefined as input', async function () { + const sutR = new KeylayoutFileReader(compilerTestCallbacks); + const inputFilename = makePathToFixture('../data/Test_noActionWhen.keylayout'); + //const result: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + const validated = sutR.validate(undefined, inputFilename); + assert.isFalse(validated); + }); }); describe("read() ", function () { @@ -91,4 +106,82 @@ describe('KeylayoutFileReader', function () { }); }); + describe('findMapIndexinKeymap ', function () { + const keyMapSelect: KL_KeyMapSelect = { + mapIndex: '', + modifier: [] + }; + + keyMapSelect.modifier.push({ keys: 'caps' }); + keyMapSelect.modifier.push({ keys: 'rightOption' }); + keyMapSelect.modifier.push({ keys: 'rightShift caps' }); + + const sutR = new KeylayoutFileReader(compilerTestCallbacks); + const inputFilename = makePathToFixture('../data/Test.keylayout'); + const jsonO: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + [ + ['0', true], + ['7', true], + ['999', false], + ['A', false], + [123, false], + ['', false], + [null, false], + [undefined, false], + ].forEach(function (values) { + it(("findMapIndexinKeymap(keyMapSelect.mapIndex = '" + values[0] + "')").padEnd(40, " ") + "should return " + "'" + values[1] + "'", async function () { + keyMapSelect.mapIndex = values[0] as string; + const result = sutR.findMapIndexinKeymap(jsonO as Keylayout.KeylayoutXMLSourceFile, keyMapSelect); + assert.isTrue(result === values[1]); + }); + }); + }); + + describe('findIndexinKeymapSelect ', function () { + const keyMap: KL_KeyMap = { + index: '', + key: [] + }; + keyMap.key.push({ code: '0', output: 'A' }); + keyMap.key.push({ code: '1', action: 'S' }); + keyMap.key.push({ code: '2', output: 'D' }); + + const sutR = new KeylayoutFileReader(compilerTestCallbacks); + const inputFilename = makePathToFixture('../data/Test.keylayout'); + const jsonO: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); + [ + ['0', true], + ['7', true], + ['999', false], + ['A', false], + [123, false], + ['', false], + [null, false], + [undefined, false], + ].forEach(function (values) { + it(("findIndexinKeymapSelect(keyMap.index = '" + values[0] + "')").padEnd(40, " ") + "should return " + "'" + values[1] + "'", async function () { + keyMap.index = values[0] as string; + const result = sutR.findIndexinKeymapSelect(jsonO as Keylayout.KeylayoutXMLSourceFile, keyMap); + assert.isTrue(result === values[1]); + }); + }); + }); + + describe('checkForCorrespondingElements ', function () { + const sutR = new KeylayoutFileReader(compilerTestCallbacks); + [ + ['../data/Test.keylayout', true], + ['../data/Test_sameKeyMapAndKeyMapselectAndJisERROR.keylayout', true], + ['../data/Test_moreKeymapSelectThanKeymapERROR.keylayout', false], + ['../data/Test_moreKeyMapThanKeyMapselectERROR.keylayout', false], + ['../data/Test_moreKeyMapThanKeyMapselectAndJisERROR.keylayout', false], + ].forEach(function (values) { + it(("checkForCorrespondingElements in " + values[0] ).padEnd(40, " ") + "should return " + "'" + values[1] + "'", async function () { + const jsonO: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(makePathToFixture(values[0] as string))); + const result = sutR.checkForCorrespondingElements(jsonO as Keylayout.KeylayoutXMLSourceFile); + assert.isTrue(result === values[1]); + }); + }); + }); + }); diff --git a/developer/src/kmc-convert/test/kmn-file-writer.tests.ts b/developer/src/kmc-convert/test/kmn-file-writer.tests.ts index c3ee842bab7..635caff3549 100644 --- a/developer/src/kmc-convert/test/kmn-file-writer.tests.ts +++ b/developer/src/kmc-convert/test/kmn-file-writer.tests.ts @@ -65,6 +65,12 @@ describe('KmnFileWriter', function () { const writtenCorrectName = sutW.writeKmnFileHeader(converted); assert.equal(writtenCorrectName, (outExpectedFirst + (converted?.keylayoutFilename ?? "") + outExpectedLast)); }); + it(('writeKmnFileHeader should return no text with null filename ').padEnd(62, " ") + 'on correct input', async function () { + const writtenEmptytName = sutW.writeKmnFileHeader(null); + assert.equal(writtenEmptytName, ''); + }); + + }); describe('convertToUnicodeCharacter ', function () { @@ -483,7 +489,12 @@ describe('KmnFileWriter', function () { const result1 = sutW.writeDataRules(data); assert.isTrue(result1 === values[1][0]); }); + }); + it(('null should create empty string '), async function () { + const result1 = sutW.writeDataRules(null); + assert.isTrue(result1 === ''); + }); }); }); From 302e7b0aa569cbb83963f5587ab980baa09ba077 Mon Sep 17 00:00:00 2001 From: Sabine Date: Tue, 5 May 2026 13:19:40 +0200 Subject: [PATCH 10/12] feat(developer): add testfile --- ...KeyMapAndKeyMapselectAndJisERROR.keylayout | 78 +++++++++++++++++++ .../kmc-convert/test/kmn-file-writer.tests.ts | 5 +- 2 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 developer/src/kmc-convert/test/data/Test_sameKeyMapAndKeyMapselectAndJisERROR.keylayout diff --git a/developer/src/kmc-convert/test/data/Test_sameKeyMapAndKeyMapselectAndJisERROR.keylayout b/developer/src/kmc-convert/test/data/Test_sameKeyMapAndKeyMapselectAndJisERROR.keylayout new file mode 100644 index 00000000000..ca02f833f00 --- /dev/null +++ b/developer/src/kmc-convert/test/data/Test_sameKeyMapAndKeyMapselectAndJisERROR.keylayout @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/developer/src/kmc-convert/test/kmn-file-writer.tests.ts b/developer/src/kmc-convert/test/kmn-file-writer.tests.ts index 635caff3549..6610d783571 100644 --- a/developer/src/kmc-convert/test/kmn-file-writer.tests.ts +++ b/developer/src/kmc-convert/test/kmn-file-writer.tests.ts @@ -11,6 +11,7 @@ import 'mocha'; import { assert } from 'chai'; import KEYMAN_VERSION from "@keymanapp/keyman-version"; import { compilerTestCallbacks, compilerTestOptions, makePathToFixture } from './helpers/index.js'; +import { KeylayoutXMLSourceFile } from '../../common/web/utils/src/types/keylayout/keylayout-xml.js'; import { KeylayoutToKmnConverter, ProcessedData, Rule } from '../src/keylayout-to-kmn/keylayout-to-kmn-converter.js'; import { KmnFileWriter } from '../src/keylayout-to-kmn/kmn-file-writer.js'; import { KeylayoutFileReader } from '../src/keylayout-to-kmn/keylayout-file-reader.js'; @@ -27,7 +28,7 @@ describe('KmnFileWriter', function () { const sutR = new KeylayoutFileReader(compilerTestCallbacks); const sutW = new KmnFileWriter(compilerTestCallbacks, compilerTestOptions); const read = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const converted = sut.convertBound.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); + const converted = sut.convertBound.convert(read as KeylayoutXMLSourceFile, inputFilename.replace(/\.keylayout$/, '.kmn')); it('writeDataRules() should return true (no error) if written', async function () { const result = sutW.writeDataRules(converted); @@ -42,7 +43,7 @@ describe('KmnFileWriter', function () { const sutW = new KmnFileWriter(compilerTestCallbacks, compilerTestOptions); const inputFilename = makePathToFixture('../data/Test.keylayout'); const read = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const converted = sut.convertBound.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); + const converted = sut.convertBound.convert(read as KeylayoutXMLSourceFile, inputFilename.replace(/\.keylayout$/, '.kmn')); const outExpectedFirst: string = "c ..................................................................................................................\n" From 5052581b076bab96dd71cb6f594674ac7d1d4635 Mon Sep 17 00:00:00 2001 From: Sabine Date: Wed, 20 May 2026 13:18:31 +0200 Subject: [PATCH 11/12] feat(developer): add missing bracket --- .../src/kmc-convert/test/keylayout-file-reader.tests.ts | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/developer/src/kmc-convert/test/keylayout-file-reader.tests.ts b/developer/src/kmc-convert/test/keylayout-file-reader.tests.ts index ea0725a32a1..a5c4bccb550 100644 --- a/developer/src/kmc-convert/test/keylayout-file-reader.tests.ts +++ b/developer/src/kmc-convert/test/keylayout-file-reader.tests.ts @@ -71,6 +71,7 @@ describe('KeylayoutFileReader', function () { //const result: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); const validated = sutR.validate(undefined, inputFilename); assert.isFalse(validated); + }); }); describe('validate() should return false on inputfiles with errors ', function () { @@ -200,11 +201,14 @@ describe('KeylayoutFileReader', function () { ['../data/Test_moreKeyMapThanKeyMapselectERROR.keylayout', false], ['../data/Test_moreKeyMapThanKeyMapselectAndJisERROR.keylayout', false], ].forEach(function (values) { - it(("checkForCorrespondingElements in " + values[0] ).padEnd(40, " ") + "should return " + "'" + values[1] + "'", async function () { + it(("checkForCorrespondingElements in " + values[0]).padEnd(40, " ") + "should return " + "'" + values[1] + "'", async function () { const jsonO: Keylayout.KeylayoutXMLSourceFile | null = sutR.read(compilerTestCallbacks.loadFile(makePathToFixture(values[0] as string))); const result = sutR.checkForCorrespondingElements(jsonO as Keylayout.KeylayoutXMLSourceFile); assert.isTrue(result === values[1]); }); + }); + }); + describe("read() check structure of returned JSON", function () { it('read() should have the correct JSON structure', async function () { From 82c8af203476b38dc0331dad9d6512bc85a244e1 Mon Sep 17 00:00:00 2001 From: Sabine Date: Wed, 20 May 2026 22:36:43 +0200 Subject: [PATCH 12/12] feat(developer): dummy change to start team city --- .../kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts index a28ce155297..099193e32b8 100644 --- a/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts +++ b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts @@ -17,7 +17,6 @@ export class KeylayoutFileReader { constructor(private callbacks: CompilerCallbacks /*,private options: CompilerOptions*/) { }; - /** * @brief helper function to check if a specific keyMap index exists in a keyMapSet * neccessary because the amount of must correspond to