diff --git a/.github/workflows/deb-packaging.yml b/.github/workflows/deb-packaging.yml index 8c745bef3071cbfadd576e89610860a4fb7c0712..8495f5577a6617e31affd811a93b27038295aadb 100644 --- a/.github/workflows/deb-packaging.yml +++ b/.github/workflows/deb-packaging.yml @@ -119,8 +119,8 @@ jobs: platform: "${{ matrix.arch }}" source_dir: "artifacts/keyman-srcpkg" sourcepackage: "keyman_${{ needs.sourcepackage.outputs.VERSION }}-1.dsc" - deb_fullname: $DEBFULLNAME - deb_email: $DEBEMAIL + deb_fullname: ${{env.DEBFULLNAME}} + deb_email: ${{env.DEBEMAIL}} prerelease_tag: ${{ needs.sourcepackage.outputs.PRERELEASE_TAG }} - name: Output resulting .deb files diff --git a/HISTORY.md b/HISTORY.md index c05c241eff96740ca9e41e287d1a851045aaf025..50d64334643faebc41b87d6c8af2d042dab78340 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,37 @@ # Keyman Version History +## 17.0.118 alpha 2023-06-05 + +* chore(ios): replace fv cert (#8900) +* fix(core): Fix compilation if hotdoc is installed (#8912) +* feat(linux): Rename column and add tooltip (#8918) + +## 17.0.117 alpha 2023-06-04 + +* chore(developer): verify long lines compile correctly (#8915) + +## 17.0.116 alpha 2023-06-02 + +* feat(linux): Add column for installation location (#8897) +* chore(developer): verify kvks files and report errors (#8892) +* refactor(developer): rearrange kmcmplib interface source (#8899) +* refactor(developer): move filename consistency check to kmc (#8907) +* chore(developer): loadFile callback error check and optimization (#8908) +* chore(common): remove url module ref from common/web/types (#8914) + +## 17.0.115 alpha 2023-06-01 + +* refactor(developer): complete fs move out of kmcmplib (#8882) +* fix(common): tweak pack/publish support for npm 9.5.1 and node 18.16.0 (#8894) +* feat(linux): Implement Options page and option to disable Sentry error reporting (#7989) + +## 17.0.114 alpha 2023-05-31 + +* chore(developer): replace cwrap wasm bindings (#8857) +* chore(developer): refactor kmcmplib interfaces (#8870) +* chore(developer): move keyboard repo fixtures (#8874) +* chore(linux): Move build steps to build.sh (#8864) + ## 17.0.113 alpha 2023-05-26 * docs(android): Update documentation for building Android on Linux (#8860) diff --git a/VERSION.md b/VERSION.md index a3a3dc809e3ff7ff42f204abcc79812de3a7b640..7d14d88a17924860f9933506ff2ae81ec0be6d1f 100644 --- a/VERSION.md +++ b/VERSION.md @@ -1 +1 @@ -17.0.114 \ No newline at end of file +17.0.119 \ No newline at end of file diff --git a/common/models/templates/build.sh b/common/models/templates/build.sh index b5ae2a8c236991d95c7f3e41edf2543f947e3087..0fe305ca5ad6dbc337deb59dad00554b7e3406c5 100755 --- a/common/models/templates/build.sh +++ b/common/models/templates/build.sh @@ -18,8 +18,9 @@ cd "$(dirname "$THIS_SCRIPT")" ################################ Main script ################################ builder_describe "Builds the predictive-text model template implementation module" \ - "@../../web/keyman-version" \ - "@../wordbreakers" \ + "@/common/web/keyman-version" \ + "@/common/web/tslib" \ + "@/common/models/wordbreakers" \ "clean" \ "configure" \ "build" \ diff --git a/common/models/templates/tsconfig.json b/common/models/templates/tsconfig.json index 9284bddd209ed1961257b57f5a69802315727412..7ff603ef29d85f26f774ea751fa7dfd2030b507a 100644 --- a/common/models/templates/tsconfig.json +++ b/common/models/templates/tsconfig.json @@ -7,6 +7,7 @@ "moduleResolution": "node16", "sourceMap": true, "sourceRoot": "/common/models/templates/src", + "importHelpers": true, "inlineSources": true, "strict": true, "lib": ["es6"], diff --git a/common/models/wordbreakers/src/default/index.ts b/common/models/wordbreakers/src/default/index.ts index 65dbd0faf00e4aca401e7d2f808b79a2a89f3de2..20e159ed1988e81ae47cd07300ee56307c38f364 100644 --- a/common/models/wordbreakers/src/default/index.ts +++ b/common/models/wordbreakers/src/default/index.ts @@ -431,7 +431,7 @@ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): numb } // Do not break across certain punctuation // WB6: (Don't break before apostrophes in contractions) - const SET_ALL_MIDLETTER = [WordBreakProperty.MidLetter, ...SET_MIDNUMLETQ]; + const SET_ALL_MIDLETTER = [WordBreakProperty.MidLetter].concat(SET_MIDNUMLETQ); if(state.match(null, SET_AHLETTER, SET_ALL_MIDLETTER, SET_AHLETTER)) { continue; } @@ -474,7 +474,7 @@ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): numb } // Do not break within sequences, such as 3.2, 3,456.789 // WB11 - const SET_ALL_MIDNUM = [WordBreakProperty.MidNum, ...SET_MIDNUMLETQ]; + const SET_ALL_MIDNUM = [WordBreakProperty.MidNum].concat(SET_MIDNUMLETQ); if(state.match([WordBreakProperty.Numeric], SET_ALL_MIDNUM, [WordBreakProperty.Numeric], null)) { continue; } @@ -488,9 +488,7 @@ function findBoundaries(text: string, options?: DefaultWordBreakerOptions): numb } // Do not break from extenders (e.g., U+202F NARROW NO-BREAK SPACE) // WB13a - const SET_NUM_KAT_LET = [WordBreakProperty.Katakana, - WordBreakProperty.Numeric, - ...SET_AHLETTER]; + const SET_NUM_KAT_LET = [WordBreakProperty.Katakana, WordBreakProperty.Numeric].concat(SET_AHLETTER); if(state.match(null, SET_NUM_KAT_LET, [WordBreakProperty.ExtendNumLet], null)) { continue; } diff --git a/common/models/wordbreakers/tsconfig.json b/common/models/wordbreakers/tsconfig.json index 6c029da40af640ef26854455c45f800dc263a4de..549b7410d6e99f994c68f7b8590adc3a0c9a1cd9 100644 --- a/common/models/wordbreakers/tsconfig.json +++ b/common/models/wordbreakers/tsconfig.json @@ -8,6 +8,7 @@ "moduleResolution": "Node16", "declaration": true, "sourceMap": true, + "importHelpers": true, "inlineSources": true, "sourceRoot": "/common/models/wordbreakers/src", "strict": true, diff --git a/common/predictive-text/build-bundler.js b/common/predictive-text/build-bundler.js index 2b2bff8713af7a23c451b2c5e8ecb7ffdb543ddd..a666e008fd6270fb454670f49027b07cb74acb9c 100644 --- a/common/predictive-text/build-bundler.js +++ b/common/predictive-text/build-bundler.js @@ -9,6 +9,9 @@ import esbuild from 'esbuild'; import { spawn } from 'child_process'; await esbuild.build({ + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", diff --git a/common/predictive-text/build.sh b/common/predictive-text/build.sh index 56680781f82e5ee69edc250dd5667a1edd518ee6..4fe5a744cb5fc87e126955f9cf2e8900cf8c0bd7 100755 --- a/common/predictive-text/build.sh +++ b/common/predictive-text/build.sh @@ -26,6 +26,7 @@ cd "$(dirname "$THIS_SCRIPT")" builder_describe "Builds the lm-layer module" \ "@/common/web/keyman-version" \ + "@/common/web/tslib" \ "@/common/web/lm-worker" \ "clean" \ "configure" \ diff --git a/common/predictive-text/src/node/mappedWorker.ts b/common/predictive-text/src/node/mappedWorker.ts new file mode 100644 index 0000000000000000000000000000000000000000..aafbba52323d6f516f40d867b449cd5e07377aac --- /dev/null +++ b/common/predictive-text/src/node/mappedWorker.ts @@ -0,0 +1,88 @@ +// We use a subset of the Worker interface here; compiling directly against the true +// WebWorker type definitions would require us to implement more methods than we do. +/// + +// Defines types related to Node workers. +import * as worker from 'worker_threads'; +import { Buffer } from 'buffer'; +import { URL } from 'url'; + +/** + * Defines mappings from Node Worker signatures to WebWorker signatures + */ +const nodeWorkerToWebWorkerMappingSource = ` +import { parentPort } from 'worker_threads'; +import fs from 'fs'; +import vm from 'vm'; + +function postMessage(...args) { + parentPort.postMessage.call(parentPort, args); +} + +parentPort.on('message', (ev) => { + onmessage({data: ev}); +}); + +function importScripts(...args) { + function loadScriptInContext(scriptPath) { + let scriptStr = fs.readFileSync(scriptPath); + var script = new vm.Script(scriptStr, { filename: scriptPath }); + script.runInThisContext(); + } + + for(let arg of args) { + loadScriptInContext(arg); + } +} + +/* + * You'd think the method signature mapping would be implied from the first line, + * but all three lines must be explicitly specified or the emulation will fail. + */ +const self = globalThis; +self.postMessage = postMessage; +self.importScripts = importScripts; +`; + +/** + * Uses the Node version of Workers to provide proper, authentic separate-thread + * 'sandboxing'. Also intercepts and interprets certain WebWorker method signatures + * necessary to run the WebWorker-oriented worker code. + * + * Alternatively, only after writing this did I discover this package: + * https://github.com/developit/web-worker. They also ran one notable issue I did: + * Node 18.x, at least, does not support use of Node Blobs for construction of a + * Worker: https://github.com/developit/web-worker/pull/32... unlike Web Workers. + * + * So... Base64-encoded Data URLs it is. + * + * What we have here is perfectly fine for now, but if we need more complicated + * cross-platform Worker support in the future, it may be wise to swap to use of + * that package. + */ +export default class MappedWorker extends worker.Worker implements Worker { + constructor(scriptStr: string) { + const concatenatedScript = ` + ${nodeWorkerToWebWorkerMappingSource} + + ${scriptStr} + `; + const buffer = Buffer.from(concatenatedScript); + const dataSrc = "data:text/javascript;base64," + buffer.toString('base64'); + //@ts-ignore + super(new URL(dataSrc)); + + // WebWorkers have a defined `onmessage` function, rather than this.on('message', ...) + this.on('message', (ev) => { + if(this.onmessage) { + this.onmessage({data: ev[0]}); + } + }); + } + + /** + * Accepts a callback function that will receive messages sent from the `VirtualizedWorker`'s `postMessage` function, + * much like the standard `Worker.onmessage`. + */ + onmessage: (this: Worker, ev: MessageEvent) => any; +} \ No newline at end of file diff --git a/common/predictive-text/src/node/sourcemappedWorker.ts b/common/predictive-text/src/node/sourcemappedWorker.ts index 5fd8bb9ec4266db97c699db2256643e54e5cff1c..aaaf79e0f6fcc1f1d42207dd767b4663f4f09e95 100644 --- a/common/predictive-text/src/node/sourcemappedWorker.ts +++ b/common/predictive-text/src/node/sourcemappedWorker.ts @@ -1,4 +1,4 @@ -import VirtualizedWorker from "./virtualizedWorker.js"; +import MappedWorker from "./mappedWorker.js"; import unwrap from '../unwrap.js'; import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-worker/worker-main.wrapped.js"; @@ -12,7 +12,7 @@ export default class SourcemappedWorker { // if(false) { scriptStr += '\n' + LMLayerWorkerSourcemapComment; // } - let worker = new VirtualizedWorker(scriptStr); + let worker = new MappedWorker(scriptStr); return worker as any as Worker; } diff --git a/common/predictive-text/src/node/tsconfig.json b/common/predictive-text/src/node/tsconfig.json index b9567ad113e7744f3abed6efeee318de75fa632e..dacf350335ead81ed5db44aea20fa1d1277d73d5 100644 --- a/common/predictive-text/src/node/tsconfig.json +++ b/common/predictive-text/src/node/tsconfig.json @@ -5,10 +5,13 @@ "declaration": true, "module": "es6", "moduleResolution": "Node16", + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman", - "target": "es5", + // This build-configuration only targets Node and exists solely for unit tests. + // Also, we need ES6 in order to properly inherit from the Node Worker-type. + "target": "ES6", "lib": ["es6"], "types": ["node"], "baseUrl": "../", diff --git a/common/predictive-text/src/node/virtualizedWorker.ts b/common/predictive-text/src/node/virtualizedWorker.ts deleted file mode 100644 index f32d265c9982d880c5830267c98ee8da32b7cb0f..0000000000000000000000000000000000000000 --- a/common/predictive-text/src/node/virtualizedWorker.ts +++ /dev/null @@ -1,84 +0,0 @@ -// We use a subset of the Worker interface here; compiling directly against the true -// WebWorker type definitions would require us to implement more methods than we do. -/// - -import * as fs from 'fs'; -import * as vm from 'vm'; - -class VirtualizedWorkerContext { - // The LMLayerWorker installs itself to 'self', the expected Worker global, so we provide an alias. - self: VirtualizedWorkerContext; - - constructor() { - this.self = this; - } - - postMessage: (message: any) => void; - - importScripts(...scriptNames: string[]) { - /* Use of vm.createContext and script.runInContext allow us to avoid - * polluting the global scope with imports. When we throw away the - * context object, imported scripts will be automatically GC'd. - */ - for(let script of scriptNames) { - this.__importScriptString(fs.readFileSync(script, "UTF-8")); - } - } - - __importScriptString(scriptStr: string) { - let context = vm.createContext(this); - var script = new vm.Script(scriptStr); - script.runInContext(context); - } -} - -/** - * Note: this does not create an actual Worker, separate process, or thread. Everything will - * be executed in-line on a virtualized context. - * - * In the future, it might be nice to use Node's Worker Threads implementation. - */ -export default class VirtualizedWorker implements Worker { - private _workerContext: VirtualizedWorkerContext; - - constructor(scriptStr: string) { - this._workerContext = new VirtualizedWorkerContext(); - // Needs to exist before setting up the worker; must exist by `.install()`. - this._workerContext.postMessage = this.workerPostMessage.bind(this); - - // Initialize the "worker". - this._workerContext.__importScriptString(scriptStr); - } - - // Sends the worker's postMessage messages to the appropriate `onmessage` handler. - private workerPostMessage(message: unknown) { - if(this.onmessage) { - this.onmessage({data: message} as any as MessageEvent); - } - } - - /** - * Accepts a callback function that will receive messages sent from the `VirtualizedWorker`'s `postMessage` function, - * much like the standard `Worker.onmessage`. - */ - onmessage: (this: Worker, ev: MessageEvent) => any; - - postMessage(message: any) { - let msgObj = {data: message}; - let msgJSON = JSON.stringify(msgObj); - - /* - * Execute the command within the virtualized worker's scope. The worker's returned - * `postMessage` calls will still reach outside, as they have a reference to `this` via - * `postMessage` (which we've set to a bound `this.workerPostMessage`). - * - * Among other things, this will allow the worker to use its internal namespaces without issue. - */ - let msgCommand = "onmessage(" + msgJSON + ")"; - this._workerContext.__importScriptString(msgCommand); - } - - terminate(): void { - this._workerContext = null; - } -} \ No newline at end of file diff --git a/common/predictive-text/src/node/worker.ts b/common/predictive-text/src/node/worker.ts index 68f14b587bd6b34d6114b3fdebecf8b9d4c6a8c9..5ad04e55d0be08b11b34073ef60d72b1e25981b7 100644 --- a/common/predictive-text/src/node/worker.ts +++ b/common/predictive-text/src/node/worker.ts @@ -1,4 +1,4 @@ -import VirtualizedWorker from "./virtualizedWorker.js"; +import MappedWorker from "./mappedWorker.js"; import unwrap from '../unwrap.js'; import { LMLayerWorkerCode, LMLayerWorkerSourcemapComment } from "@keymanapp/lm-worker/worker-main.wrapped.min.js"; @@ -8,7 +8,7 @@ export default class Worker { let scriptStr = unwrap(LMLayerWorkerCode); scriptStr += '\n' + LMLayerWorkerSourcemapComment; - let worker = new VirtualizedWorker(scriptStr); + let worker = new MappedWorker(scriptStr); return worker as any as Worker; } diff --git a/common/predictive-text/src/tsconfig.json b/common/predictive-text/src/tsconfig.json index 40fb9f45cd2e24632faba3b8fc1fea112c8a69da..f6fe671043ff00b8c01edca82b3eefbdcbbda656 100644 --- a/common/predictive-text/src/tsconfig.json +++ b/common/predictive-text/src/tsconfig.json @@ -6,6 +6,7 @@ "declaration": true, "module": "es6", "moduleResolution": "Node16", + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman", diff --git a/common/predictive-text/src/unwrap.ts b/common/predictive-text/src/unwrap.ts index 410c9db718fdf12dfeec84a75f2332e71c4721fd..fc0769e5bd112e3738169f0cc9de63bcf8b920af 100644 --- a/common/predictive-text/src/unwrap.ts +++ b/common/predictive-text/src/unwrap.ts @@ -6,6 +6,6 @@ * @param fn The function whose body will be returned. */ export default function unwrap(encodedSrc: string): string { - let wrapper = decodeURIComponent(encodedSrc); - return wrapper; + // There used to be more to this, but now it's a pretty simple passthrough! + return encodedSrc; } \ No newline at end of file diff --git a/common/predictive-text/src/web/tsconfig.json b/common/predictive-text/src/web/tsconfig.json index 9e1f43978e58f4d4973c89c2c8b5d1d840fdd995..bcb94fca1060674a915cf39762dd09712f8c92dd 100644 --- a/common/predictive-text/src/web/tsconfig.json +++ b/common/predictive-text/src/web/tsconfig.json @@ -5,6 +5,7 @@ "declaration": true, "module": "es6", "moduleResolution": "Node16", + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman", diff --git a/common/predictive-text/tsconfig.all.json b/common/predictive-text/tsconfig.all.json index 33fe749e21e92ef1a461414e20ac5e3635e4b21d..5cb3fdf96bfec7ce985c0b17641328dd897a747e 100644 --- a/common/predictive-text/tsconfig.all.json +++ b/common/predictive-text/tsconfig.all.json @@ -10,6 +10,7 @@ "declaration": true, "module": "es6", "moduleResolution": "Node16", + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman", diff --git a/common/predictive-text/unit_tests/headless/worker-dummy-integration.js b/common/predictive-text/unit_tests/headless/worker-dummy-integration.js index 6e6adb426f5116aa0a1960945b762466d3fd4f60..d546edb70403a4e4e6e1a11f126f3de3d41a2197 100644 --- a/common/predictive-text/unit_tests/headless/worker-dummy-integration.js +++ b/common/predictive-text/unit_tests/headless/worker-dummy-integration.js @@ -16,10 +16,23 @@ import { capabilities, iGotDistractedByHazel } from '@keymanapp/common-test-reso * of suggestions when loaded and return them sequentially. */ describe('LMLayer using dummy model', function () { + let lmLayer; + let worker; + + beforeEach(function() { + worker = Worker.constructInstance(); + lmLayer = new LMLayer(capabilities(), worker); + }); + + afterEach(function () { + // As we're using Node worker threads here, failure to terminate them will cause the + // headless test run to hang after completion. + lmLayer.shutdown(); + worker.terminate(); // should be covered by the former, but just in case... for CI stability. + }); + describe('Prediction', function () { it('will predict future suggestions (loaded from file)', function () { - var lmLayer = new LMLayer(capabilities(), Worker.constructInstance()); - var stripIDs = function(suggestions) { suggestions.forEach(function(suggestion) { delete suggestion.id; @@ -51,14 +64,11 @@ describe('LMLayer using dummy model', function () { }).then(function (suggestions) { stripIDs(suggestions); assert.deepEqual(suggestions, iGotDistractedByHazel()[3]); - lmLayer.shutdown(); return Promise.resolve(); }); }); it('will predict future suggestions (loaded from raw source)', function () { - var lmLayer = new LMLayer(capabilities(), Worker.constructInstance()); - var stripIDs = function(suggestions) { suggestions.forEach(function(suggestion) { delete suggestion.id; @@ -92,7 +102,6 @@ describe('LMLayer using dummy model', function () { }).then(function (suggestions) { stripIDs(suggestions); assert.deepEqual(suggestions, iGotDistractedByHazel()[3]); - lmLayer.shutdown(); return Promise.resolve(); }); }); @@ -100,8 +109,6 @@ describe('LMLayer using dummy model', function () { describe('Wordbreaking', function () { it('will perform (default) wordbreaking and return word at caret', function () { - var lmLayer = new LMLayer(capabilities(), Worker.constructInstance()); - // We're testing many as asynchronous messages in a row. // this would be cleaner using async/await syntax. // Not done yet, as this test case is a slightly-edited copy of the in-browser version. diff --git a/common/predictive-text/unit_tests/headless/worker-trie-integration.js b/common/predictive-text/unit_tests/headless/worker-trie-integration.js index 88ae133097806f4d19d365de820312cdf4466b7e..8cd257878d1491e56937ba5f01bbcb41267343a9 100644 --- a/common/predictive-text/unit_tests/headless/worker-trie-integration.js +++ b/common/predictive-text/unit_tests/headless/worker-trie-integration.js @@ -10,11 +10,24 @@ import { capabilities } from '@keymanapp/common-test-resources/model-helpers.mjs * How to run the worlist */ describe('LMLayer using the trie model', function () { + let lmLayer; + let worker; + + beforeEach(function() { + worker = Worker.constructInstance(); + lmLayer = new LMLayer(capabilities(), worker); + }); + + afterEach(function () { + // As we're using Node worker threads here, failure to terminate them will cause the + // headless test run to hang after completion. + lmLayer.shutdown(); + worker.terminate(); // should be covered by the former, but just in case... for CI stability. + }); + describe('Prediction', function () { var EXPECTED_SUGGESTIONS = 3; it('will predict an empty buffer', function () { - var lmLayer = new LMLayer(capabilities(), Worker.constructInstance()); - // We're testing many as asynchronous messages in a row. // this would be cleaner using async/await syntax. // Not done yet, as this test case is a slightly-edited copy of the in-browser version. @@ -55,8 +68,6 @@ describe('LMLayer using the trie model', function () { // // https://community.software.sil.org/t/search-term-to-key-in-lexical-model-not-working-both-ways-by-default/3133 it('should use the default searchTermToKey()', function () { - var lmLayer = new LMLayer(capabilities(), Worker.constructInstance()); - return lmLayer.loadModel( // We're running headlessly, so the path can be relative to the npm root directory. require.resolve("@keymanapp/common-test-resources/models/naive-trie.js") diff --git a/common/predictive-text/unit_tests/in_browser/manual.conf.cjs b/common/predictive-text/unit_tests/in_browser/manual.conf.cjs index 9f9463447334d0fb0b9bd31ac07520945fdc720e..738f2b7713d597b9cf047fc53af14c9aba5d3d36 100644 --- a/common/predictive-text/unit_tests/in_browser/manual.conf.cjs +++ b/common/predictive-text/unit_tests/in_browser/manual.conf.cjs @@ -19,7 +19,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/common/test/resources/json/engine_tests/chirality.json b/common/test/resources/json/engine_tests/chirality.json index f46ab5399ddb4f94005f7245f063d929fe907a3b..4f3153c2758fc2fc484e405068397e1dba6a8972 100644 --- a/common/test/resources/json/engine_tests/chirality.json +++ b/common/test/resources/json/engine_tests/chirality.json @@ -4,7 +4,13 @@ "id": "test_chirality", "name": "KeymanWeb Chirality Tester", "filename": "resources/keyboards/test_chirality.js", - "languages": {} + "languages": [ + { + "id": "en", + "name": "English", + "region": "World" + } + ] }, "inputTestSets": [ { diff --git a/common/test/resources/json/engine_tests/deadkeys.json b/common/test/resources/json/engine_tests/deadkeys.json index 1764915605bda0c7fe2b229d4dc992d69265f233..aec422f0cc6e23a6b91760a68ec43b696ce3e849 100644 --- a/common/test/resources/json/engine_tests/deadkeys.json +++ b/common/test/resources/json/engine_tests/deadkeys.json @@ -4,7 +4,13 @@ "id": "test_deadkeys", "name": "Keyman Deadkey Stress-Tester", "filename": "resources/keyboards/test_deadkeys.js", - "languages": {} + "languages": [ + { + "id": "en", + "name": "English", + "region": "World" + } + ] }, "inputTestSets": [ { diff --git a/common/web/input-processor/build-bundler.js b/common/web/input-processor/build-bundler.js index 34614fa6808e787c9d6074644c27937b573b9171..f16b207454a72af0866e5f8e88021e0dba4d065d 100644 --- a/common/web/input-processor/build-bundler.js +++ b/common/web/input-processor/build-bundler.js @@ -11,6 +11,9 @@ import { spawn } from 'child_process'; // Bundled ES module version esbuild.buildSync({ entryPoints: ['build/obj/index.js'], + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, external: ['fs', 'vm'], @@ -29,6 +32,9 @@ esbuild.buildSync({ // Bundled CommonJS (classic Node) module version esbuild.buildSync({ entryPoints: ['build/obj/index.js'], + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, external: ['fs', 'vm'], diff --git a/common/web/input-processor/src/text/prediction/languageProcessor.ts b/common/web/input-processor/src/text/prediction/languageProcessor.ts index 8aea39eedc65a588db047c251626689b4c307362..58c6e4299327f3d7f1217c15b83c000993097261 100644 --- a/common/web/input-processor/src/text/prediction/languageProcessor.ts +++ b/common/web/input-processor/src/text/prediction/languageProcessor.ts @@ -80,11 +80,10 @@ export default class LanguageProcessor extends EventEmitter`; else, `null`. + * @returns if `suggestion` is a `Suggestion`, will return a `Promise`; else, `null`. */ public accept(suggestion: Suggestion): Promise | null { let _this = this; diff --git a/common/web/input-processor/tests/cases/inputProcessor.js b/common/web/input-processor/tests/cases/inputProcessor.js index f3f3ca6b04d8ec56c15492c15bf3bacb62e6bb60..a2ba363edcec7e47b4910fef83f0aae5adcf100d 100644 --- a/common/web/input-processor/tests/cases/inputProcessor.js +++ b/common/web/input-processor/tests/cases/inputProcessor.js @@ -35,26 +35,32 @@ describe('InputProcessor', function() { it('has expected default values after initialization', function () { // Can construct without the second parameter; if so, the final assertion - .mayPredict // will be invalidated. (No worker, no ability to predict.) - let core = new InputProcessor(device, Worker.constructInstance()); - - assert.isOk(core.keyboardProcessor); - assert.isDefined(core.keyboardProcessor.contextDevice); - assert.isOk(core.languageProcessor); - assert.isOk(core.keyboardInterface); - assert.isUndefined(core.activeKeyboard); // No keyboard should be loaded yet. - assert.isUndefined(core.activeModel); // Same for the model. - - // These checks are lifted from the keyboard-processor init checks found in - // common/web/keyboard-processor/tests/cases/basic-init.js. - assert.equal('us', core.keyboardProcessor.baseLayout, 'KeyboardProcessor has unexpected base layout') - assert.isNotNull(global.KeymanWeb, 'KeymanWeb global was not automatically installed'); - assert.equal('default', core.keyboardProcessor.layerId, 'Default layer is not set to "default"'); - assert.isUndefined(core.keyboardProcessor.activeKeyboard, 'Initialized with already-active keyboard'); - - // Lifted from languageProcessor.js - the core should not be changing these with its init. - assert.isUndefined(core.languageProcessor.activeModel); - assert.isFalse(core.languageProcessor.isActive); - assert.isTrue(core.languageProcessor.mayPredict); + let worker = Worker.constructInstance(); + + try { + let core = new InputProcessor(device, worker); + + assert.isOk(core.keyboardProcessor); + assert.isDefined(core.keyboardProcessor.contextDevice); + assert.isOk(core.languageProcessor); + assert.isOk(core.keyboardInterface); + assert.isUndefined(core.activeKeyboard); // No keyboard should be loaded yet. + assert.isUndefined(core.activeModel); // Same for the model. + + // These checks are lifted from the keyboard-processor init checks found in + // common/web/keyboard-processor/tests/cases/basic-init.js. + assert.equal('us', core.keyboardProcessor.baseLayout, 'KeyboardProcessor has unexpected base layout') + assert.isNotNull(global.KeymanWeb, 'KeymanWeb global was not automatically installed'); + assert.equal('default', core.keyboardProcessor.layerId, 'Default layer is not set to "default"'); + assert.isUndefined(core.keyboardProcessor.activeKeyboard, 'Initialized with already-active keyboard'); + + // Lifted from languageProcessor.js - the core should not be changing these with its init. + assert.isUndefined(core.languageProcessor.activeModel); + assert.isFalse(core.languageProcessor.isActive); + assert.isTrue(core.languageProcessor.mayPredict); + } finally { + worker.terminate(); + } }); }); diff --git a/common/web/input-processor/tests/cases/languageProcessor.js b/common/web/input-processor/tests/cases/languageProcessor.js index 06c017b426f90eeec3afa79cee25c066a5310a36..f1934a5f6eea6063cae4c85f53d80343943bcb78 100644 --- a/common/web/input-processor/tests/cases/languageProcessor.js +++ b/common/web/input-processor/tests/cases/languageProcessor.js @@ -21,14 +21,24 @@ String.kmwEnableSupplementaryPlane(false); // Test the KeyboardProcessor interface. describe('LanguageProcessor', function() { + let worker; + + beforeEach(function() { + worker = LMWorker.constructInstance(); + }); + + afterEach(function() { + worker.terminate(); + }); + describe('[[constructor]]', function () { it('should initialize without errors', function () { - let lp = new LanguageProcessor(LMWorker.constructInstance()); + let lp = new LanguageProcessor(worker); assert.isNotNull(lp); }); it('has expected default values after initialization', function () { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); // These checks are lifted from the keyboard-processor init checks found in // common/web/keyboard-processor/tests/cases/basic-init.js. @@ -70,7 +80,7 @@ describe('LanguageProcessor', function() { }; it("successfully loads the model", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); languageProcessor.loadModel(modelSpec).then(function() { assert.isOk(languageProcessor.activeModel); // is only set after a successful load. @@ -81,7 +91,7 @@ describe('LanguageProcessor', function() { }); it("generates the expected prediction set", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("li", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -117,7 +127,7 @@ describe('LanguageProcessor', function() { describe("does not alter casing when input is lowercased", function() { it("when input is fully lowercased", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("li", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -136,7 +146,7 @@ describe('LanguageProcessor', function() { }); it("when input has non-initial uppercased letters", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("lI", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -156,7 +166,7 @@ describe('LanguageProcessor', function() { }); it("unless the suggestion has uppercased letters", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("i", 1); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -177,7 +187,7 @@ describe('LanguageProcessor', function() { describe("uppercases suggestions when input is fully capitalized ", function() { it("for suggestions with default casing (== 'lower')", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("LI", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -197,7 +207,7 @@ describe('LanguageProcessor', function() { }); it("for precapitalized suggestions", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("I", 1); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -219,7 +229,7 @@ describe('LanguageProcessor', function() { describe("initial-cases suggestions when input uses initial casing ", function() { describe("when input is a single capitalized letter", function() { it("for suggestions with default casing (== 'lower')", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("L", 1); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); @@ -241,7 +251,7 @@ describe('LanguageProcessor', function() { describe("input length > 1", function() { it("for suggestions with default casing (== 'lower')", function(done) { - let languageProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let languageProcessor = new LanguageProcessor(worker); let contextSource = new Mock("Li", 2); let transcription = contextSource.buildTranscriptionFrom(contextSource, null, null); diff --git a/common/web/input-processor/tests/cases/predictionContext.js b/common/web/input-processor/tests/cases/predictionContext.js index 3db0e89b584e19f9323f2f3a5ec4c2e26c5c665f..fd03dda639e20e30b8f7dee40aed18c6aef1e11b 100644 --- a/common/web/input-processor/tests/cases/predictionContext.js +++ b/common/web/input-processor/tests/cases/predictionContext.js @@ -66,8 +66,18 @@ const appleDummyModel = { }; describe("PredictionContext", () => { - it('receives predictions as they are generated', async () => { - const langProcessor = new LanguageProcessor(LMWorker.constructInstance()); + let worker; + + beforeEach(function() { + worker = LMWorker.constructInstance(); + }); + + afterEach(function() { + worker.terminate(); + }); + + it('receives predictions as they are generated', async function () { + const langProcessor = new LanguageProcessor(worker); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. const kbdProcessor = new KeyboardProcessor(deviceSpec); @@ -107,8 +117,8 @@ describe("PredictionContext", () => { assert.equal(suggestions.find((obj) => obj.transform.deleteLeft != 0).displayAs, 'apps'); }); - it('sendUpdateState retrieves the most recent suggestion set', async () => { - const langProcessor = new LanguageProcessor(LMWorker.constructInstance()); + it('sendUpdateState retrieves the most recent suggestion set', async function() { + const langProcessor = new LanguageProcessor(worker); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. const kbdProcessor = new KeyboardProcessor(deviceSpec); @@ -133,8 +143,8 @@ describe("PredictionContext", () => { assert.sameOrderedMembers(suggestions, initialSuggestions); }); - it('suggestion application logic & triggered effects', async () => { - const langProcessor = new LanguageProcessor(LMWorker.constructInstance()); + it('suggestion application logic & triggered effects', async function () { + const langProcessor = new LanguageProcessor(worker); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. const kbdProcessor = new KeyboardProcessor(deviceSpec); @@ -167,6 +177,15 @@ describe("PredictionContext", () => { const suggestionApply = suggestions.find((obj) => obj.displayAs == 'apply'); assert.isOk(suggestionApply); + // For awaiting the suggestions generated upon applying our desired suggestion. + // We aren't given a direct Promise for that, but we can construct one this way. + let postApplySuggestions = new Promise((resolve) => { + predictiveContext.once('update', resolve); + }); + + // Apply the desired suggestion. Also passively generates new, post-acceptance + // suggestions, but this function itself don't provide a Promise for that... + // hence the previous block. let promiseForApplyReversion = predictiveContext.accept(suggestionApply); assert.equal(updateFake.callCount, 1); // No new 'update' has been raised yet. @@ -177,8 +196,7 @@ describe("PredictionContext", () => { assert.equal(textState.getText(), 'apply '); let reversion = await promiseForApplyReversion; - // We don't seem to need to additionally rig a wait for the triggered predict call; it - // always completes first. Neat. If test becomes unstable... yeah, rig up a wait for it. + await postApplySuggestions; // Check 2: a second 'update' - post-application predictions! assert.equal(updateFake.callCount, 2); @@ -193,8 +211,8 @@ describe("PredictionContext", () => { // All other reversion details are tested in the 'reversion application logic...' section defined below. }); - it('reversion application logic & triggered effects', async () => { - const langProcessor = new LanguageProcessor(LMWorker.constructInstance()); + it('reversion application logic & triggered effects', async function () { + const langProcessor = new LanguageProcessor(worker); await langProcessor.loadModel(appleDummyModel); // await: must fully 'configure', load script into worker. const kbdProcessor = new KeyboardProcessor(deviceSpec); @@ -222,7 +240,15 @@ describe("PredictionContext", () => { assert.isOk(suggestionApply); let previousTextState = Mock.from(textState); + + // For awaiting the suggestions generated upon applying our desired suggestion. + // We aren't given a direct Promise for that, but we can construct one this way. + let postApplySuggestions = new Promise((resolve) => { + predictiveContext.once('update', resolve); + }); + let reversion = await predictiveContext.accept(suggestionApply); + await postApplySuggestions; // Test setup complete. @@ -243,6 +269,19 @@ describe("PredictionContext", () => { // Fire away! Time to apply the reversion. previousTextState = Mock.from(textState); + + // Since the test uses a separate thread via Worker, make sure to set up any important event handlers + // before we request the reversion. + let updateFake = sinon.fake(); + predictiveContext.on('update', updateFake); + + // Now, in order to synchronize... we rely on a Promise. The callback is indeed + // called synchronously. + let postRevertSuggestions = new Promise((resolve) => { + predictiveContext.once('update', resolve); + }); + + // And now, apply the reversion itself. let returnValue = predictiveContext.accept(reversion); // 'accepting' a reversion performs a rewind; there's no need for async ops here. @@ -256,17 +295,8 @@ describe("PredictionContext", () => { // Note: no space appended. assert.equal(textState.getText(), rewoundTextStateWithInput.getText()); - // Note: accepting a reversion will trigger a new prediction that completes - // asynchronously, and unfortunately... we have no handle for it. - - let updateFake = sinon.fake(); - predictiveContext.on('update', updateFake); - - // Now, in order to synchronize... we rely on a Promise. The callback is indeed - // called synchronously. - await new Promise((resolve) => { - predictiveContext.once('update', resolve); - }); + // Re-synchronize once we've received word of the new post-reversion predictions. + await postRevertSuggestions; assert.equal(updateFake.callCount, 1); const suggestionsPostReversion = updateFake.firstCall.args[0]; diff --git a/common/web/input-processor/tsconfig.json b/common/web/input-processor/tsconfig.json index 1a03881a16e8ff9e162711aa955bfcb77b98e32a..6e04340f4d1b6476e4aece4933478dbf5fcc7792 100644 --- a/common/web/input-processor/tsconfig.json +++ b/common/web/input-processor/tsconfig.json @@ -6,6 +6,7 @@ "module": "es6", "moduleResolution": "Node16", "declaration": true, + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman/", diff --git a/common/web/keyboard-processor/build-bundler.js b/common/web/keyboard-processor/build-bundler.js index 5c3ecb05a974d54c1661a97aa088b5ad3029c3ec..87625dd19ce9b6d3753677e76b5cd0f4a181cd5e 100644 --- a/common/web/keyboard-processor/build-bundler.js +++ b/common/web/keyboard-processor/build-bundler.js @@ -10,6 +10,9 @@ import { spawn } from 'child_process'; /** @type {esbuild.BuildOptions} */ const commonConfig = { + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "esm", @@ -23,6 +26,9 @@ const commonConfig = { // Bundled ES module version esbuild.buildSync({ + alias: { + 'tslib': '@keymanapp/tslib' + }, entryPoints: ['build/obj/index.js'], outfile: "build/lib/index.mjs", format: "esm", @@ -31,6 +37,9 @@ esbuild.buildSync({ // Bundled CommonJS (classic Node) module version esbuild.buildSync({ + alias: { + 'tslib': '@keymanapp/tslib' + }, entryPoints: ['build/obj/index.js'], outfile: 'build/lib/index.cjs', bundle: true, @@ -42,6 +51,9 @@ esbuild.buildSync({ esbuild.buildSync({ + alias: { + 'tslib': '@keymanapp/tslib' + }, entryPoints: ['build/obj/keyboards/loaders/dom-keyboard-loader.js'], outfile: 'build/lib/dom-keyboard-loader.mjs', format: "esm", @@ -50,6 +62,9 @@ esbuild.buildSync({ // The node-based keyboard loader needs an extra parameter due to Node-built-in imports: esbuild.buildSync({ + alias: { + 'tslib': '@keymanapp/tslib' + }, entryPoints: ['build/obj/keyboards/loaders/node-keyboard-loader.js'], outfile: 'build/lib/node-keyboard-loader.mjs', format: "esm", diff --git a/common/web/keyboard-processor/build.sh b/common/web/keyboard-processor/build.sh index fc7bf1f1a60378b98205f21e7b00355bdd20b789..01939b9e62414ffebb0f0cbcc4886c1fd2985f9c 100755 --- a/common/web/keyboard-processor/build.sh +++ b/common/web/keyboard-processor/build.sh @@ -21,6 +21,7 @@ builder_describe \ "Compiles the web-oriented utility function module." \ "@/common/web/recorder test" \ "@/common/web/keyman-version" \ + "@/common/web/tslib" \ "@/common/web/utils" \ configure \ clean \ diff --git a/common/web/keyboard-processor/src/keyboards/activeLayout.ts b/common/web/keyboard-processor/src/keyboards/activeLayout.ts index 065f8c519378f31adbf7481203ef920aaf0c959a..e6d2ab9f1578b82cc6ec97eeae3fc02db2e8200c 100644 --- a/common/web/keyboard-processor/src/keyboards/activeLayout.ts +++ b/common/web/keyboard-processor/src/keyboards/activeLayout.ts @@ -5,9 +5,7 @@ import type { KeyDistribution } from "../text/keyEvent.js"; import type { LayoutKey, LayoutRow, LayoutLayer, LayoutFormFactor, ButtonClass } from "./defaultLayouts.js"; import type Keyboard from "./keyboard.js"; -import KeyboardProcessor from "../text/keyboardProcessor.js"; - -import { deepCopy, type DeviceSpec } from "@keymanapp/web-utils"; +import { type DeviceSpec } from "@keymanapp/web-utils"; // TS 3.9 changed behavior of getters to make them // non-enumerable by default. This broke our 'polyfill' @@ -151,7 +149,7 @@ export class ActiveKey implements LayoutKey { @Enumerable public get baseKeyEvent(): KeyEvent { - return deepCopy(this._baseKeyEvent); + return new KeyEvent(this._baseKeyEvent); } /** diff --git a/common/web/keyboard-processor/src/keyboards/keyboard.ts b/common/web/keyboard-processor/src/keyboards/keyboard.ts index a657ff8d038cae830b09ac5c87bf1525fc5b8aa8..82c3883785ef270d4e8049d515f4d6412f6bba28 100644 --- a/common/web/keyboard-processor/src/keyboards/keyboard.ts +++ b/common/web/keyboard-processor/src/keyboards/keyboard.ts @@ -499,6 +499,15 @@ export default class Keyboard { // devices, the only state key in use currently is Caps Lock, which is set // when the 'caps' layer is active in ActiveKey::constructBaseKeyEvent. if(!Lkc.device.touchable) { + /* + * For desktop-style keyboards, start from a blank slate. They have a 'default' + * (implicit 'NO_CAPS') layer but not a 'caps' layer. With caps set, it just + * highlights the key on the 'default' layer instead. + * + * We should never set both `CAPS` and `NO_CAPS` at the same time, and + * same for the other modifiers. + */ + Lkc.Lstates = 0; Lkc.Lstates |= stateKeys['K_CAPS'] ? Codes.modifierCodes['CAPS'] : Codes.modifierCodes['NO_CAPS']; Lkc.Lstates |= stateKeys['K_NUMLOCK'] ? Codes.modifierCodes['NUM_LOCK'] : Codes.modifierCodes['NO_NUM_LOCK']; Lkc.Lstates |= stateKeys['K_SCROLL'] ? Codes.modifierCodes['SCROLL_LOCK'] : Codes.modifierCodes['NO_SCROLL_LOCK']; diff --git a/common/web/keyboard-processor/src/keyboards/keyboardProperties.ts b/common/web/keyboard-processor/src/keyboards/keyboardProperties.ts index 8495d6253e942e81477182ced8e48887bcbe89d6..e83d1a137f192aa5460dce6531a9103a5d22e4e1 100644 --- a/common/web/keyboard-processor/src/keyboards/keyboardProperties.ts +++ b/common/web/keyboard-processor/src/keyboards/keyboardProperties.ts @@ -142,6 +142,7 @@ export default class KeyboardProperties implements KeyboardInternalPropertySpec this.KN = other.KN; this.KL = other.KL; this.KLC = other.KLC; + // Do NOT apply fontPath here; the mobile apps will have font issues if you do! this.KFont = other.KFont; this.KOskFont = other.KOskFont; this._displayName = (other instanceof KeyboardProperties) ? other._displayName : other.displayName; diff --git a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json b/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json index 5120d36a6c1e677d4d878af567b3ad6dfd7d0585..e5ab52729e78c9ce3a5b54fd4e3e920e1e37f5e6 100644 --- a/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json +++ b/common/web/keyboard-processor/src/keyboards/loaders/tsconfig.dom.json @@ -6,6 +6,7 @@ "module": "es6", "moduleResolution": "Node16", "declaration": true, + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman/", diff --git a/common/web/keyboard-processor/src/text/outputTarget.ts b/common/web/keyboard-processor/src/text/outputTarget.ts index 30c9468295edf74fdda8f6b24bf80ccb254401f6..97a47928479356a009d1d28e40b25caf5e38baa6 100644 --- a/common/web/keyboard-processor/src/text/outputTarget.ts +++ b/common/web/keyboard-processor/src/text/outputTarget.ts @@ -390,11 +390,19 @@ export class Mock extends OutputTarget { let priorMock = outputTarget as Mock; clone = new Mock(priorMock.text, priorMock.selStart, priorMock.selEnd); } else { - let text = outputTarget.getText(); - let beforeText = outputTarget.getTextBeforeCaret(); - let afterText = outputTarget.getTextAfterCaret(); - let selectionStart = beforeText._kmwLength(); - let selectionEnd = text._kmwLength() - afterText._kmwLength(); + const text = outputTarget.getText(); + const textLen = text._kmwLength(); + + // If !hasSelection() + let selectionStart: number = textLen; + let selectionEnd: number = 0; + + if(outputTarget.hasSelection()) { + let beforeText = outputTarget.getTextBeforeCaret(); + let afterText = outputTarget.getTextAfterCaret(); + selectionStart = beforeText._kmwLength(); + selectionEnd = textLen - afterText._kmwLength(); + } // readonly group or not, the returned Mock remains the same. // New-context events should act as if the caret were at the earlier-in-context diff --git a/common/web/keyboard-processor/tests/dom/CI.conf.cjs b/common/web/keyboard-processor/tests/dom/CI.conf.cjs index c7c9813df7286705d3a73bea06a043ee99dd3f6d..a96ffebbede2a334c04f2f14a58f42e58c1d2723 100644 --- a/common/web/keyboard-processor/tests/dom/CI.conf.cjs +++ b/common/web/keyboard-processor/tests/dom/CI.conf.cjs @@ -18,7 +18,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/common/web/keyboard-processor/tests/dom/manual.conf.cjs b/common/web/keyboard-processor/tests/dom/manual.conf.cjs index 88e20d283cf2f7c4df2ed53f8a728cf577ca9210..37d982e9b5a70c983c7b64cb766adb1f00b9adfd 100644 --- a/common/web/keyboard-processor/tests/dom/manual.conf.cjs +++ b/common/web/keyboard-processor/tests/dom/manual.conf.cjs @@ -18,7 +18,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/common/web/keyboard-processor/tsconfig.common.json b/common/web/keyboard-processor/tsconfig.common.json index 9ed8c35d5433dcc4461eb64355f6875e5ea6d127..30fcd19eda836e2381c025c76fd9df69e32d6470 100644 --- a/common/web/keyboard-processor/tsconfig.common.json +++ b/common/web/keyboard-processor/tsconfig.common.json @@ -6,6 +6,7 @@ "module": "es6", "moduleResolution": "Node16", "declaration": true, + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman/", diff --git a/common/web/keyboard-processor/tsconfig.json b/common/web/keyboard-processor/tsconfig.json index df6ac8969bd95aececebb8f1629e3157434b7869..95071c36a70a844feb41d2e993da6d8f029304e2 100644 --- a/common/web/keyboard-processor/tsconfig.json +++ b/common/web/keyboard-processor/tsconfig.json @@ -6,6 +6,7 @@ "module": "es6", "moduleResolution": "Node16", "declaration": true, + "importHelpers": true, "inlineSources": true, "sourceMap": true, "sourceRoot": "keyman/", diff --git a/common/web/lm-worker/build-bundler.js b/common/web/lm-worker/build-bundler.js index 44c03ec2f1d9aaef0b65cf82ba36a152a0de8648..eb7bb5ea415abc36951f2fcf8ffa96dec06de646 100644 --- a/common/web/lm-worker/build-bundler.js +++ b/common/web/lm-worker/build-bundler.js @@ -6,7 +6,49 @@ */ import esbuild from 'esbuild'; -import { spawn } from 'child_process'; +import fs from 'fs'; +import { determineNeededDowncompileHelpers, buildTslibTreeshaker } from '@keymanapp/tslib/esbuild-tools'; + +/* + * Refer to https://github.com/microsoft/TypeScript/issues/13721#issuecomment-307259227 - + * the `@class` emit comment-annotation is designed to facilitate tree-shaking for ES5-targeted + * down-level emits. `esbuild` doesn't look for it by default... but we can override that with + * this plugin. + */ +let es5ClassAnnotationAsPurePlugin = { + name: '@class -> __PURE__', + setup(build) { + build.onLoad({filter: /\.js$/ }, async (args) => { + let source = await fs.promises.readFile(args.path, 'utf8'); + return { + // Marks any classes compiled by TS (as per the /** @class */ annotation) + // as __PURE__ in order to facilitate tree-shaking. + contents: source.replace('/** @class */', '/* @__PURE__ */ /** @class */'), + loader: 'js' + } + }); + } +} + +let EMIT_FILESIZE_PROFILE = false; + +if(process.argv.length > 2) { + for(let i = 2; i < process.argv.length; i++) { + const arg = process.argv[i]; + + switch(arg) { + case '': + break; + case '--ci': + EMIT_FILESIZE_PROFILE=true + break; + // May add other options if desired in the future. + default: + console.error("Invalid command-line option set for script; only --ci is permitted."); + process.exit(1); + } + } +} await esbuild.build({ bundle: true, @@ -28,12 +70,13 @@ await esbuild.build({ }, outdir: 'build/lib', outExtension: { '.js': '.mjs' }, + plugins: [ es5ClassAnnotationAsPurePlugin ], tsconfig: 'tsconfig.json', - target: "es5" + target: "es5", }); // Bundled CommonJS (classic Node) module version -esbuild.buildSync({ +await esbuild.build({ bundle: true, sourcemap: true, format: "cjs", @@ -44,12 +87,17 @@ esbuild.buildSync({ }, outdir: 'build/lib', outExtension: { '.js': '.cjs' }, + plugins: [ es5ClassAnnotationAsPurePlugin ], tsconfig: 'tsconfig.json', target: "es5" }); -// Direct-use version -esbuild.buildSync({ +// The one that's actually a component of our releases. + +const embeddedWorkerBuildOptions = { + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "iife", @@ -58,6 +106,39 @@ esbuild.buildSync({ 'worker-main': 'build/obj/worker-main.js' }, outdir: 'build/lib', + plugins: [ es5ClassAnnotationAsPurePlugin ], tsconfig: 'tsconfig.json', target: "es5" -}); +} + +// Prepare the needed setup for `tslib` treeshaking. +const unusedHelpers = await determineNeededDowncompileHelpers(embeddedWorkerBuildOptions); +embeddedWorkerBuildOptions.plugins = [buildTslibTreeshaker(unusedHelpers), ...embeddedWorkerBuildOptions.plugins]; + +// Direct-use version +await esbuild.build(embeddedWorkerBuildOptions); + +// ------------------------ + +// Now to generate a filesize profile for the minified version of the worker. +// We want a specialized bundle build here instead; no output, but minified like +// the actual release worker. +const minifiedProfilingOptions = { + ...embeddedWorkerBuildOptions, + minify: true, + metafile: true, + write: false // don't actually write the file. +} + +let result = await esbuild.build(minifiedProfilingOptions); +let filesizeProfile = await esbuild.analyzeMetafile(result.metafile, { verbose: true }); +fs.writeFileSync('build/filesize-profile.log', ` +// Minified Worker filesize profile, before polyfilling +${filesizeProfile} +`); + +if(EMIT_FILESIZE_PROFILE) { + console.log("Minified, pre-polyfill worker filesize profile:"); + // Profiles the sourcecode! + console.log(filesizeProfile); +} diff --git a/common/web/lm-worker/build-wrap-and-minify.js b/common/web/lm-worker/build-wrap-and-minify.js index 0b816f02ddc7cf956c62b2a6a79d872fece03256..bf50537d9beed44781a6de303d65ea57e9138b71 100644 --- a/common/web/lm-worker/build-wrap-and-minify.js +++ b/common/web/lm-worker/build-wrap-and-minify.js @@ -65,11 +65,16 @@ const srcMapString = `//# sourceMappingURL=data:application/json;charset=utf-8;b * but my attempts to do so end up triggering errors when loading. */ +let rawScript = workerConcatenation.script.toString(); +// Two layers of encoding: one for the raw source (parsed by the JS engine), +// one to 'unwrap' it from a string _within_ that source. +let jsonEncoded = JSON.stringify(rawScript); + let wrapper = ` // Autogenerated code. Do not modify! // --START:LMLayerWorkerCode-- -export var LMLayerWorkerCode = \`${encodeURIComponent(workerConcatenation.script.toString())}\` +export var LMLayerWorkerCode = ${jsonEncoded}; ${MINIFY && "// Sourcemaps have been omitted for this release build."} export var LMLayerWorkerSourcemapComment = "${DEBUG ? srcMapString : ''}"; diff --git a/common/web/lm-worker/build.sh b/common/web/lm-worker/build.sh index 91322527e665d56cbf0a59a57bac8c4399cd71ec..82fba9bb39ba3496392d96e6b3fad0d59965e1d8 100755 --- a/common/web/lm-worker/build.sh +++ b/common/web/lm-worker/build.sh @@ -28,6 +28,9 @@ WORKER_OUTPUT_FILENAME=build/lib/worker-main.js builder_describe \ "Compiles the Language Modeling Layer for common use in predictive text and autocorrective applications." \ "@/common/web/keyman-version" \ + "@/common/web/tslib" \ + "@/common/models/wordbreakers" \ + "@/common/models/templates" \ "@/common/tools/sourcemap-path-remapper" \ configure clean build test --ci @@ -56,9 +59,13 @@ if builder_start_action build; then # Build worker with tsc first tsc -b $builder_verbose || builder_die "Could not build worker." + EXT_FLAGS= + if builder_has_option --ci; then + EXT_FLAGS=--ci + fi echo "Bundling worker modules" - node build-bundler.js + node build-bundler.js "$EXT_FLAGS" # Declaration bundling. npm run tsc -- --emitDeclarationOnly --outFile ./build/lib/index.d.ts diff --git a/common/web/lm-worker/tsconfig.json b/common/web/lm-worker/tsconfig.json index 551f1645a950a4902e12c0af66b158fe11d4d1b0..2fdda0135a121b963f752307aa01074a355a467a 100644 --- a/common/web/lm-worker/tsconfig.json +++ b/common/web/lm-worker/tsconfig.json @@ -5,7 +5,8 @@ "allowJs": false, "declaration": true, "module": "es6", - "moduleResolution": "node", + "moduleResolution": "node16", + "importHelpers": true, "inlineSourceMap": true, "inlineSources": true, "sourceRoot": "/common/web/lm-worker/src", diff --git a/common/web/tslib/README.md b/common/web/tslib/README.md new file mode 100644 index 0000000000000000000000000000000000000000..4c87474f627b9528f7f774d7c0b50e2a3175bbf8 --- /dev/null +++ b/common/web/tslib/README.md @@ -0,0 +1,15 @@ +The default import setup for the `tslib` package is unfortunately incompatible with `esbuild` when in ES5 mode. But... +with a little elbow grease, we can fix that with _this_ package by importing its ES5-compatible file and exporting it +as _this_ package's default export for use in anything looking to `"importHelpers"`. + +To utilize this with `esbuild` while enabling the `"importHelpers"` compilation option in your tsconfig.json, you'll want +to set the following in your `esbuild` config: + +```javascript + alias: { + 'tslib': '@keymanapp/tslib' + }, +``` + +Note that esbuild 0.15.16 is the minimum required version to utilize the 'alias' feature necessary to replace `tslib` for +`tsc`-generated `import { /* */ } from 'tslib'` statements that result from enabling `"importHelpers"` in a tsconfig. \ No newline at end of file diff --git a/common/web/tslib/build.sh b/common/web/tslib/build.sh new file mode 100755 index 0000000000000000000000000000000000000000..411e32d8bbb4c3817687363b738f1cc3790e5ca6 --- /dev/null +++ b/common/web/tslib/build.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env bash + +## START STANDARD BUILD SCRIPT INCLUDE +# adjust relative paths as necessary +THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" +. "${THIS_SCRIPT%/*}/../../../resources/build/build-utils.sh" +## END STANDARD BUILD SCRIPT INCLUDE + +. "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" + +cd "$THIS_SCRIPT_PATH" + +################################ Main script ################################ + +builder_describe \ + "A ES5 + esbuild compatibility wrapper for the 'tslib' package." \ + clean configure build + +builder_describe_outputs \ + configure "/node_modules" \ + build "/common/web/tslib/build/index.js" + +builder_parse "$@" + +builder_run_action configure verify_npm_setup +builder_run_action clean rm -rf build/ +builder_run_action build tsc --build \ No newline at end of file diff --git a/common/web/tslib/package.json b/common/web/tslib/package.json new file mode 100644 index 0000000000000000000000000000000000000000..ceec8c64099e23bd539dc23302fe11b39846a4b0 --- /dev/null +++ b/common/web/tslib/package.json @@ -0,0 +1,28 @@ +{ + "name": "@keymanapp/tslib", + "description": "An ES5 + esbuild-compatible wrapper for the 'tslib' library", + "main": "./build/index.js", + "exports": { + ".": { + "types": "./build/index.d.ts", + "import": "./build/index.js" + }, + "./esbuild-tools": { + "types": "./build/esbuild-tools.d.ts", + "import": "./build/esbuild-tools.js" + } + }, + "scripts": { + "build": "gosh ./build.sh build", + "clean": "gosh ./build.sh clean" + }, + "dependencies": { + "tslib": "^2.5.2", + "typescript": "^4.9.5" + }, + "devDependencies": { + "@keymanapp/resources-gosh": "*", + "esbuild": "^0.15.16" + }, + "type": "module" +} diff --git a/common/web/tslib/src/esbuild-tools.ts b/common/web/tslib/src/esbuild-tools.ts new file mode 100644 index 0000000000000000000000000000000000000000..9e258992535eab18c6f1e4b471b538bd429a4393 --- /dev/null +++ b/common/web/tslib/src/esbuild-tools.ts @@ -0,0 +1,327 @@ +import esbuild from 'esbuild'; +import fs from 'fs'; + +// Note: this package is intended 100% as a dev-tool, hence why esbuild is just a dev-dependency. + +// Component #1: Detect all `tslib` helpers we actually want to use. +export async function determineNeededDowncompileHelpers(config: esbuild.BuildOptions, ignoreFilePattern?: RegExp, log?: boolean) { + const tslibHelperNames = [ + "__extends", + "__assign", + "__rest", + "__decorate", + "__param", + "__metadata", + "__awaiter", + "__generator", + "__exportStar", // uses __createBinding + "__createBinding", + "__values", + "__read", + "__spread", + "__spreadArray", + "__spreadArrays", + "__await", + "__asyncGenerator", + "__asyncDelegator", + "__asyncValues", + "__makeTemplateObject", + "__importStar", + "__setModuleDefault", // only used by the previous entry! + "__importDefault", + "__classPrivateFieldGet", + "__classPrivateFieldSet", + "__classPrivateFieldIn", + "__runInitializers", + "__setFunctionName", + "__propKey", + "__esDecorate" + ]; + + const detectedHelpers: string[] = []; + + let tslibHelperDetectionPlugin = { + name: 'tslib helper use detection', + setup(build) { + build.onLoad({filter: /\.js$/}, async (args) => { + let source = await fs.promises.readFile(args.path, 'utf8'); + + let warnings: esbuild.Message[] = []; + + if(/tslib.js$/.test(args.path)) { + let declarationRegex = /var (__[a-zA-Z0-9]+)/g; + let results = source.match(declarationRegex); + + for(let result of results) { + let capture = result.substring(4); + + if(!tslibHelperNames.find((entry) => entry == capture)) { + // Match: `result` itself. Grab index, build location, build message. + let index = source.indexOf(result); + + warnings.push(buildMessage( + 'tslib helper use detection', + 'Probable `tslib` helper `' + capture + '` has not been validated for tree-shaking potential', + indexToSourceLocation(source, index, result.length, args.path) + )); + } + } + + // Returning `undefined` for `content` makes this 'pass-through' - it doesn't prevent other + // configured plugins from working. + return { + warnings: warnings + }; + } + + if(ignoreFilePattern?.test(args.path)) { + return; + } + + for(let helper of tslibHelperNames) { + if(source.indexOf(helper) > -1 && !detectedHelpers.find((entry) => entry == helper)) { + detectedHelpers.push(helper); + + if(helper == '__importStar') { + detectedHelpers.push('__setModuleDefault'); + detectedHelpers.push('__createBinding'); + } else if(helper == '__exportStar') { + detectedHelpers.push('__createBinding'); + } + } + } + + // Returning `undefined` makes this 'pass-through' - it doesn't prevent other + // configured plugins from working. + return; + }); + } + } + + // tslib tree-shake phase 1 - detecting which helpers are safe to remove. + await esbuild.build({ + ...config, + // `tslibHelperDetectionPlugin` is pass-through, and so has no net effect on + // the build. We just use this run to scan for any utilized `tslib` helper funcs, + // not to manipulate the actual source in any way. + plugins: [ tslibHelperDetectionPlugin, ...(config.plugins ?? []) ], + write: false + }); + + // At this point, we can determine what's unused. + detectedHelpers.sort(); + + const unusedHelpers: string[] = []; + tslibHelperNames.forEach((entry) => { + if(!detectedHelpers.find((detected) => detected == entry)) { + unusedHelpers.push(entry); + } + }); + + if(log) { + // Logs on the tree-shaking decisions + console.log("Detected helpers from tslib: "); + console.log(); + + console.log(); + console.log("Unused helpers: "); + console.log(unusedHelpers); + } + + return unusedHelpers; +} + +function indexToSourceLocation(source: string, index: number, matchLength: number, file: string): esbuild.Location { + let priorText = source.substring(0, index); + let lineNum = priorText.split('\n').length; + let lastLineBreakIndex = priorText.lastIndexOf('\n'); + let colNum = index - lastLineBreakIndex; + let nextLineBreakIndex = source.indexOf('\n', lastLineBreakIndex+1); + + return { + file: file, + line: lineNum, + column: colNum, + lineText: source.substring(lastLineBreakIndex+1, nextLineBreakIndex), + namespace: '', + suggestion: '', + length: matchLength + } +} + +function buildMessage(pluginName: string, message: string, location: esbuild.Location): esbuild.Message { + return { + id: '', + notes: [], + detail: '', + pluginName: pluginName, + text: message, + location: location + } +} + +// Ugh. https://github.com/evanw/esbuild/issues/2656#issuecomment-1304013941 +function wrapClassPlugin(instancePlugin: esbuild.Plugin) { + return { + name: instancePlugin.name, + setup: instancePlugin.setup.bind(instancePlugin) + }; +} + +// Component #2: when we've actually determined which ones are safe to remove, this plugin +// can remove their code. +class TslibTreeshaker implements esbuild.Plugin { + public readonly name = 'tslib forced treeshaking'; + + private unusedHelpers: string[]; + + constructor(unusedHelpers: string[]) { + this.unusedHelpers = unusedHelpers; + } + + treeshakeDefinition(source: string, start: number, expectTernary: boolean, name: string, location: esbuild.Location) { + let scopeDepth = 0; + let parenDepth = 0; + let topLevelTernaryActive = 0; + let i = start; + let char = source.charAt(i); + while(char != '}' || --scopeDepth != 0 || topLevelTernaryActive) { + if(char == '{') { + scopeDepth++; + } else if(char == '(') { + parenDepth++; + } else if(char == ')') { + parenDepth--; + } else if(char == '?' && scopeDepth == 0) { + if(!expectTernary) { + return { + source: source, + error: buildMessage(this.name, `Unexpected conditional for definition of ${name}`, location) + } + } + topLevelTernaryActive++; + } else if(char == ':' && topLevelTernaryActive && scopeDepth == 0) { + topLevelTernaryActive--; + } + i++; + + if(i > source.length) { + return { + source: source, + error: buildMessage(this.name, `Failed to determine end of definition for ${name}`, location) + }; + } + + char = source.charAt(i); + } + i++; // we want to erase the final '}', too. + + // The functions may be wrapped with parens. + if(parenDepth == 1) { + let nextOpen = source.indexOf('(', i); + let nextClosed = source.indexOf(')', i); + + if(nextOpen < nextClosed) { + return { + source: source, + error: buildMessage(this.name, `Failed to determine end of definition for ${name}`, location) + }; + } else { + i = nextClosed + 1; + } + } + + if(source.charAt(i) == ';') { + i++; + } + + return { + source: source.replace(source.substring(start, i), '') + }; + } + + setup(build: esbuild.PluginBuild) { + build.onLoad({filter: /tslib.js$/}, async (args) => { + const trueSource = await fs.promises.readFile(args.path, 'utf8'); + let source = trueSource; + + let warnings: esbuild.Message[] = []; + let errors: esbuild.Message[] = []; + + for(let unusedHelper of this.unusedHelpers) { + // Removes the 'exporter' line used to actually export it from the tslib source. + source = source.replace(`exporter\(\"${unusedHelper}\", ${unusedHelper}\);`, ''); + + // Removes the actual helper function definition - obviously, the biggest filesize savings to be had here. + let matchString = `${unusedHelper} = function`; + let expectTernary = false; + + // A special case - it has two different versions depending on if Object.create exists or not. + // This is established via a ternary conditional. Just adds a bit to the parsing. + if(unusedHelper == '__createBinding') { + matchString = `${unusedHelper} = `; + expectTernary = true; + } else if(unusedHelper == '__setModuleDefault') { + matchString = `var ${unusedHelper} = `; // is inlined, not declared at top! + expectTernary = true; + } + let definitionStart = source.indexOf(matchString); + + if(definitionStart > -1) { + const result = this.treeshakeDefinition( + source, + definitionStart, + expectTernary, + unusedHelper, + indexToSourceLocation(trueSource, trueSource.indexOf(matchString), matchString.length, args.path) + ); + + if(result.error) { + errors.push(result.error); + } else { + source = result.source; + } + continue; + } + + // If we reached this point, we couldn't treeshake the unused helper appropriately. + if(definitionStart == -1) { + // Matches the standard definition pattern's left-hand assignment component. + matchString = `${unusedHelper} =` + let bestGuessIndex = trueSource.indexOf(matchString); + + if(bestGuessIndex == -1) { + // Failing the above, we match the declaration higher up within the file. + matchString = `var ${unusedHelper}` + bestGuessIndex = trueSource.indexOf(matchString); + } + + // Failing THAT, we just give up and go start-of-file. + if(bestGuessIndex == -1) { + matchString = ''; + bestGuessIndex = 0; + } + + warnings.push( + buildMessage( + this.name, + "tslib has likely been updated recently; could not force-treeshake tslib helper " + unusedHelper, + indexToSourceLocation(trueSource, bestGuessIndex, matchString.length, args.path) + ) + ); + } + } + + return { + contents: source, + loader: 'js', + warnings: warnings, + errors: errors + }; + }); + } +}; + +export function buildTslibTreeshaker(unusedHelpers: string[]) { + return wrapClassPlugin(new TslibTreeshaker(unusedHelpers)); +}; \ No newline at end of file diff --git a/common/web/tslib/src/index.ts b/common/web/tslib/src/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..2f173ad83fa91de2a1a7c3f982e39bae3e5617d9 --- /dev/null +++ b/common/web/tslib/src/index.ts @@ -0,0 +1,3 @@ +export * from '../../../../node_modules/tslib/tslib.js'; + +export * as tslib from '../../../../node_modules/tslib/tslib.js'; \ No newline at end of file diff --git a/common/web/tslib/tsconfig.json b/common/web/tslib/tsconfig.json new file mode 100644 index 0000000000000000000000000000000000000000..c141af3d3c5f0ae12b4bb8c67f824a443b13383a --- /dev/null +++ b/common/web/tslib/tsconfig.json @@ -0,0 +1,22 @@ +{ + "extends": "../../../tsconfig-base.json", + "compilerOptions": { + "allowJs": true, + "module": "es6", + "moduleResolution": "Node", + "allowSyntheticDefaultImports": true, + "inlineSources": true, + "sourceMap": true, + "declaration": true, + "target": "es5", + "tsBuildInfoFile": "./build/tsconfig.tsbuildinfo", + "types": ["node"], + "lib": ["es6", "DOM"], // The latter is b/c esbuild expects the type. + "baseUrl": "./src", + "outDir": "./build/", + "rootDir": "./src" + }, + "include": [ + "./src/*.ts" + ] +} diff --git a/common/web/types/src/kpj/kpj-file-reader.ts b/common/web/types/src/kpj/kpj-file-reader.ts index 5f768c54f1ea159dccd318e1eb997be1d1d5e5c6..21e92c975b2313a3dd0f1508cbd663ef20654188 100644 --- a/common/web/types/src/kpj/kpj-file-reader.ts +++ b/common/web/types/src/kpj/kpj-file-reader.ts @@ -27,8 +27,8 @@ export class KPJFileReader { return data as KPJFile; } - public validate(source: KPJFile, schemaBuffer: Buffer): void { - const schema = JSON.parse(schemaBuffer.toString('utf8')); + public validate(source: KPJFile, schemaBuffer: Uint8Array): void { + const schema = JSON.parse(new TextDecoder().decode(schemaBuffer)); const ajv = new Ajv(); if(!ajv.validate(schema, source)) { throw new Error(ajv.errorsText()); diff --git a/common/web/types/src/kvk/kvks-file-reader.ts b/common/web/types/src/kvk/kvks-file-reader.ts index c12b7036e4a3828098dd05ae5f99536d9e081783..13c6c8716c11c1b6a02e98f8b63fc18ed12cb6d0 100644 --- a/common/web/types/src/kvk/kvks-file-reader.ts +++ b/common/web/types/src/kvk/kvks-file-reader.ts @@ -7,12 +7,6 @@ import { VisualKeyboard, VisualKeyboardHeaderFlags, VisualKeyboardKey, VisualKey import { USVirtualKeyCodes } from '../consts/virtual-key-constants.js'; import { BUILDER_KVK_HEADER_VERSION } from './kvk-file.js'; -export enum KVKSParseErrorType { invalidVkey }; -export class KVKSParseError extends Error { - public type: KVKSParseErrorType; - public vkey: string; -}; - export default class KVKSFileReader { public read(file: Uint8Array): KVKSourceFile { let source: KVKSourceFile; @@ -33,10 +27,12 @@ export default class KVKSFileReader { // rather than using the version tagged on npmjs.com. }); - parser.parseString(file, (e: unknown, r: unknown) => { source = r as KVKSourceFile }); - source = this.boxArrays(source); - this.cleanupFlags(source); - this.cleanupUnderscore('visualkeyboard', source.visualkeyboard); + parser.parseString(file, (e: unknown, r: unknown) => { if(e) { throw e }; source = r as KVKSourceFile }); + if(source) { + source = this.boxArrays(source); + this.cleanupFlags(source); + this.cleanupUnderscore('visualkeyboard', source.visualkeyboard); + } return source; } @@ -72,15 +68,15 @@ export default class KVKSFileReader { } } - public validate(source: KVKSourceFile, schemaBuffer: Buffer): void { - const schema = JSON.parse(schemaBuffer.toString('utf8')); + public validate(source: KVKSourceFile, schemaBuffer: Uint8Array): void { + const schema = JSON.parse(new TextDecoder().decode(schemaBuffer)); const ajv = new Ajv(); if(!ajv.validate(schema, source)) { throw new Error(ajv.errorsText()); } } - public transform(source: KVKSourceFile, errors?: KVKSParseError[]): VisualKeyboard { + public transform(source: KVKSourceFile, invalidVkeys?: string[]): VisualKeyboard { // NOTE: at this point, the xml should have been validated // and matched the schema result so we can assume properties exist let result: VisualKeyboard = { @@ -118,11 +114,8 @@ export default class KVKSFileReader { for(let sourceKey of layer.key) { let vkey = (USVirtualKeyCodes as any)[sourceKey.$?.vkey]; if(!vkey) { - if(errors) { - let e = new KVKSParseError(); - e.type = KVKSParseErrorType.invalidVkey; - e.vkey = sourceKey.$?.vkey; - errors.push(e); + if(typeof invalidVkeys !== 'undefined') { + invalidVkeys.push(sourceKey.$?.vkey); } continue; } diff --git a/common/web/types/src/kvk/kvks-file-writer.ts b/common/web/types/src/kvk/kvks-file-writer.ts index 613c6caa5108289ac9c99901ddc00a68383313dd..1b3a819f580f2d2ef23f1582aade95c98a6b97a7 100644 --- a/common/web/types/src/kvk/kvks-file-writer.ts +++ b/common/web/types/src/kvk/kvks-file-writer.ts @@ -96,7 +96,6 @@ export default class KVKSFileWriter { l.key.push(k); } - // console.dir(kvks, {depth:8}); let result = builder.buildObject(kvks); return result; //Uint8Array.from(result); } diff --git a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts index 35d7f627956417b508e9b358473ce019147afc80..fa74126462451b8a0dd4c70a30532f7f82cc6c53 100644 --- a/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/common/web/types/src/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -14,16 +14,20 @@ interface NameAndProps { '$$'?: any; // children }; -export default class LDMLKeyboardXMLSourceFileReader { - callbacks: CompilerCallbacks; +export class LDMLKeyboardXMLSourceFileReaderOptions { + importsPath: string; +}; + +export class LDMLKeyboardXMLSourceFileReader { + constructor(private options: LDMLKeyboardXMLSourceFileReaderOptions, private callbacks : CompilerCallbacks) { + } - constructor(callbacks : CompilerCallbacks) { - this.callbacks = callbacks; + static get defaultImportsURL() { + return new URL(`../import/`, import.meta.url); } - readImportFile(version: string, subpath: string): Buffer { - // TODO-LDML: sanitize input string - let importPath = new URL(`../import/${version}/${subpath}`, import.meta.url); + readImportFile(version: string, subpath: string): Uint8Array { + const importPath = this.callbacks.resolveFilename(this.options.importsPath, `${version}/${subpath}`); return this.callbacks.loadFile(importPath); } @@ -201,8 +205,8 @@ export default class LDMLKeyboardXMLSourceFileReader { /** * @returns true if valid, false if invalid */ - public validate(source: LDMLKeyboardXMLSourceFile | LDMLKeyboardTestDataXMLSourceFile, schemaSource: Buffer): boolean { - const schema = JSON.parse(schemaSource.toString('utf8')); + public validate(source: LDMLKeyboardXMLSourceFile | LDMLKeyboardTestDataXMLSourceFile, schemaSource: Uint8Array): boolean { + const schema = JSON.parse(new TextDecoder().decode(schemaSource)); const ajv = new Ajv(); if(!ajv.validate(schema, source)) { for (let err of ajv.errors) { diff --git a/common/web/types/src/main.ts b/common/web/types/src/main.ts index 4e7755038403dc3b57ceecc96d2dbd9470a19aa9..ddaa318d3e073cba9a368cd1f20bcb71eb7c40fc 100644 --- a/common/web/types/src/main.ts +++ b/common/web/types/src/main.ts @@ -14,7 +14,7 @@ export * as KvksFile from './kvk/kvk-file.js'; export * as LDMLKeyboard from './ldml-keyboard/ldml-keyboard-xml.js'; export { LDMLKeyboardTestDataXMLSourceFile } from './ldml-keyboard/ldml-keyboard-testdata-xml.js'; -export { default as LDMLKeyboardXMLSourceFileReader } from './ldml-keyboard/ldml-keyboard-xml-reader.js'; +export { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from './ldml-keyboard/ldml-keyboard-xml-reader.js'; export * as Constants from './consts/virtual-key-constants.js'; diff --git a/common/web/types/src/util/compiler-interfaces.ts b/common/web/types/src/util/compiler-interfaces.ts index 849e945321fb5b811f2c81b9b05b7d5d74455381..d3932b7bfa887a997569f686c7309f5421de0152 100644 --- a/common/web/types/src/util/compiler-interfaces.ts +++ b/common/web/types/src/util/compiler-interfaces.ts @@ -120,13 +120,11 @@ export interface CompilerFileSystemCallbacks { export interface CompilerCallbacks { /** * Attempt to load a file. Return falsy if not found. - * TODO: accept only string * TODO: never return falsy, just throw if not found? - * TODO: Buffer is Node-only. * @param baseFilename * @param filename */ - loadFile(filename: string | URL): Buffer; + loadFile(filename: string): Uint8Array; get path(): CompilerPathCallbacks; get fs(): CompilerFileSystemCallbacks; @@ -138,7 +136,7 @@ export interface CompilerCallbacks { */ resolveFilename(baseFilename: string, filename: string): string; - loadSchema(schema: CompilerSchema): Buffer; + loadSchema(schema: CompilerSchema): Uint8Array; reportMessage(event: CompilerEvent): void; debug(msg: string): void; }; diff --git a/common/web/types/test/helpers/TestCompilerCallbacks.ts b/common/web/types/test/helpers/TestCompilerCallbacks.ts index 596a731d58880a1fbf4d7dab95a81c795bf212e1..8316264a774bf8b85e4adeb94c48937935247fdf 100644 --- a/common/web/types/test/helpers/TestCompilerCallbacks.ts +++ b/common/web/types/test/helpers/TestCompilerCallbacks.ts @@ -42,7 +42,7 @@ export class TestCompilerCallbacks implements CompilerCallbacks { return resolveFilename(baseFilename, filename); } - loadFile(filename: string | URL): Buffer { + loadFile(filename: string): Uint8Array { // TODO: error management, does it belong here? try { return loadFile(filename); diff --git a/common/web/types/test/helpers/index.ts b/common/web/types/test/helpers/index.ts index 58dc32dc9c1618da13acb027d39ad3a379a53394..06906df148589fb5a10808b92e685888d5fd8a7b 100644 --- a/common/web/types/test/helpers/index.ts +++ b/common/web/types/test/helpers/index.ts @@ -28,7 +28,10 @@ export function loadSchema(schema: CompilerSchema): Buffer { } export function resolveFilename(baseFilename: string, filename: string) { - const basePath = path.dirname(baseFilename); + const basePath = + baseFilename.endsWith('/') || baseFilename.endsWith('\\') ? + baseFilename : + path.dirname(baseFilename); // Transform separators to platform separators -- we are agnostic // in our use here but path prefers files may use // either / or \, although older kps files were always \. diff --git a/common/web/types/test/helpers/reader-callback-test.ts b/common/web/types/test/helpers/reader-callback-test.ts index 8c85a759acf270a8a99e5eb947faf8b51db8bb93..204e0ec5bf4875168e082630354ff2453b756def 100644 --- a/common/web/types/test/helpers/reader-callback-test.ts +++ b/common/web/types/test/helpers/reader-callback-test.ts @@ -1,11 +1,16 @@ import 'mocha'; import {assert} from 'chai'; import { loadFile, makePathToFixture, loadSchema } from '../helpers/index.js'; -import LDMLKeyboardXMLSourceFileReader from '../../src/ldml-keyboard/ldml-keyboard-xml-reader.js'; +import { LDMLKeyboardXMLSourceFileReader, LDMLKeyboardXMLSourceFileReaderOptions } from '../../src/ldml-keyboard/ldml-keyboard-xml-reader.js'; import { CompilerEvent } from '../../src/util/compiler-interfaces.js'; import { LDMLKeyboardXMLSourceFile } from '../../src/ldml-keyboard/ldml-keyboard-xml.js'; import { LDMLKeyboardTestDataXMLSourceFile } from '../ldml-keyboard/ldml-keyboard-testdata-xml.js'; import { TestCompilerCallbacks } from './TestCompilerCallbacks.js'; +import { fileURLToPath } from 'url'; + +const readerOptions: LDMLKeyboardXMLSourceFileReaderOptions = { + importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL) +}; export interface CompilationCase { /** @@ -71,7 +76,7 @@ export interface TestDataCase { export function testReaderCases(cases : CompilationCase[]) { // we need our own callbacks rather than using the global so messages don't get mixed const callbacks = new TestCompilerCallbacks(); - const reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + const reader = new LDMLKeyboardXMLSourceFileReader(readerOptions, callbacks); for (let testcase of cases) { const expectFailure = testcase.throws || !!(testcase.errors); // if true, we expect this to fail const testHeading = expectFailure ? `should fail to load: ${testcase.subpath}`: @@ -122,7 +127,7 @@ export function testReaderCases(cases : CompilationCase[]) { export function testTestdataReaderCases(cases : TestDataCase[]) { // we need our own callbacks rather than using the global so messages don't get mixed const callbacks = new TestCompilerCallbacks(); - const reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + const reader = new LDMLKeyboardXMLSourceFileReader(readerOptions, callbacks); for (let testcase of cases) { const expectFailure = testcase.throws || !!(testcase.errors); // if true, we expect this to fail const testHeading = expectFailure ? `should fail to load: ${testcase.subpath}`: diff --git a/common/web/types/test/kpj/test-kpj-file-reader.ts b/common/web/types/test/kpj/test-kpj-file-reader.ts index b7c2f710131db0d9bdf8be3f9190a267ee4a2ad5..95b5f1d01ae86030139e1ff594eed17fb5796713 100644 --- a/common/web/types/test/kpj/test-kpj-file-reader.ts +++ b/common/web/types/test/kpj/test-kpj-file-reader.ts @@ -15,7 +15,6 @@ describe('kpj-file-reader', function () { const input = fs.readFileSync(path); const reader = new KPJFileReader(callbacks); const kpj = reader.read(input); - console.dir(kpj); assert.doesNotThrow(() => { reader.validate(kpj, loadSchema('kpj')); }); @@ -79,7 +78,6 @@ describe('kpj-file-reader', function () { assert.lengthOf(project.files, 2); let f: KeymanDeveloperProjectFile10 = project.files[0]; - console.dir(f); assert.equal(f.id, 'id_f347675c33d2e6b1c705c787fad4941a'); assert.equal(f.filename, 'khmer_angkor.kmn'); assert.equal(f.filePath, 'source/khmer_angkor.kmn'); diff --git a/common/web/types/test/kvk/test-kvk-round-trip.ts b/common/web/types/test/kvk/test-kvk-round-trip.ts index 41dbe2dbc0cb25b553c790172998f006269c416b..031ad60b9e6e1c2fda395c2dda7caf8614794a90 100644 --- a/common/web/types/test/kvk/test-kvk-round-trip.ts +++ b/common/web/types/test/kvk/test-kvk-round-trip.ts @@ -5,7 +5,7 @@ import Hexy from 'hexy'; import gitDiff from 'git-diff'; const { hexy } = Hexy; import { loadSchema, makePathToFixture } from '../helpers/index.js'; -import KvksFileReader, { KVKSParseError } from "../../src/kvk/kvks-file-reader.js"; +import KvksFileReader from "../../src/kvk/kvks-file-reader.js"; import KvkFileReader from "../../src/kvk/kvk-file-reader.js"; import KvkFileWriter from "../../src/kvk/kvk-file-writer.js"; import KvksFileWriter from "../../src/kvk/kvks-file-writer.js"; @@ -53,9 +53,9 @@ describe('kvks-file-reader', function () { assert.doesNotThrow(() => { reader.validate(kvks, loadSchema('kvks')); }); - const errors: KVKSParseError[] = []; - const vk = reader.transform(kvks, errors); - assert.isEmpty(errors); + const invalidVkeys: string[] = []; + const vk = reader.transform(kvks, invalidVkeys); + assert.isEmpty(invalidVkeys); const writer = new KvkFileWriter(); const output = writer.write(vk); assertBufferMatch(Buffer.from(output), compiled); @@ -76,9 +76,9 @@ describe('kvks-file-reader', function () { assert.doesNotThrow(() => { kvksReader.validate(kvks, loadSchema('kvks')); }); - const errors: KVKSParseError[] = []; - const vk2 = kvksReader.transform(kvks, errors); - assert.isEmpty(errors); + const invalidVkeys: string[] = []; + const vk2 = kvksReader.transform(kvks, invalidVkeys); + assert.isEmpty(invalidVkeys); // make sure the binary is the same assert.deepEqual(vk2, vk); diff --git a/common/web/types/test/kvk/test-kvks-file.ts b/common/web/types/test/kvk/test-kvks-file.ts index 523bfaf54d618ce21da9f9137c67d9b04c39bd6a..4cbb299a0085b188d405d5a46aa23e964ad1a344 100644 --- a/common/web/types/test/kvk/test-kvks-file.ts +++ b/common/web/types/test/kvk/test-kvks-file.ts @@ -1,7 +1,7 @@ import * as fs from 'fs'; import 'mocha'; import { loadSchema, makePathToFixture } from '../helpers/index.js'; -import KvksFileReader, { KVKSParseError } from "../../src/kvk/kvks-file-reader.js"; +import KvksFileReader from "../../src/kvk/kvks-file-reader.js"; import KvksFileWriter from "../../src/kvk/kvks-file-writer.js"; import { verify_khmer_angkor } from './test-kvk-utils.js'; import { assert } from 'chai'; @@ -16,9 +16,9 @@ describe('kvks-file-reader', function() { assert.doesNotThrow(() => { reader.validate(kvks, loadSchema('kvks')); }); - const errors: KVKSParseError[] = []; - const vk = reader.transform(kvks, errors); - assert.isEmpty(errors); + const invalidVkeys: string[] = []; + const vk = reader.transform(kvks, invalidVkeys); + assert.isEmpty(invalidVkeys); verify_khmer_angkor(vk); }); }); @@ -30,9 +30,9 @@ describe('kvks-file-writer', function() { const reader = new KvksFileReader(); const kvksExpected = reader.read(input); - const errors: KVKSParseError[] = []; - const vk = reader.transform(kvksExpected, errors); - assert.isEmpty(errors); + const invalidVkeys: string[] = []; + const vk = reader.transform(kvksExpected, invalidVkeys); + assert.isEmpty(invalidVkeys); const writer = new KvksFileWriter(); const output = writer.write(vk); diff --git a/common/web/utils/build-bundler.js b/common/web/utils/build-bundler.js index 72df8e0ae1e9540388ee26abcde5c941ab4126a1..cda215474f8ecc4eae4c086c72028cf3bdfffcb2 100644 --- a/common/web/utils/build-bundler.js +++ b/common/web/utils/build-bundler.js @@ -8,6 +8,9 @@ import { spawn } from 'child_process'; // Bundles to a compact ESModule esbuild.buildSync({ entryPoints: ['build/obj/index.js'], + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, //minify: true, // No need to minify a module. @@ -24,6 +27,9 @@ esbuild.buildSync({ // Bundles to a compact CommonJS (classic Node) module esbuild.buildSync({ entryPoints: ['build/obj/index.js'], + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, //minify: true, // No need to minify a module. diff --git a/common/web/utils/build.sh b/common/web/utils/build.sh index f8daffb4a3e282fc16170b501f86f4afb3ff6c50..d4ff9ef8f31dde20531e589695cae39ffc2dd1ac 100755 --- a/common/web/utils/build.sh +++ b/common/web/utils/build.sh @@ -19,6 +19,7 @@ cd "$THIS_SCRIPT_PATH" builder_describe \ "Compiles the web-oriented utility function module." \ "@/common/web/keyman-version" \ + "@/common/web/tslib" \ clean configure build test \ "--ci For use with action ${BUILDER_TERM_START}test${BUILDER_TERM_END} - emits CI-friendly test reports" @@ -39,16 +40,12 @@ if builder_start_action clean; then fi if builder_start_action build; then - # Note: in a dependency build, we'll expect utils to be built by tsc -b - if builder_is_dep_build; then - builder_echo "skipping tsc -b; will be completed by $builder_dep_parent" - else - tsc --build "$THIS_SCRIPT_PATH/tsconfig.json" - node build-bundler.js - - # So... tsc does declaration-bundling on its own pretty well, at least for local development. - tsc --emitDeclarationOnly --outFile ./build/lib/index.d.ts - fi + tsc --build "$THIS_SCRIPT_PATH/tsconfig.json" + node build-bundler.js + + # So... tsc does declaration-bundling on its own pretty well, at least for local development. + tsc --emitDeclarationOnly --outFile ./build/lib/index.d.ts + builder_finish_action success build fi diff --git a/common/web/utils/tsconfig.json b/common/web/utils/tsconfig.json index 838a704dfb31ced141fb365755e78f9450eb7f85..e19eadb2d3efe6a14b9648489b578aec941539fa 100644 --- a/common/web/utils/tsconfig.json +++ b/common/web/utils/tsconfig.json @@ -4,6 +4,7 @@ "allowJs": true, "module": "es6", "moduleResolution": "Node16", + "importHelpers": true, "inlineSources": true, "sourceMap": true, "declaration": true, diff --git a/common/windows/cpp/include/legacy_kmx_file.h b/common/windows/cpp/include/legacy_kmx_file.h index ae18543312f2303575ac276e1521c7bff9c20eb7..d961250912f78fc5bab8726af0d652f6b6db86e6 100644 --- a/common/windows/cpp/include/legacy_kmx_file.h +++ b/common/windows/cpp/include/legacy_kmx_file.h @@ -409,7 +409,6 @@ typedef COMP_GROUP *PCOMP_GROUP; typedef struct _COMPILER_OPTIONS { DWORD dwSize; BOOL ShouldAddCompilerVersion; - BOOL UseKmcmpLib; } COMPILER_OPTIONS; typedef COMPILER_OPTIONS *PCOMPILER_OPTIONS; diff --git a/core/doc/hotdoc.json b/core/doc/hotdoc.json index 61a0028fb0bd464c8e01ff40a8d4b22a32b41d51..4ece50d0f52c505abb5a21b46c5da2e8199d9227 100644 --- a/core/doc/hotdoc.json +++ b/core/doc/hotdoc.json @@ -1,10 +1,14 @@ { "project_name": "@project_name@", "project_version": "@project_version@", - "sitemap": "../../doc/sitemap.txt", - "index": "../../doc/markdown_files/index.md", + "sitemap": "@doc_dir@/sitemap.txt", + "index": "@doc_dir@/markdown_files/index.md", "c_sources": [ - "../include/keyboardprocessor.h" + "@include_dir@/keyman/keyboardprocessor.h" + ], + "c_include_directories": [ + "@include_dir@", + "@doc_dir@/../../common/include" ], "c_smart_index" : true, "output": ".", diff --git a/core/doc/meson.build b/core/doc/meson.build index 2772819ce7ff8b54cea5774e59f13cbea7802a00..01fcb5c98ad60250fb7e3ac3561c5ac5d9791a22 100644 --- a/core/doc/meson.build +++ b/core/doc/meson.build @@ -12,18 +12,20 @@ if hotdoc.found() cfg = configuration_data() cfg.set('project_name', meson.project_name()) cfg.set('project_version', meson.project_version()) + cfg.set('doc_dir', meson.current_source_dir()) + cfg.set('include_dir', meson.current_source_dir() / '../include') configure_file(input: 'hotdoc.json', output: 'hotdoc.json', configuration: cfg) deps = files( - '../include/keyman/keyboardprocessor.h.in', + '../include/keyman/keyboardprocessor.h', '../src/jsonpp.hpp', '../src/utfcodec.hpp' ) docs = custom_target('docs', output: ['html'], - input: ['sitemap.txt', 'markdown_files/index.md'], + input: ['sitemap.txt', 'markdown_files/index.md', deps], command: [hotdoc, '--verbose', '--conf-file=doc/hotdoc.json', '--output=doc', 'run'], depend_files: deps, install: true, diff --git a/core/include/keyman/keyboardprocessor.h b/core/include/keyman/keyboardprocessor.h index 3513cdede2e88223e47ada46547975b3d3bc856a..429a8ae6cbf9118850a12460db2a289c262920f1 100644 --- a/core/include/keyman/keyboardprocessor.h +++ b/core/include/keyman/keyboardprocessor.h @@ -779,7 +779,7 @@ km_kbp_keyboard_get_key_list(km_kbp_keyboard const *keyboard, km_kbp_keyboard_key **out); -/* +/** ``` ### `km_kbp_keyboard_key_list_dispose` ##### Description: @@ -794,28 +794,41 @@ returned by `km_kbp_keyboard_get_key_list`. KMN_API void km_kbp_keyboard_key_list_dispose(km_kbp_keyboard_key *key_list); - /** - * Returns the list of IMX libraries and function names that are referenced by - * the keyboard. The matching dispose call needs to be called to free the memory. + * km_kbp_keyboard_get_imx_list: + * + * Returns: the list of IMX libraries and function names that are referenced by + * the keyboard.The matching dispose call needs to be called to free the memory. */ KMN_API -km_kbp_status km_kbp_keyboard_get_imx_list(km_kbp_keyboard const *keyboard, km_kbp_keyboard_imx** imx_list); +km_kbp_status km_kbp_keyboard_get_imx_list(km_kbp_keyboard const *keyboard, km_kbp_keyboard_imx **imx_list); /** + * km_kbp_keyboard_imx_list_dispose: + * * Disposes of the IMX list + * + * Returns: -- */ KMN_API void km_kbp_keyboard_imx_list_dispose(km_kbp_keyboard_imx *imx_list); /** + * km_kbp_state_imx_register_callback: + * * Register the IMX callback endpoint for the client. + * + * Returns: -- */ KMN_API void km_kbp_state_imx_register_callback(km_kbp_state *state, km_kbp_keyboard_imx_platform imx_callback, void *callback_object); /** + * km_kbp_state_imx_deregister_callback: + * * De-register IMX callback endpoint for the client. + * + * Returns: -- */ KMN_API void km_kbp_state_imx_deregister_callback(km_kbp_state *state); @@ -1042,6 +1055,8 @@ enum km_kbp_tech_value { }; /** + * km_kbp_event_flags: + * * Bit flags to be used with the event_flags parameter of km_kbp_process_event */ enum km_kbp_event_flags { @@ -1162,10 +1177,8 @@ km_kbp_event( ); enum km_kbp_event_code { - /** - * A keyboard has been activated by the user. The processor may use this - * event, for example, to switch caps lock state or provide other UX. - */ + // A keyboard has been activated by the user. The processor may use this + // event, for example, to switch caps lock state or provide other UX. KM_KBP_EVENT_KEYBOARD_ACTIVATED = 1, //future: KM_KBP_EVENT_KEYBOARD_DEACTIVATED = 2, }; diff --git a/developer/src/common/delphi/compiler/compile.pas b/developer/src/common/delphi/compiler/compile.pas index b89ef0eb72f63f230ba04a9142175b0ff8c278b0..0124150ef6297561d33f62191f572bc06cf03945 100644 --- a/developer/src/common/delphi/compiler/compile.pas +++ b/developer/src/common/delphi/compiler/compile.pas @@ -176,7 +176,6 @@ type TCompilerOptions = record dwSize: DWORD; ShouldAddCompilerVersion: BOOL; - UseKmcmpLib: BOOL; end; COMPILER_OPTIONS = TCompilerOptions; diff --git a/developer/src/common/include/kmn_compiler_errors.h b/developer/src/common/include/kmn_compiler_errors.h index 6cfae03fc9ea00c559fe53f1b55ddb0e5759b66a..31c5faf0c7b33a806b43ce14d013714ac07f4e54 100644 --- a/developer/src/common/include/kmn_compiler_errors.h +++ b/developer/src/common/include/kmn_compiler_errors.h @@ -224,8 +224,8 @@ #define CWARN_KeyShouldIncludeNCaps 0x000020AD #define CHINT_UnreachableRule 0x000010AE -#define CHINT_FilenameHasDifferingCase 0x000010AF -#define CWARN_MissingFile 0x000020B0 +#define CHINT_FilenameHasDifferingCase 0x000010AF // only used in kmcmpdll +#define CWARN_MissingFile 0x000020B0 // only used in kmcmpdll #define CERR_BufferOverflow 0x000080C0 #define CERR_Break 0x000080C1 diff --git a/developer/src/common/web/test-helpers/index.ts b/developer/src/common/web/test-helpers/index.ts index 7f34107dc91a8406b6d82c395f9fc0b0a6f21e36..a134aa67dfd40551100ede0ac74d57ac2ea0a075 100644 --- a/developer/src/common/web/test-helpers/index.ts +++ b/developer/src/common/web/test-helpers/index.ts @@ -36,7 +36,7 @@ export class TestCompilerCallbacks implements CompilerCallbacks { /* CompilerCallbacks */ - loadFile(filename: string | URL): Buffer { + loadFile(filename: string): Uint8Array { try { return fs.readFileSync(filename); } catch(e) { @@ -57,7 +57,10 @@ export class TestCompilerCallbacks implements CompilerCallbacks { } resolveFilename(baseFilename: string, filename: string): string { - const basePath = path.dirname(baseFilename); + const basePath = + baseFilename.endsWith('/') || baseFilename.endsWith('\\') ? + baseFilename : + path.dirname(baseFilename); // Transform separators to platform separators -- we are agnostic // in our use here but path prefers files may use // either / or \, although older kps files were always \. @@ -77,7 +80,7 @@ export class TestCompilerCallbacks implements CompilerCallbacks { this.messages.push(event); } - loadSchema(schema: CompilerSchema): Buffer { + loadSchema(schema: CompilerSchema): Uint8Array { return fs.readFileSync(new URL(SCHEMA_BASE + schema + '.schema.json', import.meta.url)); } diff --git a/developer/src/kmc-kmn/src/compiler/compiler.ts b/developer/src/kmc-kmn/src/compiler/compiler.ts index c46b894cb3caa3be6ee6cb430f9befc671468cac..9962ea6abd5048b6a1561de0dcae2605163f36fb 100644 --- a/developer/src/kmc-kmn/src/compiler/compiler.ts +++ b/developer/src/kmc-kmn/src/compiler/compiler.ts @@ -42,62 +42,26 @@ const baseOptions: CompilerOptions = { */ let callbackProcIdentifier = 0; -/** - * Pointer in wasm-space - */ -type WasmPtr = number; - -/** - * The wrapped functions - */ -class WasmWrapper { - Module: any; - - compileKeyboardFile?: (pszInfile: string, pszOutfile: string, aSaveDebug: number, aCompilerWarningsAsErrors: number, aWarnDeprecatedCode: number, msgProc: string) => boolean; - parseUnicodeSet?: (pat: string, buf: WasmPtr, length: number) => number; - setCompilerOptions?: (shouldAddCompilerVersion: number) => boolean; - - constructor(wasmModule: any) { - this.Module = wasmModule; - if (!wasmModule) { - throw Error(`wasm host did not load`); - } - this.compileKeyboardFile = this.Module.cwrap('kmcmp_Wasm_CompileKeyboardFile', 'boolean', ['string', 'string', 'number', 'number', 'number', 'string']); - this.parseUnicodeSet = this.Module.cwrap('kmcmp_Wasm_ParseUnicodeSet', 'number', ['string', 'number', 'number']); - this.setCompilerOptions = this.Module.cwrap('kmcmp_Wasm_SetCompilerOptions', 'boolean', ['number']); - - if (this.parseUnicodeSet === undefined - || this.setCompilerOptions === undefined - || this.compileKeyboardFile === undefined) { - throw Error(`some wasm functions did not load properly.`); - } - } - - /** - * Entry point into Wasm functions - * @returns WasmWrapper - */ - public static async load() : Promise { - return new WasmWrapper(await loadWasmHost()); - } -}; +const + callbackPrefix = 'kmnCompilerCallbacks_'; export class KmnCompiler { - callbackName: string; + private Module: any; + callbackID: string; // a unique numeric id added to globals with prefixed names callbacks: CompilerCallbacks; - wasm: WasmWrapper; constructor() { - this.callbackName = 'kmnCompilerCallback' + callbackProcIdentifier; + this.callbackID = callbackPrefix + callbackProcIdentifier.toString(); callbackProcIdentifier++; } public async init(callbacks: CompilerCallbacks): Promise { this.callbacks = callbacks; - if(!this.wasm) { + if(!this.Module) { try { - this.wasm = await WasmWrapper.load(); + this.Module = await loadWasmHost(); } catch(e: any) { + /* c8 ignore next 3 */ this.callbacks.reportMessage(CompilerMessages.Fatal_MissingWasmModule({e})); return false; } @@ -114,7 +78,9 @@ export class KmnCompiler { // Can't report a message here. throw Error('Must call Compiler.init(callbacks) before proceeding'); } - if(!this.wasm) { // fail if wasm not loaded or function not found + if(!this.Module) { + /* c8 ignore next 4 */ + // fail if wasm not loaded or function not found this.callbacks.reportMessage(CompilerMessages.Fatal_MissingWasmModule({})); return false; } @@ -122,16 +88,7 @@ export class KmnCompiler { } public run(infile: string, outfile: string, options?: CompilerOptions): boolean { - if(!this.verifyInitialized()) { - return false; - } - - options = {...baseOptions, ...options}; - (globalThis as any)[this.callbackName] = this.compilerMessageCallback; - // TODO: use callbacks for file access -- so kmc-kmn is entirely fs agnostic let result = this.runCompiler(infile, outfile, options); - delete (globalThis as any)[this.callbackName]; - //TODO: write the file out! if(result) { if(result.kmx) { this.callbacks.fs.writeFileSync(result.kmx.filename, result.kmx.data); @@ -151,17 +108,66 @@ export class KmnCompiler { return 1; } - private runCompiler(infile: string, outfile: string, options: CompilerOptions): CompilerResult { + private cachedFile: {filename: string; data: Uint8Array} = { + filename: null, + data: null + }; + + private loadFileCallback = (filename: string, baseFilename: string, buffer: number, bufferSize: number): number => { + let resolvedFilename = this.callbacks.resolveFilename(baseFilename, filename); + let data: Uint8Array; + if(this.cachedFile.filename == resolvedFilename) { + data = this.cachedFile.data; + } + else { + data = this.callbacks.loadFile(resolvedFilename); + if(!data) { + return -1; + } + this.cachedFile.filename = resolvedFilename; + this.cachedFile.data = data; + } + + if(buffer == 0) { + /* We need to return buffer size required */ + return data.byteLength; + } + + if(bufferSize != data.byteLength) { + /* c8 ignore next 2 */ + throw new Error(`loadFileCallback: second call, expected file size ${bufferSize} == ${data.byteLength}`); + } + + this.Module.HEAP8.set(data, buffer); + + return 1; + } + + public runCompiler(infile: string, outfile: string, options: CompilerOptions): CompilerResult { + if(!this.verifyInitialized()) { + /* c8 ignore next 2 */ + return null; + } + + options = {...baseOptions, ...options}; + + (globalThis as any)[this.callbackID] = { + message: this.compilerMessageCallback, + loadFile: this.loadFileCallback + }; + let result: CompilerResult = {}; - let wasm_interface = new this.wasm.Module.CompilerInterface(); + let wasm_interface = new this.Module.CompilerInterface(); + let wasm_options = new this.Module.CompilerOptions(); let wasm_result = null; try { - wasm_interface.saveDebug = options.saveDebug; - wasm_interface.compilerWarningsAsErrors = options.compilerWarningsAsErrors; - wasm_interface.warnDeprecatedCode = options.warnDeprecatedCode; - wasm_interface.messageCallback = this.callbackName; - wasm_interface.loadFileCallback = this.callbackName; // TODO: this is wrong, needs to be a new callback; not yet used though - wasm_result = this.wasm.Module.kmcmp_compile(infile, wasm_interface); + wasm_options.saveDebug = options.saveDebug; + wasm_options.compilerWarningsAsErrors = options.compilerWarningsAsErrors; + wasm_options.warnDeprecatedCode = options.warnDeprecatedCode; + wasm_options.shouldAddCompilerVersion = options.shouldAddCompilerVersion; + wasm_options.target = 0; //CKF_KEYMAN; TODO, support KMW + wasm_interface.callbacksKey = this.callbackID; // key of object on globalThis + wasm_result = this.Module.kmcmp_compile(infile, wasm_options, wasm_interface); if(!wasm_result.result) { return null; } @@ -175,11 +181,12 @@ export class KmnCompiler { result.kmx = { filename: outfile, - data: new Uint8Array(this.wasm.Module.HEAP8.buffer, wasm_result.kmx, wasm_result.kmxSize) + data: new Uint8Array(this.Module.HEAP8.buffer, wasm_result.kmx, wasm_result.kmxSize) }; return result; } catch(e) { + /* c8 ignore next 3 */ this.callbacks.reportMessage(CompilerMessages.Fatal_UnexpectedException({e:e})); return null; } finally { @@ -187,6 +194,8 @@ export class KmnCompiler { wasm_result.delete(); } wasm_interface.delete(); + wasm_options.delete(); + delete (globalThis as any)[this.callbackID]; } } @@ -194,20 +203,19 @@ export class KmnCompiler { // The compiler detected a .kvks file, which needs to be captured let reader = new KvksFileReader(); kvksFilename = this.callbacks.resolveFilename(kmnFilename, kvksFilename); - let kvks = reader.read(this.callbacks.loadFile(kvksFilename)); + let filename = this.callbacks.path.basename(kvksFilename); + let kvks = null; try { + kvks = reader.read(this.callbacks.loadFile(kvksFilename)); reader.validate(kvks, this.callbacks.loadSchema('kvks')); } catch(e) { - console.log(e); - // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); + this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({filename, e})); return null; } - let errors: any = []; //TODO: KVKSParseError[]; - let vk = reader.transform(kvks, errors); - if(!vk || errors.length) { - console.dir(errors); - // TODO: this.callbacks.reportMessage(CompilerMessages.Error_InvalidKvksFile({e})); - return null; + let invalidVkeys: string[] = []; + let vk = reader.transform(kvks, invalidVkeys); + for(let invalidVkey of invalidVkeys) { + this.callbacks.reportMessage(CompilerMessages.Warn_InvalidVkeyInKvksFile({filename, invalidVkey})); } let writer = new KvkFileWriter(); return { @@ -224,22 +232,23 @@ export class KmnCompiler { */ public parseUnicodeSet(pattern: string, bufferSize: number) : UnicodeSet | null { if(!this.verifyInitialized()) { + /* c8 ignore next 2 */ return null; } if (!bufferSize) { + /* c8 ignore next 2 */ bufferSize = 100; // TODO-LDML: Preflight mode? Reuse buffer? } - const { Module } = this.wasm; - const buf = Module.asm.malloc(bufferSize * 2 * Module.HEAPU32.BYTES_PER_ELEMENT); + const buf = this.Module.asm.malloc(bufferSize * 2 * this.Module.HEAPU32.BYTES_PER_ELEMENT); // TODO-LDML: Catch OOM - const rc = this.wasm.parseUnicodeSet(pattern, buf, bufferSize); + const rc = this.Module.kmcmp_parseUnicodeSet(pattern, buf, bufferSize); if (rc >= 0) { const ranges = []; - const startu = (buf / Module.HEAPU32.BYTES_PER_ELEMENT); + const startu = (buf / this.Module.HEAPU32.BYTES_PER_ELEMENT); for (let i = 0; i < rc; i++) { - const low = Module.HEAPU32[startu + (i * 2) + 0]; - const high = Module.HEAPU32[startu + (i * 2) + 1]; + const low = this.Module.HEAPU32[startu + (i * 2) + 0]; + const high = this.Module.HEAPU32[startu + (i * 2) + 1]; ranges.push([low, high]); } // TODO-LDML: no free?? @@ -276,6 +285,7 @@ function getUnicodeSetError(rc: number) : CompilerEvent { case KMCMP_FATAL_OUT_OF_RANGE: return CompilerMessages.Fatal_UnicodeSetOutOfRange(); default: + /* c8 ignore next */ return CompilerMessages.Fatal_UnexpectedException({e: `Unexpected UnicodeSet error code ${rc}`}); } } diff --git a/developer/src/kmc-kmn/src/compiler/messages.ts b/developer/src/kmc-kmn/src/compiler/messages.ts index 10578870d7c1499350936abe16981a503cc8167a..0769096e070014b221a545ce4c34736ec95b9d02 100644 --- a/developer/src/kmc-kmn/src/compiler/messages.ts +++ b/developer/src/kmc-kmn/src/compiler/messages.ts @@ -67,6 +67,14 @@ export class CompilerMessages { static Error_UnicodeSetSyntaxError = () => m(this.ERROR_UnicodeSetSyntaxError, `UnicodeSet had a Syntax Error while parsing`); static ERROR_UnicodeSetSyntaxError = SevError | 0x1007; + + static Error_InvalidKvksFile = (o:{filename: string, e: any}) => m(this.ERROR_InvalidKvksFile, + `Error encountered parsing ${o.filename}: ${o.e}`); + static ERROR_InvalidKvksFile = SevError | 0x1008; + + static Warn_InvalidVkeyInKvksFile = (o:{filename: string, invalidVkey: string}) => m(this.WARN_InvalidVkeyInKvksFile, + `Invalid virtual key ${o.invalidVkey} found in ${o.filename}`); + static WARN_InvalidVkeyInKvksFile = SevWarn | 0x1009; } export function mapErrorFromKmcmplib(line: number, code: number, msg: string): CompilerEvent { diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kmn new file mode 100644 index 0000000000000000000000000000000000000000..f37d6397c5099dbbb61bfb3fa464c90c29dc9958 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmc throws an error with an invalid .kvks file + +store(&NAME) 'error_invalid_kvks_file' +store(&VERSION) '9.0' +store(&VISUALKEYBOARD) 'error_invalid_kvks_file.kvks' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kvks b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kvks new file mode 100644 index 0000000000000000000000000000000000000000..c681064f5b188af0703f5c1a31c66332abccf68c --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/error_invalid_kvks_file.kvks @@ -0,0 +1,9 @@ + + +
+ 10.0 + caps_lock_layer_3620 + + +
+
diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kmn b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kmn new file mode 100644 index 0000000000000000000000000000000000000000..18a88973e93d128ea46c93c30f5346cc0395c3b1 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmc reports a warning with an invalid vkey in the kvks + +store(&NAME) 'warn_invalid_vkey_in_kvks_file' +store(&VERSION) '9.0' +store(&VISUALKEYBOARD) 'warn_invalid_vkey_in_kvks_file.kvks' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks new file mode 100644 index 0000000000000000000000000000000000000000..f7b247db34e1f9ea8072c40c3a57289d3b461380 --- /dev/null +++ b/developer/src/kmc-kmn/test/fixtures/invalid-keyboards/warn_invalid_vkey_in_kvks_file.kvks @@ -0,0 +1,14 @@ + + +
+ 10.0 + caps_lock_layer_3620 + +
+ + + + ážž + + +
diff --git a/developer/src/kmc-kmn/test/helpers/index.ts b/developer/src/kmc-kmn/test/helpers/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..8250dfd101a0a07fd2966cf3e5537ac3386a92f5 --- /dev/null +++ b/developer/src/kmc-kmn/test/helpers/index.ts @@ -0,0 +1,17 @@ +/** + * Helpers and utilities for the Mocha tests. + */ +import * as path from 'path'; +import { fileURLToPath } from 'url'; + +/** + * Builds a path to the fixture with the given path components. + * + * e.g., makePathToFixture('example.qaa.trivial') + * e.g., makePathToFixture('example.qaa.trivial', 'model.ts') + * + * @param components One or more path components. + */ + export function makePathToFixture(...components: string[]): string { + return fileURLToPath(new URL(path.join('..', '..', '..', 'test', 'fixtures', ...components), import.meta.url)); +} diff --git a/developer/src/kmc-kmn/test/test-compiler.ts b/developer/src/kmc-kmn/test/test-compiler.ts index 925656f8f554e938141b13bbb35f4f7de817ff76..8c353983613f6d766f6fb8780b91153558c8b51c 100644 --- a/developer/src/kmc-kmn/test/test-compiler.ts +++ b/developer/src/kmc-kmn/test/test-compiler.ts @@ -86,20 +86,20 @@ describe('Compiler class', function() { const kmxFixture = fixtureDir + '/binary/caps_lock_layer_3620.kmx'; const kvkFixture = fixtureDir + '/binary/caps_lock_layer_3620.kvk'; - const kmxfile = __dirname + '/caps_lock_layer_3620.kmx'; - const kvkfile = __dirname + '/caps_lock_layer_3620.kvk'; + const resultingKmxfile = __dirname + '/caps_lock_layer_3620.kmx'; + const resultingKvkfile = __dirname + '/caps_lock_layer_3620.kvk'; - assert(compiler.run(infile, kmxfile, {saveDebug: true, shouldAddCompilerVersion: false})); + assert(compiler.run(infile, resultingKmxfile, {saveDebug: true, shouldAddCompilerVersion: false})); - assert(fs.existsSync(kmxfile)); - assert(fs.existsSync(kvkfile)); + assert(fs.existsSync(resultingKmxfile)); + assert(fs.existsSync(resultingKvkfile)); - const kmxData = fs.readFileSync(kmxfile); + const kmxData = fs.readFileSync(resultingKmxfile); const kmxFixtureData = fs.readFileSync(kmxFixture); assert.equal(kmxData.byteLength, kmxFixtureData.byteLength); assert.deepEqual(kmxData, kmxFixtureData); - const kvkData = fs.readFileSync(kvkfile); + const kvkData = fs.readFileSync(resultingKvkfile); const kvkFixtureData = fs.readFileSync(kvkFixture); assert.equal(kvkData.byteLength, kvkFixtureData.byteLength); assert.deepEqual(kvkData, kvkFixtureData); diff --git a/developer/src/kmc-kmn/test/test-messages.ts b/developer/src/kmc-kmn/test/test-messages.ts index 0dee53f12112d47b67ca19bc266630a791dd4bf9..4d724daaf5338213125c96876ebef03af699093b 100644 --- a/developer/src/kmc-kmn/test/test-messages.ts +++ b/developer/src/kmc-kmn/test/test-messages.ts @@ -1,9 +1,55 @@ import 'mocha'; +import path from 'path'; +import { assert } from 'chai'; import { CompilerMessages } from '../src/compiler/messages.js'; -import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers'; +import { TestCompilerCallbacks, verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers'; +import { makePathToFixture } from './helpers/index.js'; +import { KmnCompiler } from '../src/main.js'; describe('CompilerMessages', function () { + const callbacks = new TestCompilerCallbacks(); + it('should have a valid CompilerMessages object', function() { return verifyCompilerMessagesObject(CompilerMessages); }); + + // + // Message tests + // + + async function testForMessage(context: Mocha.Context, fixture: string[], messageId?: number) { + context.timeout(10000); + + callbacks.clear(); + + const compiler = new KmnCompiler(); + assert(await compiler.init(callbacks)); + assert(compiler.verifyInitialized()); + + const kmnPath = makePathToFixture(...fixture); + const outfile = path.basename(kmnPath, '.kmn') + '.kmx'; + + // Note: throwing away compile results (just to memory) + compiler.runCompiler(kmnPath, outfile, {saveDebug: true, shouldAddCompilerVersion: false}); + + if(messageId) { + assert.isTrue(callbacks.hasMessage(messageId), `messageId ${messageId.toString(16)} not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); + assert.lengthOf(callbacks.messages, 1); + } else { + assert.lengthOf(callbacks.messages, 0, `messages should be empty, but instead got: `+JSON.stringify(callbacks.messages,null,2)); + } + } + + // ERROR_InvalidKvksFile + + it('should generate ERROR_InvalidKvksFile if the kvks is not valid XML', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_invalid_kvks_file.kmn'], CompilerMessages.ERROR_InvalidKvksFile); + }); + + // WARN_InvalidVkeyInKvksFile + + it('should generate WARN_InvalidVkeyInKvksFile if the kvks contains an invalid virtual key', async function() { + await testForMessage(this, ['invalid-keyboards', 'warn_invalid_vkey_in_kvks_file.kmn'], CompilerMessages.WARN_InvalidVkeyInKvksFile); + }); + }); diff --git a/developer/src/kmc-kmn/test/tsconfig.json b/developer/src/kmc-kmn/test/tsconfig.json index fdb9e7bbabfa8fe9da1e568ed2c915fd546b5c91..eb43dc68f19d86f3f6b9b1d0637e6de450c6bc03 100644 --- a/developer/src/kmc-kmn/test/tsconfig.json +++ b/developer/src/kmc-kmn/test/tsconfig.json @@ -13,7 +13,8 @@ }, }, "include": [ - "**/test-*.ts" + "**/test-*.ts", + "./helpers/index.ts" ], "references": [ { "path": "../../../../common/web/keyman-version/" }, diff --git a/developer/src/kmc-ldml/src/compiler/compiler-options.ts b/developer/src/kmc-ldml/src/compiler/compiler-options.ts index 3d29e6393ad3de65cca0db87f464c8cc4f91c8f1..da81ef2f7d5489f7ccf3b742a4bf0d7088fc6e13 100644 --- a/developer/src/kmc-ldml/src/compiler/compiler-options.ts +++ b/developer/src/kmc-ldml/src/compiler/compiler-options.ts @@ -1,4 +1,4 @@ - +import { LDMLKeyboardXMLSourceFileReaderOptions } from "@keymanapp/common-types"; export interface CompilerOptions { /** @@ -10,4 +10,9 @@ export interface CompilerOptions { * Add metadata about the compiler version to .kmx file when compiling */ addCompilerVersion?: boolean; + + /** + * Paths and other options required for reading .xml files + */ + readerOptions: LDMLKeyboardXMLSourceFileReaderOptions; }; diff --git a/developer/src/kmc-ldml/src/compiler/compiler.ts b/developer/src/kmc-ldml/src/compiler/compiler.ts index b7b46e49b2a2e9304f0b2616ff9d22eab6ab0268..4b1472a58530dac22ca620b783c7c4d8b68290df 100644 --- a/developer/src/kmc-ldml/src/compiler/compiler.ts +++ b/developer/src/kmc-ldml/src/compiler/compiler.ts @@ -30,16 +30,14 @@ const SECTION_COMPILERS = [ export class LdmlKeyboardCompiler { private readonly callbacks: CompilerCallbacks; - // private readonly options: CompilerOptions; // not currently used + private readonly options: CompilerOptions; - constructor (callbacks: CompilerCallbacks, _options?: CompilerOptions) { - /* + constructor (callbacks: CompilerCallbacks, options: CompilerOptions) { this.options = { debug: false, addCompilerVersion: true, ...options }; - */ this.callbacks = callbacks; } @@ -54,7 +52,7 @@ export class LdmlKeyboardCompiler { * @returns the source file, or null if invalid */ public load(filename: string): LDMLKeyboardXMLSourceFile | null { - const reader = new LDMLKeyboardXMLSourceFileReader(this.callbacks); + const reader = new LDMLKeyboardXMLSourceFileReader(this.options.readerOptions, this.callbacks); const data = this.callbacks.loadFile(filename); if(!data) { this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: 'Unable to read XML file'})); @@ -84,7 +82,7 @@ export class LdmlKeyboardCompiler { * @returns the source file, or null if invalid */ public loadTestData(filename: string): LDMLKeyboardTestDataXMLSourceFile | null { - const reader = new LDMLKeyboardXMLSourceFileReader(this.callbacks); + const reader = new LDMLKeyboardXMLSourceFileReader(this.options.readerOptions, this.callbacks); const data = this.callbacks.loadFile(filename); if(!data) { this.callbacks.reportMessage(CompilerMessages.Error_InvalidFile({errorText: 'Unable to read XML file'})); diff --git a/developer/src/kmc-ldml/test/helpers/index.ts b/developer/src/kmc-ldml/test/helpers/index.ts index 8ba8a28aa84ded8a6549461d6ea8b657302bb431..f10af945c82488d9e1543efbe9e623a138488c12 100644 --- a/developer/src/kmc-ldml/test/helpers/index.ts +++ b/developer/src/kmc-ldml/test/helpers/index.ts @@ -33,6 +33,12 @@ export function makePathToFixture(...components: string[]): string { export const compilerTestCallbacks = new TestCompilerCallbacks(); +export const compilerTestOptions: CompilerOptions = { + readerOptions: { + importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL) + } +}; + beforeEach(function() { compilerTestCallbacks.clear(); }); @@ -43,14 +49,13 @@ afterEach(function() { } }); - export function loadSectionFixture(compilerClass: typeof SectionCompiler, filename: string, callbacks: TestCompilerCallbacks): Section { callbacks.messages = []; const inputFilename = makePathToFixture(filename); const data = callbacks.loadFile(inputFilename); assert.isNotNull(data); - const reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + const reader = new LDMLKeyboardXMLSourceFileReader(compilerTestOptions.readerOptions, callbacks); const source = reader.load(data); assert.isNotNull(source); diff --git a/developer/src/kmc-ldml/test/test-compiler-e2e.ts b/developer/src/kmc-ldml/test/test-compiler-e2e.ts index ec711549051a4f4a5d43911f8ae8b2924996b962..7d8b179f3125782b8ae557143558a344afa61b86 100644 --- a/developer/src/kmc-ldml/test/test-compiler-e2e.ts +++ b/developer/src/kmc-ldml/test/test-compiler-e2e.ts @@ -2,7 +2,7 @@ import 'mocha'; import {assert} from 'chai'; import x_hextobin from '@keymanapp/hextobin'; import { KMXBuilder } from '@keymanapp/common-types'; -import {checkMessages, compileKeyboard, makePathToFixture} from './helpers/index.js'; +import {checkMessages, compileKeyboard, compilerTestOptions, makePathToFixture} from './helpers/index.js'; const hextobin = (x_hextobin as any).default; @@ -17,7 +17,7 @@ describe('compiler-tests', function() { const binaryFilename = makePathToFixture('basic.txt'); // Compile the keyboard - const kmx = compileKeyboard(inputFilename, {debug: true, addCompilerVersion: false}); + const kmx = compileKeyboard(inputFilename, {...compilerTestOptions, debug: true, addCompilerVersion: false}); assert.isNotNull(kmx); // Use the builder to generate the binary output file diff --git a/developer/src/kmc-ldml/test/test-keymanweb-compiler.ts b/developer/src/kmc-ldml/test/test-keymanweb-compiler.ts index fb4f28daeeedf1fef9a6c191df57b55e0c5f2933..711820e5e4051a751304f1620369e7f1f131d151 100644 --- a/developer/src/kmc-ldml/test/test-keymanweb-compiler.ts +++ b/developer/src/kmc-ldml/test/test-keymanweb-compiler.ts @@ -1,6 +1,6 @@ import 'mocha'; import { assert } from 'chai'; -import { checkMessages, compilerTestCallbacks, makePathToFixture } from './helpers/index.js'; +import { checkMessages, compilerTestCallbacks, compilerTestOptions, makePathToFixture } from './helpers/index.js'; import { LdmlKeyboardKeymanWebCompiler } from '../src/compiler/keymanweb-compiler.js'; import { LdmlKeyboardCompiler } from '../src/compiler/compiler.js'; import * as fs from 'fs'; @@ -16,7 +16,7 @@ describe('LdmlKeyboardKeymanWebCompiler', function() { // Load input data; we'll use the LDML keyboard compiler loader to save us // effort here - const k = new LdmlKeyboardCompiler(compilerTestCallbacks, {debug: true, addCompilerVersion: false}); + const k = new LdmlKeyboardCompiler(compilerTestCallbacks, {...compilerTestOptions, debug: true, addCompilerVersion: false}); const source = k.load(inputFilename); checkMessages(); assert.isNotNull(source, 'k.load should not have returned null'); @@ -27,7 +27,7 @@ describe('LdmlKeyboardKeymanWebCompiler', function() { assert.isTrue(valid, 'k.validate should not have failed'); // Actual test: compile to javascript - const jsCompiler = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {debug: true}); + const jsCompiler = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {...compilerTestOptions, debug: true}); const output = jsCompiler.compile('basic.xml', source); assert.isNotNull(output); @@ -36,7 +36,7 @@ describe('LdmlKeyboardKeymanWebCompiler', function() { assert.strictEqual(output, outputFixture); // Second test: compile to javascript without debug formatting - const jsCompilerNoDebug = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {debug: false}); + const jsCompilerNoDebug = new LdmlKeyboardKeymanWebCompiler(compilerTestCallbacks, {...compilerTestOptions, debug: false}); const outputNoDebug = jsCompilerNoDebug.compile('basic.xml', source); assert.isNotNull(outputNoDebug); diff --git a/developer/src/kmc-ldml/test/test-metadata-compiler.ts b/developer/src/kmc-ldml/test/test-metadata-compiler.ts index 59500d09950c313568948fb70b8322ea0799b0d7..ede77ed4bccfba209a0d6f1e570ffa9bd56e7755 100644 --- a/developer/src/kmc-ldml/test/test-metadata-compiler.ts +++ b/developer/src/kmc-ldml/test/test-metadata-compiler.ts @@ -1,6 +1,6 @@ import 'mocha'; import { assert } from 'chai'; -import { checkMessages, compileKeyboard, makePathToFixture } from './helpers/index.js'; +import { checkMessages, compileKeyboard, compilerTestOptions, makePathToFixture } from './helpers/index.js'; import { KMX } from '@keymanapp/common-types'; import KEYMAN_VERSION from '@keymanapp/keyman-version'; @@ -13,7 +13,7 @@ describe('kmx metadata compiler', function () { const inputFilename = makePathToFixture('basic.xml'); // Compile the keyboard - const kmx = compileKeyboard(inputFilename, {debug:true, addCompilerVersion:true}); + const kmx = compileKeyboard(inputFilename, {...compilerTestOptions, debug:true, addCompilerVersion:true}); checkMessages(); assert.isNotNull(kmx); @@ -48,7 +48,7 @@ describe('kmx metadata compiler', function () { const inputFilename = makePathToFixture('basic.xml'); // Compile the keyboard - const kmx = compileKeyboard(inputFilename, {debug:true, addCompilerVersion:false}); + const kmx = compileKeyboard(inputFilename, {...compilerTestOptions, debug:true, addCompilerVersion:false}); checkMessages(); assert.isNotNull(kmx); diff --git a/developer/src/kmc-ldml/test/test-testdata-e2e.ts b/developer/src/kmc-ldml/test/test-testdata-e2e.ts index 25ac1963213a5fed475292821837b4fbb8c005fb..ece4ce85608174bac50522c48810ca3f89fac3df 100644 --- a/developer/src/kmc-ldml/test/test-testdata-e2e.ts +++ b/developer/src/kmc-ldml/test/test-testdata-e2e.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import 'mocha'; import {assert} from 'chai'; -import {loadTestdata, makePathToFixture} from './helpers/index.js'; +import {compilerTestOptions, loadTestdata, makePathToFixture} from './helpers/index.js'; describe('testdata-tests', function() { this.slow(500); // 0.5 sec -- json schema validation takes a while @@ -14,7 +14,7 @@ describe('testdata-tests', function() { const jsonFilename = makePathToFixture('test-fr.json'); // Compile the keyboard - const testData = loadTestdata(inputFilename, {debug: true, addCompilerVersion: false}); + const testData = loadTestdata(inputFilename, {...compilerTestOptions, debug: true, addCompilerVersion: false}); assert.isNotNull(testData); const jsonData = JSON.parse(readFileSync(jsonFilename, 'utf-8')); diff --git a/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts b/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts index 1b1e34069e5ac2b13982f5ee18654b80d1bacb6f..4af68278387c8dd89283108fcbe6d031ee8b51a8 100644 --- a/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts +++ b/developer/src/kmc-ldml/test/test-visual-keyboard-compiler-e2e.ts @@ -2,7 +2,7 @@ import 'mocha'; import {assert} from 'chai'; import x_hextobin from '@keymanapp/hextobin'; import { KvkFileWriter } from '@keymanapp/common-types'; -import {checkMessages, compileVisualKeyboard, makePathToFixture} from './helpers/index.js'; +import {checkMessages, compilerTestOptions, compileVisualKeyboard, makePathToFixture} from './helpers/index.js'; const hextobin = (x_hextobin as any).default; @@ -17,7 +17,7 @@ describe('visual-keyboard-compiler', function() { const binaryFilename = makePathToFixture('basic-kvk.txt'); // Compile the visual keyboard - const vk = compileVisualKeyboard(inputFilename, {debug: true, addCompilerVersion: false}); + const vk = compileVisualKeyboard(inputFilename, {...compilerTestOptions, debug: true, addCompilerVersion: false}); assert.isNotNull(vk); // Use the builder to generate the binary output file diff --git a/developer/src/kmc-package/src/compiler/kmp-compiler.ts b/developer/src/kmc-package/src/compiler/kmp-compiler.ts index b660641df9c564972064f4ed644433dd793c3f65..198a1ef2de2823b43902867c517aa9b85aa66b91 100644 --- a/developer/src/kmc-package/src/compiler/kmp-compiler.ts +++ b/developer/src/kmc-package/src/compiler/kmp-compiler.ts @@ -16,7 +16,12 @@ export class KmpCompiler { public transformKpsToKmpObject(kpsFilename: string): KmpJsonFile.KmpJsonFile { // Load the KPS data from XML as JS structured data. - const data = this.callbacks.fs.readFileSync(kpsFilename, 'utf-8'); + const buffer = this.callbacks.loadFile(kpsFilename); + if(!buffer) { + this.callbacks.reportMessage(CompilerMessages.Error_FileDoesNotExist({filename: kpsFilename})); + return null; + } + const data = new TextDecoder().decode(buffer); const kpsPackage = (() => { let a: KpsFile.KpsPackage; @@ -24,7 +29,8 @@ export class KmpCompiler { tagNameProcessors: [xml2js.processors.firstCharLowerCase], explicitArray: false }); - parser.parseString(data, (e: unknown, r: unknown) => { a = r as KpsFile.KpsPackage }); + // TODO: add unit test for xml errors parsing .kps file + parser.parseString(data, (e: unknown, r: unknown) => { if(e) throw e; a = r as KpsFile.KpsPackage }); return a; })(); @@ -284,9 +290,20 @@ export class KmpCompiler { * we want that to remain the responsibility of the keyboard compiler, so we'll warn the * few users who are still doing this */ - private warnIfKvkFileIsNotBinary(filename: string, data: Buffer) { - // TODO: Buffer is not available on web - if(filename.match(/\.kvk$/) && data.compare(Buffer.from(KvkFile.KVK_HEADER_IDENTIFIER_BYTES), 0, 3, 0, 3) != 0) { + private warnIfKvkFileIsNotBinary(filename: string, data: Uint8Array) { + if(!filename.match(/\.kvk$/)) { + return; + } + + if(data.byteLength < 4) { + // TODO: Not a valid .kvk file; should we be reporting this? + return; + } + + if(data[0] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[0] || + data[1] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[1] || + data[2] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[2] || + data[3] != KvkFile.KVK_HEADER_IDENTIFIER_BYTES[3]) { this.callbacks.reportMessage(CompilerMessages.Warn_FileIsNotABinaryKvkFile({filename: filename})); } } diff --git a/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts b/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts index 024f7f534002f6231322b561c7b36c0315debe04..40b9513d320e1971824a93f30c62c3f4ea206604 100644 --- a/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts +++ b/developer/src/kmc/src/commands/build/BuildLdmlKeyboard.ts @@ -1,8 +1,9 @@ import * as path from 'path'; import * as fs from 'fs'; import * as kmcLdml from '@keymanapp/kmc-ldml'; -import { KvkFileWriter, CompilerCallbacks } from '@keymanapp/common-types'; +import { KvkFileWriter, CompilerCallbacks, LDMLKeyboardXMLSourceFileReader } from '@keymanapp/common-types'; import { BuildActivity, BuildActivityOptions } from './BuildActivity.js'; +import { fileURLToPath } from 'url'; export class BuildLdmlKeyboard extends BuildActivity { public get name(): string { return 'LDML keyboard'; } @@ -46,11 +47,14 @@ function buildLdmlKeyboardToMemory(inputFilename: string, callbacks: CompilerCal let compilerOptions: kmcLdml.CompilerOptions = { debug: options.debug ?? false, addCompilerVersion: options.compilerVersion ?? true, + readerOptions: { + importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL) + } // TODO: warnDeprecatedCode: options.warnDeprecatedCode, // TODO: treatWarningsAsErrors: options.treatWarningsAsErrors, } - const k = new kmcLdml.LdmlKeyboardCompiler(callbacks, options); + const k = new kmcLdml.LdmlKeyboardCompiler(callbacks, compilerOptions); let source = k.load(inputFilename); if (!source) { return [null, null, null]; diff --git a/developer/src/kmc/src/commands/buildTestData/index.ts b/developer/src/kmc/src/commands/buildTestData/index.ts index 0990b91edb55de750acdb544ffa0a6e1105cbd09..b6ebf34a739326e797565407d2dddb217a39a622 100644 --- a/developer/src/kmc/src/commands/buildTestData/index.ts +++ b/developer/src/kmc/src/commands/buildTestData/index.ts @@ -1,8 +1,9 @@ import * as fs from 'fs'; import * as path from 'path'; import * as kmc from '@keymanapp/kmc-ldml'; -import { CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile } from '@keymanapp/common-types'; +import { CompilerCallbacks, LDMLKeyboardTestDataXMLSourceFile, LDMLKeyboardXMLSourceFileReader } from '@keymanapp/common-types'; import { NodeCompilerCallbacks } from '../../messages/NodeCompilerCallbacks.js'; +import { fileURLToPath } from 'url'; export interface BuildTestDataOptions { outFile?: string; @@ -11,7 +12,10 @@ export interface BuildTestDataOptions { export function buildTestData(infile: string, options: BuildTestDataOptions) { let compilerOptions: kmc.CompilerOptions = { debug: false, - addCompilerVersion: false + addCompilerVersion: false, + readerOptions: { + importsPath: fileURLToPath(LDMLKeyboardXMLSourceFileReader.defaultImportsURL) + } }; let testData = loadTestData(infile, compilerOptions); diff --git a/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts b/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts index ceae18c4c59e7f8165a39460fab40558621fd211..dfcb7e2ecbfae760a3c72f66b9b1baad50819040 100644 --- a/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts +++ b/developer/src/kmc/src/messages/NodeCompilerCallbacks.ts @@ -1,14 +1,52 @@ import * as fs from 'fs'; import * as path from 'path'; import { CompilerCallbacks, CompilerSchema, CompilerEvent, compilerErrorSeverityName, CompilerPathCallbacks, CompilerFileSystemCallbacks } from '@keymanapp/common-types'; +import { InfrastructureMessages } from './messages.js'; /** * Concrete implementation for CLI use */ +// TODO: Make a common class for all the CompilerCallbacks implementations + export class NodeCompilerCallbacks implements CompilerCallbacks { - // TODO: REMOVE! - loadFile(filename: string | URL): Buffer { + /* NodeCompilerCallbacks */ + + messages: CompilerEvent[] = []; + silent: boolean; + + constructor(silent?: boolean) { + this.silent = !!silent; + } + + clear() { + this.messages = []; + } + + hasMessage(code: number): boolean { + return this.messages.find((item) => item.code == code) === undefined ? false : true; + } + + private verifyFilenameConsistency(originalFilename: string): void { + if(fs.existsSync(originalFilename)) { + // Note, we only check this if the file exists, because + // if it is not found, that will be returned as an error + // from loadFile anyway. + const filename = fs.realpathSync(originalFilename); + const nativeFilename = fs.realpathSync.native(filename); + if(filename != nativeFilename) { + this.reportMessage(InfrastructureMessages.Hint_FilenameHasDifferingCase({ + reference: originalFilename, + filename: nativeFilename + })); + } + } + } + + /* CompilerCallbacks */ + + loadFile(filename: string): Uint8Array { + this.verifyFilenameConsistency(filename); try { return fs.readFileSync(filename); } catch (e) { @@ -29,6 +67,10 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { } reportMessage(event: CompilerEvent): void { + this.messages.push(event); + if(this.silent) { + return; + } const code = event.code.toString(16); if(event.line) { console.log(`${compilerErrorSeverityName(event.code)} ${code} [${event.line}]: ${event.message}`); @@ -41,7 +83,7 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { console.debug(msg); } - loadSchema(schema: CompilerSchema) { + loadSchema(schema: CompilerSchema): Uint8Array { let schemaPath = new URL('../util/' + schema + '.schema.json', import.meta.url); return fs.readFileSync(schemaPath); } @@ -51,7 +93,10 @@ export class NodeCompilerCallbacks implements CompilerCallbacks { } resolveFilename(baseFilename: string, filename: string) { - const basePath = path.dirname(baseFilename); + const basePath = + baseFilename.endsWith('/') || baseFilename.endsWith('\\') ? + baseFilename : + path.dirname(baseFilename); // Transform separators to platform separators -- we are agnostic // in our use here but path prefers files may use // either / or \, although older kps files were always \. diff --git a/developer/src/kmc/src/messages/messages.ts b/developer/src/kmc/src/messages/messages.ts index 1f1761e715d6f70f523d95d074b2eebab1fe1500..358a59406bb3a80f1c025f9c88ed80a9e1945009 100644 --- a/developer/src/kmc/src/messages/messages.ts +++ b/developer/src/kmc/src/messages/messages.ts @@ -2,7 +2,7 @@ import { CompilerErrorNamespace, CompilerErrorSeverity, CompilerMessageSpec as m const Namespace = CompilerErrorNamespace.Infrastructure; const SevInfo = CompilerErrorSeverity.Info | Namespace; -// const SevHint = CompilerErrorSeverity.Hint | Namespace; +const SevHint = CompilerErrorSeverity.Hint | Namespace; // const SevWarn = CompilerErrorSeverity.Warn | Namespace; const SevError = CompilerErrorSeverity.Error | Namespace; const SevFatal = CompilerErrorSeverity.Fatal | Namespace; @@ -39,5 +39,9 @@ export class InfrastructureMessages { static Error_InvalidProjectFile = (o:{message:string}) => m(this.ERROR_InvalidProjectFile, `Project file is not valid: ${o.message}`); static ERROR_InvalidProjectFile = SevError | 0x0008; + + static Hint_FilenameHasDifferingCase = (o:{reference:string, filename:string}) => m(this.HINT_FilenameHasDifferingCase, + `File ${o.filename} differs in case from reference ${o.reference}; this will fail on platforms with case-sensitive filesystems.`); + static HINT_FilenameHasDifferingCase = SevHint | 0x0009; } diff --git a/developer/src/kmc/test/fixtures/invalid-keyboards/hint_filename_has_differing_case.kmn b/developer/src/kmc/test/fixtures/invalid-keyboards/hint_filename_has_differing_case.kmn new file mode 100644 index 0000000000000000000000000000000000000000..5792016e10383d7bee622a0a49d6ec5e9ee03e09 --- /dev/null +++ b/developer/src/kmc/test/fixtures/invalid-keyboards/hint_filename_has_differing_case.kmn @@ -0,0 +1,6 @@ +store(&name) 'hint_filename_has_differing_case' +store(&version) '7.0' + +begin unicode > use(main) + +group(main) using keys diff --git a/developer/src/kmc/test/test-messages.ts b/developer/src/kmc/test/test-messages.ts index 7b591ad72de7a2e3dec348e55f52155c77030cbb..045718075daaf989c6871ede3648af8fe5e0d675 100644 --- a/developer/src/kmc/test/test-messages.ts +++ b/developer/src/kmc/test/test-messages.ts @@ -1,9 +1,80 @@ import 'mocha'; +import { assert } from 'chai'; import { InfrastructureMessages } from '../src/messages/messages.js'; import { verifyCompilerMessagesObject } from '@keymanapp/developer-test-helpers'; +import { makePathToFixture } from './helpers/index.js'; +import { NodeCompilerCallbacks } from '../src/messages/NodeCompilerCallbacks.js'; describe('InfrastructureMessages', function () { it('should have a valid InfrastructureMessages object', function() { return verifyCompilerMessagesObject(InfrastructureMessages); }); + + // + // Message tests + // + + /* + TODO: + + let callbacks = new TestCompilerCallbacks(); + + async function testForMessage(context: Mocha.Context, fixture: string[], messageId?: number) { + context.timeout(10000); + + callbacks.clear(); + + const builder = new BuildKmnKeyboard(); + const path = makePathToFixture(...fixture); + let result = await builder.build(path, callbacks, { + compilerVersion: false, + compilerWarningsAsErrors: true, + debug: false, + warnDeprecatedCode: true, + }); + + if(messageId) { + assert.isTrue(callbacks.hasMessage(messageId), `messageId ${messageId.toString(16)} not generated, instead got: `+JSON.stringify(callbacks.messages,null,2)); + assert.lengthOf(callbacks.messages, 1); + } else { + assert.lengthOf(callbacks.messages, 0, `messages should be empty, but instead got: `+JSON.stringify(callbacks.messages,null,2)); + assert.isTrue(result); + } + } + + // ERROR_FileDoesNotExist + + it('should generate ERROR_FileDoesNotExist if a file does not exist', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_file_does_not_exist.kmn'], CompilerMessages.ERROR_FileDoesNotExist); + }); + + // ERROR_FileTypeNotRecognized + + it('should generate ERROR_FileTypeNotRecognized if a file is not a recognized type', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_file_type_not_recognized.xxx'], CompilerMessages.ERROR_FileTypeNotRecognized); + }); + + // ERROR_OutFileNotValidForProjects + + it('should generate ERROR_OutFileNotValidForProjects if an output file is specified for a project build', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_out_file_not_valid_for_projects.kpj'], CompilerMessages.ERROR_OutFileNotValidForProjects); + }); + + // ERROR_InvalidProjectFile + + it('should generate ERROR_InvalidProjectFile if a project file is invalid', async function() { + await testForMessage(this, ['invalid-keyboards', 'error_invalid_project_file.kpj'], CompilerMessages.ERROR_InvalidProjectFile); + }); + */ + + // HINT_FilenameHasDifferingCase + + it('should generate HINT_FilenameHasDifferingCase if a referenced file has differing case', async function() { + // This message is generated by NodeCompilerCallbacks, because that's where the filesystem is visible, + // so we can't use our usual testForMessage pattern. + const ncb = new NodeCompilerCallbacks(true); + ncb.loadFile(makePathToFixture('invalid-keyboards', 'Hint_Filename_Has_Differing_Case.kmn')); + assert.isTrue(ncb.hasMessage(InfrastructureMessages.HINT_FilenameHasDifferingCase), + `HINT_FilenameHasDifferingCase not generated, instead got: `+JSON.stringify(ncb.messages,null,2)); + }); }); diff --git a/developer/src/kmcmpdll/Compiler.cpp b/developer/src/kmcmpdll/Compiler.cpp index 8cc28d2ab7f18bfdc5e6e65dd78b99bf1fe8afe4..100e43fc5bd4356d1fe7cd95b427b6e28a6afe6d 100644 --- a/developer/src/kmcmpdll/Compiler.cpp +++ b/developer/src/kmcmpdll/Compiler.cpp @@ -90,8 +90,6 @@ #include "UnreachableRules.h" #include "CheckForDuplicates.h" -#include "../kmcmplib/include/kmcmplibapi.h" - int xatoi(PWSTR *p); int atoiW(PWSTR p); void safe_wcsncpy(PWSTR out, PWSTR in, int cbMax); @@ -291,33 +289,15 @@ BOOL AddCompileMessage(DWORD msg) return FALSE; } -bool flag_use_new_kmcomp = true; // flag to switch to new kmcompx - extern "C" BOOL __declspec(dllexport) SetCompilerOptions(PCOMPILER_OPTIONS options) { if(!options || options->dwSize < sizeof(COMPILER_OPTIONS)) { return FALSE; } - flag_use_new_kmcomp = options->UseKmcmpLib; - - //printf("\n---> started in SetCompilerOptions() of kmcmpdll\n"); - if ( flag_use_new_kmcomp ) - { - KMCMP_COMPILER_OPTIONS kmcmp_options = {0}; - kmcmp_options.dwSize = sizeof(KMCMP_COMPILER_OPTIONS); - kmcmp_options.ShouldAddCompilerVersion = options->ShouldAddCompilerVersion; - return kmcmp_SetCompilerOptions(&kmcmp_options); - } - //printf("---> stayed in SetCompilerOptions() of kmcmpdll\n"); - FShouldAddCompilerVersion = options->ShouldAddCompilerVersion; return TRUE; } -int kmcmpMsgproc(int line, uint32_t dwMsgCode, char* szText, void* context) { - return static_cast(context)(line, dwMsgCode, szText); -} - extern "C" BOOL __declspec(dllexport) CompileKeyboardFile(PSTR pszInfile, PSTR pszOutfile, BOOL ASaveDebug, BOOL ACompilerWarningsAsErrors, BOOL AWarnDeprecatedCode, CompilerMessageProc pMsgProc) // I4865 // I4866 { HANDLE hInfile = INVALID_HANDLE_VALUE, hOutfile = INVALID_HANDLE_VALUE; @@ -325,15 +305,6 @@ extern "C" BOOL __declspec(dllexport) CompileKeyboardFile(PSTR pszInfile, PSTR p DWORD len; char str[260]; - //printf("\n---> started in CompileKeyboardFile() of kmcmpdll\n"); - - if ( flag_use_new_kmcomp ) - { - return kmcmp_CompileKeyboardFile(pszInfile, pszOutfile, ASaveDebug, ACompilerWarningsAsErrors,AWarnDeprecatedCode, kmcmpMsgproc, (void*) pMsgProc); - } - - //printf("---> stayed in CompileKeyboardFile() of kmcmpdll\n"); - FSaveDebug = ASaveDebug; FCompilerWarningsAsErrors = ACompilerWarningsAsErrors; // I4865 FWarnDeprecatedCode = AWarnDeprecatedCode; // I4866 @@ -418,15 +389,6 @@ extern "C" BOOL __declspec(dllexport) CompileKeyboardFileToBuffer(PSTR pszInfile DWORD len; char str[260]; - //printf("\n---> started in CompileKeyboardFileToBuffer() of kmcmpdll\n"); - if ( flag_use_new_kmcomp ) - { - return kmcmp_CompileKeyboardFileToBuffer( pszInfile, (void*) pfkBuffer, ACompilerWarningsAsErrors, AWarnDeprecatedCode, kmcmpMsgproc, (void*) pMsgProc, Target); - } - - //printf("---> stayed in CompileKeyboardFileToBuffer() of kmcmpdll\n"); - - FSaveDebug = TRUE; // I3681 FCompilerWarningsAsErrors = ACompilerWarningsAsErrors; // I4865 FWarnDeprecatedCode = AWarnDeprecatedCode; // I4866 diff --git a/developer/src/kmcmpdll/json-validation.cpp b/developer/src/kmcmpdll/json-validation.cpp index e35dc0485dcae1480b00b1f002bad5fa75b56d26..c3c1af0484318b176cacef53dec22f58c7f2f118 100644 --- a/developer/src/kmcmpdll/json-validation.cpp +++ b/developer/src/kmcmpdll/json-validation.cpp @@ -1,14 +1,11 @@ #include #include -#include "../kmcmplib/include/kmcmplibapi.h" #include typedef bool (*kmcmp_ValidateJsonMessageProc)(int64_t offset, const char* szText, void* context); -extern bool flag_use_new_kmcomp; - using nlohmann::json; using nlohmann::json_uri; using nlohmann::json_schema_draft4::json_validator; @@ -35,13 +32,6 @@ bool kmcmpMessageProc(int64_t offset, const char* szText, void* context) { extern "C" BOOL __declspec(dllexport) ValidateJsonFile(PWSTR pwszSchemaFile, PWSTR pwszJsonFile, ValidateJsonMessageProc MessageProc) { - if ( flag_use_new_kmcomp ) - { - std::fstream f(pwszSchemaFile); - std::fstream fd(pwszJsonFile); - return kmcmp_ValidateJsonFile(f, fd, kmcmpMessageProc, (void*)MessageProc); - } - std::fstream f(pwszSchemaFile); if (!f.good()) { MessageProc(-1, "Schema file could not be loaded."); diff --git a/developer/src/kmcmpdll/kmcmpdll.vcxproj b/developer/src/kmcmpdll/kmcmpdll.vcxproj index c7f7e1f48f062c068d8cc15e1846f86f25942d9f..2bcc038bd00df813dc7fc9cd7db7a3f3b268d034 100644 --- a/developer/src/kmcmpdll/kmcmpdll.vcxproj +++ b/developer/src/kmcmpdll/kmcmpdll.vcxproj @@ -89,25 +89,25 @@ $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(KEYMAN_ROOT)\developer\src\kmcmplib\build\x86\$(Configuration)\src;$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86) + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86) $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator $(ProjectDir)obj\$(Platform)\$(Configuration)\ $(ProjectName).x64 $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(KEYMAN_ROOT)\developer\src\kmcmplib\build\x64\$(Configuration)\src;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator - $(KEYMAN_ROOT)\developer\src\kmcmplib\build\x86\$(Configuration)\src;$(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86) + $(VC_LibraryPath_x86);$(WindowsSDK_LibraryPath_x86) $(VC_IncludePath);$(WindowsSDK_IncludePath);$(KEYMAN_ROOT)\developer\src\common\include;$(KEYMAN_ROOT)\developer\src\ext\json;$(KEYMAN_ROOT)\developer\src\ext\json-schema-validator $(ProjectDir)obj\$(Platform)\$(Configuration)\ $(ProjectName).x64 $(ProjectDir)bin\$(Platform)\$(Configuration)\ - $(KEYMAN_ROOT)\developer\src\kmcmplib\build\x64\$(Configuration)\src;$(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) + $(VC_LibraryPath_x64);$(WindowsSDK_LibraryPath_x64) @@ -142,7 +142,7 @@ $(KEYMAN_ROOT)\windows\src\global\inc - libkmcmplib.a;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) true true @@ -190,7 +190,7 @@ $(KEYMAN_ROOT)\windows\src\global\inc - libkmcmplib.a;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) true true Windows @@ -233,7 +233,7 @@ $(KEYMAN_ROOT)\windows\src\global\inc - libkmcmplib.a;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) true true true @@ -277,7 +277,7 @@ $(KEYMAN_ROOT)\windows\src\global\inc - libkmcmplib.a;version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) + version.lib;setupapi.lib;iphlpapi.lib;imm32.lib;crypt32.lib;wintrust.lib;imagehlp.lib;ws2_32.lib;%(AdditionalDependencies) true true true diff --git a/developer/src/kmcmplib/checkout-keyboards.inc.sh b/developer/src/kmcmplib/checkout-keyboards.inc.sh index defcb479ef4070a759b2767115d1949e7cc78ac1..6c76d53caa1fc9f39eeef1a9bcdce0140c57ed86 100644 --- a/developer/src/kmcmplib/checkout-keyboards.inc.sh +++ b/developer/src/kmcmplib/checkout-keyboards.inc.sh @@ -9,8 +9,8 @@ function locate_keyboards_repo() { builder_die "keyboards_commit_ref.txt does not exist, run prep.sh" fi - if [[ ! -d "$THIS_SCRIPT_PATH/tests/fixtures" ]]; then - builder_die "fixtures folder does not exist, run prep.sh" + if [[ ! -d "$THIS_SCRIPT_PATH/tests/fixtures/keyboards-repo" ]]; then + builder_die "fixtures/keyboards-repo folder does not exist, run prep.sh" fi if [[ -z "${KEYBOARDS_ROOT+x}" ]]; then diff --git a/developer/src/kmcmplib/include/kmcmplibapi.h b/developer/src/kmcmplib/include/kmcmplibapi.h index 2fb35979b2def0839a4f36fa552c76abe8604cfe..0b41af69909254876d3a92c0d3bf61d37e143162 100644 --- a/developer/src/kmcmplib/include/kmcmplibapi.h +++ b/developer/src/kmcmplib/include/kmcmplibapi.h @@ -16,53 +16,52 @@ #define EXTERN EMSCRIPTEN_KEEPALIVE #endif -typedef struct _KMCMP_COMPILER_OPTIONS { - uint32_t dwSize; - bool ShouldAddCompilerVersion; -} KMCMP_COMPILER_OPTIONS; - -EXTERN bool kmcmp_SetCompilerOptions( - KMCMP_COMPILER_OPTIONS* options -); - -typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, char* szText, void* context); - -EXTERN bool kmcmp_CompileKeyboardFile( - char* pszInfile, - char* pszOutfile, - bool ASaveDebug, - bool ACompilerWarningsAsErrors, - bool AWarnDeprecatedCode, - kmcmp_CompilerMessageProc pMsgproc, - void* AmsgprocContext -); - /* Compile target */ #define CKF_KEYMAN 0 #define CKF_KEYMANWEB 1 -EXTERN bool kmcmp_CompileKeyboardFileToBuffer( - char* pszInfile, - void* pfkBuffer, - bool ACompilerWarningsAsErrors, - bool AWarnDeprecatedCode, - kmcmp_CompilerMessageProc pMsgproc, - void* AmsgprocContext, - int Target -); - -typedef bool (*kmcmp_ValidateJsonMessageProc)(int64_t offset, const char* szText, void* context); - -EXTERN bool kmcmp_ValidateJsonFile( - std::fstream& f, - std::fstream& fd, - kmcmp_ValidateJsonMessageProc MessageProc, - void* context +struct KMCMP_COMPILER_OPTIONS { + bool saveDebug; + bool compilerWarningsAsErrors; + bool warnDeprecatedCode; + bool shouldAddCompilerVersion; + int target; // CKF_KEYMAN, CKF_KEYMANWEB +}; + +struct KMCMP_COMPILER_RESULT { + void* kmx; + size_t kmxSize; + std::string kvksFilename; +}; + +// TODO: parameters in UTF-8 #8887 +typedef int (*kmcmp_CompilerMessageProc)(int line, uint32_t dwMsgCode, const char* szText, void* context); + +// parameters in UTF-8 +// TODO typical usage: +// if(!kmcmp_LoadFileProc("filename.ico", "/tmp/filename.kmn", nullptr, &size)) { +// return error; +// } +// buf = new unsigned char[size]; +// if(!kmcmp_LoadFileProc("filename.ico", "/tmp/filename.kmn", buf, &size)) { +// delete[] buf; +// return error; +// } +typedef bool (*kmcmp_LoadFileProc)(const char* loadFilename, const char* baseFilename, void* buffer, int* bufferSize, void* context); + +// Parameters in UTF-8 +EXTERN bool kmcmp_CompileKeyboard( + const char* pszInfile, + const KMCMP_COMPILER_OPTIONS& options, + kmcmp_CompilerMessageProc messageProc, + kmcmp_LoadFileProc loadFileProc, + const void* procContext, + KMCMP_COMPILER_RESULT& result ); /** - * kmcmp_ParseUnicodeSet is successful if it returns >= USET_OK + * kmcmp_parseUnicodeSet is successful if it returns >= USET_OK */ static const int KMCMP_USET_OK = 0; @@ -83,24 +82,19 @@ static const int KMCMP_ERROR_UNSUPPORTED_PROPERTY = -3; */ static const int KMCMP_FATAL_OUT_OF_RANGE = -4; -/** - * Function pointer to kmcmp_ParseUnicodeSet - */ -typedef int (*kmcmp_ParseUnicodeSetProc)(const char* szText, uint32_t* output, uint32_t outputLength); - /** * Parse a UnicodeSet into 32-bit ranges. * For example, "[]" will return 0 (KMCMP_USET_OK) as a zero-length set. * "[" will return KMCMP_ERROR_SYNTAX_ERR, * and "[x A-C]" will return 2 and [0x41, 0x43, 0x78, 0x78] - * @param szText input txt, null terminated, in UTF-8 format + * @param text input txt, null terminated, in UTF-8 format * @param outputBuffer output buffer, owned by caller: Pairs of ranges in order * @param outputBufferSize length of output buffer. Needs to be twice the number of expected ranges * @return If >= KMCMP_USET_OK, number of ranges, otherwise one of the negative error values. */ -EXTERN int kmcmp_ParseUnicodeSet( - const char* szText, - uint32_t* outputBuffer, +EXTERN int kmcmp_parseUnicodeSet( + const std::string text, + uintptr_t outputBuffer_, uint32_t outputBufferSize ); diff --git a/developer/src/kmcmplib/include/meson.build b/developer/src/kmcmplib/include/meson.build index f98a4d8c762da2134f33054a4b18948aa3a5e658..dde8e69f6b0cd52d1c8f1ce66f2a65c413e3809f 100644 --- a/developer/src/kmcmplib/include/meson.build +++ b/developer/src/kmcmplib/include/meson.build @@ -20,7 +20,5 @@ configure_file( inc = include_directories( '.', '../../common/include', - '../../../../common/include', - '../../ext/json', - '../../ext/json-schema-validator' + '../../../../common/include' ) diff --git a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp b/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp deleted file mode 100644 index 627298bf375de6155aeae55fb1ddff96a157df14..0000000000000000000000000000000000000000 --- a/developer/src/kmcmplib/src/CheckFilenameConsistency.cpp +++ /dev/null @@ -1,145 +0,0 @@ - -#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING 1 -#include "pch.h" -#include "compfile.h" -#include -#include "kmcmplib.h" -#include -#include "CheckFilenameConsistency.h" -#include "kmx_u16.h" -#include "filesystem.h" - -#ifdef _MSC_VER -#include -#endif - -namespace kmcmp { - extern KMX_CHAR CompileDir[260]; // TODO: this should not be a fixed buffer -} -bool IsRelativePath(KMX_CHAR const * p) { - // Relative path (returns TRUE): - // ..\...\BITMAP.BMP - // PATH\BITMAP.BMP - // BITMAP.BMP - - // Semi-absolute path (returns FALSE): - // \...\BITMAP.BMP - - // Absolute path (returns FALSE): - // C:\...\BITMAP.BMP - // \\SERVER\SHARE\...\BITMAP.BMP - - if ((*p == '\\') || (*p == '/')) return FALSE; - if (*p && *(p + 1) == ':') return FALSE; - - return TRUE; -} - -bool IsRelativePath(KMX_WCHAR const * p) { - // Relative path (returns TRUE): - // ..\...\BITMAP.BMP - // PATH\BITMAP.BMP - // BITMAP.BMP - - // Semi-absolute path (returns FALSE): - // \...\BITMAP.BMP - - // Absolute path (returns FALSE): - // C:\...\BITMAP.BMP - // \\SERVER\SHARE\...\BITMAP.BMP - - if ((*p == u'\\') || (*p == u'/'))return FALSE; - if (*p && *(p + 1) == u':') return FALSE; - - return TRUE; -} - -KMX_DWORD CheckFilenameConsistency( KMX_CHAR const * Filename, bool ReportMissingFile) { - PKMX_WCHAR WFilename = strtowstr(( KMX_CHAR *)Filename); - KMX_DWORD const result = CheckFilenameConsistency(WFilename, ReportMissingFile); - delete WFilename; - return result; -} - -KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissingFile) { - // not ready yet: needs more attention-> common includes for non-Windows platforms - KMX_WCHAR Name[260]; // TODO: fixed buffer sizes bad - - - if (IsRelativePath(Filename)) { - PKMX_WCHAR WCompileDir = strtowstr(kmcmp::CompileDir); - u16ncpy(Name, WCompileDir, _countof(Name)); // I3481 - u16ncat(Name, Filename, _countof(Name)); // I3481 - delete[] WCompileDir; - } else { - u16ncpy(Name, Filename, _countof(Name)); // I3481 - } - -#ifndef _MSC_VER - // Filename consistency only needs to be checked on Windows, because other - // platforms are going to fail if the filename is inconsistent anyway! - if(!kmcmp_FileExists(Name)) { - if (ReportMissingFile) { - u16cpy(ErrExtraW, u"referenced file '"); - u16ncat(ErrExtraW, Filename, 256); - u16ncat(ErrExtraW, u"'", 256); - strcpy(ErrExtraLIB, string_from_u16string(ErrExtraW).c_str()); - AddWarning(CWARN_MissingFile); - } - return CERR_None; - } - return CERR_None; -#else - _wfinddata_t fi; - intptr_t n; - if ((n = _wfindfirst((const wchar_t*) Name, &fi)) == -1) { - if (ReportMissingFile) { - sprintf(ErrExtraLIB, "referenced file '%ls'", (wchar_t*) Filename); - AddWarning(CWARN_MissingFile); - } - return CERR_None; - } - - _findclose(n); - - KMX_WCHAR FName[_MAX_FNAME], Ext[_MAX_EXT]; - wchar_t WChName[_MAX_PATH]; - _wsplitpath_s((const wchar_t*)Filename, nullptr, 0, nullptr, 0, (wchar_t*) FName, _MAX_FNAME, (wchar_t*) Ext, _MAX_EXT); - _wmakepath_s(WChName, _MAX_PATH, nullptr, nullptr, (const wchar_t*) FName, (const wchar_t*) Ext); - if (wcscmp(WChName, fi.name) != 0) { - sprintf(ErrExtraLIB, "reference '%ls' does not match actual filename '%ls'", WChName, fi.name); - - AddWarning(CHINT_FilenameHasDifferingCase); - - } -#endif - - return CERR_None; -} - -KMX_DWORD CheckFilenameConsistencyForCalls(PFILE_KEYBOARD fk) { - // call() statements depend on a fairly ugly hack for js, - // where store(DllFunction) "my.dll:func" will look for a - // file called function.call_js. ( or should this be func.call_js ? ) - // This is ripe for rewrite! - // But let's check what we have anyway - - PFILE_STORE sp; - KMX_DWORD i, msg; - for (i = 0, sp = fk->dpStoreArray; i < fk->cxStoreArray; i++, sp++) { - if (!sp->fIsCall) continue; - - const std::u16string callsite(sp->dpString); - const auto colon = callsite.find(':'); - if (colon == std::u16string::npos) continue; - - auto func1 = callsite.substr(colon + 1); - std::u16string str_js(u".call_js"); - std::u16string func = func1+ str_js; - - if ((msg = CheckFilenameConsistency(func.c_str(), FALSE)) != CERR_None) { - return msg; - } - } - return CERR_None; -} diff --git a/developer/src/kmcmplib/src/CheckFilenameConsistency.h b/developer/src/kmcmplib/src/CheckFilenameConsistency.h deleted file mode 100644 index 39f86db391b1ac6b870d7a521f297372059bd9b4..0000000000000000000000000000000000000000 --- a/developer/src/kmcmplib/src/CheckFilenameConsistency.h +++ /dev/null @@ -1,11 +0,0 @@ -#pragma once - -#include "compfile.h" -#include "kmcmplib.h" - -KMX_DWORD CheckFilenameConsistencyForCalls(PFILE_KEYBOARD fk); -KMX_DWORD CheckFilenameConsistency(KMX_CHAR const * Filename, bool ReportMissingFile); -KMX_DWORD CheckFilenameConsistency(KMX_WCHAR const * Filename, bool ReportMissingFile); - -bool IsRelativePath(KMX_CHAR const * p); -bool IsRelativePath(KMX_WCHAR const * p); diff --git a/developer/src/kmcmplib/src/CompMsg.cpp b/developer/src/kmcmplib/src/CompMsg.cpp index 2fa0d68322dc1217207bb8541a62e304fee40f76..dd41336a0e9d262a96a0b4026cbaac54b80683a3 100644 --- a/developer/src/kmcmplib/src/CompMsg.cpp +++ b/developer/src/kmcmplib/src/CompMsg.cpp @@ -111,7 +111,6 @@ const struct CompilerError CompilerErrors[] = { { CERR_DuplicateStore , "A store with this name has already been defined."}, { CERR_RepeatedBegin , "Begin has already been set"}, - { CHINT_FilenameHasDifferingCase , "Casing differences may fail on some platforms: "}, { CHINT_UnreachableRule , "This rule will never be matched as another rule takes precedence"}, { CWARN_TooManyWarnings , "Too many warnings or errors"}, @@ -142,7 +141,6 @@ const struct CompilerError CompilerErrors[] = { { CWARN_NulNotFirstStatementInContext , "nul must be the first statement in the context"}, { CWARN_IfShouldBeAtStartOfContext , "if, platform and baselayout should be at start of context (after nul, if present)"}, { CWARN_KeyShouldIncludeNCaps , "Other rules which reference this key include CAPS or NCAPS modifiers, so this rule must include NCAPS modifier to avoid inconsistent matches"}, - { CWARN_MissingFile , "The referenced file could not be found: "}, { 0, nullptr } }; diff --git a/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp b/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e90938f18a735ffca4ab986ee53004bb3703a62a --- /dev/null +++ b/developer/src/kmcmplib/src/CompileKeyboardBuffer.cpp @@ -0,0 +1,160 @@ +#include "pch.h" +#include +#include "kmcmplib.h" +#include "CheckNCapsConsistency.h" +#include "DeprecationChecks.h" +#include "versioning.h" +#include "CompileKeyboardBuffer.h" +#include "../../../../common/windows/cpp/include/keymanversion.h" + +bool CompileKeyboardBuffer(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk) +{ + PKMX_WCHAR str, p; + + KMX_DWORD msg; + + kmcmp::FMnemonicLayout = FALSE; + + if (!fk) { + AddCompileError(CERR_SomewhereIGotItWrong); + return FALSE; + } + + str = new KMX_WCHAR[LINESIZE]; + if (!str) { + AddCompileError(CERR_CannotAllocateMemory); + return FALSE; + } + + fk->KeyboardID = 0; + fk->version = 0; + fk->dpStoreArray = NULL; + fk->dpGroupArray = NULL; + fk->cxStoreArray = 0; + fk->cxGroupArray = 0; + fk->StartGroup[0] = fk->StartGroup[1] = -1; + fk->szName[0] = 0; + fk->szCopyright[0] = 0; + fk->dwFlags = KF_AUTOMATICVERSION; + fk->currentGroup = 0xFFFFFFFF; + fk->currentStore = 0; + fk->cxDeadKeyArray = 0; + fk->dpDeadKeyArray = NULL; + fk->cxVKDictionary = 0; // I3438 + fk->dpVKDictionary = NULL; // I3438 + fk->extra->kvksFilename = u""; +/* fk->szMessage[0] = 0; + fk->szLanguageName[0] = 0;*/ + fk->dwBitmapSize = 0; + fk->dwHotKey = 0; + + kmcmp::BeginLine[BEGIN_ANSI] = -1; + kmcmp::BeginLine[BEGIN_UNICODE] = -1; + kmcmp::BeginLine[BEGIN_NEWCONTEXT] = -1; + kmcmp::BeginLine[BEGIN_POSTKEYSTROKE] = -1; + + + /* Add a store for the Keyman 6.0 copyright information string */ + + if(kmcmp::FShouldAddCompilerVersion) { + u16sprintf(str,LINESIZE, L"Created with Keyman Developer version %d.%d.%d.%d", KEYMAN_VersionMajor, KEYMAN_VersionMinor, KEYMAN_VersionPatch, 0); + AddStore(fk, TSS_KEYMANCOPYRIGHT, str); + } + + /* Add a system store for the Keyman edition number */ + AddStore(fk, TSS_CUSTOMKEYMANEDITION, u"0"); + AddStore(fk, TSS_CUSTOMKEYMANEDITIONNAME, u"Keyman"); + + int offset = 0; + + // must preprocess for group and store names -> this isn't really necessary, but never mind! + while ((msg = ReadLine(infile, sz, offset, str, TRUE)) == CERR_None) + { + p = str; + switch (LineTokenType(&p)) + { + case T_VERSION: + *(p + 4) = 0; + if ((msg = AddStore(fk, TSS_VERSION, p)) != CERR_None) { + AddCompileError(msg); + return FALSE; + } + break; + + case T_GROUP: + if ((msg = ProcessGroupLine(fk, p)) != CERR_None) { + AddCompileError(msg); + return FALSE; + } + break; + + case T_STORE: + if ((msg = ProcessStoreLine(fk, p)) != CERR_None) { + AddCompileError(msg); + return FALSE; + } + break; + + default: + break; + } + } + + if (msg != CERR_EndOfFile) { + AddCompileError(msg); + return FALSE; + } + + offset = 0; + kmcmp::currentLine = 0; + + /* Reindex the list of codeconstants after stores added */ + + kmcmp::CodeConstants->reindex(); + + /* ReadLine will automatically skip over $Keyman lines, and parse wrapped lines */ + while ((msg = ReadLine(infile, sz, offset, str, FALSE)) == CERR_None) + { + msg = ParseLine(fk, str); + if (msg != CERR_None) { + AddCompileError(msg); + return FALSE; + } + } + + if (msg != CERR_EndOfFile) { + AddCompileError(msg); + return FALSE; + } + + ProcessGroupFinish(fk); + + if (kmcmp::FSaveDebug) kmcmp::RecordDeadkeyNames(fk); + + /* Add the compiler version as a system store */ + if ((msg = kmcmp::AddCompilerVersionStore(fk)) != CERR_None) { + AddCompileError(msg); + return FALSE; + } + + if ((msg = BuildVKDictionary(fk)) != CERR_None) { + AddCompileError(msg); + return FALSE; + } + + delete str; + + if (!kmcmp::CheckKeyboardFinalVersion(fk)) { + return FALSE; + } + + /* Warn on inconsistent use of NCAPS */ + if (!kmcmp::FMnemonicLayout) { + CheckNCapsConsistency(fk); + } + + /* Flag presence of deprecated features */ + kmcmp::CheckForDeprecatedFeatures(fk); + + return TRUE; +} diff --git a/developer/src/kmcmplib/src/CompileKeyboardBuffer.h b/developer/src/kmcmplib/src/CompileKeyboardBuffer.h new file mode 100644 index 0000000000000000000000000000000000000000..78bac6665399d721b52baf563c62de3732a570fb --- /dev/null +++ b/developer/src/kmcmplib/src/CompileKeyboardBuffer.h @@ -0,0 +1,5 @@ +#pragma once + +#include "compfile.h" + +bool CompileKeyboardBuffer(KMX_BYTE* infile, int sz, PFILE_KEYBOARD fk); diff --git a/developer/src/kmcmplib/src/Compiler.cpp b/developer/src/kmcmplib/src/Compiler.cpp index b09e36cbc1079fdf47feeb9ac882eaf640b3f425..e5d5f94cbc7d0381c8aaec67f59cdbdd43379035 100644 --- a/developer/src/kmcmplib/src/Compiler.cpp +++ b/developer/src/kmcmplib/src/Compiler.cpp @@ -94,11 +94,12 @@ #include "CasedKeys.h" #include #include -#include "CheckFilenameConsistency.h" +#include +#include + #include "UnreachableRules.h" #include "CheckForDuplicates.h" #include "kmx_u16.h" -#include "filesystem.h" #include /* These macros are adapted from winnt.h and legacy use only */ @@ -122,7 +123,6 @@ namespace kmcmp{ KMX_BOOL FMnemonicLayout = FALSE; KMX_BOOL FOldCharPosMatching = FALSE; int CompileTarget; - KMX_CHAR CompileDir[260]; // TODO: this should not be a fixed buffer int BeginLine[4]; KMX_BOOL IsValidCallStore(PFILE_STORE fs); @@ -230,6 +230,8 @@ enum LinePrefixType { lptNone, lptKeymanAndKeymanWeb, lptKeymanWebOnly, lptKeyma /* Compile target */ kmcmp_CompilerMessageProc msgproc = NULL; +kmcmp_LoadFileProc loadfileproc = NULL; + void* msgprocContext = NULL; int kmcmp::currentLine = 0; @@ -862,7 +864,6 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE int i, j; KMX_DWORD msg; PKMX_WCHAR p, q; - KMX_CHAR *pp; if (!pssBuf) pssBuf = new KMX_WCHAR[GLOBAL_BUFSIZE]; PKMX_WCHAR buf = pssBuf; @@ -912,13 +913,9 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE case TSS_INCLUDECODES: VERIFY_KEYBOARD_VERSION(fk, VERSION_60, CERR_60FeatureOnly_NamedCodes); - pp = wstrtostr(sp->dpString); - if (!kmcmp::CodeConstants->LoadFile(pp)) - { - delete[] pp; + if (!kmcmp::CodeConstants->LoadFile(fk, sp->dpString)) { return CERR_CannotLoadIncludeFile; } - delete[] pp; kmcmp::CodeConstants->reindex(); // I4982 break; @@ -1047,10 +1044,6 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE delete[] sp->dpString; sp->dpString = q; - - if ((msg = CheckFilenameConsistency( (sp->dpString), FALSE)) != CERR_None) { - return msg; - } } break; case TSS_KMW_RTL: @@ -1061,16 +1054,10 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE case TSS_KMW_HELPFILE: case TSS_KMW_EMBEDJS: VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_70FeatureOnly); - if ((msg = CheckFilenameConsistency(sp->dpString, FALSE)) != CERR_None) { - return msg; - } break; case TSS_KMW_EMBEDCSS: VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnlyEmbedCSS); - if ((msg = CheckFilenameConsistency(sp->dpString, FALSE)) != CERR_None) { - return msg; - } break; case TSS_TARGETS: // I4504 @@ -1120,9 +1107,6 @@ KMX_DWORD ProcessSystemStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, PFILE_STORE case TSS_LAYOUTFILE: // I3483 VERIFY_KEYBOARD_VERSION(fk, VERSION_90, CERR_90FeatureOnlyLayoutFile); // I4140 - if ((msg = CheckFilenameConsistency(sp->dpString, FALSE)) != CERR_None) { - return msg; - } // Used by KMW compiler break; @@ -3068,7 +3052,7 @@ KMX_DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, KMX_BYTE**data, size_t& dataS return CERR_None; } -KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess) +KMX_DWORD ReadLine(KMX_BYTE* infile, int sz, int& offset, PKMX_WCHAR wstr, KMX_BOOL PreProcess) { KMX_DWORD len; PKMX_WCHAR p; @@ -3076,21 +3060,26 @@ KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess) KMX_DWORD n; KMX_WCHAR currentQuotes = 0; KMX_WCHAR str[LINESIZE + 3]; - len = (KMX_DWORD)fread( str , 1 ,LINESIZE * 2,fp_in); - if (ferror(fp_in) ) return CERR_CannotReadInfile; - len /= 2; - str[len] = 0; auto cur = ftell(fp_in); - fseek(fp_in, 0, SEEK_END); - auto fsize = ftell(fp_in); - fseek(fp_in, cur, SEEK_SET); - if (cur == fsize) + if(offset >= sz) { + return CERR_EndOfFile; + } - // \r\n is still added here even though Linux doesn`t use \r. - // This is to ensure to still have a working windows-only-version - u16ncat(str, u"\r\n", _countof(str)); // I3481 // Always a "\r\n" to the EOF, avoids funny bugs + len = offset + LINESIZE*2 > sz ? sz-offset : LINESIZE*2; + memcpy(str, infile+offset, len); + offset += len; + len /= 2; + str[len] = 0; - if (len == 0) return CERR_EndOfFile; + if(offset == sz) { + // \r\n is still added here even though Linux doesn`t use \r. + // This is to ensure to still have a working windows-only-version + u16ncat(str, u"\r\n", _countof(str)); // I3481 // Always a "\r\n" to the EOF, avoids funny bugs + } + + if (len == 0) { + return CERR_EndOfFile; + } // neccessary to add this block for using on non-windows platforms (removes all \r for platforms that use \n instead of \r\n) for (p = str, n = 0; n < len; n++, p++) { @@ -3172,9 +3161,13 @@ KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess) return (PreProcess ? CERR_None : CERR_LineTooLong); } - if (*p == L'\n') kmcmp::currentLine++; + kmcmp::currentLine++; - fseek(fp_in, -(int)(len * 2 - (int)(p - str) * 2 - 2), SEEK_CUR); + offset -= (int)(len * 2 - (int)(p - str) * 2 - 2); + if(offset >= sz) { + // If we've appended a \n, we can go past EOF + offset = sz; + } p--; while (p >= str && iswspace(*p)) p--; @@ -3220,58 +3213,38 @@ KMX_BOOL IsSameToken(PKMX_WCHAR *p, KMX_WCHAR const * token) return FALSE; } -KMX_DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PKMX_WCHAR szName, PKMX_DWORD FileSize, PKMX_BYTE *Buf) +static bool endsWith(const std::string& str, const std::string& suffix) { - FILE *fp; - KMX_WCHAR szNewName[260]; - - if (IsRelativePath(szName)) - { - PKMX_WCHAR WCompileDir = strtowstr(kmcmp::CompileDir); - u16ncpy(szNewName, WCompileDir, _countof(szNewName)); // I3481 - u16ncat(szNewName,szName, _countof(szNewName )); // I3481 - } - else - u16ncpy(szNewName, szName, _countof(szNewName)); // I3481 - - fp=Open_File(szNewName, u"rb"); - - if ( fp == NULL) - { - // else if filename.bmp is not in the folder -> attempt to open filename.bmp.bmp ! - if ( u16cmp(szNewName+u16len(szNewName)-4, u".bmp") ) - u16ncat(szNewName, u".bmp", _countof(szNewName)); // I3481 + return str.size() >= suffix.size() && 0 == str.compare(str.size()-suffix.size(), suffix.size(), suffix); +} - fp= Open_File(szNewName, u"rb"); +KMX_DWORD ImportBitmapFile(PFILE_KEYBOARD fk, PKMX_WCHAR szName, PKMX_DWORD FileSize, PKMX_BYTE *Buf) +{ + auto szNameUtf8 = string_from_u16string(szName); - if ( fp == NULL) + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), nullptr, (int*) FileSize, msgprocContext)) { + // Append .bmp and try again + if(endsWith(szNameUtf8, ".bmp")) { return CERR_CannotReadBitmapFile; + } + szNameUtf8.append(".bmp"); + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), nullptr, (int*) FileSize, msgprocContext)) { + return CERR_CannotReadBitmapFile; + } } - KMX_DWORD msg; - if ((msg = CheckFilenameConsistency(szNewName, FALSE)) != CERR_None) { - return msg; - } - - fseek(fp, 0, SEEK_END); - *FileSize = (KMX_DWORD)ftell(fp); - fseek(fp ,0,SEEK_SET); - if (*FileSize < 0) { - fclose(fp); + if(*FileSize < 2) { + // Zero-byte file is invalid; 2 byte file is too, but we only really care + // about the prolog at this point so we don't overrun our buffer return CERR_CannotReadBitmapFile; } - if (*FileSize < 2) return CERR_CannotReadBitmapFile; *Buf = new KMX_BYTE[*FileSize]; - - if (fread(*Buf, 1, *FileSize, fp) < (size_t) *FileSize) { - delete[] * Buf; - *Buf = NULL; + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), *Buf, (int*) FileSize, msgprocContext)) { + delete[] *Buf; return CERR_CannotReadBitmapFile; } - fclose(fp); - /* Test for version 7.0 icon support */ if (*((PKMX_CHAR)*Buf) != 'B' && *(((PKMX_CHAR)*Buf) + 1) != 'M') { VERIFY_KEYBOARD_VERSION(fk, VERSION_70, CERR_70FeatureOnly); @@ -3427,81 +3400,54 @@ KMX_BOOL kmcmp::IsValidCallStore(PFILE_STORE fs) return i == 1; } -FILE* CreateTempFile() -{ - return tmpfile(); +/////////////////// + +bool hasPreamble(std::u16string result) { + return result.size() > 0 && result[0] == 0xFEFF; } -/////////////////// +#include "unicode/ucnv.h" -FILE* UTF16TempFromUTF8(FILE* fp_in , KMX_BOOL hasPreamble) -{ - FILE *fp_out = CreateTempFile(); - if(fp_out == NULL) // I3228 // I3510 - { - fclose(fp_in); - return NULL; //return INVALID_HANDLE_VALUE; _S2 can I exchange that? +bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16) { + if(sz == 0) { + return FALSE; } - PKMX_BYTE buf, p; - PKMX_WCHAR outbuf, poutbuf; - KMX_DWORD len; - KMX_DWORD len2; - KMX_WCHAR prolog = 0xFEFF; - fwrite(&prolog,2, 1, fp_out); - - fseek(fp_in, 0, SEEK_END); - len = (KMX_DWORD)ftell(fp_in); - fseek(fp_in, 0, SEEK_SET); - if (hasPreamble) { - fseek( fp_in,3,SEEK_SET); // Cut off UTF-8 marker - len -= 3; - } - - buf = new KMX_BYTE[len + 1]; // null terminated - outbuf = new KMX_WCHAR[len + 1]; - - len2= (KMX_DWORD)fread(buf,1,len,fp_in); - if (len2) { - buf[len2] = 0; - p = buf; - poutbuf = outbuf; - if (hasPreamble) { - // We have a preamble, so we attempt to read as UTF-8 and allow conversion errors to be filtered. This is not great for a - // compiler but matches existing behaviour -- in future versions we may not do lenient conversion. - ConvertUTF8toUTF16(&p, &buf[len2], (UTF16 **)&poutbuf, (const UTF16 *)&outbuf[len], lenientConversion); - fwrite(outbuf, (KMX_DWORD)(poutbuf - outbuf) * 2 , 1, fp_out); + std::u16string result; + + try { + std::wstring_convert, char16_t> converter; + result = converter.from_bytes((char*)infile, (char*)infile+sz); + } catch(std::range_error e) { + UErrorCode status = U_ZERO_ERROR; + // TODO: we need ICU data files here #8884 + UConverter* conv = ucnv_open("windows-1252", &status); + if(U_FAILURE(status)) { + return FALSE; } - else { - // No preamble, so we attempt to read as strict UTF-8 and fall back to ANSI if that fails - ConversionResult cr = ConvertUTF8toUTF16(&p, &buf[len2], (UTF16 **)&poutbuf, (const UTF16 *)&outbuf[len], strictConversion); - if (cr == sourceIllegal) { - // Not a valid UTF-8 file, so fall back to ANSI - // AddCompileError(CHINT_NonUnicodeFile); - // note, while this message is defined, for now we will not emit it - // because we don't support HINT/INFO messages yet and we don't want - // this to cause a blocking compile at this stage - // do strtowstr only when no invalid characters are found - if( p==0){ - poutbuf = strtowstr((PKMX_STR)buf); - fwrite(poutbuf, (KMX_DWORD)u16len(poutbuf) * 2 , 1, fp_out); - delete[] poutbuf; - } - else - AddCompileError(CERR_InvalidCharacter); - } - else { - fwrite(outbuf, (KMX_DWORD)(poutbuf - outbuf) * 2 , 1, fp_out); - } + char16_t* dest = new char16_t[sz*2]; + ucnv_toUChars(conv, dest, sz*2, (char*)infile, sz, &status); + if(U_FAILURE(status)) { + delete[] dest; + return FALSE; } + + result = dest; + delete[] dest; } - fclose( fp_in); - delete[] buf; - delete[] outbuf; - fseek( fp_out,2,SEEK_SET); - return fp_out; + if(hasPreamble(result)) { + *sz16 = result.size() * 2 - 2; + *tempfile = new KMX_BYTE[*sz16]; + memcpy(*tempfile, result.c_str() + 1, *sz16); + } else { + *sz16 = result.size() * 2; + *tempfile = new KMX_BYTE[*sz16]; + memcpy(*tempfile, result.c_str(), *sz16); + } + + return TRUE; } PFILE_STORE FindSystemStore(PFILE_KEYBOARD fk, KMX_DWORD dwSystemID) diff --git a/developer/src/kmcmplib/src/CompilerInterfaces.cpp b/developer/src/kmcmplib/src/CompilerInterfaces.cpp index a1609d35449562695c622e689649f962f6f6c57f..fdfbdf19613cc26d3de1161ca47e2dfdeb9c7bd7 100644 --- a/developer/src/kmcmplib/src/CompilerInterfaces.cpp +++ b/developer/src/kmcmplib/src/CompilerInterfaces.cpp @@ -1,453 +1,110 @@ #include "pch.h" - #include #include #include "kmcmplib.h" -#include "filesystem.h" -#include "CheckFilenameConsistency.h" -#include "CheckNCapsConsistency.h" -#include "DeprecationChecks.h" -#include "versioning.h" #include "../../../../common/windows/cpp/include/ConvertUTF.h" -#include "../../../../common/windows/cpp/include/keymanversion.h" - -bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk); -bool CompileKeyboard(const char* pszInfile, - void* pfkBuffer, bool ASaveDebug, bool ACompilerWarningsAsErrors, - bool AWarnDeprecatedCode, kmcmp_CompilerMessageProc pMsgproc, const void* AmsgprocContext, - int Target); - -EXTERN bool kmcmp_SetCompilerOptions(KMCMP_COMPILER_OPTIONS* options) { - //printf("°°-> changed to SetCompilerOptions() of kmcmplib \n"); - if(!options || options->dwSize < sizeof(KMCMP_COMPILER_OPTIONS)) { - return FALSE; - } - kmcmp::FShouldAddCompilerVersion = options->ShouldAddCompilerVersion; - return TRUE; -} - -#ifdef __EMSCRIPTEN__ - -/* - WASM interface for compiler message callback -*/ -EM_JS(int, wasm_msgproc, (int line, int msgcode, char* text, char* context), { - const proc = globalThis[UTF8ToString(context)]; - if(!proc || typeof proc != 'function') { - console.log(`[${line}: ${msgcode}: ${UTF8ToString(text)}]`); - return 0; - } else { - return proc(line, msgcode, UTF8ToString(text)); - } -}); - -int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, char* szText, void* context) { - char* msgProc = static_cast(context); - return wasm_msgproc(line, dwMsgCode, szText, msgProc); -} - -//DEPRECATED -EXTERN bool kmcmp_Wasm_SetCompilerOptions(int ShouldAddCompilerVersion) { - KMCMP_COMPILER_OPTIONS options; - options.dwSize = sizeof(KMCMP_COMPILER_OPTIONS); - options.ShouldAddCompilerVersion = ShouldAddCompilerVersion; - return kmcmp_SetCompilerOptions(&options); -} - -//DEPRECATED -EXTERN bool kmcmp_Wasm_CompileKeyboardFile(char* pszInfile, - char* pszOutfile, int ASaveDebug, int ACompilerWarningsAsErrors, - int AWarnDeprecatedCode, char* msgProc +#include "CompileKeyboardBuffer.h" + +EXTERN bool kmcmp_CompileKeyboard( + const char* pszInfile, + const KMCMP_COMPILER_OPTIONS& options, + kmcmp_CompilerMessageProc messageProc, + kmcmp_LoadFileProc loadFileProc, + const void* procContext, + KMCMP_COMPILER_RESULT& result ) { - return kmcmp_CompileKeyboardFile( - pszInfile, - pszOutfile, - ASaveDebug, - ACompilerWarningsAsErrors, - AWarnDeprecatedCode, - wasm_CompilerMessageProc, - msgProc - ); -} - -EXTERN int kmcmp_Wasm_ParseUnicodeSet(char* pat, - uint32_t* buf, int length -) { - return kmcmp_ParseUnicodeSet( - pat, buf, length - ); -} - - -struct COMPILER_INTERFACE { - bool saveDebug; - bool compilerWarningsAsErrors; - bool warnDeprecatedCode; - bool shouldAddCompilerVersion; - int target; // CKF_KEYMAN, CKF_KEYMANWEB - std::string messageCallback; // int line, uint32_t dwMsgCode, char* szText - std::string loadFileCallback; // char* infile, char* filenameRelativeToInfile --> buffer -}; - -struct COMPILER_RESULT { - bool result; - // Following are pointer offsets in heap + buffer size - int kmx; - int kmxSize; - // Following are compiler side-channel data, required for - // follow-on transform - std::string kvksFilename; - // TODO: additional data to be passed back -}; - -COMPILER_RESULT kmcmp_compile(std::string pszInfile, const COMPILER_INTERFACE intf) { - COMPILER_RESULT r = {false}; FILE_KEYBOARD fk; + fk.extra = new FILE_KEYBOARD_EXTRA; + fk.extra->kmnFilename = pszInfile; - // TODO: this should be included in CompileKeyboard? - kmcmp::FShouldAddCompilerVersion = intf.shouldAddCompilerVersion; - - r.result = CompileKeyboard( - pszInfile.c_str(), - &fk, - intf.saveDebug, - intf.compilerWarningsAsErrors, - intf.warnDeprecatedCode, - wasm_CompilerMessageProc, - intf.messageCallback.c_str(), - intf.target); - - if(!r.result) { - return r; - } - - KMX_DWORD msg; - KMX_BYTE* data = nullptr; - size_t dataSize = 0; - msg = WriteCompiledKeyboard(&fk, &data, dataSize); - //TODO: FreeKeyboardPointers(fk); - - if(msg != CERR_None) { - AddCompileError(msg); - r.result = FALSE; - return r; - } - - r.kmx = (int) data; - r.kmxSize = (int) dataSize; - r.kvksFilename = string_from_u16string(fk.extra->kvksFilename); // convert to UTF8 - - return r; -} - -EMSCRIPTEN_BINDINGS(compiler_interface) { - emscripten::class_("CompilerInterface") - .constructor<>() - .property("saveDebug", &COMPILER_INTERFACE::saveDebug) - .property("compilerWarningsAsErrors", &COMPILER_INTERFACE::compilerWarningsAsErrors) - .property("warnDeprecatedCode", &COMPILER_INTERFACE::warnDeprecatedCode) - .property("shouldAddCompilerVersion", &COMPILER_INTERFACE::shouldAddCompilerVersion) - .property("target", &COMPILER_INTERFACE::target) - .property("messageCallback", &COMPILER_INTERFACE::messageCallback) - .property("loadFileCallback", &COMPILER_INTERFACE::loadFileCallback) - ; - - emscripten::class_("CompilerResult") - .constructor<>() - .property("result", &COMPILER_RESULT::result) - .property("kmx", &COMPILER_RESULT::kmx) - .property("kmxSize", &COMPILER_RESULT::kmxSize) - .property("kvksFilename", &COMPILER_RESULT::kvksFilename) - ; - - emscripten::function("kmcmp_compile", &kmcmp_compile); -} - -#endif - -bool CompileKeyboard(const char* pszInfile, - void* pfkBuffer, bool ASaveDebug, bool ACompilerWarningsAsErrors, - bool AWarnDeprecatedCode, kmcmp_CompilerMessageProc pMsgproc, const void* AmsgprocContext, - int Target) { - - FILE* fp_in = NULL; - KMX_CHAR str[260]; + kmcmp::FSaveDebug = options.saveDebug; // I3681 + kmcmp::FCompilerWarningsAsErrors = options.compilerWarningsAsErrors; // I4865 + AWarnDeprecatedCode_GLOBAL_LIB = options.warnDeprecatedCode; + kmcmp::FShouldAddCompilerVersion = options.shouldAddCompilerVersion; + kmcmp::CompileTarget = options.target; - kmcmp::FSaveDebug = ASaveDebug; // I3681 - kmcmp::FCompilerWarningsAsErrors = ACompilerWarningsAsErrors; // I4865 - AWarnDeprecatedCode_GLOBAL_LIB = AWarnDeprecatedCode; - - kmcmp::CompileTarget = Target; - - if (!pMsgproc || !pszInfile || !pfkBuffer) { + if (!messageProc || !loadFileProc || !pszInfile) { AddCompileError(CERR_BadCallParams); return FALSE; } - PKMX_STR p; - - if ((p = strrchr_slash((char*)pszInfile)) != nullptr) - { - strncpy(kmcmp::CompileDir, pszInfile, (int)(p - pszInfile + 1)); // I3481 - kmcmp::CompileDir[(int)(p - pszInfile + 1)] = 0; - } - else - kmcmp::CompileDir[0] = 0; - - msgproc = pMsgproc; - msgprocContext = (void*)AmsgprocContext; + msgproc = messageProc; + loadfileproc = loadFileProc; + msgprocContext = (void*)procContext; kmcmp::currentLine = 0; kmcmp::nErrors = 0; - fp_in = Open_File(pszInfile, "rb"); - - if (fp_in == NULL) { + int sz; + if(!loadFileProc(pszInfile, "", nullptr, &sz, msgprocContext)) { AddCompileError(CERR_InfileNotExist); return FALSE; } - // Transfer the file to a memory stream for processing UTF-8 or ANSI to UTF-16? - // What about really large files? Transfer to a temp file... - if (!fread(str, 1, 3, fp_in)) { - fclose(fp_in); + if(sz < 3) { + // Technically, a 3 byte file can never be a valid .kmn, so we can shortcut + // here and avoid testing outside memory bounds for looking at BOM AddCompileError(CERR_CannotReadInfile); return FALSE; } - fseek(fp_in, 0, SEEK_SET); - if (str[0] == UTF8Sig[0] && str[1] == UTF8Sig[1] && str[2] == UTF8Sig[2]) - fp_in = UTF16TempFromUTF8(fp_in, TRUE); - else if (str[0] == UTF16Sig[0] && str[1] == UTF16Sig[1]) - fseek(fp_in, 2, SEEK_SET); - else - fp_in = UTF16TempFromUTF8(fp_in, FALSE); - if (fp_in == NULL) { - AddCompileError(CERR_CannotCreateTempfile); + KMX_BYTE* infile = new KMX_BYTE[sz+1]; + if(!infile) { + AddCompileError(CERR_CannotAllocateMemory); return FALSE; } - - kmcmp::CodeConstants = new kmcmp::NamedCodeConstants; - bool result = CompileKeyboardHandle(fp_in, static_cast(pfkBuffer)); - delete kmcmp::CodeConstants; - - fclose(fp_in); - - if (kmcmp::nErrors > 0) { + if(!loadFileProc(pszInfile, "", infile, &sz, msgprocContext)) { + delete[] infile; + AddCompileError(CERR_CannotReadInfile); return FALSE; } + infile[sz] = 0; // zero-terminate for safety, not technically needed but helps avoid memory bugs - return result; -} - -EXTERN bool kmcmp_CompileKeyboardFileToBuffer(char* pszInfile, void* pfkBuffer, bool ACompilerWarningsAsErrors, bool AWarnDeprecatedCode, - kmcmp_CompilerMessageProc pMsgproc, void* AmsgprocContext, int Target) // I4865 // I4866 -{ - if (!pMsgproc || !pszInfile || !pfkBuffer) { - AddCompileError(CERR_BadCallParams); - return FALSE; + int offset = 0; + if(infile[0] == (KMX_BYTE) UTF16Sig[0] && infile[1] == (KMX_BYTE) UTF16Sig[1]) { + // UTF-16 source file + offset = 2; + } else { + // UTF-8 source file + KMX_BYTE* infile16; + int sz16; + if(!UTF16TempFromUTF8(infile, sz, &infile16, &sz16)) { + delete[] infile; + AddCompileError(CERR_CannotCreateTempfile); + return FALSE; + } + delete[] infile; + infile = infile16; + sz = sz16; } - return CompileKeyboard(pszInfile, pfkBuffer, TRUE, ACompilerWarningsAsErrors, AWarnDeprecatedCode, - pMsgproc, AmsgprocContext, Target); -} + kmcmp::CodeConstants = new kmcmp::NamedCodeConstants; + bool success = CompileKeyboardBuffer(infile+offset, sz-offset, &fk); + delete kmcmp::CodeConstants; -EXTERN bool kmcmp_CompileKeyboardFile(char* pszInfile, - char* pszOutfile, bool ASaveDebug, bool ACompilerWarningsAsErrors, - bool AWarnDeprecatedCode, kmcmp_CompilerMessageProc pMsgproc, void* AmsgprocContext) // I4865 // I4866 -{ - if (!pMsgproc || !pszInfile || !pszOutfile) { - AddCompileError(CERR_BadCallParams); - return FALSE; - } + delete[] infile; - FILE_KEYBOARD fk; - if(!CompileKeyboard(pszInfile, &fk, ASaveDebug, ACompilerWarningsAsErrors, AWarnDeprecatedCode, - pMsgproc, AmsgprocContext, CKF_KEYMAN)) { - // any errors will have been reported directly by CompileKeyboard - return FALSE; - } - - FILE* fp_out = Open_File(pszOutfile, "wb"); - if (fp_out == NULL) { - AddCompileError(CERR_CannotCreateOutfile); + if (kmcmp::nErrors > 0 || !success) { return FALSE; } + // fill in result data KMX_DWORD msg; KMX_BYTE* data = nullptr; size_t dataSize = 0; - if ((msg = WriteCompiledKeyboard(&fk, &data, dataSize)) != CERR_None) { - AddCompileError(msg); - } else { - if(fwrite(data, 1, dataSize, fp_out) != dataSize) { - AddCompileError(CERR_UnableToWriteFully); - } - delete[] data; - } - - fclose(fp_out); - - if (kmcmp::nErrors > 0) { - remove(pszOutfile); - return FALSE; - } - - return TRUE; -} - -bool CompileKeyboardHandle(FILE* fp_in, PFILE_KEYBOARD fk) -{ - PKMX_WCHAR str, p; - - KMX_DWORD msg; - - kmcmp::FMnemonicLayout = FALSE; - - if (!fk) { - AddCompileError(CERR_SomewhereIGotItWrong); - return FALSE; - } - - str = new KMX_WCHAR[LINESIZE]; - if (!str) { - AddCompileError(CERR_CannotAllocateMemory); - return FALSE; - } - - fk->KeyboardID = 0; - fk->version = 0; - fk->dpStoreArray = NULL; - fk->dpGroupArray = NULL; - fk->cxStoreArray = 0; - fk->cxGroupArray = 0; - fk->StartGroup[0] = fk->StartGroup[1] = -1; - fk->szName[0] = 0; - fk->szCopyright[0] = 0; - fk->dwFlags = KF_AUTOMATICVERSION; - fk->currentGroup = 0xFFFFFFFF; - fk->currentStore = 0; - fk->cxDeadKeyArray = 0; - fk->dpDeadKeyArray = NULL; - fk->cxVKDictionary = 0; // I3438 - fk->dpVKDictionary = NULL; // I3438 - fk->extra = new FILE_KEYBOARD_EXTRA; - fk->extra->kvksFilename = u""; -/* fk->szMessage[0] = 0; - fk->szLanguageName[0] = 0;*/ - fk->dwBitmapSize = 0; - fk->dwHotKey = 0; - - kmcmp::BeginLine[BEGIN_ANSI] = -1; - kmcmp::BeginLine[BEGIN_UNICODE] = -1; - kmcmp::BeginLine[BEGIN_NEWCONTEXT] = -1; - kmcmp::BeginLine[BEGIN_POSTKEYSTROKE] = -1; - - - /* Add a store for the Keyman 6.0 copyright information string */ - - if(kmcmp::FShouldAddCompilerVersion) { - u16sprintf(str,LINESIZE, L"Created with Keyman Developer version %d.%d.%d.%d", KEYMAN_VersionMajor, KEYMAN_VersionMinor, KEYMAN_VersionPatch, 0); - AddStore(fk, TSS_KEYMANCOPYRIGHT, str); - } - - /* Add a system store for the Keyman edition number */ - AddStore(fk, TSS_CUSTOMKEYMANEDITION, u"0"); - AddStore(fk, TSS_CUSTOMKEYMANEDITIONNAME, u"Keyman"); - - // must preprocess for group and store names -> this isn't really necessary, but never mind! - while ((msg = ReadLine(fp_in, str, TRUE)) == CERR_None) - { - p = str; - switch (LineTokenType(&p)) - { - case T_VERSION: - *(p + 4) = 0; - if ((msg = AddStore(fk, TSS_VERSION, p)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } - break; - - case T_GROUP: - if ((msg = ProcessGroupLine(fk, p)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } - break; - - case T_STORE: - if ((msg = ProcessStoreLine(fk, p)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } - break; - - default: - break; - } - } - - if (msg != CERR_EndOfFile) { - AddCompileError(msg); - return FALSE; - } - - fseek( fp_in,2,SEEK_SET); - kmcmp::currentLine = 0; - - /* Reindex the list of codeconstants after stores added */ - - kmcmp::CodeConstants->reindex(); - - /* ReadLine will automatically skip over $Keyman lines, and parse wrapped lines */ - while ((msg = ReadLine(fp_in, str, FALSE)) == CERR_None) - { - msg = ParseLine(fk, str); - if (msg != CERR_None) { - AddCompileError(msg); - return FALSE; - } - } - - if (msg != CERR_EndOfFile) { - AddCompileError(msg); - return FALSE; - } - - ProcessGroupFinish(fk); - - if (kmcmp::FSaveDebug) kmcmp::RecordDeadkeyNames(fk); - - /* Add the compiler version as a system store */ - if ((msg = kmcmp::AddCompilerVersionStore(fk)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } + msg = WriteCompiledKeyboard(&fk, &data, dataSize); - if ((msg = BuildVKDictionary(fk)) != CERR_None) { - AddCompileError(msg); - return FALSE; - } + //TODO: FreeKeyboardPointers(fk); - if ((msg = CheckFilenameConsistencyForCalls(fk)) != CERR_None) { + if(msg != CERR_None) { AddCompileError(msg); return FALSE; } - delete str; - - if (!kmcmp::CheckKeyboardFinalVersion(fk)) { - return FALSE; - } - - /* Warn on inconsistent use of NCAPS */ - if (!kmcmp::FMnemonicLayout) { - CheckNCapsConsistency(fk); - } - - /* Flag presence of deprecated features */ - kmcmp::CheckForDeprecatedFeatures(fk); + result.kmx = data; + result.kmxSize = dataSize; + result.kvksFilename = string_from_u16string(fk.extra->kvksFilename); // convert to UTF8 return TRUE; } + diff --git a/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp new file mode 100644 index 0000000000000000000000000000000000000000..0aab9ba51d4abf4262595356feed46819d2c0c4a --- /dev/null +++ b/developer/src/kmcmplib/src/CompilerInterfacesWasm.cpp @@ -0,0 +1,117 @@ +#include "pch.h" +#include + +#ifdef __EMSCRIPTEN__ + +/* + WASM interface for compiler message callback +*/ +EM_JS(int, wasm_msgproc, (int line, int msgcode, const char* text, char* context), { + const proc = globalThis[UTF8ToString(context)].message; + if(!proc || typeof proc != 'function') { + console.log(`[${line}: ${msgcode}: ${UTF8ToString(text)}]`); + return 0; + } else { + return proc(line, msgcode, UTF8ToString(text)); + } +}); + +EM_JS(int, wasm_loadfileproc, (const char* filename, const char* baseFilename, void* buffer, int bufferSize, char* context), { + const proc = globalThis[UTF8ToString(context)].loadFile; + if(!proc || typeof proc != 'function') { + return 0; + } else { + if(buffer == 0) { + return proc(UTF8ToString(filename), UTF8ToString(baseFilename), 0, 0); + } else { + return proc(UTF8ToString(filename), UTF8ToString(baseFilename), buffer, bufferSize); + } + } +}); + +bool wasm_LoadFileProc(const char* filename, const char* baseFilename, void* buffer, int* bufferSize, void* context) { + char* msgProc = static_cast(context); + if(buffer == nullptr) { + *bufferSize = wasm_loadfileproc(filename, baseFilename, 0, 0, msgProc); + return *bufferSize != -1; + } else { + return wasm_loadfileproc(filename, baseFilename, buffer, *bufferSize, msgProc) == 1; + } +} + +int wasm_CompilerMessageProc(int line, uint32_t dwMsgCode, const char* szText, void* context) { + char* msgProc = static_cast(context); + return wasm_msgproc(line, dwMsgCode, szText, msgProc); +} + +struct WASM_COMPILER_INTERFACE { + std::string callbacksKey; // key of callbacks object on globalThis +}; + +struct WASM_COMPILER_RESULT { + bool result; + // Following are pointer offsets in heap + buffer size + int kmx; + int kmxSize; + // Following are compiler side-channel data, required for + // follow-on transform + std::string kvksFilename; + // TODO: additional data to be passed back +}; + +WASM_COMPILER_RESULT kmcmp_wasm_compile(std::string pszInfile, const KMCMP_COMPILER_OPTIONS options, const WASM_COMPILER_INTERFACE intf) { + WASM_COMPILER_RESULT r = {false}; + KMCMP_COMPILER_RESULT kr; + + r.kmx = 0; + r.kmxSize = 0; + r.kvksFilename = ""; + + r.result = kmcmp_CompileKeyboard( + pszInfile.c_str(), + options, + wasm_CompilerMessageProc, + wasm_LoadFileProc, + intf.callbacksKey.c_str(), + kr + ); + + if(r.result) { + // TODO: additional data as required by kmc_kmw + r.kmx = (int) kr.kmx; + r.kmxSize = (int) kr.kmxSize; + r.kvksFilename = kr.kvksFilename; + } + + return r; +} + +EMSCRIPTEN_BINDINGS(compiler_interface) { + + emscripten::class_("CompilerOptions") + .constructor<>() + .property("saveDebug", &KMCMP_COMPILER_OPTIONS::saveDebug) + .property("compilerWarningsAsErrors", &KMCMP_COMPILER_OPTIONS::compilerWarningsAsErrors) + .property("warnDeprecatedCode", &KMCMP_COMPILER_OPTIONS::warnDeprecatedCode) + .property("shouldAddCompilerVersion", &KMCMP_COMPILER_OPTIONS::shouldAddCompilerVersion) + .property("target", &KMCMP_COMPILER_OPTIONS::target) + ; + + emscripten::class_("CompilerInterface") + .constructor<>() + .property("callbacksKey", &WASM_COMPILER_INTERFACE::callbacksKey) + ; + + emscripten::class_("CompilerResult") + .constructor<>() + .property("result", &WASM_COMPILER_RESULT::result) + .property("kmx", &WASM_COMPILER_RESULT::kmx) + .property("kmxSize", &WASM_COMPILER_RESULT::kmxSize) + .property("kvksFilename", &WASM_COMPILER_RESULT::kvksFilename) + ; + + emscripten::function("kmcmp_compile", &kmcmp_wasm_compile); + emscripten::function("kmcmp_parseUnicodeSet", &kmcmp_parseUnicodeSet); +} + +#endif diff --git a/developer/src/kmcmplib/src/NamedCodeConstants.cpp b/developer/src/kmcmplib/src/NamedCodeConstants.cpp index e6b1ae6d70ce20846ba23d24e0ab0efd05d2120a..a8af8fa783c23ac87f211452266d1c85cb9b87c8 100644 --- a/developer/src/kmcmplib/src/NamedCodeConstants.cpp +++ b/developer/src/kmcmplib/src/NamedCodeConstants.cpp @@ -24,18 +24,14 @@ #include "pch.h" #include #include "NamedCodeConstants.h" -#include "CheckFilenameConsistency.h" #include #include "kmcompx.h" -#include "filesystem.h" using namespace kmcmp; int IsHangulSyllable(const KMX_WCHAR *codename, int *code); namespace kmcmp { - extern KMX_CHAR CompileDir[]; - int __cdecl sort_entries(const void *elem1, const void *elem2) { return u16icmp( @@ -117,85 +113,57 @@ char *kmc_strupr(char *s) { return s; } -KMX_BOOL NamedCodeConstants::IntLoadFile(const KMX_CHAR *filename) -{ +KMX_BOOL NamedCodeConstants::LoadFile(PFILE_KEYBOARD fk, const KMX_WCHAR *filename) { const int str_size = 256; - FILE *fp = NULL; - if (CheckFilenameConsistency(filename, FALSE) != 0) { + auto szNameUtf8 = string_from_u16string(filename); + + int FileSize; + KMX_BYTE* Buf; + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), nullptr, &FileSize, msgprocContext)) { return FALSE; } - fp = Open_File(filename, "rt"); - if(fp == NULL) { - return FALSE; // I3481 + Buf = new KMX_BYTE[FileSize+1]; + if(!loadfileproc(szNameUtf8.c_str(), fk->extra->kmnFilename.c_str(), Buf, &FileSize, msgprocContext)) { + delete[] Buf; + return FALSE; } + Buf[FileSize] = 0; // zero-terminate for strtok - KMX_CHAR str[str_size], *p, *q, *context = NULL; - KMX_BOOL isEol , first = TRUE; + char* filetok; + char* filecontext; + filetok = strtok_r((char*)Buf, "\n", &filecontext); - while(fgets(str, str_size, fp)) - { - isEol = *(strchr(str, 0) - 1) == '\n'; - p = strtok_r(str, ";", &context); // I3481 - q = strtok_r(NULL, ";\n", &context); - if(p && q) - { - if(first && *p == (KMX_CHAR)0xEF && *(p+1) == (KMX_CHAR)0xBB && *(p+2) == (KMX_CHAR)0xBF) p += 3; // I3056 UTF-8 // I3512 - first = FALSE; + if(*filetok == (KMX_CHAR)0xEF && *(filetok+1) == (KMX_CHAR)0xBB && *(filetok+2) == (KMX_CHAR)0xBF) filetok += 3; // I3056 UTF-8 // I3512 + + while(filetok) { + KMX_CHAR str[str_size], *p, *q, *context = NULL; + + if(strlen(filetok) >= str_size) { + delete[] Buf; + // TODO chuck a wobbly + return FALSE; + } + strcpy(str, filetok); + p = strtok_r(str, ";\r", &context); // I3481 + q = strtok_r(nullptr, ";\r", &context); + if(p && q) { kmc_strupr(q); // I3481 // I3641 - long n = strtol(p, NULL, 16); + long n = strtol(p, nullptr, 16); if (*q != '<') { PKMX_WCHAR q0 = strtowstr(q); AddCode_IncludedCodes((int)n, q0); delete[] q0; } } - if(!isEol ) - { - while(fgets(str, str_size, fp)) if(*(strchr(str, 0)-1) == '\n') break; - } + filetok = strtok_r(nullptr, "\n", &filecontext); } - fclose(fp); - - return TRUE; -} - -KMX_BOOL NamedCodeConstants::LoadFile(const KMX_CHAR *filename) -{ - const int buf_size = 260; - KMX_CHAR buf[buf_size]; - // Look in current directory first -- REMOVED AS DANGEROUS - /* strncpy(buf, filename, (buf_size-1)); buf[buf_size-1] = 0; // I3481 - if(kmcmp_FileExists(buf)) - return IntLoadFile(buf); - */ - // Then look in keyboard file directory (CompileDir) - strncpy(buf, CompileDir, (buf_size-1)); buf[buf_size-1] = 0; // I3481 - strncat(buf, filename, (buf_size-1)-strlen(CompileDir)); buf[buf_size-1] = 0; - if(kmcmp_FileExists(buf)) - return IntLoadFile(buf); - - //TODO: sort out how to find common includes in non-Windows platforms: - #ifdef _WINDOWS_ - // Finally look in kmcmpdll.dll directory - GetModuleFileName(0, buf, buf_size); - - KMX_CHAR *p = strrchr_slash(buf); - if(p) - p++; - else - p = buf; - *p = 0; - strncat_s(buf, _countof(buf), filename, (buf_size-1)-strlen(buf)); buf[buf_size-1] = 0; // I3481 // I3641 - if(kmcmp_FileExists(buf)) - return IntLoadFile(buf); - #endif + delete[] Buf; reindex(); - - return FALSE; + return TRUE; } void NamedCodeConstants::reindex() diff --git a/developer/src/kmcmplib/src/NamedCodeConstants.h b/developer/src/kmcmplib/src/NamedCodeConstants.h index 83d707d092cc4535a24cf3676aaa3d2a44d7b859..c537a73361b40f5539a75275fe86787e244b3541 100644 --- a/developer/src/kmcmplib/src/NamedCodeConstants.h +++ b/developer/src/kmcmplib/src/NamedCodeConstants.h @@ -2,6 +2,8 @@ #ifndef NAMEDCODECONSTANTS_H #define NAMEDCODECONSTANTS_H +#include "compfile.h" + #define MAX_ENAME 128 #define ALLOC_SIZE 256 @@ -23,14 +25,13 @@ namespace kmcmp{ int GetCode_IncludedCodes(const KMX_WCHAR *codename); void AddCode_IncludedCodes(int n, const KMX_WCHAR *p); - KMX_BOOL IntLoadFile(const KMX_CHAR *filename); public: NamedCodeConstants(); ~NamedCodeConstants(); void reindex(); void AddCode(int n, const KMX_WCHAR *p, KMX_DWORD storeIndex); - KMX_BOOL LoadFile(const KMX_CHAR *filename); + KMX_BOOL LoadFile(PFILE_KEYBOARD fk, const KMX_WCHAR *filename); int GetCode(const KMX_WCHAR *codename, KMX_DWORD *storeIndex); }; } diff --git a/developer/src/kmcmplib/src/compfile.h b/developer/src/kmcmplib/src/compfile.h index 595d1fececb2cf8b01b25e49a026fda1c7d103ac..c54663aba7659f871ce6271089494aa81420f6d0 100644 --- a/developer/src/kmcmplib/src/compfile.h +++ b/developer/src/kmcmplib/src/compfile.h @@ -123,7 +123,8 @@ typedef FILE_VKDICTIONARY *PFILE_VKDICTIONARY; * Extra metadata for API consumers */ struct FILE_KEYBOARD_EXTRA { - std::u16string kvksFilename; // original TSS_VISUALKEYBOARD value + std::string kmnFilename; // utf-8 + std::u16string kvksFilename; // utf-16, original TSS_VISUALKEYBOARD value }; typedef struct FILE_KEYBOARD_EXTRA* PFILE_KEYBOARD_EXTRA; @@ -197,33 +198,4 @@ struct COMPILEMESSAGES { typedef COMPILEMESSAGES *PCOMPILEMESSAGES; -/* -struct TVersion -{ - //int MinVersion; // 0x0500 usually - //int CompilerVersion[4]; - //int MinCompilerVersion[4]; - int KeyboardVersion; // 0x0500 usually -}; - -extern TVersion FVersionInfo; -*/ - -/* -#define bstrcpy(c,d) (LPBYTE)strcpy((LPSTR)(c),(LPSTR)(d)) -#define bstrlen(c) strlen((LPSTR)(c)) -#define bstrcmp(c,d) strcmp((LPSTR)(c),(LPSTR)(d)) -#define bstrncmp(c,d,n) strncmp((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrnicmp(c,d,n) strnicmp((LPSTR)(c),(LPSTR)(d),(n)) -#define bstricmp(c,d) stricmp((LPSTR)(c),(LPSTR)(d)) -#define bstrchr(c,ch) (LPBYTE)strchr((LPSTR)(c),(char)ch) -#define bstrncpy(c,d,n) (LPBYTE)strncpy((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrtok(c,d) (LPBYTE)strtok((LPSTR)(c),(LPSTR)(d)) -#define bstrcat(c,d) (LPBYTE)strcat((LPSTR)(c),(LPSTR)(d)) -#define bstrncat(c,d,n) (LPBYTE)strncat((LPSTR)(c),(LPSTR)(d),(n)) -#define bstrrev(c) (LPBYTE)strrev((LPSTR)(c)) -#define batoi(c) atoi((LPSTR)(c)) -#define bstrtol(c,d,n) strtol((LPSTR)(c),(LPSTR *)(d),(n)) -*/ - #endif // _COMPFILE_H diff --git a/developer/src/kmcmplib/src/json-validation.cpp b/developer/src/kmcmplib/src/json-validation.cpp deleted file mode 100644 index f58d8c950534943427285a4f400fb39c190217ff..0000000000000000000000000000000000000000 --- a/developer/src/kmcmplib/src/json-validation.cpp +++ /dev/null @@ -1,68 +0,0 @@ - -#include -#include - -#include -#include "kmcompx.h" - -using nlohmann::json; -using nlohmann::json_uri; -using nlohmann::json_schema_draft4::json_validator; - -static void loader(const json_uri &uri, json &schema) -{ - std::fstream lf("." + uri.path()); - if (!lf.good()) - throw std::invalid_argument("could not open " + uri.url() + " tried with " + uri.path()); - - lf >> schema; -} - -EXTERN bool kmcmp_ValidateJsonFile(std::fstream& f, std::fstream& fd, kmcmp_ValidateJsonMessageProc MessageProc, void* context) { - if (!f.good()) { - MessageProc(-1, "Schema file could not be loaded.", context); - return FALSE; - } - - // 1) Read the schema for the document you want to validate - json schema; - try { - f >> schema; - } - catch (std::exception &e) { - MessageProc(f.tellp(), e.what(), context); - return FALSE; - } - - // 2) create the validator and - json_validator validator(loader, [](const std::string &, const std::string &) {}); - - try { - // insert this schema as the root to the validator - // this resolves remote-schemas, sub-schemas and references via the given loader-function - validator.set_root_schema(schema); - } - catch (std::exception &e) { - MessageProc(-2, e.what(), context); - return FALSE; - } - - // 3) do the actual validation of the document - json document; - - if (!fd.good()) { - MessageProc(-3, "Json file could not be loaded.", context); - return FALSE; - } - - try { - fd >> document; - validator.validate(document); - } - catch (std::exception &e) { - MessageProc(fd.tellp(), e.what(), context); - return FALSE; - } - - return TRUE; -} \ No newline at end of file diff --git a/developer/src/kmcmplib/src/kmcmplib.h b/developer/src/kmcmplib/src/kmcmplib.h index 78e945ee0d866af69158dd7a9b1e04e3916a914a..f22c836baeeff7cdf0e72de4a3fb2caab4dfd740 100644 --- a/developer/src/kmcmplib/src/kmcmplib.h +++ b/developer/src/kmcmplib/src/kmcmplib.h @@ -14,7 +14,6 @@ namespace kmcmp { extern KMX_BOOL FMnemonicLayout; extern KMX_BOOL FOldCharPosMatching; extern int CompileTarget; - extern KMX_CHAR CompileDir[260]; // TODO: this should not be a fixed buffer extern int BeginLine[4]; extern int currentLine; extern NamedCodeConstants *CodeConstants; @@ -24,6 +23,7 @@ namespace kmcmp { } extern kmcmp_CompilerMessageProc msgproc; +extern kmcmp_LoadFileProc loadfileproc; extern void* msgprocContext; extern KMX_BOOL AWarnDeprecatedCode_GLOBAL_LIB; @@ -40,10 +40,10 @@ KMX_BOOL AddCompileError(KMX_DWORD msg); PKMX_WCHAR strtowstr(PKMX_STR in); PFILE_STORE FindSystemStore(PFILE_KEYBOARD fk, KMX_DWORD dwSystemID); -FILE* UTF16TempFromUTF8(FILE* fp_in , KMX_BOOL hasPreamble); +bool UTF16TempFromUTF8(KMX_BYTE* infile, int sz, KMX_BYTE** tempfile, int *sz16); KMX_DWORD WriteCompiledKeyboard(PFILE_KEYBOARD fk, KMX_BYTE**data, size_t& dataSize); KMX_DWORD AddStore(PFILE_KEYBOARD fk, KMX_DWORD SystemID, const KMX_WCHAR * str, KMX_DWORD *dwStoreID= NULL); -KMX_DWORD ReadLine(FILE* fp_in , PKMX_WCHAR wstr, KMX_BOOL PreProcess); +KMX_DWORD ReadLine(KMX_BYTE* infile, int sz, int& offset, PKMX_WCHAR wstr, KMX_BOOL PreProcess); KMX_DWORD ParseLine(PFILE_KEYBOARD fk, PKMX_WCHAR str); KMX_DWORD ProcessGroupLine(PFILE_KEYBOARD fk, PKMX_WCHAR p); KMX_DWORD ProcessGroupFinish(PFILE_KEYBOARD fk); diff --git a/developer/src/kmcmplib/src/meson.build b/developer/src/kmcmplib/src/meson.build index 292b158ce276abf20060f38c8770cdbf1f0dcb84..c6ed2b84b01869c07311687fa5445dfed1b9f06d 100644 --- a/developer/src/kmcmplib/src/meson.build +++ b/developer/src/kmcmplib/src/meson.build @@ -7,6 +7,7 @@ # TODO: is this required? It should be Keyman Core only defns += ['-DKMN_KBP_EXPORTING'] version_res = [] +lib_links = [] if cpp_compiler.get_id() == 'gcc' or cpp_compiler.get_id() == 'clang' warns += [ @@ -24,12 +25,10 @@ endif name_suffix = [] if cpp_compiler.get_id() == 'emscripten' - links += ['-lnodefs.js', '-sMODULARIZE', '-sEXPORT_ES6', '--whole-archive', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'cwrap\', \'UTF8ToString\']'] - # tests are building as ES6 so we need to declare the file extension - # note that meson currently struggles with the sanitycheckc_cross.exe - # program, because it has a hard coded extension (.exe) which is not - # valid for node programs in module mode. - name_suffix = '.mjs' + # wasm-exceptions supported in Node 18+, Chrome 95+, Firefox 100+, Safari 15.2+ + flags += ['-fwasm-exceptions'] + lib_links = ['--whole-archive', '-sMODULARIZE', '-sEXPORT_ES6'] + links += ['-fwasm-exceptions', '--bind', '-sEXPORTED_RUNTIME_METHODS=[\'UTF8ToString\']'] endif icu = subproject('icu-for-uset', default_options: [ 'default_library=static', 'cpp_std=c++17', 'warning_level=0', 'werror=false']) @@ -38,36 +37,29 @@ icuuc_dep = icu.get_variable('icuuc_dep') lib = library('kmcmplib', 'CasedKeys.cpp', 'CharToKeyConversion.cpp', + 'CheckForDuplicates.cpp', + 'CheckNCapsConsistency.cpp', + 'CompileKeyboardBuffer.cpp', 'Compiler.cpp', 'CompilerInterfaces.cpp', + 'CompilerInterfacesWasm.cpp', + 'CompMsg.cpp', 'DeprecationChecks.cpp', 'Edition.cpp', - 'filesystem.cpp', - 'json-validation.cpp', + 'kmx_u16.cpp', 'NamedCodeConstants.cpp', + 'UnreachableRules.cpp', + 'uset-api.cpp', 'versioning.cpp', 'virtualcharkeys.cpp', 'xstring.cpp', - 'CharToKeyConversion.cpp', - 'CheckFilenameConsistency.cpp', - 'CheckForDuplicates.cpp', - 'CheckNCapsConsistency.cpp', - 'UnreachableRules.cpp', - 'kmx_u16.cpp', - 'CompMsg.cpp', - 'uset-api.cpp', - '../../../../common/windows/cpp/src/ConvertUTF.c', '../../../../common/windows/cpp/src/crc32.cpp', '../../../../common/windows/cpp/src/vkeys.cpp', - 'xstring.cpp', - '../../ext/json-schema-validator/json-schema-draft4.json.cpp', - '../../ext/json-schema-validator/json-uri.cpp', - '../../ext/json-schema-validator/json-validator.cpp', version_res, cpp_args: defns + warns + flags, - link_args: links, + link_args: links + lib_links, version: meson.project_version(), include_directories: inc, install: true, @@ -81,7 +73,7 @@ if cpp_compiler.get_id() == 'emscripten' host = executable('wasm-host', #'wasm-host.cpp', cpp_args: defns, include_directories: inc, - link_args: links, + link_args: links + lib_links, objects: lib.extract_all_objects(), dependencies: icuuc_dep) endif diff --git a/developer/src/kmcmplib/src/pch.h b/developer/src/kmcmplib/src/pch.h index f878ad11be4f892ad4e9f0ece5864712c6af5c6b..358e28998abc8e65bba25cf2b594ca95cbed5b57 100644 --- a/developer/src/kmcmplib/src/pch.h +++ b/developer/src/kmcmplib/src/pch.h @@ -10,6 +10,4 @@ #include "../../../../common/windows/cpp/include/crc32.h" #include -#include - #include diff --git a/developer/src/kmcmplib/src/uset-api.cpp b/developer/src/kmcmplib/src/uset-api.cpp index 1aa22477b2b63465a8cb0db8b7d624409d9ce083..c2c72cab347587e64998ce7ecfed41614ea8fc49 100644 --- a/developer/src/kmcmplib/src/uset-api.cpp +++ b/developer/src/kmcmplib/src/uset-api.cpp @@ -4,16 +4,13 @@ #include "unicode/uniset.h" #include "unicode/unistr.h" -EXTERN int kmcmp_ParseUnicodeSet( - const char* szText, - uint32_t* outputBuffer, +EXTERN int kmcmp_parseUnicodeSet( + const std::string text, + uintptr_t outputBuffer_, uint32_t outputBufferSize ) { - if (szText == nullptr) { - // null string coming in - return KMCMP_ERROR_SYNTAX_ERR; - } - const icu::UnicodeString str = icu::UnicodeString::fromUTF8(szText); + uint32_t* outputBuffer = reinterpret_cast(outputBuffer_); + const icu::UnicodeString str = icu::UnicodeString::fromUTF8(text.c_str()); if (str.isBogus() || str.isEmpty()) { // empty string return KMCMP_ERROR_SYNTAX_ERR; diff --git a/developer/src/kmcmplib/tests/api-test.cpp b/developer/src/kmcmplib/tests/api-test.cpp index 4c46b1f1a408c166cda98dda0ef43d7449471a1e..cb0337caef680107910d8b219bca8a69171c5a15 100644 --- a/developer/src/kmcmplib/tests/api-test.cpp +++ b/developer/src/kmcmplib/tests/api-test.cpp @@ -18,32 +18,20 @@ #include #include "../src/compfile.h" #include +#include "util_filesystem.h" +#include "util_callbacks.h" void setup(); -void test_kmcmp_CompileKeyboardFile(); -void test_kmcmp_CompileKeyboardFileToBuffer(); - -std::vector error_vec; - -int msgproc(int line, uint32_t dwMsgCode, char* szText, void* context) { - error_vec.push_back(dwMsgCode); - const char*t = "unknown"; - switch(dwMsgCode & 0xF000) { - case CERR_HINT: t=" hint"; break; - case CERR_WARNING: t="warning"; break; - case CERR_ERROR: t=" error"; break; - case CERR_FATAL: t=" fatal"; break; - } - printf("line %d %s %04.4x: %s\n", line, t, (unsigned int)dwMsgCode, szText); - return 1; -} +void test_kmcmp_CompileKeyboard(char *kmn_file); int main(int argc, char *argv[]) { + if(argc < 1) { + puts("Usage: api-test "); + puts("Warning: blank_keyboard will be overwritten"); + return 1; + } setup(); - test_kmcmp_CompileKeyboardFile(); - - setup(); - test_kmcmp_CompileKeyboardFileToBuffer(); + test_kmcmp_CompileKeyboard(argv[1]); return 0; } @@ -52,36 +40,27 @@ void setup() { error_vec.clear(); } -void test_kmcmp_CompileKeyboardFile() { - char kmn_file[L_tmpnam], kmx_file[L_tmpnam]; - tmpnam(kmn_file); - tmpnam(kmx_file); - - // Create an empty file - FILE *fp = fopen(kmn_file, "w"); - fclose(fp); - - // It should fail when a zero-byte file is passed in - assert(!kmcmp_CompileKeyboardFile(kmn_file, kmx_file, true, false, true, msgproc, nullptr)); - assert(error_vec.size() == 1); - assert(error_vec[0] == CERR_CannotReadInfile); - - unlink(kmn_file); -} - -void test_kmcmp_CompileKeyboardFileToBuffer() { - char kmn_file[L_tmpnam], kmx_file[L_tmpnam]; - tmpnam(kmn_file); - tmpnam(kmx_file); +/* + TODO: tests to run: + 4. ANSI (no BOM of course) #8884 + 8. file without blank last line (cannot compare with fixture due to bug in kmcmpdll...) + Hint to add: k004_ansi.kmn: Hint: 10A6 Keyman Developer has detected that the file has ANSI encoding. Consider converting this file to UTF-8 +*/ +void test_kmcmp_CompileKeyboard(char *kmn_file) { // Create an empty file - FILE *fp = fopen(kmn_file, "w"); + FILE *fp = Open_File(kmn_file, "wb"); fclose(fp); - FILE_KEYBOARD fk; - // It should fail when a zero-byte file is passed in - assert(!kmcmp_CompileKeyboardFileToBuffer(kmn_file, &fk, true, false, msgproc, nullptr, CKF_KEYMAN)); + KMCMP_COMPILER_RESULT result; + KMCMP_COMPILER_OPTIONS options; + options.saveDebug = true; + options.compilerWarningsAsErrors = false; + options.warnDeprecatedCode = true; + options.shouldAddCompilerVersion = false; + options.target = CKF_KEYMAN; + assert(!kmcmp_CompileKeyboard(kmn_file, options, msgproc, loadfileProc, nullptr, result)); assert(error_vec.size() == 1); assert(error_vec[0] == CERR_CannotReadInfile); diff --git a/developer/src/kmcmplib/tests/fixtures/adiga_danef.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/adiga_danef.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/adiga_danef.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/adiga_danef.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ahom_star.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ahom_star.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ahom_star.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ahom_star.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/aksarabali_panlex.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/aksarabali_panlex.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/aksarabali_panlex.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/aksarabali_panlex.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/alephwithbeth.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/alephwithbeth.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/alephwithbeth.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/alephwithbeth.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/anii.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/anii.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/anii.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/anii.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/arabic_izza.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/arabic_izza.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/arabic_izza.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/arabic_izza.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/aramaic_hebrew.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/aramaic_hebrew.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/aramaic_hebrew.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/aramaic_hebrew.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/armenian_mnemonic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/armenian_mnemonic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/armenian_mnemonic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/armenian_mnemonic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/armenian_mnemonic_r.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/armenian_mnemonic_r.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/armenian_mnemonic_r.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/armenian_mnemonic_r.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/athinkra_vai.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/athinkra_vai.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/athinkra_vai.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/athinkra_vai.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/athinkra_vai_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/athinkra_vai_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/athinkra_vai_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/athinkra_vai_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ausephon.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ausephon.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ausephon.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ausephon.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_inpage.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_inpage.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_inpage.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_inpage.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_persian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_persian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_persian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_persian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_scientific.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_scientific.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_scientific.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_scientific.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/balochi_urdu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_urdu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/balochi_urdu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/balochi_urdu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bangla_joy.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_joy.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bangla_joy.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_joy.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bangla_munir.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_munir.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bangla_munir.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_munir.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bangla_probhat.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_probhat.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bangla_probhat.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bangla_probhat.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbda1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbda1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbda2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbda2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbda3.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda3.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbda3.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbda3.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdadlm.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdadlm.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdadlm.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdadlm.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdal.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdal.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdal.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdal.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdarme.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarme.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdarme.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarme.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdarmph.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmph.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdarmph.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmph.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdarmty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdarmty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdarmw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdarmw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdarmw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdaze.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdaze.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdaze.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdaze.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdazel.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdazel.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdazel.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdazel.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdazst.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdazst.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdazst.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdazst.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbash.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbash.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbash.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbash.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbe.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbe.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbe.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbe.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbene.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbene.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbene.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbene.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbgph.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbgph.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbgph.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbgph.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbgph1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbgph1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbgph1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbgph1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbhc.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbhc.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbhc.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbhc.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdblr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdblr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdblr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdblr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbug.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbug.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbug.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbug.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdbulg.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbulg.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdbulg.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdbulg.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdca.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdca.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdca.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdca.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcher.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcher.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcher.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcher.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcherp.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcherp.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcherp.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcherp.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcz.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcz.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcz1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcz1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdcz2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdcz2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdcz2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdda.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdda.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdda.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdda.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbddiv1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddiv1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbddiv1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddiv1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbddiv2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddiv2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbddiv2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddiv2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbddv.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddv.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbddv.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddv.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbddzo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddzo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbddzo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbddzo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdes.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdes.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdes.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdes.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdest.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdest.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdest.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdest.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfi1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfi1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfi1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfi1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdfthrk.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfthrk.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdfthrk.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdfthrk.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgae.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgae.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgae.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgae.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgeo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgeo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgeoer.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeoer.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgeoer.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeoer.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgeome.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeome.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgeome.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeome.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgeooa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeooa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgeooa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeooa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgeoqw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeoqw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgeoqw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgeoqw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgkl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgkl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgkl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgkl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgn.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgn.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgn.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgn.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgr1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgr1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgr1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgr1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgrlnd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgrlnd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgrlnd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgrlnd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdgthc.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgthc.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdgthc.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdgthc.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhau.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhau.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhau.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhau.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhaw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhaw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhaw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhaw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhe.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhe.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhe220.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe220.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhe220.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe220.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhe319.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe319.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhe319.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhe319.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdheb.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdheb.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdheb.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdheb.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhebl3.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhebl3.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhebl3.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhebl3.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhela2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhela2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhela2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhela2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhela3.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhela3.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhela3.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhela3.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhept.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhept.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhept.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhept.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdhu1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhu1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdhu1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdhu1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdibo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdibo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdibo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdibo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinasa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinasa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinasa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinasa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinbe2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinbe2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinbe2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinbe2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinben.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinben.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinben.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinben.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdindev.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdindev.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdindev.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdindev.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinen.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinen.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinen.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinen.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinguj.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinguj.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinguj.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinguj.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinhin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinhin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinhin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinhin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinkan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinkan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinkan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinkan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinmal.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinmal.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinmal.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinmal.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinmar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinmar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinmar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinmar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinori.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinori.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinori.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinori.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinpun.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinpun.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinpun.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinpun.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdintam.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdintam.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdintam.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdintam.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdintel.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdintel.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdintel.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdintel.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdinuk2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinuk2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdinuk2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdinuk2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdir.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdir.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdir.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdir.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdit.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdit.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdit.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdit.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdit142.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdit142.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdit142.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdit142.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdiulat.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdiulat.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdiulat.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdiulat.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdjav.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdjav.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdjav.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdjav.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdkaz.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkaz.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdkaz.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkaz.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdkhmr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkhmr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdkhmr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkhmr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdkni.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkni.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdkni.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkni.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdkurd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkurd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdkurd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkurd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdkyr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkyr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdkyr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdkyr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdla.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdla.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdla.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdla.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlao.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlao.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlao.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlao.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlisub.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlisub.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlisub.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlisub.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlisus.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlisus.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlisus.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlisus.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlt.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlt.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlt1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlt1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlt2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlt2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlt2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlv.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlv.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlv.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlv.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlv1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlv1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlv1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlv1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdlvst.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlvst.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdlvst.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdlvst.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmac.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmac.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmac.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmac.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmacst.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmacst.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmacst.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmacst.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmaori.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmaori.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmaori.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmaori.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmlt47.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmlt47.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmlt47.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmlt47.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmlt48.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmlt48.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmlt48.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmlt48.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmon.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmon.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmon.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmon.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmonmo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmonmo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmonmo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmonmo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmonst.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmonst.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmonst.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmonst.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdmyan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmyan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdmyan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdmyan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdne.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdne.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdne.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdne.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdnepr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnepr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdnepr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnepr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdnko.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnko.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdnko.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnko.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdno.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdno.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdno.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdno.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdno1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdno1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdno1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdno1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdnso.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnso.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdnso.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdnso.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdntl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdntl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdntl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdntl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdogham.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdogham.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdogham.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdogham.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdolch.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdolch.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdolch.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdolch.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdoldit.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdoldit.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdoldit.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdoldit.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdosa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdosa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdosa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdosa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdosm.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdosm.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdosm.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdosm.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdpash.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpash.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdpash.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpash.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdphags.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdphags.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdphags.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdphags.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdpl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdpl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdpl1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpl1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdpl1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpl1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdpo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdpo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdpo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdropr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdropr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdropr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdropr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdrost.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdrost.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdrost.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdrost.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdru.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdru.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdru.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdru.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdru1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdru1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdru1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdru1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdrum.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdrum.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdrum.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdrum.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsf.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsf.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsf.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsf.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsg.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsg.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsg.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsg.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsl1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsl1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsl1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsl1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsmsfi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsmsfi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsmsfi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsmsfi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsmsno.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsmsno.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsmsno.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsmsno.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsn1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsn1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsn1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsn1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsora.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsora.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsora.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsora.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsorex.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsorex.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsorex.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsorex.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsors1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsors1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsors1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsors1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsp.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsp.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsp.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsp.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsw09.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsw09.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsw09.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsw09.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsyr1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsyr1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsyr1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsyr1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdsyr2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsyr2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdsyr2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdsyr2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtaile.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtaile.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtaile.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtaile.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtajik.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtajik.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtajik.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtajik.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtam99.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtam99.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtam99.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtam99.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdth0.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth0.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdth0.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth0.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdth1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdth1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdth2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdth2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdth3.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth3.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdth3.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdth3.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtifi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtifi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtifi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtifi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtifi2.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtifi2.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtifi2.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtifi2.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtiprd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtiprd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtiprd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtiprd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtt102.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtt102.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtt102.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtt102.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtuf.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtuf.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtuf.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtuf.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtuq.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtuq.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtuq.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtuq.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdturme.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdturme.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdturme.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdturme.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdtzm.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtzm.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdtzm.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdtzm.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdughr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdughr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdughr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdughr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdughr1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdughr1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdughr1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdughr1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbduk.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbduk.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbduk.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbduk.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdukx.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdukx.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdukx.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdukx.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdur.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdur.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdur.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdur.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdur1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdur1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdur1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdur1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdurdu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdurdu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdurdu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdurdu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdus.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdus.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdus.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdus.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdusa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdusa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdusl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdusl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdusr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdusr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdusx.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusx.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdusx.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdusx.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbduzb.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbduzb.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbduzb.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbduzb.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdvntc.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdvntc.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdvntc.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdvntc.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdwol.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdwol.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdwol.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdwol.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdyak.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdyak.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdyak.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdyak.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdyba.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdyba.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdyba.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdyba.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdycc.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdycc.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdycc.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdycc.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/basic_kbdycl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdycl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/basic_kbdycl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/basic_kbdycl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/batak.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/batak.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/batak.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/batak.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/baybayin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/baybayin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/baybayin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/baybayin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bhaiksuki_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bhaiksuki_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bhaiksuki_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bhaiksuki_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_cree_east.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_cree_east.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_cree_east_james_bay.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east_james_bay.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_cree_east_james_bay.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east_james_bay.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_cree_east_latn.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east_latn.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_cree_east_latn.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_east_latn.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_cree_west_latn.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_west_latn.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_cree_west_latn.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_west_latn.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_cree_woods.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_woods.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_cree_woods.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_cree_woods.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_innu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_innu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_innu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_innu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_innu_phonemic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_innu_phonemic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_innu_phonemic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_innu_phonemic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_mista_wasaha_cree.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_mista_wasaha_cree.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_mista_wasaha_cree.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_mista_wasaha_cree.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_naskapi_classic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_naskapi_classic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_naskapi_classic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_naskapi_classic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_naskapi_common.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_naskapi_common.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_naskapi_common.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_naskapi_common.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bj_oji_cree.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_oji_cree.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bj_oji_cree.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bj_oji_cree.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/brahmi_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/brahmi_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/brahmi_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/brahmi_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/btl_kenya.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/btl_kenya.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/btl_kenya.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/btl_kenya.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/bu_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/bu_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/bu_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/bu_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/buhid.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/buhid.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/buhid.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/buhid.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/burushaski_girminas.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/burushaski_girminas.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/burushaski_girminas.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/burushaski_girminas.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/cabecar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/cabecar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/cabecar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/cabecar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/carian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/carian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/carian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/carian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/chalchiteko.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/chalchiteko.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/chalchiteko.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/chalchiteko.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/chechen_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/chechen_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/chechen_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/chechen_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/chinuk_wawa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/chinuk_wawa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/chinuk_wawa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/chinuk_wawa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/choctaw_modern.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/choctaw_modern.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/choctaw_modern.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/choctaw_modern.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/cim.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/cim.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/cim.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/cim.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/clavbur9.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/clavbur9.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/clavbur9.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/clavbur9.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/colchis_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/colchis_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/colchis_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/colchis_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/colchis_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/colchis_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/colchis_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/colchis_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/common_devanagari.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/common_devanagari.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/common_devanagari.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/common_devanagari.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/coptic_greek.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/coptic_greek.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/coptic_greek.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/coptic_greek.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/coptic_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/coptic_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/coptic_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/coptic_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/dagbani.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/dagbani.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/dagbani.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/dagbani.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/dega.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/dega.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/dega.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/dega.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/dene.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/dene.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/dene.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/dene.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/deseret.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/deseret.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/deseret.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/deseret.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/dogra_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/dogra_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/dogra_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/dogra_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/easy_chakma.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/easy_chakma.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/easy_chakma.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/easy_chakma.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ekwtamil99uni.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ekwtamil99uni.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ekwtamil99uni.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ekwtamil99uni.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_dinka.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_dinka.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_dinka.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_dinka.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_harari_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_harari_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_harari_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_harari_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_naija.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_naija.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_naija.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_naija.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_nuer.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_nuer.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_nuer.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_nuer.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_pasifika.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_pasifika.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_pasifika.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_pasifika.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/el_yolngu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_yolngu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/el_yolngu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/el_yolngu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/embera_north.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/embera_north.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/embera_north.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/embera_north.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/enga.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/enga.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/enga.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/enga.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/enggano.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/enggano.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/enggano.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/enggano.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/english_shavian_igc.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/english_shavian_igc.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/english_shavian_igc.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/english_shavian_igc.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/english_shavian_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/english_shavian_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/english_shavian_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/english_shavian_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/engram.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/engram.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/engram.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/engram.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/esperuni.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/esperuni.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/esperuni.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/esperuni.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/farsiman.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/farsiman.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/farsiman.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/farsiman.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/finongan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/finongan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/finongan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/finongan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fulfulde_ajami_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fulfulde_ajami_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fulfulde_ajami_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fulfulde_ajami_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fulfulde_latin_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fulfulde_latin_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fulfulde_latin_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fulfulde_latin_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_dakelh.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_dakelh.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_dakelh.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_dakelh.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_dane_zaa_zaage.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_dane_zaa_zaage.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_dane_zaa_zaage.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_dane_zaa_zaage.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_denesuline_epsilon.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_denesuline_epsilon.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_denesuline_epsilon.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_denesuline_epsilon.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_diitiidatx.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_diitiidatx.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_diitiidatx.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_diitiidatx.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_gitsenimx.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_gitsenimx.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_gitsenimx.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_gitsenimx.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_gwichin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_gwichin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_gwichin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_gwichin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_hailzaqvla.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_hailzaqvla.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_hailzaqvla.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_hailzaqvla.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_haisla.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_haisla.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_haisla.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_haisla.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_halqemeylem.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_halqemeylem.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_halqemeylem.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_halqemeylem.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_han.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_han.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_han.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_han.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_henqeminem.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_henqeminem.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_henqeminem.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_henqeminem.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_hlgaagilda_xaayda_kil.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_hlgaagilda_xaayda_kil.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_hlgaagilda_xaayda_kil.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_hlgaagilda_xaayda_kil.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_kanienkeha_e.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kanienkeha_e.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_kanienkeha_e.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kanienkeha_e.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_ktunaxa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_ktunaxa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_ktunaxa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_ktunaxa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_kwakwala.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kwakwala.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_kwakwala.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kwakwala.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_kwakwala_liqwala.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kwakwala_liqwala.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_kwakwala_liqwala.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_kwakwala_liqwala.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_migmaq.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_migmaq.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_migmaq.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_migmaq.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_natwits.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_natwits.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_natwits.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_natwits.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_nisgaa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nisgaa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_nisgaa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nisgaa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_nlekepmxcin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nlekepmxcin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_nlekepmxcin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nlekepmxcin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_northern_tutchone.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_northern_tutchone.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_northern_tutchone.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_northern_tutchone.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_nsilxcen.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nsilxcen.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_nsilxcen.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nsilxcen.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_nuucaanul.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nuucaanul.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_nuucaanul.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_nuucaanul.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_secwepemctsin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_secwepemctsin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_secwepemctsin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_secwepemctsin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_sencoten.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_sencoten.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_sencoten.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_sencoten.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_shashishalhem.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_shashishalhem.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_shashishalhem.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_shashishalhem.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_smalgyax.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_smalgyax.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_smalgyax.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_smalgyax.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_southern_tutchone.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_southern_tutchone.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_southern_tutchone.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_southern_tutchone.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_statimcets.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_statimcets.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_statimcets.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_statimcets.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_stlatlimxec.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_stlatlimxec.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_stlatlimxec.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_stlatlimxec.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_tagizi_dene.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tagizi_dene.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_tagizi_dene.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tagizi_dene.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_taltan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_taltan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_taltan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_taltan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_tlingit.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tlingit.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_tlingit.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tlingit.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_tsekehne.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tsekehne.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_tsekehne.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tsekehne.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_tsilhqotin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tsilhqotin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_tsilhqotin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_tsilhqotin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_uwikala.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_uwikala.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_uwikala.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_uwikala.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/fv_xaislakala.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_xaislakala.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/fv_xaislakala.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/fv_xaislakala.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/galaxie_greek_mnemonic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_greek_mnemonic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/galaxie_greek_mnemonic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_greek_mnemonic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/galaxie_greek_positional.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_greek_positional.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/galaxie_greek_positional.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_greek_positional.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/galaxie_hebrew_mnemonic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_hebrew_mnemonic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/galaxie_hebrew_mnemonic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_hebrew_mnemonic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/galaxie_hebrew_positional.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_hebrew_positional.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/galaxie_hebrew_positional.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/galaxie_hebrew_positional.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gandhari.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gandhari.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gandhari.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gandhari.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/geezbrhan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/geezbrhan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/geezbrhan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/geezbrhan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_amh_7.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_amh_7.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_amh_7.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_amh_7.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_amharic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_amharic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_amharic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_amharic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_blin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_blin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_blin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_blin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_ethiopic_7.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_ethiopic_7.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_ethiopic_7.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_ethiopic_7.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_geez.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_geez.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_geez.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_geez.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_gurage.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_gurage.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_gurage.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_gurage.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_gurage_legacy.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_gurage_legacy.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_gurage_legacy.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_gurage_legacy.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_musnad.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_musnad.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_musnad.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_musnad.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_tigrinya_eritrea.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_tigrinya_eritrea.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_tigrinya_eritrea.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_tigrinya_eritrea.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gff_tigrinya_ethiopia.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_tigrinya_ethiopia.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gff_tigrinya_ethiopia.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gff_tigrinya_ethiopia.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ghana.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ghana.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ghana.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ghana.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gilaki.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gilaki.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gilaki.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gilaki.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gilaki_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gilaki_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gilaki_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gilaki_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gondi_dev.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_dev.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gondi_dev.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_dev.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gondi_gunjala.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_gunjala.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gondi_gunjala.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_gunjala.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/gondi_tel.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_tel.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/gondi_tel.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/gondi_tel.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/greekclassical.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/greekclassical.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/greekclassical.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/greekclassical.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hanunoo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hanunoo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hanunoo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hanunoo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/haroi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/haroi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/haroi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/haroi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hatran_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hatran_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hatran_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hatran_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hausa_ajami_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hausa_ajami_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hausa_ajami_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hausa_ajami_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hausa_kano.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hausa_kano.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hausa_kano.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hausa_kano.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hcesar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hcesar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hcesar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hcesar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hieroglyphic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hieroglyphic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hieroglyphic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hieroglyphic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/himyarit_musnad.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/himyarit_musnad.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/himyarit_musnad.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/himyarit_musnad.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/hindi_modular.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/hindi_modular.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/hindi_modular.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/hindi_modular.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/indigenous_nt.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/indigenous_nt.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/indigenous_nt.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/indigenous_nt.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/indonesia.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/indonesia.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/indonesia.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/indonesia.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/indonesian_suku.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/indonesian_suku.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/indonesian_suku.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/indonesian_suku.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/inuktitut_naqittaut.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/inuktitut_naqittaut.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/inuktitut_naqittaut.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/inuktitut_naqittaut.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ishkashimi_cyrillic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ishkashimi_cyrillic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ishkashimi_cyrillic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ishkashimi_cyrillic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_bengali.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_bengali.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_bengali.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_bengali.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_devanagari_hindi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_devanagari_hindi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_devanagari_hindi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_devanagari_hindi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_devanagari_sanskrit_vedic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_devanagari_sanskrit_vedic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_devanagari_sanskrit_vedic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_devanagari_sanskrit_vedic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_gujarati.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_gujarati.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_gujarati.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_gujarati.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_gurmukhi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_gurmukhi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_gurmukhi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_gurmukhi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_odia.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_odia.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_odia.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_odia.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/itrans_roman.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_roman.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/itrans_roman.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/itrans_roman.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/jawa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/jawa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/jawa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/jawa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/jorai.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/jorai.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/jorai.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/jorai.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/karakalpak_cyrillic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/karakalpak_cyrillic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/karakalpak_cyrillic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/karakalpak_cyrillic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/karakalpak_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/karakalpak_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/karakalpak_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/karakalpak_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/kayan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/kayan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/kayan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/kayan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/kbdsn1.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/kbdsn1.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/kbdsn1.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/kbdsn1.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/kharoshthi_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/kharoshthi_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/kharoshthi_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/kharoshthi_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/khmer_advanced.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/khmer_advanced.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/khmer_advanced.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/khmer_advanced.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/khmer_angkor.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/khmer_angkor.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/khmer_angkor.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/khmer_angkor.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/khojki_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/khojki_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/khojki_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/khojki_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/kmhmu_2008.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/kmhmu_2008.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/kmhmu_2008.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/kmhmu_2008.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/koalibrere.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/koalibrere.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/koalibrere.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/koalibrere.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/korean_rr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/korean_rr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/korean_rr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/korean_rr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/koreguaje.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/koreguaje.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/koreguaje.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/koreguaje.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/krung.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/krung.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/krung.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/krung.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lahu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lahu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lahu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lahu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lamkaang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lamkaang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lamkaang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lamkaang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/landuma.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/landuma.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/landuma.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/landuma.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lao_2008_basic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_2008_basic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lao_2008_basic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_2008_basic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lao_2008_rapid.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_2008_rapid.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lao_2008_rapid.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_2008_rapid.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lao_pali.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_pali.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lao_pali.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_pali.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lao_pali_us.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_pali_us.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lao_pali_us.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_pali_us.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lao_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lao_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lao_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/lazuri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/lazuri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/lazuri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/lazuri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/libtralo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/libtralo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/libtralo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/libtralo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/makasar_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/makasar_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/makasar_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/makasar_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/malar_braille.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_braille.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/malar_braille.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_braille.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/malar_malayalam.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_malayalam.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/malar_malayalam.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_malayalam.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/malar_malayalam_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_malayalam_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/malar_malayalam_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_malayalam_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/malar_tirhuta.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_tirhuta.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/malar_tirhuta.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/malar_tirhuta.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/maltese.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/maltese.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/maltese.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/maltese.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mandaic_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mandaic_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mandaic_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mandaic_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/masaram_gondi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/masaram_gondi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/masaram_gondi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/masaram_gondi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/me_en.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/me_en.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/me_en.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/me_en.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/meitei_legacy.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/meitei_legacy.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/meitei_legacy.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/meitei_legacy.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/miluk_hanis_siuslaw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/miluk_hanis_siuslaw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/miluk_hanis_siuslaw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/miluk_hanis_siuslaw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/modi_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/modi_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/modi_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/modi_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mon_anonta.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mon_anonta.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mon_anonta.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mon_anonta.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mon_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mon_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mon_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mon_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mongolian_cyrillic_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mongolian_cyrillic_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mongolian_cyrillic_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mongolian_cyrillic_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mozhi_malayalam.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mozhi_malayalam.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mozhi_malayalam.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mozhi_malayalam.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/mro_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/mro_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/mro_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/mro_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/multani_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/multani_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/multani_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/multani_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/multi_pak_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/multi_pak_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/multi_pak_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/multi_pak_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/munji.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/munji.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/munji.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/munji.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/myancode_san.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/myancode_san.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/myancode_san.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/myancode_san.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nabataean_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nabataean_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nabataean_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nabataean_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/naijatype.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/naijatype.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/naijatype.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/naijatype.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nailangs.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nailangs.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nailangs.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nailangs.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nasa_yuwe.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nasa_yuwe.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nasa_yuwe.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nasa_yuwe.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nepali_traditional.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nepali_traditional.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nepali_traditional.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nepali_traditional.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/newa_romanized.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/newa_romanized.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/newa_romanized.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/newa_romanized.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/newa_traditional.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/newa_traditional.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/newa_traditional.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/newa_traditional.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nias.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nias.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nias.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nias.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nisenan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nisenan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nisenan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nisenan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nko.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nko.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nko.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nko.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nkonya.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nkonya.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nkonya.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nkonya.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_bengali_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_bengali_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_bengali_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_bengali_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_devanagari_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_devanagari_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_devanagari_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_devanagari_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_gujarati_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_gujarati_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_gujarati_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_gujarati_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_gurmukhi_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_gurmukhi_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_gurmukhi_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_gurmukhi_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_ipa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_ipa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_ipa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_ipa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_kannada_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_kannada_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_kannada_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_kannada_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_malayalam_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_malayalam_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_malayalam_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_malayalam_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_oriya_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_oriya_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_oriya_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_oriya_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_tamil_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_tamil_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_tamil_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_tamil_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nlci_telugu_winscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_telugu_winscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nlci_telugu_winscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nlci_telugu_winscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nrc_makah.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nrc_makah.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nrc_makah.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nrc_makah.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ntl_onekey.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ntl_onekey.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ntl_onekey.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ntl_onekey.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/numanggang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/numanggang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/numanggang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/numanggang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/nw_iranian_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/nw_iranian_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/nw_iranian_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/nw_iranian_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/o_tissi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/o_tissi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/o_tissi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/o_tissi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/obolo_chwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/obolo_chwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/obolo_chwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/obolo_chwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/obolo_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/obolo_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/obolo_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/obolo_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/old_hungarian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/old_hungarian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/old_hungarian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/old_hungarian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/old_turkic_udw21_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/old_turkic_udw21_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/old_turkic_udw21_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/old_turkic_udw21_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/orma.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/orma.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/orma.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/orma.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/osage_nation.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/osage_nation.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/osage_nation.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/osage_nation.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/osage_nation_new.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/osage_nation_new.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/osage_nation_new.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/osage_nation_new.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/otoe_missouria.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/otoe_missouria.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/otoe_missouria.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/otoe_missouria.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/persian_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/persian_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/persian_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/persian_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/pid_piaroa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/pid_piaroa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/pid_piaroa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/pid_piaroa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/pingelap.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/pingelap.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/pingelap.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/pingelap.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/postmodern_english_uk_dualstroke.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_uk_dualstroke.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/postmodern_english_uk_dualstroke.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_uk_dualstroke.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/postmodern_english_uk_natural.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_uk_natural.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/postmodern_english_uk_natural.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_uk_natural.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/postmodern_english_us_dualstroke.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_us_dualstroke.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/postmodern_english_us_dualstroke.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_us_dualstroke.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/postmodern_english_us_natural.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_us_natural.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/postmodern_english_us_natural.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/postmodern_english_us_natural.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/pukapuka.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/pukapuka.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/pukapuka.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/pukapuka.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/qom.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/qom.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/qom.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/qom.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/quinault.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/quinault.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/quinault.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/quinault.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/qwerty_farang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/qwerty_farang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/qwerty_farang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/qwerty_farang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_aer.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_aer.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_aer.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_aer.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_arabic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_arabic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_arabic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_arabic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_balti.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_balti.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_balti.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_balti.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_brahui.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_brahui.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_brahui.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_brahui.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_brahui_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_brahui_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_brahui_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_brahui_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_burushaski.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_burushaski.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_burushaski.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_burushaski.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_dameli.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dameli.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_dameli.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dameli.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_dhatki.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dhatki.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_dhatki.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dhatki.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_dogri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dogri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_dogri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_dogri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_gawar_bati.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_gawar_bati.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_gawar_bati.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_gawar_bati.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_gawri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_gawri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_gawri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_gawri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_hazaragi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_hazaragi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_hazaragi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_hazaragi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_hindko.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_hindko.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_hindko.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_hindko.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_indus_kohistani.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_indus_kohistani.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_indus_kohistani.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_indus_kohistani.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_kalasha.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kalasha.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_kalasha.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kalasha.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_kashmir_shina.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kashmir_shina.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_kashmir_shina.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kashmir_shina.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_kashmiri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kashmiri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_kashmiri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_kashmiri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_khowar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_khowar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_khowar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_khowar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_marwari.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_marwari.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_marwari.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_marwari.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_munji.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_munji.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_munji.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_munji.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_oadki.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_oadki.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_oadki.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_oadki.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_ormuri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_ormuri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_ormuri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_ormuri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_pahari.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pahari.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_pahari.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pahari.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_palula.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_palula.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_palula.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_palula.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_parkari_koli.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_parkari_koli.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_parkari_koli.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_parkari_koli.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_pashai.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pashai.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_pashai.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pashai.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_pashto.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pashto.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_pashto.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_pashto.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_saraiki.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_saraiki.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_saraiki.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_saraiki.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_shina.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_shina.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_shina.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_shina.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_sindhi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_sindhi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_sindhi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_sindhi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_torwali.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_torwali.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_torwali.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_torwali.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_urdu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_urdu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_urdu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_urdu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_ushojo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_ushojo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_ushojo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_ushojo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_uyghur.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_uyghur.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_uyghur.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_uyghur.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_wadiyara.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_wadiyara.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_wadiyara.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_wadiyara.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_wakhi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_wakhi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_wakhi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_wakhi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_western_punjabi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_western_punjabi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_western_punjabi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_western_punjabi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rac_yidgha.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_yidgha.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rac_yidgha.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rac_yidgha.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rawang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rawang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rawang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rawang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rejang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rejang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rejang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rejang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/remington_gail.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/remington_gail.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/remington_gail.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/remington_gail.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rohingya_arab.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rohingya_arab.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rohingya_arab.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rohingya_arab.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/rossel.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/rossel.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/rossel.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/rossel.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/runeboard.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/runeboard.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/runeboard.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/runeboard.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/russian_mnemonic_r.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/russian_mnemonic_r.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/russian_mnemonic_r.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/russian_mnemonic_r.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sabdalipi_assamese.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sabdalipi_assamese.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sabdalipi_assamese.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sabdalipi_assamese.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sahaptin_umatilla.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sahaptin_umatilla.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sahaptin_umatilla.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sahaptin_umatilla.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sahaptin_yakima.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sahaptin_yakima.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sahaptin_yakima.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sahaptin_yakima.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sanjha_punjabi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sanjha_punjabi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sanjha_punjabi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sanjha_punjabi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/santali_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/santali_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/santali_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/santali_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/saraiki.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/saraiki.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/saraiki.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/saraiki.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/satere.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/satere.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/satere.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/satere.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/shahmukhi_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/shahmukhi_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/shahmukhi_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/shahmukhi_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/shan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/shan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/shan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/shan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/shaw_2layer.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/shaw_2layer.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/shaw_2layer.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/shaw_2layer.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/siddham_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/siddham_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/siddham_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/siddham_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_akha_act.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_akha_act.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_akha_act.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_akha_act.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_arabic_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_arabic_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_arabic_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_arabic_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_areare.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_areare.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_areare.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_areare.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bari.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bari.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bari.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bari.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bengali_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bengali_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bengali_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bengali_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bolivia.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bolivia.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bolivia.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bolivia.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_boonkit.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_boonkit.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_boonkit.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_boonkit.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_brao.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_brao.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_brao.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_brao.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bru.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bru.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bru.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bru.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_buang.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_buang.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_buang.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_buang.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bunong.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bunong.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bunong.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bunong.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_busa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_busa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_busa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_busa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_bwe_karen.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bwe_karen.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_bwe_karen.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_bwe_karen.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_cameroon_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cameroon_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_cameroon_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cameroon_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_cameroon_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cameroon_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_cameroon_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cameroon_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_cherokee_nation.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cherokee_nation.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_cherokee_nation.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cherokee_nation.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_cheyenne.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cheyenne.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_cheyenne.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cheyenne.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_cipher_music.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cipher_music.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_cipher_music.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_cipher_music.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_devanagari_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_devanagari_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_devanagari_romanized.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_romanized.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_devanagari_romanized.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_romanized.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_devanagari_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_devanagari_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_devanagari_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_dzongkha.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_dzongkha.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_dzongkha.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_dzongkha.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_eastern_congo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_eastern_congo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_eastern_congo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_eastern_congo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_el_ethiopian_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_el_ethiopian_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_el_ethiopian_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_el_ethiopian_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_ethiopic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ethiopic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_ethiopic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ethiopic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_ethiopic_power_g.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ethiopic_power_g.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_ethiopic_power_g.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ethiopic_power_g.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_euro_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_euro_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_euro_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_euro_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_extended_urdu_np.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_extended_urdu_np.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_extended_urdu_np.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_extended_urdu_np.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_greek_polytonic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_greek_polytonic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_greek_polytonic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_greek_polytonic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_hawaiian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hawaiian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_hawaiian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hawaiian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_hebr_grek_trans.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebr_grek_trans.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_hebr_grek_trans.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebr_grek_trans.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_hebrew.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebrew.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_hebrew.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebrew.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_hebrew_legacy.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebrew_legacy.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_hebrew_legacy.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hebrew_legacy.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_hmd_plrd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hmd_plrd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_hmd_plrd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_hmd_plrd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_indic_roman.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_indic_roman.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_indic_roman.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_indic_roman.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_ipa.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ipa.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_ipa.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ipa.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_jarai.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_jarai.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_jarai.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_jarai.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_kayah_kali.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_kali.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_kayah_kali.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_kali.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_kayah_latn.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_latn.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_kayah_latn.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_latn.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_kayah_mymr.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_mymr.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_kayah_mymr.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kayah_mymr.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_khamti.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khamti.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_khamti.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khamti.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_khmer.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khmer.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_khmer.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khmer.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_khowar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khowar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_khowar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_khowar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_kmhmu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kmhmu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_kmhmu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kmhmu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_korda_jamo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korda_jamo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_korda_jamo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korda_jamo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_korda_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korda_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_korda_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korda_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_korean_morse.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korean_morse.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_korean_morse.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_korean_morse.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_kvl_kayaw.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kvl_kayaw.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_kvl_kayaw.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_kvl_kayaw.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_lepcha.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lepcha.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_lepcha.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lepcha.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_limbu_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_limbu_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_limbu_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_limbu_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_limbu_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_limbu_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_limbu_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_limbu_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_lisu_basic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lisu_basic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_lisu_basic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lisu_basic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_lisu_standard.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lisu_standard.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_lisu_standard.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lisu_standard.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_lpo_plrd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lpo_plrd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_lpo_plrd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_lpo_plrd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_madi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_madi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_madi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_madi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_makuri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_makuri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_makuri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_makuri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_mali_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_mali_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_mali_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_mali_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_mali_qwertz.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_qwertz.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_mali_qwertz.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_mali_qwertz.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_moore.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_moore.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_moore.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_moore.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_myanmar_my3.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_myanmar_my3.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_myanmar_my3.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_myanmar_my3.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_myanmar_mywinext.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_myanmar_mywinext.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_myanmar_mywinext.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_myanmar_mywinext.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_nigeria_dot.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_dot.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_nigeria_dot.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_dot.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_nigeria_odd_vowels.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_odd_vowels.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_nigeria_odd_vowels.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_odd_vowels.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_nigeria_underline.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_underline.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_nigeria_underline.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nigeria_underline.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_nko.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nko.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_nko.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nko.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_nubian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nubian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_nubian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_nubian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_pan_africa_mnemonic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_pan_africa_mnemonic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_pan_africa_mnemonic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_pan_africa_mnemonic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_pan_africa_positional.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_pan_africa_positional.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_pan_africa_positional.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_pan_africa_positional.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_philippines.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_philippines.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_philippines.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_philippines.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_sahu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_sahu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_sahu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_sahu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_bsc_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_bsc_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_bsc_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_bsc_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_cou_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_cou_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_cou_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_cou_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_dyo_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_dyo_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_dyo_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_dyo_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_gsl_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_gsl_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_gsl_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_gsl_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_krx_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_krx_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_krx_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_krx_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_ndv_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_ndv_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_ndv_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_ndv_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_sav_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_sav_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_sav_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_sav_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_snf_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_snf_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_snf_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_snf_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_srr_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_srr_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_srr_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_srr_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_senegal_wo_azerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_wo_azerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_senegal_wo_azerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_senegal_wo_azerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_sgaw_karen.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_sgaw_karen.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_sgaw_karen.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_sgaw_karen.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_shan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_shan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_shan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_shan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tai_dam.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tai_dam.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tai_dam_lao.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_lao.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tai_dam_lao.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_lao.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tai_dam_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tai_dam_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tai_dam_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tai_dam_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tai_dam_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tawallammat.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tawallammat.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tawallammat.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tawallammat.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tchad.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tchad.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tchad.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tchad.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tepehuan.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tepehuan.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tepehuan.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tepehuan.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_torwali.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_torwali.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_torwali.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_torwali.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_tunisian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tunisian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_tunisian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_tunisian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_uganda_tanzania.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_uganda_tanzania.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_uganda_tanzania.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_uganda_tanzania.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_vai.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_vai.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_vai.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_vai.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_wayuu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_wayuu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_wayuu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_wayuu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_ygp_plrd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ygp_plrd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_ygp_plrd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ygp_plrd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yna_plrd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yna_plrd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yna_plrd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yna_plrd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yoruba8.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba8.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yoruba8.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba8.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yoruba_bar.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba_bar.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yoruba_bar.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba_bar.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yoruba_dot.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba_dot.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yoruba_dot.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yoruba_dot.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yupik_cyrillic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yupik_cyrillic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yupik_cyrillic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yupik_cyrillic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_yupik_cyrillic_ru.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yupik_cyrillic_ru.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_yupik_cyrillic_ru.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_yupik_cyrillic_ru.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sil_ywq_plrd.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ywq_plrd.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sil_ywq_plrd.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sil_ywq_plrd.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/slc_saliba.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/slc_saliba.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/slc_saliba.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/slc_saliba.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/soqotri_arabic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/soqotri_arabic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/soqotri_arabic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/soqotri_arabic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/srr_ajami_qwerty.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/srr_ajami_qwerty.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/srr_ajami_qwerty.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/srr_ajami_qwerty.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sundanese.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sundanese.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sundanese.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sundanese.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sundanese_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sundanese_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sundanese_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sundanese_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/swanalekha_malayalam.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/swanalekha_malayalam.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/swanalekha_malayalam.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/swanalekha_malayalam.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sxava.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sxava.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sxava.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sxava.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sxava_eo.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sxava_eo.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sxava_eo.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sxava_eo.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/sylheti_nagri.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/sylheti_nagri.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/sylheti_nagri.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/sylheti_nagri.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/syriac_arabic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/syriac_arabic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/syriac_arabic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/syriac_arabic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/syriac_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/syriac_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/syriac_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/syriac_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tagbanwa_inscript.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tagbanwa_inscript.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tagbanwa_inscript.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tagbanwa_inscript.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/taigi_poj.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/taigi_poj.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/taigi_poj.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/taigi_poj.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tainua.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tainua.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tainua.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tainua.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tangsa_lakhum.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tangsa_lakhum.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tangsa_lakhum.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tangsa_lakhum.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tawallammat_latin.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tawallammat_latin.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tawallammat_latin.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tawallammat_latin.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/teggargrent_lat.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/teggargrent_lat.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/teggargrent_lat.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/teggargrent_lat.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tem_kdh.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tem_kdh.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tem_kdh.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tem_kdh.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/thamizha_anjal_paangu.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_anjal_paangu.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/thamizha_anjal_paangu.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_anjal_paangu.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/thamizha_bamini.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_bamini.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/thamizha_bamini.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_bamini.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/thamizha_new_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_new_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/thamizha_new_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_new_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/thamizha_tamil99_ext.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_tamil99_ext.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/thamizha_tamil99_ext.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/thamizha_tamil99_ext.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tibetan_direct_input.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tibetan_direct_input.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tibetan_direct_input.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tibetan_direct_input.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tibetan_ewts.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tibetan_ewts.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tibetan_ewts.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tibetan_ewts.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tirhuta.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tirhuta.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tirhuta.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tirhuta.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tlahuica.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tlahuica.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tlahuica.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tlahuica.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tsakonian.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tsakonian.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tsakonian.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tsakonian.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/tuareg_tifinagh.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/tuareg_tifinagh.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/tuareg_tifinagh.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/tuareg_tifinagh.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/turkmen_cyrl.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/turkmen_cyrl.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/turkmen_cyrl.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/turkmen_cyrl.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/txo_toto.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/txo_toto.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/txo_toto.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/txo_toto.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/udi_keyboard.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/udi_keyboard.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/udi_keyboard.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/udi_keyboard.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/ukwuani.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/ukwuani.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/ukwuani.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/ukwuani.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/uma_graphic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/uma_graphic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/uma_graphic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/uma_graphic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/uma_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/uma_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/uma_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/uma_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/urdu_phonetic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/urdu_phonetic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/urdu_phonetic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/urdu_phonetic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/urdu_phonetic_crulp.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/urdu_phonetic_crulp.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/urdu_phonetic_crulp.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/urdu_phonetic_crulp.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/venetia_et_histria.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/venetia_et_histria.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/venetia_et_histria.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/venetia_et_histria.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/vm_tamil_modular.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/vm_tamil_modular.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/vm_tamil_modular.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/vm_tamil_modular.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/vm_tamil_typewriter.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/vm_tamil_typewriter.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/vm_tamil_typewriter.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/vm_tamil_typewriter.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/wakhi_anglicized.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_anglicized.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/wakhi_anglicized.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_anglicized.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/wakhi_cyrillic.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_cyrillic.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/wakhi_cyrillic.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_cyrillic.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/wakhi_standard.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_standard.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/wakhi_standard.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/wakhi_standard.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/wancho.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/wancho.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/wancho.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/wancho.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/warang_citi.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/warang_citi.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/warang_citi.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/warang_citi.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/wolofal.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/wolofal.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/wolofal.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/wolofal.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/xinaliq.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/xinaliq.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/xinaliq.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/xinaliq.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/yiddish_pasekh.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/yiddish_pasekh.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/yiddish_pasekh.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/yiddish_pasekh.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/yidgha.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/yidgha.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/yidgha.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/yidgha.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/younger_futhark_short_twig.kmx b/developer/src/kmcmplib/tests/fixtures/keyboards-repo/younger_futhark_short_twig.kmx similarity index 100% rename from developer/src/kmcmplib/tests/fixtures/younger_futhark_short_twig.kmx rename to developer/src/kmcmplib/tests/fixtures/keyboards-repo/younger_futhark_short_twig.kmx diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat new file mode 100644 index 0000000000000000000000000000000000000000..5e8b891ea6cf163c1e4dce6c089a835c6b153e78 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/compile_legacy.bat @@ -0,0 +1,4 @@ +@echo off +echo Compiles the keyboards using the legacy kmcomp.exe +echo to use as baseline comparisons for kmcmplib +for %%d in (*.kmn) do ..\..\..\..\..\bin\kmcomp -no-compiler-version -d %%d \ No newline at end of file diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmn new file mode 100644 index 0000000000000000000000000000000000000000..2b3180c36ab4b473e74b1b3487fc8f0a61d623f6 Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmn differ diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmx new file mode 100644 index 0000000000000000000000000000000000000000..13ea2a686e09c16db68ff7bb355e114b9125d76d Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k001_utf16.kmx differ diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmn new file mode 100644 index 0000000000000000000000000000000000000000..da6d4e70873e5762dd49a41d13721abebb5ba668 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmn @@ -0,0 +1,11 @@ +c Description: Verifies that kmcmplib can compile a UTF-8 file (without BOM) + +store(&NAME) 'k002_utf8_without_bom' +store(&VERSION) '9.0' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmx new file mode 100644 index 0000000000000000000000000000000000000000..0b9c9294182d9387165c98e1ffc84c8fe45452e3 Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k002_utf8_without_bom.kmx differ diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmn new file mode 100644 index 0000000000000000000000000000000000000000..b2bccb036caad4d891ef62c5bc31c02ebaaffddd --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmn @@ -0,0 +1,11 @@ +c Description: Verifies that kmcmplib can compile a UTF-8 file (with BOM) + +store(&NAME) 'k003_utf8_with_bom' +store(&VERSION) '9.0' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmx new file mode 100644 index 0000000000000000000000000000000000000000..ee1cf4225502e3c7944b58f301e5be649af4ec00 Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k003_utf8_with_bom.kmx differ diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmn new file mode 100644 index 0000000000000000000000000000000000000000..50a1b31ea1c7b4d369e66dc69021be22f9cdeacb --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmn @@ -0,0 +1,13 @@ +c Description: Verifies that kmcmplib can compile an ANSI file +c This has some high-ascii letters in cp1252 to ensure that +c it fails to load as 'utf8 without bom' + +store(&NAME) 'k004_ansi' +store(&VERSION) '9.0' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ÀÐ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmx new file mode 100644 index 0000000000000000000000000000000000000000..6c41da979cb2a3ec20aadf6de0ca778fb8ce5ec1 Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k004_ansi.kmx differ diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.bmp b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.bmp new file mode 100644 index 0000000000000000000000000000000000000000..509b89ae6df7f09a442ae60f215a6c7b3211b34f Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.bmp differ diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmn new file mode 100644 index 0000000000000000000000000000000000000000..926ccdeeafbf5bf648903d0bdc530a5e0ffa9ce9 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmcmplib can load a bitmap without file extension + +store(&NAME) 'k005_bitmap' +store(&VERSION) '9.0' +store(&BITMAP) 'k005_bitmap' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmx new file mode 100644 index 0000000000000000000000000000000000000000..bc9bc9b671724050e169e59fce976a18d54a030e Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k005_bitmap.kmx differ diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.ico b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.ico new file mode 100644 index 0000000000000000000000000000000000000000..e1b64cf9013e6702420d97fd88b120490f33b137 Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.ico differ diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmn new file mode 100644 index 0000000000000000000000000000000000000000..4ad3e61659683213ddc6b44a0454c87cc7576d11 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmcmplib can load an icon with file extension + +store(&NAME) 'k006_icon' +store(&VERSION) '9.0' +store(&BITMAP) 'k006_icon.ico' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > 'a' +'a' + [K_B] > 'ážáŸ’មែរ' diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmx new file mode 100644 index 0000000000000000000000000000000000000000..f994a1dcac9e5ae797eb88eed9cee4b0e6f85b00 Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k006_icon.kmx differ diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmn new file mode 100644 index 0000000000000000000000000000000000000000..71d72d5120b522cd20303f3d0843e4720791230c --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmcmplib can load an includecodes file with \r\n line endings + +store(&NAME) 'k007_includecodes_r_n' +store(&VERSION) '9.0' +store(&includecodes) 'k007_includecodes_r_n.txt' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > $LOWER_A +$LOWER_A + [K_B] > $LOWER_B diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmx new file mode 100644 index 0000000000000000000000000000000000000000..f8b4eac1b0d9103c7767e487b5109a06aed8e1ec Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.kmx differ diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.txt b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.txt new file mode 100644 index 0000000000000000000000000000000000000000..4cc3fe4351ddeb205cdd985d04648ec403065e35 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k007_includecodes_r_n.txt @@ -0,0 +1,2 @@ +0061;LOWER_A +0062;LOWER_B diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmn new file mode 100644 index 0000000000000000000000000000000000000000..ea357b4b0ef6fc8331efb7f63aca9e3fc9291652 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmn @@ -0,0 +1,12 @@ +c Description: Verifies that kmcmplib can load an includecodes file with \n line endings + +store(&NAME) 'k008_includecodes_n' +store(&VERSION) '9.0' +store(&includecodes) 'k008_includecodes_n.txt' + +begin unicode > use(main) + +group(main) using keys + ++ [K_A] > $LOWER_A +$LOWER_A + [K_B] > $LOWER_B diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmx new file mode 100644 index 0000000000000000000000000000000000000000..aa158cdd546cdde915e4a55c1db521e57d4abb04 Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.kmx differ diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.txt b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.txt new file mode 100644 index 0000000000000000000000000000000000000000..4cc3fe4351ddeb205cdd985d04648ec403065e35 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k008_includecodes_n.txt @@ -0,0 +1,2 @@ +0061;LOWER_A +0062;LOWER_B diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmn b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmn new file mode 100644 index 0000000000000000000000000000000000000000..a0ad9d0d9fbc820432982d13455f5a02acda4018 --- /dev/null +++ b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmn @@ -0,0 +1,31 @@ +c Description: Verifies that kmcmplib can deal with long lines continued with backslashes, and also comments + +store(&NAME) 'k009_long_lines' +store(&VERSION) '9.0' + +begin unicode > use(main) + +group(main) using keys + +c +c ***Note*** This file has intentional whitespace at the end of line ending in [SHIFT K_N] +c in order to verify that line continuations are working correctly +c + +store(c_key) [K_K] [K_X] [SHIFT K_K] [SHIFT K_X] [K_G] \ + [K_C] [K_Q] [SHIFT K_C] [SHIFT K_Q] [SHIFT K_J] \ + [K_D] [K_Z] [SHIFT K_D] [SHIFT K_Z] [SHIFT K_N] \ + [K_T] [K_F] [SHIFT K_T] [SHIFT K_F] [K_N] \ + [K_B] [K_P] [SHIFT K_B] [SHIFT K_P] [K_M] \ + [K_Y] [K_R] [K_L] [K_V] [K_S] [K_H] [SHIFT K_L] [SHIFT K_G] \ + [RALT K_K] [RALT K_B] +store(c_out) U+1780 U+1781 U+1782 U+1783 U+1784 \ + U+1785 U+1786 U+1787 U+1788 U+1789 \ + U+178A U+178B U+178C U+178D U+178E \ + U+178F U+1790 U+1791 U+1792 U+1793 \ + U+1794 U+1795 U+1796 U+1797 U+1798 \ + U+1799 U+179A U+179B U+179C U+179F U+17A0 U+17A1 U+17A2 \ + U+179D U+179E c deprecated, but they are used in minority languages + ++ any(c_key) > index(c_out, 1) + diff --git a/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmx b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmx new file mode 100644 index 0000000000000000000000000000000000000000..4acd4cc051adc10d35c6ae842b24be5cf43f0cf4 Binary files /dev/null and b/developer/src/kmcmplib/tests/fixtures/valid-keyboards/k009_long_lines.kmx differ diff --git a/developer/src/kmcmplib/tests/kmcompxtest.cpp b/developer/src/kmcmplib/tests/kmcompxtest.cpp index 1ce542917d389df52ad136047d9b7ae09d15d9dd..5dbaee88bc9990af9513c8ffd84882250c25d92d 100644 --- a/developer/src/kmcmplib/tests/kmcompxtest.cpp +++ b/developer/src/kmcmplib/tests/kmcompxtest.cpp @@ -13,6 +13,8 @@ #include #include #include +#include "util_filesystem.h" +#include "util_callbacks.h" #ifdef _MSC_VER #else @@ -21,29 +23,11 @@ using namespace std; -vector < int > error_vec; - #define CERR_FATAL 0x00008000 #define CERR_ERROR 0x00004000 #define CERR_WARNING 0x00002000 #define CERR_HINT 0x00001000 -int msgproc(int line, uint32_t dwMsgCode, char* szText, void* context) -{ - error_vec.push_back(dwMsgCode); - const char*t = "unknown"; - switch(dwMsgCode & 0xF000) { - case CERR_HINT: t=" hint"; break; - case CERR_WARNING: t="warning"; break; - case CERR_ERROR: t=" error"; break; - case CERR_FATAL: t=" fatal"; break; - } - printf("line %d %s %04.4x: %s\n", line, t, (unsigned int)dwMsgCode, szText); - return 1; -} - -#include "../src/filesystem.h" - int main(int argc, char *argv[]) { if(argc < 4) { @@ -71,14 +55,15 @@ int main(int argc, char *argv[]) char first5[6] = "CERR_"; char* pfirst5 = first5; - KMCMP_COMPILER_OPTIONS kcopts; - kcopts.dwSize = sizeof(KMCMP_COMPILER_OPTIONS); - kcopts.ShouldAddCompilerVersion = false; // So we can compare against existing compiled keyboards that also don't have compiler version - if(!kmcmp_SetCompilerOptions(&kcopts)) { - return __LINE__; - } + KMCMP_COMPILER_RESULT result; + KMCMP_COMPILER_OPTIONS options; + options.saveDebug = true; + options.compilerWarningsAsErrors = false; + options.warnDeprecatedCode = true; + options.shouldAddCompilerVersion = false; + options.target = CKF_KEYMAN; - if(kmcmp_CompileKeyboardFile(kmn_file, kmx_file, true, false, true, msgproc, nullptr)) { + if(kmcmp_CompileKeyboard(kmn_file, options, msgproc, loadfileProc, nullptr, result)) { char* testname = strrchr( (char*) kmn_file, '/') + 1; if(strncmp(testname, pfirst5, 5) == 0){ return __LINE__; // exit code: CERR_ in Name + no Error found @@ -87,26 +72,24 @@ int main(int argc, char *argv[]) // On non-win32 platforms, we cannot get kmcmpdll.dll to build keyboards // legacy-mode, so we'll compare to a hopefully existing file that we've // been passed - FILE* fp1 = Open_File(kmx_file, "rb"); + FILE* fp1 = Open_File(kmx_file, "wb"); if(!fp1) return __LINE__; + // Write out for reference + fwrite(result.kmx, 1, result.kmxSize, fp1); + FILE* fp2 = Open_File(reference_kmx, "rb"); if(!fp2) return __LINE__; // exit code: fail if no reference kmx file in build-folder - fseek(fp1, 0, SEEK_END); - auto sz1 = ftell(fp1); - fseek(fp1, 0, SEEK_SET); fseek(fp2, 0, SEEK_END); auto sz2 = ftell(fp2); fseek(fp2, 0, SEEK_SET); - if (sz1 != sz2) return __LINE__; // exit code: size of kmx-file in build differs from size of kmx-file in source folder + if (result.kmxSize != sz2) return __LINE__; // exit code: size of kmx-file in build differs from size of kmx-file in source folder - char* buf1 = new char[sz1]; - char* buf2 = new char[sz1]; - fread(buf1, 1, sz1, fp1); - fread(buf2, 1, sz1, fp2); - return memcmp(buf1, buf2, sz1) ? __LINE__ : 0; // exit code: when contents of kmx-file in build differs from contents of kmx-file in source folder + char* buf2 = new char[result.kmxSize]; + fread(buf2, 1, result.kmxSize, fp2); + return memcmp(result.kmx, buf2, result.kmxSize) ? __LINE__ : 0; // exit code: when contents of kmx-file in build differs from contents of kmx-file in source folder // success: when contents of kmx-file in build and source folder are the same } else { /*if Errors found: check number (e.g. CERR_4061_balochi_phonetic.kmn should produce Error 4061)*/ diff --git a/developer/src/kmcmplib/tests/meson.build b/developer/src/kmcmplib/tests/meson.build index b81429806e16a3d414c2626c34fba9caa4d2bc12..a048bc8236b61803f5f16ef5f91bad7b7e56eae0 100644 --- a/developer/src/kmcmplib/tests/meson.build +++ b/developer/src/kmcmplib/tests/meson.build @@ -6,16 +6,20 @@ fs = import('fs') -tests_flags = [] +tests_links = [] + +if cpp_compiler.get_id() == 'emscripten' + tests_links += ['-lnodefs.js'] +endif input_path = meson.current_source_dir() / '../../../../common/test/keyboards/baseline' output_path = meson.current_build_dir() -kmcompxtest = executable('kmcompxtest', 'kmcompxtest.cpp', - cpp_args: defns, +kmcompxtest = executable('kmcompxtest', ['kmcompxtest.cpp','util_filesystem.cpp','util_callbacks.cpp'], + cpp_args: defns + flags, include_directories: inc, name_suffix: name_suffix, - link_args: links + tests_flags, + link_args: links + tests_links, objects: lib.extract_all_objects(), dependencies: icuuc_dep, ) @@ -76,6 +80,26 @@ foreach kbd : tests test(kbd, kmcompxtest, args: [kbd_src, kbd_obj, join_paths(input_path, kbd) + '.kmx']) endforeach +valid_keyboard_tests = [ + 'k001_utf16', + 'k002_utf8_without_bom', + 'k003_utf8_with_bom', + # 'k004_ansi', # TODO: enable ansi test when we have the icu datafiles #8884 + 'k005_bitmap', + 'k006_icon', + 'k007_includecodes_r_n', + 'k008_includecodes_n', + 'k009_long_lines', +] + +fixtures_path = meson.current_source_dir() / 'fixtures/valid-keyboards' + +foreach kbd : valid_keyboard_tests + kbd_src = join_paths(fixtures_path, kbd) + '.kmn' + kbd_obj = join_paths(output_path, kbd) + '.kmx' + test(kbd, kmcompxtest, args: [kbd_src, kbd_obj, join_paths(fixtures_path, kbd) + '.kmx']) +endforeach + # Test fixtures that come from keyboards repo -- but only for a "full" test, # which typically we run on CI no more than once a day, because it's expensive. @@ -96,7 +120,7 @@ if get_option('full_test') # kbd is going to be an absolute path kbd_obj = output_path / test_basename + '.kmx' - reference_kmx = meson.current_source_dir() / 'fixtures' / test_basename + '.kmx' + reference_kmx = meson.current_source_dir() / 'fixtures/keyboards-repo' / test_basename + '.kmx' if fs.is_file(reference_kmx) test(test_basename, kmcompxtest, args: [kbd, kbd_obj, reference_kmx]) @@ -107,22 +131,22 @@ endif # Test the API endpoints -apitest = executable('api-test', 'api-test.cpp', - cpp_args: defns, +apitest = executable('api-test', ['api-test.cpp','util_filesystem.cpp','util_callbacks.cpp'], + cpp_args: defns + flags, include_directories: inc, name_suffix: name_suffix, - link_args: links + tests_flags, + link_args: links + tests_links, objects: lib.extract_all_objects(), - dependencies: icuuc_dep, + dependencies: icuuc_dep ) -test('api-test', apitest) +test('api-test', apitest, args: [output_path / 'blank_keyboard.kmx']) usetapitest = executable('uset-api-test', 'uset-api-test.cpp', - cpp_args: defns, + cpp_args: defns + flags, include_directories: inc, name_suffix: name_suffix, - link_args: links + tests_flags, + link_args: links + tests_links, objects: lib.extract_all_objects(), dependencies: icuuc_dep, ) diff --git a/developer/src/kmcmplib/tests/prep.sh b/developer/src/kmcmplib/tests/prep.sh index 058d4a1b9b0bfdd50fe13d29bcbdff7af508bd3c..8ad9b81171f165903fe74d4b7eb47f6f39154800 100755 --- a/developer/src/kmcmplib/tests/prep.sh +++ b/developer/src/kmcmplib/tests/prep.sh @@ -39,30 +39,30 @@ if [[ ! -d "$KEYBOARDS_ROOT/.git" ]] || [[ ! -f "$KEYBOARDS_ROOT/tools/regressio fi if builder_start_action clean; then - rm -rf "$THIS_SCRIPT_PATH/fixtures" + rm -rf "$THIS_SCRIPT_PATH/fixtures/keyboards-repo" rm -f "$THIS_SCRIPT_PATH/keyboards_commit_ref.txt" builder_finish_action success clean fi if builder_start_action build; then # yes, remove existing fixtures before build so we don't end up with stale fixtures - rm -rf "$THIS_SCRIPT_PATH/fixtures" + rm -rf "$THIS_SCRIPT_PATH/fixtures/keyboards-repo" # record the revision of the keyboards repo pushd "$KEYBOARDS_ROOT" > /dev/null git rev-parse head > "$THIS_SCRIPT_PATH/keyboards_commit_ref.txt" popd > /dev/null - "$KEYBOARDS_ROOT/tools/regression.sh" --use-legacy-compiler --local "$KEYMAN_ROOT/developer/bin/" --output "$THIS_SCRIPT_PATH/fixtures/" + "$KEYBOARDS_ROOT/tools/regression.sh" --use-legacy-compiler --local "$KEYMAN_ROOT/developer/bin/" --output "$THIS_SCRIPT_PATH/fixtures/keyboards-repo/" cd "$THIS_SCRIPT_PATH" # We don't need visual keyboards - rm ./fixtures/*.kvk + rm ./fixtures/keyboards-repo/*.kvk # Nor do we need kmw outputs - rm ./fixtures/*.js + rm ./fixtures/keyboards-repo/*.js # Finally, we remove any keyboards over 500kb, as we won't automatically test them for now # TODO: consider if we want to keep those large keyboards in the future - find ./fixtures/ -size +500k -exec rm {} \+ + find ./fixtures/keyboards-repo/ -size +500k -exec rm {} \+ builder_finish_action success build fi \ No newline at end of file diff --git a/developer/src/kmcmplib/tests/uset-api-test.cpp b/developer/src/kmcmplib/tests/uset-api-test.cpp index 4fe599ca2e83a1af9d50d61368bafa4b337f3c82..61d263ad157cc365d885c463bc6bc200a893add7 100644 --- a/developer/src/kmcmplib/tests/uset-api-test.cpp +++ b/developer/src/kmcmplib/tests/uset-api-test.cpp @@ -19,30 +19,32 @@ #include "../src/compfile.h" #include -void test_kmcmp_ParseUnicodeSetProc(); +void test_kmcmp_parseUnicodeSet(); // std::vector error_vec; int main(int argc, char *argv[]) { - test_kmcmp_ParseUnicodeSetProc(); + test_kmcmp_parseUnicodeSet(); return 0; } -void test_kmcmp_ParseUnicodeSetProc() { +void test_kmcmp_parseUnicodeSet() { { // null test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[]", buf_, bufsiz); assert(rc == KMCMP_USET_OK); } { // basic test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[x A-C]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[x A-C]", buf_, bufsiz); assert(rc == 2); assert(buf[0] == 0x41); assert(buf[1] == 0x43); @@ -53,7 +55,8 @@ void test_kmcmp_ParseUnicodeSetProc() { // bigger test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[[🙀A-C]-[CB]]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[[🙀A-C]-[CB]]", buf_, bufsiz); assert(rc == 2); assert(buf[0] == 0x41); assert(buf[1] == 0x41); @@ -64,35 +67,40 @@ void test_kmcmp_ParseUnicodeSetProc() { // overflow test const auto bufsiz = 1; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[x A-C]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[x A-C]", buf_, bufsiz); assert(rc == KMCMP_FATAL_OUT_OF_RANGE); } { // err test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[:Adlm:]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[:Adlm:]", buf_, bufsiz); assert(rc == KMCMP_ERROR_UNSUPPORTED_PROPERTY); } { // err test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[[\\p{Mn}]&[A-Z]]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[[\\p{Mn}]&[A-Z]]", buf_, bufsiz); assert(rc == KMCMP_ERROR_UNSUPPORTED_PROPERTY); } { // err test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[abc{def}]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[abc{def}]", buf_, bufsiz); assert(rc == KMCMP_ERROR_HAS_STRINGS); } { // err test const auto bufsiz = 128; uint32_t buf[bufsiz]; - int rc = kmcmp_ParseUnicodeSet(u8"[[]", buf, bufsiz); + uintptr_t buf_ = reinterpret_cast(buf); + int rc = kmcmp_parseUnicodeSet(u8"[[]", buf_, bufsiz); assert(rc == KMCMP_ERROR_SYNTAX_ERR); } } diff --git a/developer/src/kmcmplib/tests/util_callbacks.cpp b/developer/src/kmcmplib/tests/util_callbacks.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e610a54c09e8beb6974675d23deb0cc5199140d6 --- /dev/null +++ b/developer/src/kmcmplib/tests/util_callbacks.cpp @@ -0,0 +1,59 @@ +#include +#include +#include +#include "util_filesystem.h" +#include "../src/compfile.h" +#include + +std::vector error_vec; + +int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context) { + error_vec.push_back(dwMsgCode); + const char*t = "unknown"; + switch(dwMsgCode & 0xF000) { + case CERR_HINT: t=" hint"; break; + case CERR_WARNING: t="warning"; break; + case CERR_ERROR: t=" error"; break; + case CERR_FATAL: t=" fatal"; break; + } + printf("line %d %s %04.4x: %s\n", line, t, (unsigned int)dwMsgCode, szText); + return 1; +} + +bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context) { + std::string resolvedFilename = filename; + if(baseFilename && *baseFilename && IsRelativePath(filename)) { + char* p; + if ((p = strrchr_slash((char*)baseFilename)) != nullptr) { + std::string basePath = std::string(baseFilename, (int)(p - baseFilename + 1)); + resolvedFilename = basePath; + resolvedFilename.append(filename); + } + } + + FILE* fp = Open_File(resolvedFilename.c_str(), "rb"); + if(!fp) { + return false; + } + + if(!data) { + // return size + if(fseek(fp, 0, SEEK_END) != 0) { + fclose(fp); + return false; + } + *size = ftell(fp); + if(*size == -1L) { + fclose(fp); + return false; + } + } else { + // return data + if(fread(data, 1, *size, fp) != *size) { + fclose(fp); + return false; + } + } + fclose(fp); + return true; +} \ No newline at end of file diff --git a/developer/src/kmcmplib/tests/util_callbacks.h b/developer/src/kmcmplib/tests/util_callbacks.h new file mode 100644 index 0000000000000000000000000000000000000000..345de64b9f484b568f28c1652e800ee20e7307ee --- /dev/null +++ b/developer/src/kmcmplib/tests/util_callbacks.h @@ -0,0 +1,8 @@ +#pragma once + +#include + +int msgproc(int line, uint32_t dwMsgCode, const char* szText, void* context); +bool loadfileProc(const char* filename, const char* baseFilename, void* data, int* size, void* context); + +extern std::vector error_vec; \ No newline at end of file diff --git a/developer/src/kmcmplib/src/filesystem.cpp b/developer/src/kmcmplib/tests/util_filesystem.cpp similarity index 84% rename from developer/src/kmcmplib/src/filesystem.cpp rename to developer/src/kmcmplib/tests/util_filesystem.cpp index a343641acc128ed5537e0e1835d45a2253c29f2e..7f9edd6b367a303573e165767ca1805f13bfd558 100644 --- a/developer/src/kmcmplib/src/filesystem.cpp +++ b/developer/src/kmcmplib/tests/util_filesystem.cpp @@ -7,7 +7,7 @@ #include #include #include -#include "filesystem.h" +#include "util_filesystem.h" #ifdef _MSC_VER #include @@ -186,3 +186,42 @@ KMX_BOOL kmcmp_FileExists(const KMX_WCHAR* filename) { return FALSE; }; + + +bool IsRelativePath(KMX_CHAR const * p) { + // Relative path (returns TRUE): + // ..\...\BITMAP.BMP + // PATH\BITMAP.BMP + // BITMAP.BMP + + // Semi-absolute path (returns FALSE): + // \...\BITMAP.BMP + + // Absolute path (returns FALSE): + // C:\...\BITMAP.BMP + // \\SERVER\SHARE\...\BITMAP.BMP + + if ((*p == '\\') || (*p == '/')) return FALSE; + if (*p && *(p + 1) == ':') return FALSE; + + return TRUE; +} + +bool IsRelativePath(KMX_WCHAR const * p) { + // Relative path (returns TRUE): + // ..\...\BITMAP.BMP + // PATH\BITMAP.BMP + // BITMAP.BMP + + // Semi-absolute path (returns FALSE): + // \...\BITMAP.BMP + + // Absolute path (returns FALSE): + // C:\...\BITMAP.BMP + // \\SERVER\SHARE\...\BITMAP.BMP + + if ((*p == u'\\') || (*p == u'/'))return FALSE; + if (*p && *(p + 1) == u':') return FALSE; + + return TRUE; +} \ No newline at end of file diff --git a/developer/src/kmcmplib/src/filesystem.h b/developer/src/kmcmplib/tests/util_filesystem.h similarity index 82% rename from developer/src/kmcmplib/src/filesystem.h rename to developer/src/kmcmplib/tests/util_filesystem.h index 786b7268ac0c09bdda634296445e7e448404cc1e..4c94dae6bf4e4049d80efe8fa5d52d4b4fd7bb93 100644 --- a/developer/src/kmcmplib/src/filesystem.h +++ b/developer/src/kmcmplib/tests/util_filesystem.h @@ -1,7 +1,7 @@ #pragma once #include -#include "kmx_u16.h" +#include "../src/kmx_u16.h" // Opens files on windows and non-windows platforms. Datatypes for Filename and mode must be the same. // returns FILE* if file could be opened; FILE needs to be closed in calling function @@ -10,3 +10,6 @@ FILE* Open_File(const KMX_WCHART* Filename, const KMX_WCHART* mode); FILE* Open_File(const KMX_WCHAR* Filename, const KMX_WCHAR* mode); KMX_BOOL kmcmp_FileExists(const KMX_CHAR *filename); KMX_BOOL kmcmp_FileExists(const KMX_WCHAR *filename); + +bool IsRelativePath(KMX_CHAR const * p); +bool IsRelativePath(KMX_WCHAR const * p); diff --git a/developer/src/kmcomp/main.pas b/developer/src/kmcomp/main.pas index 5a29927a807bccd47dea671dbb6ce8d629a1f727..4aa29cf6c1ace54494285d0f9bd9c1f701d45040 100644 --- a/developer/src/kmcomp/main.pas +++ b/developer/src/kmcomp/main.pas @@ -65,7 +65,7 @@ uses UKeymanTargets; function CompileKeyboard(FInFile, FOutFile: string; FDebug, FWarnAsError: Boolean): Boolean; forward; // I4706 -function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion, FUseKmcmpLib: Boolean): Boolean; forward; +function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion: Boolean): Boolean; forward; //function CompilerMessage(line: Integer; msgcode: LongWord; text: PAnsiChar): Integer; stdcall; forward; procedure FixupPathSlashes(var path: string); forward; @@ -90,7 +90,6 @@ var FParamDistribution: Boolean; FMergingValidateIds: Boolean; FShouldAddCompilerVersion: Boolean; - FUseKmcmpLib: Boolean; FJsonSchemaPath: string; FParamSourcePath: string; FParamHelpLink: string; @@ -117,7 +116,6 @@ begin FColorMode := cmDefault; FShouldAddCompilerVersion := True; - FUseKmcmpLib := True; FParamInfile := ''; FParamOutfile := ''; @@ -193,8 +191,6 @@ begin FColorMode := cmForceNoColor else if s = '-no-compiler-version' then FShouldAddCompilerVersion := False - else if s = '-use-legacy-compiler' then - FUseKmcmpLib := False else if (s = '-help') or (s = '-h') then begin // Force help @@ -223,8 +219,6 @@ begin writeln(SKeymanDeveloperName + ' Compiler (32-bit)'); {$ENDIF} writeln('Version ' + CKeymanVersionInfo.VersionWithTag + ', ' + GetVersionCopyright); - if not FUseKmcmpLib then - writeln('Note: using legacy compiler'); end; if FError or (FParamInfile = '') then @@ -234,7 +228,7 @@ begin writeln(''); writeln('Usage: '+cmd+' [-s[s]] [-nologo] [-c] [-d] [-w] [-cfc] [-v[s|d]] [-source-path path] [-schema-path path] '); writeln(' '+spc+' [-m] infile [-m infile] [-t target] [outfile.kmx|outfile.js [error.log]]'); // I4699 - writeln(' '+spc+' [-add-help-link path] [-color|-no-color] [-no-compiler-version] [-use-legacy-compiler]'); + writeln(' '+spc+' [-add-help-link path] [-color|-no-color] [-no-compiler-version]'); writeln(' '+spc+' [-extract-keyboard-info field[,field...]]'); writeln(' infile can be a .kmn file (Keyboard Source, .kps file (Package Source), or .kpj (project)'); // I4699 // I4825 writeln(' if -v specified, can also be a .keyboard_info file'); @@ -258,7 +252,6 @@ begin writeln(' uses console mode to determine whether color should be used.'); writeln; writeln(' -no-compiler-version Don''t embed the compiler version stores, useful for regression tests.'); - writeln(' -use-legacy-compiler Use legacy compiler (will be removed in 18.0)'); writeln; writeln(' JSON .keyboard_info compile targets:'); writeln(' -v[s] validate infile against source schema'); @@ -298,7 +291,7 @@ begin TProjectLogConsole.Create(FSilent, FFullySilent, hOutfile, FColorMode); - KCSetCompilerOptions(FParamInfile, FShouldAddCompilerVersion, FUseKmcmpLib); + KCSetCompilerOptions(FParamInfile, FShouldAddCompilerVersion); if FValidateRepoChanges then FError := not TValidateRepoChanges.Execute(FParamInfile, FParamOutfile) @@ -324,7 +317,7 @@ begin ExitCode := 1; end; -function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion, FUseKmcmpLib: Boolean): Boolean; +function KCSetCompilerOptions(const FInFile: string; FShouldAddCompilerVersion: Boolean): Boolean; var opt: TCompilerOptions; begin @@ -332,7 +325,6 @@ begin opt.dwSize := sizeof(TCompilerOptions); opt.ShouldAddCompilerVersion := FShouldAddCompilerVersion; - opt.UseKmcmpLib := FUseKmcmpLib; Result := SetCompilerOptions(@opt, @CompilerMessageW); diff --git a/developer/src/server/build.sh b/developer/src/server/build.sh index 53585f1df6b567f62a912e47239d8177c2aa8a22..c735e26fecb3f93b9cd7962f5726cc48969c656c 100755 --- a/developer/src/server/build.sh +++ b/developer/src/server/build.sh @@ -124,20 +124,18 @@ fi if (( build_keymanweb )); then pushd "$KEYMAN_ROOT/web/" - ./build.sh --no-minify + ./build.sh build --debug popd fi if (( copy_keymanweb )); then - WEB_SRC="$KEYMAN_ROOT/web/build/app/web/debug" - UI_SRC="$KEYMAN_ROOT/web/build/app/ui/debug" + WEB_SRC="$KEYMAN_ROOT/web/build/publish/debug" DST="$(dirname "$THIS_SCRIPT")/src/site/resource" rm -rf "$DST" mkdir -p "$DST/osk" mkdir -p "$DST/ui" cp "$WEB_SRC/"*.js "$WEB_SRC/"*.js.map "$DST/" - cp "$UI_SRC/"*.js "$UI_SRC/"*.js.map "$DST/" cp -R "$WEB_SRC/osk/"* "$DST/osk/" cp -R "$WEB_SRC/ui/"* "$DST/ui/" cp "$KEYMAN_ROOT/web/LICENSE" "$DST/" diff --git a/developer/src/server/src/site/chargrid.js b/developer/src/server/src/site/chargrid.js index 1ce9206bae57604d9fcf6ee7abec88a38098413b..f6c31f2df30dd4acd221dcc6cdc0004f357354d4 100644 --- a/developer/src/server/src/site/chargrid.js +++ b/developer/src/server/src/site/chargrid.js @@ -73,17 +73,10 @@ function updateLogCursor() { var i, selStart, selLength, selDirection; - if(keyman.isPositionSynthesized()) { // this is an internal function - // For touch devices, we need to ask KMW - selStart = 0; - selLength = 0; - selDirection = 'forward'; - } else { - // For desktop devices, we use the position reported by the textarea control - selStart = ta1.selectionStart; - selLength = ta1.selectionEnd - ta1.selectionStart; - selDirection = ta1.selectionDirection; - } + // We use the position reported by the textarea control + selStart = ta1.selectionStart; + selLength = ta1.selectionEnd - ta1.selectionStart; + selDirection = ta1.selectionDirection; selLength = calculateLengthByCodepoint(ta1.value, selStart, selLength); selStart = calculateLengthByCodepoint(ta1.value, 0, selStart); diff --git a/developer/src/server/src/site/test.js b/developer/src/server/src/site/test.js index 954ac203c45c841e609a9140a007bb42e2a5c7bb..60a27cc484f6cb85ef1df344bafeefbbed4eec79 100644 --- a/developer/src/server/src/site/test.js +++ b/developer/src/server/src/site/test.js @@ -221,7 +221,7 @@ window.onload = function() { // Create a new on screen keyboard view and tell KeymanWeb that // we are using the targetDevice for context input. - newOSK = new com.keyman.osk.InlinedOSKView(targetDevice, keyman.util.device.coreSpec); + newOSK = new keyman.views.InlinedOSKView(keyman, { device: targetDevice }); keyman.core.contextDevice = targetDevice; keyman.osk = newOSK; @@ -238,11 +238,10 @@ window.onload = function() { keyman.addEventListener('keyboardchange', function(keyboardProperties) { if(newOSK) { keyman.osk = newOSK; - newOSK.activeKeyboard = keyman.core.activeKeyboard; + newOSK.activeKeyboard = keyman.contextManager.activeKeyboard; // Private API refs on both sides } keyboardDropdown.set(keyboardProperties.internalName); window.sessionStorage.setItem('current-keyboard', keyboardProperties.internalName); - keyman.alignInputs(); }); } diff --git a/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas b/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas index 703a1e4a0b6edd891950b647e5240c2e1250f02b..2b6dea94ec580d34adc0c54f471f021683ec3b44 100644 --- a/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas +++ b/developer/src/tike/project/Keyman.Developer.System.Project.kmnProjectFileAction.pas @@ -115,7 +115,7 @@ begin TProject.CompilerMessageFile := Self; options.dwSize := sizeof(COMPILER_OPTIONS); options.ShouldAddCompilerVersion := addVersion; - options.UseKmcmpLib := not useLegacyCompiler; + // TODO: useLegacyCompiler means we switch to kmcmpdll vs kmc if not SetCompilerOptions(@options, ProjectCompilerMessage) then begin Log(plsFatal, 'Unable to set compiler options', CERR_FATAL, 0); diff --git a/linux/debian/rules b/linux/debian/rules index ab4ad409e4e38c790121455b5e84b54834aa4af1..db1f9250174663f6c5a46a58d0c60b0c6f282306 100755 --- a/linux/debian/rules +++ b/linux/debian/rules @@ -9,7 +9,9 @@ export PYBUILD_INSTALL_ARGS=--install-scripts=/usr/share/keyman-config/ export KEYMAN_PKG_BUILD=1 +export HOME=$(shell mktemp -d) export XDG_DATA_HOME=$(shell mktemp -d) +export XDG_CONFIG_HOME=$(shell mktemp -d) # xenial needs this to be explicit export LC_ALL=C.UTF-8 @@ -32,26 +34,23 @@ override_dh_auto_configure: --wrap-mode=nodownload --prefix=/usr --sysconfdir=/etc --localstatedir=/var linux/keyman-system-service/build.sh configure -- \ --wrap-mode=nodownload --prefix=/usr --sysconfdir=/etc --localstatedir=/var - # keyman-config + linux/keyman-config/build.sh configure override_dh_auto_build: core/build.sh --no-tests build:arch linux/ibus-keyman/build.sh build linux/keyman-system-service/build.sh build - # keyman-config + linux/keyman-config/build.sh build cd linux/keyman-config && \ - make man && \ sed -i -e "s/^__pkgversion__ = \"[^\"]*\"/__pkgversion__ = \"$(DEB_VERSION)\"/g" keyman_config/version.py && \ make compile-po - dh_auto_build --sourcedir=linux/keyman-config --buildsystem=pybuild $@ override_dh_auto_test: ifeq (,$(filter nocheck,$(DEB_BUILD_OPTIONS))) core/build.sh --no-tests test:arch linux/ibus-keyman/build.sh test linux/keyman-system-service/build.sh test - # keyman-config - dh_auto_test --sourcedir=linux/keyman-config --buildsystem=pybuild $@ + linux/keyman-config/build.sh test endif override_dh_auto_install: @@ -63,6 +62,7 @@ override_dh_auto_install: install -d $(CURDIR)/debian/keyman/usr/share/ cp -r linux/keyman-config/locale/ $(CURDIR)/debian/keyman/usr/share/ rm $(CURDIR)/debian/keyman/usr/share/locale/*.po* + # Don't call `build.sh install` - dh_auto_install does some extra smarts dh_auto_install --sourcedir=linux/keyman-config --buildsystem=pybuild $@ # Unfortunately bash-completion 2.10 (focal) doesn't yet provide dh-sequence-bash-completion, # which we could add as build-dependency, so we'll have to explicitly call diff --git a/linux/keyman-config/Makefile b/linux/keyman-config/Makefile index 41eaab30efb727be2c2e527059e32a1f2d7aa9e4..f057dcad0ce8c0f81e2e82af0014c99e429a41f8 100644 --- a/linux/keyman-config/Makefile +++ b/linux/keyman-config/Makefile @@ -1,49 +1,19 @@ #!/usr/bin/make -default: clean version man langtags - python3 setup.py build - -langtags: - cd buildtools && python3 ./build-langtags.py +default: + ./build.sh clean build install: - if [ -n "${SUDO_USER}" ]; then \ - make install-sudo; \ - else \ - make install-temp; \ - fi - -install-sudo: # run as sudo - pip3 install qrcode sentry-sdk - # eventually change this to: pip3 install . - python3 setup.py install - # install icons - mkdir -p /usr/local/share/keyman/icons - cp keyman_config/icons/* /usr/local/share/keyman/icons - # install man pages - mkdir -p /usr/local/share/man/man1 - cp ../../debian/man/*.1 /usr/local/share/man/man1 - -install-temp: - mkdir -p /tmp/keyman/$(shell python3 -c 'import sys;import os;pythonver="python%d.%d" % (sys.version_info[0], sys.version_info[1]);sitedir = os.path.join("lib", pythonver, "site-packages");print(sitedir)') - # when we no longer have to support old pip version (python > 3.6) change this to: - # pip3 install --prefix /tmp/keyman . - PYTHONUSERBASE=/tmp/keyman python3 setup.py install --user - -uninstall: clean # run as sudo - rm -rf /usr/local/share/keyman/icons - rm -f /usr/local/share/man/man1/km-*.1 - pip3 uninstall keyman_config - rm -f /usr/local/bin/km-config - rm -f /usr/local/bin/km-kvk2ldml - rm -f /usr/local/bin/km-package-get - rm -f /usr/local/bin/km-package-install - rm -f /usr/local/bin/km-package-list-installed - rm -f /usr/local/bin/km-package-uninstall + ./build.sh install + +uninstall: # run as sudo + ./build.sh uninstall clean: - -rm -rf dist make_deb build keyman_config/version.py *.egg-info __pycache__ \ - keyman_config/standards/lang_tags_map.py + ./build.sh clean + +check: + ./build.sh test devdist: version python3 setup.py egg_info -b.`TZ=UTC git log -1 --pretty=format:%cd --date=format-local:%Y%m%d%H%M` sdist @@ -93,6 +63,3 @@ $(MOFILES): %/LC_MESSAGES/keyman-config.mo: %.po msgfmt -v --check --output-file=$@ $^ compile-po: $(MOFILES) - -check: - ./run-tests.sh diff --git a/linux/keyman-config/build.sh b/linux/keyman-config/build.sh index 38cf22e917b40ab8a37c60b15a5748681bc5c66e..1cb603f091b8801ffc7fd807d02c07b461cac688 100755 --- a/linux/keyman-config/build.sh +++ b/linux/keyman-config/build.sh @@ -24,9 +24,90 @@ cd "$THIS_SCRIPT_PATH" builder_describe_outputs \ build "/linux/keyman-config/keyman_config/standards/lang_tags_map.py" -builder_run_action clean make clean +clean_action() { + rm -rf dist make_deb build keyman_config/version.py ./*.egg-info __pycache__ \ + keyman_config/standards/lang_tags_map.py +} + +build_man_pages() { + local TEMP_DATA_DIR SCHEMA_DIR + TEMP_DATA_DIR=$(mktemp -d) + SCHEMA_DIR=$TEMP_DATA_DIR/glib-2.0/schemas + export XDG_DATA_DIRS=$TEMP_DATA_DIR:${XDG_DATA_DIRS-} + export GSETTINGS_SCHEMA_DIR="${SCHEMA_DIR}" + mkdir -p "$SCHEMA_DIR" + cp ./com.keyman.gschema.xml "$SCHEMA_DIR"/ + glib-compile-schemas "$SCHEMA_DIR" + ./build-help.sh --man --no-reconf + export XDG_DATA_DIRS=${XDG_DATA_DIRS#*:} + unset GSETTINGS_SCHEMA_DIR + rm -rf "$TEMP_DATA_DIR" +} + +build_action() { + builder_echo "Create version.py" + pushd keyman_config + sed \ + -e "s/_VERSION_/${VERSION}/g" \ + -e "s/_VERSIONWITHTAG_/${VERSION_WITH_TAG}/g" \ + -e "s/_VERSIONGITTAG_/${VERSION_GIT_TAG}/g" \ + -e "s/_MAJORVERSION_/${VERSION_MAJOR}/g" \ + -e "s/_RELEASEVERSION_/${VERSION_RELEASE}/g" \ + -e "s/_TIER_/${TIER}/g" \ + -e "s/_ENVIRONMENT_/${VERSION_ENVIRONMENT}/g" \ + -e "s/_UPLOADSENTRY_/${UPLOAD_SENTRY}/g" \ + version.py.in > version.py + popd + pushd buildtools + if [ -f build-langtags.py ]; then + builder_echo "Create lang_tags_map.py" + python3 ./build-langtags.py + else + builder_echo "Skip building lang_tags_map.py during package build" + fi + popd + builder_echo "Building man pages" + build_man_pages + builder_echo "Building keyman-config" + python3 setup.py build +} + +install_action() { + if [ -n "${SUDO_USER:-}" ]; then + pip3 install qrcode sentry-sdk + # eventually change this to: pip3 install . + python3 setup.py install + # install icons + mkdir -p /usr/local/share/keyman/icons + cp keyman_config/icons/* /usr/local/share/keyman/icons + # install man pages + mkdir -p /usr/local/share/man/man1 + cp ../../debian/man/*.1 /usr/local/share/man/man1 + else + mkdir -p "/tmp/keyman/$(python3 -c 'import sys;import os;pythonver="python%d.%d" % (sys.version_info[0], sys.version_info[1]);sitedir = os.path.join("lib", pythonver, "site-packages");print(sitedir)')" + # when we no longer have to support old pip version (python > 3.6) change this to: + # pip3 install --prefix /tmp/keyman . + PYTHONUSERBASE=${DESTDIR:-/tmp/keyman} python3 setup.py install --user + fi +} + +uninstall_action() { + # run as sudo + clean_action + rm -rf /usr/local/share/keyman/icons + rm -f /usr/local/share/man/man1/km-*.1 + pip3 uninstall keyman_config + rm -f /usr/local/bin/km-config + rm -f /usr/local/bin/km-kvk2ldml + rm -f /usr/local/bin/km-package-get + rm -f /usr/local/bin/km-package-install + rm -f /usr/local/bin/km-package-list-installed + rm -f /usr/local/bin/km-package-uninstall +} + +builder_run_action clean clean_action builder_run_action configure # nothing to do -builder_run_action build make +builder_run_action build build_action builder_run_action test ./run-tests.sh -builder_run_action install make install -builder_run_action uninstall make uninstall +builder_run_action install install_action +builder_run_action uninstall uninstall_action diff --git a/linux/keyman-config/com.keyman.gschema.xml b/linux/keyman-config/com.keyman.gschema.xml index 8f05a42bf9b0c70d4bc2ee9ffdb6b3ec6716c9c3..df50060d8c44eb97e2ec4e8d4848ab23c34ef665 100644 --- a/linux/keyman-config/com.keyman.gschema.xml +++ b/linux/keyman-config/com.keyman.gschema.xml @@ -1,8 +1,12 @@ - + + true + Automatic error reporting + true to enable automatic reporting of errors to Sentry + diff --git a/linux/keyman-config/keyman_config/__init__.py b/linux/keyman-config/keyman_config/__init__.py index e5cecf4cc82d6b6ae333dab67dd5aa615454b3e2..04098ade00fcbeeed91a9f7b5ee63070780d60af 100644 --- a/linux/keyman-config/keyman_config/__init__.py +++ b/linux/keyman-config/keyman_config/__init__.py @@ -1,20 +1,18 @@ -import getpass import gettext -import importlib -import logging -import os -import platform -import sys -from .version import __version__ -from .version import __versionwithtag__ -from .version import __versiongittag__ -from .version import __majorversion__ -from .version import __releaseversion__ -from .version import __tier__ -from .version import __pkgversion__ -from .version import __environment__ -from .version import __uploadsentry__ +from keyman_config.sentry_handling import SentryErrorHandling + +from keyman_config.version import ( + __version__, + __versionwithtag__, + __versiongittag__, + __majorversion__, + __releaseversion__, + __tier__, + __pkgversion__, + __environment__, + __uploadsentry__ +) def _(txt): @@ -24,7 +22,7 @@ def _(txt): return translation -def secure_lookup(data, key1, key2 = None): +def secure_lookup(data, key1, key2=None): """ Return data[key1][key2] while dealing with data being None or key1 or key2 not existing """ @@ -38,18 +36,10 @@ def secure_lookup(data, key1, key2 = None): return None -def before_send(event, hint): - if 'exc_info' in hint: - exc_type, exc_value, tb = hint['exc_info'] - if isinstance(exc_value, KeyboardInterrupt): - # Ignore KeyboardInterrupt exception - return None - return event - - gettext.bindtextdomain('keyman-config', '/usr/share/locale') gettext.textdomain('keyman-config') + #if __tier__ == 'alpha' or __tier__ == 'beta': // #7227 disabling: # Alpha and beta versions will work against the staging server so that they # can access new APIs etc that will only be available there. The staging @@ -60,60 +50,8 @@ gettext.textdomain('keyman-config') KeymanComUrl = 'https://keyman.com' KeymanApiUrl = 'https://api.keyman.com' + # There's no staging site for downloads KeymanDownloadsUrl = 'https://downloads.keyman.com' -if 'unittest' in sys.modules.keys(): - print('Not reporting to Sentry', file=sys.stderr) -elif os.environ.get('KEYMAN_NOSENTRY'): - print('Not reporting to Sentry because KEYMAN_NOSENTRY environment variable set', file=sys.stderr) -elif not __uploadsentry__: - print('Not reporting to Sentry because UPLOAD_SENTRY is false (%s)' % __environment__, file=sys.stderr) -else: - try: - # Try new sentry-sdk first - sentry_sdk = importlib.import_module('sentry_sdk') - from sentry_sdk import configure_scope, set_user - from sentry_sdk.integrations.logging import LoggingIntegration - HaveSentryNewSdk = True - - sentry_logging = LoggingIntegration( - level=logging.INFO, # Capture info and above as breadcrumbs - event_level=logging.CRITICAL # Send critical errors as events - ) - SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357@o1005580.ingest.sentry.io/5983525" - sentry_sdk.init( - dsn=SentryUrl, - environment=__environment__, - release=__versiongittag__, - integrations=[sentry_logging], - before_send=before_send - ) - set_user({'id': hash(getpass.getuser())}) - with configure_scope() as scope: - scope.set_tag("app", os.path.basename(sys.argv[0])) - scope.set_tag("pkgversion", __pkgversion__) - scope.set_tag("platform", platform.platform()) - scope.set_tag("system", platform.system()) - scope.set_tag("tier", __tier__) - except ImportError: - try: - # sentry-sdk is not available, so use older raven - raven = importlib.import_module('raven') - from raven import Client - HaveSentryNewSdk = False - - # Note, legacy raven API requires secret (https://github.com/keymanapp/keyman/pull/5787#discussion_r721457909) - SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357:e6d5a81ee6944fc79bd9f0cbb1f2c2a4@o1005580.ingest.sentry.io/5983525" - client = Client(SentryUrl, environment=__environment__, release=__versiongittag__) - client.user_context({'id': hash(getpass.getuser())}) - client.tags_context({ - 'app': os.path.basename(sys.argv[0]), - 'pkgversion': __pkgversion__, - 'platform': platform.platform(), - 'system': platform.system(), - 'tier': __tier__, - }) - except ImportError: - # even raven is not available. This is the case on Ubuntu 16.04. Just ignore. - print(_('Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting.'), file=sys.stderr) +SentryErrorHandling().initialize_sentry() diff --git a/linux/keyman-config/keyman_config/dconf_util.py b/linux/keyman-config/keyman_config/dconf_util.py index 09b95d1815a5f42021674cba68b40a6c2f7887fa..c28ec3fd19c5aa7b6a126c020caadb3412890f3b 100644 --- a/linux/keyman-config/keyman_config/dconf_util.py +++ b/linux/keyman-config/keyman_config/dconf_util.py @@ -2,25 +2,25 @@ from gi.repository import Gio -# DConf path destkop/ibus/keyman/options -DCONF_BASE = "com.keyman.options" +# GSettings path destkop/ibus/keyman/options +GSETTINGS_BASE = "com.keyman.options" -# Utilities to get and set Keyman options in DConf: +# Utilities to get and set Keyman options in GSettings: # /desktop/ibus/keyman/options/packageID/keyboardID/options def get_child_schema(info): - settings = Gio.Settings.new(DCONF_BASE) + settings = Gio.Settings.new(GSETTINGS_BASE) path = settings.get_property('path') if not path.endswith('/'): path += '/' path += info['packageID'] + '/' + info['keyboardID'] + '/' - return Gio.Settings(DCONF_BASE + '.child', path) + return Gio.Settings(GSETTINGS_BASE + '.child', path) def get_option(info): """ - Get the Keyman keyboard options from DConf + Get the Keyman keyboard options from GSettings Convert from list of comma-separated strings into dictionary Args: @@ -41,7 +41,7 @@ def get_option(info): def set_option(info, options): """ - Store the Keyman keyboard options in DConf as a list of strings + Store the Keyman keyboard options in GSettings as a list of strings Args: info: dictionary diff --git a/linux/keyman-config/keyman_config/get_kmp.py b/linux/keyman-config/keyman_config/get_kmp.py index af7b34063d3336fd2c451c4ddddbdcf4073a1570..ed3f7ec8a444fdc178238f495f9ef29ca05f41f9 100755 --- a/linux/keyman-config/keyman_config/get_kmp.py +++ b/linux/keyman-config/keyman_config/get_kmp.py @@ -9,6 +9,7 @@ import requests import requests_cache from gi.repository import GObject +from keyman_config import _ from keyman_config import KeymanApiUrl, KeymanDownloadsUrl from keyman_config.deprecated_decorator import deprecated @@ -20,6 +21,16 @@ class InstallLocation(GObject.GEnum): Unknown = 99 +def get_install_area_string(area): + if area == InstallLocation.OS: + return _('System') + elif area == InstallLocation.Shared: + return _('Shared') + elif area == InstallLocation.User: + return _('User') + return _('Unknown') + + def get_package_download_data(packageID, weekCache=False): """ Get package download data from keyboards download api. diff --git a/linux/keyman-config/keyman_config/sentry_handling.py b/linux/keyman-config/keyman_config/sentry_handling.py new file mode 100644 index 0000000000000000000000000000000000000000..f2b2f8adc3441b83ced5dc75879f06cbea06310f --- /dev/null +++ b/linux/keyman-config/keyman_config/sentry_handling.py @@ -0,0 +1,152 @@ +#!/usr/bin/python3 +import getpass +import importlib +import logging +import os +import platform +import sys +from keyman_config.gsettings import GSettings +from keyman_config.version import ( + __version__, + __versionwithtag__, + __versiongittag__, + __majorversion__, + __releaseversion__, + __tier__, + __pkgversion__, + __environment__, + __uploadsentry__ +) + +import gi +gi.require_version('Gtk', '3.0') +from gi.repository import Gio, Gtk + + +class SentryErrorHandling: + def __init__(self) -> None: + self.settings = Gio.Settings.new('com.keyman.options') + + def initialize_sentry(self): + (enabled, reason) = self.is_sentry_enabled() + if not enabled: + print(reason, file=sys.stderr) + logging.info(reason) + return (enabled, reason) + else: + try: + self._sentry_sdk_initialize() + except ImportError: + try: + self._raven_initialize() + except ImportError: + # even raven is not available. This is the case on Ubuntu 16.04. Just ignore. + print(_('Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting.'), + file=sys.stderr) + logging.info('Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting.') + return (False, _('Neither sentry-sdk nor raven is available. Not enabling Sentry error reporting.')) + return (True, '') + + def is_sentry_enabled(self): + if 'unittest' in sys.modules.keys(): + return (False, 'Running unit tests, not reporting to Sentry') + elif self._get_environ_nosentry(): + return (False, 'Not reporting to Sentry because KEYMAN_NOSENTRY environment variable set') + elif not __uploadsentry__: + return (False, f'Not reporting to Sentry because UPLOAD_SENTRY is false ({__environment__})') + elif not self._get_setting(): + return (False, 'Not reporting to Sentry because disabled in GSettings') + return (True, 'Reporting to Sentry') + + def is_sentry_disabled_by_variable(self): + return self._get_environ_nosentry() or not __uploadsentry__ + + def bind_checkbutton(self, button: Gtk.CheckButton): + self.settings.bind("error-reporting", button, "active", Gio.SettingsBindFlags.NO_SENSITIVITY) + self.settings.connect("changed::error-reporting", self._on_sentry_reporting_toggled) + + def set_enabled(self, enabled): + assert not self.is_sentry_disabled_by_variable() + was_enabled = self.is_sentry_enabled() + self._save_setting(enabled) + if enabled != was_enabled: + self._handle_enabled(enabled) + + def _get_environ_nosentry(self): + keyman_nosentry = os.environ.get('KEYMAN_NOSENTRY') + return keyman_nosentry and (int(keyman_nosentry) == 1) + + def _handle_enabled(self, enabled): + if enabled: + self.initialize_sentry() + else: + self._close_sentry() + + def _save_setting(self, enabled: bool): + self.settings.set_boolean('error-reporting', enabled) + + def _get_setting(self) -> bool: + return self.settings.get_boolean('error-reporting') + + def _on_sentry_reporting_toggled(self, settings, key): + self._handle_enabled(self.settings.get_boolean('error-reporting')) + + def _close_sentry(self): + from sentry_sdk import Hub + logging.info("Shutting down Sentry error reporting") + client = Hub.current.client + if client is not None: + client.close(timeout=2.0) + + def _sentry_sdk_initialize(self): + # Try new sentry-sdk first + sentry_sdk = importlib.import_module('sentry_sdk') + from sentry_sdk import configure_scope, set_user + from sentry_sdk.integrations.logging import LoggingIntegration + + sentry_logging = LoggingIntegration( + level=logging.INFO, # Capture info and above as breadcrumbs + event_level=logging.CRITICAL # Send critical errors as events + ) + SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357@o1005580.ingest.sentry.io/5983525" + sentry_sdk.init( + dsn=SentryUrl, + environment=__environment__, + release=__versiongittag__, + integrations=[sentry_logging], + before_send=self._before_send + ) + set_user({'id': hash(getpass.getuser())}) + with configure_scope() as scope: + scope.set_tag("app", os.path.basename(sys.argv[0])) + scope.set_tag("pkgversion", __pkgversion__) + scope.set_tag("platform", platform.platform()) + scope.set_tag("system", platform.system()) + scope.set_tag("tier", __tier__) + logging.info("Initialized Sentry error reporting") + + def _raven_initialize(self): + # sentry-sdk is not available, so use older raven + raven = importlib.import_module('raven') + from raven import Client + + # Note, legacy raven API requires secret (https://github.com/keymanapp/keyman/pull/5787#discussion_r721457909) + SentryUrl = "https://1d0edbf2d0dc411b87119b6e92e2c357:e6d5a81ee6944fc79bd9f0cbb1f2c2a4@o1005580.ingest.sentry.io/5983525" + client = Client(SentryUrl, environment=__environment__, release=__versiongittag__) + client.user_context({'id': hash(getpass.getuser())}) + client.tags_context({ + 'app': os.path.basename(sys.argv[0]), + 'pkgversion': __pkgversion__, + 'platform': platform.platform(), + 'system': platform.system(), + 'tier': __tier__, + }) + logging.info("Initialized Sentry error reporting (raven)") + + def _before_send(self, event, hint): + if 'exc_info' in hint: + exc_type, exc_value, tb = hint['exc_info'] + if isinstance(exc_value, KeyboardInterrupt): + # Ignore KeyboardInterrupt exception + return None + return event diff --git a/linux/keyman-config/keyman_config/view_installed.py b/linux/keyman-config/keyman_config/view_installed.py index 01b1a59b89b3e413619c87abd56c479667abeab2..263d665d25f8ec89ef1694185015fad5e7f2d9b4 100755 --- a/linux/keyman-config/keyman_config/view_installed.py +++ b/linux/keyman-config/keyman_config/view_installed.py @@ -19,12 +19,13 @@ from keyman_config.accelerators import bind_accelerator, init_accel from keyman_config.dbus_util import get_keyman_config_service from keyman_config.downloadkeyboard import DownloadKmpWindow from keyman_config.get_kmp import (InstallLocation, get_keyboard_dir, - get_keyman_dir) + get_keyman_dir, get_install_area_string) from keyman_config.install_window import InstallKmpWindow, find_keyman_image from keyman_config.keyboard_details import KeyboardDetailsView from keyman_config.kmpmetadata import get_fonts, parsemetadata from keyman_config.list_installed_kmp import get_installed_kmp from keyman_config.options import OptionsView +from keyman_config.sentry_handling import SentryErrorHandling from keyman_config.uninstall_kmp import uninstall_kmp from keyman_config.welcome import WelcomeView @@ -94,7 +95,7 @@ class ViewInstalledWindowBase(Gtk.Window): self.close() def run(self): - self.resize(576, 324) + self.resize(776, 424) self.connect("destroy", Gtk.main_quit) self.show_all() Gtk.main() @@ -104,26 +105,60 @@ class ViewInstalledWindow(ViewInstalledWindowBase): def __init__(self): ViewInstalledWindowBase.__init__(self) -# window is split left/right hbox -# right is ButtonBox -# possibly 2 ButtonBox in a vbox -# top one with _Remove, _About, ?_Welcome? or ?Read_Me?, _Options -# bottom one with _Download, _Install, Re_fresh, _Close -# left is GtkTreeView - does it need to be inside anything else apart from the hbox? -# with liststore which defines columns -# GdkPixbuf icon -# gchararray name -# gchararray version -# gchararray packageID (hidden) -# enum? area (user, shared, system) (icon or hidden?) -# gchararray welcomefile (hidden) (or just use area and packageID?) -# changing selected item in treeview changes what buttons are activated -# on selected_item_changed signal set the data that the buttons will use in their callbacks -# see https://developer.gnome.org/gtk3/stable/TreeWidget.html#TreeWidget + self.sentry = SentryErrorHandling() + outerHbox = Gtk.HBox() + sidebar = Gtk.StackSidebar() + stack = Gtk.Stack() + + outerHbox.pack_start(sidebar, False, False, 0) + outerHbox.pack_end(Gtk.Separator(), False, False, 0) + outerHbox.pack_end(stack, True, True, 0) + stack.set_hexpand(True) + stack.set_vexpand(True) + sidebar.set_stack(stack) + + stack.add_titled(self.add_keyboard_layouts_widget(), "KeyboardLayouts", _("Keyboard Layouts")) + stack.add_titled(self.add_options_widget(), "Options", _("Options")) + + outmostVBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + outmostVBox.pack_start(outerHbox, True, True, 0) + + bbox_bottom = Gtk.ButtonBox(spacing=12, orientation=Gtk.Orientation.HORIZONTAL) + bbox_bottom.set_layout(Gtk.ButtonBoxStyle.START) + + button = Gtk.Button.new_with_mnemonic(_("_Install keyboard...")) + button.set_tooltip_text(_("Install a keyboard from a file")) + button.connect("clicked", self.on_installfile_clicked) + bbox_bottom.add(button) + + button = Gtk.Button.new_with_mnemonic(_("_Download keyboard...")) + button.set_tooltip_text(_("Download and install a keyboard from the Keyman website")) + button.connect("clicked", self.on_download_clicked) + bbox_bottom.add(button) + + button = Gtk.Button.new_with_mnemonic(_("_Refresh")) + button.set_tooltip_text(_("Refresh keyboard list")) + button.connect("clicked", self.on_refresh_clicked) + bbox_bottom.add(button) + + button = Gtk.Button.new_with_mnemonic(_("_Close")) + button.set_tooltip_text(_("Close window")) + button.connect("clicked", self.on_close_clicked) + bind_accelerator(self.accelerators, button, 'q') + bind_accelerator(self.accelerators, button, 'w') + bbox_bottom.add(button) + + bbox_hbox = Gtk.HBox(spacing=12) + bbox_hbox.pack_start(bbox_bottom, True, True, 12) + outmostVBox.pack_end(bbox_hbox, False, True, 12) + self.add(outmostVBox) + + def add_keyboard_layouts_widget(self): hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) - s = Gtk.ScrolledWindow() - hbox.pack_start(s, True, True, 0) + + scrolledWindow = Gtk.ScrolledWindow() + hbox.pack_start(scrolledWindow, True, True, 0) self.store = Gtk.ListStore( GdkPixbuf.Pixbuf, # icon @@ -131,15 +166,11 @@ class ViewInstalledWindow(ViewInstalledWindowBase): str, # version str, # packageID int, # enum InstallLocation (KmpArea is GObject version) + str, # InstallLocation area + str, # Tooltip with InstallLocation path str, # path to welcome file if it exists or None str) # path to options file if it exists or None - # add installed keyboards to the the store e.g. - # treeiter = store.append([GdkPixbuf.Pixbuf.new_from_file_at_size( - # "/usr/local/share/keyman/libtralo/libtralo.ico.png", 16, 16), \ - # "LIBTRALO", "1.6.1", \ - # "libtralo", KmpArea.SHARED, True]) - self.refresh_installed_kmp() self.tree = Gtk.TreeView(self.store) @@ -155,11 +186,16 @@ class ViewInstalledWindow(ViewInstalledWindowBase): # i18n: column header in table displaying installed keyboards column = Gtk.TreeViewColumn(_("Version"), renderer, text=2) self.tree.append_column(column) + # i18n: column header in table displaying installed keyboards + column = Gtk.TreeViewColumn(_("Area"), renderer, text=5) + self.tree.append_column(column) + + self.tree.set_tooltip_column(column=6) select = self.tree.get_selection() select.connect("changed", self.on_tree_selection_changed) - s.add(self.tree) + scrolledWindow.add(self.tree) vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=12) @@ -191,36 +227,34 @@ class ViewInstalledWindow(ViewInstalledWindowBase): bbox_top.add(self.options_button) vbox.pack_start(bbox_top, False, False, 12) + hbox.pack_start(vbox, False, False, 12) - bbox_bottom = Gtk.ButtonBox(spacing=12, orientation=Gtk.Orientation.VERTICAL) - bbox_bottom.set_layout(Gtk.ButtonBoxStyle.END) + return hbox - button = Gtk.Button.new_with_mnemonic(_("_Refresh")) - button.set_tooltip_text(_("Refresh keyboard list")) - button.connect("clicked", self.on_refresh_clicked) - bbox_bottom.add(button) + def add_options_widget(self): + vbox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) + label = Gtk.Label(_("General")) + label.set_padding(5, 5) + label.set_halign(Gtk.Align.START) + vbox.pack_start(label, False, False, 10) - button = Gtk.Button.new_with_mnemonic(_("_Download")) - button.set_tooltip_text(_("Download and install a keyboard from the Keyman website")) - button.connect("clicked", self.on_download_clicked) - bbox_bottom.add(button) - - button = Gtk.Button.new_with_mnemonic(_("_Install")) - button.set_tooltip_text(_("Install a keyboard from a file")) - button.connect("clicked", self.on_installfile_clicked) - bbox_bottom.add(button) + (enabled, reason) = self.sentry.is_sentry_enabled() + disabledByVariable = self.sentry.is_sentry_disabled_by_variable() - button = Gtk.Button.new_with_mnemonic(_("_Close")) - button.set_tooltip_text(_("Close window")) - button.connect("clicked", self.on_close_clicked) - bind_accelerator(self.accelerators, button, 'q') - bind_accelerator(self.accelerators, button, 'w') - bbox_bottom.add(button) + self.errorReportingButton = Gtk.CheckButton(_("Automatically report errors to keyman.com")) + self.errorReportingButton.set_active(enabled) + self.errorReportingButton.set_sensitive(not disabledByVariable) + self.sentry.bind_checkbutton(self.errorReportingButton) + vbox.pack_start(self.errorReportingButton, False, False, 0) - vbox.pack_end(bbox_bottom, False, False, 12) + if disabledByVariable: + label = Gtk.Label(reason) + label.set_halign(Gtk.Align.START) + label.set_padding(25, 0) + label.set_sensitive(False) + vbox.pack_start(label, False, False, 0) - hbox.pack_start(vbox, False, False, 12) - self.add(hbox) + return vbox def addlistitems(self, installed_kmp, store, install_area): for kmp in sorted(installed_kmp): @@ -255,6 +289,8 @@ class ViewInstalledWindow(ViewInstalledWindowBase): kmpdata['version'], kmpdata['packageID'], install_area, + get_install_area_string(install_area), + path.replace(os.path.expanduser('~'), '~'), welcome_file, options_file]) diff --git a/linux/keyman-config/run-tests.sh b/linux/keyman-config/run-tests.sh index 3b8664512adc639aa376c6e6ed4b5da8e479713c..b390ce613bc589d926da09a8ca39c28ecd7c3a19 100755 --- a/linux/keyman-config/run-tests.sh +++ b/linux/keyman-config/run-tests.sh @@ -1,6 +1,9 @@ #!/bin/bash PYTHONPATH=.:$PYTHONPATH +XDG_CONFIG_HOME=$(mktemp --directory) +export XDG_CONFIG_HOME + if [ -n "$TEAMCITY_VERSION" ]; then if ! pip3 list --format=columns | grep -q teamcity-messages; then pip3 install teamcity-messages @@ -9,3 +12,5 @@ if [ -n "$TEAMCITY_VERSION" ]; then else python3 -m unittest discover -v -s tests -p test_*.py fi + +rm -rf "$XDG_CONFIG_HOME" diff --git a/linux/keyman-config/tests/test_gnome_keyboards_util.py b/linux/keyman-config/tests/test_gnome_keyboards_util.py index b5a0cac842fff324e018f25672c1b93817ef376b..a53908cdd3296aaee7dfa26b838ac0fe29042f67 100644 --- a/linux/keyman-config/tests/test_gnome_keyboards_util.py +++ b/linux/keyman-config/tests/test_gnome_keyboards_util.py @@ -9,14 +9,14 @@ class GnomeKeyboardsUtilTests(unittest.TestCase): def setUp(self): _reset_gnome_shell() - @patch('keyman_config.os.system') + @patch('os.system') def test_IsGnomeShell_RunningGnomeShell(self, mockSystem): # Setup mockSystem.return_value = 0 # Execute/Verify self.assertEqual(is_gnome_shell(), True) - @patch('keyman_config.os.system') + @patch('os.system') def test_IsGnomeShell_NotRunningGnomeShell(self, mockSystem): # Setup mockSystem.return_value = 1 diff --git a/linux/keyman-config/tests/test_install_kmp.py b/linux/keyman-config/tests/test_install_kmp.py index 2f250550cd3ecef1ea5f1f1a88debe4a292c6178..d1e10bd645b6d9aaa450bc9fed3cc64883b92b31 100644 --- a/linux/keyman-config/tests/test_install_kmp.py +++ b/linux/keyman-config/tests/test_install_kmp.py @@ -262,13 +262,11 @@ class InstallKmpTests(unittest.TestCase): def test_InstallKmp_FutureKeymanVersion(self): # Setup - workdir = tempfile.TemporaryDirectory().name - os.makedirs(workdir) - packagedir = tempfile.TemporaryDirectory().name - os.makedirs(packagedir) - self.mockGetKeyboardDir.return_value = packagedir - kmpfile = self._createEmptyKmp(workdir) - self._createKmpJson(packagedir, ', "fileVersion": "99.0"') + workdir = tempfile.TemporaryDirectory() + packagedir = tempfile.TemporaryDirectory() + self.mockGetKeyboardDir.return_value = packagedir.name + kmpfile = self._createEmptyKmp(workdir.name) + self._createKmpJson(packagedir.name, ', "fileVersion": "99.0"') # Execute with self.assertRaises(InstallError) as context: @@ -278,22 +276,24 @@ class InstallKmpTests(unittest.TestCase): self.assertTrue('foo.kmp requires Keyman 99.0 or higher' in context.exception.message) self.mockInstallToIbus.assert_not_called() + # Teardown + workdir.cleanup() + packagedir.cleanup() + def test_InstallKmp(self): for testcase in [ {'name': 'PreviousKeymanVersion', 'fileVersion': ', "fileVersion": "7.0"'}, - {'name': 'SameKeymanVersion', 'fileVersion': ', "fileVersion": "' + __version__ + '"'}, + {'name': 'SameKeymanVersion', 'fileVersion': f', "fileVersion": "{__version__}"'}, {'name': 'NoFileVersion', 'fileVersion': ''} ]: with self.subTest(msg=testcase['name'], data=testcase['fileVersion']): # Setup self.mockInstallToIbus.reset_mock() - workdir = tempfile.TemporaryDirectory().name - os.makedirs(workdir) - packagedir = tempfile.TemporaryDirectory().name - os.makedirs(packagedir) - self.mockGetKeyboardDir.return_value = packagedir - kmpfile = self._createEmptyKmp(workdir) - self._createKmpJson(packagedir, testcase['fileVersion']) + workdir = tempfile.TemporaryDirectory() + packagedir = tempfile.TemporaryDirectory() + self.mockGetKeyboardDir.return_value = packagedir.name + kmpfile = self._createEmptyKmp(workdir.name) + self._createKmpJson(packagedir.name, testcase['fileVersion']) # Execute InstallKmp()._install_kmp(kmpfile, 'km', InstallLocation.User) @@ -301,6 +301,10 @@ class InstallKmpTests(unittest.TestCase): # Verify self.mockInstallToIbus.assert_called_once() + # Teardown + workdir.cleanup() + packagedir.cleanup() + if __name__ == '__main__': unittest.main() diff --git a/linux/keyman-config/tests/test_package_install_completion.py b/linux/keyman-config/tests/test_package_install_completion.py index 97238e3dc2e57051c63d085d1692018267841f71..61374a6c66cbc5756e22b0c42e42810adf378b03 100644 --- a/linux/keyman-config/tests/test_package_install_completion.py +++ b/linux/keyman-config/tests/test_package_install_completion.py @@ -2,7 +2,7 @@ import os import tempfile import unittest -from unittest.mock import patch, ANY +from unittest.mock import patch from importlib.machinery import SourceFileLoader from importlib.util import module_from_spec, spec_from_loader @@ -10,17 +10,11 @@ from importlib.util import module_from_spec, spec_from_loader class PackageInstallCompletionTests(unittest.TestCase): def setUp(self): - patcher1 = patch('keyman_config.install_kmp.extract_kmp') - self.mockExtractKmp = patcher1.start() - self.addCleanup(patcher1.stop) - patcher2 = patch('keyman_config.kmpmetadata.get_metadata') - self.mockGetMetadata = patcher2.start() - self.addCleanup(patcher2.stop) - + self.mockExtractKmp = self._setupMock('keyman_config.install_kmp.extract_kmp') + self.mockGetMetadata = self._setupMock('keyman_config.kmpmetadata.get_metadata') loader = SourceFileLoader('km_package_install', os.path.join(os.path.dirname( os.path.abspath(__file__)), '../km-package-install')) - spec = spec_from_loader(loader.name, loader) - if spec: + if spec := spec_from_loader(loader.name, loader): self.mod = module_from_spec(spec) loader.exec_module(self.mod) @@ -29,32 +23,43 @@ class PackageInstallCompletionTests(unittest.TestCase): self.cacheDir = os.path.join(self.tempDir.name, 'keyman') os.makedirs(self.cacheDir) + def tearDown(self): + self.tempDir.cleanup() + + def _setupMock(self, arg0): + patcher = patch(arg0) + result = patcher.start() + self.addCleanup(patcher.stop) + return result + def _list_languages_for_keyboard_impl(self, packageId): return self.mod._list_languages_for_keyboard_impl(packageId, 'someDir') def test_PackageCompletionNoLanguage(self): - open(os.path.join(self.cacheDir, 'foo'), 'w') - self.mockGetMetadata.return_value = (None, None, None, [{}], None) - result = self._list_languages_for_keyboard_impl('foo') - self.assertEqual(result, "") + with open(os.path.join(self.cacheDir, 'foo'), 'w'): + self.mockGetMetadata.return_value = (None, None, None, [{}], None) + result = self._list_languages_for_keyboard_impl('foo') + self.assertEqual(result, "") def test_PackageCompletionOneLanguage(self): - open(os.path.join(self.cacheDir, 'khmer_angkor'), 'w') - self.mockGetMetadata.return_value = ( - None, None, None, - [{'languages': [{'name': 'Central Khmer (Khmer, Cambodia)', 'id': 'km'}]}], - None) - result = self._list_languages_for_keyboard_impl('khmer_angkor') - self.assertEqual(result, "km") + with open(os.path.join(self.cacheDir, 'khmer_angkor'), 'w'): + self.mockGetMetadata.return_value = ( + None, None, None, + [{'languages': [{'name': 'Central Khmer (Khmer, Cambodia)', 'id': 'km'}]}], + None + ) + result = self._list_languages_for_keyboard_impl('khmer_angkor') + self.assertEqual(result, "km") def test_PackageCompletionMultipleLanguages(self): - open(os.path.join(self.cacheDir, 'sil_euro_latin'), 'w') - self.mockGetMetadata.return_value = ( - None, None, None, - [{'languages': [ - {'name': 'English', 'id': 'en'}, - {'name': 'French', 'id': 'fr'}, - {'name': 'German', 'id': 'de'}]}], - None) - result = self._list_languages_for_keyboard_impl('sil_euro_latin') - self.assertEqual(result, 'en\nfr\nde') + with open(os.path.join(self.cacheDir, 'sil_euro_latin'), 'w'): + self.mockGetMetadata.return_value = ( + None, None, None, + [{'languages': [ + {'name': 'English', 'id': 'en'}, + {'name': 'French', 'id': 'fr'}, + {'name': 'German', 'id': 'de'}]}], + None + ) + result = self._list_languages_for_keyboard_impl('sil_euro_latin') + self.assertEqual(result, 'en\nfr\nde') diff --git a/linux/scripts/install.sh b/linux/scripts/install.sh index 40f2eb8f5b09bcd7f6571fa272d660391349ebaa..55074f48481915a713d630926f56b3161d76b955 100755 --- a/linux/scripts/install.sh +++ b/linux/scripts/install.sh @@ -15,7 +15,7 @@ INSTALLDIR=${INSTALLDIR:-"/usr/local"} if [[ "${SUDOINSTALL}" != "no" ]]; then if [ "$EUID" -ne 0 ] then - echo "Please run 'make un/install' with sudo" + echo "Please run 'build.sh un/install' with sudo" exit fi fi @@ -24,7 +24,7 @@ if [ -f "/usr/share/ibus/component/keyman.xml" ] && [ "${SUDOINSTALL}" == "yes" if grep -Fq "/usr/lib/ibus" /usr/share/ibus/component/keyman.xml then echo "component file is in ibus-keyman package version so move it" - echo "run 'sudo make uninstall' to put it back" + echo "run 'sudo build.sh uninstall' to put it back" mv /usr/share/ibus/component/keyman.xml /usr/share/doc/ibus-keyman/ else echo "component file is local one so overwrite it" @@ -37,10 +37,10 @@ cd "$BASEDIR" cd ibus-keyman if [[ "${SUDOINSTALL}" == "uninstall" ]]; then - echo "doing make uninstall of ibus-keyman" + echo "doing build.sh uninstall of ibus-keyman" ./build.sh uninstall else - echo "doing make install of ibus-keyman" + echo "doing build.sh install of ibus-keyman" ./build.sh install fi cd "$BASEDIR" @@ -49,20 +49,20 @@ cd keyman-config echo "SUDOINSTALL: ${SUDOINSTALL}" if [[ "${SUDOINSTALL}" == "yes" ]]; then if [ ! -d build ]; then - echo "keyman-config must be built before it is installed. Run 'make configure' if needed then 'make'" + echo "keyman-config must be built before it is installed. Run 'build.sh configure build'." exit 1 fi echo "doing sudo glib-compile-schemas for keyman-config" cp com.keyman.gschema.xml /usr/share/glib-2.0/schemas/ glib-compile-schemas /usr/share/glib-2.0/schemas/ echo "doing sudo install of keyman-config" - make install + ./build.sh install elif [[ "${SUDOINSTALL}" == "uninstall" ]]; then echo "doing sudo uninstall of keyman-config" - make uninstall + ./build.sh uninstall else echo "doing /tmp install of keyman-config" - make install-temp + ./build.sh install fi cd "$BASEDIR" diff --git a/linux/scripts/package-build.inc.sh b/linux/scripts/package-build.inc.sh index 39ea70f7d1719859c9bba2a3de0240b013628b52..6c249891c1633a3bcd5da6279f6fc13a316cc29a 100644 --- a/linux/scripts/package-build.inc.sh +++ b/linux/scripts/package-build.inc.sh @@ -28,10 +28,7 @@ function downloadSource() { if [ "${proj:=}" == "keyman" ]; then cd "${BASEDIR}" || exit - fi - - if [ "${proj}" == "keyman" ]; then - make clean + ./build.sh clean fi # Update tier in Debian watch files (replacing any previously set tier) and remove comment diff --git a/linux/scripts/reconf.sh b/linux/scripts/reconf.sh index cd74571a4a1dc696934de83f04d395d4850cda05..4e4f5c1b115cbc37e02905dfcdc262ccb9941024 100755 --- a/linux/scripts/reconf.sh +++ b/linux/scripts/reconf.sh @@ -21,7 +21,7 @@ cd "$BASEDIR/ibus-keyman" ./build.sh clean configure cd "$BASEDIR/keyman-config" -make clean +./build.sh clean cd keyman_config sed \ diff --git a/oem/firstvoices/ios/exportAppStore.plist b/oem/firstvoices/ios/exportAppStore.plist index 123af31a0c437e0927eddef23f03b3a850374642..c4ac972730f0715629ebd05914f11ab9f1026ca6 100644 --- a/oem/firstvoices/ios/exportAppStore.plist +++ b/oem/firstvoices/ios/exportAppStore.plist @@ -7,9 +7,9 @@ teamID D7TR486TEH signingCertificate - EFA9DE793B2A25F38270468AE62060CFF55CD634 + C4726D4C8BD4C2FE38A551B84CEDB8E4B3FD12D6 installerSigningCertificate - EFA9DE793B2A25F38270468AE62060CFF55CD634 + C4726D4C8BD4C2FE38A551B84CEDB8E4B3FD12D6 provisioningProfiles com.firstvoices.keyboards diff --git a/package-lock.json b/package-lock.json index 865cff0557fbc4e0bec564ed631cd3520c658368..d78384e30fc3d19b8a5008e283abf8a4eb4dc4d6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,15 +35,16 @@ "devDependencies": { "@typescript-eslint/eslint-plugin": "^5.59.1", "chai": "^4.3.4", + "esbuild": "^0.15.16", "eslint": "^8.39.0", "eslint-config-standard-with-typescript": "^34.0.1", "eslint-plugin-import": "^2.27.5", "eslint-plugin-n": "^15.7.0", "eslint-plugin-promise": "^6.1.1", - "esbuild": "^0.15.15", "mocha": "^10.0.0", "mocha-teamcity-reporter": "^4.0.0", "ts-node": "^10.9.1", + "tslib": "^2.5.2", "typescript": "^4.9.5" } }, @@ -481,6 +482,17 @@ "typescript": "^4.9.5" } }, + "common/web/tslib": { + "name": "@keymanapp/tslib", + "dependencies": { + "tslib": "^2.5.2", + "typescript": "^4.9.5" + }, + "devDependencies": { + "@keymanapp/resources-gosh": "*", + "esbuild": "^0.15.16" + } + }, "common/web/types": { "name": "@keymanapp/common-types", "license": "MIT", @@ -2252,6 +2264,38 @@ "node": ">=12" } }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", @@ -2325,38 +2369,6 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, - "node_modules/@esbuild/android-arm": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.15.tgz", - "integrity": "sha512-JJjZjJi2eBL01QJuWjfCdZxcIgot+VoK6Fq7eKF9w4YHm9hwl7nhBR1o2Wnt/WcANk5l9SkpvrldW1PLuXxcbw==", - "cpu": [ - "arm" - ], - "dev": true, - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=12" - } - }, - "node_modules/@esbuild/linux-loong64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.15.tgz", - "integrity": "sha512-lhz6UNPMDXUhtXSulw8XlFAtSYO26WmHQnCi2Lg2p+/TMiJKNLtZCYUxV4wG6rZMzXmr8InGpNwk+DLT2Hm0PA==", - "cpu": [ - "loong64" - ], - "dev": true, - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=12" - } - }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -2530,6 +2542,10 @@ "resolved": "common/tools/sourcemap-path-remapper", "link": true }, + "node_modules/@keymanapp/tslib": { + "resolved": "common/web/tslib", + "link": true + }, "node_modules/@keymanapp/web-sentry-manager": { "resolved": "common/web/sentry-manager", "link": true @@ -2777,6 +2793,11 @@ "node": ">=6" } }, + "node_modules/@sentry/browser/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sentry/cli": { "version": "2.2.0", "dev": true, @@ -2811,6 +2832,11 @@ "node": ">=6" } }, + "node_modules/@sentry/core/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sentry/hub": { "version": "5.30.0", "license": "BSD-3-Clause", @@ -2823,6 +2849,11 @@ "node": ">=6" } }, + "node_modules/@sentry/hub/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sentry/minimal": { "version": "5.30.0", "license": "BSD-3-Clause", @@ -2835,6 +2866,11 @@ "node": ">=6" } }, + "node_modules/@sentry/minimal/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sentry/node": { "version": "6.19.6", "license": "BSD-3-Clause", @@ -2908,6 +2944,11 @@ "node": ">=6" } }, + "node_modules/@sentry/node/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sentry/types": { "version": "5.30.0", "license": "BSD-3-Clause", @@ -2926,6 +2967,11 @@ "node": ">=6" } }, + "node_modules/@sentry/utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==" + }, "node_modules/@sindresorhus/is": { "version": "4.6.0", "license": "MIT", @@ -4944,9 +4990,9 @@ "license": "MIT" }, "node_modules/esbuild": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.15.tgz", - "integrity": "sha512-TEw/lwK4Zzld9x3FedV6jy8onOUHqcEX3ADFk4k+gzPUwrxn8nWV62tH0udo8jOtjFodlEfc4ypsqX3e+WWO6w==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", "dev": true, "hasInstallScript": true, "bin": { @@ -4956,34 +5002,34 @@ "node": ">=12" }, "optionalDependencies": { - "@esbuild/android-arm": "0.15.15", - "@esbuild/linux-loong64": "0.15.15", - "esbuild-android-64": "0.15.15", - "esbuild-android-arm64": "0.15.15", - "esbuild-darwin-64": "0.15.15", - "esbuild-darwin-arm64": "0.15.15", - "esbuild-freebsd-64": "0.15.15", - "esbuild-freebsd-arm64": "0.15.15", - "esbuild-linux-32": "0.15.15", - "esbuild-linux-64": "0.15.15", - "esbuild-linux-arm": "0.15.15", - "esbuild-linux-arm64": "0.15.15", - "esbuild-linux-mips64le": "0.15.15", - "esbuild-linux-ppc64le": "0.15.15", - "esbuild-linux-riscv64": "0.15.15", - "esbuild-linux-s390x": "0.15.15", - "esbuild-netbsd-64": "0.15.15", - "esbuild-openbsd-64": "0.15.15", - "esbuild-sunos-64": "0.15.15", - "esbuild-windows-32": "0.15.15", - "esbuild-windows-64": "0.15.15", - "esbuild-windows-arm64": "0.15.15" + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" } }, "node_modules/esbuild-android-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.15.tgz", - "integrity": "sha512-F+WjjQxO+JQOva3tJWNdVjouFMLK6R6i5gjDvgUthLYJnIZJsp1HlF523k73hELY20WPyEO8xcz7aaYBVkeg5Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", "cpu": [ "x64" ], @@ -4997,9 +5043,9 @@ } }, "node_modules/esbuild-android-arm64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.15.tgz", - "integrity": "sha512-attlyhD6Y22jNyQ0fIIQ7mnPvDWKw7k6FKnsXlBvQE6s3z6s6cuEHcSgoirquQc7TmZgVCK5fD/2uxmRN+ZpcQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", "cpu": [ "arm64" ], @@ -5013,9 +5059,9 @@ } }, "node_modules/esbuild-darwin-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.15.tgz", - "integrity": "sha512-ohZtF8W1SHJ4JWldsPVdk8st0r9ExbAOSrBOh5L+Mq47i696GVwv1ab/KlmbUoikSTNoXEhDzVpxUR/WIO19FQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", "cpu": [ "x64" ], @@ -5029,9 +5075,9 @@ } }, "node_modules/esbuild-darwin-arm64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.15.tgz", - "integrity": "sha512-P8jOZ5zshCNIuGn+9KehKs/cq5uIniC+BeCykvdVhx/rBXSxmtj3CUIKZz4sDCuESMbitK54drf/2QX9QHG5Ag==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", "cpu": [ "arm64" ], @@ -5045,9 +5091,9 @@ } }, "node_modules/esbuild-freebsd-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.15.tgz", - "integrity": "sha512-KkTg+AmDXz1IvA9S1gt8dE24C8Thx0X5oM0KGF322DuP+P3evwTL9YyusHAWNsh4qLsR80nvBr/EIYs29VSwuA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", "cpu": [ "x64" ], @@ -5061,9 +5107,9 @@ } }, "node_modules/esbuild-freebsd-arm64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.15.tgz", - "integrity": "sha512-FUcML0DRsuyqCMfAC+HoeAqvWxMeq0qXvclZZ/lt2kLU6XBnDA5uKTLUd379WYEyVD4KKFctqWd9tTuk8C/96g==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", "cpu": [ "arm64" ], @@ -5077,9 +5123,9 @@ } }, "node_modules/esbuild-linux-32": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.15.tgz", - "integrity": "sha512-q28Qn5pZgHNqug02aTkzw5sW9OklSo96b5nm17Mq0pDXrdTBcQ+M6Q9A1B+dalFeynunwh/pvfrNucjzwDXj+Q==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", "cpu": [ "ia32" ], @@ -5093,9 +5139,9 @@ } }, "node_modules/esbuild-linux-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.15.tgz", - "integrity": "sha512-217KPmWMirkf8liO+fj2qrPwbIbhNTGNVtvqI1TnOWJgcMjUWvd677Gq3fTzXEjilkx2yWypVnTswM2KbXgoAg==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", "cpu": [ "x64" ], @@ -5109,9 +5155,9 @@ } }, "node_modules/esbuild-linux-arm": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.15.tgz", - "integrity": "sha512-RYVW9o2yN8yM7SB1yaWr378CwrjvGCyGybX3SdzPHpikUHkME2AP55Ma20uNwkNyY2eSYFX9D55kDrfQmQBR4w==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", "cpu": [ "arm" ], @@ -5125,9 +5171,9 @@ } }, "node_modules/esbuild-linux-arm64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.15.tgz", - "integrity": "sha512-/ltmNFs0FivZkYsTzAsXIfLQX38lFnwJTWCJts0IbCqWZQe+jjj0vYBNbI0kmXLb3y5NljiM5USVAO1NVkdh2g==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", "cpu": [ "arm64" ], @@ -5141,9 +5187,9 @@ } }, "node_modules/esbuild-linux-mips64le": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.15.tgz", - "integrity": "sha512-PksEPb321/28GFFxtvL33yVPfnMZihxkEv5zME2zapXGp7fA1X2jYeiTUK+9tJ/EGgcNWuwvtawPxJG7Mmn86A==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", "cpu": [ "mips64el" ], @@ -5157,9 +5203,9 @@ } }, "node_modules/esbuild-linux-ppc64le": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.15.tgz", - "integrity": "sha512-ek8gJBEIhcpGI327eAZigBOHl58QqrJrYYIZBWQCnH3UnXoeWMrMZLeeZL8BI2XMBhP+sQ6ERctD5X+ajL/AIA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", "cpu": [ "ppc64" ], @@ -5173,9 +5219,9 @@ } }, "node_modules/esbuild-linux-riscv64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.15.tgz", - "integrity": "sha512-H5ilTZb33/GnUBrZMNJtBk7/OXzDHDXjIzoLXHSutwwsLxSNaLxzAaMoDGDd/keZoS+GDBqNVxdCkpuiRW4OSw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", "cpu": [ "riscv64" ], @@ -5189,9 +5235,9 @@ } }, "node_modules/esbuild-linux-s390x": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.15.tgz", - "integrity": "sha512-jKaLUg78mua3rrtrkpv4Or2dNTJU7bgHN4bEjT4OX4GR7nLBSA9dfJezQouTxMmIW7opwEC5/iR9mpC18utnxQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", "cpu": [ "s390x" ], @@ -5205,9 +5251,9 @@ } }, "node_modules/esbuild-netbsd-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.15.tgz", - "integrity": "sha512-aOvmF/UkjFuW6F36HbIlImJTTx45KUCHJndtKo+KdP8Dhq3mgLRKW9+6Ircpm8bX/RcS3zZMMmaBLkvGY06Gvw==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", "cpu": [ "x64" ], @@ -5221,9 +5267,9 @@ } }, "node_modules/esbuild-openbsd-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.15.tgz", - "integrity": "sha512-HFFX+WYedx1w2yJ1VyR1Dfo8zyYGQZf1cA69bLdrHzu9svj6KH6ZLK0k3A1/LFPhcEY9idSOhsB2UyU0tHPxgQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", "cpu": [ "x64" ], @@ -5237,9 +5283,9 @@ } }, "node_modules/esbuild-sunos-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.15.tgz", - "integrity": "sha512-jOPBudffG4HN8yJXcK9rib/ZTFoTA5pvIKbRrt3IKAGMq1EpBi4xoVoSRrq/0d4OgZLaQbmkHp8RO9eZIn5atA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", "cpu": [ "x64" ], @@ -5253,9 +5299,9 @@ } }, "node_modules/esbuild-windows-32": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.15.tgz", - "integrity": "sha512-MDkJ3QkjnCetKF0fKxCyYNBnOq6dmidcwstBVeMtXSgGYTy8XSwBeIE4+HuKiSsG6I/mXEb++px3IGSmTN0XiA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", "cpu": [ "ia32" ], @@ -5269,9 +5315,9 @@ } }, "node_modules/esbuild-windows-64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.15.tgz", - "integrity": "sha512-xaAUIB2qllE888SsMU3j9nrqyLbkqqkpQyWVkfwSil6BBPgcPk3zOFitTTncEKCLTQy3XV9RuH7PDj3aJDljWA==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", "cpu": [ "x64" ], @@ -5285,9 +5331,9 @@ } }, "node_modules/esbuild-windows-arm64": { - "version": "0.15.15", - "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.15.tgz", - "integrity": "sha512-ttuoCYCIJAFx4UUKKWYnFdrVpoXa3+3WWkXVI6s09U+YjhnyM5h96ewTq/WgQj9LFSIlABQvadHSOQyAVjW5xQ==", + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", "cpu": [ "arm64" ], @@ -6577,11 +6623,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/google-closure-compiler-java": { - "version": "20200224.0.0", - "dev": true, - "license": "Apache-2.0" - }, "node_modules/gopd": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", @@ -6910,20 +6951,6 @@ "version": "2.0.4", "license": "ISC" }, - "node_modules/internal-slot": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", - "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", - "dev": true, - "dependencies": { - "get-intrinsic": "^1.2.0", - "has": "^1.0.3", - "side-channel": "^1.0.4" - }, - "engines": { - "node": ">= 0.4" - } - }, "node_modules/inline-source-map": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/inline-source-map/-/inline-source-map-0.6.2.tgz", @@ -6942,6 +6969,20 @@ "node": ">=0.10.0" } }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/interpret": { "version": "1.4.0", "dev": true, @@ -7743,18 +7784,18 @@ "version": "4.4.2", "license": "MIT" }, - "node_modules/lodash.merge": { - "version": "4.6.2", - "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", - "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", - "dev": true - }, "node_modules/lodash.memoize": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-3.0.4.tgz", "integrity": "sha512-eDn9kqrAmVUC1wmZvlQ6Uhde44n+tXpqPrN8olQJbttgh0oKclk+SF54P47VEGE9CEiMeRwAP8BaM7UHvBkz2A==", "dev": true }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "node_modules/lodash.set": { "version": "4.3.2", "license": "MIT" @@ -10287,8 +10328,9 @@ } }, "node_modules/tslib": { - "version": "1.14.1", - "license": "0BSD" + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.2.tgz", + "integrity": "sha512-5svOrSA2w3iGFDs1HibEVBGbDrAY82bFQ3HZ3ixB+88nsbsWQoKqDRb5UBYAUPEzbBn6dAp5gRNXglySbx1MlA==" }, "node_modules/tsutils": { "version": "3.21.0", @@ -10305,6 +10347,12 @@ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" } }, + "node_modules/tsutils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, "node_modules/tunnel": { "version": "0.0.6", "license": "MIT", @@ -11080,16 +11128,17 @@ "@keymanapp/lexical-model-layer": "*", "@keymanapp/models-types": "*", "@keymanapp/recorder-core": "*", + "@keymanapp/tslib": "*", "@keymanapp/web-utils": "*", "@types/node": "^11.9.4", - "eventemitter3": "^4.0.0" + "eventemitter3": "^4.0.0", + "tslib": "^2.5.2" }, "devDependencies": { "@keymanapp/resources-gosh": "*", "@keymanapp/web-sentry-manager": "*", "@sentry/cli": "2.2.0", "chai": "^4.3.4", - "google-closure-compiler-java": "^20200224.0.0", "karma": "^6.4.1", "karma-browserstack-launcher": "^1.6.0", "karma-chai": "^0.1.0", diff --git a/package.json b/package.json index 11fa521e1863cdf9640f979070ec2f8860e71100..787fe68e5a2b5c08988f0cae658d88de9f0b3fa3 100644 --- a/package.json +++ b/package.json @@ -2,18 +2,19 @@ "name": "root", "private": true, "devDependencies": { - "chai": "^4.3.4", - "esbuild": "^0.15.15", - "mocha": "^10.0.0", - "mocha-teamcity-reporter": "^4.0.0", - "ts-node": "^10.9.1", - "typescript": "^4.9.5", "@typescript-eslint/eslint-plugin": "^5.59.1", + "chai": "^4.3.4", + "esbuild": "^0.15.16", "eslint": "^8.39.0", "eslint-config-standard-with-typescript": "^34.0.1", "eslint-plugin-import": "^2.27.5", "eslint-plugin-n": "^15.7.0", - "eslint-plugin-promise": "^6.1.1" + "eslint-plugin-promise": "^6.1.1", + "mocha": "^10.0.0", + "mocha-teamcity-reporter": "^4.0.0", + "ts-node": "^10.9.1", + "tslib": "^2.5.2", + "typescript": "^4.9.5" }, "scripts": {}, "workspaces": [ @@ -37,10 +38,10 @@ "web" ], "dependencies": { + "@keymanapp/common-types": "file:common/web/types", + "@keymanapp/developer-test-helpers": "file:developer/src/common/web/test-helpers", "@keymanapp/hextobin": "file:common/tools/hextobin", "@keymanapp/keyman-version": "file:common/web/keyman-version", - "@keymanapp/common-types": "file:common/web/types", - "@keymanapp/ldml-keyboard-constants": "file:core/include/ldml", - "@keymanapp/developer-test-helpers": "file:developer/src/common/web/test-helpers" + "@keymanapp/ldml-keyboard-constants": "file:core/include/ldml" } } diff --git a/resources/build/build-utils-ci.inc.sh b/resources/build/build-utils-ci.inc.sh index de36ae9e9b606556869c22f2f7492240825070f7..cab449a4636d59e5f9a388bfad84601d165e0956 100644 --- a/resources/build/build-utils-ci.inc.sh +++ b/resources/build/build-utils-ci.inc.sh @@ -100,18 +100,7 @@ function _builder_publish_npm_package() { dry_run=--dry-run fi - # We use --no-git-tag-version because our CI system controls version numbering - # and already tags releases. We also want to have the version of this match - # the release of Keyman Developer -- these two versions should be in sync. - # Because this is a large repo with multiple projects and build systems, it's - # better for us that individual build systems don't take too much ownership of - # git tagging. :) - npm version --allow-same-version --no-git-tag-version --no-commit-hooks "$VERSION_WITH_TAG" - - # Update all @keymanapp/* [*]dependencies in package.json to the current - # version-with-tag, so that the published version has precise dependencies, - # and we don't accidentally end up with either older or newer deps here - _builder_npm_set_dependency_version + _builder_write_npm_version # Note: In either case, npm publish MUST be given --access public to publish a # package in the @keymanapp scope on the public npm package index. @@ -128,18 +117,33 @@ function _builder_publish_npm_package() { fi } -# Updates all @keymanapp/* [*]dependencies in package.json to the current -# version-with-tag, so that the published version has precise dependencies, and -# we don't accidentally end up with either older or newer deps. This overwrites -# the local package.json, so it does need to be restored afterwards -function _builder_npm_set_dependency_version() { - cat package.json | "$JQ" --arg VERSION_WITH_TAG "$VERSION_WITH_TAG" \ - ' - . + - (try { dependencies: (.dependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + - (try { devDependencies: (.devDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + - (try { bundleDependencies: (.bundleDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + - (try { optionalDependencies: (.optionalDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) - ' > package1.json - mv -f package1.json package.json +function _builder_write_npm_version() { + # We use --no-git-tag-version because our CI system controls version numbering + # and already tags releases. We also want to have the version of this match + # the release of Keyman Developer -- these two versions should be in sync. + # Because this is a large repo with multiple projects and build systems, it's + # better for us that individual build systems don't take too much ownership of + # git tagging. :) + if ! "$JQ" -e '.version' package.json > /dev/null; then + pushd "$KEYMAN_ROOT" > /dev/null + npm version --allow-same-version --no-git-tag-version --no-commit-hooks --workspaces "$VERSION_WITH_TAG" + popd > /dev/null + fi + + # Updates all @keymanapp/* [*]dependencies in all package.jsons to the current + # version-with-tag, so that the published version has precise dependencies, and + # we don't accidentally end up with either older or newer deps. This overwrites + # the local package.json files, so they do need to be restored afterwards + find "$KEYMAN_ROOT" -name "package.json" -not -path '*/node_modules/*' -print0 | \ + while IFS= read -r -d '' line; do + cat "$line" | "$JQ" --arg VERSION_WITH_TAG "$VERSION_WITH_TAG" \ + ' + . + + (try { dependencies: (.dependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + + (try { devDependencies: (.devDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + + (try { bundleDependencies: (.bundleDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + + (try { optionalDependencies: (.optionalDependencies | to_entries | . + map(select(.key | match("@keymanapp/.*")) .value |= $VERSION_WITH_TAG) | from_entries) } catch {}) + ' > "${line}_" + mv -f "${line}_" "$line" + done } \ No newline at end of file diff --git a/tsconfig.cjs.json b/tsconfig.cjs.json index 3508de15c7f2bd727c4ebcb7c6567024bb0b4d9d..4695672e90831e10d897d2f540feed5943d3689d 100644 --- a/tsconfig.cjs.json +++ b/tsconfig.cjs.json @@ -7,30 +7,14 @@ "references": [ { "path": "./common/web/gesture-recognizer/tsconfig.json" }, { "path": "./common/web/gesture-recognizer/src/tools/unit-test-resources/tsconfig.json" }, - { "path": "./common/web/input-processor/tsconfig.json" }, - { "path": "./common/web/keyboard-processor/tsconfig.json" }, - { "path": "./common/web/recorder/tsconfig.json" }, - { "path": "./common/web/sentry-manager/src/tsconfig.json" }, - { "path": "./common/web/utils/tsconfig.json" }, - - { "path": "./common/models/templates/tsconfig.json" }, - { "path": "./common/models/types/tsconfig.json" }, - { "path": "./common/models/wordbreakers/tsconfig.json" }, { "path": "./common/predictive-text/testing/one-stage-embedded-webworker/tsconfig.json" }, { "path": "./common/predictive-text/testing/two-stage-embedded-webworker/tsconfig.json" }, { "path": "./common/predictive-text/testing/two-stage-embedded-webworker/worker/tsconfig.json" }, - { "path": "./common/predictive-text/tsconfig.json" }, { "path": "./developer/src/server/tsconfig.json" }, { "path": "./resources/build/version/tsconfig.json" }, { "path": "./resources/build/version/tsconfig.production.json" }, // { "path": "./web/bulk_rendering/tsconfig.json" }, - { "path": "./web/src/tsconfig.all.json" }, - // { "path": "./web/tools/recorder/tsconfig.json" }, - // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, - { "path": "./common/web/lm-message-types/" }, - { "path": "./common/web/lm-worker/" }, - { "path": "./common/web/keyman-version/" }, ] } \ No newline at end of file diff --git a/tsconfig.esm.json b/tsconfig.esm.json index 40c63618c94fd80adfbf4dc2e68bb4b8a1a388d9..de47c84fdeafa7cfbf90c721ac86bd3a0abedd2c 100644 --- a/tsconfig.esm.json +++ b/tsconfig.esm.json @@ -23,5 +23,22 @@ { "path": "./common/web/keyman-version" }, { "path": "./common/web/types/" }, + + { "path": "./common/web/input-processor/tsconfig.json" }, + { "path": "./common/web/keyboard-processor/tsconfig.json" }, + { "path": "./common/web/recorder/tsconfig.json" }, + { "path": "./common/web/sentry-manager/src/tsconfig.json" }, + { "path": "./common/web/utils/tsconfig.json" }, + + { "path": "./common/models/templates/tsconfig.json" }, + { "path": "./common/models/types/tsconfig.json" }, + { "path": "./common/models/wordbreakers/tsconfig.json" }, + { "path": "./common/predictive-text/tsconfig.json" }, + + { "path": "./web/src/tsconfig.all.json" }, + // { "path": "./web/tools/recorder/tsconfig.json" }, + // { "path": "./web/tools/sourcemap-root/tsconfig.json" }, + { "path": "./common/web/lm-message-types/" }, + { "path": "./common/web/lm-worker/" }, ] } \ No newline at end of file diff --git a/web/build.sh b/web/build.sh index f7701259be037293c14d40d02ace2f0796a159ab..35ee1b5ebbe943773eacd1c85205715db3390fe1 100755 --- a/web/build.sh +++ b/web/build.sh @@ -1,9 +1,6 @@ #!/usr/bin/env bash # # Compile keymanweb and copy compiled javascript and resources to output/embedded folder -# - -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -42,9 +39,9 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \ ":engine/main Builds all common code used by KMW's app/-level targets" \ ":engine/osk Builds the Web OSK module" \ ":engine/package-cache Subset used to collate keyboards and request them from the cloud" \ - ":engine/paths Subset used to configure KMW" - -# ":app/browser The website-integrating, browser-based version of KMW" \ + ":engine/paths Subset used to configure KMW" \ + ":samples Builds all needed resources for the KMW sample-page set" \ + "--ci+ Set to utilize CI-based test configurations & reporting." # Possible TODO? # "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \ @@ -52,10 +49,6 @@ builder_describe "Builds engine modules for Keyman Engine for Web (KMW)." \ builder_describe_outputs \ configure /node_modules -# build:app/webview build/app/webview/lib/index.js \ - - # TODO: app/ui linkage. - builder_parse "$@" #### Build action definitions #### @@ -68,11 +61,9 @@ builder_run_child_actions clean ## Clean actions -# If a full-on general clean was requested, we can nuke the entire build folder. -if builder_start_action clean; then - rm -rf ./build - builder_finish_action success clean -fi +###--- Future tie-in: if #8831 gets accepted, uncomment the next two lines. ---### +# # If a full-on general clean was requested, we can nuke the entire build folder. +# builder_run_action clean:project rm -rf ./build builder_run_child_actions configure @@ -105,22 +96,24 @@ builder_run_child_actions build:app/webview # Uses literally everything `engine/` above builder_run_child_actions build:app/browser -builder_run_child_actions test +# Uses app/browser above for engine type-defs +builder_run_child_actions build:app/ui -if builder_has_action build:app/browser; then - builder_warn "Modularization work is not yet complete; consumers may find needed API or components to be missing" -fi +# Needs both app/browser and app/ui. +builder_run_child_actions build:samples + +builder_run_child_actions test if builder_start_action test; then - ./test.sh :engine + TEST_OPTS= + if builder_has_option --ci; then + TEST_OPTS=--ci + fi + ./test.sh $TEST_OPTS builder_finish_action success test fi -if builder_has_action build:app/ui; then - builder_die "Modularization work is not yet complete; builds dependent on this will fail." -fi - ### Temporary tie-in for pre-modularization version of the gesture-recognizer if builder_start_action build:engine/gestures; then diff --git a/web/ci.sh b/web/ci.sh index d160cc41902f52065f0527963dc3fcafa44387fb..2f038b90b2ca00e650261424096b7a0e50329101 100755 --- a/web/ci.sh +++ b/web/ci.sh @@ -25,6 +25,7 @@ cd "$THIS_SCRIPT_PATH" S_KEYMAN_COM= builder_describe "Defines and implements the CI build steps for Keyman Engine for Web (KMW)." \ + "@/web/src/tools/building/sourcemap-root prepare:s.keyman.com" \ "build" \ "test Runs all unit tests." \ "post-test Runs post-test cleanup. Should be run even if a prior step fails." \ @@ -48,7 +49,9 @@ if builder_start_action build; then # - clean: make extra-sure that no prior build products exist. # - also useful when validating this script on a local dev machine! # - build: then do the ACTUAL build. - ./build.sh configure clean build + # one option: + # - --ci: For app/browser, outputs 'release' config filesize profiling logs + ./build.sh configure clean build --ci builder_finish_action success build fi @@ -56,15 +59,17 @@ fi if builder_start_action test; then # Testing step: run ALL unit tests, including those of the submodules. - # For only top-level build-product tests, specify the :engine target - # For all others, specify only the :libraries target - FLAGS= - + OPTIONS= if ! builder_is_debug_build; then - FLAGS=--ci + OPTIONS=--ci fi - ./test.sh $FLAGS + # No --reporter option exists yet for the headless modules. + + $KEYMAN_ROOT/common/web/keyboard-processor/build.sh test $OPTIONS + $KEYMAN_ROOT/common/web/input-processor/build.sh test $OPTIONS + + ./build.sh test $OPTIONS builder_finish_action success test fi @@ -115,14 +120,14 @@ if builder_start_action prepare:s.keyman.com; then echo "FOLDER: $BASE_PUBLISH_FOLDER" mkdir -p "$BASE_PUBLISH_FOLDER" - cp -Rf build/app/browser/release/* "$BASE_PUBLISH_FOLDER" - cp -Rf build/app/resources/* "$BASE_PUBLISH_FOLDER/resources" - cp -Rf build/app/ui/release/* "$BASE_PUBLISH_FOLDER" + # s.keyman.com - release-config only. It's notably smaller, thus far more favorable + # for distribution via cloud service. + cp -Rf build/publish/release/* "$BASE_PUBLISH_FOLDER" # Third phase: tweak the sourcemaps # We can use an alt-mode of Web's sourcemap-root tool for this. for sourcemap in "$BASE_PUBLISH_FOLDER/"*.map; do - node build/tools/building/sourcemap-root/index.mjs null "$sourcemap" --sourceRoot "https://s.keyman.com/kmw/engine/$VERSION/src" + node "$KEYMAN_ROOT/web/build/tools/building/sourcemap-root/index.mjs" null "$sourcemap" --sourceRoot "https://s.keyman.com/kmw/engine/$VERSION/src" done # Actual construction of the PR will be left to CI-config scripting for now. @@ -161,20 +166,9 @@ if builder_start_action prepare:downloads.keyman.com; then fi fi - pushd build/app/browser/release - "${COMPRESS_CMD}" $COMPRESS_ADD ../../../../$ZIP * - cd .. - "${COMPRESS_CMD}" $COMPRESS_ADD ../../../$ZIP debug - popd - - pushd build/app/resources - "${COMPRESS_CMD}" $COMPRESS_ADD ../../../$ZIP * - popd - - pushd build/app/ui/release + pushd build/publish + # Zip both the 'debug' and 'release' configurations together. "${COMPRESS_CMD}" $COMPRESS_ADD ../../../../$ZIP * - cd .. - "${COMPRESS_CMD}" $COMPRESS_ADD ../../../$ZIP debug popd # --- Second action artifact - the 'static' folder (hosted user testing on downloads.keyman.com) --- diff --git a/web/common.inc.sh b/web/common.inc.sh index 0a615962de2a7bb8cdeda65958f3401295c243df..96a3c61bbd8eccf782d9330bbfdd2130fa9fa072 100644 --- a/web/common.inc.sh +++ b/web/common.inc.sh @@ -20,14 +20,66 @@ compile ( ) { fi local COMPILE_TARGET="$1" + local BUNDLE_FLAG="${2:-}" tsc -b "${KEYMAN_ROOT}/web/src/$COMPILE_TARGET" -v - # COMPILE_TARGET entries are all prefixed with `engine`, so remove that. if [ -f "./build-bundler.js" ]; then - node "./build-bundler.js" + node "./build-bundler.js" "$BUNDLE_FLAG" # So... tsc does declaration-bundling on its own pretty well, at least for local development. tsc --emitDeclarationOnly --outFile "${KEYMAN_ROOT}/web/build/$COMPILE_TARGET/lib/index.d.ts" -p "${KEYMAN_ROOT}/web/src/$COMPILE_TARGET" fi +} + +_copy_dir_if_exists ( ) { + local SRC=$1 + local DST=$2 + + if [ -d "$SRC" ]; then + cp -rf "$SRC/." "$DST" + fi +} + +# Copies top-level build artifacts into common 'debug' and 'release' config folders +# for use in publishing. +prepare ( ) { + local CHILD_BUILD_ROOT="$KEYMAN_ROOT/web/build/app" + local PUBLISH_BUILD_ROOT="$KEYMAN_ROOT/web/build/publish" + + mkdir -p "$PUBLISH_BUILD_ROOT/debug" + mkdir -p "$PUBLISH_BUILD_ROOT/release" + + _copy_dir_if_exists "$CHILD_BUILD_ROOT/browser/debug" "$PUBLISH_BUILD_ROOT/debug" + _copy_dir_if_exists "$CHILD_BUILD_ROOT/browser/release" "$PUBLISH_BUILD_ROOT/release" + + _copy_dir_if_exists "$CHILD_BUILD_ROOT/resources" "$PUBLISH_BUILD_ROOT/debug" + _copy_dir_if_exists "$CHILD_BUILD_ROOT/resources" "$PUBLISH_BUILD_ROOT/release" + + _copy_dir_if_exists "$CHILD_BUILD_ROOT/ui/debug" "$PUBLISH_BUILD_ROOT/debug" + _copy_dir_if_exists "$CHILD_BUILD_ROOT/ui/release" "$PUBLISH_BUILD_ROOT/release" +} + +# Runs all headless tests corresponding to the specified target. +# This should be called from the working directory of a child project's +# build script. +# +# ### Parameters +# +# * 1: `product` the folder under src/test/auto/headless containing the +# child project's tests +# +# ### Example +# +# ```bash +# # from engine/osk +# test-headless osk +# ``` +test-headless ( ) { + TEST_OPTS= + if builder_has_option --ci; then + TEST_OPTS="--reporter mocha-teamcity-reporter" + fi + + mocha --recursive "${KEYMAN_ROOT}/web/src/test/auto/headless/$1" $TEST_OPTS } \ No newline at end of file diff --git a/web/package.json b/web/package.json index 120f5c350c4dafa6fb2510068570fdbc1cafc92f..21b17525a6701bd4ba8336dbfa613a0af00b08f3 100644 --- a/web/package.json +++ b/web/package.json @@ -2,6 +2,10 @@ "name": "keyman", "description": "Facilitates text input in any language.", "exports": { + "./app/browser": { + "types": "./build/app/browser/obj/test-index.d.js", + "import": "./build/app/browser/obj/test-index.js" + }, "./engine/attachment": { "types": "./build/engine/attachment/obj/index.d.ts", "import": "./build/engine/attachment/obj/index.js" @@ -68,7 +72,6 @@ "@keymanapp/web-sentry-manager": "*", "@sentry/cli": "2.2.0", "chai": "^4.3.4", - "google-closure-compiler-java": "^20200224.0.0", "karma": "^6.4.1", "karma-browserstack-launcher": "^1.6.0", "karma-chai": "^0.1.0", @@ -103,9 +106,11 @@ "@keymanapp/lexical-model-layer": "*", "@keymanapp/models-types": "*", "@keymanapp/recorder-core": "*", + "@keymanapp/tslib": "*", "@keymanapp/web-utils": "*", "@types/node": "^11.9.4", - "eventemitter3": "^4.0.0" + "eventemitter3": "^4.0.0", + "tslib": "^2.5.2" }, "type": "module" } diff --git a/web/src/app/browser/build-bundler.js b/web/src/app/browser/build-bundler.js index 3d855f92ed26fa90cca23c87ecfa472368d7260c..cc81f7c402c301ae3739f5c6e928fca8d7603d7b 100644 --- a/web/src/app/browser/build-bundler.js +++ b/web/src/app/browser/build-bundler.js @@ -6,8 +6,28 @@ */ import esbuild from 'esbuild'; -import { spawn } from 'child_process'; import fs from 'fs'; +import { determineNeededDowncompileHelpers, buildTslibTreeshaker } from '@keymanapp/tslib/esbuild-tools'; + +let EMIT_FILESIZE_PROFILE = false; + +if(process.argv.length > 2) { + for(let i = 2; i < process.argv.length; i++) { + const arg = process.argv[i]; + + switch(arg) { + case '': + break; + case '--ci': + EMIT_FILESIZE_PROFILE=true + break; + // May add other options if desired in the future. + default: + console.error("Invalid command-line option set for script; only --ci is permitted."); + process.exit(1); + } + } +} /* * Refer to https://github.com/microsoft/TypeScript/issues/13721#issuecomment-307259227 - @@ -30,7 +50,10 @@ let es5ClassAnnotationAsPurePlugin = { } } -await esbuild.build({ +const commonConfig = { + alias: { + 'tslib': '@keymanapp/tslib' + }, bundle: true, sourcemap: true, format: "iife", @@ -43,38 +66,46 @@ await esbuild.build({ target: "es5", treeShaking: true, tsconfig: './tsconfig.json' -}); +}; -await esbuild.build({ - bundle: true, - sourcemap: true, +// Prepare the needed setup for `tslib` treeshaking. +const unusedHelpers = await determineNeededDowncompileHelpers(commonConfig, /worker-main\.wrapped(?:\.min)?\.js/); +commonConfig.plugins = [buildTslibTreeshaker(unusedHelpers), ...commonConfig.plugins]; + +// And now... do the actual builds. +await esbuild.build(commonConfig); + +let result = await esbuild.build({ + ...commonConfig, minifyWhitespace: true, minifySyntax: true, - minifyIdentifiers: false, + minifyIdentifiers: true, format: "iife", nodePaths: ['../../../../node_modules'], entryPoints: { 'index': '../../../build/app/browser/obj/release-main.js', }, outfile: '../../../build/app/browser/release/keymanweb.js', - plugins: [ es5ClassAnnotationAsPurePlugin ], - target: "es5", - treeShaking: true, - tsconfig: './tsconfig.json' + // Enables source-file output size profiling! + metafile: true }); +let filesizeProfile = await esbuild.analyzeMetafile(result.metafile, { verbose: true }); +fs.writeFileSync('../../../build/app/browser/filesize-profile.log', ` +// Minified Keyman Engine for Web ('app/browser' target), filesize profile +${filesizeProfile} +`); +if(EMIT_FILESIZE_PROFILE) { + // Profiles the sourcecode! + console.log(filesizeProfile); +} + await esbuild.build({ - bundle: true, - sourcemap: true, - minify: false, + ...commonConfig, format: "esm", - nodePaths: ['../../../../node_modules'], entryPoints: { 'index': '../../../build/app/browser/obj/test-index.js', }, outfile: '../../../build/app/browser/lib/index.mjs', - plugins: [ es5ClassAnnotationAsPurePlugin ], - target: "es5", - treeShaking: true, tsconfig: './tsconfig.json' }); \ No newline at end of file diff --git a/web/src/app/browser/build.sh b/web/src/app/browser/build.sh index e5340bdff86d8fa7417955a533f017cd03d887cd..2cbc51fae5a070028fe3eeba113b5ca88756068d 100755 --- a/web/src/app/browser/build.sh +++ b/web/src/app/browser/build.sh @@ -1,8 +1,4 @@ #!/usr/bin/env bash -# - -# set -x -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,15 +6,13 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=app/browser +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder cd "$THIS_SCRIPT_PATH" -# Imports common Web build-script definitions & functions -SUBPROJECT_NAME=app/browser -. "$KEYMAN_ROOT/web/common.inc.sh" - # ################################ Main script ################################ builder_describe "Builds the Keyman Engine for Web's website-integrating version for use in non-puppeted browsers." \ @@ -27,7 +21,8 @@ builder_describe "Builds the Keyman Engine for Web's website-integrating version "clean" \ "configure" \ "build" \ - "test" + "test" \ + "--ci+ Set to utilize CI-based test configurations & reporting." # Possible TODO?s # "upload-symbols Uploads build product to Sentry for error report symbolification. Only defined for $DOC_BUILD_EMBED_WEB" \ @@ -45,27 +40,28 @@ builder_describe_outputs \ #### Build action definitions #### -if builder_start_action configure; then - verify_npm_setup - - builder_finish_action success configure -fi - -if builder_start_action clean; then - rm -rf "$KEYMAN_ROOT/web/build/$SUBPROJECT_NAME" - builder_finish_action success clean -fi - -if builder_start_action build; then - compile $SUBPROJECT_NAME +compile_and_copy() { + local COMPILE_FLAGS= + if builder_has_option --ci; then + COMPILE_FLAGS=--ci + fi + compile $SUBPROJECT_NAME $COMPILE_FLAGS mkdir -p "$KEYMAN_ROOT/web/build/app/resources/osk" cp -R "$KEYMAN_ROOT/web/src/resources/osk/." "$KEYMAN_ROOT/web/build/app/resources/osk/" - builder_finish_action success build -fi + # Update the build/publish copy of our build artifacts + prepare + + local PROFILE_DEST="$KEYMAN_ROOT/web/build/profiling/" + mkdir -p "$PROFILE_DEST" + cp "$KEYMAN_ROOT/web/build/app/browser/filesize-profile.log" "$PROFILE_DEST/web-engine-filesize.log" + cp "$KEYMAN_ROOT/common/web/lm-worker/build/filesize-profile.log" "$PROFILE_DEST/lm-worker-filesize.log" +} + +builder_run_action configure verify_npm_setup +builder_run_action clean rm -rf "$KEYMAN_ROOT/web/build/$SUBPROJECT_NAME" +builder_run_action build compile_and_copy -if builder_start_action test; then - # No headless tests of yet. - builder_finish_action success test -fi \ No newline at end of file +# No headless tests for this child project. Currently, DOM-based unit & +# integrated tests are run solely by the top-level $KEYMAN_ROOT/web project. \ No newline at end of file diff --git a/web/src/app/browser/src/beepHandler.ts b/web/src/app/browser/src/beepHandler.ts new file mode 100644 index 0000000000000000000000000000000000000000..5fae87425777442d7faee91df9b092672d65222d --- /dev/null +++ b/web/src/app/browser/src/beepHandler.ts @@ -0,0 +1,87 @@ +import { type KeyboardInterface } from '@keymanapp/keyboard-processor'; +import { DesignIFrame, OutputTarget } from 'keyman/engine/element-wrappers'; + +// Utility object used to handle beep (keyboard error response) operations. +class BeepData { + e: HTMLElement; + c: string; + + constructor(e: HTMLElement) { + this.e = e; + this.c = e.style.backgroundColor; + } + + reset(): void { + this.e.style.backgroundColor = this.c; + } +} + +export class BeepHandler { + readonly keyboardInterface: KeyboardInterface; + + constructor(keyboardInterface: KeyboardInterface) { + this.keyboardInterface = keyboardInterface; + } + + _BeepObjects: BeepData[] = []; // BeepObjects - maintains a list of active 'beep' visual feedback elements + _BeepTimeout: number = 0; // BeepTimeout - a flag indicating if there is an active 'beep'. + // Set to 1 if there is an active 'beep', otherwise leave as '0'. + /** + * Function beep KB (DOM-side implementation) + * Scope Public + * @param {Object} Pelem element to flash + * Description Flash body as substitute for audible beep; notify embedded device to vibrate + */ + beep(outputTarget: OutputTarget) { + if(!(outputTarget instanceof OutputTarget)) { + return; + } + + // All code after this point is DOM-based, triggered by the beep. + var Pelem: HTMLElement = outputTarget.getElement(); + if(outputTarget instanceof DesignIFrame) { + Pelem = outputTarget.docRoot; // I1446 - beep sometimes fails to flash when using OSK and rich control + } + + if(!Pelem) { + return; // There's no way to signal a 'beep' to null, so just cut everything short. + } + + if(!Pelem.style || typeof(Pelem.style.backgroundColor)=='undefined') { + return; + } + + for(var Lbo=0; Lbo { + this.keyboardInterface.resetContextCache(); + + var Lbo; + this._BeepTimeout = 0; + for(Lbo=0;Lbo { const baseReport = super.debugReport(); baseReport.attachType = this.attachType; diff --git a/web/src/app/browser/src/context/focusAssistant.ts b/web/src/app/browser/src/context/focusAssistant.ts index bda60dee5dd5fdbbd3d1a1a3b5c6009bc52fb719..c0ae8a6bdb1b7caa8ead25b222cf655fbc5bd470 100644 --- a/web/src/app/browser/src/context/focusAssistant.ts +++ b/web/src/app/browser/src/context/focusAssistant.ts @@ -41,6 +41,18 @@ interface EventMap { export class FocusAssistant extends EventEmitter { private _maintainingFocus: boolean = false; // ActivatingKeymanWebUI - Does the OSK have active focus / an active interaction? + /** + * Returns `true` only when the active target has an active `forceScroll` method/state, which deliberately + * blurs and then refocuses the same element in order to force a browser-default page scroll to keep the + * element and text-caret visible. + */ + readonly isTargetForcingScroll: () => boolean; + + constructor(isTargetForcingScroll: () => boolean) { + super(); + this.isTargetForcingScroll = isTargetForcingScroll; + } + /* * Long-term idea here: about all of the relevant OSK events that would interact with this have "enter" and * "leave" variants - we could take a stack of `Promise`s. On a `Promise` fulfillment, remove it from the @@ -109,16 +121,6 @@ export class FocusAssistant extends EventEmitter { */ _IgnoreNextSelChange = 0; - /** - * JH (2023-04-24): Set only by the OutputTarget `forceScroll` method, which deliberately blurs and - * then refocuses the same element in order to force a browser-default page scroll to keep the element - * visible. - * - * While it feels like this should be possible to merge with the other class fields in some form... it - * doesn't seem as safe to do on first glance. - */ - _IgnoreBlurFocus: boolean = false; - /** * Is used as a time-delayed async `restoringFocus` or `maintainingFocus` - could be modeled decently as a Promise. * Probably more the latter, as it's a touch-OSK interaction like the other `maintainingFocus` cases. diff --git a/web/src/app/browser/src/context/pageIntegrationHandlers.ts b/web/src/app/browser/src/context/pageIntegrationHandlers.ts index 98367eb4220b7645d795b524a81a33bd7e18ca30..30b91cb102e654dd46da183407f2176fb389efe8 100644 --- a/web/src/app/browser/src/context/pageIntegrationHandlers.ts +++ b/web/src/app/browser/src/context/pageIntegrationHandlers.ts @@ -2,6 +2,7 @@ import { DomEventTracker } from 'keyman/engine/events'; import KeymanEngine from "../keymanEngine.js"; import { FocusAssistant } from './focusAssistant.js'; +import { RotationProcessor } from '../utils/rotationProcessor.js'; // Note: in the future, it'd probably be best to have an instance per iframe window as // well as the top-level window. This was not done in or before KMW 16.0 though, so @@ -30,11 +31,35 @@ export class PageIntegrationHandlers { */ private deactivateOnScroll: boolean; + /** + * This component should only ever be applied to a base page: we need the ability to add 'scroll + * space' on touch devices so that the OSK doesn't block the bottom. + */ + private mobilePageTrailer: HTMLDivElement; + + private rotationProcessor: RotationProcessor; + constructor(window: Window, engine: KeymanEngine) { this.window = window; this.engine = engine; this.attachHandlers(); + + if(engine.config.hostDevice.touchable) { + this.buildPageTrailer(); + + this.rotationProcessor = new RotationProcessor(this.engine); + this.rotationProcessor.init(); + } + } + + private buildPageTrailer() { + // Add a blank DIV to the bottom of the page to allow the bottom of the page to be shown + const dTrailer = this.mobilePageTrailer = document.createElement('div'); + const ds=dTrailer.style; + ds.width='100%'; + ds.height=(screen.width/2)+'px'; // ... interesting choice, but okay. + document.body.appendChild(dTrailer); } private get focusAssistant(): FocusAssistant { @@ -42,8 +67,10 @@ export class PageIntegrationHandlers { } private suppressFocusCheck: (e: FocusEvent) => boolean = (e) => { - if(this.focusAssistant._IgnoreBlurFocus) { - // Prevent triggering other blur-handling events (as possible) + if(this.focusAssistant.isTargetForcingScroll()) { + // Prevent triggering other blur-handling events (as possible) - this blur + // is programmatic in order to force a browser scroll-position update. + // All focus changes should be prevented at this time. e.stopPropagation(); e.cancelBubble = true; } @@ -177,8 +204,7 @@ export class PageIntegrationHandlers { eventTracker.attachDOMEvent(window, 'load', this._WindowLoad, false); eventTracker.attachDOMEvent(window, 'unload', this._WindowUnload,false); - // TODO: Hotkey module stuff. Is not yet modularized. - // eventTracker.attachDOMEvent(document, 'keyup', this.engine.hotkeyManager._Process, false); + eventTracker.attachDOMEvent(document, 'keyup', this.engine.hotkeyManager._Process, false); } public shutdown() { @@ -198,12 +224,13 @@ export class PageIntegrationHandlers { eventTracker.detachDOMEvent(docBody, 'touchstart', this.touchStartActivationHandler,false); eventTracker.detachDOMEvent(docBody, 'touchmove', this.touchMoveActivationHandler, false); eventTracker.detachDOMEvent(docBody, 'touchend', this.touchEndActivationHandler, false); + + this.mobilePageTrailer?.parentElement.removeChild(this.mobilePageTrailer); } eventTracker.detachDOMEvent(window, 'load', this._WindowLoad, false); eventTracker.detachDOMEvent(window, 'unload', this._WindowUnload,false); - // TODO: Hotkey module stuff. - // eventTracker.detachDOMEvent(document, 'keyup', this.engine.hotkeyManager._Process, false); + eventTracker.detachDOMEvent(document, 'keyup', this.engine.hotkeyManager._Process, false); } } \ No newline at end of file diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 226f62b4c013862303f472fcc4b19ba2ee4a1ab4..42e7edd729f291c3bbef37a8e6ec00dee3d0b6e3 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -12,7 +12,7 @@ import { import { BrowserConfiguration } from './configuration.js'; import { FocusAssistant } from './context/focusAssistant.js'; -interface KeyboardCookie { +export interface KeyboardCookie { current: string; } @@ -43,7 +43,7 @@ function _SetTargDir(Ptarg: HTMLElement, activeKeyboard: Keyboard) { export default class ContextManager extends ContextManagerBase { private _activeKeyboard: {keyboard: Keyboard, metadata: KeyboardStub}; private cookieManager = new CookieSerializer('KeymanWeb_Keyboard'); - readonly focusAssistant = new FocusAssistant(); + readonly focusAssistant = new FocusAssistant(() => this.activeTarget?.isForcingScroll()); readonly page: PageContextAttachment; private mostRecentTarget: OutputTarget; private currentTarget: OutputTarget; @@ -183,8 +183,15 @@ export default class ContextManager extends ContextManagerBase) { + public setActiveTarget(target: OutputTarget, sendEvents: boolean) { const previousTarget = this.mostRecentTarget; const originalTarget = this.activeTarget; // may differ, depending on focus state. + if(target == originalTarget) { + /** + * If it's already active, we should cancel early. + * + * The #1 reason - we don't want .resetContext calls in this scenario. + * In particular, moving the caret or setting the selection range of an + * or + +

or in this input field:

+ + + +

Add a keyboard by keyboard name:

+ + + +

Add a keyboard by BCP-47 language code:

+ + + +

Add a keyboard by language name:

+ + + + +

Return to samples home page

+ + + + + + + diff --git a/web/src/samples/index.html b/web/src/samples/index.html index 8d0ac059c08f57a1c0f708a805d4c5f38593fc30..2a9259063f919bdeb29a8f2d489c905be9b83f3d 100644 --- a/web/src/samples/index.html +++ b/web/src/samples/index.html @@ -20,9 +20,9 @@

KeymanWeb Samples

-

Test unminified Keymanweb

-

Test fully compiled Keymanweb

-

Full multilingual (compiled) Keymanweb

-

Return to main index. +

Example 1 - Toggle UI, all resources in same folder as page

+

Example 2 - Toggle UI, all resources in a common subfolder

+

Example 3 - Toolbar UI, all resources in a common subfolder

+

Example 4 - a more complex file-organization setup

diff --git a/web/src/samples/samplehdr.js b/web/src/samples/samplehdr.js index 2ce215c666e9d49a04e468f79e6a19b7b3f7fc26..129d54fc011ebe663f889fb914524d9dbf04799b 100644 --- a/web/src/samples/samplehdr.js +++ b/web/src/samples/samplehdr.js @@ -1,111 +1,146 @@ // JavaScript Document samplehdr.js: Keyboard management for KeymanWeb demonstration pages -/* +/* The keyboard name and/or BCP-47 language code must be specified for each keyboard that is to be available. If the same keyboard is used for several languages, it must be listed for each - language, but the keyboard itself will only be loaded once. + language, but the keyboard itself will only be loaded once. If two (or more) keyboards are to be available for a given language, both must be listed. - Any number of keyboards may be specified in one or more calls. - Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path. + Any number of keyboards may be specified in one or more calls. + Keyboard paths may be absolute (with respect to the server root) or relative to the keyboards option path. The actual keyboard object will be downloaded asynchronously when first selected for use. - + Each argument to addKeyboards() is a string, for example: european2 loads the current version of the Eurolatin 2 keyboard (for its default language) european2@fr loads the current version of the Eurolatin 2 keyboard for French european2@fr@1.2 loads version 1.2 of the Eurolatin 2 keyboard for French - + Argument syntax also supports the following extensions: @fr load the current version of the default keyboard for French @fr$ load all available keyboards (current version) for French - - Each call to addKeyboards() requires a single call to the remote server, + + Each call to addKeyboards() requires a single call to the remote server, (unless all keyboards listed are local and fully specified) so it is better - to use multiple arguments rather than separate function calls. - - Calling addKeyboards() with no arguments returns a list of *all* available keyboards. - The Toolbar (desktop browser) UI is best suited for allowing users to select + to use multiple arguments rather than separate function calls. + + Calling addKeyboards() with no arguments returns a list of *all* available keyboards. + The Toolbar (desktop browser) UI is best suited for allowing users to select the appropriate language and keyboard in this case. Keyboards may also be specified by language name using addKeyboardsForLanguage() for example: keymanweb.addKeyboardsForLanguage('Burmese'); - + Appending $ to the language name will again cause all available keyboards for that language to be loaded rather than the default keyboard. - - The first call to addKeyboardsForLanguage() makes an additional call to the + + The first call to addKeyboardsForLanguage() makes an additional call to the keyman API to load the current list of keyboard/language associations. In this example, the following function loads the indicated keyboards, - and is called when the page loads. + and is called when the page loads. */ - function loadKeyboards() - { + function errToString(err) { + // Painful? Kinda. But needed on un-updated Android API 21! + if(Array.isArray(err)) { + var result = ''; + for(var i = 0; i < err.length; i++) { + var e = err[i]; + if(e.error instanceof Error) { + result += e.error.message + '\n'; + } else { + result += JSON.stringify(e) + '\n'; + } + } + return result; + } + if(err instanceof Error) { + return err.message; + } + return JSON.stringify(err); + } + + function doAddKeyboards(data) { + return keyman.addKeyboards(data).catch(function(err) { + console.error('keyman.addKeyboards failed with '+errToString(err)+' for '+JSON.stringify(data)); + }); + } + + function doAddKeyboardsForLanguage(data) { + return keyman.addKeyboardsForLanguage(data).catch(function(err) { + console.error('keyman.addKeyboardsForLanguage failed with '+errToString(err)+' for '+JSON.stringify(data)); + }); + } + + function loadKeyboards(nestLevel) { var kmw=keyman; - + + var base_prefix = '../'; + var prefix = './'; // The default - when prefix == 0. + + if(nestLevel !== undefined && nestLevel > 0) { + prefix = ''; + for(var i=0; i < nestLevel; i++) { + prefix = prefix + base_prefix; + } + } + // The first keyboard added will be the default keyboard for touch devices. // For faster loading, it may be best for the default keyboard to be // locally sourced. - kmw.addKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, - filename:'./us-1.0.js'}); - - // Add more keyboards to the language menu, by keyboard name, - // keyboard name and language code, or just the BCP-47 language code. - kmw.addKeyboards('french', 'sil_euro_latin@no,sv', '@he'); // Loads all from uniquely-identifying strings. - + doAddKeyboards({id:'us',name:'English',languages:{id:'en',name:'English'}, + filename:(prefix + 'us-1.0.js')}); + + // Add more keyboards to the language menu by: + // 1. keyboard name ('french'), + // 2. keyboard name and language code ('sil_euro_latin@no,sv'), + // 3. or just the BCP-47 language code ('@he'). + kmw.addKeyboards('french', 'sil_euro_latin@no,sv', '@he'); + // Add a keyboard by language name. Note that the name must be spelled // correctly, or the keyboard will not be found. (Using BCP-47 codes is // usually easier.) - kmw.addKeyboardsForLanguage('Dzongkha'); - - // Add a fully-specified, locally-sourced, keyboard with custom font - kmw.addKeyboards({id:'lao_2008_basic',name:'Lao Basic', - languages:{ - id:'lo',name:'Lao',region:'Asia', - // A font can be specified here if its files are available. - // Example: - //font:{family:'LaoWeb',source:['../font/saysettha_web.ttf','../font/saysettha_web.woff','../font/saysettha_web.eot']} - }, - filename:'./lao_2008_basic-1.2.js' - }); - - // The following two optional calls should be delayed until language menus are fully loaded: - // (a) a specific mapped input element input is focused, to ensure that the OSK appears - // (b) a specific keyboard is loaded, rather than the keyboard last used. - //window.setTimeout(function(){kmw.setActiveElement('ta1',true);},2500); - //window.setTimeout(function(){kmw.setActiveKeyboard('Keyboard_french','fr');},3000); - - // Note that locally specified keyboards will be listed before keyboards + doAddKeyboardsForLanguage('Dzongkha'); + + // Add a fully-specified, locally-sourced, keyboard with custom font + doAddKeyboards({ + id:'lao_2008_basic', + name:'Lao Basic', + languages: { + id:'lo',name:'Lao',region:'Asia', + }, + filename:(prefix + 'lao_2008_basic-1.2.js') + }); + + // Note that locally specified keyboards will be listed before keyboards // requested from the remote server by user interfaces that do not order // keyboards alphabetically by language. } - - // Script to allow a user to add any keyboard to the keyboard menu - function addKeyboard(n) - { - var sKbd,kmw=keyman; - switch(n) - { + + // Script to allow a user to add any keyboard to the keyboard menu + function addKeyboard(n) { + var sKbd; + switch(n) { case 1: sKbd=document.getElementById('kbd_id1').value; - kmw.addKeyboards(sKbd); + doAddKeyboards(sKbd); break; case 2: sKbd=document.getElementById('kbd_id2').value.toLowerCase(); - kmw.addKeyboards('@'+sKbd); + doAddKeyboards('@'+sKbd); break; case 3: + // Add keyboard for comma-separated language name(s) sKbd=document.getElementById('kbd_id3').value; - kmw.addKeyboardsForLanguage(sKbd); + doAddKeyboardsForLanguage(sKbd); break; } } - + // Add keyboard on Enter (as well as pressing button) function clickOnEnter(e,id) { e = e || window.event; - if(e.keyCode == 13) addKeyboard(id); + if(e.keyCode == 13) addKeyboard(id); } diff --git a/web/src/samples/simplest/.gitignore b/web/src/samples/simplest/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..61b48a93bc558b4a380ea353106564be89a46d08 --- /dev/null +++ b/web/src/samples/simplest/.gitignore @@ -0,0 +1,3 @@ +**/* +!.gitignore +!index.html \ No newline at end of file diff --git a/web/src/samples/minified.html b/web/src/samples/simplest/index.html similarity index 89% rename from web/src/samples/minified.html rename to web/src/samples/simplest/index.html index 9b5d1674e89a537e6a33035bf31bcc2db8510b7a..c2ed9fd70bc53cdbfb61e754322cd9c72720522c 100644 --- a/web/src/samples/minified.html +++ b/web/src/samples/simplest/index.html @@ -23,7 +23,7 @@ - + - + - + -

KeymanWeb Sample Page - Fully Compiled/Minified Source

+

KeymanWeb Sample Page - Simple Subfolder + Toggle UI

- KeymanWeb Sample Page - Unminified Source + KeymanWeb Sample Page - Fully Compiled Source - - + + - + - + - + -

KeymanWeb Sample Page - Unminified Source

+

KeymanWeb Sample Page - Simple Subfolder + Toggle UI

+ diff --git a/web/src/samples/subfolder_toolbar/.gitignore b/web/src/samples/subfolder_toolbar/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..659fe973e2a11029bed8f9381d1dd9fa9b00e468 --- /dev/null +++ b/web/src/samples/subfolder_toolbar/.gitignore @@ -0,0 +1 @@ +keyman/**/* \ No newline at end of file diff --git a/web/src/samples/multilingual.html b/web/src/samples/subfolder_toolbar/index.html similarity index 82% rename from web/src/samples/multilingual.html rename to web/src/samples/subfolder_toolbar/index.html index de2174a49a8e4dd706ccc89480a4cfcc3347cbf4..7e9aed3f3f7074eca65c9603f410df2d24452607 100644 --- a/web/src/samples/multilingual.html +++ b/web/src/samples/subfolder_toolbar/index.html @@ -21,17 +21,16 @@ - + - + @@ -76,7 +72,7 @@ -

Return to samples home page

+

Return to samples home page

diff --git a/web/src/test/auto/dom/CI.conf.cjs b/web/src/test/auto/dom/CI.conf.cjs index c7c9813df7286705d3a73bea06a043ee99dd3f6d..a96ffebbede2a334c04f2f14a58f42e58c1d2723 100644 --- a/web/src/test/auto/dom/CI.conf.cjs +++ b/web/src/test/auto/dom/CI.conf.cjs @@ -18,7 +18,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js b/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js index abc1999e2cc30c059232466d929a431fb6df3e58..5076432ef5c37c080771c21d6dcd6250c2e37a4f 100644 --- a/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js +++ b/web/src/test/auto/dom/cases/attachment/pageContextAttachment.js @@ -268,6 +268,7 @@ describe('KMW element-attachment logic', function () { // Note: iframes require additional time to resolve. await promiseForIframeLoad(document.getElementById('iframe')); + await promiseForIframeLoad(document.getElementById('design-iframe')); // Give the design-mode iframe a bit of time to set itself up properly. // Note: it is thus important that whatever sends the `install` command has also @@ -495,6 +496,7 @@ describe('KMW element-attachment logic', function () { // Note: iframes require additional time to resolve. await promiseForIframeLoad(document.getElementById('iframe')); + await promiseForIframeLoad(document.getElementById('design-iframe')); // Our mutation observers delay slightly here to ensure that any doc-internal handlers // have a chance to resolve before we attach. Currently: 10ms. @@ -639,6 +641,7 @@ describe('KMW element-attachment logic', function () { // Note: iframes require additional time to resolve. await promiseForIframeLoad(document.getElementById('iframe')); + await promiseForIframeLoad(document.getElementById('design-iframe')); await timedPromise(20); // for the design-iframe to set itself into design-mode. diff --git a/web/src/test/auto/dom/cases/browser/contextManager.js b/web/src/test/auto/dom/cases/browser/contextManager.js index b2085e44d25e3056c0870895a5a04431aefcd2de..7d749bf30d1a69e0d853e95515370ffa2abd10fd 100644 --- a/web/src/test/auto/dom/cases/browser/contextManager.js +++ b/web/src/test/auto/dom/cases/browser/contextManager.js @@ -149,6 +149,7 @@ describe('app/browser: ContextManager', function () { // Note: iframes require additional time to resolve. await promiseForIframeLoad(document.getElementById('iframe')); + await promiseForIframeLoad(document.getElementById('design-iframe')); // Give the design-mode iframe a bit of time to set itself up properly. // Note: it is thus important that whatever sends the `install` command has also @@ -178,8 +179,9 @@ describe('app/browser: ContextManager', function () { contextManager.configure({ keyboardCache: keyboardCache, predictionContext: { - // we're dummying this one out. - resetContext: () => {} + // we're dummying these out. + resetContext: () => {}, + setCurrentTarget: () => {} }, resetContext: () => {} }); diff --git a/web/src/test/auto/dom/cases/packages/keyboardRequisitioner.js b/web/src/test/auto/dom/cases/packages/keyboardRequisitioner.js index 759ead8e847b6bed2b1e4a2cca4c376cb45f9f68..194b56f166c6f03b76607f5eca52da75ab24ac59 100644 --- a/web/src/test/auto/dom/cases/packages/keyboardRequisitioner.js +++ b/web/src/test/auto/dom/cases/packages/keyboardRequisitioner.js @@ -68,6 +68,7 @@ describe("KeyboardRequisitioner", function () { // https://api.keyman.com/cloud/4.0/keyboards?jsonp=keyman.register&languageidtype=bcp47&version=17.0&keyboardid=khmer_angkor&timerid=49. // // The edits are minimal and notated within the fixture file. + // Path becomes /resources/keyboards/khmer_angkor.js. mockQuery(keyboardRequisitioner.cloudQueryEngine, `base/web/src/test/auto/resources/query-mock-results/khmer_angkor.hand-edited.js.fixture`); const [stub] = await keyboardRequisitioner.addKeyboardArray(['khmer_angkor']); assert.strictEqual(cache.getStub(stub.KI, stub.KLC), stub); @@ -85,10 +86,14 @@ describe("KeyboardRequisitioner", function () { const keyboardRequisitioner = new KeyboardRequisitioner(keyboardLoader, new DOMCloudRequester(true), pathConfig); const cache = keyboardRequisitioner.cache; - // Expects the keyboard at resources/keyboards/khmer_angkor.js; this should resolve to - // http://localhost:9876/resources/keyboards/khmer_angkor.js. + // Expects the keyboard at resources/keyboards/khmer_angkor.js; this would resolve to + // http://localhost:9876/resources/keyboards/khmer_angkor.js aased on our configured path. const stubJSON = fixture.load("/keyboards/khmer_angkor.json", true); + // But we can't be sure that `localhost:9876` is the actual path it will have on EVERYONE's machine. Let's fix it. + // We'll make it match the path for the previous test by ensuring a '/' for absolute pathing based on server root. + stubJSON.filename = '/' + stubJSON.filename; + // The `pathConfig` setup + internal logic should ensure that the filepath points to the correct location // with no additional effort required here. const [stub] = await keyboardRequisitioner.addKeyboardArray([stubJSON]); diff --git a/web/src/test/auto/dom/manual.conf.cjs b/web/src/test/auto/dom/manual.conf.cjs index 88e20d283cf2f7c4df2ed53f8a728cf577ca9210..37d982e9b5a70c983c7b64cb766adb1f00b9adfd 100644 --- a/web/src/test/auto/dom/manual.conf.cjs +++ b/web/src/test/auto/dom/manual.conf.cjs @@ -18,7 +18,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/web/src/test/auto/headless/packages/keyboardStub.js b/web/src/test/auto/headless/packages/keyboardStub.js index 4e9d08500d56917d5f9990f059de1606ddd6295a..af6d3bb68376b0de0274579e9c60bf38b58d878d 100644 --- a/web/src/test/auto/headless/packages/keyboardStub.js +++ b/web/src/test/auto/headless/packages/keyboardStub.js @@ -38,6 +38,48 @@ describe("KeyboardStub", () => { }; } + it('construction from internal stub format, partially configured paths', () => { + const rawStub = { + KI: 'dummy', + KN: 'test dummy', + KL: 'English', + KLC: 'en', + KF: 'dummy.js', + // The way font paths are currently handled feels pretty rough and unclear. + // Their paths aren't updated in the same way as the KF entry. + // So... leaving font stuff out of the test for now. + // (Also, Developer doesn't seem to bother specifying font files in its stubs, so it's + // less criitcal.) + }; + + const stub = new KeyboardStub(rawStub, 'http://localhost/keyboards/', 'http://localhost/fonts/'); + + assert.equal(stub.KF, 'http://localhost/keyboards/dummy.js'); + }); + + it('construction from internal stub format, pre-configured paths', () => { + // Based on actual font pathing as hosted by the Android app. + const absolutePath = '/data/user/0/com.tavultesoft.kmapro.debug/app_data/packages/dummy/dummy.ttf'; + + const rawStub = { + KI: 'dummy', + KN: 'test dummy', + KL: 'English', + KLC: 'en', + KF: absolutePath, + // The way font paths are currently handled feels pretty rough and unclear. + // Their paths aren't updated in the same way as the KF entry. + // So... leaving font stuff out of the test for now. + // (Also, Developer doesn't seem to bother specifying font files in its stubs, so it's + // less criitcal.) + }; + + // These components... are not, but that's OK - the test is to ignore them. + const stub = new KeyboardStub(rawStub, 'http://localhost/keyboards/', 'http://localhost/fonts/'); + + assert.equal(stub.KF, absolutePath); + }); + it('merge(): barebones stub + fetched sil_euro_latin@no', async () => { const query = performMockedRequest(`${__dirname}/../../resources/query-mock-results/sil_euro_latin@no_sv.js.fixture`); await query.promise; diff --git a/web/src/test/auto/integrated/CI.conf.cjs b/web/src/test/auto/integrated/CI.conf.cjs index 38ee2c97a004acbde24885d058867cb07197506d..94e94a0fc80c6bebc7b0237cd7d6e0f2369dc126 100644 --- a/web/src/test/auto/integrated/CI.conf.cjs +++ b/web/src/test/auto/integrated/CI.conf.cjs @@ -1,4 +1,4 @@ -var BASE_CONFIG = require("./base.conf.js"); +var BASE_CONFIG = require("./base.conf.cjs"); var ci_config_adapter = require("../../../../../common/test/resources/karma-browserstack-config.cjs"); module.exports = ci_config_adapter(BASE_CONFIG, "full"); \ No newline at end of file diff --git a/web/src/test/auto/integrated/base.conf.cjs b/web/src/test/auto/integrated/base.conf.cjs index c625a6162dd158ab5349b671c39d398d08dd00f5..c7eaa8b219f23c2333bf1c32b2e46082020df997 100644 --- a/web/src/test/auto/integrated/base.conf.cjs +++ b/web/src/test/auto/integrated/base.conf.cjs @@ -57,10 +57,10 @@ module.exports = { {pattern: 'common/test/resources/fixtures/**/*.html', watched: true}, // HTML structures useful for testing. {pattern: 'common/test/resources/**/*.*', watched: true, served: true, included: false}, // General testing resources. - {pattern: 'web/build/app/web/debug/**/*.css', watched: false, served: true, included: false}, // OSK resources - {pattern: 'web/build/app/web/debug/**/*.gif', watched: false, served: true, included: false}, // OSK resources - {pattern: 'web/build/app/web/debug/**/*.png', watched: false, served: true, included: false}, // OSK resources - {pattern: 'web/build/app/web/debug/**/*.ttf', watched: false, served: true, included: false}, // OSK resources + {pattern: 'web/build/app/resources/**/*.css', watched: false, served: true, included: false}, // OSK resources + {pattern: 'web/build/app/resources/**/*.gif', watched: false, served: true, included: false}, // OSK resources + {pattern: 'web/build/app/resources/**/*.png', watched: false, served: true, included: false}, // OSK resources + {pattern: 'web/build/app/resources/**/*.ttf', watched: false, served: true, included: false}, // OSK resources {pattern: 'web/build/app/web/debug/*.js', watched: true, served: true, included: false}, // The actual KMW code. {pattern: 'web/build/app/web/debug/*.map', watched: true, served: true, included: false}, // + sourcemaps. {pattern: 'web/build/app/ui/debug/*.js', watched: true, served: true, included: false}, // The actual KMW UI code. @@ -73,8 +73,9 @@ module.exports = { ], proxies: { - "/source/": "/base/web/build/app/web/debug/", - "/ui-source/": "/base/web/build/app/ui/debug/", + "/build/app/browser/": "/base/web/build/app/browser/debug/", + "/build/app/resources/": "/base/web/build/app/resources/", + "/build/app/ui/": "/base/web/build/app/ui/debug/", "/resources/": "/base/common/test/resources/", "/source/recorder_InputEvents.js.map": "/base/common/tests/recorder_InputEvents.js.map", "/node_modules/": "/base/node_modules/", diff --git a/web/src/test/auto/integrated/cases/basics.js b/web/src/test/auto/integrated/cases/basics.js index 083d8f615e9e9c729a589716694e11ea26f1f455..c06ce73d380dcabb8f5b5e850b7e3806958ecd3d 100644 --- a/web/src/test/auto/integrated/cases/basics.js +++ b/web/src/test/auto/integrated/cases/basics.js @@ -1,154 +1,158 @@ -// var assert = chai.assert; - -// describe('Basic KeymanWeb', function() { -// this.timeout(testconfig.timeouts.standard); - -// before(function() { -// // These tests require use of KMW's device-detection functionality. -// assert.isFalse(com.keyman.karma.DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure."); -// }) - -// beforeEach(function() { -// this.timeout(testconfig.timeouts.scriptLoad); - -// fixture.setBase('fixtures'); -// fixture.load("singleInput.html"); -// return setupKMW(null, testconfig.timeouts.scriptLoad); -// }); - -// afterEach(function() { -// fixture.cleanup(); -// teardownKMW(); -// }); - -// describe('Initialization', function() { -// it('KMW should attach to the input element.', function() { -// var singleton = document.getElementById('singleton'); -// assert.isTrue(keyman.isAttached(singleton), "KeymanWeb did not automatically attach to the element!"); -// }); -// it('KMW\'s initialization variable should indicate completion.', function() { -// assert(keyman.initialized == 2, 'Keyman indicates incomplete initialization!'); -// }); -// }); -// }); - -// Modernizr.on('touchevents', function(result) { -// if(!result) { -// describe('Basic Toggle UI', function() { -// this.timeout(testconfig.timeouts.scriptLoad); - -// beforeEach(function() { -// this.timeout(testconfig.timeouts.uiLoad); -// fixture.setBase('fixtures'); -// fixture.load('singleInput.html'); - -// // Loads two scripts in parallel, but just in case, 2x timeout. -// return setupKMW('toggle', testconfig.timeouts.uiLoad); -// }); - -// afterEach(function() { -// fixture.cleanup(); -// teardownKMW(); -// }); - -// it('The Toggle UI initializes correctly.', function() { -// assert(keyman.ui.initialized, 'Initialization flag is set to false!'); - -// assert.isNotNull(keyman.ui.controller, 'Failed to create the controller element!'); - -// var divs = document.getElementsByTagName("div"); -// var match = false; - -// for(var i=0; i < divs.length; i++) { -// if(divs[i] == keyman.ui.controller) { -// match = true; -// } -// } - -// assert(match, 'Controller element has not been added to the page!'); -// }) -// }); - -// describe('Basic Button UI', function() { - -// beforeEach(function() { -// this.timeout(testconfig.timeouts.uiLoad); -// fixture.setBase('fixtures'); -// fixture.load('singleInput.html'); - -// // Loads two scripts in parallel, but just in case, 2x timeout. -// return setupKMW('button', testconfig.timeouts.uiLoad); -// }); - -// afterEach(function() { -// fixture.cleanup(); -// teardownKMW(); -// }); - -// it('The Button UI initializes correctly.', function() { -// assert(keyman.ui.init, 'Initialization flag is set to false!'); -// }) -// }); - -// describe('Basic Float UI', function() { - -// beforeEach(function() { -// this.timeout(testconfig.timeouts.uiLoad); -// fixture.setBase('fixtures'); -// fixture.load('singleInput.html'); - -// // Loads two scripts in parallel, but just in case, 2x timeout. -// return setupKMW('float', testconfig.timeouts.uiLoad); -// }); - -// afterEach(function() { -// fixture.cleanup(); -// teardownKMW(); -// }); - -// it('The Float UI initializes correctly.', function() { -// assert(keyman.ui.initialized, 'Initialization flag is set to false!'); - -// assert.isNotNull(keyman.ui.outerDiv, 'Failed to create the floating controller element!'); - -// var divs = document.getElementsByTagName("div"); -// var match = false; - -// for(var i=0; i < divs.length; i++) { -// if(divs[i] == keyman.ui.outerDiv) { -// match = true; -// } -// } - -// assert(match, 'Floating controller element has not been added to the page!'); -// }) -// }); - -// describe('Basic Toolbar UI', function() { - -// beforeEach(function() { -// this.timeout(testconfig.timeouts.uiLoad); -// fixture.setBase('fixtures'); -// fixture.load('singleInput.html'); +var assert = chai.assert; + +import { DEVICE_DETECT_FAILURE, setupKMW, teardownKMW } from "../test_utils.js"; + +describe('Basic KeymanWeb', function() { + this.timeout(testconfig.timeouts.standard); + + before(function() { + // These tests require use of KMW's device-detection functionality. + assert.isFalse(DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure."); + }) + + beforeEach(function() { + this.timeout(testconfig.timeouts.scriptLoad); + + fixture.setBase('fixtures'); + fixture.load("singleInput.html"); + return setupKMW(null, testconfig.timeouts.scriptLoad); + }); + + afterEach(function() { + fixture.cleanup(); + teardownKMW(); + }); + + describe('Initialization', function() { + it('KMW should attach to the input element.', function() { + var singleton = document.getElementById('singleton'); + assert.isTrue(keyman.isAttached(singleton), "KeymanWeb did not automatically attach to the element!"); + }); + it('KMW\'s initialization variable should indicate completion.', function() { + assert(keyman.initialized == 2, 'Keyman indicates incomplete initialization!'); + }); + }); +}); + +Modernizr.on('touchevents', function(result) { + // Warning: desktop Chrome gives `result == true`! + // It _does_ at least prevent a problem on mobile-app platforms, though. + if(!result) { + describe('Basic Toggle UI', function() { + this.timeout(testconfig.timeouts.scriptLoad); + + beforeEach(function() { + this.timeout(testconfig.timeouts.uiLoad); + fixture.setBase('fixtures'); + fixture.load('singleInput.html'); + + // Loads two scripts in parallel, but just in case, 2x timeout. + return setupKMW('toggle', testconfig.timeouts.uiLoad); + }); + + afterEach(function() { + fixture.cleanup(); + teardownKMW(); + }); + + it('The Toggle UI initializes correctly.', function() { + assert(keyman.ui.initialized, 'Initialization flag is set to false!'); + + assert.isNotNull(keyman.ui.controller, 'Failed to create the controller element!'); + + var divs = document.getElementsByTagName("div"); + var match = false; + + for(var i=0; i < divs.length; i++) { + if(divs[i] == keyman.ui.controller) { + match = true; + } + } + + assert(match, 'Controller element has not been added to the page!'); + }) + }); + + describe('Basic Button UI', function() { + + beforeEach(function() { + this.timeout(testconfig.timeouts.uiLoad); + fixture.setBase('fixtures'); + fixture.load('singleInput.html'); + + // Loads two scripts in parallel, but just in case, 2x timeout. + return setupKMW('button', testconfig.timeouts.uiLoad); + }); + + afterEach(function() { + fixture.cleanup(); + teardownKMW(); + }); + + it('The Button UI initializes correctly.', function() { + assert(keyman.ui.init, 'Initialization flag is set to false!'); + }) + }); + + describe('Basic Float UI', function() { + + beforeEach(function() { + this.timeout(testconfig.timeouts.uiLoad); + fixture.setBase('fixtures'); + fixture.load('singleInput.html'); + + // Loads two scripts in parallel, but just in case, 2x timeout. + return setupKMW('float', testconfig.timeouts.uiLoad); + }); + + afterEach(function() { + fixture.cleanup(); + teardownKMW(); + }); + + it('The Float UI initializes correctly.', function() { + assert(keyman.ui.initialized, 'Initialization flag is set to false!'); + + assert.isNotNull(keyman.ui.outerDiv, 'Failed to create the floating controller element!'); + + var divs = document.getElementsByTagName("div"); + var match = false; + + for(var i=0; i < divs.length; i++) { + if(divs[i] == keyman.ui.outerDiv) { + match = true; + } + } + + assert(match, 'Floating controller element has not been added to the page!'); + }) + }); + + describe('Basic Toolbar UI', function() { + + beforeEach(function() { + this.timeout(testconfig.timeouts.uiLoad); + fixture.setBase('fixtures'); + fixture.load('singleInput.html'); + + // Loads two scripts in parallel, but just in case, 2x timeout. + return setupKMW('toolbar', testconfig.timeouts.uiLoad); + }); -// // Loads two scripts in parallel, but just in case, 2x timeout. -// return setupKMW('toolbar', testconfig.timeouts.uiLoad); -// }); + afterEach(function() { + fixture.cleanup(); + teardownKMW(); + }); -// afterEach(function() { -// fixture.cleanup(); -// teardownKMW(); -// }); + it('The Toolbar UI initializes correctly.', function() { + assert(keyman.ui.init, 'Initialization flag is set to false!'); -// it('The Toolbar UI initializes correctly.', function() { -// assert(keyman.ui.init, 'Initialization flag is set to false!'); + var kwc = document.getElementById('KeymanWebControl'); + assert.isNotNull(kwc, 'Toolbar DIV was not added to the page!'); -// var kwc = document.getElementById('KeymanWebControl'); -// assert.isNotNull(kwc, 'Toolbar DIV was not added to the page!'); - -// var toolbar = document.getElementById('kmw_controls'); -// assert.isNotNull(toolbar, 'The main toolbar element was not added to the page!'); -// }) -// }); -// } -// }); \ No newline at end of file + var toolbar = document.getElementById('kmw_controls'); + assert.isNotNull(toolbar, 'The main toolbar element was not added to the page!'); + }) + }); + } +}); \ No newline at end of file diff --git a/web/src/test/auto/integrated/cases/engine.js b/web/src/test/auto/integrated/cases/engine.js index 9172038551283148187d24930ddf52d20837fef0..d6fba7d400c4ff63612275b209ec3257a25f58a3 100644 --- a/web/src/test/auto/integrated/cases/engine.js +++ b/web/src/test/auto/integrated/cases/engine.js @@ -1,292 +1,319 @@ -// var assert = chai.assert; - -// describe('Engine - Browser Interactions', function() { -// this.timeout(testconfig.timeouts.scriptLoad); - -// before(function() { -// fixture.setBase('fixtures'); -// return setupKMW(null, testconfig.timeouts.scriptLoad); -// }); - -// beforeEach(function(done) { -// fixture.load("singleInput.html"); - -// window.setTimeout(function() { -// done() -// }, testconfig.timeouts.eventDelay); -// }); - -// after(function() { -// teardownKMW(); -// }); - -// afterEach(function() { -// fixture.cleanup(); -// }); - -// describe('RegisterStub', function() { -// it('RegisterStub on same keyboard twice', function() { -// this.timeout(testconfig.timeouts.scriptLoad); - -// var test_callback = function() { -// assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!"); -// assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!"); -// keyman.removeKeyboards('lao_2008_basic'); -// assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!"); -// } - -// let finalPromise = loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad) -// .then(test_callback); - -// var stub = { -// 'KI': 'Keyboard_lao_2008_basic', -// 'KN': 'Lao 2008 Basic', -// 'KLC': 'lo', -// 'KL': 'Lao', -// 'KF': 'resources/keyboards/lao_2008_basic.js' -// }; -// assert.equal(com.keyman.text.KeyboardInterface.prototype.registerStub(stub), 1, "Registering existing keyboard should return 1!"); -// return finalPromise; -// }); - -// }); - -// describe('Variable Stores', function() { -// this.timeout(testconfig.timeouts.scriptLoad + testconfig.timeouts.standard); - -// beforeEach(function() { -// return loadKeyboardFromJSON("/keyboards/options_with_save.json", testconfig.timeouts.scriptLoad); -// }); - -// after(function() { -// keyman.removeKeyboards('options_with_save'); -// fixture.cleanup(); -// }); - -// it('Backing up and restoring (loadStore/saveStore)', function() { -// // Keyboard's default value is 0, corresponding to "no foo." -// var keyboardID = "options_with_save"; -// var prefixedKeyboardID = "Keyboard_" + keyboardID; -// var storeName = "foo"; - -// return keyman.setActiveKeyboard(keyboardID, 'en').then(function() { -// // Alas, saveStore itself requires the keyboard to be active! -// KeymanWeb.saveStore(storeName, 1); - -// // First, ensure that we get the same thing if we load the value immediately. -// var value = KeymanWeb.loadStore(prefixedKeyboardID, storeName, 0); -// assert.equal(value, 1, "loadStore did not see the value saved to initialize the test before resetting keyboard"); - -// // Reload the keyboard so that we can test its loaded value. -// keyman.removeKeyboards(keyboardID, true); -// }).then(() => { -// return loadKeyboardFromJSON("/keyboards/options_with_save.json", testconfig.timeouts.scriptLoad); -// }).then(() => { -// return keyman.setActiveKeyboard(keyboardID, 'en'); -// }).then(() => { -// // This requires proper storage to a cookie, as we'll be on a new instance of the same keyboard. -// var value = KeymanWeb.loadStore(prefixedKeyboardID, storeName, 0); -// assert.equal(value, 1, "Did not properly save and reload variable store setting"); -// }).finally(() => { -// KeymanWeb.saveStore(storeName, 0); -// }); -// }); - -// it("Multiple-sequence check", function() { -// this.timeout(testconfig.timeouts.standard + testconfig.timeouts.scriptLoad * 3); -// var keyboardID = "options_with_save"; -// var storeName = "foo"; - -// return keyman.setActiveKeyboard(keyboardID, 'en').then(function() { -// KeymanWeb.saveStore(storeName, 1); -// keyman.removeKeyboards(keyboardID, true); -// }).then(() => { -// // First test: expects option to be "on" from cookie-init setting, emitting "foo.", then turning option "off". -// return runKeyboardTestFromJSON('/engine_tests/options_with_save_1.json', -// {usingOSK: false}, -// assert.equal, -// testconfig.timeouts.scriptLoad) -// }).then(() => { -// // Reset the keyboard... again. -// keyman.removeKeyboards(keyboardID, true); - -// // Second test: expects option to still be "off" b/c cookies. -// return runKeyboardTestFromJSON('/engine_tests/options_with_save_2.json', -// {usingOSK: false}, -// assert.equal, -// testconfig.timeouts.scriptLoad); -// }); -// }); -// }); - -// // Performs basic processing system checks/tests to ensure the sequence testing -// // is based on correct assumptions about the code. -// describe('Integrated Simulation Checks', function() { -// this.timeout(testconfig.timeouts.standard); - -// before(function() { -// this.timeout = testconfig.timeouts.scriptLoad; -// return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad); -// }); - -// beforeEach(function() { -// var inputElem = document.getElementById('singleton'); -// keyman.setActiveElement(inputElem); -// inputElem.value = ""; -// }); - -// after(function() { -// keyman.removeKeyboards('lao_2008_basic'); -// fixture.cleanup(); -// }); - -// it('Simple Keypress', function() { -// var inputElem = document.getElementById('singleton'); - -// var lao_s_key_json = {"type": "key", "key":"s", "code":"KeyS","keyCode":83,"modifierSet":0,"location":0}; -// var lao_s_event = new KMWRecorder.PhysicalInputEventSpec(lao_s_key_json); - -// let eventDriver = new KMWRecorder.BrowserDriver(inputElem); -// eventDriver.simulateEvent(lao_s_event); - -// if(inputElem['base']) { -// inputElem = inputElem['base']; -// } -// assert.equal(inputElem.value, "ຫ"); -// }); - -// it('Simple OSK click', function() { -// var inputElem = document.getElementById('singleton'); - -// var lao_s_osk_json = {"type": "osk", "keyID": 'shift-K_S'}; -// var lao_s_event = new KMWRecorder.OSKInputEventSpec(lao_s_osk_json); - -// let eventDriver = new KMWRecorder.BrowserDriver(inputElem); -// eventDriver.simulateEvent(lao_s_event); - -// if(inputElem['base']) { -// inputElem = inputElem['base']; -// } -// assert.equal(inputElem.value, ";"); -// }); -// }) - -// describe('Sequence Simulation Checks', function() { -// this.timeout(testconfig.timeouts.scriptLoad); - -// it('Keyboard simulation', function() { -// return runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: false}, assert.equal, testconfig.timeouts.scriptLoad); -// }); - -// it('OSK simulation', function() { -// return runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad); -// }) -// }); -// }); - -// describe('Unmatched Final Groups', function() { -// this.timeout(testconfig.timeouts.scriptLoad); - -// before(function() { -// fixture.setBase('fixtures'); -// return setupKMW(null, testconfig.timeouts.scriptLoad + testconfig.timeouts.eventDelay); -// }); - -// beforeEach(function(done) { -// fixture.load("singleTextArea.html"); - -// window.setTimeout(function() { -// done() -// }, testconfig.timeouts.eventDelay); -// }); - -// after(function() { -// teardownKMW(); -// }); - -// afterEach(function() { -// fixture.cleanup(); -// }); - -// it('matches rule from early group AND performs default behavior', function() { -// // While a TAB-oriented version would be nice, it's much harder to write the test -// // to detect change in last input element. -// return runKeyboardTestFromJSON('/engine_tests/ghp_enter.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad); -// }); -// }); - -// // Kept separate to maintain an extra-clean setup for this test. -// describe('Engine - Browser Interactions', function() { -// this.timeout(testconfig.timeouts.scriptLoad); - -// before(function() { -// fixture.setBase('fixtures'); -// }); - -// beforeEach(function() { -// fixture.load("singleInput.html"); -// return setupKMW(null, testconfig.timeouts.scriptLoad); -// }); - -// afterEach(function() { -// fixture.cleanup(); -// teardownKMW(); -// }); - -// describe('Keyboard Loading', function() { -// it('Local', function() { -// this.timeout(testconfig.timeouts.scriptLoad); - -// return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", -// testconfig.timeouts.scriptLoad).then(function() { -// assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!"); -// assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!"); -// keyman.removeKeyboards('lao_2008_basic'); -// assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!"); -// }); -// }); - -// it('Automatically sets first available keyboard', function() { -// this.timeout(2 * testconfig.timeouts.scriptLoad); - -// return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", -// testconfig.timeouts.scriptLoad, -// {passive: true}).then(() => { -// // Because we're loading the keyboard 'passively', KMW's setActiveKeyboard function is auto-called -// // on the stub-add. That specific call (for first keyboard auto-activation) is outside of KMW's -// // current Promise chain, so we can't _directly_ rely on a KMW Promise to test it. -// return new Promise((resolve) => { -// let hasResolved = false; -// // So, we give KMW the time needed for auto-activation to happen, polling a bit actively so that we don't -// // wait unnecessarily long after it occurs. -// let absoluteTimer = window.setTimeout(() => { -// if(!hasResolved) { -// resolve(); -// hasResolved = true; -// } - -// window.clearTimeout(intervalTimer); -// }, testconfig.timeouts.scriptLoad); - -// let intervalTimer = window.setInterval(() => { -// if(keyman.getActiveKeyboard() != '') { -// window.clearTimeout(intervalTimer); -// window.clearTimeout(absoluteTimer); - -// if(!hasResolved) { -// resolve(); -// hasResolved = true; -// } -// } -// }, 50); -// }); -// // Once this delay-Promise resolves successfully (either way)... -// }).then(function() { -// assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!"); -// assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!"); -// keyman.removeKeyboards('lao_2008_basic'); -// assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!"); -// }); // THEN we run our checks. -// }); -// }); -// }); \ No newline at end of file +var assert = chai.assert; +import { + loadKeyboardFromJSON, + runKeyboardTestFromJSON, + setupKMW, + teardownKMW +} from "../test_utils.js"; +import * as KMWRecorder from '/@keymanapp/keyman/build/tools/testing/recorder/lib/index.mjs'; + +describe('Engine - Browser Interactions', function() { + this.timeout(testconfig.timeouts.scriptLoad); + + before(function() { + fixture.setBase('fixtures'); + return setupKMW(null, testconfig.timeouts.scriptLoad); + }); + + beforeEach(function(done) { + fixture.load("singleInput.html"); + + window.setTimeout(function() { + done() + }, testconfig.timeouts.eventDelay); + }); + + after(function() { + teardownKMW(); + }); + + afterEach(function() { + fixture.cleanup(); + }); + + describe('RegisterStub', function() { + it('RegisterStub on same keyboard twice', async function() { + this.timeout(testconfig.timeouts.scriptLoad); + + var test_callback = async function() { + assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!"); + assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!"); + keyman.removeKeyboards('lao_2008_basic'); + await Promise.resolve(); // so that the keyboard can reset. + assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!"); + } + + let finalPromise = loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad) + .then(test_callback); + + var stub = { + 'KI': 'Keyboard_lao_2008_basic', + 'KN': 'Lao 2008 Basic', + 'KLC': 'lo', + 'KL': 'Lao', + 'KF': 'resources/keyboards/lao_2008_basic.js' + }; + + await Promise.resolve(); + + assert.equal(KeymanWeb.registerStub(stub), 1, "Registering existing keyboard should return 1!"); + return finalPromise; + }); + + }); + + describe('Variable Stores', function() { + this.timeout(testconfig.timeouts.scriptLoad + testconfig.timeouts.standard); + + beforeEach(function() { + return loadKeyboardFromJSON("/keyboards/options_with_save.json", testconfig.timeouts.scriptLoad); + }); + + after(function() { + keyman.removeKeyboards('options_with_save'); + fixture.cleanup(); + }); + + it('Backing up and restoring (loadStore/saveStore)', function() { + // Keyboard's default value is 0, corresponding to "no foo." + var keyboardID = "options_with_save"; + var prefixedKeyboardID = "Keyboard_" + keyboardID; + var storeName = "foo"; + + return keyman.setActiveKeyboard(keyboardID, 'en').then(function() { + // Alas, saveStore itself requires the keyboard to be active! + KeymanWeb.saveStore(storeName, 1); + + // First, ensure that we get the same thing if we load the value immediately. + var value = KeymanWeb.loadStore(prefixedKeyboardID, storeName, 0); + assert.equal(value, 1, "loadStore did not see the value saved to initialize the test before resetting keyboard"); + + // Reload the keyboard so that we can test its loaded value. + keyman.removeKeyboards(keyboardID); + + return Promise.resolve(); + }).then(() => { + return loadKeyboardFromJSON("/keyboards/options_with_save.json", testconfig.timeouts.scriptLoad); + }).then(() => { + return keyman.setActiveKeyboard(keyboardID, 'en'); + }).then(() => { + // This requires proper storage to a cookie, as we'll be on a new instance of the same keyboard. + var value = KeymanWeb.loadStore(prefixedKeyboardID, storeName, 0); + assert.equal(value, 1, "Did not properly save and reload variable store setting"); + }).finally(() => { + KeymanWeb.saveStore(storeName, 0); + }); + }); + + it("Multiple-sequence check", function() { + this.timeout(testconfig.timeouts.standard + testconfig.timeouts.scriptLoad * 3); + var keyboardID = "options_with_save"; + var storeName = "foo"; + + return keyman.setActiveKeyboard(keyboardID, 'en').then(async function() { + KeymanWeb.saveStore(storeName, 1); + keyman.removeKeyboards(keyboardID); + + await Promise.resolve(); + }).then(() => { + // First test: expects option to be "on" from cookie-init setting, emitting "foo.", then turning option "off". + return runKeyboardTestFromJSON('/engine_tests/options_with_save_1.json', + {usingOSK: false}, + assert.equal, + testconfig.timeouts.scriptLoad) + }).then(async () => { + // Reset the keyboard... again. + keyman.removeKeyboards(keyboardID); + + await Promise.resolve(); + + // Second test: expects option to still be "off" b/c cookies. + return runKeyboardTestFromJSON('/engine_tests/options_with_save_2.json', + {usingOSK: false}, + assert.equal, + testconfig.timeouts.scriptLoad); + }); + }); + }); + + // Performs basic processing system checks/tests to ensure the sequence testing + // is based on correct assumptions about the code. + describe('Integrated Simulation Checks', function() { + this.timeout(testconfig.timeouts.standard); + + before(function() { + this.timeout = testconfig.timeouts.scriptLoad; + return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad); + }); + + beforeEach(function() { + var inputElem = document.getElementById('singleton'); + keyman.setActiveElement(inputElem); + inputElem.value = ""; + }); + + after(function() { + keyman.removeKeyboards('lao_2008_basic'); + fixture.cleanup(); + }); + + /** + * This can be surprisingly useful for catching browser-specific oddities related to keystroke + * and keyboard-rule processing! Keep this test cross-browser at all costs if possible! + * + * See #8830. + */ + it('Simple Keypress', function() { + var inputElem = document.getElementById('singleton'); + + var lao_s_key_json = {"type": "key", "key":"s", "code":"KeyS","keyCode":83,"modifierSet":0,"location":0}; + var lao_s_event = new KMWRecorder.PhysicalInputEventSpec(lao_s_key_json); + + let eventDriver = new KMWRecorder.BrowserDriver(inputElem); + eventDriver.simulateEvent(lao_s_event); + + if(inputElem['base']) { + inputElem = inputElem['base']; + } + assert.equal(inputElem.value, "ຫ"); + }); + + it('Simple OSK click', function() { + var inputElem = document.getElementById('singleton'); + + var lao_s_osk_json = {"type": "osk", "keyID": 'shift-K_S'}; + var lao_s_event = new KMWRecorder.OSKInputEventSpec(lao_s_osk_json); + + let eventDriver = new KMWRecorder.BrowserDriver(inputElem); + eventDriver.simulateEvent(lao_s_event); + + if(inputElem['base']) { + inputElem = inputElem['base']; + } + assert.equal(inputElem.value, ";"); + }); + }) + + describe('Sequence Simulation Checks', function() { + this.timeout(testconfig.timeouts.scriptLoad); + + it('Keyboard simulation', function() { + return runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: false}, assert.equal, testconfig.timeouts.scriptLoad); + }); + + it('OSK simulation', function() { + return runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad); + }) + }); +}); + +describe('Unmatched Final Groups', function() { + this.timeout(testconfig.timeouts.scriptLoad); + + before(function() { + fixture.setBase('fixtures'); + return setupKMW(null, testconfig.timeouts.scriptLoad + testconfig.timeouts.eventDelay); + }); + + beforeEach(function(done) { + fixture.load("singleTextArea.html"); + + window.setTimeout(function() { + done() + }, testconfig.timeouts.eventDelay); + }); + + after(function() { + teardownKMW(); + }); + + afterEach(function() { + fixture.cleanup(); + }); + + it('matches rule from early group AND performs default behavior', function() { + // While a TAB-oriented version would be nice, it's much harder to write the test + // to detect change in last input element. + return runKeyboardTestFromJSON('/engine_tests/ghp_enter.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad); + }); +}); + +// Kept separate to maintain an extra-clean setup for this test. +describe('Engine - Browser Interactions', function() { + this.timeout(testconfig.timeouts.scriptLoad); + + before(function() { + fixture.setBase('fixtures'); + }); + + beforeEach(function() { + fixture.load("singleInput.html"); + return setupKMW(null, testconfig.timeouts.scriptLoad); + }); + + afterEach(function() { + fixture.cleanup(); + teardownKMW(); + }); + + describe('Keyboard Loading', function() { + it('Local', function() { + this.timeout(testconfig.timeouts.scriptLoad); + + return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", + testconfig.timeouts.scriptLoad).then(async function() { + assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!"); + assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!"); + keyman.removeKeyboards('lao_2008_basic'); + + await Promise.resolve(); + assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!"); + }); + }); + + it('Automatically sets first available keyboard', function() { + this.timeout(2 * testconfig.timeouts.scriptLoad); + + return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", + testconfig.timeouts.scriptLoad, + {passive: true}).then(() => { + // Because we're loading the keyboard 'passively', KMW's setActiveKeyboard function is auto-called + // on the stub-add. That specific call (for first keyboard auto-activation) is outside of KMW's + // current Promise chain, so we can't _directly_ rely on a KMW Promise to test it. + return new Promise((resolve) => { + let hasResolved = false; + // So, we give KMW the time needed for auto-activation to happen, polling a bit actively so that we don't + // wait unnecessarily long after it occurs. + let absoluteTimer = window.setTimeout(() => { + if(!hasResolved) { + resolve(); + hasResolved = true; + } + + window.clearTimeout(intervalTimer); + }, testconfig.timeouts.scriptLoad); + + let intervalTimer = window.setInterval(() => { + if(keyman.getActiveKeyboard() != '') { + window.clearTimeout(intervalTimer); + window.clearTimeout(absoluteTimer); + + if(!hasResolved) { + resolve(); + hasResolved = true; + } + } + }, 50); + }); + // Once this delay-Promise resolves successfully (either way)... + }).then(async function() { + assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!"); + assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!"); + keyman.removeKeyboards('lao_2008_basic'); + + await Promise.resolve(); + assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!"); + }); // THEN we run our checks. + }); + }); +}); \ No newline at end of file diff --git a/web/src/test/auto/integrated/cases/engine_chirality.js b/web/src/test/auto/integrated/cases/engine_chirality.js index 481bd1bcaed7fb6d123f8e206fa3c29f1531d67d..a6607a8899a6ceaa68ac3237cc5bddbc6902dc76 100644 --- a/web/src/test/auto/integrated/cases/engine_chirality.js +++ b/web/src/test/auto/integrated/cases/engine_chirality.js @@ -1,47 +1,53 @@ -// var assert = chai.assert; +var assert = chai.assert; +import { + loadKeyboardFromJSON, + runKeyboardTestFromJSON, + setupKMW, + teardownKMW +} from "../test_utils.js"; -// describe('Engine - Chirality', function() { -// this.timeout(testconfig.timeouts.scriptLoad); +describe('Engine - Chirality', function() { + this.timeout(testconfig.timeouts.scriptLoad); -// before(function() { -// fixture.setBase('fixtures'); -// return setupKMW(null, testconfig.timeouts.scriptLoad); -// }); + before(function() { + fixture.setBase('fixtures'); + return setupKMW(null, testconfig.timeouts.scriptLoad); + }); -// beforeEach(function(done) { -// fixture.load("singleInput.html"); + beforeEach(function(done) { + fixture.load("singleInput.html"); -// window.setTimeout(function() { -// done() -// }, testconfig.timeouts.eventDelay); -// }); + window.setTimeout(function() { + done() + }, testconfig.timeouts.eventDelay); + }); -// after(function() { -// teardownKMW(); -// }); + after(function() { + teardownKMW(); + }); -// afterEach(function() { -// fixture.cleanup(); -// }); + afterEach(function() { + fixture.cleanup(); + }); -// it('Keyboard + OSK simulation', function() { -// this.timeout(testconfig.timeouts.scriptLoad * (testconfig.mobile ? 1 : 2)); -// /* Interestingly, this still works on iOS, probably because we're able to force-set -// * the 'location' property in the simulated event on mobile devices, even when iOS neglects to -// * set it for real events. -// */ -// return runKeyboardTestFromJSON('/engine_tests/chirality.json', -// {usingOSK: false}, -// assert.equal, -// testconfig.timeouts.scriptLoad).then(() => { -// /* We only really care to test the 'desktop' OSK because of how it directly models the modifier keys. -// * -// * The 'phone' and 'layout' versions take shortcuts that bypass any tricky chiral logic; -// * a better test for those would be to ensure the touch OSK is constructed properly. -// */ -// if(!testconfig.mobile) { -// return runKeyboardTestFromJSON('/engine_tests/chirality.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad); -// } -// }); -// }); -// }); \ No newline at end of file + it('Keyboard + OSK simulation', function() { + this.timeout(testconfig.timeouts.scriptLoad * (testconfig.mobile ? 1 : 2)); + /* Interestingly, this still works on iOS, probably because we're able to force-set + * the 'location' property in the simulated event on mobile devices, even when iOS neglects to + * set it for real events. + */ + return runKeyboardTestFromJSON('/engine_tests/chirality.json', + {usingOSK: false}, + assert.equal, + testconfig.timeouts.scriptLoad).then(() => { + /* We only really care to test the 'desktop' OSK because of how it directly models the modifier keys. + * + * The 'phone' and 'layout' versions take shortcuts that bypass any tricky chiral logic; + * a better test for those would be to ensure the touch OSK is constructed properly. + */ + if(!testconfig.mobile) { + return runKeyboardTestFromJSON('/engine_tests/chirality.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad); + } + }); + }); +}); \ No newline at end of file diff --git a/web/src/test/auto/integrated/cases/events.js b/web/src/test/auto/integrated/cases/events.js index 3160e6ad6c8be115dd033da73f8e23249aae4153..2068a86aeabbb7feb6d9d206cf3db5893f8533c5 100644 --- a/web/src/test/auto/integrated/cases/events.js +++ b/web/src/test/auto/integrated/cases/events.js @@ -1,113 +1,118 @@ -// var assert = chai.assert; +var assert = chai.assert; -// describe('Event Management', function() { -// this.timeout(testconfig.timeouts.standard); +import { + loadKeyboardFromJSON, + setupKMW, + teardownKMW +} from "../test_utils.js"; +import * as KMWRecorder from '/@keymanapp/keyman/build/tools/testing/recorder/lib/index.mjs'; -// before(function() { -// this.timeout(testconfig.timeouts.scriptLoad * 2); -// fixture.setBase('fixtures'); -// fixture.load("eventTestConfig.html"); +describe('Event Management', function() { + this.timeout(testconfig.timeouts.standard); -// return setupKMW(null, testconfig.timeouts.scriptLoad).then(() => { -// // We use this keyboard since we only need minimal input functionality for these tests. -// // Smaller is better when dealing with net latency. -// return loadKeyboardFromJSON("/keyboards/test_simple_deadkeys.json", testconfig.timeouts.scriptLoad); -// }); -// }); + before(function() { + this.timeout(testconfig.timeouts.scriptLoad * 2); + fixture.setBase('fixtures'); + fixture.load("eventTestConfig.html"); -// after(function() { -// teardownKMW(); -// }); + return setupKMW(null, testconfig.timeouts.scriptLoad).then(() => { + // We use this keyboard since we only need minimal input functionality for these tests. + // Smaller is better when dealing with net latency. + return loadKeyboardFromJSON("/keyboards/test_simple_deadkeys.json", testconfig.timeouts.scriptLoad); + }); + }); -// it('Keystroke-based onChange event generation', function() { -// var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0}; -// var event = new KMWRecorder.PhysicalInputEventSpec(simple_A); + after(function() { + teardownKMW(); + }); -// var ele = document.getElementById("input"); + it('Keystroke-based onChange event generation', function() { + var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0}; + var event = new KMWRecorder.PhysicalInputEventSpec(simple_A); -// ele.onchange = function() { -// ele.onchange = null; -// } + var ele = document.getElementById("input"); -// // A bit of a force-hack to ensure the element is seen as active for the tests. -// com.keyman.dom['DOMEventHandlers'].states._lastActiveElement = ele; -// com.keyman.dom['DOMEventHandlers'].states._activeElement = ele; + ele.onchange = function() { + ele.onchange = null; + } -// let eventDriver = new KMWRecorder.BrowserDriver(ele); -// eventDriver.simulateEvent(event); + keyman.setActiveElement(ele); -// let focusEvent = new FocusEvent('blur', {relatedTarget: ele}); -// ele.dispatchEvent(focusEvent); + let eventDriver = new KMWRecorder.BrowserDriver(ele); + eventDriver.simulateEvent(event); -// // Asserts that the handler is called. As the handler clears itself, it will only -// // remain set if it hasn't been called. -// assert.isNull(ele.onchange, '`onchange` handler was not called'); -// }); + let focusEvent = new FocusEvent('blur', {relatedTarget: ele}); + ele.dispatchEvent(focusEvent); -// it('OSK-based onChange event generation', function() { -// var simple_A = {"type":"osk","keyID":"default-K_A"}; -// var event = new KMWRecorder.OSKInputEventSpec(simple_A); + // Asserts that the handler is called. As the handler clears itself, it will only + // remain set if it hasn't been called. + assert.isNull(ele.onchange, '`onchange` handler was not called'); + }); -// var ele = document.getElementById("input"); + it('OSK-based onChange event generation', function() { + var simple_A = {"type":"osk","keyID":"default-K_A"}; + var event = new KMWRecorder.OSKInputEventSpec(simple_A); -// ele.onchange = function() { -// ele.onchange = null; -// } + var ele = document.getElementById("input"); -// // A bit of a force-hack to ensure the element is seen as active for the tests. -// com.keyman.dom['DOMEventHandlers'].states._lastActiveElement = ele; -// com.keyman.dom['DOMEventHandlers'].states._activeElement = ele; + ele.onchange = function() { + ele.onchange = null; + } -// let eventDriver = new KMWRecorder.BrowserDriver(ele); -// eventDriver.simulateEvent(event); + keyman.setActiveElement(ele); -// let focusEvent = new FocusEvent('blur', {relatedTarget: ele}); -// ele.dispatchEvent(focusEvent); + let eventDriver = new KMWRecorder.BrowserDriver(ele); + eventDriver.simulateEvent(event); -// // Asserts that the handler is called. As the handler clears itself, it will only -// // remain set if it hasn't been called. -// assert.isNull(ele.onchange, '`onchange` handler was not called'); -// }); + let focusEvent = new FocusEvent('blur', {relatedTarget: ele}); + ele.dispatchEvent(focusEvent); -// it('Keystroke-based onInput event generation', function() { -// var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0}; -// var event = new KMWRecorder.PhysicalInputEventSpec(simple_A); + // Asserts that the handler is called. As the handler clears itself, it will only + // remain set if it hasn't been called. + assert.isNull(ele.onchange, '`onchange` handler was not called'); + }); -// var ele = document.getElementById("input"); + it('Keystroke-based onInput event generation', function() { + var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0}; + var event = new KMWRecorder.PhysicalInputEventSpec(simple_A); -// var counterObj = {i:0}; -// var fin = 3; + var ele = document.getElementById("input"); + keyman.setActiveElement(ele); -// ele.addEventListener("input", function() { -// counterObj.i++; -// }); + var counterObj = {i:0}; + var fin = 3; -// let eventDriver = new KMWRecorder.BrowserDriver(ele); -// eventDriver.simulateEvent(event); -// eventDriver.simulateEvent(event); -// eventDriver.simulateEvent(event); + ele.addEventListener("input", function() { + counterObj.i++; + }); -// assert.equal(counterObj.i, fin, "Event handler not called the expected number of times"); -// }); + let eventDriver = new KMWRecorder.BrowserDriver(ele); + eventDriver.simulateEvent(event); + eventDriver.simulateEvent(event); + eventDriver.simulateEvent(event); -// it('OSK-based onInput event generation', function() { -// var simple_A = {"type":"osk","keyID":"default-K_A"}; -// var event = new KMWRecorder.OSKInputEventSpec(simple_A); + assert.equal(counterObj.i, fin, "Event handler not called the expected number of times"); + }); -// var ele = document.getElementById("input"); + it('OSK-based onInput event generation', function() { + var simple_A = {"type":"osk","keyID":"default-K_A"}; + var event = new KMWRecorder.OSKInputEventSpec(simple_A); -// var counterObj = {i:0}; -// var fin = 3; + var ele = document.getElementById("input"); + keyman.setActiveElement(ele); -// ele.addEventListener("input", function() { -// counterObj.i++; -// }); + var counterObj = {i:0}; + var fin = 3; -// let eventDriver = new KMWRecorder.BrowserDriver(ele); -// eventDriver.simulateEvent(event); -// eventDriver.simulateEvent(event); -// eventDriver.simulateEvent(event); + ele.addEventListener("input", function() { + counterObj.i++; + }); -// assert.equal(counterObj.i, fin, "Event handler not called the expected number of times"); -// }); -// }); \ No newline at end of file + let eventDriver = new KMWRecorder.BrowserDriver(ele); + eventDriver.simulateEvent(event); + eventDriver.simulateEvent(event); + eventDriver.simulateEvent(event); + + assert.equal(counterObj.i, fin, "Event handler not called the expected number of times"); + }); +}); \ No newline at end of file diff --git a/web/src/test/auto/integrated/cases/text_selection.js b/web/src/test/auto/integrated/cases/text_selection.js index 3896d8218dea9ceac71ee6bb96ae5a5091c85188..906d2c36993b7bde82b1b2b25a16a4f7e293f848 100644 --- a/web/src/test/auto/integrated/cases/text_selection.js +++ b/web/src/test/auto/integrated/cases/text_selection.js @@ -1,545 +1,550 @@ -// var assert = chai.assert; - -// describe('Text Selection', function() { -// this.timeout(testconfig.timeouts.standard); - -// /* Utility functions */ - -// function setupElement(ele) { -// // A bit of a force-hack to ensure the element is seen as active for the tests. -// com.keyman.dom['DOMEventHandlers'].states._lastActiveElement = ele; -// com.keyman.dom['DOMEventHandlers'].states._activeElement = ele; - -// return ele; -// } - -// function assertInputSteps(ele, count, setup) { -// return new Promise((resolve, reject) => { -// var i = 0; -// var listener = function() { -// i++; -// if(i == count) { -// ele.removeEventListener("input", listener); -// resolve(); -// } -// }; -// ele.addEventListener("input", listener); -// setup(); -// }); -// } - -// function instantiateBrowserDriver(ele) { -// return new KMWRecorder.BrowserDriver(setupElement(ele)); -// } - -// /* Define key event specs */ - -// var keys = {}; -// for (var i = 0; i < 26; i++) { -// var simple = {"type":"key","key":String.fromCharCode(i+97),"code":"Key"+String.fromCharCode(i+65),"keyCode":i+65,"modifierSet":0,"location":0}; -// var key = new KMWRecorder.PhysicalInputEventSpec(simple); -// keys[String.fromCharCode(i+65)] = key; -// } -// keys.Backspace = new KMWRecorder.PhysicalInputEventSpec({"type":"key","key":"Backspace","code":"Backspace","keyCode":8,"modifierSet":0,"location":0}); - -// /* Test Setup */ - -// // TODO: Add automated tests for editable DIV, designMode iframe -// for(var inputType of ['Input', 'TextArea']) { -// describe('Text Selection in '+inputType, function() { -// before(function() { -// // These tests require use of KMW's device-detection functionality. -// assert.isFalse(com.keyman.karma.DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure."); -// fixture.setBase('fixtures'); -// fixture.load("single"+inputType+".html"); - -// this.timeout(testconfig.timeouts.scriptLoad*2); -// return setupKMW(null, testconfig.timeouts.scriptLoad).then(() => { -// return loadKeyboardFromJSON("/keyboards/web_context_tests.json", testconfig.timeouts.scriptLoad).then(() => { -// return keyman.setActiveKeyboard("web_context_tests"); -// }); -// }); -// }); - -// after(function() { -// fixture.cleanup(); -// keyman.removeKeyboards('web_context_tests'); -// teardownKMW(); -// }); - -// afterEach(function(done) { -// fixture.load("single"+inputType+".html"); -// window.setTimeout(function(){ -// done(); -// }, testconfig.timeouts.eventDelay); -// }); - -// /* -// --- Tests --- - -// A-E: BMP tests -// F-J: SMP tests -// K-O: Mixed BMP+SMP tests (O = SMP) -// Q-Z: Unused -// */ - -// /** -// * TEST_CONTEXT_BASE -// * -// * Using the web_context_tests keyboard, type abcd. The output after the final key should be '!'. -// */ -// it('Should do a basic transform without selection involved', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); - -// await assertInputSteps(ele, 5, () => { -// eventDriver.simulateEvent(keys.E); -// eventDriver.simulateEvent(keys.A); -// eventDriver.simulateEvent(keys.B); -// eventDriver.simulateEvent(keys.C); -// eventDriver.simulateEvent(keys.D); -// }); - -// assert.strictEqual(ele.value, 'e!', "Expected keys eabcd to transform to e!"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2,2], "Expected caret to be at end of text"); -// }); - -// it('Should do a basic transform without selection involved - SMP', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); - -// await assertInputSteps(ele, 5, () => { -// eventDriver.simulateEvent(keys.J); -// eventDriver.simulateEvent(keys.F); -// eventDriver.simulateEvent(keys.G); -// eventDriver.simulateEvent(keys.H); -// eventDriver.simulateEvent(keys.I); -// }); - -// assert.strictEqual(ele.value, 'ðŒˆðŒ™', "Expected keys eabcd to transform to ðŒˆðŒ™"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4,4], "Expected caret to be at end of text"); -// }); - -// for(var direction of ['forward', 'backward']) { - -// /** -// * TEST_SELECTION -// * -// * Using the web_context_tests keyboard, type abcx. Select bc and type q. -// * The output after the final key should be aqx. -// */ -// it('Should do a basic selection replacement, in '+direction+' direction', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); - -// // Step 1: 'abcx' - -// await assertInputSteps(ele, 4, () => { -// eventDriver.simulateEvent(keys.A); -// eventDriver.simulateEvent(keys.B); -// eventDriver.simulateEvent(keys.C); -// eventDriver.simulateEvent(keys.X); -// }); - -// assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'"); - -// // Step 2, select 'bc', replace with 'q' - -// ele.selectionStart = 1; -// ele.selectionEnd = 3; -// ele.selectionDirection = direction; -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 3], "Sanity check: expected selection to be 'bc'"); - -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.Q); -// }); - -// assert.strictEqual(ele.value, 'aqx', "Expected selection to have been replaced with 'q'"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'q'"); -// }); - -// it('Should do a basic selection replacement, with a matching rule, in '+direction+' direction', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); - -// // Step 1: 'abcx' - -// await assertInputSteps(ele, 4, () => { -// eventDriver.simulateEvent(keys.A); -// eventDriver.simulateEvent(keys.B); -// eventDriver.simulateEvent(keys.C); -// eventDriver.simulateEvent(keys.X); -// }); - -// assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'"); - -// // Step 2, select 'bc', replace with [p] => 'q' - -// ele.selectionStart = 1; -// ele.selectionEnd = 3; -// ele.selectionDirection = direction; -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 3], "Sanity check: expected selection to be 'bc'"); - -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.P); -// }); - -// assert.strictEqual(ele.value, 'aqx', "Expected selection to have been replaced with 'q'"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'q'"); -// }); - -// it('Should do a basic selection replacement, in '+direction+' direction - SMP', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); - -// // Step 1: 'fghj' - -// await assertInputSteps(ele, 4, () => { -// eventDriver.simulateEvent(keys.F); -// eventDriver.simulateEvent(keys.G); -// eventDriver.simulateEvent(keys.H); -// eventDriver.simulateEvent(keys.J); -// }); - -// assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚ðŒˆ', "Sanity check: expected text to be 'ðŒ€ðŒðŒ‚ðŒˆ'"); - -// // Step 2, select 'ðŒðŒ‚', replace with 'ðˆ' - -// ele.selectionStart = 2; -// ele.selectionEnd = 6; -// ele.selectionDirection = direction; -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 6], "Sanity check: expected selection to be 'ðŒðŒ‚'"); - -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.O); -// }); - -// assert.strictEqual(ele.value, 'ðŒ€ðˆðŒˆ', "Expected selection to have been replaced with 'ðˆ'"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4, 4], "Expected caret to be after 'ðˆ'"); -// }); +var assert = chai.assert; + +import { + DEVICE_DETECT_FAILURE, + loadKeyboardFromJSON, + setupKMW, + teardownKMW +} from "../test_utils.js"; +import * as KMWRecorder from '/@keymanapp/keyman/build/tools/testing/recorder/lib/index.mjs'; + +describe('Text Selection', function() { + this.timeout(testconfig.timeouts.standard); + + /* Utility functions */ + + function setupElement(ele) { + keyman.setActiveElement(ele); + return ele; + } + + function assertInputSteps(ele, count, setup) { + return new Promise((resolve, reject) => { + var i = 0; + var listener = function() { + i++; + if(i == count) { + ele.removeEventListener("input", listener); + resolve(); + } + }; + ele.addEventListener("input", listener); + setup(); + }); + } + + function instantiateBrowserDriver(ele) { + return new KMWRecorder.BrowserDriver(setupElement(ele)); + } + + /* Define key event specs */ + + var keys = {}; + for (var i = 0; i < 26; i++) { + var simple = {"type":"key","key":String.fromCharCode(i+97),"code":"Key"+String.fromCharCode(i+65),"keyCode":i+65,"modifierSet":0,"location":0}; + var key = new KMWRecorder.PhysicalInputEventSpec(simple); + keys[String.fromCharCode(i+65)] = key; + } + keys.Backspace = new KMWRecorder.PhysicalInputEventSpec({"type":"key","key":"Backspace","code":"Backspace","keyCode":8,"modifierSet":0,"location":0}); + + /* Test Setup */ + + // TODO: Add automated tests for editable DIV, designMode iframe + for(var inputType of ['Input', 'TextArea']) { + describe('Text Selection in '+inputType, function() { + before(function() { + // These tests require use of KMW's device-detection functionality. + assert.isFalse(DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure."); + fixture.setBase('fixtures'); + fixture.load("single"+inputType+".html"); + + this.timeout(testconfig.timeouts.scriptLoad*2); + return setupKMW(null, testconfig.timeouts.scriptLoad).then(() => { + return loadKeyboardFromJSON("/keyboards/web_context_tests.json", testconfig.timeouts.scriptLoad).then(() => { + return keyman.setActiveKeyboard("web_context_tests"); + }); + }); + }); + + after(function() { + fixture.cleanup(); + keyman.removeKeyboards('web_context_tests'); + teardownKMW(); + }); + + afterEach(function(done) { + fixture.load("single"+inputType+".html"); + window.setTimeout(function(){ + done(); + }, testconfig.timeouts.eventDelay); + }); + + /* + --- Tests --- + + A-E: BMP tests + F-J: SMP tests + K-O: Mixed BMP+SMP tests (O = SMP) + Q-Z: Unused + */ + + /** + * TEST_CONTEXT_BASE + * + * Using the web_context_tests keyboard, type abcd. The output after the final key should be '!'. + */ + it('Should do a basic transform without selection involved', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); + + await assertInputSteps(ele, 5, () => { + eventDriver.simulateEvent(keys.E); + eventDriver.simulateEvent(keys.A); + eventDriver.simulateEvent(keys.B); + eventDriver.simulateEvent(keys.C); + eventDriver.simulateEvent(keys.D); + }); + + assert.strictEqual(ele.value, 'e!', "Expected keys eabcd to transform to e!"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2,2], "Expected caret to be at end of text"); + }); + + it('Should do a basic transform without selection involved - SMP', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); + + await assertInputSteps(ele, 5, () => { + eventDriver.simulateEvent(keys.J); + eventDriver.simulateEvent(keys.F); + eventDriver.simulateEvent(keys.G); + eventDriver.simulateEvent(keys.H); + eventDriver.simulateEvent(keys.I); + }); + + assert.strictEqual(ele.value, 'ðŒˆðŒ™', "Expected keys eabcd to transform to ðŒˆðŒ™"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4,4], "Expected caret to be at end of text"); + }); + + for(var direction of ['forward', 'backward']) { + + /** + * TEST_SELECTION + * + * Using the web_context_tests keyboard, type abcx. Select bc and type q. + * The output after the final key should be aqx. + */ + it('Should do a basic selection replacement, in '+direction+' direction', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); + + // Step 1: 'abcx' + + await assertInputSteps(ele, 4, () => { + eventDriver.simulateEvent(keys.A); + eventDriver.simulateEvent(keys.B); + eventDriver.simulateEvent(keys.C); + eventDriver.simulateEvent(keys.X); + }); + + assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'"); + + // Step 2, select 'bc', replace with 'q' + + ele.selectionStart = 1; + ele.selectionEnd = 3; + ele.selectionDirection = direction; + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 3], "Sanity check: expected selection to be 'bc'"); + + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.Q); + }); + + assert.strictEqual(ele.value, 'aqx', "Expected selection to have been replaced with 'q'"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'q'"); + }); + + it('Should do a basic selection replacement, with a matching rule, in '+direction+' direction', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); + + // Step 1: 'abcx' + + await assertInputSteps(ele, 4, () => { + eventDriver.simulateEvent(keys.A); + eventDriver.simulateEvent(keys.B); + eventDriver.simulateEvent(keys.C); + eventDriver.simulateEvent(keys.X); + }); + + assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'"); + + // Step 2, select 'bc', replace with [p] => 'q' + + ele.selectionStart = 1; + ele.selectionEnd = 3; + ele.selectionDirection = direction; + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 3], "Sanity check: expected selection to be 'bc'"); + + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.P); + }); + + assert.strictEqual(ele.value, 'aqx', "Expected selection to have been replaced with 'q'"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'q'"); + }); + + it('Should do a basic selection replacement, in '+direction+' direction - SMP', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); + + // Step 1: 'fghj' + + await assertInputSteps(ele, 4, () => { + eventDriver.simulateEvent(keys.F); + eventDriver.simulateEvent(keys.G); + eventDriver.simulateEvent(keys.H); + eventDriver.simulateEvent(keys.J); + }); + + assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚ðŒˆ', "Sanity check: expected text to be 'ðŒ€ðŒðŒ‚ðŒˆ'"); + + // Step 2, select 'ðŒðŒ‚', replace with 'ðˆ' + + ele.selectionStart = 2; + ele.selectionEnd = 6; + ele.selectionDirection = direction; + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 6], "Sanity check: expected selection to be 'ðŒðŒ‚'"); + + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.O); + }); + + assert.strictEqual(ele.value, 'ðŒ€ðˆðŒˆ', "Expected selection to have been replaced with 'ðˆ'"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4, 4], "Expected caret to be after 'ðˆ'"); + }); -// /** -// * TEST_CONTEXT_SELECTION -// * -// * Using the attached web_context_tests keyboard, type abcx. Select the x character, and type d. -// * The output after the final key should be 'abcd'. -// */ -// it('Should ignore context when a selection, in '+direction+' direction, is made', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); + /** + * TEST_CONTEXT_SELECTION + * + * Using the attached web_context_tests keyboard, type abcx. Select the x character, and type d. + * The output after the final key should be 'abcd'. + */ + it('Should ignore context when a selection, in '+direction+' direction, is made', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); -// // Step 1: 'abcx' + // Step 1: 'abcx' -// await assertInputSteps(ele, 4, () => { -// eventDriver.simulateEvent(keys.A); -// eventDriver.simulateEvent(keys.B); -// eventDriver.simulateEvent(keys.C); -// eventDriver.simulateEvent(keys.X); -// }); + await assertInputSteps(ele, 4, () => { + eventDriver.simulateEvent(keys.A); + eventDriver.simulateEvent(keys.B); + eventDriver.simulateEvent(keys.C); + eventDriver.simulateEvent(keys.X); + }); -// assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'"); + assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'"); -// // Step 2, select 'x', replace with 'd' + // Step 2, select 'x', replace with 'd' -// ele.selectionStart = 3; -// ele.selectionEnd = 4; -// ele.selectionDirection = direction; -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 4], "Sanity check: expected selection to be 'x'"); + ele.selectionStart = 3; + ele.selectionEnd = 4; + ele.selectionDirection = direction; + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 4], "Sanity check: expected selection to be 'x'"); -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.D); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.D); + }); -// assert.strictEqual(ele.value, 'abcd', "Expected selection to have been replaced with 'd'"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4, 4], "Expected caret to be after 'd'"); -// }); + assert.strictEqual(ele.value, 'abcd', "Expected selection to have been replaced with 'd'"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4, 4], "Expected caret to be after 'd'"); + }); -// it('Should ignore context when a selection, in '+direction+' direction, is made - SMP', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); + it('Should ignore context when a selection, in '+direction+' direction, is made - SMP', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); -// // Step 1: 'fghj' + // Step 1: 'fghj' -// await assertInputSteps(ele, 4, () => { -// eventDriver.simulateEvent(keys.F); -// eventDriver.simulateEvent(keys.G); -// eventDriver.simulateEvent(keys.H); -// eventDriver.simulateEvent(keys.J); -// }); + await assertInputSteps(ele, 4, () => { + eventDriver.simulateEvent(keys.F); + eventDriver.simulateEvent(keys.G); + eventDriver.simulateEvent(keys.H); + eventDriver.simulateEvent(keys.J); + }); -// assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚ðŒˆ', "Sanity check: expected text to be 'ðŒ€ðŒðŒ‚ðŒˆ'"); + assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚ðŒˆ', "Sanity check: expected text to be 'ðŒ€ðŒðŒ‚ðŒˆ'"); -// // Step 2, select 'ðŒˆ', replace with [i] + // Step 2, select 'ðŒˆ', replace with [i] -// ele.selectionStart = 6; -// ele.selectionEnd = 8; -// ele.selectionDirection = direction; -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 8], "Sanity check: expected selection to be 'ðŒˆ'"); + ele.selectionStart = 6; + ele.selectionEnd = 8; + ele.selectionDirection = direction; + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 8], "Sanity check: expected selection to be 'ðŒˆ'"); -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.I); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.I); + }); -// assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚i', "Expected selection to have been replaced with 'i'"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [7, 7], "Expected caret to be after 'i'"); -// }); + assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚i', "Expected selection to have been replaced with 'i'"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [7, 7], "Expected caret to be after 'i'"); + }); -// /** -// * TEST_CONTEXT_SELECTION_2 -// * -// * Using the attached web_context_tests keyboard, type abcx. Select the x character, and -// * delete it with Backspace. Type d. The output after the final key should be !. -// */ -// it('Should correctly delete selection, in '+direction+' direction, with backspace and not lose sync', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); + /** + * TEST_CONTEXT_SELECTION_2 + * + * Using the attached web_context_tests keyboard, type abcx. Select the x character, and + * delete it with Backspace. Type d. The output after the final key should be !. + */ + it('Should correctly delete selection, in '+direction+' direction, with backspace and not lose sync', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); -// // Step 1: 'abcx' + // Step 1: 'abcx' -// await assertInputSteps(ele, 4, () => { -// eventDriver.simulateEvent(keys.A); -// eventDriver.simulateEvent(keys.B); -// eventDriver.simulateEvent(keys.C); -// eventDriver.simulateEvent(keys.X); -// }); + await assertInputSteps(ele, 4, () => { + eventDriver.simulateEvent(keys.A); + eventDriver.simulateEvent(keys.B); + eventDriver.simulateEvent(keys.C); + eventDriver.simulateEvent(keys.X); + }); -// assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'"); + assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'"); -// // Step 2, select 'x', press Backspace + // Step 2, select 'x', press Backspace -// ele.selectionStart = 3; -// ele.selectionEnd = 4; -// ele.selectionDirection = direction; -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 4], "Sanity check: expected selection to be 'x'"); + ele.selectionStart = 3; + ele.selectionEnd = 4; + ele.selectionDirection = direction; + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 4], "Sanity check: expected selection to be 'x'"); -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.Backspace); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.Backspace); + }); -// assert.strictEqual(ele.value, 'abc', "Expected selection to have been deleted"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 3], "Expected caret to be after 'c'"); + assert.strictEqual(ele.value, 'abc', "Expected selection to have been deleted"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 3], "Expected caret to be after 'c'"); -// // Step 3, type [d] + // Step 3, type [d] -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.D); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.D); + }); -// assert.strictEqual(ele.value, '!', "Expected text to have been transformed to '!'"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 1], "Expected caret to be after '!'"); -// }); + assert.strictEqual(ele.value, '!', "Expected text to have been transformed to '!'"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 1], "Expected caret to be after '!'"); + }); -// it('Should correctly delete selection, in '+direction+' direction, with backspace and not lose sync - SMP', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); + it('Should correctly delete selection, in '+direction+' direction, with backspace and not lose sync - SMP', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); -// // Step 1: 'abcx' + // Step 1: 'abcx' -// await assertInputSteps(ele, 4, () => { -// eventDriver.simulateEvent(keys.F); -// eventDriver.simulateEvent(keys.G); -// eventDriver.simulateEvent(keys.H); -// eventDriver.simulateEvent(keys.J); -// }); + await assertInputSteps(ele, 4, () => { + eventDriver.simulateEvent(keys.F); + eventDriver.simulateEvent(keys.G); + eventDriver.simulateEvent(keys.H); + eventDriver.simulateEvent(keys.J); + }); -// assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚ðŒˆ', "Sanity check: expected text to be 'ðŒ€ðŒðŒ‚ðŒˆ'"); + assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚ðŒˆ', "Sanity check: expected text to be 'ðŒ€ðŒðŒ‚ðŒˆ'"); -// // Step 2, select 'ðŒˆ', press Backspace + // Step 2, select 'ðŒˆ', press Backspace -// ele.selectionStart = 6; -// ele.selectionEnd = 8; -// ele.selectionDirection = direction; -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 8], "Sanity check: expected selection to be 'ðŒˆ'"); + ele.selectionStart = 6; + ele.selectionEnd = 8; + ele.selectionDirection = direction; + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 8], "Sanity check: expected selection to be 'ðŒˆ'"); -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.Backspace); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.Backspace); + }); -// assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚', "Expected selection to have been deleted"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 6], "Expected caret to be after 'ðŒ‚'"); + assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚', "Expected selection to have been deleted"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 6], "Expected caret to be after 'ðŒ‚'"); -// // Step 3, type [i] + // Step 3, type [i] -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.I); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.I); + }); -// assert.strictEqual(ele.value, 'ðŒ™', "Expected text to have been transformed to 'ðŒ™'"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'ðŒ™'"); -// }); + assert.strictEqual(ele.value, 'ðŒ™', "Expected text to have been transformed to 'ðŒ™'"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'ðŒ™'"); + }); -// /** -// * TEST_CONTEXT_SELECTION_3 -// * -// * Using the attached web_context_tests keyboard, type abcx. Select the x character, and type y. -// * Press Backspace, and type d. The output after the final key should be !. -// */ -// it('Should correctly replace selection, in '+direction+' direction, and not lose sync', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); + /** + * TEST_CONTEXT_SELECTION_3 + * + * Using the attached web_context_tests keyboard, type abcx. Select the x character, and type y. + * Press Backspace, and type d. The output after the final key should be !. + */ + it('Should correctly replace selection, in '+direction+' direction, and not lose sync', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); -// // Step 1: 'abcx' + // Step 1: 'abcx' -// await assertInputSteps(ele, 4, () => { -// eventDriver.simulateEvent(keys.A); -// eventDriver.simulateEvent(keys.B); -// eventDriver.simulateEvent(keys.C); -// eventDriver.simulateEvent(keys.X); -// }); + await assertInputSteps(ele, 4, () => { + eventDriver.simulateEvent(keys.A); + eventDriver.simulateEvent(keys.B); + eventDriver.simulateEvent(keys.C); + eventDriver.simulateEvent(keys.X); + }); -// assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'"); + assert.strictEqual(ele.value, 'abcx', "Sanity check: expected text to be 'abcx'"); -// // Step 2, select 'x', press y + // Step 2, select 'x', press y -// ele.selectionStart = 3; -// ele.selectionEnd = 4; -// ele.selectionDirection = direction; -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 4], "Sanity check: expected selection to be 'x'"); + ele.selectionStart = 3; + ele.selectionEnd = 4; + ele.selectionDirection = direction; + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 4], "Sanity check: expected selection to be 'x'"); -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.Y); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.Y); + }); -// assert.strictEqual(ele.value, 'abcy', "Expected selection to have been replaced with y"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4, 4], "Expected caret to be after 'y'"); + assert.strictEqual(ele.value, 'abcy', "Expected selection to have been replaced with y"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4, 4], "Expected caret to be after 'y'"); -// // Step 3, press Backspace + // Step 3, press Backspace -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.Backspace); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.Backspace); + }); -// assert.strictEqual(ele.value, 'abc', "Expected y to have been deleted"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 3], "Expected caret to be after 'c'"); + assert.strictEqual(ele.value, 'abc', "Expected y to have been deleted"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 3], "Expected caret to be after 'c'"); -// // Step 4, type 'd' + // Step 4, type 'd' -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.D); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.D); + }); -// assert.strictEqual(ele.value, '!', "Expected text to have been transformed to '!'"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 1], "Expected caret to be after '!'"); -// }); + assert.strictEqual(ele.value, '!', "Expected text to have been transformed to '!'"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 1], "Expected caret to be after '!'"); + }); -// it('Should correctly replace selection, in '+direction+' direction, and not lose sync - SMP', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); + it('Should correctly replace selection, in '+direction+' direction, and not lose sync - SMP', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); -// // Step 1: 'abcx' + // Step 1: 'abcx' -// await assertInputSteps(ele, 4, () => { -// eventDriver.simulateEvent(keys.F); -// eventDriver.simulateEvent(keys.G); -// eventDriver.simulateEvent(keys.H); -// eventDriver.simulateEvent(keys.J); -// }); + await assertInputSteps(ele, 4, () => { + eventDriver.simulateEvent(keys.F); + eventDriver.simulateEvent(keys.G); + eventDriver.simulateEvent(keys.H); + eventDriver.simulateEvent(keys.J); + }); -// assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚ðŒˆ', "Sanity check: expected text to be 'ðŒ€ðŒðŒ‚ðŒˆ'"); + assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚ðŒˆ', "Sanity check: expected text to be 'ðŒ€ðŒðŒ‚ðŒˆ'"); -// // Step 2, select 'ðŒˆ', press [k] + // Step 2, select 'ðŒˆ', press [k] -// ele.selectionStart = 6; -// ele.selectionEnd = 8; -// ele.selectionDirection = direction; -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 8], "Sanity check: expected selection to be 'ðŒˆ'"); + ele.selectionStart = 6; + ele.selectionEnd = 8; + ele.selectionDirection = direction; + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 8], "Sanity check: expected selection to be 'ðŒˆ'"); -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.K); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.K); + }); -// assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚ðŒ°', "Expected selection to have been replaced with ðŒ°"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [8, 8], "Expected caret to be after 'ðŒ°'"); + assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚ðŒ°', "Expected selection to have been replaced with ðŒ°"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [8, 8], "Expected caret to be after 'ðŒ°'"); -// // Step 3, press Backspace + // Step 3, press Backspace -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.Backspace); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.Backspace); + }); -// assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚', "Expected ðŒ° to have been deleted"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 6], "Expected caret to be after 'ðŒ‚'"); + assert.strictEqual(ele.value, 'ðŒ€ðŒðŒ‚', "Expected ðŒ° to have been deleted"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [6, 6], "Expected caret to be after 'ðŒ‚'"); -// // Step 4, type [i] + // Step 4, type [i] -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.I); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.I); + }); -// assert.strictEqual(ele.value, 'ðŒ™', "Expected text to have been transformed to 'ðŒ™'"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'ðŒ™'"); -// }); + assert.strictEqual(ele.value, 'ðŒ™', "Expected text to have been transformed to 'ðŒ™'"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'ðŒ™'"); + }); -// /** -// * TEST_CONTEXT_SELECTION_4 -// * -// * Using the attached web_context_tests keyboard, type xabcx. -// * Select the abc characters, and type d. The output after the final key should be xdx. -// */ -// it('Should not treat the selection, in '+direction+' direction, as context', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); + /** + * TEST_CONTEXT_SELECTION_4 + * + * Using the attached web_context_tests keyboard, type xabcx. + * Select the abc characters, and type d. The output after the final key should be xdx. + */ + it('Should not treat the selection, in '+direction+' direction, as context', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); -// // Step 1: 'xabcx' + // Step 1: 'xabcx' -// await assertInputSteps(ele, 5, () => { -// eventDriver.simulateEvent(keys.X); -// eventDriver.simulateEvent(keys.A); -// eventDriver.simulateEvent(keys.B); -// eventDriver.simulateEvent(keys.C); -// eventDriver.simulateEvent(keys.X); -// }); + await assertInputSteps(ele, 5, () => { + eventDriver.simulateEvent(keys.X); + eventDriver.simulateEvent(keys.A); + eventDriver.simulateEvent(keys.B); + eventDriver.simulateEvent(keys.C); + eventDriver.simulateEvent(keys.X); + }); -// assert.strictEqual(ele.value, 'xabcx', "Sanity check: expected text to be 'xabcx'"); + assert.strictEqual(ele.value, 'xabcx', "Sanity check: expected text to be 'xabcx'"); -// // Step 2, select 'abc', press 'd' + // Step 2, select 'abc', press 'd' -// ele.selectionStart = 1; -// ele.selectionEnd = 4; -// ele.selectionDirection = direction; -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 4], "Sanity check: expected selection to be 'abc'"); + ele.selectionStart = 1; + ele.selectionEnd = 4; + ele.selectionDirection = direction; + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [1, 4], "Sanity check: expected selection to be 'abc'"); -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.D); -// }); + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.D); + }); -// assert.strictEqual(ele.value, 'xdx', "Expected selection to have been replaced with d"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'd'"); -// }); + assert.strictEqual(ele.value, 'xdx', "Expected selection to have been replaced with d"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 2], "Expected caret to be after 'd'"); + }); -// it('Should not treat the selection, in '+direction+' direction, as context - SMP', async () => { -// var ele = document.getElementById("singleton"); -// var eventDriver = instantiateBrowserDriver(ele); + it('Should not treat the selection, in '+direction+' direction, as context - SMP', async () => { + var ele = document.getElementById("singleton"); + var eventDriver = instantiateBrowserDriver(ele); -// // Step 1: [k][f][g][h][k] + // Step 1: [k][f][g][h][k] -// await assertInputSteps(ele, 5, () => { -// eventDriver.simulateEvent(keys.K); -// eventDriver.simulateEvent(keys.F); -// eventDriver.simulateEvent(keys.G); -// eventDriver.simulateEvent(keys.H); -// eventDriver.simulateEvent(keys.K); -// }); + await assertInputSteps(ele, 5, () => { + eventDriver.simulateEvent(keys.K); + eventDriver.simulateEvent(keys.F); + eventDriver.simulateEvent(keys.G); + eventDriver.simulateEvent(keys.H); + eventDriver.simulateEvent(keys.K); + }); -// assert.strictEqual(ele.value, 'ðŒ°ðŒ€ðŒðŒ‚ðŒ°', "Sanity check: expected text to be 'ðŒ°ðŒ€ðŒðŒ‚ðŒ°'"); + assert.strictEqual(ele.value, 'ðŒ°ðŒ€ðŒðŒ‚ðŒ°', "Sanity check: expected text to be 'ðŒ°ðŒ€ðŒðŒ‚ðŒ°'"); -// // Step 2, select 'ðŒ€ðŒðŒ‚', press [i] + // Step 2, select 'ðŒ€ðŒðŒ‚', press [i] -// ele.selectionStart = 2; -// ele.selectionEnd = 8; -// ele.selectionDirection = direction; -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 8], "Sanity check: expected selection to be 'ðŒ€ðŒðŒ‚'"); - -// await assertInputSteps(ele, 1, () => { -// eventDriver.simulateEvent(keys.I); -// }); + ele.selectionStart = 2; + ele.selectionEnd = 8; + ele.selectionDirection = direction; + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [2, 8], "Sanity check: expected selection to be 'ðŒ€ðŒðŒ‚'"); + + await assertInputSteps(ele, 1, () => { + eventDriver.simulateEvent(keys.I); + }); -// assert.strictEqual(ele.value, 'ðŒ°iðŒ°', "Expected selection to have been replaced with i"); -// assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 3], "Expected caret to be after 'i'"); -// }); -// } -// }); -// } -// }); + assert.strictEqual(ele.value, 'ðŒ°iðŒ°', "Expected selection to have been replaced with i"); + assert.deepEqual([ele.selectionStart, ele.selectionEnd], [3, 3], "Expected caret to be after 'i'"); + }); + } + }); + } +}); diff --git a/web/src/test/auto/integrated/manual.conf.cjs b/web/src/test/auto/integrated/manual.conf.cjs index 2c9e939f08fa4a0ab71c5db92189e22653a34759..1969c4005033328cbf5d94410f77b0b53af735cc 100644 --- a/web/src/test/auto/integrated/manual.conf.cjs +++ b/web/src/test/auto/integrated/manual.conf.cjs @@ -18,7 +18,7 @@ module.exports = function(config) { // level of logging // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.js b/c of constant's definition style. + // Can't set in base.conf.cjs b/c of constant's definition style. logLevel: config.LOG_INFO }; diff --git a/web/src/test/auto/integrated/test_utils.js b/web/src/test/auto/integrated/test_utils.js index b88c5a92d01dc40264aac133237c89cdde2ad787..7392013ebfd204783f04d22d08b6e692a6cb4aa3 100644 --- a/web/src/test/auto/integrated/test_utils.js +++ b/web/src/test/auto/integrated/test_utils.js @@ -1,6 +1,7 @@ // // KeymanWeb test suite - processing of the Karma configuration's client.args parameter. import Device from '/@keymanapp/keyman/build/engine/device-detect/lib/index.mjs'; +import * as KMWRecorder from '/@keymanapp/keyman/build/tools/testing/recorder/lib/index.mjs'; export let DEVICE_DETECT_FAILURE = false; @@ -15,168 +16,177 @@ try { // // Keyman test suite utility methods -// var setupKMW = function(kmwOptions, timeout) { -// var ui; +export function setupKMW(kmwOptions, timeout) { + let ui; -// if(typeof(kmwOptions) == 'string' || typeof(kmwOptions) == 'undefined' || kmwOptions == null) { -// ui = kmwOptions; + if(typeof(kmwOptions) == 'string' || typeof(kmwOptions) == 'undefined' || kmwOptions == null) { + ui = kmwOptions; -// var kmwOptions = { -// attachType:'auto', -// root:'source', -// resources:'../../../../source' -// }; + var kmwOptions = { + attachType:'auto', + root:'/', + resources:'/build/app/resources' + }; -// if(ui) { -// kmwOptions.ui = ui; -// } -// } + if(ui) { + kmwOptions.ui = ui; + } + } -// const kmwPromise = setupScript('source/keymanweb.js', timeout, (scriptEle) => { -// fixture.el.appendChild(scriptEle); -// }); + const kmwPromise = setupScript('build/app/browser/keymanweb.js', timeout, (scriptEle) => { + fixture.el.appendChild(scriptEle); + }); -// ui = kmwOptions.ui; + ui = kmwOptions.ui; -// kmwOptions.attachType = kmwOptions.attachType ? kmwOptions.attachType : 'auto'; + kmwOptions.attachType = kmwOptions.attachType ? kmwOptions.attachType : 'auto'; -// if(!kmwOptions.root) { -// kmwOptions.root = 'source'; -// } + if(!kmwOptions.root) { + kmwOptions.root = '/build/app/browser'; + } -// if(!kmwOptions.resources) { -// kmwOptions.resources = '../../../../source'; -// } + if(!kmwOptions.resources) { + kmwOptions.resources = '/build/resources'; + } -// let uiPromise; -// if(ui) { -// uiPromise = setupScript('ui-source/kmwui' + ui + '.js', timeout, (scriptEle) => { -// fixture.el.appendChild(scriptEle); -// }); + let uiPromise; + if(ui) { + uiPromise = setupScript('build/app/ui/kmwui' + ui + '.js', timeout, (scriptEle) => { + fixture.el.appendChild(scriptEle); + }); -// kmwOptions.ui=ui; -// } + kmwOptions.ui=ui; + } -// let compositePromise = kmwPromise; -// if(uiPromise) { -// compositePromise = Promise.all([kmwPromise, uiPromise]); -// } + let compositePromise = kmwPromise; + if(uiPromise) { + compositePromise = Promise.all([kmwPromise, uiPromise]); + } -// return finalPromise = compositePromise.then(() => { -// if(window['keyman']) { -// return window['keyman'].init(kmwOptions); -// } else { -// return Promise.reject(); -// } -// }); -// } + return compositePromise.then(() => { + if(window['keyman']) { + return window['keyman'].init(kmwOptions); + } else { + return Promise.reject(); + } + }); +} -// /** -// * Produces a script element tied to a Promise for its eventual load (or failure thereof). -// * -// * The script element is only available via callback due to implementation constraints. -// * -// * @param {*} src The source script's (relative) path on the test server. -// * @param {*} timeout -// * @param {*} functor A callback to handle the script element. -// * @returns -// */ -// var setupScript = function(src, timeout, functor) { -// return new Promise((resolve, reject) => { -// const Lscript = document.createElement('script'); -// let hasResolved = false; -// Lscript.charset="UTF-8"; // KMEW-89 -// Lscript.type = 'text/javascript'; -// Lscript.async = false; - -// const timer = window.setTimeout(() => { -// reject("Script load attempt timed out."); -// }, timeout); - -// Lscript.onload = Lscript.onreadystatechange = () => { -// window.clearTimeout(timer); -// if(!hasResolved && (Lscript.readyState === undefined || Lscript.readyState == "complete")) { -// hasResolved = true; -// resolve(); -// } -// } - -// Lscript.onerror = (err) => { -// window.clearTimeout(timer); -// reject(err); -// } - -// Lscript.src = src; - -// functor(Lscript); -// }); -// } +/** + * Produces a script element tied to a Promise for its eventual load (or failure thereof). + * + * The script element is only available via callback due to implementation constraints. + * + * @param {*} src The source script's (relative) path on the test server. + * @param {*} timeout + * @param {*} functor A callback to handle the script element. + * @returns + */ +export function setupScript(src, timeout, functor) { + return new Promise((resolve, reject) => { + const Lscript = document.createElement('script'); + let hasResolved = false; + Lscript.charset="UTF-8"; // KMEW-89 + Lscript.type = 'text/javascript'; + Lscript.async = false; + + const timer = window.setTimeout(() => { + reject("Script load attempt timed out."); + }, timeout); + + Lscript.onload = Lscript.onreadystatechange = () => { + window.clearTimeout(timer); + if(!hasResolved && (Lscript.readyState === undefined || Lscript.readyState == "complete")) { + hasResolved = true; + resolve(); + } + } -// var teardownKMW = function() { -// var error = null; -// if(keyman) { // If our setupKMW fails somehow, this guard prevents a second error report on teardown. - -// // We want to be SURE teardown works correctly, or we'll get lots of strange errors on other tests. -// // Thus, error-handling on shutdown itself. It HAS mattered. -// try { -// keyman['shutdown'](); -// } catch(err) { -// error = err; -// } - -// try { -// var success = delete window["keyman"]; -// if(!success) { -// window["keyman"] = undefined; -// } -// } finally { -// if(error) { -// console.log("Error during KMW shutdown!"); -// throw error; -// } -// } -// } -// } + Lscript.onerror = (err) => { + window.clearTimeout(timer); + reject(err); + } -// var loadKeyboardStub = function(stub, timeout, params) { -// var kbdName = "Keyboard_" + stub.id; - -// keyman.addKeyboards(stub); -// if(!params || !params.passive) { -// return keyman.setActiveKeyboard(kbdName, stub.languages.id); -// } else if(keyman.getActiveKeyboard() != kbdName) { -// return setupScript(stub.filename, timeout, (ele) => { -// fixture.el.appendChild(ele); -// }); -// } else { -// return Promise.resolve(); -// } -// } + Lscript.src = src; -// var loadKeyboardFromJSON = function(jsonPath, timeout, params) { -// var stub = fixture.load(jsonPath, true); + functor(Lscript); + }); +} -// return loadKeyboardStub(stub, timeout, params); -// } +export function teardownKMW() { + var error = null; + if(keyman) { // If our setupKMW fails somehow, this guard prevents a second error report on teardown. -// function runLoadedKeyboardTest(testDef, device, usingOSK, assertCallback) { -// var inputElem = document.getElementById('singleton'); + // We want to be SURE teardown works correctly, or we'll get lots of strange errors on other tests. + // Thus, error-handling on shutdown itself. It HAS mattered. + try { + keyman['shutdown'](); + } catch(err) { + error = err; + } -// let proctor = new KMWRecorder.BrowserProctor(inputElem, device, usingOSK, assertCallback); -// testDef.test(proctor); -// } + try { + var success = delete window["keyman"]; + if(!success) { + window["keyman"] = undefined; + } + } finally { + if(error) { + console.log("Error during KMW shutdown!"); + throw error; + } + } + } +} -// function runKeyboardTestFromJSON(jsonPath, params, assertCallback, timeout) { -// var testSpec = new KMWRecorder.KeyboardTest(fixture.load(jsonPath, true)); -// let device = new com.keyman.Device(); -// device.detect(); +export async function loadKeyboardStub(stub, timeout, params) { + var kbdName = "Keyboard_" + stub.id; -// return loadKeyboardStub(testSpec.keyboard, timeout).then(() => { -// runLoadedKeyboardTest(testSpec, device.coreSpec, params.usingOSK, assertCallback); -// }).finally(() => { -// keyman.removeKeyboards(testSpec.keyboard.id); -// }); -// } + // Returning an "error stub" does not actually throw an error. Makes + // picking up on related errors in unit-test dev a bit trickier, but + // we can manually throw the error from here. + let result = await keyman.addKeyboards(stub); + for(let i=0; i < result.length; i++) { + if(result[i].error) { + throw result[i].error; + } + } + + if(!params || !params.passive) { + return keyman.setActiveKeyboard(kbdName, stub.languages.id); + } else if(keyman.getActiveKeyboard() != kbdName) { + return setupScript(stub.filename, timeout, (ele) => { + fixture.el.appendChild(ele); + }); + } else { + return Promise.resolve(); + } +} + +export async function loadKeyboardFromJSON(jsonPath, timeout, params) { + var stub = fixture.load(jsonPath, true); + + return loadKeyboardStub(stub, timeout, params); +} + +function runLoadedKeyboardTest(testDef, device, usingOSK, assertCallback) { + var inputElem = document.getElementById('singleton'); + + let proctor = new KMWRecorder.BrowserProctor(inputElem, device, usingOSK, assertCallback); + testDef.test(proctor); +} + +export function runKeyboardTestFromJSON(jsonPath, params, assertCallback, timeout) { + var testSpec = new KMWRecorder.KeyboardTest(fixture.load(jsonPath, true)); + let device = new Device(); + device.detect(); + + return loadKeyboardStub(testSpec.keyboard, timeout).then(() => { + runLoadedKeyboardTest(testSpec, device.coreSpec, params.usingOSK, assertCallback); + }).finally(() => { + keyman.removeKeyboards(testSpec.keyboard.id); + }); +} // function retrieveAndReset(Pelem) { // let val = Pelem.value; diff --git a/web/src/test/manual/web/attachment-api/index.html b/web/src/test/manual/web/attachment-api/index.html index 0ee6896af425d0d964f1d1f7ec2c866695dc89a9..f904d25f08e4b631f55473c5f38f979bd3a06a26 100644 --- a/web/src/test/manual/web/attachment-api/index.html +++ b/web/src/test/manual/web/attachment-api/index.html @@ -23,7 +23,7 @@ - + - + + - + diff --git a/web/src/test/manual/web/build-visual-keyboard/index.html b/web/src/test/manual/web/build-visual-keyboard/index.html index a572a638a7373e3a84814ff8d5248e783b6b4d54..2c3e1d6c0aa02477f6bac5e6f27a947e1bc3b0be 100644 --- a/web/src/test/manual/web/build-visual-keyboard/index.html +++ b/web/src/test/manual/web/build-visual-keyboard/index.html @@ -24,7 +24,7 @@ - + - + diff --git a/web/src/test/manual/web/caps-lock-layer-3620/index.html b/web/src/test/manual/web/caps-lock-layer-3620/index.html index 0c8981a72581b5f097056bb1b5e02b59e715aabe..c5b328189f62aef858a86ec3c6122bd8b585d881 100644 --- a/web/src/test/manual/web/caps-lock-layer-3620/index.html +++ b/web/src/test/manual/web/caps-lock-layer-3620/index.html @@ -17,7 +17,7 @@ - + - + + - + + - + + - + + - + diff --git a/web/src/test/manual/web/desktop-ui/float.html b/web/src/test/manual/web/desktop-ui/float.html index 049fa9d316b0d377e3fa2bee87e7c840028aa707..338a66a160c8d07db1d31eed2bbd4a3e961703fe 100644 --- a/web/src/test/manual/web/desktop-ui/float.html +++ b/web/src/test/manual/web/desktop-ui/float.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/desktop-ui/toggle.html b/web/src/test/manual/web/desktop-ui/toggle.html index 6aa99b1c5abb6f9934bc8f4223c7b8dc75a4de4f..857e98466c337f0248cbb8e2367ab547e718e303 100644 --- a/web/src/test/manual/web/desktop-ui/toggle.html +++ b/web/src/test/manual/web/desktop-ui/toggle.html @@ -23,7 +23,7 @@ - + - + @@ -41,7 +41,7 @@ + - + diff --git a/web/src/test/manual/web/empty-row/index.html b/web/src/test/manual/web/empty-row/index.html index 460d544b64528bbc0668c58ae36296e89718da21..02953493e91a9231c2e299c1c0c802c16008090f 100644 --- a/web/src/test/manual/web/empty-row/index.html +++ b/web/src/test/manual/web/empty-row/index.html @@ -23,7 +23,7 @@ - + - + + - + + #KeymanWebControl {width:50%;min-width:600px;} + + + + - - - + + - + keyman.init({ + attachType:'auto' + }); + + - - + + - - + +

KeymanWeb: 'Test case' for proper implementation of the removeKeyboards API function

- -
+

This version is used to test the removeKeyboards API function for proper argument handling.

Automatically-loaded keyboard IDs:

    @@ -71,34 +70,34 @@


    -
+

Add a keyboard by keyboard ID:

- - + +

Remove a keyboard by keyboard ID:

- +

Return to testing home page

- - + + - - + * + *** + --> diff --git a/web/src/test/manual/web/issue103/index.html b/web/src/test/manual/web/issue103/index.html index c02d9312ed49c0d9f672fc5b25cea6f01ccc9487..35b006e34721cc3e4c0ac5d1273ed0b954df909e 100644 --- a/web/src/test/manual/web/issue103/index.html +++ b/web/src/test/manual/web/issue103/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/issue115/index.html b/web/src/test/manual/web/issue115/index.html index 5c9ffeb3afb0b7630e3565f2bcd2393634a8adcb..cd9afcb8fa00258eb353669fc173e32abaddb090 100644 --- a/web/src/test/manual/web/issue115/index.html +++ b/web/src/test/manual/web/issue115/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/issue116/index.html b/web/src/test/manual/web/issue116/index.html index b8b49e1375e17758af2fe77edf29639338f62a87..d81fb947c04ba2c81fa34efbfba19fcf6018b1e7 100644 --- a/web/src/test/manual/web/issue116/index.html +++ b/web/src/test/manual/web/issue116/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/issue1332/index.html b/web/src/test/manual/web/issue1332/index.html index 4b8715a7f170494626f20a98925cdd1b8a5f726e..8bd8c403aa4af93ff275f2d4f850e0add033fef6 100644 --- a/web/src/test/manual/web/issue1332/index.html +++ b/web/src/test/manual/web/issue1332/index.html @@ -23,7 +23,7 @@ - + - + + - + diff --git a/web/src/test/manual/web/issue266/index.html b/web/src/test/manual/web/issue266/index.html index aca186b178488f480c0c6277c506e6f967449ce9..56e52016d765ca9c752ca85638692ffa2e6e7918 100644 --- a/web/src/test/manual/web/issue266/index.html +++ b/web/src/test/manual/web/issue266/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/issue271/index.html b/web/src/test/manual/web/issue271/index.html index 8c055c4015f06d62862f21a7040e93ed8754d835..fa969195a138f80723073ff1ec7ea7297fc1bae1 100644 --- a/web/src/test/manual/web/issue271/index.html +++ b/web/src/test/manual/web/issue271/index.html @@ -23,7 +23,7 @@ - + - + @@ -57,10 +57,10 @@
  • us, en
  • french, fr
  • -
  • european2, no
  • -
  • european2, sv
  • +
  • sil_euro_latin, no
  • +
  • sil_euro_latin, sv
  • hebrew, he
  • -
  • dzongkha, dzo
  • +
  • dzongkha, dz
  • lao_2008_basic, lo

diff --git a/web/src/test/manual/web/issue29/index.html b/web/src/test/manual/web/issue29/index.html index 92fed6a8c1ca8a658e2e5cdb2cc5b4d8c5dbf555..24a226326ca54147df38c301f00aa732754c62a4 100644 --- a/web/src/test/manual/web/issue29/index.html +++ b/web/src/test/manual/web/issue29/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/issue2924/header.js b/web/src/test/manual/web/issue2924/header.js index 8e3ecb745ab7c4a9ba8a6c85d200c03b6a6f89b1..dcd8083c7dc52e8a10883c0bf56702ef3a8e2094 100644 --- a/web/src/test/manual/web/issue2924/header.js +++ b/web/src/test/manual/web/issue2924/header.js @@ -35,7 +35,7 @@ window.addEventListener('load', function() { ? window.location.href.substr(0, window.location.href.lastIndexOf('/')+1) : window.location.href; - keyman.modelManager.register({ + keyman.addModel({ id: 'nrc.en.mtnt', languages: ['en'], path: pageRef + 'dmg.dv.test.model.js' diff --git a/web/src/test/manual/web/issue2924/index.html b/web/src/test/manual/web/issue2924/index.html index 61a1be97cde25f9efe1b20a3c5043ca536f0b96f..12396420c24bf2a13f6feecc31d409e7e8fb245b 100644 --- a/web/src/test/manual/web/issue2924/index.html +++ b/web/src/test/manual/web/issue2924/index.html @@ -6,8 +6,8 @@ KeymanWeb Issue 2924 - Variable Stores and Predictive Text - - + + diff --git a/web/src/test/manual/web/issue3701/index.html b/web/src/test/manual/web/issue3701/index.html index ca2729133abb2df3d439af5c8dd54eedeccbc6b4..4b621678cc699a8ee002a4364c308434f4aca961 100644 --- a/web/src/test/manual/web/issue3701/index.html +++ b/web/src/test/manual/web/issue3701/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + diff --git a/web/src/test/manual/web/issue5312-touch-alias-optimization/index.html b/web/src/test/manual/web/issue5312-touch-alias-optimization/index.html deleted file mode 100644 index 682e279ab6b37293354289e1e6f62c55cf9667a5..0000000000000000000000000000000000000000 --- a/web/src/test/manual/web/issue5312-touch-alias-optimization/index.html +++ /dev/null @@ -1,112 +0,0 @@ - - - - - - - - - - - - - - - KeymanWeb Sample Page - Unminified Source - - - - - - - - - - - - - - - - - - - - -

KeymanWeb Sample Page - Touch-Alias Element Optimization

- -
- -
- -

Text performance with this element:

- -

Text was generated by the Lorem Ipsum Generator.

- -

Return to testing home page

-
- - -
-

--End of Document--

- - - - - diff --git a/web/src/test/manual/web/issue5455/index.html b/web/src/test/manual/web/issue5455/index.html index 0ba1f0f84a97b84fe623ac9b0de88ebb6e945c24..77ea7dbb216bb6c67864433bc4caf5d1399c8bcd 100644 --- a/web/src/test/manual/web/issue5455/index.html +++ b/web/src/test/manual/web/issue5455/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + diff --git a/web/src/test/manual/web/issue63/index.html b/web/src/test/manual/web/issue63/index.html index 43842dc3e9cad571b93f2850f700e70b9a9a877a..55cee63c97995cd2e9f6a064b7e54f6fe7e3abfc 100644 --- a/web/src/test/manual/web/issue63/index.html +++ b/web/src/test/manual/web/issue63/index.html @@ -23,7 +23,7 @@ - + - + diff --git a/web/src/test/manual/web/issue917-context-and-notany/index.html b/web/src/test/manual/web/issue917-context-and-notany/index.html index 1db3b10c4dbf0b3204094b195b64a018c5fea3e0..251edb9142a6c04df491e22c081b0aca7867ed73 100644 --- a/web/src/test/manual/web/issue917-context-and-notany/index.html +++ b/web/src/test/manual/web/issue917-context-and-notany/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + diff --git a/web/src/test/manual/web/keyboard-errors/timeout.js b/web/src/test/manual/web/keyboard-errors/timeout.js index 2af6bf907a3a953dcc3ccce76a13c64e23001c29..d4e21179dfcd6905d02b10d06bd4aeb9a7f708a5 100644 --- a/web/src/test/manual/web/keyboard-errors/timeout.js +++ b/web/src/test/manual/web/keyboard-errors/timeout.js @@ -1,10 +1,21 @@ (function() { var me = document.currentScript; console.log(me); - document.body.addEventListener('load', function(e) { + + var onload = me.onload; + if(onload) { + me.onload = null; + } + + document.body.addEventListener('load', function(e) { // Prevent the element's onload event from firing if(e.srcElement == me || e.target == me) { e.cancelBubble = true; } }, {capture: true}); + + window.setTimeout(function () { + // Restores the function after a slight delay. + me.onload = onload; + }, 1); })(); \ No newline at end of file diff --git a/web/src/test/manual/web/mnemonic/index.html b/web/src/test/manual/web/mnemonic/index.html index 65331615aa5d7eeea99e761b4d5b7f24ac070c51..e0b0f0de79be9402cfe76a55b3a2bf330dacb82e 100644 --- a/web/src/test/manual/web/mnemonic/index.html +++ b/web/src/test/manual/web/mnemonic/index.html @@ -20,7 +20,7 @@ - + - + diff --git a/web/src/test/manual/web/options-with-save/index.html b/web/src/test/manual/web/options-with-save/index.html index 0f91ea354a2f080a232168d55b1205ba29a88aa5..9f607fd11f47c3b7ccd90cc52186de36f85ce960 100644 --- a/web/src/test/manual/web/options-with-save/index.html +++ b/web/src/test/manual/web/options-with-save/index.html @@ -20,7 +20,7 @@ - + - + diff --git a/web/src/test/manual/web/osk-event-buttons/index.html b/web/src/test/manual/web/osk-event-buttons/index.html index 3c88e222017a7797530e2fd385aaa0aa66836519..92dafcdb4a9ec84152176d7e25f168a3e0f57f87 100644 --- a/web/src/test/manual/web/osk-event-buttons/index.html +++ b/web/src/test/manual/web/osk-event-buttons/index.html @@ -23,7 +23,7 @@ - + - + + - + + - + + - + + - + + - + @@ -52,8 +52,7 @@ kmw.init({ attachType: 'auto', - useAlerts: alertType, - resources:'../../resources' + useAlerts: alertType }).then(function() { loadKeyboards(); }); diff --git a/web/src/test/manual/web/rotation-events/index.html b/web/src/test/manual/web/rotation-events/index.html index 6e884f505a248541b8a546e15f759a1b71c0618e..c07b3300c9426d230712a366794ab4c4231752b1 100644 --- a/web/src/test/manual/web/rotation-events/index.html +++ b/web/src/test/manual/web/rotation-events/index.html @@ -14,7 +14,7 @@ - + - + diff --git a/web/src/test/manual/web/scrolling/index.html b/web/src/test/manual/web/scrolling/index.html deleted file mode 100644 index 138e5a43c115bbcb8a26913d0edc06d26d02f2ce..0000000000000000000000000000000000000000 --- a/web/src/test/manual/web/scrolling/index.html +++ /dev/null @@ -1,97 +0,0 @@ - - - - - - - - - - - - - - - KeymanWeb Testing Page - Predictive Text: robust testing - - - - - - - - - - - - - - - - -

KeymanWeb Sample Page - Scrolling Touch Elements

-
- -
- -

Type in your language in this text area:

- - -

Return to testing home page

-
- - - - - - - diff --git a/web/src/test/manual/web/sentry-integration/index.html b/web/src/test/manual/web/sentry-integration/index.html index 9dbf167b88bb85606aa9960f9498da6c37d77c40..51d4635c67bf959349467a2cc85e6bbd0ebd0d5c 100644 --- a/web/src/test/manual/web/sentry-integration/index.html +++ b/web/src/test/manual/web/sentry-integration/index.html @@ -23,7 +23,7 @@ - + - + - + diff --git a/web/src/test/manual/web/spacebar-text/index.html b/web/src/test/manual/web/spacebar-text/index.html index ee1053be0caf7affc0f0dea3e1559a94f537a7a3..2db281323bd6567c3411f6c5d5bbbd22eca7705d 100644 --- a/web/src/test/manual/web/spacebar-text/index.html +++ b/web/src/test/manual/web/spacebar-text/index.html @@ -17,7 +17,7 @@ - + - + + - + + - + + - + + - + + @@ -34,9 +34,9 @@ var kmw=window.keyman; kmw.init({ attachType:'auto' - }).then(function() { - kmw.addKeyboards(); - }); + }).then(function() { + kmw.addKeyboards(); + }); function runRenderer() { diff --git a/web/src/tools/testing/bulk_rendering/renderer_core.ts b/web/src/tools/testing/bulk_rendering/renderer_core.ts index adcbc45069f8eb81f4595edd97296eb068899e51..3348b72f16715d89c8dd481d61cbfc8fb656a938 100644 --- a/web/src/tools/testing/bulk_rendering/renderer_core.ts +++ b/web/src/tools/testing/bulk_rendering/renderer_core.ts @@ -1,296 +1,297 @@ // Includes KeymanWeb's Device class, as it's quite a useful resource for KMW-related projects. - -type KeyboardMap = {[id: string]: any}; - -namespace com.keyman.renderer { - export class BatchRenderer { - static divMaster: HTMLDivElement; - static dummy: HTMLInputElement; - static captureStream: any; - static video: any; - static boundingRect: DOMRect; - static allLayers = false; - static keyboardMatch: RegExp; - - async startCapture() { - let captureStream = null; - - try { - captureStream = await (navigator.mediaDevices as any).getDisplayMedia({ - audio: false, - video: { - width: screen.width, - height: screen.height, - frameRate: 60, - } - }); - } catch(err) { - console.error("Error: " + err); - } - - const video = document.createElement("video"); - video.srcObject = captureStream; - video.play(); - - const result = await new Promise((resolve, reject) => { - video.onloadedmetadata = function () { - resolve(); +import { Device } from 'keyman/engine/device-detect'; + +import type { KeymanEngine } from 'keyman/app/browser'; +import type { FloatingOSKView } from 'keyman/engine/osk'; + +declare var keyman: KeymanEngine; + +type KeyboardData = ReturnType; +type KeyboardMap = {[id: string]: KeyboardData}; + +export class BatchRenderer { + static divMaster: HTMLDivElement; + static dummy: HTMLInputElement; + static captureStream: any; + static video: any; + static boundingRect: DOMRect; + static allLayers = false; + static keyboardMatch: RegExp; + + async startCapture() { + let captureStream = null; + + try { + captureStream = await (navigator.mediaDevices as any).getDisplayMedia({ + audio: false, + video: { + width: screen.width, + height: screen.height, + frameRate: 60, } }); - - return {captureStream: captureStream, video: video}; + } catch(err) { + console.error("Error: " + err); } - // Filters the keyboard array to ensure only a single entry remains, rather than an entry per language. - private filterKeyboards(): KeyboardMap { - let keyman = window['keyman']; + const video = document.createElement("video"); + video.srcObject = captureStream; + video.play(); - let kbds = keyman['getKeyboards'](); + const result = await new Promise((resolve, reject) => { + video.onloadedmetadata = function () { + resolve(); + } + }); - let keyboardMap = []; + return {captureStream: captureStream, video: video}; + } - for(var i = 0; i < kbds.length; i++) { - let id: string = kbds[i]['InternalName']; - if(!id.match(BatchRenderer.keyboardMatch)) continue; - if(id.match(/^Keyboard_bod/)) continue; // bod keyboards currently don't load on Chrome and break the test run; they need rebuild. - if(keyboardMap[id]) { - continue; - } else { - keyboardMap[id] = kbds[i]; - } - } + // Filters the keyboard array to ensure only a single entry remains, rather than an entry per language. + private filterKeyboards(): KeyboardMap { + let kbds = keyman.getKeyboards(); - return keyboardMap; + let keyboardMap = {}; + + for(var i = 0; i < kbds.length; i++) { + let id: string = kbds[i].InternalName; + if(!id.match(BatchRenderer.keyboardMatch)) continue; + if(id.match(/^Keyboard_bod/)) continue; // bod keyboards currently don't load on Chrome and break the test run; they need rebuild. + if(keyboardMap[id]) { + continue; + } else { + keyboardMap[id] = kbds[i]; + } } - private render(ele: HTMLElement, isMobile?: boolean): Promise { - const capture = async () => { - const result = await new Promise((resolve, reject) => { - // from: https://github.com/kasprownik/electron-screencapture/blob/master/index.js - const canvas = document.createElement('canvas'); - canvas.width = BatchRenderer.boundingRect.width; - canvas.height = BatchRenderer.boundingRect.height; - const context = canvas.getContext('2d'); - // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement - context.drawImage(BatchRenderer.video, - BatchRenderer.boundingRect.left, - BatchRenderer.boundingRect.top, - canvas.width, canvas.height, - 0, 0, canvas.width, canvas.height); - - const frame = canvas.toDataURL("image/png"); - let imgOut = document.createElement('img'); - imgOut.src = frame; - - resolve(imgOut); - }); + return keyboardMap; + } - return result; - } + private render(ele: HTMLElement, isMobile?: boolean): Promise { + const capture = async () => { + const result = await new Promise((resolve, reject) => { + // from: https://github.com/kasprownik/electron-screencapture/blob/master/index.js + const canvas = document.createElement('canvas'); + canvas.width = BatchRenderer.boundingRect.width; + canvas.height = BatchRenderer.boundingRect.height; + const context = canvas.getContext('2d'); + // see: https://developer.mozilla.org/en-US/docs/Web/API/HTMLVideoElement + context.drawImage(BatchRenderer.video, + BatchRenderer.boundingRect.left, + BatchRenderer.boundingRect.top, + canvas.width, canvas.height, + 0, 0, canvas.width, canvas.height); + + const frame = canvas.toDataURL("image/png"); + let imgOut = document.createElement('img'); + imgOut.src = frame; + + resolve(imgOut); + }); - return capture(); + return result; } - createKeyboardHeader(kbd, loaded: boolean): HTMLDivElement { - let divHeader = document.createElement('div'); - let eleName = document.createElement('h2'); + return capture(); + } - eleName.textContent = 'ID: ' + kbd['InternalName']; - divHeader.appendChild(eleName); + createKeyboardHeader(kbd: KeyboardData, loaded: boolean): HTMLDivElement { + let divHeader = document.createElement('div'); + let eleName = document.createElement('h2'); - let eleDescription = document.createElement('p'); + eleName.textContent = 'ID: ' + kbd.InternalName; + divHeader.appendChild(eleName); - if(loaded) { + let eleDescription = document.createElement('p'); - eleDescription.appendChild(document.createTextNode('Name: ' + kbd['Name'])); - eleDescription.appendChild(document.createElement('br')); - eleDescription.appendChild(document.createTextNode('Font: ' + window['keyman'].core.activeKeyboard._legacyLayoutSpec.F)); + if(loaded) { - } else { - eleDescription.appendChild(document.createTextNode('Unable to load this keyboard!')); - } + eleDescription.appendChild(document.createTextNode('Name: ' + kbd.Name)); + eleDescription.appendChild(document.createElement('br')); + eleDescription.appendChild(document.createTextNode('Font: ' + keyman.core.activeKeyboard['_legacyLayoutSpec'].F)); - divHeader.appendChild(eleDescription); - - return divHeader; + } else { + eleDescription.appendChild(document.createTextNode('Unable to load this keyboard!')); } - private processKeyboard(kbd) { - let keyman = window['keyman']; - let p: Promise = keyman.setActiveKeyboard(kbd['InternalName']); - let isMobile = keyman.util.device.formFactor != 'desktop'; + divHeader.appendChild(eleDescription); - // Establish common keyboard header info. - let divSummary = document.createElement('div'); - // Establishes a linkable target for this keyboard's data. - divSummary.id = "summary-" + kbd['InternalName']; + return divHeader; + } - BatchRenderer.divMaster.insertAdjacentElement('afterbegin', divSummary); + private processKeyboard(kbd: KeyboardData) { + let p: Promise = keyman.setActiveKeyboard(kbd.InternalName); + let isMobile = keyman.config.hostDevice.formFactor != 'desktop'; - // A nice, closure-friendly reference for use in our callbacks. - let renderer = this; + // Establish common keyboard header info. + let divSummary = document.createElement('div'); + // Establishes a linkable target for this keyboard's data. + divSummary.id = "summary-" + kbd.InternalName; + BatchRenderer.divMaster.insertAdjacentElement('afterbegin', divSummary); - // Once the keyboard's loaded, we can really get started. - return p.then(function() { - let box: HTMLDivElement = keyman.osk._Box; + // A nice, closure-friendly reference for use in our callbacks. + let renderer = this; - BatchRenderer.boundingRect = box.getBoundingClientRect(); + // Once the keyboard's loaded, we can really get started. + return p.then(function() { + let box: HTMLDivElement = keyman.osk._Box; - // Appromixate handling for non-fullscreen mode runs. - // Adjusts for the browser window's title bars, address bars, etc. - const screenOffsetY = window.outerHeight - window.innerHeight; - BatchRenderer.boundingRect.y += screenOffsetY; + BatchRenderer.boundingRect = box.getBoundingClientRect(); - divSummary.appendChild(renderer.createKeyboardHeader(kbd, true)); + // Appromixate handling for non-fullscreen mode runs. + // Adjusts for the browser window's title bars, address bars, etc. + const screenOffsetY = window.outerHeight - window.innerHeight; + BatchRenderer.boundingRect.y += screenOffsetY; - let divRenders = document.createElement('div'); - divSummary.appendChild(divRenders); + divSummary.appendChild(renderer.createKeyboardHeader(kbd, true)); - // Uses 'private' APIs that may be subject to change in the future. Keep it updated! - var layers; - if(isMobile) { - layers = keyman.osk.vkbd.layerGroup.layers; - } else { - // The desktop OSK will be overpopulated, with a number of blank layers to display in most cases. - // We instead rely upon the KLS definition to ensure we keep the renders sparse. - layers = keyman.core.activeKeyboard._legacyLayoutSpec.KLS; - } + let divRenders = document.createElement('div'); + divSummary.appendChild(divRenders); - let renderLayer = function(i: number) { - return new Promise(function(resolve) { - // (Private API) Directly sets the keyboard layer within KMW, then uses .show to force-display it. - if(keyman.osk.vkbd) { - keyman.core.keyboardProcessor.layerId = Object.keys(layers)[i]; - } else { - console.error("Error - keyman.osk.vkbd is undefined!"); - } - // Make sure the active element's still set! - renderer.setActiveDummy(); - keyman.osk.show(true); - - (document as any).fonts.ready.then(function() { - window.setTimeout(function() { - renderer.render(box, isMobile).then(function(imgEle: HTMLImageElement) { - let eleLayer = document.createElement('div'); - let eleLayerId = document.createElement('p'); - eleLayerId.textContent = 'Layer ID: ' + Object.keys(layers)[i]; - - eleLayer.appendChild(eleLayerId); - eleLayer.appendChild(imgEle); - eleLayer.appendChild(document.createElement('br')); - - divRenders.appendChild(eleLayer); - resolve(i); - }); - }, 100); - }); + // Uses 'private' APIs that may be subject to change in the future. Keep it updated! + var layers; + if(isMobile) { + layers = keyman.osk.vkbd.layerGroup.layers; + } else { + // The desktop OSK will be overpopulated, with a number of blank layers to display in most cases. + // We instead rely upon the KLS definition to ensure we keep the renders sparse. + // + // _legacyLayoutSpec is technically private, but it's what we've been using, so... yeah. + layers = keyman.core.activeKeyboard['_legacyLayoutSpec'].KLS; + } + + let renderLayer = function(i: number) { + return new Promise(function(resolve) { + // (Private API) Directly sets the keyboard layer within KMW, then uses .show to force-display it. + if(keyman.osk.vkbd) { + keyman.core.keyboardProcessor.layerId = Object.keys(layers)[i]; + } else { + console.error("Error - keyman.osk.vkbd is undefined!"); + } + // Make sure the active element's still set! + renderer.setActiveDummy(); + keyman.osk.show(true); + + (document as any).fonts.ready.then(function() { + window.setTimeout(function() { + renderer.render(box, isMobile).then(function(imgEle: HTMLImageElement) { + let eleLayer = document.createElement('div'); + let eleLayerId = document.createElement('p'); + eleLayerId.textContent = 'Layer ID: ' + Object.keys(layers)[i]; + + eleLayer.appendChild(eleLayerId); + eleLayer.appendChild(imgEle); + eleLayer.appendChild(document.createElement('br')); + + divRenders.appendChild(eleLayer); + resolve(i); + }); + }, 100); }); - }; + }); + }; + + // The resulting Promise will only call it's `.then()` once all of this keyboard's renders have been completed. + return renderer.arrayPromiseIteration(renderLayer, Object.keys(layers).length); + }).catch(function() { + console.log("Failed to load the \"" + kbd.InternalName + "\" keyboard for rendering!"); + divSummary.appendChild(renderer.createKeyboardHeader(kbd, false)); + return Promise.resolve(); + }); + } - // The resulting Promise will only call it's `.then()` once all of this keyboard's renders have been completed. - return renderer.arrayPromiseIteration(renderLayer, Object.keys(layers).length); - }).catch(function() { - console.log("Failed to load the \"" + kbd['InternalName'] + "\" keyboard for rendering!"); - divSummary.appendChild(renderer.createKeyboardHeader(kbd, false)); + // Synchronously performs asynchronous operations across a loop, one at a time. + // Necessary due to the nature of KMW OSK rendering. + private arrayPromiseIteration(promiseGenerator: (i: number) => Promise, length: number): Promise { + let iteration = function(index: number): Promise { + if(index < length) { + var promise = promiseGenerator(index); + return promise.then(function(index: number) { + return iteration(++index); + }) + } else { return Promise.resolve(); - }); - } - - // Synchronously performs asynchronous operations across a loop, one at a time. - // Necessary due to the nature of KMW OSK rendering. - private arrayPromiseIteration(promiseGenerator: (i: number) => Promise, length: number): Promise { - let iteration = function(index: number): Promise { - if(index < length) { - var promise = promiseGenerator(index); - return promise.then(function(index: number) { - return iteration(++index); - }) - } else { - return Promise.resolve(); - } } - - return iteration(0); } - fillDeviceNotes() { - let description = document.createElement('p'); - let device = new com.keyman.Device(); - device.detect(); + return iteration(0); + } - description.appendChild(document.createTextNode('Browser: ' + device.browser)); - description.appendChild(document.createElement('br')); - description.appendChild(document.createTextNode('OS: ' + device.OS)); - description.appendChild(document.createElement('br')); - description.appendChild(document.createTextNode('Form factor: ' + device.formFactor)); - description.appendChild(document.createElement('br')); - description.appendChild(document.createTextNode('Touchable: ' + device.touchable)); + fillDeviceNotes() { + let description = document.createElement('p'); + let device = new Device(); + device.detect(); - document.getElementById('deviceNotes').appendChild(description); - } + description.appendChild(document.createTextNode('Browser: ' + device.browser)); + description.appendChild(document.createElement('br')); + description.appendChild(document.createTextNode('OS: ' + device.OS)); + description.appendChild(document.createElement('br')); + description.appendChild(document.createTextNode('Form factor: ' + device.formFactor)); + description.appendChild(document.createElement('br')); + description.appendChild(document.createTextNode('Touchable: ' + device.touchable)); - setActiveDummy() { - window['keyman'].domManager.activeElement = BatchRenderer.dummy; - } + document.getElementById('deviceNotes').appendChild(description); + } - async run(allLayers, filter) { - BatchRenderer.allLayers = allLayers; - BatchRenderer.keyboardMatch = new RegExp('^Keyboard_('+filter+')', 'i'); - if(window['keyman']) { - let keyman = window['keyman']; - - let cc = await this.startCapture(); - BatchRenderer.captureStream = cc.captureStream; - BatchRenderer.video = cc.video; - - // Establish a 'dummy' element to bypass the 'nothing's active' check KMW usually uses. - BatchRenderer.dummy = document.createElement('input'); - window['keyman'].attachToControl(BatchRenderer.dummy); - this.setActiveDummy(); - - BatchRenderer.divMaster = document.getElementById('renderList'); - if(BatchRenderer.divMaster.childElementCount > 0) { - console.log("Prior bulk-renderer run detected. Terminating execution."); - return; - } + setActiveDummy() { + keyman.setActiveElement(BatchRenderer.dummy, true); + } - // We want the renderer to control where the keyboard is displayed. - // Also bypasses another 'fun' OSK complication. - if(keyman.util.device.formFactor == 'desktop') { - keyman.osk.userPositioned = true; - } + async run(allLayers, filter) { + BatchRenderer.allLayers = allLayers; + BatchRenderer.keyboardMatch = new RegExp('^Keyboard_('+filter+')', 'i'); + if(window['keyman']) { + let cc = await this.startCapture(); + BatchRenderer.captureStream = cc.captureStream; + BatchRenderer.video = cc.video; + + // Establish a 'dummy' element to bypass the 'nothing's active' check KMW usually uses. + BatchRenderer.dummy = document.createElement('input'); + keyman.attachToControl(BatchRenderer.dummy); + this.setActiveDummy(); + + BatchRenderer.divMaster = document.getElementById('renderList'); + if(BatchRenderer.divMaster.childElementCount > 0) { + console.log("Prior bulk-renderer run detected. Terminating execution."); + return; + } - // Assumes that the keyboards have been preloaded for us. - let kbds = this.filterKeyboards(); + // We want the renderer to control where the keyboard is displayed. + // Also bypasses another 'fun' OSK complication. + if(keyman.config.hostDevice.formFactor == 'desktop') { + (keyman.osk as FloatingOSKView).userPositioned = true; + } - console.log("Unique keyboard ids detected: " + Object.keys(kbds).length); + // Assumes that the keyboards have been preloaded for us. + let kbds = this.filterKeyboards(); - let renderer = this; + console.log("Unique keyboard ids detected: " + Object.keys(kbds).length); - let keyboardIterator = function(i) { - return new Promise(function(resolve) { - renderer.processKeyboard(kbds[Object.keys(kbds)[i]]).then(function () { - //console.log("Keyboard " + i + " processed!"); - resolve(i); - }); + let renderer = this; + + let keyboardIterator = function(i) { + return new Promise(function(resolve) { + renderer.processKeyboard(kbds[Object.keys(kbds)[i]]).then(function () { + //console.log("Keyboard " + i + " processed!"); + resolve(i); }); - }; + }); + }; - this.arrayPromiseIteration(keyboardIterator, Object.keys(kbds).length).then(function() { - // Once all renders are done, we can now tidy the page up and prep it for final display + potential file-saving. + this.arrayPromiseIteration(keyboardIterator, Object.keys(kbds).length).then(function() { + // Once all renders are done, we can now tidy the page up and prep it for final display + potential file-saving. - // This will go at the top of the page when finished, but not when actively rendering. - // We want to leave as much space visible as possible when actively rendering keyboards - // so that auto-scrolling isn't an issue. - renderer.fillDeviceNotes(); - }); - } else { - console.error("KeymanWeb not detected!"); - } + // This will go at the top of the page when finished, but not when actively rendering. + // We want to leave as much space visible as possible when actively rendering keyboards + // so that auto-scrolling isn't an issue. + renderer.fillDeviceNotes(); + }); + } else { + console.error("KeymanWeb not detected!"); } } +} - (function(){ - window['kmw_renderer'] = new com.keyman.renderer.BatchRenderer(); - })(); -} \ No newline at end of file +(function(){ + window['kmw_renderer'] = new BatchRenderer(); +})(); \ No newline at end of file diff --git a/web/src/tools/testing/bulk_rendering/tsconfig.json b/web/src/tools/testing/bulk_rendering/tsconfig.json index b200d751f079d1a8e6c194fdd240e7db9578e142..12d77f538969b443a381da5389b5feb7760021a3 100644 --- a/web/src/tools/testing/bulk_rendering/tsconfig.json +++ b/web/src/tools/testing/bulk_rendering/tsconfig.json @@ -3,11 +3,18 @@ "compilerOptions": { "allowJs": true, + "module": "es6", + "moduleResolution": "Node16", + "allowSyntheticDefaultImports": true, "inlineSources": true, - "module": "none", - "outFile": "../../../../build/tools/testing/bulk_rendering/bulk_render.js", "sourceMap": true, - "target": "es5" + "sourceRoot": "keyman", + "baseUrl": "./", + "outDir": "../../../../build/tools/testing/bulk_rendering/obj/", + "tsBuildInfoFile": "../../../../build/tools/testing/bulk_rendering/obj/tsconfig.tsbuildinfo", + "target": "es5", + "rootDir": ".", + "composite": true }, "files": [ @@ -15,9 +22,7 @@ ], "references": [ - { "path": "../../../../../common/web/utils", "prepend": true }, - { "path": "../../../../../common/web/keyman-version", "prepend": true }, - { "path": "../../../../../common/web/lm-message-types" }, - { "path": "../../../engine/device-detect", "prepend": true} + { "path": "../../../app/browser" }, + { "path": "../../../engine/device-detect" } ] } diff --git a/web/src/tools/testing/recorder/browserDriver.ts b/web/src/tools/testing/recorder/browserDriver.ts index 55402f5afb6a302d3994406cdb147745018b687e..53b16ab53d98068372120b507de2aa718343afd5 100644 --- a/web/src/tools/testing/recorder/browserDriver.ts +++ b/web/src/tools/testing/recorder/browserDriver.ts @@ -1,93 +1,102 @@ -namespace KMWRecorder { - export class BrowserDriver { - static readonly physicalEventClass: string = "KeyboardEvent"; - static readonly physicalEventType: string = "keydown"; - - static readonly oskEventClass: string = "MouseEvent"; - static readonly oskDownMouseType: string = "mousedown"; - static readonly oskUpMouseType: string = "mouseup"; - static readonly oskDownTouchType: string = "touchstart"; - static readonly oskUpTouchType: string = "touchend"; - - /** The destination element for use in simulation. */ - private target: HTMLElement; - - constructor(target: HTMLElement) { - this.target = target; - } +import { + InputEventSpec, + InputEventSpecSequence, + OSKInputEventSpec, + PhysicalInputEventSpec +} from "@keymanapp/recorder-core"; + +import { type KeymanEngine } from 'keyman/app/browser'; + +declare var keyman: KeymanEngine; + +export class BrowserDriver { + static readonly physicalEventClass: string = "KeyboardEvent"; + static readonly physicalEventType: string = "keydown"; + + static readonly oskEventClass: string = "MouseEvent"; + static readonly oskDownMouseType: string = "mousedown"; + static readonly oskUpMouseType: string = "mouseup"; + static readonly oskDownTouchType: string = "touchstart"; + static readonly oskUpTouchType: string = "touchend"; + + /** The destination element for use in simulation. */ + private target: HTMLElement; + + constructor(target: HTMLElement) { + this.target = target; + } - simulateEvent(eventSpec: InputEventSpec) { - switch(eventSpec.type) { - case "key": - this.simulateHardwareEvent(eventSpec as PhysicalInputEventSpec); - break; - case "osk": - this.simulateOSKEvent(eventSpec as OSKInputEventSpec); - break; - } + simulateEvent(eventSpec: InputEventSpec) { + switch(eventSpec.type) { + case "key": + this.simulateHardwareEvent(eventSpec as PhysicalInputEventSpec); + break; + case "osk": + this.simulateOSKEvent(eventSpec as OSKInputEventSpec); + break; } + } + + simulateHardwareEvent(eventSpec: PhysicalInputEventSpec) { + // Yep, not KeyboardEvent. "keyCode" is nasty-bugged in Chrome and unusable if initializing through KeyboardEvent. + let event = new Event(BrowserDriver.physicalEventType); + event['key'] = eventSpec.key; + event['code'] = eventSpec.code; + event['keyCode'] = eventSpec.keyCode; + event['location'] = eventSpec.location; + event['getModifierState'] = eventSpec.getModifierState.bind(eventSpec); + + this.target.dispatchEvent(event); + } - simulateHardwareEvent(eventSpec: PhysicalInputEventSpec) { - // Yep, not KeyboardEvent. "keyCode" is nasty-bugged in Chrome and unusable if initializing through KeyboardEvent. - let event = new Event(BrowserDriver.physicalEventType); - event['key'] = eventSpec.key; - event['code'] = eventSpec.code; - event['keyCode'] = eventSpec.keyCode; - event['location'] = eventSpec.location; - event['getModifierState'] = eventSpec.getModifierState.bind(eventSpec); + simulateOSKEvent(eventSpec: OSKInputEventSpec) { + let target = this.target; + let oskKeyElement = document.getElementById(eventSpec.keyID); - this.target.dispatchEvent(event); + if(!oskKeyElement) { + console.error('Could not find OSK key "' + eventSpec.keyID + '"!'); + // The following lines will throw an appropriate-enough error. + return; } - simulateOSKEvent(eventSpec: OSKInputEventSpec) { - let target = this.target; - let oskKeyElement = document.getElementById(eventSpec.keyID); - - if(!oskKeyElement) { - console.error('Could not find OSK key "' + eventSpec.keyID + '"!'); - // The following lines will throw an appropriate-enough error. - return; - } - - // To be safe, we replicate the MouseEvent similarly to the keystroke event. - var downEvent; - var upEvent; - if((window['keyman'] as any).util.device.touchable) { - downEvent = new Event(BrowserDriver.oskDownTouchType); - upEvent = new Event(BrowserDriver.oskUpTouchType); - downEvent['touches'] = [{"target": oskKeyElement}]; - upEvent['touches'] = [{"target": oskKeyElement}]; - downEvent['changedTouches'] = [{"target": oskKeyElement}]; - upEvent['changedTouches'] = [{"target": oskKeyElement}]; - } else { - downEvent = new Event(BrowserDriver.oskDownMouseType); - upEvent = new Event(BrowserDriver.oskUpMouseType); - downEvent['relatedTarget'] = target; - upEvent['relatedTarget'] = target; - // Mouse-click driven OSK use involves use of at least one mouse button. - downEvent['button'] = upEvent['button'] = 0; - downEvent['buttons'] = 1; - upEvent['buttons'] = 0; - } - - oskKeyElement.dispatchEvent(downEvent); - oskKeyElement.dispatchEvent(upEvent); + // To be safe, we replicate the MouseEvent similarly to the keystroke event. + var downEvent; + var upEvent; + if(keyman.config.hostDevice.touchable) { + downEvent = new Event(BrowserDriver.oskDownTouchType); + upEvent = new Event(BrowserDriver.oskUpTouchType); + downEvent['touches'] = [{"target": oskKeyElement}]; + upEvent['touches'] = [{"target": oskKeyElement}]; + downEvent['changedTouches'] = [{"target": oskKeyElement}]; + upEvent['changedTouches'] = [{"target": oskKeyElement}]; + } else { + downEvent = new Event(BrowserDriver.oskDownMouseType); + upEvent = new Event(BrowserDriver.oskUpMouseType); + downEvent['relatedTarget'] = target; + upEvent['relatedTarget'] = target; + // Mouse-click driven OSK use involves use of at least one mouse button. + downEvent['button'] = upEvent['button'] = 0; + downEvent['buttons'] = 1; + upEvent['buttons'] = 0; } - // Execution of a test sequence depends on the testing environment; integrated - // testing requires browser-specific code. - simulateSequence(sequence: InputEventSpecSequence): string { - let ele = this.target; + oskKeyElement.dispatchEvent(downEvent); + oskKeyElement.dispatchEvent(upEvent); + } + + // Execution of a test sequence depends on the testing environment; integrated + // testing requires browser-specific code. + simulateSequence(sequence: InputEventSpecSequence): string { + let ele = this.target; - for(var i=0; i < sequence.inputs.length; i++) { - this.simulateEvent(sequence.inputs[i]); - } + for(var i=0; i < sequence.inputs.length; i++) { + this.simulateEvent(sequence.inputs[i]); + } - if(ele instanceof HTMLInputElement || ele instanceof HTMLTextAreaElement) { - return ele.value; - } else { - return ele.textContent; - } + if(ele instanceof HTMLInputElement || ele instanceof HTMLTextAreaElement) { + return ele.value; + } else { + return ele.textContent; } } } \ No newline at end of file diff --git a/web/src/tools/testing/recorder/browserProctor.ts b/web/src/tools/testing/recorder/browserProctor.ts index 74b02449b298cae67ffcd4673884b8389906455e..8a4d4f6b860db11ebbaf77f1fe9c6fbeeab41eea 100644 --- a/web/src/tools/testing/recorder/browserProctor.ts +++ b/web/src/tools/testing/recorder/browserProctor.ts @@ -1,80 +1,97 @@ -/// // In theory, we could totally split Scribe off into its own, separate micro-module. // Only BrowserDriver.ts and its prerequisites are needed during actual integrated unit testing. /// -namespace KMWRecorder { - type AssertCallback = (s1: any, s2: any, msg?: string) => void; +import { type DeviceSpec } from "@keymanapp/web-utils"; - function resetElement(ele: HTMLElement): void { - if(ele instanceof HTMLInputElement || ele instanceof HTMLTextAreaElement) { - (window['keyman'] as any).resetContext(); - ele.value = ""; - } else { - (window['keyman'] as any).resetContext(); - ele.textContent = ""; - } - } +import { type OutputTarget } from "@keymanapp/keyboard-processor"; - /** - * Runs Recorder-generated tests in a browser. - * - * Future notes: further abstraction needed; much of this code will be in common with Node. - */ - export class BrowserProctor extends Proctor { - target: HTMLElement; - usingOSK: boolean; +import { type KeymanEngine } from 'keyman/app/browser'; - constructor(target: HTMLElement, device: com.keyman.utils.DeviceSpec, usingOSK: boolean, assert: AssertCallback) { - super(device, assert); +declare var keyman: KeymanEngine; - this.target = target; - this.usingOSK = usingOSK; - } +import { + InputEventSpec, + InputEventSpecSequence, + KeyboardTest, + Proctor, + RecordedKeystrokeSequence, + TestSequence, + TestSet +} from "@keymanapp/recorder-core"; +import { BrowserDriver } from "./browserDriver.js"; - // Performs browser-specific global test prep. - beforeAll() { - let ele = this.target; - (window['keyman'] as any).setActiveElement(ele['base'] ? ele['base'] : ele); - } - before() { - resetElement(this.target); - } +type AssertCallback = (s1: any, s2: any, msg?: string) => void; - compatibleWithSuite(testSuite: KeyboardTest): boolean { - // So far, the only thing using Recorder code always specifies the necessary data to construct - // DOM events for use in integrated testing. - return true; - } +function resetElement(ele: HTMLElement): void { + if(ele instanceof HTMLInputElement || ele instanceof HTMLTextAreaElement) { + keyman.resetContext(); + ele.value = ""; + } else { + keyman.resetContext(); + ele.textContent = ""; + } +} - // We only need to filter test cases when performing tests in an integrated - // environment. - matchesTestSet(testSet: TestSet) { - return testSet.isValidForDevice(this.device, this.usingOSK); - } +/** + * Runs Recorder-generated tests in a browser. + * + * Future notes: further abstraction needed; much of this code will be in common with Node. + */ +export class BrowserProctor extends Proctor { + target: HTMLElement; + usingOSK: boolean; - // Execution of a test sequence depends on the testing environment; this handles - // the browser-specific aspects. - simulateSequence(sequence: TestSequence, outputTarget?: com.keyman.text.OutputTarget): string { - let driver = new BrowserDriver(this.target); + constructor(target: HTMLElement, device: DeviceSpec, usingOSK: boolean, assert: AssertCallback) { + super(device, assert); - // For the version 10.0 spec - if(sequence instanceof InputEventSpecSequence) { - return driver.simulateSequence(sequence); + this.target = target; + this.usingOSK = usingOSK; + } - // For the version 14.0+ spec - } else if(sequence instanceof RecordedKeystrokeSequence) { - let inputSpecs: InputEventSpec[] = []; + // Performs browser-specific global test prep. + beforeAll() { + let ele = this.target; + (window['keyman'] as any).setActiveElement(ele['base'] ? ele['base'] : ele); + } - for(let i=0; i < sequence.inputs.length; i++) { - inputSpecs[i] = sequence.inputs[i].inputEventSpec; - } + before() { + resetElement(this.target); + } + + compatibleWithSuite(testSuite: KeyboardTest): boolean { + // So far, the only thing using Recorder code always specifies the necessary data to construct + // DOM events for use in integrated testing. + return true; + } + + // We only need to filter test cases when performing tests in an integrated + // environment. + matchesTestSet(testSet: TestSet) { + return testSet.isValidForDevice(this.device, this.usingOSK); + } - // Converts the sequence back to version 10.0, since it's very well made for browser simulation. - let eventSpecSequence = new InputEventSpecSequence(inputSpecs, sequence.output, sequence.msg); - return driver.simulateSequence(eventSpecSequence); + // Execution of a test sequence depends on the testing environment; this handles + // the browser-specific aspects. + simulateSequence(sequence: TestSequence, outputTarget?: OutputTarget): string { + let driver = new BrowserDriver(this.target); + + // For the version 10.0 spec + if(sequence instanceof InputEventSpecSequence) { + return driver.simulateSequence(sequence); + + // For the version 14.0+ spec + } else if(sequence instanceof RecordedKeystrokeSequence) { + let inputSpecs: InputEventSpec[] = []; + + for(let i=0; i < sequence.inputs.length; i++) { + inputSpecs[i] = sequence.inputs[i].inputEventSpec; } + + // Converts the sequence back to version 10.0, since it's very well made for browser simulation. + let eventSpecSequence = new InputEventSpecSequence(inputSpecs, sequence.output, sequence.msg); + return driver.simulateSequence(eventSpecSequence); } } } \ No newline at end of file diff --git a/web/src/tools/testing/recorder/build-bundler.js b/web/src/tools/testing/recorder/build-bundler.js new file mode 100644 index 0000000000000000000000000000000000000000..d74fbecd2bd4b6831dec41aa02af7d42bf5b6e39 --- /dev/null +++ b/web/src/tools/testing/recorder/build-bundler.js @@ -0,0 +1,15 @@ +import esbuild from 'esbuild'; + +await esbuild.build({ + bundle: true, + sourcemap: true, + format: "esm", + nodePaths: ['../../../../../node_modules'], + entryPoints: { + 'index': '../../../../build/tools/testing/recorder/obj/index.js', + }, + outdir: '../../../../build/tools/testing/recorder/lib/', + outExtension: { '.js': '.mjs' }, + tsconfig: './tsconfig.json', + target: "es5" +}); \ No newline at end of file diff --git a/web/src/tools/testing/recorder/build.sh b/web/src/tools/testing/recorder/build.sh index dfe0d600d7e560aad0b5cf81bb8a6c10f8e03dd4..6b5cb18342668eee08e4938148103a1c810ecc61 100755 --- a/web/src/tools/testing/recorder/build.sh +++ b/web/src/tools/testing/recorder/build.sh @@ -1,8 +1,6 @@ #!/usr/bin/env bash # # Compile KeymanWeb's dev & test tool modules -# -set -eu ## START STANDARD BUILD SCRIPT INCLUDE # adjust relative paths as necessary @@ -10,6 +8,8 @@ THIS_SCRIPT="$(readlink -f "${BASH_SOURCE[0]}")" . "${THIS_SCRIPT%/*}/../../../../../resources/build/build-utils.sh" ## END STANDARD BUILD SCRIPT INCLUDE +SUBPROJECT_NAME=tools/testing/recorder +. "$KEYMAN_ROOT/web/common.inc.sh" . "$KEYMAN_ROOT/resources/shellHelperFunctions.sh" # This script runs from its own folder @@ -27,28 +27,10 @@ builder_describe "Builds the Keyman Engine for Web's test-sequence recording too builder_describe_outputs \ configure /node_modules \ - build /web/build/tools/testing/recorder/index.js + build /web/build/$SUBPROJECT_NAME/lib/index.mjs builder_parse "$@" -### CONFIGURE ACTIONS - -if builder_start_action configure; then - verify_npm_setup - builder_finish_action success configure -fi - -### CLEAN ACTIONS - -if builder_start_action clean; then - rm -rf ../../../../build/tools/testing/recorder/ - builder_finish_action success clean -fi - -### BUILD ACTIONS - -if builder_start_action build; then - tsc -b $THIS_SCRIPT_PATH/tsconfig.json - - builder_finish_action success build -fi \ No newline at end of file +builder_run_action configure verify_npm_setup +builder_run_action clean rm -rf ../../../../build/$SUBPROJECT_NAME/ +builder_run_action build compile $SUBPROJECT_NAME \ No newline at end of file diff --git a/web/src/tools/testing/recorder/index.html b/web/src/tools/testing/recorder/index.html index 5f7dfe19efb50da42d7403362c18debc3d63917a..f4a68ba76902102e7d46e1fbd34f4af4c245fd8f 100644 --- a/web/src/tools/testing/recorder/index.html +++ b/web/src/tools/testing/recorder/index.html @@ -46,23 +46,17 @@ } - - - - - + + + +