diff --git a/developer/src/kmc-convert/src/converter-messages.ts b/developer/src/kmc-convert/src/converter-messages.ts index a915c9a068a..7c8b0d3a3e4 100644 --- a/developer/src/kmc-convert/src/converter-messages.ts +++ b/developer/src/kmc-convert/src/converter-messages.ts @@ -18,7 +18,7 @@ const SevError = CompilerErrorSeverity.Error | Namespace; 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 f60e2c285ea..bdb841438c3 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 ?? '.kmn'); 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-file-reader.ts b/developer/src/kmc-convert/src/keylayout-to-kmn/keylayout-file-reader.ts index 21c6dd5bee5..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,9 +17,10 @@ export class KeylayoutFileReader { constructor(private callbacks: CompilerCallbacks /*,private options: CompilerOptions*/) { }; - /** - * @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 +37,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 +56,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 @@ -82,6 +85,10 @@ export class KeylayoutFileReader { * @returns true if valid, false if invalid */ public validate(source: Keylayout.KeylayoutXMLSourceFile, inputFilename: string): boolean { + if (!source) { + this.callbacks.reportMessage(ConverterMessages.Error_UnableToReadFile({ inputFilename: inputFilename })); + return false; + } if (!SchemaValidators.default.keylayout(source)) { for (const err of (SchemaValidators.default.keylayout).errors) { this.callbacks.reportMessage(DeveloperUtilsMessages.Error_InvalidXml({ @@ -148,7 +155,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); 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 2e8a708e93e..c91905b2907 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 @@ -52,7 +52,7 @@ export interface KeylayoutFileData { key?: string; behavior: string; modifier?: string; - outchar?: string; + outchar?: string | undefined; }; /** @@ -109,7 +109,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) { throw new Error('Input filename is required'); @@ -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 = KeylayoutReader.read(binaryData); + const jsonO: Keylayout.KeylayoutXMLSourceFile | null = KeylayoutReader.read(binaryData); if (!jsonO) { this.callbacks.reportMessage(ConverterMessages.Error_UnableToReadFile({ inputFilename: inputFilename })); @@ -128,7 +128,7 @@ export class KeylayoutToKmnConverter { if (!KeylayoutReader.validate(jsonO, inputFilename)) { return null; } - } catch (e) { + } catch (e: any) { this.callbacks.reportMessage(ConverterMessages.Error_InvalidFile({ errorText: e.toString() })); return null; } @@ -137,10 +137,16 @@ 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; + + if (!processedData || !outputKmn) { + return null; + } const result: ConverterToKmnResult = { artifacts: { - kmn: { data: outputKmn, filename: processedData.kmnFilename } + kmn: { + data: outputKmn, filename: processedData.kmnFilename + } } }; return result; @@ -151,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[][] = []; @@ -197,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; @@ -293,8 +299,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 !== "")) { @@ -339,7 +345,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[][]; // ........................................................................................................................................................................................ @@ -373,8 +379,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) @@ -403,21 +409,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 ........................................................................................................................................................................ @@ -429,7 +436,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 ) ......................................... @@ -440,17 +447,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)), @@ -462,14 +469,15 @@ 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) - && (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); + } } } } @@ -484,7 +492,7 @@ export class KeylayoutToKmnConverter { } else { this.callbacks.reportMessage(ConverterMessages.Error_UnsupportedCharactersDetected({ 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'])) })); @@ -928,6 +936,7 @@ export class KeylayoutToKmnConverter { * @param isCAPSused : boolean flag to indicate if CAPS is used in a keylayout file or not * @return an array: KeylayoutFileData[] containing [{KeyName,actionId,behavior,modifier,output}] */ + public getKeyBehaviorModOutputArrayFromKeyActionBehaviorOutputArray(data: Keylayout.KeylayoutXMLSourceFile, search: KeylayoutFileData[], isCAPSused: boolean): KeylayoutFileData[] { const keyBehaviorModOutput = []; if (!((search === undefined) || (search === null) || (search.length === 0))) { @@ -946,7 +955,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 && @@ -997,7 +1006,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 fa70ea6acab..68cc0be3822 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"; interface MessageCharacter { @@ -47,12 +46,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); } /** @@ -60,7 +54,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 = ""; @@ -87,8 +84,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 = ""; @@ -104,7 +103,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) @@ -121,7 +120,7 @@ export class KmnFileWriter { unique.push(o); } return unique; - }, []); + }, [] as Rule[]); //................................................ C0 C1 ................................................................ @@ -158,10 +157,15 @@ export class KmnFileWriter { // const outputUnicodeCharacter = util.convertToUnicodeCharacter(outputCharacter); // const outputUnicodeCodePoint = util.convertToUnicodeCodePoint(outputCharacter); - if ((outputCharacter !== undefined) || (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; - 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 @@ -223,10 +227,13 @@ export class KmnFileWriter { // const outputUnicodeCharacter = util.convertToUnicodeCharacter(outputCharacter); // const outputUnicodeCodePoint = util.convertToUnicodeCodePoint(outputCharacter); - if ((outputCharacter !== undefined) || (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; - 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 @@ -308,10 +315,13 @@ export class KmnFileWriter { const outputCharacter = new TextDecoder().decode(uniqueDataRules[k].output); // TODO-kmc-convert: after merge of PR 14569 use functions from util instead of the ones in this class - if ((outputCharacter !== undefined) || (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; - 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 @@ -533,7 +543,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 ?? "") + "\' "); } @@ -544,7 +554,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 ?? "") + "\' "); } } @@ -629,7 +639,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 ?? "") + "\' "); } @@ -642,7 +652,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 ?? "") + "\' "); } @@ -770,7 +780,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 ?? "") + "\' "); } @@ -783,7 +793,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 ?? "") + "\' "); } @@ -844,7 +854,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 ?? "") + "\' "); } @@ -857,7 +867,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 ?? "") + "\' "); } } @@ -1626,7 +1636,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; @@ -1644,7 +1654,7 @@ 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 characters in 'U+...', '&#x...' or '&#...' format as well as in "" format @@ -1670,7 +1680,7 @@ export class KmnFileWriter { } } else { - out.character = this.convertToUnicodeCharacter(ctr);; + out.character = this.convertToUnicodeCharacter(ctr) ?? ""; } return out; } @@ -1681,7 +1691,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/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/keylayout-file-reader.tests.ts b/developer/src/kmc-convert/test/keylayout-file-reader.tests.ts index c085e019ffc..a5c4bccb550 100644 --- a/developer/src/kmc-convert/test/keylayout-file-reader.tests.ts +++ b/developer/src/kmc-convert/test/keylayout-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 () { @@ -27,6 +28,50 @@ describe('KeylayoutFileReader', function () { 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 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 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 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 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('validate() should return false on inputfiles with errors ', function () { @@ -86,6 +131,84 @@ 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]); + }); + }); + }); + describe("read() check structure of returned JSON", function () { it('read() should have the correct JSON structure', async function () { 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 989ac91e7cb..b4443a6f9f3 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 () { @@ -94,7 +95,7 @@ describe('KeylayoutToKmnConverter', function () { 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()); @@ -114,7 +115,7 @@ describe('KeylayoutToKmnConverter', function () { ['../data/OutputXName.bb'], ].forEach(function (files) { it(infile + " should run ", async function () { - await NodeAssert.doesNotReject(async () => sut.run(makePathToFixture(infile), makePathToFixture(files[0]))); + await NodeAssert.doesNotReject(async () => sut.run(makePathToFixture(infile), makePathToFixture(files[0])?? undefined)); assert.equal(compilerTestCallbacks.messages.length, 0); }); }); @@ -127,20 +128,23 @@ describe('KeylayoutToKmnConverter', function () { // ProcessedData from usable file const inputFilename = makePathToFixture('../data/Test.keylayout'); const read = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const converted = sut.unitTestEndpoints.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); + + const converted = sut.unitTestEndpoints.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.unitTestEndpoints.convert(readUnavailable, inputFilenameUnavailable.replace(/\.keylayout$/, '.kmn')); + + const convertedUnavailable = sut.unitTestEndpoints.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.unitTestEndpoints.convert(readEmpty, inputFilenameEmpty); + const convertedEmpty = sut.unitTestEndpoints.convert(readEmpty as KeylayoutXMLSourceFile, 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 null on empty name as input', async function () { @@ -287,7 +291,9 @@ describe('KeylayoutToKmnConverter', function () { const sutR = new KeylayoutFileReader(compilerTestCallbacks); const inputFilename = makePathToFixture('../data/Test.keylayout'); const read = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const converted = sut.unitTestEndpoints.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); + + const converted = sut.unitTestEndpoints.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? ']]], @@ -302,7 +308,8 @@ 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 unknown 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])); }); }); @@ -324,11 +331,13 @@ 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])); + const result = sut.getKeyModifierArrayFromActionID(read as KeylayoutXMLSourceFile, String(values[0])); assert.equal(JSON.stringify(result), JSON.stringify(values[1])); }); }); @@ -354,7 +363,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])); }); }); @@ -378,7 +387,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])); }); }); @@ -398,7 +407,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])); }); }); @@ -408,7 +417,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])); }); }); @@ -491,7 +500,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])); }); }); @@ -505,7 +514,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]])); }); }); @@ -516,7 +525,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])); }); }); @@ -563,7 +572,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])); }); }); @@ -574,7 +583,8 @@ describe('KeylayoutToKmnConverter', function () { const sutR = new KeylayoutFileReader(compilerTestCallbacks); const inputFilename = makePathToFixture('../data/Test.keylayout'); const read = sutR.read(compilerTestCallbacks.loadFile(inputFilename)); - const converted = sut.unitTestEndpoints.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); + + const converted = sut.unitTestEndpoints.convert(read as KeylayoutXMLSourceFile, inputFilename.replace(/\.keylayout$/, '.kmn')); [ ['A_1', 'A', true, [{ "outchar": "A", "actionId": "A_1", "behavior": "1", "key": "K_A", "modifier": "CAPS" }, @@ -595,12 +605,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 as KeylayoutXMLSourceFile, converted.modifiers, String(values[0]), String(values[1]), Boolean(values[2])); + assert.equal(JSON.stringify(result), JSON.stringify(values[3])); + }); + } }); }); @@ -652,7 +664,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])); }); }); @@ -682,7 +694,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])); }); }); @@ -693,7 +705,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])); }); }); @@ -703,7 +715,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])); }); }); @@ -763,8 +775,8 @@ 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.unitTestEndpoints.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); - assert.deepEqual(processedData.rules[0], values[1][0]); + const processedData = sut.unitTestEndpoints.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-writer.tests.ts b/developer/src/kmc-convert/test/kmn-file-writer.tests.ts index 41405fa3c1f..caa21faf653 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.unitTestEndpoints.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); + const converted = sut.unitTestEndpoints.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.unitTestEndpoints.convert(read, inputFilename.replace(/\.keylayout$/, '.kmn')); + const converted = sut.unitTestEndpoints.convert(read as KeylayoutXMLSourceFile, inputFilename.replace(/\.keylayout$/, '.kmn')); const outExpectedFirst: string = "c ..................................................................................................................\n" @@ -63,8 +64,14 @@ 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)); }); + 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 () { @@ -95,8 +102,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 +120,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); [ @@ -443,7 +490,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 === ''); + }); }); });