diff --git a/developer/src/kmc-generate/src/abstract-generator.ts b/developer/src/kmc-generate/src/abstract-generator.ts
index e4e6ca34a76..bfdac23737b 100644
--- a/developer/src/kmc-generate/src/abstract-generator.ts
+++ b/developer/src/kmc-generate/src/abstract-generator.ts
@@ -212,5 +212,9 @@ export class AbstractGenerator {
public readonly unitTestEndpoints = {
targetPath: this.targetPath
}
+ /**
+ * @internal
+ */
+ public get test_tokenMap() { return this._tokenMap; }
}
diff --git a/developer/src/kmc-generate/src/basic-generator.ts b/developer/src/kmc-generate/src/basic-generator.ts
index 0cc058b2898..58c3098d417 100644
--- a/developer/src/kmc-generate/src/basic-generator.ts
+++ b/developer/src/kmc-generate/src/basic-generator.ts
@@ -38,8 +38,6 @@ export class BasicGenerator extends AbstractGenerator {
}
protected generate(artifacts: GeneratorArtifacts): boolean {
-
-
return this.transformAll(artifacts);
}
@@ -112,4 +110,20 @@ export class BasicGenerator extends AbstractGenerator {
return true;
}
+
+ /**
+ * @internal
+ * these are exported only for unit tests, do not use
+ */
+ public readonly test_templatePath = () => this.templatePath;
+ public readonly test_languageTags = () => this.languageTags;
+
+ public readonly test_preGenerate = () => this.preGenerate();
+ public readonly test_generate = (artifacts: GeneratorArtifacts) => this.generate(artifacts);
+ public readonly test_getLanguageName = (tag: string) => this.getLanguageName(tag);
+ public readonly test_generateLanguageListForPackage = () => this.generateLanguageListForPackage();
+ public readonly test_getPlatformDotListForReadme = () => this.getPlatformDotListForReadme();
+ public readonly test_transformAll = (artifacts: GeneratorArtifacts) => this.transformAll(artifacts);
+ public readonly test_transform = (sourceFile: string, destFile: string, artifacts: GeneratorArtifacts) =>
+ this.transform(sourceFile, destFile, artifacts);
}
diff --git a/developer/src/kmc-generate/src/main.ts b/developer/src/kmc-generate/src/main.ts
index be2fcdf4a6d..957e50e8d4a 100644
--- a/developer/src/kmc-generate/src/main.ts
+++ b/developer/src/kmc-generate/src/main.ts
@@ -1,5 +1,6 @@
+/* c8 ignore start */
export { AbstractGenerator, GeneratorOptions } from "./abstract-generator.js";
export { KeymanKeyboardGenerator } from "./keyman-keyboard-generator.js";
export { LexicalModelGenerator } from "./lexical-model-generator.js";
export { LdmlKeyboardGenerator } from "./ldml-keyboard-generator.js";
-export { GeneratorMessages } from "./generator-messages.js";
+export { GeneratorMessages } from "./generator-messages.js";
\ No newline at end of file
diff --git a/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/HISTORY.md b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/HISTORY.md
new file mode 100644
index 00000000000..d4aceee4a8c
--- /dev/null
+++ b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/HISTORY.md
@@ -0,0 +1,6 @@
+Sample Project Change History
+====================
+
+1.0 (2024-04-12)
+----------------
+* Created by Sample Author
diff --git a/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/LICENSE.md b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/LICENSE.md
new file mode 100644
index 00000000000..7dfb994497a
--- /dev/null
+++ b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/LICENSE.md
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+© 2024 TheAuthor
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/README.md b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/README.md
new file mode 100644
index 00000000000..8ebc4631ab7
--- /dev/null
+++ b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/README.md
@@ -0,0 +1,18 @@
+Sample Project keyboard
+==============
+
+Description
+-----------
+# A mighty description
+
+Links
+-----
+Keyboard Homepage: https://keyman.com/keyboards/sample
+
+Copyright
+---------
+See [LICENSE.md](LICENSE.md)
+
+Supported Platforms
+-------------------
+ * Windows
diff --git a/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/sample.kpj b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/sample.kpj
new file mode 100644
index 00000000000..7ed1ac72df6
--- /dev/null
+++ b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/sample.kpj
@@ -0,0 +1,8 @@
+
+
+ # A mighty description +
+ +© TheAuthor
+ + + diff --git a/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/source/sample.kmn b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/source/sample.kmn new file mode 100644 index 00000000000..97e1c74de5d --- /dev/null +++ b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/source/sample.kmn @@ -0,0 +1,12 @@ +c keyboard generated from template at 2024-04-12 +c with name "Sample Project" +store(&NAME) 'Sample Project' +store(©RIGHT) '© TheAuthor' +store(&VERSION) '10.0' +store(&KEYBOARDVERSION) '1.0' +store(&TARGETS) 'windows' +store(&VISUALKEYBOARD) 'sample.kvks' + +begin Unicode > use(main) + +group(main) using keys diff --git a/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/source/sample.kps b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/source/sample.kps new file mode 100644 index 00000000000..2d0cfbd7d97 --- /dev/null +++ b/developer/src/kmc-generate/test/fixtures/keyman-keyboard/sample/source/sample.kps @@ -0,0 +1,61 @@ + ++ # A mighty description +
+ ++ # A mighty description +
+ +© TheAuthor
+ + + diff --git a/developer/src/kmc-generate/test/fixtures/ldml-keyboard/sample/source/sample.kps b/developer/src/kmc-generate/test/fixtures/ldml-keyboard/sample/source/sample.kps new file mode 100644 index 00000000000..2d0cfbd7d97 --- /dev/null +++ b/developer/src/kmc-generate/test/fixtures/ldml-keyboard/sample/source/sample.kps @@ -0,0 +1,61 @@ + ++ # A mighty description +
+ ++ # A mighty description +
+ +© TheAuthor
+ + + diff --git a/developer/src/kmc-generate/test/fixtures/lexical-model/sample.en.sample/source/sample.en.sample.model.kps b/developer/src/kmc-generate/test/fixtures/lexical-model/sample.en.sample/source/sample.en.sample.model.kps new file mode 100644 index 00000000000..6d80e0ecd0d --- /dev/null +++ b/developer/src/kmc-generate/test/fixtures/lexical-model/sample.en.sample/source/sample.en.sample.model.kps @@ -0,0 +1,56 @@ + ++ # A mighty description +
+ +© TheAuthor
+ + + \ No newline at end of file diff --git a/developer/src/kmc-generate/test/fixtures/lexical-model/sample.en.sample/source/wordlist.tsv b/developer/src/kmc-generate/test/fixtures/lexical-model/sample.en.sample/source/wordlist.tsv new file mode 100644 index 00000000000..04ce1378ffc --- /dev/null +++ b/developer/src/kmc-generate/test/fixtures/lexical-model/sample.en.sample/source/wordlist.tsv @@ -0,0 +1,16 @@ +# +# Sample Project 1.0 generated from template. +# +# This is an example tab-separated wordlist file that can be edited in a spreadsheet +# program or regenerated from a wordlist tool. See lexical model documentation at +# https://help.keyman.com/developer/ for tools. +# +# Columns +# ======= +# WORD FREQUENCY (Optional)NOTES +# +the 100 +example 5 +hello 10 +world 8 +wordlist 3 \ No newline at end of file diff --git a/developer/src/kmc-generate/test/helpers/index.ts b/developer/src/kmc-generate/test/helpers/index.ts new file mode 100644 index 00000000000..8250dfd101a --- /dev/null +++ b/developer/src/kmc-generate/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-generate/test/shared-options.ts b/developer/src/kmc-generate/test/shared-options.ts new file mode 100644 index 00000000000..ae08ede55f1 --- /dev/null +++ b/developer/src/kmc-generate/test/shared-options.ts @@ -0,0 +1,14 @@ +import { GeneratorOptions } from "../src/abstract-generator.js"; + +export const options: GeneratorOptions = { + icon: false, + id: 'sample', + languageTags: ['en'], + name: 'Sample Project', + outPath: '.', + targets: ['windows'], + version: '1.0', + author: 'Sample Author', + copyright: 'TheAuthor', + description: '# A mighty description', +}; \ No newline at end of file diff --git a/developer/src/kmc-generate/test/test-abstract-generator.ts b/developer/src/kmc-generate/test/test-abstract-generator.ts index 35bee5af602..d1e801ea93c 100644 --- a/developer/src/kmc-generate/test/test-abstract-generator.ts +++ b/developer/src/kmc-generate/test/test-abstract-generator.ts @@ -3,21 +3,9 @@ import * as path from 'path'; import * as fs from 'fs'; import { fileURLToPath } from 'url'; import { assert } from 'chai'; -import { AbstractGenerator, GeneratorArtifacts, GeneratorOptions } from '../src/abstract-generator.js'; +import { AbstractGenerator, GeneratorArtifacts } from '../src/abstract-generator.js'; import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; - -const options: GeneratorOptions = { - icon: false, - id: 'sample', - languageTags: ['en'], - name: 'Sample Project', - outPath: '.', - targets: ['windows'], - version: '1.0', - author: 'Sample Author', - copyright: 'TheAuthor', - description: '# A mighty description', -}; +import { options } from './shared-options.js'; describe('AbstractGenerator', function () { it('should have a valid targetPath', async function() { diff --git a/developer/src/kmc-generate/test/test-basic-generator.ts b/developer/src/kmc-generate/test/test-basic-generator.ts new file mode 100644 index 00000000000..64ddccdda45 --- /dev/null +++ b/developer/src/kmc-generate/test/test-basic-generator.ts @@ -0,0 +1,19 @@ +import 'mocha'; +import { assert } from 'chai'; +import { BasicGenerator } from '../src/basic-generator.js'; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; +import { options } from './shared-options.js'; + +describe('BasicGenerator', function () { + it('should configure default settings', async function() { + const bg = new BasicGenerator(); + const callbacks = new TestCompilerCallbacks(); + assert(await bg.init(callbacks, options)); + bg.test_preGenerate(); + + assert.equal(bg.test_tokenMap['$NAME'], 'Sample Project'); + }); + + // TODO: test transform +}); + diff --git a/developer/src/kmc-generate/test/test-keyman-keyboard-generator.ts b/developer/src/kmc-generate/test/test-keyman-keyboard-generator.ts new file mode 100644 index 00000000000..7f896742bf0 --- /dev/null +++ b/developer/src/kmc-generate/test/test-keyman-keyboard-generator.ts @@ -0,0 +1,57 @@ +import 'mocha'; +import * as fs from 'fs'; +import * as path from 'path'; +import { assert } from 'chai'; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; +import { options } from './shared-options.js'; +import { KeymanKeyboardGenerator } from '../src/keyman-keyboard-generator.js'; +import { makePathToFixture } from './helpers/index.js'; + +// Fixture generated with: +// kmc generate keyman-keyboard -L en -n 'Sample Project' -t windows -v 1.0 -a 'Sample Author' -c 'TheAuthor' --description '# A mighty description' sample + +function getFilenames(p: string, base?: string): string[] { + base = base ?? ''; + const files = fs.readdirSync(p); + let result: string[] = []; + for(const file of files) { + const fp = path.join(p, file); + if(fs.statSync(fp).isDirectory()) { + result = result.concat(getFilenames(fp, base + file + path.sep)); + } else { + result.push(base + file); + } + } + return result; +} + +describe('KeymanKeyboardGenerator', function () { + it('should generate a Keyman keyboard from provided options', async function() { + const generator = new KeymanKeyboardGenerator(); + const callbacks = new TestCompilerCallbacks(); + assert(await generator.init(callbacks, options)); + const result = await generator.run(); + assert.exists(result); + assert.exists(result.artifacts); + + const samplePath = makePathToFixture('keyman-keyboard'); + const files = getFilenames(samplePath); + + // Verify that there are no unexpected artifacts + for(const artifact of Object.keys(result.artifacts)) { + assert.include(files, artifact); + } + + // compare each file content as a UTF-8 string. TODO: this only works until + // we have binary files (icons) + for(const file of files) { + if(!fs.statSync(path.join(samplePath, file)).isDirectory()) { + assert.isDefined(result.artifacts[file]); + assert.equal(result.artifacts[file].filename, file); + const fixtureUTF8 = fs.readFileSync(path.join(samplePath, file), 'utf-8'); + const actualUTF8 = new TextDecoder().decode(result.artifacts[file].data); + assert.equal(actualUTF8, fixtureUTF8, `File ${file} does not have expected content`); + } + } + }); +}); diff --git a/developer/src/kmc-generate/test/test-ldml-keyboard-generator.ts b/developer/src/kmc-generate/test/test-ldml-keyboard-generator.ts new file mode 100644 index 00000000000..40fb4be93eb --- /dev/null +++ b/developer/src/kmc-generate/test/test-ldml-keyboard-generator.ts @@ -0,0 +1,56 @@ +import 'mocha'; +import * as fs from 'fs'; +import * as path from 'path'; +import { assert } from 'chai'; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; +import { options } from './shared-options.js'; +import { LdmlKeyboardGenerator } from '../src/ldml-keyboard-generator.js'; +import { makePathToFixture } from './helpers/index.js'; + +// Fixture generated with: +// kmc generate ldml-keyboard -L en -n 'Sample Project' -v 1.0 -a 'Sample Author' -c 'TheAuthor' --description '# A mighty description' sample + +function getFilenames(p: string, base?: string): string[] { + base = base ?? ''; + const files = fs.readdirSync(p); + let result: string[] = []; + for(const file of files) { + const fp = path.join(p, file); + if(fs.statSync(fp).isDirectory()) { + result = result.concat(getFilenames(fp, base + file + path.sep)); + } else { + result.push(base + file); + } + } + return result; +} + +describe('LdmlKeyboardGenerator', function () { + it('should generate a LDML keyboard from provided options', async function() { + const generator = new LdmlKeyboardGenerator(); + const callbacks = new TestCompilerCallbacks(); + assert(await generator.init(callbacks, options)); + const result = await generator.run(); + assert.exists(result); + assert.exists(result.artifacts); + + const samplePath = makePathToFixture('ldml-keyboard'); + const files = getFilenames(samplePath); + + // Verify that there are no unexpected artifacts + for(const artifact of Object.keys(result.artifacts)) { + assert.include(files, artifact); + } + + // compare each file content as a UTF-8 string + for(const file of files) { + if(!fs.statSync(path.join(samplePath, file)).isDirectory()) { + assert.isDefined(result.artifacts[file]); + assert.equal(result.artifacts[file].filename, file); + const fixtureUTF8 = fs.readFileSync(path.join(samplePath, file), 'utf-8'); + const actualUTF8 = new TextDecoder().decode(result.artifacts[file].data); + assert.equal(actualUTF8, fixtureUTF8, `File ${file} does not have expected content`); + } + } + }); +}); diff --git a/developer/src/kmc-generate/test/test-lexical-model-generator.ts b/developer/src/kmc-generate/test/test-lexical-model-generator.ts new file mode 100644 index 00000000000..95809fa09a7 --- /dev/null +++ b/developer/src/kmc-generate/test/test-lexical-model-generator.ts @@ -0,0 +1,61 @@ +import 'mocha'; +import * as fs from 'fs'; +import * as path from 'path'; +import { assert } from 'chai'; +import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers'; +import { options } from './shared-options.js'; +import { LexicalModelGenerator } from '../src/lexical-model-generator.js'; +import { makePathToFixture } from './helpers/index.js'; +import { GeneratorOptions } from '../src/abstract-generator.js'; + +// Fixture generated with: +// kmc generate lexical-model -L en -n 'Sample Project' -v 1.0 -a 'Sample Author' -c 'TheAuthor' --description '# A mighty description' sample.en.sample + +function getFilenames(p: string, base?: string): string[] { + base = base ?? ''; + const files = fs.readdirSync(p); + let result: string[] = []; + for(const file of files) { + const fp = path.join(p, file); + if(fs.statSync(fp).isDirectory()) { + result = result.concat(getFilenames(fp, base + file + path.sep)); + } else { + result.push(base + file); + } + } + return result; +} + +describe('LexicalModelGenerator', function () { + it('should generate a lexical model from provided options', async function() { + const generator = new LexicalModelGenerator(); + const callbacks = new TestCompilerCallbacks(); + const opts: GeneratorOptions = {...options}; + opts.id = 'sample.en.sample'; + opts.targets = ['any']; + + assert(await generator.init(callbacks, opts)); + const result = await generator.run(); + assert.exists(result); + assert.exists(result.artifacts); + + const samplePath = makePathToFixture('lexical-model'); + const files = getFilenames(samplePath); + + // Verify that there are no unexpected artifacts + for(const artifact of Object.keys(result.artifacts)) { + assert.include(files, artifact); + } + + // compare each file content as a UTF-8 string + for(const file of files) { + if(!fs.statSync(path.join(samplePath, file)).isDirectory()) { + assert.isDefined(result.artifacts[file]); + assert.equal(result.artifacts[file].filename, file); + const fixtureUTF8 = fs.readFileSync(path.join(samplePath, file), 'utf-8'); + const actualUTF8 = new TextDecoder().decode(result.artifacts[file].data); + assert.equal(actualUTF8, fixtureUTF8, `File ${file} does not have expected content`); + } + } + }); +}); diff --git a/developer/src/kmc-generate/test/tsconfig.json b/developer/src/kmc-generate/test/tsconfig.json index f281f40353e..145099305d2 100644 --- a/developer/src/kmc-generate/test/tsconfig.json +++ b/developer/src/kmc-generate/test/tsconfig.json @@ -16,6 +16,7 @@ }, "include": [ "**/test-*.ts", + "./shared-options.ts", "./helpers/index.ts" ], "references": [ diff --git a/developer/src/kmc/src/commands/generate.ts b/developer/src/kmc/src/commands/generate.ts index f6f2fdb35f0..5fe3058d8d9 100644 --- a/developer/src/kmc/src/commands/generate.ts +++ b/developer/src/kmc/src/commands/generate.ts @@ -21,16 +21,16 @@ function declareGenerateKmnKeyboard(command: Command) { subCommand .description('Generate a .kmn keyboard project') .option('-t, --target