From 25b2d255aa925c445cb38a4c260ff7e5400fc65c Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Thu, 1 Sep 2022 16:56:50 +1000 Subject: [PATCH 1/2] feat(developer): use json schema to verify source file As we do not appear to have access to a good DTD validation library in Node.js, we will instead convert the DTD to a JSON schema file and use that for validation. I have not verified every aspect of the JSON schema at this point, but will incrementally do so as I implement further aspects of the compiler. This updates all the testing to use the schema. As the schema validation is slow, I have increased the 'normal' time for tests. --- developer/src/kmldmlc/build.sh | 2 + developer/src/kmldmlc/package.json | 7 +- .../kmldmlc/src/keyman/compiler/callbacks.ts | 1 + .../src/kmldmlc/src/keyman/compiler/errors.ts | 4 + .../ldml-keyboard/ldml-keyboard-xml-reader.ts | 24 +- developer/src/kmldmlc/src/kmldmlc.ts | 3 + .../fixtures/invalid-structure-per-dtd.xml | 8 + developer/src/kmldmlc/test/helpers/index.ts | 5 + .../test-ldml-keyboard-xml-reader.ts | 19 + .../src/kmldmlc/test/test-compiler-e2e.ts | 2 + developer/src/kmldmlc/test/test-loca.ts | 2 + developer/src/kmldmlc/test/test-meta.ts | 2 + package-lock.json | 63 +- .../standards-data/ldml-keyboards/readme.md | 9 + .../techpreview/ldml-keyboard.schema.json | 677 ++++++++++++++++++ .../techpreview/ldmlKeyboard.xsd | 304 ++++++++ 16 files changed, 1119 insertions(+), 13 deletions(-) create mode 100644 developer/src/kmldmlc/test/fixtures/invalid-structure-per-dtd.xml create mode 100644 developer/src/kmldmlc/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts create mode 100644 resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json create mode 100644 resources/standards-data/ldml-keyboards/techpreview/ldmlKeyboard.xsd diff --git a/developer/src/kmldmlc/build.sh b/developer/src/kmldmlc/build.sh index 0008bf3a3d..d2191e1139 100755 --- a/developer/src/kmldmlc/build.sh +++ b/developer/src/kmldmlc/build.sh @@ -46,6 +46,8 @@ fi if builder_has_action build; then npm run build + # We need the schema file at runtime and bundled + cp "$KEYMAN_ROOT/resources/standards-data/ldml-keyboards/techpreview/ldml-keyboard.schema.json" "$THIS_SCRIPT_PATH/build/src/" builder_report success build fi diff --git a/developer/src/kmldmlc/package.json b/developer/src/kmldmlc/package.json index e6b0b87cdb..3b66eb82d3 100644 --- a/developer/src/kmldmlc/package.json +++ b/developer/src/kmldmlc/package.json @@ -9,7 +9,7 @@ ], "scripts": { "build": "tsc -b", - "test": "cd test && tsc -b && cd .. && mocha ", + "test": "cd test && tsc -b && cd .. && mocha", "prepublishOnly": "npm run build" }, "author": "Marc Durdin (https://github.com/mcdurdin)", @@ -19,6 +19,7 @@ "kmldmlc": "build/src/kmldmlc.js" }, "dependencies": { + "ajv": "^8.11.0", "commander": "^3.0.0", "crc-32": "^1.2.2", "restructure": "^3.0.0", @@ -37,7 +38,9 @@ "ts-node": "^9.1.1" }, "mocha": { - "spec": "build/test/test-*.js" + "spec": "build/test/**/test-*.js", + "require": ["source-map-support/register"] + }, "repository": { "type": "git", diff --git a/developer/src/kmldmlc/src/keyman/compiler/callbacks.ts b/developer/src/kmldmlc/src/keyman/compiler/callbacks.ts index 2cccb540df..8d32f5e8c4 100644 --- a/developer/src/kmldmlc/src/keyman/compiler/callbacks.ts +++ b/developer/src/kmldmlc/src/keyman/compiler/callbacks.ts @@ -5,5 +5,6 @@ export interface CompilerEvent { export default interface CompilerCallbacks { loadFile(baseFilename: string, filename: string): Buffer; + loadLdmlKeyboardSchema(): Buffer; reportMessage(event: CompilerEvent): void; }; diff --git a/developer/src/kmldmlc/src/keyman/compiler/errors.ts b/developer/src/kmldmlc/src/keyman/compiler/errors.ts index 6d403421f6..79dbb03a65 100644 --- a/developer/src/kmldmlc/src/keyman/compiler/errors.ts +++ b/developer/src/kmldmlc/src/keyman/compiler/errors.ts @@ -37,6 +37,10 @@ export class CompilerErrors { m(this.HINT_OneOrMoreRepeatedLocales, `After minimization, one or more locales is repeated and has been removed`); static HINT_OneOrMoreRepeatedLocales = SevHint | 0x0006; + static InvalidFile = (errorText: string) => + m(this.ERROR_InvalidFile, `The source file has an invalid structure: ${errorText}`); + static ERROR_InvalidFile = SevError | 0x0007; + static severityName(code: number): string { let severity = code & CompilerErrorSeverity.Severity_Mask; switch(severity) { diff --git a/developer/src/kmldmlc/src/keyman/ldml-keyboard/ldml-keyboard-xml-reader.ts b/developer/src/kmldmlc/src/keyman/ldml-keyboard/ldml-keyboard-xml-reader.ts index 31ecdd50a6..07b2a95994 100644 --- a/developer/src/kmldmlc/src/keyman/ldml-keyboard/ldml-keyboard-xml-reader.ts +++ b/developer/src/kmldmlc/src/keyman/ldml-keyboard/ldml-keyboard-xml-reader.ts @@ -1,6 +1,8 @@ import * as xml2js from 'xml2js'; import LDMLKeyboardXMLSourceFile from './ldml-keyboard-xml'; import CompilerCallbacks from '../compiler/callbacks'; +import Ajv from 'ajv'; +import { CompilerErrors } from '../compiler/errors'; export default class LDMLKeyboardXMLSourceFileReader { private readonly callbacks: CompilerCallbacks; @@ -16,7 +18,14 @@ export default class LDMLKeyboardXMLSourceFileReader { */ private boxArrays(source: any) { let box = (o: any, x: string) => { - if(typeof o == 'object' && !Array.isArray(o[x])) o[x] = [o[x]]; + if(typeof o == 'object' && !Array.isArray(o[x])) { + if(o[x] === null || o[x] === undefined) { + o[x] = []; + } + else { + o[x] = [o[x]]; + } + } } box(source?.keyboard, 'layerMaps'); @@ -36,6 +45,16 @@ export default class LDMLKeyboardXMLSourceFileReader { return source; } + public validate(source: LDMLKeyboardXMLSourceFile): LDMLKeyboardXMLSourceFile { + const schema = JSON.parse(this.callbacks.loadLdmlKeyboardSchema().toString('utf8')); + const ajv = new Ajv(); + if(!ajv.validate(schema, source)) { + this.callbacks.reportMessage(CompilerErrors.InvalidFile(ajv.errorsText())); + return null; + } + return source; + } + public loadFile(filename: string) { const buf = this.callbacks.loadFile(filename, filename); return this.load(buf); @@ -47,11 +66,12 @@ export default class LDMLKeyboardXMLSourceFileReader { let parser = new xml2js.Parser({ explicitArray: false, mergeAttrs: true, + emptyTag: {} }); parser.parseString(file, (e: unknown, r: unknown) => { a = r as LDMLKeyboardXMLSourceFile }); return a; })(); - return this.boxArrays(source); + return this.validate(this.boxArrays(source)); } } \ No newline at end of file diff --git a/developer/src/kmldmlc/src/kmldmlc.ts b/developer/src/kmldmlc/src/kmldmlc.ts index a74116c6bc..067291e95d 100644 --- a/developer/src/kmldmlc/src/kmldmlc.ts +++ b/developer/src/kmldmlc/src/kmldmlc.ts @@ -46,6 +46,9 @@ class CompilerCallbacks { reportMessage(event: CompilerEvent): void { console.log(CompilerErrors.severityName(event.code) + ' ' + event.code.toString(16) + ': ' + event.message); } + loadLdmlKeyboardSchema(): Buffer { + return fs.readFileSync(path.join(__dirname, 'ldml-keyboard.schema.json')); + } } function compileKeyboard(inputFilename: string): Uint8Array { diff --git a/developer/src/kmldmlc/test/fixtures/invalid-structure-per-dtd.xml b/developer/src/kmldmlc/test/fixtures/invalid-structure-per-dtd.xml new file mode 100644 index 0000000000..cb2a42e1fc --- /dev/null +++ b/developer/src/kmldmlc/test/fixtures/invalid-structure-per-dtd.xml @@ -0,0 +1,8 @@ + + + + + \ No newline at end of file diff --git a/developer/src/kmldmlc/test/helpers/index.ts b/developer/src/kmldmlc/test/helpers/index.ts index 79b7e88ce2..8d698af76a 100644 --- a/developer/src/kmldmlc/test/helpers/index.ts +++ b/developer/src/kmldmlc/test/helpers/index.ts @@ -29,6 +29,11 @@ export class CompilerCallbacks { reportMessage(event: CompilerEvent): void { this.messages.push(event); } + loadLdmlKeyboardSchema(): Buffer { + // Relative paths phooey! + return fs.readFileSync(path.join(__dirname, '..', '..', '..', '..', '..', '..', + 'resources', 'standards-data', 'ldml-keyboards', 'techpreview', 'ldml-keyboard.schema.json')); + } } export function loadSectionFixture(compilerClass: typeof SectionCompiler, filename: string, callbacks: CompilerCallbacks): Section { diff --git a/developer/src/kmldmlc/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts b/developer/src/kmldmlc/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts new file mode 100644 index 0000000000..3f84cce011 --- /dev/null +++ b/developer/src/kmldmlc/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts @@ -0,0 +1,19 @@ +import 'mocha'; +import {assert} from 'chai'; +import {CompilerCallbacks, makePathToFixture} from '../helpers/index'; +import LDMLKeyboardXMLSourceFileReader from '../../src/keyman/ldml-keyboard/ldml-keyboard-xml-reader'; +import { CompilerErrors } from '../../src/keyman/compiler/errors'; + +describe('ldml keyboard xml reader tests', function() { + this.slow(500); // 0.5 sec -- json schema validation takes a while + + it("should fail to load files that don't conform to DTD", function() { + const inputFilename = makePathToFixture('invalid-structure-per-dtd.xml'); + const callbacks = new CompilerCallbacks(); + let reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + const source = reader.loadFile(inputFilename); + assert.isNull(source); + assert.equal(callbacks.messages.length, 1); + assert.deepEqual(callbacks.messages[0], CompilerErrors.InvalidFile("data/keyboard must have required property 'names'")); + }); +}); \ No newline at end of file diff --git a/developer/src/kmldmlc/test/test-compiler-e2e.ts b/developer/src/kmldmlc/test/test-compiler-e2e.ts index 81280ff667..6f9f12b5f8 100644 --- a/developer/src/kmldmlc/test/test-compiler-e2e.ts +++ b/developer/src/kmldmlc/test/test-compiler-e2e.ts @@ -30,6 +30,8 @@ function compileKeyboard(inputFilename: string): Uint8Array { } describe('compiler-tests', function() { + this.slow(500); // 0.5 sec -- json schema validation takes a while + it('should-build-fixtures', async function() { // Let's build basic.xml // It should match basic.kmx (built from basic.txt) diff --git a/developer/src/kmldmlc/test/test-loca.ts b/developer/src/kmldmlc/test/test-loca.ts index 5bc931a556..93f9e9d63a 100644 --- a/developer/src/kmldmlc/test/test-loca.ts +++ b/developer/src/kmldmlc/test/test-loca.ts @@ -6,6 +6,8 @@ import { Loca } from '../src/keyman/kmx/kmx-plus'; import { CompilerErrors } from '../src/keyman/compiler/errors'; describe('loca', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + it('should compile minimal loca data', function() { const callbacks = new CompilerCallbacks(); let loca = loadSectionFixture(LocaCompiler, 'sections/loca/minimal.xml', callbacks) as Loca; diff --git a/developer/src/kmldmlc/test/test-meta.ts b/developer/src/kmldmlc/test/test-meta.ts index 715d1a17a8..ddfd0aea86 100644 --- a/developer/src/kmldmlc/test/test-meta.ts +++ b/developer/src/kmldmlc/test/test-meta.ts @@ -6,6 +6,8 @@ import { KeyboardSettings, Meta } from '../src/keyman/kmx/kmx-plus'; import { CompilerErrors } from '../src/keyman/compiler/errors'; describe('meta', function () { + this.slow(500); // 0.5 sec -- json schema validation takes a while + it('should compile minimal metadata', function() { const callbacks = new CompilerCallbacks(); let meta = loadSectionFixture(MetaCompiler, 'sections/meta/minimal.xml', callbacks) as Meta; diff --git a/package-lock.json b/package-lock.json index 00d7ed72f7..5ad4edfda9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -473,6 +473,7 @@ "name": "@keymanapp/ldml-keyboard-compiler", "license": "MIT", "dependencies": { + "ajv": "^8.11.0", "commander": "^3.0.0", "crc-32": "^1.2.2", "restructure": "^3.0.0", @@ -506,6 +507,21 @@ "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", "dev": true }, + "developer/src/kmldmlc/node_modules/ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, "developer/src/kmldmlc/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -556,6 +572,11 @@ "node": ">=4" } }, + "developer/src/kmldmlc/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "developer/src/kmldmlc/node_modules/supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -3340,8 +3361,7 @@ "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "optional": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "node_modules/fast-json-stable-stringify": { "version": "2.1.0", @@ -5580,7 +5600,6 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "optional": true, "engines": { "node": ">=6" } @@ -5747,6 +5766,14 @@ "node": ">=0.10.0" } }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -6600,7 +6627,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "optional": true, "dependencies": { "punycode": "^2.1.0" } @@ -7781,6 +7807,7 @@ "@types/mocha": "^5.2.7", "@types/node": "^10.14.6", "@types/xml2js": "^0.4.5", + "ajv": "^8.11.0", "chai": "^4.3.4", "chalk": "^2.4.2", "commander": "^3.0.0", @@ -7804,6 +7831,17 @@ "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", "dev": true }, + "ajv": { + "version": "8.11.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.0.tgz", + "integrity": "sha512-wGgprdCvMalC0BztXvitD2hC04YffAvtsUn93JbGXYLAtCUO4xd17mCCZQxUOItiBwZvJScWo8NIvQMQ71rdpg==", + "requires": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + } + }, "ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", @@ -7845,6 +7883,11 @@ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true }, + "json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -9981,8 +10024,7 @@ "fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "optional": true + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, "fast-json-stable-stringify": { "version": "2.1.0", @@ -11747,8 +11789,7 @@ "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", - "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "optional": true + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, "q": { "version": "1.5.1", @@ -11874,6 +11915,11 @@ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, + "require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" + }, "require-main-filename": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", @@ -12520,7 +12566,6 @@ "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", - "optional": true, "requires": { "punycode": "^2.1.0" } diff --git a/resources/standards-data/ldml-keyboards/readme.md b/resources/standards-data/ldml-keyboards/readme.md index 826f6d5dd5..bcd04b1109 100644 --- a/resources/standards-data/ldml-keyboards/readme.md +++ b/resources/standards-data/ldml-keyboards/readme.md @@ -12,4 +12,13 @@ That will very roughly correspond to + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From 918d261f99fc105993588d013a1ae75ce08bc6c7 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Fri, 2 Sep 2022 08:01:48 +1000 Subject: [PATCH 2/2] feat(developer): add conformsTo validity unit test This is reasonably random, but wanted to add another test for content rather than structure, just to verify that the DTD-XSD-jsonschema translation is doing the right thing according to what we expect. Looking good. --- .../test/fixtures/invalid-conforms-to.xml | 19 +++++++++++++++++++ .../test-ldml-keyboard-xml-reader.ts | 11 +++++++++++ 2 files changed, 30 insertions(+) create mode 100644 developer/src/kmldmlc/test/fixtures/invalid-conforms-to.xml diff --git a/developer/src/kmldmlc/test/fixtures/invalid-conforms-to.xml b/developer/src/kmldmlc/test/fixtures/invalid-conforms-to.xml new file mode 100644 index 0000000000..ba806bdcc5 --- /dev/null +++ b/developer/src/kmldmlc/test/fixtures/invalid-conforms-to.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/developer/src/kmldmlc/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts b/developer/src/kmldmlc/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts index 3f84cce011..b3803c07ac 100644 --- a/developer/src/kmldmlc/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts +++ b/developer/src/kmldmlc/test/ldml-keyboard/test-ldml-keyboard-xml-reader.ts @@ -16,4 +16,15 @@ describe('ldml keyboard xml reader tests', function() { assert.equal(callbacks.messages.length, 1); assert.deepEqual(callbacks.messages[0], CompilerErrors.InvalidFile("data/keyboard must have required property 'names'")); }); + + it("should fail to load files with an invalid conformsTo", function() { + const inputFilename = makePathToFixture('invalid-conforms-to.xml'); + const callbacks = new CompilerCallbacks(); + let reader = new LDMLKeyboardXMLSourceFileReader(callbacks); + const source = reader.loadFile(inputFilename); + assert.isNull(source); + assert.equal(callbacks.messages.length, 1); + assert.deepEqual(callbacks.messages[0], CompilerErrors.InvalidFile("data/keyboard/conformsTo must be equal to one of the allowed values")); + }); + }); \ No newline at end of file