mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-28 19:27:44 +00:00
Address a review comment from #15665 and cleanup additional references that I missed the first time around, along with the `fs.readFileSync` `Uint8Array` cast. Follows: #15665 Test-bot: skip
310 lines
13 KiB
TypeScript
310 lines
13 KiB
TypeScript
import 'mocha';
|
|
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import { assert } from 'chai';
|
|
import JSZip from 'jszip';
|
|
|
|
import KEYMAN_VERSION from "@keymanapp/keyman-version";
|
|
import { KmpJsonFile } from '@keymanapp/common-types';
|
|
import { TestCompilerCallbacks } from '@keymanapp/developer-test-helpers';
|
|
|
|
import { makePathToFixture } from './helpers/index.js';
|
|
|
|
import { KmpCompiler } from '../src/compiler/kmp-compiler.js';
|
|
import { PackageCompilerMessages } from '../src/compiler/package-compiler-messages.js';
|
|
import { PackageValidation } from '../src/compiler/package-validation.js';
|
|
|
|
const debug = false;
|
|
|
|
describe('KmpCompiler', function () {
|
|
const MODELS : string[] = [
|
|
'example.qaa.sencoten',
|
|
'withfolders.qaa.sencoten',
|
|
];
|
|
const callbacks = new TestCompilerCallbacks(this);
|
|
let kmpCompiler: KmpCompiler = null;
|
|
|
|
this.beforeAll(async function() {
|
|
kmpCompiler = new KmpCompiler();
|
|
assert.isTrue(await kmpCompiler.init(callbacks, null));
|
|
});
|
|
|
|
for (const modelID of MODELS) {
|
|
const kpsPath = modelID.includes('withfolders') ?
|
|
makePathToFixture(modelID, 'source', `${modelID}.model.kps`) : makePathToFixture(modelID, `${modelID}.model.kps`);
|
|
const jsPath = modelID.includes('withfolders') ?
|
|
makePathToFixture(modelID, 'source', `${modelID}.model.js`) : makePathToFixture(modelID, `${modelID}.model.js`);
|
|
const kmpJsonIntermediatePath = makePathToFixture(modelID, `${modelID}.model.kmp.intermediate.json`);
|
|
const kmpJsonZippedPath = makePathToFixture(modelID, `${modelID}.model.kmp.zipped.json`);
|
|
const kmpJsonIntermediateFixture = JSON.parse(fs.readFileSync(kmpJsonIntermediatePath, 'utf-8'));
|
|
const kmpJsonZippedFixture = JSON.parse(fs.readFileSync(kmpJsonZippedPath, 'utf-8'));
|
|
|
|
// We override the fixture version so that we can compare with the compiler output
|
|
kmpJsonIntermediateFixture.system.keymanDeveloperVersion = KEYMAN_VERSION.VERSION;
|
|
|
|
//
|
|
// Test just the transform from kps to kmp.json
|
|
//
|
|
it(`should transform ${modelID}.model.kps to kmp.json`, function () {
|
|
let kmpJson: KmpJsonFile.KmpJsonFile;
|
|
|
|
assert.doesNotThrow(() => {
|
|
kmpJson = kmpCompiler.transformKpsToKmpObject(kpsPath);
|
|
});
|
|
|
|
// Test that the kmp.json data is identical
|
|
assert.deepEqual(kmpJson, kmpJsonIntermediateFixture);
|
|
|
|
// Note that in-memory kmp.json still contains paths in the files array.
|
|
// However, when building the .kmp, the final written kmp.json data is
|
|
// modified to strip paths.
|
|
|
|
// This was used when building initial test data
|
|
//fs.writeFileSync(kmpJsonPath, JSON.stringify(kmpJson), 'utf-8');
|
|
});
|
|
it(`should build a full .kmp for ${modelID}`, async function() {
|
|
const zip = JSZip();
|
|
// Build kmp.json in memory
|
|
const kmpJson: KmpJsonFile.KmpJsonFile = kmpCompiler.transformKpsToKmpObject(kpsPath);
|
|
// Build file.kmp in memory
|
|
const promise = kmpCompiler.buildKmpFile(kpsPath, kmpJson);
|
|
promise.then(data => {
|
|
// Check that file.kmp contains just 2 files - kmp.json and file.model.js,
|
|
// and that they match exactly what we expect
|
|
return zip.loadAsync(data, {checkCRC32: true}).then(zipFile => {
|
|
assert.equal(zipFile.length, 2);
|
|
return Promise.all([
|
|
zipFile.file("kmp.json").async('uint8array').then(kmpJsonOutput => {
|
|
assert.deepEqual(kmpJsonOutput, kmpJsonZippedFixture);
|
|
}),
|
|
zipFile.file(`${modelID}.model.js`).async('uint8array').then(modelJsFile => {
|
|
assert.deepEqual(modelJsFile, new Uint8Array(fs.readFileSync(jsPath)));
|
|
})
|
|
]);
|
|
});
|
|
});
|
|
|
|
return promise;
|
|
});
|
|
}
|
|
|
|
it('should generates a valid .kmp (zip) file', async function() {
|
|
this.timeout(10000); // building a zip file can sometimes be slow
|
|
|
|
const kpsPath = makePathToFixture('khmer_angkor', 'source', 'khmer_angkor.kps');
|
|
const kmpJsonRefPath = makePathToFixture('khmer_angkor', 'ref', 'kmp.json');
|
|
|
|
const kmpCompiler = new KmpCompiler();
|
|
assert.isTrue(await kmpCompiler.init(callbacks, null));
|
|
|
|
const kmpJsonFixture: KmpJsonFile.KmpJsonFile = JSON.parse(fs.readFileSync(kmpJsonRefPath, 'utf-8'));
|
|
|
|
// We override the fixture version so that we can compare with the compiler output
|
|
kmpJsonFixture.system.keymanDeveloperVersion = KEYMAN_VERSION.VERSION;
|
|
|
|
let kmpJson = null;
|
|
assert.doesNotThrow(() => {
|
|
kmpJson = kmpCompiler.transformKpsToKmpObject(kpsPath);
|
|
});
|
|
|
|
const kmpData = await kmpCompiler.buildKmpFile(kpsPath, kmpJson);
|
|
|
|
const zip = JSZip();
|
|
|
|
const jszip = await zip.loadAsync(kmpData);
|
|
assert.isNotNull(jszip.file('kmp.json')); // kmp.json should be present
|
|
// kmp file should contain the following files
|
|
const expectedFiles = [
|
|
'FONTLOG.txt', 'image002.png', 'KAK_Documentation_EN.pdf', 'KAK_Documentation_KH.pdf',
|
|
'keyboard_layout.png', 'khmer_angkor.js', 'khmer_angkor.kmx', 'khmer_angkor.kvk',
|
|
'khmer_busra_kbd.ttf', 'Mondulkiri-R.ttf', 'OFL.txt', 'OFL-FAQ.txt', 'readme.htm',
|
|
'splash.gif', 'welcome.htm',
|
|
'kmp.json', // standard .kmp metadata file
|
|
'kmp.inf', // legacy keyboard .kmp metdata file
|
|
];
|
|
|
|
assert.sameMembers(Object.entries(jszip.files).map(([s, o]) => o.name).sort(),
|
|
expectedFiles.sort(),
|
|
'khmer_angkor.kmp file should have exactly the expected files');
|
|
|
|
const kmpJsonData = JSON.parse(await jszip.file('kmp.json').async('string'));
|
|
assert.deepEqual(kmpJsonData, kmpJsonFixture);
|
|
});
|
|
|
|
/*
|
|
* Testing kmp.json generation
|
|
*/
|
|
|
|
it(`should transform a .kps file for a keyboard package to a correct kmp.json`, function () {
|
|
const kpsPath = makePathToFixture('kmp.json', 'ahom_star.kps');
|
|
const kmpJsonRefPath = makePathToFixture('kmp.json', 'kmp.json');
|
|
|
|
const kmpJsonActual = kmpCompiler.transformKpsToKmpObject(kpsPath);
|
|
if(kmpJsonActual == null) {
|
|
callbacks.printMessages();
|
|
assert.isNotNull(kmpJsonActual);
|
|
}
|
|
const kmpJsonFixture = JSON.parse(fs.readFileSync(kmpJsonRefPath, 'utf-8'));
|
|
assert.isNotNull(kmpJsonFixture);
|
|
|
|
// Blank out system.keymanDeveloperVersion which will vary
|
|
kmpJsonActual.system.keymanDeveloperVersion = '-';
|
|
kmpJsonFixture.system.keymanDeveloperVersion = '-';
|
|
|
|
// Strip file paths to basename for the actual (this is currently part of the
|
|
// zip phase and not unit testable without refactoring)
|
|
for(const file of kmpJsonActual.files) {
|
|
file.name = path.basename(file.name);
|
|
}
|
|
|
|
assert.deepEqual(kmpJsonActual, kmpJsonFixture);
|
|
});
|
|
|
|
it(`should support .kps 17.0 metadata correctly`, function () {
|
|
const kpsPath = makePathToFixture('kmp_2.0', 'khmer_angkor.kps');
|
|
const kmpJsonRefPath = makePathToFixture('kmp_2.0', 'kmp.json');
|
|
|
|
const kmpJsonActual = kmpCompiler.transformKpsToKmpObject(kpsPath);
|
|
if(kmpJsonActual == null) {
|
|
callbacks.printMessages();
|
|
assert.isNotNull(kmpJsonActual);
|
|
}
|
|
const kmpJsonFixture = JSON.parse(fs.readFileSync(kmpJsonRefPath, 'utf-8'));
|
|
assert.isNotNull(kmpJsonFixture);
|
|
|
|
// Blank out system.keymanDeveloperVersion which will vary
|
|
kmpJsonActual.system.keymanDeveloperVersion = '-';
|
|
kmpJsonFixture.system.keymanDeveloperVersion = '-';
|
|
|
|
// Strip file paths to basename for the actual (this is currently part of the
|
|
// zip phase and not unit testable without refactoring)
|
|
for(const file of kmpJsonActual.files) {
|
|
file.name = path.basename(file.name);
|
|
}
|
|
|
|
assert.deepEqual(kmpJsonActual, kmpJsonFixture);
|
|
});
|
|
/*
|
|
* Testing Warnings and Errors
|
|
*/
|
|
|
|
it('should warn on absolute paths', async function() {
|
|
this.timeout(10000); // building a zip file can sometimes be slow
|
|
|
|
const kpsPath = makePathToFixture('absolute_path', 'source', 'absolute_path.kps');
|
|
const kmpCompiler = new KmpCompiler();
|
|
assert.isTrue(await kmpCompiler.init(callbacks, null));
|
|
|
|
let kmpJson: KmpJsonFile.KmpJsonFile = null;
|
|
|
|
assert.doesNotThrow(() => {
|
|
kmpJson = kmpCompiler.transformKpsToKmpObject(kpsPath);
|
|
});
|
|
|
|
assert.isNotNull(kmpJson);
|
|
|
|
await assert.isNull(kmpCompiler.buildKmpFile(kpsPath, kmpJson));
|
|
|
|
if(debug) callbacks.printMessages();
|
|
|
|
assert.lengthOf(callbacks.messages, 2);
|
|
assert.deepEqual(callbacks.messages[0].code, PackageCompilerMessages.WARN_AbsolutePath);
|
|
assert.deepEqual(callbacks.messages[1].code, PackageCompilerMessages.ERROR_FileDoesNotExist);
|
|
});
|
|
|
|
// Testing path normalization
|
|
|
|
it('should normalize DOS pathnames from \\ to /', async function() {
|
|
// this.timeout(10000); // building a zip file can sometimes be slow
|
|
|
|
const kpsPath = makePathToFixture('normalize_paths', 'source', 'khmer_angkor.kps');
|
|
const kmpCompiler = new KmpCompiler();
|
|
assert.isTrue(await kmpCompiler.init(callbacks, null));
|
|
|
|
let kmpJson: KmpJsonFile.KmpJsonFile = null;
|
|
|
|
assert.doesNotThrow(() => {
|
|
kmpJson = kmpCompiler.transformKpsToKmpObject(kpsPath);
|
|
});
|
|
|
|
callbacks.printMessages();
|
|
assert.lengthOf(callbacks.messages, 0);
|
|
/*
|
|
<ExecuteProgram>exe\myprogram.exe</ExecuteProgram>
|
|
<ReadMeFile>..\source\readme.htm</ReadMeFile>
|
|
<GraphicFile>..\source\splash.gif</GraphicFile>
|
|
<WelcomeFile>..\source\welcome.htm</WelcomeFile>
|
|
<MSIFileName>msi\myprogram.msi</MSIFileName>
|
|
*/
|
|
assert.equal(kmpJson.options.executeProgram, 'exe/myprogram.exe');
|
|
assert.equal(kmpJson.options.readmeFile, '../source/readme.htm');
|
|
assert.equal(kmpJson.options.graphicFile, '../source/splash.gif');
|
|
assert.equal(kmpJson.options.welcomeFile, '../source/welcome.htm');
|
|
assert.equal(kmpJson.options.msiFilename, 'msi/myprogram.msi');
|
|
assert.equal(kmpJson.files[0].name, '../build/khmer_angkor.js');
|
|
|
|
/*
|
|
<OSKFont>..\shared\fonts\khmer\busrakbd\khmer_busra_kbd.ttf</OSKFont>
|
|
<DisplayFont>..\shared\fonts\khmer\mondulkiri\Mondulkiri-R.ttf</DisplayFont>
|
|
*/
|
|
|
|
// These are stripped to basename; note that there may be platform
|
|
// differences
|
|
assert.equal(kmpJson.keyboards[0].oskFont, 'khmer_busra_kbd.ttf');
|
|
assert.equal(kmpJson.keyboards[0].displayFont, 'Mondulkiri-R.ttf');
|
|
});
|
|
|
|
//
|
|
// Test some invalid package metadata
|
|
//
|
|
it(`should load a package with missing keyboard ID metadata`, function () {
|
|
const kmpJson = kmpCompiler.transformKpsToKmpObject(makePathToFixture('invalid', 'missing_keyboard_id.kps'));
|
|
assert.isNull(kmpJson); // with a missing keyboard_id, the package shouldn't load, but it shouldn't crash either
|
|
assert.deepEqual(callbacks.messages[0].code, PackageCompilerMessages.ERROR_MissingKeyboardId);
|
|
});
|
|
|
|
it(`should load a package with missing model ID metadata`, function () {
|
|
const kmpJson = kmpCompiler.transformKpsToKmpObject(makePathToFixture('invalid', 'missing_model_id.kps'));
|
|
assert.isNull(kmpJson); // with a missing model_id, the package shouldn't load, but it shouldn't crash either
|
|
assert.deepEqual(callbacks.messages[0].code, PackageCompilerMessages.ERROR_MissingModelId);
|
|
});
|
|
|
|
it(`should load a package with missing keyboard name metadata`, function () {
|
|
const kmpJson = kmpCompiler.transformKpsToKmpObject(makePathToFixture('invalid', 'missing_keyboard_name.kps'));
|
|
assert.equal(kmpJson.keyboards[0].name, 'version 4'); // picks up example.kmx's name
|
|
});
|
|
|
|
it(`should load a package with missing keyboard version metadata`, function () {
|
|
const kmpJson = kmpCompiler.transformKpsToKmpObject(makePathToFixture('invalid', 'missing_keyboard_version.kps'));
|
|
assert.equal(kmpJson.keyboards[0].version, '4.0'); // picks up example.kmx's version
|
|
});
|
|
|
|
it(`should handle a range of valid BCP47 tags`, function () {
|
|
const inputFilename = makePathToFixture('bcp47', 'valid_bcp47.kps');
|
|
const outputFilename = makePathToFixture('bcp47', 'valid_bcp47.kmp');
|
|
const kmpJson = kmpCompiler.transformKpsToKmpObject(inputFilename);
|
|
assert.isNotNull(kmpJson);
|
|
const validation = new PackageValidation(callbacks, {});
|
|
assert.isTrue(validation.validate(inputFilename, outputFilename, kmpJson));
|
|
});
|
|
|
|
it(`should reject an invalid BCP47 tag`, function () {
|
|
const inputFilename = makePathToFixture('bcp47', 'invalid_bcp47_1.kps');
|
|
const outputFilename = makePathToFixture('bcp47', 'invalid_bcp47_1.kmp');
|
|
const kmpJson = kmpCompiler.transformKpsToKmpObject(inputFilename);
|
|
assert.isNotNull(kmpJson);
|
|
const validation = new PackageValidation(callbacks, {});
|
|
assert.isFalse(validation.validate(inputFilename, outputFilename, kmpJson));
|
|
});
|
|
|
|
it(`should reject a package that contains itself`, function () {
|
|
const inputFilename = makePathToFixture('invalid', 'error_package_must_not_contain_itself.kps');
|
|
const outputFilename = makePathToFixture('invalid', 'error_package_must_not_contain_itself.kmp');
|
|
const kmpJson = kmpCompiler.transformKpsToKmpObject(inputFilename);
|
|
assert.isNotNull(kmpJson);
|
|
const validation = new PackageValidation(callbacks, {});
|
|
assert.isFalse(validation.validate(inputFilename, outputFilename, kmpJson));
|
|
assert.equal(callbacks.messages[0].code, PackageCompilerMessages.ERROR_PackageMustNotContainItself);
|
|
});
|
|
|
|
});
|