diff options
-rw-r--r-- | test/test-all.js | 71 | ||||
-rw-r--r-- | test/test-anki-note-builder.js | 322 | ||||
-rw-r--r-- | test/test-cache-map.js | 137 | ||||
-rw-r--r-- | test/test-core.js | 300 | ||||
-rw-r--r-- | test/test-database.js | 982 | ||||
-rw-r--r-- | test/test-document-util.js | 339 | ||||
-rw-r--r-- | test/test-hotkey-util.js | 189 | ||||
-rw-r--r-- | test/test-japanese-util.js | 915 | ||||
-rw-r--r-- | test/test-json-schema.js | 1048 | ||||
-rw-r--r-- | test/test-manifest.js | 49 | ||||
-rw-r--r-- | test/test-object-property-accessor.js | 458 | ||||
-rw-r--r-- | test/test-profile-conditions-util.js | 1136 | ||||
-rw-r--r-- | test/test-text-source-map.js | 244 | ||||
-rw-r--r-- | test/test-translator.js | 102 | ||||
-rw-r--r-- | test/test-workers.js | 168 |
15 files changed, 0 insertions, 6460 deletions
diff --git a/test/test-all.js b/test/test-all.js deleted file mode 100644 index 9219d278..00000000 --- a/test/test-all.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const fs = require('fs'); -const path = require('path'); -const {spawnSync} = require('child_process'); -const {getArgs} = require('../dev/util'); - - -/** - * @throws {Error} - */ -function main() { - const args = getArgs(process.argv.slice(2), new Map(/** @type {[key: string, value: (boolean|null|number|string|string[])][]} */ ([ - ['skip', []], - [null, []] - ]))); - const directories = /** @type {string[]} */ (args.get(null)); - const skipArg = /** @type {string[]} */ (args.get('skip')); - const skip = new Set([__filename, ...skipArg].map((value) => path.resolve(value))); - - const node = process.execPath; - const fileNamePattern = /\.js$/i; - - let first = true; - for (const directory of directories) { - const fileNames = fs.readdirSync(directory); - for (const fileName of fileNames) { - if (!fileNamePattern.test(fileName)) { continue; } - - const fullFileName = path.resolve(path.join(directory, fileName)); - if (skip.has(fullFileName)) { continue; } - - const stats = fs.lstatSync(fullFileName); - if (!stats.isFile()) { continue; } - - process.stdout.write(`${first ? '' : '\n'}Running ${fileName}...\n`); - first = false; - - const {error, status} = spawnSync(node, [fileName], {cwd: directory, stdio: 'inherit'}); - - if (status !== null && status !== 0) { - process.exit(status); - return; - } - if (error) { - throw error; - } - } - } - - process.exit(0); -} - - -if (require.main === module) { main(); } diff --git a/test/test-anki-note-builder.js b/test/test-anki-note-builder.js deleted file mode 100644 index 8e0ab9d9..00000000 --- a/test/test-anki-note-builder.js +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2021-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); -const {JSDOM} = require('jsdom'); -const {testMain} = require('../dev/util'); -const {TranslatorVM} = require('../dev/translator-vm'); - - -/** - * @template T - * @param {T} value - * @returns {T} - */ -function clone(value) { - return JSON.parse(JSON.stringify(value)); -} - -/** - * @returns {Promise<{vm: TranslatorVM, AnkiNoteBuilder: typeof AnkiNoteBuilder2, JapaneseUtil: typeof JapaneseUtil2}>} - */ -async function createVM() { - const dom = new JSDOM(); - const {Node, NodeFilter, document} = dom.window; - - const vm = new TranslatorVM({ - Node, - NodeFilter, - document, - location: new URL('https://yomichan.test/') - }); - - const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', 'valid-dictionary1'); - await vm.prepare(dictionaryDirectory, 'Test Dictionary 2'); - - vm.execute([ - 'js/data/anki-note-builder.js', - 'js/data/anki-util.js', - 'js/dom/sandbox/css-style-applier.js', - 'js/display/sandbox/pronunciation-generator.js', - 'js/display/sandbox/structured-content-generator.js', - 'js/templates/sandbox/anki-template-renderer.js', - 'js/templates/sandbox/anki-template-renderer-content-manager.js', - 'js/templates/sandbox/template-renderer.js', - 'js/templates/sandbox/template-renderer-media-provider.js', - 'lib/handlebars.min.js' - ]); - - /** @type {[typeof JapaneseUtil, typeof AnkiNoteBuilder, typeof AnkiTemplateRenderer]} */ - const [ - JapaneseUtil2, - AnkiNoteBuilder2, - AnkiTemplateRenderer2 - ] = vm.get([ - 'JapaneseUtil', - 'AnkiNoteBuilder', - 'AnkiTemplateRenderer' - ]); - - class TemplateRendererProxy { - constructor() { - /** @type {?Promise<void>} */ - this._preparePromise = null; - /** @type {AnkiTemplateRenderer} */ - this._ankiTemplateRenderer = new AnkiTemplateRenderer2(); - } - - /** - * @param {string} template - * @param {import('template-renderer').PartialOrCompositeRenderData} data - * @param {import('anki-templates').RenderMode} type - * @returns {Promise<import('template-renderer').RenderResult>} - */ - async render(template, data, type) { - await this._prepare(); - return await this._ankiTemplateRenderer.templateRenderer.render(template, data, type); - } - - /** - * @param {import('template-renderer').RenderMultiItem[]} items - * @returns {Promise<import('core').Response<import('template-renderer').RenderResult>[]>} - */ - async renderMulti(items) { - await this._prepare(); - return await this._ankiTemplateRenderer.templateRenderer.renderMulti(items); - } - - /** - * @returns {Promise<void>} - */ - _prepare() { - if (this._preparePromise === null) { - this._preparePromise = this._prepareInternal(); - } - return this._preparePromise; - } - - /** */ - async _prepareInternal() { - await this._ankiTemplateRenderer.prepare(); - } - } - vm.set({TemplateRendererProxy}); - - return {vm, AnkiNoteBuilder: AnkiNoteBuilder2, JapaneseUtil: JapaneseUtil2}; -} - -/** - * @param {'terms'|'kanji'} type - * @returns {string[]} - */ -function getFieldMarkers(type) { - switch (type) { - case 'terms': - return [ - 'audio', - 'clipboard-image', - 'clipboard-text', - 'cloze-body', - 'cloze-prefix', - 'cloze-suffix', - 'conjugation', - 'dictionary', - 'document-title', - 'expression', - 'frequencies', - 'furigana', - 'furigana-plain', - 'glossary', - 'glossary-brief', - 'glossary-no-dictionary', - 'part-of-speech', - 'pitch-accents', - 'pitch-accent-graphs', - 'pitch-accent-positions', - 'reading', - 'screenshot', - 'search-query', - 'selection-text', - 'sentence', - 'sentence-furigana', - 'tags', - 'url' - ]; - case 'kanji': - return [ - 'character', - 'clipboard-image', - 'clipboard-text', - 'cloze-body', - 'cloze-prefix', - 'cloze-suffix', - 'dictionary', - 'document-title', - 'glossary', - 'kunyomi', - 'onyomi', - 'screenshot', - 'search-query', - 'selection-text', - 'sentence', - 'sentence-furigana', - 'stroke-count', - 'tags', - 'url' - ]; - default: - return []; - } -} - -/** - * @param {import('dictionary').DictionaryEntry[]} dictionaryEntries - * @param {'terms'|'kanji'} type - * @param {import('settings').ResultOutputMode} mode - * @param {string} template - * @param {typeof AnkiNoteBuilder} AnkiNoteBuilder - * @param {typeof JapaneseUtil} JapaneseUtil - * @param {boolean} write - * @returns {Promise<import('anki').NoteFields[]>} - */ -async function getRenderResults(dictionaryEntries, type, mode, template, AnkiNoteBuilder, JapaneseUtil, write) { - const markers = getFieldMarkers(type); - /** @type {import('anki-note-builder').Field[]} */ - const fields = []; - for (const marker of markers) { - fields.push([marker, `{${marker}}`]); - } - - const japaneseUtil = new JapaneseUtil(null); - const clozePrefix = 'cloze-prefix'; - const clozeSuffix = 'cloze-suffix'; - const results = []; - for (const dictionaryEntry of dictionaryEntries) { - let source = ''; - switch (dictionaryEntry.type) { - case 'kanji': - source = dictionaryEntry.character; - break; - case 'term': - if (dictionaryEntry.headwords.length > 0 && dictionaryEntry.headwords[0].sources.length > 0) { - source = dictionaryEntry.headwords[0].sources[0].originalText; - } - break; - } - const ankiNoteBuilder = new AnkiNoteBuilder({japaneseUtil}); - const context = { - url: 'url:', - sentence: { - text: `${clozePrefix}${source}${clozeSuffix}`, - offset: clozePrefix.length - }, - documentTitle: 'title', - query: 'query', - fullQuery: 'fullQuery' - }; - /** @type {import('anki-note-builder').CreateNoteDetails} */ - const details = { - dictionaryEntry, - mode: 'test', - context, - template, - deckName: 'deckName', - modelName: 'modelName', - fields, - tags: ['yomichan'], - checkForDuplicates: true, - duplicateScope: 'collection', - duplicateScopeCheckAllModels: false, - resultOutputMode: mode, - glossaryLayoutMode: 'default', - compactTags: false, - requirements: [], - mediaOptions: null - }; - const {note: {fields: noteFields}, errors} = await ankiNoteBuilder.createNote(details); - if (!write) { - for (const error of errors) { - console.error(error); - } - assert.strictEqual(errors.length, 0); - } - results.push(noteFields); - } - - return results; -} - - -/** */ -async function main() { - const write = (process.argv[2] === '--write'); - - const {vm, AnkiNoteBuilder, JapaneseUtil} = await createVM(); - - const testInputsFilePath = path.join(__dirname, 'data', 'translator-test-inputs.json'); - const {optionsPresets, tests} = JSON.parse(fs.readFileSync(testInputsFilePath, {encoding: 'utf8'})); - - const testResults1FilePath = path.join(__dirname, 'data', 'anki-note-builder-test-results.json'); - const expectedResults1 = JSON.parse(fs.readFileSync(testResults1FilePath, {encoding: 'utf8'})); - const actualResults1 = []; - - const template = fs.readFileSync(path.join(__dirname, '..', 'ext', 'data/templates/default-anki-field-templates.handlebars'), {encoding: 'utf8'}); - - for (let i = 0, ii = tests.length; i < ii; ++i) { - const test = tests[i]; - const expected1 = expectedResults1[i]; - switch (test.func) { - case 'findTerms': - { - const {name, mode, text} = test; - /** @type {import('translation').FindTermsOptions} */ - const options = vm.buildOptions(optionsPresets, test.options); - const {dictionaryEntries} = clone(await vm.translator.findTerms(mode, text, options)); - const results = mode !== 'simple' ? clone(await getRenderResults(dictionaryEntries, 'terms', mode, template, AnkiNoteBuilder, JapaneseUtil, write)) : null; - actualResults1.push({name, results}); - if (!write) { - assert.deepStrictEqual(results, expected1.results); - } - } - break; - case 'findKanji': - { - const {name, text} = test; - /** @type {import('translation').FindKanjiOptions} */ - const options = vm.buildOptions(optionsPresets, test.options); - const dictionaryEntries = clone(await vm.translator.findKanji(text, options)); - const results = clone(await getRenderResults(dictionaryEntries, 'kanji', 'split', template, AnkiNoteBuilder, JapaneseUtil, write)); - actualResults1.push({name, results}); - if (!write) { - assert.deepStrictEqual(results, expected1.results); - } - } - break; - } - } - - if (write) { - // Use 2 indent instead of 4 to save a bit of file size - fs.writeFileSync(testResults1FilePath, JSON.stringify(actualResults1, null, 2), {encoding: 'utf8'}); - } -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-cache-map.js b/test/test-cache-map.js deleted file mode 100644 index 56a31898..00000000 --- a/test/test-cache-map.js +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM({console}); -vm.execute([ - 'js/general/cache-map.js' -]); -/** @type {typeof CacheMap} */ -const CacheMap2 = vm.getSingle('CacheMap'); - - -/** */ -function testConstructor() { - const data = /** @type {[throws: boolean, create: () => void][]} */ ([ - [false, () => new CacheMap2(0)], - [false, () => new CacheMap2(1)], - [false, () => new CacheMap2(Number.MAX_VALUE)], - [true, () => new CacheMap2(-1)], - [true, () => new CacheMap2(1.5)], - [true, () => new CacheMap2(Number.NaN)], - [true, () => new CacheMap2(Number.POSITIVE_INFINITY)], - // @ts-ignore - Ignore because it should throw an error - [true, () => new CacheMap2('a')] - ]); - - for (const [throws, create] of data) { - if (throws) { - assert.throws(create); - } else { - assert.doesNotThrow(create); - } - } -} - -/** */ -function testApi() { - const data = [ - { - maxSize: 1, - expectedSize: 0, - calls: [] - }, - { - maxSize: 10, - expectedSize: 1, - calls: [ - {func: 'get', args: ['a1-b-c'], returnValue: void 0}, - {func: 'has', args: ['a1-b-c'], returnValue: false}, - {func: 'set', args: ['a1-b-c', 32], returnValue: void 0}, - {func: 'get', args: ['a1-b-c'], returnValue: 32}, - {func: 'has', args: ['a1-b-c'], returnValue: true} - ] - }, - { - maxSize: 10, - expectedSize: 2, - calls: [ - {func: 'set', args: ['a1-b-c', 32], returnValue: void 0}, - {func: 'get', args: ['a1-b-c'], returnValue: 32}, - {func: 'set', args: ['a1-b-c', 64], returnValue: void 0}, - {func: 'get', args: ['a1-b-c'], returnValue: 64}, - {func: 'set', args: ['a2-b-c', 96], returnValue: void 0}, - {func: 'get', args: ['a2-b-c'], returnValue: 96} - ] - }, - { - maxSize: 2, - expectedSize: 2, - calls: [ - {func: 'has', args: ['a1-b-c'], returnValue: false}, - {func: 'has', args: ['a2-b-c'], returnValue: false}, - {func: 'has', args: ['a3-b-c'], returnValue: false}, - {func: 'set', args: ['a1-b-c', 1], returnValue: void 0}, - {func: 'has', args: ['a1-b-c'], returnValue: true}, - {func: 'has', args: ['a2-b-c'], returnValue: false}, - {func: 'has', args: ['a3-b-c'], returnValue: false}, - {func: 'set', args: ['a2-b-c', 2], returnValue: void 0}, - {func: 'has', args: ['a1-b-c'], returnValue: true}, - {func: 'has', args: ['a2-b-c'], returnValue: true}, - {func: 'has', args: ['a3-b-c'], returnValue: false}, - {func: 'set', args: ['a3-b-c', 3], returnValue: void 0}, - {func: 'has', args: ['a1-b-c'], returnValue: false}, - {func: 'has', args: ['a2-b-c'], returnValue: true}, - {func: 'has', args: ['a3-b-c'], returnValue: true} - ] - } - ]; - - for (const {maxSize, expectedSize, calls} of data) { - const cache = new CacheMap2(maxSize); - assert.strictEqual(cache.maxSize, maxSize); - for (const call of calls) { - const {func, args} = call; - let returnValue; - switch (func) { - case 'get': returnValue = cache.get(args[0]); break; - case 'set': returnValue = cache.set(args[0], args[1]); break; - case 'has': returnValue = cache.has(args[0]); break; - case 'clear': returnValue = cache.clear(); break; - } - if (Object.prototype.hasOwnProperty.call(call, 'returnValue')) { - const {returnValue: expectedReturnValue} = call; - assert.deepStrictEqual(returnValue, expectedReturnValue); - } - } - assert.strictEqual(cache.size, expectedSize); - } -} - - -/** */ -function main() { - testConstructor(); - testApi(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-core.js b/test/test-core.js deleted file mode 100644 index 2c48ac20..00000000 --- a/test/test-core.js +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM(); -vm.execute([ - 'js/core.js', - 'js/core/extension-error.js' -]); -const values = vm.get(['DynamicProperty', 'deepEqual']); -const DynamicProperty2 = /** @type {typeof DynamicProperty} */ (values[0]); -const deepEqual2 = /** @type {typeof deepEqual} */ (values[1]); - - -/** */ -function testDynamicProperty() { - const data = [ - { - initialValue: 0, - /** @type {{operation: ?string, expectedDefaultValue: number, expectedValue: number, expectedOverrideCount: number, expeectedEventOccurred: boolean, args: [value: number, priority?: number]}[]} */ - operations: [ - { - operation: null, - args: [0], - expectedDefaultValue: 0, - expectedValue: 0, - expectedOverrideCount: 0, - expeectedEventOccurred: false - }, - { - operation: 'set.defaultValue', - args: [1], - expectedDefaultValue: 1, - expectedValue: 1, - expectedOverrideCount: 0, - expeectedEventOccurred: true - }, - { - operation: 'set.defaultValue', - args: [1], - expectedDefaultValue: 1, - expectedValue: 1, - expectedOverrideCount: 0, - expeectedEventOccurred: false - }, - { - operation: 'set.defaultValue', - args: [0], - expectedDefaultValue: 0, - expectedValue: 0, - expectedOverrideCount: 0, - expeectedEventOccurred: true - }, - { - operation: 'setOverride', - args: [8], - expectedDefaultValue: 0, - expectedValue: 8, - expectedOverrideCount: 1, - expeectedEventOccurred: true - }, - { - operation: 'setOverride', - args: [16], - expectedDefaultValue: 0, - expectedValue: 8, - expectedOverrideCount: 2, - expeectedEventOccurred: false - }, - { - operation: 'setOverride', - args: [32, 1], - expectedDefaultValue: 0, - expectedValue: 32, - expectedOverrideCount: 3, - expeectedEventOccurred: true - }, - { - operation: 'setOverride', - args: [64, -1], - expectedDefaultValue: 0, - expectedValue: 32, - expectedOverrideCount: 4, - expeectedEventOccurred: false - }, - { - operation: 'clearOverride', - args: [-4], - expectedDefaultValue: 0, - expectedValue: 32, - expectedOverrideCount: 3, - expeectedEventOccurred: false - }, - { - operation: 'clearOverride', - args: [-3], - expectedDefaultValue: 0, - expectedValue: 32, - expectedOverrideCount: 2, - expeectedEventOccurred: false - }, - { - operation: 'clearOverride', - args: [-2], - expectedDefaultValue: 0, - expectedValue: 64, - expectedOverrideCount: 1, - expeectedEventOccurred: true - }, - { - operation: 'clearOverride', - args: [-1], - expectedDefaultValue: 0, - expectedValue: 0, - expectedOverrideCount: 0, - expeectedEventOccurred: true - } - ] - } - ]; - - for (const {initialValue, operations} of data) { - const property = new DynamicProperty2(initialValue); - const overrideTokens = []; - let eventOccurred = false; - const onChange = () => { eventOccurred = true; }; - property.on('change', onChange); - for (const {operation, args, expectedDefaultValue, expectedValue, expectedOverrideCount, expeectedEventOccurred} of operations) { - eventOccurred = false; - switch (operation) { - case 'set.defaultValue': property.defaultValue = args[0]; break; - case 'setOverride': overrideTokens.push(property.setOverride(...args)); break; - case 'clearOverride': property.clearOverride(overrideTokens[overrideTokens.length + args[0]]); break; - } - assert.strictEqual(eventOccurred, expeectedEventOccurred); - assert.strictEqual(property.defaultValue, expectedDefaultValue); - assert.strictEqual(property.value, expectedValue); - assert.strictEqual(property.overrideCount, expectedOverrideCount); - } - property.off('change', onChange); - } -} - -/** */ -function testDeepEqual() { - const data = [ - // Simple tests - { - value1: 0, - value2: 0, - expected: true - }, - { - value1: null, - value2: null, - expected: true - }, - { - value1: 'test', - value2: 'test', - expected: true - }, - { - value1: true, - value2: true, - expected: true - }, - { - value1: 0, - value2: 1, - expected: false - }, - { - value1: null, - value2: false, - expected: false - }, - { - value1: 'test1', - value2: 'test2', - expected: false - }, - { - value1: true, - value2: false, - expected: false - }, - - // Simple object tests - { - value1: {}, - value2: {}, - expected: true - }, - { - value1: {}, - value2: [], - expected: false - }, - { - value1: [], - value2: [], - expected: true - }, - { - value1: {}, - value2: null, - expected: false - }, - - // Complex object tests - { - value1: [1], - value2: [], - expected: false - }, - { - value1: [1], - value2: [1], - expected: true - }, - { - value1: [1], - value2: [2], - expected: false - }, - - { - value1: {}, - value2: {test: 1}, - expected: false - }, - { - value1: {test: 1}, - value2: {test: 1}, - expected: true - }, - { - value1: {test: 1}, - value2: {test: {test2: false}}, - expected: false - }, - { - value1: {test: {test2: true}}, - value2: {test: {test2: false}}, - expected: false - }, - { - value1: {test: {test2: [true]}}, - value2: {test: {test2: [true]}}, - expected: true - }, - - // Recursive - { - value1: (() => { const x = {}; x.x = x; return x; })(), - value2: (() => { const x = {}; x.x = x; return x; })(), - expected: false - } - ]; - - let index = 0; - for (const {value1, value2, expected} of data) { - const actual1 = deepEqual2(value1, value2); - assert.strictEqual(actual1, expected, `Failed for test ${index}`); - - const actual2 = deepEqual2(value2, value1); - assert.strictEqual(actual2, expected, `Failed for test ${index}`); - - ++index; - } -} - - -/** */ -function main() { - testDynamicProperty(); - testDeepEqual(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-database.js b/test/test-database.js deleted file mode 100644 index 947e369b..00000000 --- a/test/test-database.js +++ /dev/null @@ -1,982 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const path = require('path'); -const assert = require('assert'); -const {createDictionaryArchive, testMain} = require('../dev/util'); -const {DatabaseVM, DatabaseVMDictionaryImporterMediaLoader} = require('../dev/database-vm'); - - -const vm = new DatabaseVM(); -vm.execute([ - 'js/core.js', - 'js/core/extension-error.js', - 'js/general/cache-map.js', - 'js/data/json-schema.js', - 'js/media/media-util.js', - 'js/language/dictionary-importer.js', - 'js/data/database.js', - 'js/language/dictionary-database.js' -]); -/** @type {typeof DictionaryImporter} */ -const DictionaryImporter2 = vm.getSingle('DictionaryImporter'); -/** @type {typeof DictionaryDatabase} */ -const DictionaryDatabase2 = vm.getSingle('DictionaryDatabase'); - - -/** - * @param {string} dictionary - * @param {string} [dictionaryName] - * @returns {import('jszip')} - */ -function createTestDictionaryArchive(dictionary, dictionaryName) { - const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', dictionary); - return createDictionaryArchive(dictionaryDirectory, dictionaryName); -} - - -/** - * @param {import('dictionary-importer').OnProgressCallback} [onProgress] - * @returns {DictionaryImporter} - */ -function createDictionaryImporter(onProgress) { - const dictionaryImporterMediaLoader = new DatabaseVMDictionaryImporterMediaLoader(); - return new DictionaryImporter2(dictionaryImporterMediaLoader, (...args) => { - const {stepIndex, stepCount, index, count} = args[0]; - assert.ok(stepIndex < stepCount); - assert.ok(index <= count); - if (typeof onProgress === 'function') { - onProgress(...args); - } - }); -} - - -/** - * @param {import('dictionary-database').TermEntry[]} dictionaryDatabaseEntries - * @param {string} term - * @returns {number} - */ -function countDictionaryDatabaseEntriesWithTerm(dictionaryDatabaseEntries, term) { - return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.term === term ? 1 : 0)), 0); -} - -/** - * @param {import('dictionary-database').TermEntry[]} dictionaryDatabaseEntries - * @param {string} reading - * @returns {number} - */ -function countDictionaryDatabaseEntriesWithReading(dictionaryDatabaseEntries, reading) { - return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.reading === reading ? 1 : 0)), 0); -} - -/** - * @param {import('dictionary-database').TermMeta[]|import('dictionary-database').KanjiMeta[]} metas - * @param {import('dictionary-database').TermMetaType|import('dictionary-database').KanjiMetaType} mode - * @returns {number} - */ -function countMetasWithMode(metas, mode) { - let i = 0; - for (const item of metas) { - if (item.mode === mode) { ++i; } - } - return i; -} - -/** - * @param {import('dictionary-database').KanjiEntry[]} kanji - * @param {string} character - * @returns {number} - */ -function countKanjiWithCharacter(kanji, character) { - let i = 0; - for (const item of kanji) { - if (item.character === character) { ++i; } - } - return i; -} - - -/** - * @param {number} timeout - * @returns {Promise<void>} - */ -function clearDatabase(timeout) { - return new Promise((resolve, reject) => { - /** @type {?number} */ - let timer = setTimeout(() => { - timer = null; - reject(new Error(`clearDatabase failed to resolve after ${timeout}ms`)); - }, timeout); - - (async () => { - const indexedDB = vm.indexedDB; - for (const {name} of await indexedDB.databases()) { - if (typeof name !== 'string') { continue; } - /** @type {Promise<void>} */ - const promise2 = new Promise((resolve2, reject2) => { - const request = indexedDB.deleteDatabase(name); - request.onerror = (e) => reject2(e); - request.onsuccess = () => resolve2(); - }); - await promise2; - } - if (timer !== null) { - clearTimeout(timer); - } - resolve(); - })(); - }); -} - - -/** */ -async function testDatabase1() { - // Load dictionary data - const testDictionary = createTestDictionaryArchive('valid-dictionary1'); - const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); - const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); - - const title = testDictionaryIndex.title; - const titles = new Map([ - [title, {priority: 0, allowSecondarySearches: false}] - ]); - - // Setup iteration data - const iterations = [ - { - cleanup: async () => { - // Test purge - await dictionaryDatabase.purge(); - await testDatabaseEmpty1(dictionaryDatabase); - } - }, - { - cleanup: async () => { - // Test deleteDictionary - let progressEvent = false; - await dictionaryDatabase.deleteDictionary( - title, - 1000, - () => { - progressEvent = true; - } - ); - assert.ok(progressEvent); - - await testDatabaseEmpty1(dictionaryDatabase); - } - }, - { - cleanup: async () => {} - } - ]; - - // Setup database - const dictionaryDatabase = new DictionaryDatabase2(); - await dictionaryDatabase.prepare(); - - for (const {cleanup} of iterations) { - const expectedSummary = { - title, - revision: 'test', - sequenced: true, - version: 3, - importDate: 0, - prefixWildcardsSupported: true, - counts: { - kanji: {total: 2}, - kanjiMeta: {total: 6, freq: 6}, - media: {total: 4}, - tagMeta: {total: 15}, - termMeta: {total: 38, freq: 31, pitch: 7}, - terms: {total: 21} - } - }; - - // Import data - let progressEvent = false; - const dictionaryImporter = createDictionaryImporter(() => { progressEvent = true; }); - const {result, errors} = await dictionaryImporter.importDictionary( - dictionaryDatabase, - testDictionarySource, - {prefixWildcardsSupported: true} - ); - expectedSummary.importDate = result.importDate; - vm.assert.deepStrictEqual(errors, []); - vm.assert.deepStrictEqual(result, expectedSummary); - assert.ok(progressEvent); - - // Get info summary - const info = await dictionaryDatabase.getDictionaryInfo(); - vm.assert.deepStrictEqual(info, [expectedSummary]); - - // Get counts - const counts = await dictionaryDatabase.getDictionaryCounts( - info.map((v) => v.title), - true - ); - vm.assert.deepStrictEqual(counts, { - counts: [{kanji: 2, kanjiMeta: 6, terms: 21, termMeta: 38, tagMeta: 15, media: 4}], - total: {kanji: 2, kanjiMeta: 6, terms: 21, termMeta: 38, tagMeta: 15, media: 4} - }); - - // Test find* functions - await testFindTermsBulkTest1(dictionaryDatabase, titles); - await testTindTermsExactBulk1(dictionaryDatabase, titles); - await testFindTermsBySequenceBulk1(dictionaryDatabase, title); - await testFindTermMetaBulk1(dictionaryDatabase, titles); - await testFindKanjiBulk1(dictionaryDatabase, titles); - await testFindKanjiMetaBulk1(dictionaryDatabase, titles); - await testFindTagForTitle1(dictionaryDatabase, title); - - // Cleanup - await cleanup(); - } - - await dictionaryDatabase.close(); -} - -/** - * @param {DictionaryDatabase} database - */ -async function testDatabaseEmpty1(database) { - const info = await database.getDictionaryInfo(); - vm.assert.deepStrictEqual(info, []); - - const counts = await database.getDictionaryCounts([], true); - vm.assert.deepStrictEqual(counts, { - counts: [], - total: {kanji: 0, kanjiMeta: 0, terms: 0, termMeta: 0, tagMeta: 0, media: 0} - }); -} - -/** - * @param {DictionaryDatabase} database - * @param {import('dictionary-database').DictionarySet} titles - */ -async function testFindTermsBulkTest1(database, titles) { - /** @type {{inputs: {matchType: import('dictionary-database').MatchType, termList: string[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - matchType: 'exact', - termList: ['打', '打つ', '打ち込む'] - }, - { - matchType: 'exact', - termList: ['だ', 'ダース', 'うつ', 'ぶつ', 'うちこむ', 'ぶちこむ'] - }, - { - matchType: 'prefix', - termList: ['打'] - } - ], - expectedResults: { - total: 10, - terms: [ - ['打', 2], - ['打つ', 4], - ['打ち込む', 4] - ], - readings: [ - ['だ', 1], - ['ダース', 1], - ['うつ', 2], - ['ぶつ', 2], - ['うちこむ', 2], - ['ぶちこむ', 2] - ] - } - }, - { - inputs: [ - { - matchType: 'exact', - termList: ['込む'] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - }, - { - inputs: [ - { - matchType: 'suffix', - termList: ['込む'] - } - ], - expectedResults: { - total: 4, - terms: [ - ['打ち込む', 4] - ], - readings: [ - ['うちこむ', 2], - ['ぶちこむ', 2] - ] - } - }, - { - inputs: [ - { - matchType: 'exact', - termList: [] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {termList, matchType} of inputs) { - const results = await database.findTermsBulk(termList, titles, matchType); - assert.strictEqual(results.length, expectedResults.total); - for (const [term, count] of expectedResults.terms) { - assert.strictEqual(countDictionaryDatabaseEntriesWithTerm(results, term), count); - } - for (const [reading, count] of expectedResults.readings) { - assert.strictEqual(countDictionaryDatabaseEntriesWithReading(results, reading), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {import('dictionary-database').DictionarySet} titles - */ -async function testTindTermsExactBulk1(database, titles) { - /** @type {{inputs: {termList: {term: string, reading: string}[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - termList: [ - {term: '打', reading: 'だ'}, - {term: '打つ', reading: 'うつ'}, - {term: '打ち込む', reading: 'うちこむ'} - ] - } - ], - expectedResults: { - total: 5, - terms: [ - ['打', 1], - ['打つ', 2], - ['打ち込む', 2] - ], - readings: [ - ['だ', 1], - ['うつ', 2], - ['うちこむ', 2] - ] - } - }, - { - inputs: [ - { - termList: [ - {term: '打', reading: 'だ?'}, - {term: '打つ', reading: 'うつ?'}, - {term: '打ち込む', reading: 'うちこむ?'} - ] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - }, - { - inputs: [ - { - termList: [ - {term: '打つ', reading: 'うつ'}, - {term: '打つ', reading: 'ぶつ'} - ] - } - ], - expectedResults: { - total: 4, - terms: [ - ['打つ', 4] - ], - readings: [ - ['うつ', 2], - ['ぶつ', 2] - ] - } - }, - { - inputs: [ - { - termList: [ - {term: '打つ', reading: 'うちこむ'} - ] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - }, - { - inputs: [ - { - termList: [] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {termList} of inputs) { - const results = await database.findTermsExactBulk(termList, titles); - assert.strictEqual(results.length, expectedResults.total); - for (const [term, count] of expectedResults.terms) { - assert.strictEqual(countDictionaryDatabaseEntriesWithTerm(results, term), count); - } - for (const [reading, count] of expectedResults.readings) { - assert.strictEqual(countDictionaryDatabaseEntriesWithReading(results, reading), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {string} mainDictionary - */ -async function testFindTermsBySequenceBulk1(database, mainDictionary) { - /** @type {{inputs: {sequenceList: number[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - sequenceList: [1, 2, 3, 4, 5] - } - ], - expectedResults: { - total: 11, - terms: [ - ['打', 2], - ['打つ', 4], - ['打ち込む', 4], - ['画像', 1] - ], - readings: [ - ['だ', 1], - ['ダース', 1], - ['うつ', 2], - ['ぶつ', 2], - ['うちこむ', 2], - ['ぶちこむ', 2], - ['がぞう', 1] - ] - } - }, - { - inputs: [ - { - sequenceList: [1] - } - ], - expectedResults: { - total: 1, - terms: [ - ['打', 1] - ], - readings: [ - ['だ', 1] - ] - } - }, - { - inputs: [ - { - sequenceList: [2] - } - ], - expectedResults: { - total: 1, - terms: [ - ['打', 1] - ], - readings: [ - ['ダース', 1] - ] - } - }, - { - inputs: [ - { - sequenceList: [3] - } - ], - expectedResults: { - total: 4, - terms: [ - ['打つ', 4] - ], - readings: [ - ['うつ', 2], - ['ぶつ', 2] - ] - } - }, - { - inputs: [ - { - sequenceList: [4] - } - ], - expectedResults: { - total: 4, - terms: [ - ['打ち込む', 4] - ], - readings: [ - ['うちこむ', 2], - ['ぶちこむ', 2] - ] - } - }, - { - inputs: [ - { - sequenceList: [5] - } - ], - expectedResults: { - total: 1, - terms: [ - ['画像', 1] - ], - readings: [ - ['がぞう', 1] - ] - } - }, - { - inputs: [ - { - sequenceList: [-1] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - }, - { - inputs: [ - { - sequenceList: [] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {sequenceList} of inputs) { - const results = await database.findTermsBySequenceBulk(sequenceList.map((query) => ({query, dictionary: mainDictionary}))); - assert.strictEqual(results.length, expectedResults.total); - for (const [term, count] of expectedResults.terms) { - assert.strictEqual(countDictionaryDatabaseEntriesWithTerm(results, term), count); - } - for (const [reading, count] of expectedResults.readings) { - assert.strictEqual(countDictionaryDatabaseEntriesWithReading(results, reading), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {import('dictionary-database').DictionarySet} titles - */ -async function testFindTermMetaBulk1(database, titles) { - /** @type {{inputs: {termList: string[]}[], expectedResults: {total: number, modes: [key: import('dictionary-database').TermMetaType, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - termList: ['打'] - } - ], - expectedResults: { - total: 11, - modes: [ - ['freq', 11] - ] - } - }, - { - inputs: [ - { - termList: ['打つ'] - } - ], - expectedResults: { - total: 10, - modes: [ - ['freq', 10] - ] - } - }, - { - inputs: [ - { - termList: ['打ち込む'] - } - ], - expectedResults: { - total: 12, - modes: [ - ['freq', 10], - ['pitch', 2] - ] - } - }, - { - inputs: [ - { - termList: ['?'] - } - ], - expectedResults: { - total: 0, - modes: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {termList} of inputs) { - const results = await database.findTermMetaBulk(termList, titles); - assert.strictEqual(results.length, expectedResults.total); - for (const [mode, count] of expectedResults.modes) { - assert.strictEqual(countMetasWithMode(results, mode), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {import('dictionary-database').DictionarySet} titles - */ -async function testFindKanjiBulk1(database, titles) { - /** @type {{inputs: {kanjiList: string[]}[], expectedResults: {total: number, kanji: [key: string, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - kanjiList: ['打'] - } - ], - expectedResults: { - total: 1, - kanji: [ - ['打', 1] - ] - } - }, - { - inputs: [ - { - kanjiList: ['込'] - } - ], - expectedResults: { - total: 1, - kanji: [ - ['込', 1] - ] - } - }, - { - inputs: [ - { - kanjiList: ['?'] - } - ], - expectedResults: { - total: 0, - kanji: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {kanjiList} of inputs) { - const results = await database.findKanjiBulk(kanjiList, titles); - assert.strictEqual(results.length, expectedResults.total); - for (const [kanji, count] of expectedResults.kanji) { - assert.strictEqual(countKanjiWithCharacter(results, kanji), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {import('dictionary-database').DictionarySet} titles - */ -async function testFindKanjiMetaBulk1(database, titles) { - /** @type {{inputs: {kanjiList: string[]}[], expectedResults: {total: number, modes: [key: import('dictionary-database').KanjiMetaType, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - kanjiList: ['打'] - } - ], - expectedResults: { - total: 3, - modes: [ - ['freq', 3] - ] - } - }, - { - inputs: [ - { - kanjiList: ['込'] - } - ], - expectedResults: { - total: 3, - modes: [ - ['freq', 3] - ] - } - }, - { - inputs: [ - { - kanjiList: ['?'] - } - ], - expectedResults: { - total: 0, - modes: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {kanjiList} of inputs) { - const results = await database.findKanjiMetaBulk(kanjiList, titles); - assert.strictEqual(results.length, expectedResults.total); - for (const [mode, count] of expectedResults.modes) { - assert.strictEqual(countMetasWithMode(results, mode), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {string} title - */ -async function testFindTagForTitle1(database, title) { - const data = [ - { - inputs: [ - { - name: 'E1' - } - ], - expectedResults: { - value: {category: 'default', dictionary: title, name: 'E1', notes: 'example tag 1', order: 0, score: 0} - } - }, - { - inputs: [ - { - name: 'K1' - } - ], - expectedResults: { - value: {category: 'default', dictionary: title, name: 'K1', notes: 'example kanji tag 1', order: 0, score: 0} - } - }, - { - inputs: [ - { - name: 'kstat1' - } - ], - expectedResults: { - value: {category: 'class', dictionary: title, name: 'kstat1', notes: 'kanji stat 1', order: 0, score: 0} - } - }, - { - inputs: [ - { - name: 'invalid' - } - ], - expectedResults: { - value: null - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {name} of inputs) { - const result = await database.findTagForTitle(name, title); - vm.assert.deepStrictEqual(result, expectedResults.value); - } - } -} - - -/** */ -async function testDatabase2() { - // Load dictionary data - const testDictionary = createTestDictionaryArchive('valid-dictionary1'); - const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); - const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); - - const title = testDictionaryIndex.title; - const titles = new Map([ - [title, {priority: 0, allowSecondarySearches: false}] - ]); - - // Setup database - const dictionaryDatabase = new DictionaryDatabase2(); - /** @type {import('dictionary-importer').ImportDetails} */ - const detaultImportDetails = {prefixWildcardsSupported: false}; - - // Error: not prepared - await assert.rejects(async () => await dictionaryDatabase.deleteDictionary(title, 1000, () => {})); - await assert.rejects(async () => await dictionaryDatabase.findTermsBulk(['?'], titles, 'exact')); - await assert.rejects(async () => await dictionaryDatabase.findTermsExactBulk([{term: '?', reading: '?'}], titles)); - await assert.rejects(async () => await dictionaryDatabase.findTermsBySequenceBulk([{query: 1, dictionary: title}])); - await assert.rejects(async () => await dictionaryDatabase.findTermMetaBulk(['?'], titles)); - await assert.rejects(async () => await dictionaryDatabase.findTermMetaBulk(['?'], titles)); - await assert.rejects(async () => await dictionaryDatabase.findKanjiBulk(['?'], titles)); - await assert.rejects(async () => await dictionaryDatabase.findKanjiMetaBulk(['?'], titles)); - await assert.rejects(async () => await dictionaryDatabase.findTagForTitle('tag', title)); - await assert.rejects(async () => await dictionaryDatabase.getDictionaryInfo()); - await assert.rejects(async () => await dictionaryDatabase.getDictionaryCounts([...titles.keys()], true)); - await assert.rejects(async () => await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)); - - await dictionaryDatabase.prepare(); - - // Error: already prepared - await assert.rejects(async () => await dictionaryDatabase.prepare()); - - await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails); - - // Error: dictionary already imported - await assert.rejects(async () => await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)); - - await dictionaryDatabase.close(); -} - - -/** */ -async function testDatabase3() { - const invalidDictionaries = [ - 'invalid-dictionary1', - 'invalid-dictionary2', - 'invalid-dictionary3', - 'invalid-dictionary4', - 'invalid-dictionary5', - 'invalid-dictionary6' - ]; - - // Setup database - const dictionaryDatabase = new DictionaryDatabase2(); - /** @type {import('dictionary-importer').ImportDetails} */ - const detaultImportDetails = {prefixWildcardsSupported: false}; - await dictionaryDatabase.prepare(); - - for (const invalidDictionary of invalidDictionaries) { - const testDictionary = createTestDictionaryArchive(invalidDictionary); - const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); - - let error = null; - try { - await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails); - } catch (e) { - error = e; - } - - if (error === null) { - assert.ok(false, `Expected an error while importing ${invalidDictionary}`); - } else { - const prefix = 'Dictionary has invalid data'; - const message = /** @type {import('core').UnknownObject} */ (error).message; - assert.ok(typeof message, 'string'); - if (typeof message === 'string') { - assert.ok(message.startsWith(prefix), `Expected error message to start with '${prefix}': ${message}`); - } - } - } - - await dictionaryDatabase.close(); -} - - -/** */ -async function main() { - const clearTimeout = 5000; - try { - await testDatabase1(); - await clearDatabase(clearTimeout); - - await testDatabase2(); - await clearDatabase(clearTimeout); - - await testDatabase3(); - await clearDatabase(clearTimeout); - } catch (e) { - console.log(e); - process.exit(-1); - throw e; - } -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-document-util.js b/test/test-document-util.js deleted file mode 100644 index 93ce1669..00000000 --- a/test/test-document-util.js +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); -const {JSDOM} = require('jsdom'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - - -// DOMRect class definition -class DOMRect { - /** - * @param {number} x - * @param {number} y - * @param {number} width - * @param {number} height - */ - constructor(x, y, width, height) { - /** @type {number} */ - this._x = x; - /** @type {number} */ - this._y = y; - /** @type {number} */ - this._width = width; - /** @type {number} */ - this._height = height; - } - - /** @type {number} */ - get x() { return this._x; } - /** @type {number} */ - get y() { return this._y; } - /** @type {number} */ - get width() { return this._width; } - /** @type {number} */ - get height() { return this._height; } - /** @type {number} */ - get left() { return this._x + Math.min(0, this._width); } - /** @type {number} */ - get right() { return this._x + Math.max(0, this._width); } - /** @type {number} */ - get top() { return this._y + Math.min(0, this._height); } - /** @type {number} */ - get bottom() { return this._y + Math.max(0, this._height); } - /** @returns {string} */ - toJSON() { return '<not implemented>'; } -} - - -/** - * @param {string} fileName - * @returns {JSDOM} - */ -function createJSDOM(fileName) { - const domSource = fs.readFileSync(fileName, {encoding: 'utf8'}); - const dom = new JSDOM(domSource); - const document = dom.window.document; - const window = dom.window; - - // Define innerText setter as an alias for textContent setter - Object.defineProperty(window.HTMLDivElement.prototype, 'innerText', { - set(value) { this.textContent = value; } - }); - - // Placeholder for feature detection - document.caretRangeFromPoint = () => null; - - return dom; -} - -/** - * @param {Element} element - * @param {string|undefined} selector - * @returns {?Element} - */ -function querySelectorChildOrSelf(element, selector) { - return selector ? element.querySelector(selector) : element; -} - -/** - * @param {JSDOM} dom - * @param {?Node} node - * @returns {?Text|Node} - */ -function getChildTextNodeOrSelf(dom, node) { - if (node === null) { return null; } - const Node = dom.window.Node; - const childNode = node.firstChild; - return (childNode !== null && childNode.nodeType === Node.TEXT_NODE ? childNode : node); -} - -/** - * @param {unknown} value - * @returns {unknown} - */ -function getPrototypeOfOrNull(value) { - try { - return Object.getPrototypeOf(value); - } catch (e) { - return null; - } -} - -/** - * @param {Document} document - * @returns {?Element} - */ -function findImposterElement(document) { - // Finds the imposter element based on it's z-index style - return document.querySelector('div[style*="2147483646"]>*'); -} - - -/** */ -async function testDocument1() { - const dom = createJSDOM(path.join(__dirname, 'data', 'html', 'test-document1.html')); - const window = dom.window; - const document = window.document; - const Node = window.Node; - const Range = window.Range; - - const vm = new VM({document, window, Range, Node}); - vm.execute([ - 'js/data/sandbox/string-util.js', - 'js/dom/dom-text-scanner.js', - 'js/dom/text-source-range.js', - 'js/dom/text-source-element.js', - 'js/dom/document-util.js' - ]); - /** @type {[DOMTextScanner: typeof DOMTextScanner, TextSourceRange: typeof TextSourceRange, TextSourceElement: typeof TextSourceElement, DocumentUtil: typeof DocumentUtil]} */ - const [DOMTextScanner2, TextSourceRange2, TextSourceElement2, DocumentUtil2] = vm.get([ - 'DOMTextScanner', - 'TextSourceRange', - 'TextSourceElement', - 'DocumentUtil' - ]); - - try { - await testDocumentTextScanningFunctions(dom, {DocumentUtil: DocumentUtil2, TextSourceRange: TextSourceRange2, TextSourceElement: TextSourceElement2}); - await testTextSourceRangeSeekFunctions(dom, {DOMTextScanner: DOMTextScanner2}); - } finally { - window.close(); - } -} - -/** - * @param {JSDOM} dom - * @param {{DocumentUtil: typeof DocumentUtil, TextSourceRange: typeof TextSourceRange, TextSourceElement: typeof TextSourceElement}} details - */ -async function testDocumentTextScanningFunctions(dom, {DocumentUtil, TextSourceRange, TextSourceElement}) { - const document = dom.window.document; - - for (const testElement of /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.test[data-test-type=scan]'))) { - // Get test parameters - const { - elementFromPointSelector, - caretRangeFromPointSelector, - startNodeSelector, - startOffset, - endNodeSelector, - endOffset, - resultType, - sentenceScanExtent, - sentence, - hasImposter, - terminateAtNewlines - } = testElement.dataset; - - const elementFromPointValue = querySelectorChildOrSelf(testElement, elementFromPointSelector); - const caretRangeFromPointValue = querySelectorChildOrSelf(testElement, caretRangeFromPointSelector); - const startNode = getChildTextNodeOrSelf(dom, querySelectorChildOrSelf(testElement, startNodeSelector)); - const endNode = getChildTextNodeOrSelf(dom, querySelectorChildOrSelf(testElement, endNodeSelector)); - - const startOffset2 = parseInt(/** @type {string} */ (startOffset), 10); - const endOffset2 = parseInt(/** @type {string} */ (endOffset), 10); - const sentenceScanExtent2 = parseInt(/** @type {string} */ (sentenceScanExtent), 10); - const terminateAtNewlines2 = (terminateAtNewlines !== 'false'); - - assert.notStrictEqual(elementFromPointValue, null); - assert.notStrictEqual(caretRangeFromPointValue, null); - assert.notStrictEqual(startNode, null); - assert.notStrictEqual(endNode, null); - - // Setup functions - document.elementFromPoint = () => elementFromPointValue; - - document.caretRangeFromPoint = (x, y) => { - const imposter = getChildTextNodeOrSelf(dom, findImposterElement(document)); - assert.strictEqual(!!imposter, hasImposter === 'true'); - - const range = document.createRange(); - range.setStart(/** @type {Node} */ (imposter ? imposter : startNode), startOffset2); - range.setEnd(/** @type {Node} */ (imposter ? imposter : startNode), endOffset2); - - // Override getClientRects to return a rect guaranteed to contain (x, y) - range.getClientRects = () => { - /** @type {import('test/document-types').PseudoDOMRectList} */ - const domRectList = Object.assign( - [new DOMRect(x - 1, y - 1, 2, 2)], - { - /** - * @this {DOMRect[]} - * @param {number} index - * @returns {DOMRect} - */ - item: function item(index) { return this[index]; } - } - ); - return domRectList; - }; - return range; - }; - - // Test docRangeFromPoint - const source = DocumentUtil.getRangeFromPoint(0, 0, { - deepContentScan: false, - normalizeCssZoom: true - }); - switch (resultType) { - case 'TextSourceRange': - assert.strictEqual(getPrototypeOfOrNull(source), TextSourceRange.prototype); - break; - case 'TextSourceElement': - assert.strictEqual(getPrototypeOfOrNull(source), TextSourceElement.prototype); - break; - case 'null': - assert.strictEqual(source, null); - break; - default: - assert.ok(false); - break; - } - if (source === null) { continue; } - - // Sentence info - const terminatorString = '…。..??!!'; - const terminatorMap = new Map(); - for (const char of terminatorString) { - terminatorMap.set(char, [false, true]); - } - const quoteArray = [['「', '」'], ['『', '』'], ['\'', '\''], ['"', '"']]; - const forwardQuoteMap = new Map(); - const backwardQuoteMap = new Map(); - for (const [char1, char2] of quoteArray) { - forwardQuoteMap.set(char1, [char2, false]); - backwardQuoteMap.set(char2, [char1, false]); - } - - // Test docSentenceExtract - const sentenceActual = DocumentUtil.extractSentence( - source, - false, - sentenceScanExtent2, - terminateAtNewlines2, - terminatorMap, - forwardQuoteMap, - backwardQuoteMap - ).text; - assert.strictEqual(sentenceActual, sentence); - - // Clean - source.cleanup(); - } -} - -/** - * @param {JSDOM} dom - * @param {{DOMTextScanner: typeof DOMTextScanner}} details - */ -async function testTextSourceRangeSeekFunctions(dom, {DOMTextScanner}) { - const document = dom.window.document; - - for (const testElement of /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.test[data-test-type=text-source-range-seek]'))) { - // Get test parameters - const { - seekNodeSelector, - seekNodeIsText, - seekOffset, - seekLength, - seekDirection, - expectedResultNodeSelector, - expectedResultNodeIsText, - expectedResultOffset, - expectedResultContent - } = testElement.dataset; - - const seekOffset2 = parseInt(/** @type {string} */ (seekOffset), 10); - const seekLength2 = parseInt(/** @type {string} */ (seekLength), 10); - const expectedResultOffset2 = parseInt(/** @type {string} */ (expectedResultOffset), 10); - - /** @type {?Node} */ - let seekNode = testElement.querySelector(/** @type {string} */ (seekNodeSelector)); - if (seekNodeIsText === 'true' && seekNode !== null) { - seekNode = seekNode.firstChild; - } - - /** @type {?Node} */ - let expectedResultNode = testElement.querySelector(/** @type {string} */ (expectedResultNodeSelector)); - if (expectedResultNodeIsText === 'true' && expectedResultNode !== null) { - expectedResultNode = expectedResultNode.firstChild; - } - - const {node, offset, content} = ( - seekDirection === 'forward' ? - new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset2, true, false).seek(seekLength2) : - new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset2, true, false).seek(-seekLength2) - ); - - assert.strictEqual(node, expectedResultNode); - assert.strictEqual(offset, expectedResultOffset2); - assert.strictEqual(content, expectedResultContent); - } -} - - -/** */ -async function main() { - await testDocument1(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-hotkey-util.js b/test/test-hotkey-util.js deleted file mode 100644 index 88f5a8d2..00000000 --- a/test/test-hotkey-util.js +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - - -/** - * @template T - * @param {T} value - * @returns {T} - */ -function clone(value) { - return JSON.parse(JSON.stringify(value)); -} - -/** - * @returns {HotkeyUtil} - */ -function createHotkeyUtil() { - const vm = new VM(); - vm.execute(['js/input/hotkey-util.js']); - /** @type {typeof HotkeyUtil} */ - const HotkeyUtil2 = vm.getSingle('HotkeyUtil'); - return new HotkeyUtil2(); -} - - -/** */ -function testCommandConversions() { - /** @type {{os: import('environment').OperatingSystem, command: string, expectedCommand: string, expectedInput: {key: string, modifiers: import('input').Modifier[]}}[]} */ - const data = [ - {os: 'win', command: 'Alt+F', expectedCommand: 'Alt+F', expectedInput: {key: 'KeyF', modifiers: ['alt']}}, - {os: 'win', command: 'F1', expectedCommand: 'F1', expectedInput: {key: 'F1', modifiers: []}}, - - {os: 'win', command: 'Ctrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, - {os: 'win', command: 'MacCtrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, - {os: 'win', command: 'Command+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}}, - - {os: 'mac', command: 'Ctrl+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}}, - {os: 'mac', command: 'MacCtrl+Alt+Shift+F1', expectedCommand: 'MacCtrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, - {os: 'mac', command: 'Command+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}}, - - {os: 'linux', command: 'Ctrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, - {os: 'linux', command: 'MacCtrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, - {os: 'linux', command: 'Command+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}} - ]; - - const hotkeyUtil = createHotkeyUtil(); - for (const {command, os, expectedInput, expectedCommand} of data) { - hotkeyUtil.os = os; - const input = clone(hotkeyUtil.convertCommandToInput(command)); - assert.deepStrictEqual(input, expectedInput); - const command2 = hotkeyUtil.convertInputToCommand(input.key, input.modifiers); - assert.deepStrictEqual(command2, expectedCommand); - } -} - -/** */ -function testDisplayNames() { - /** @type {{os: import('environment').OperatingSystem, key: ?string, modifiers: import('input').Modifier[], expected: string}[]} */ - const data = [ - {os: 'win', key: null, modifiers: [], expected: ''}, - {os: 'win', key: 'KeyF', modifiers: [], expected: 'F'}, - {os: 'win', key: 'F1', modifiers: [], expected: 'F1'}, - {os: 'win', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, - {os: 'win', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, - {os: 'win', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, - {os: 'win', key: null, modifiers: ['alt'], expected: 'Alt'}, - {os: 'win', key: 'KeyF', modifiers: ['alt'], expected: 'Alt + F'}, - {os: 'win', key: 'F1', modifiers: ['alt'], expected: 'Alt + F1'}, - {os: 'win', key: null, modifiers: ['shift'], expected: 'Shift'}, - {os: 'win', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, - {os: 'win', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, - {os: 'win', key: null, modifiers: ['meta'], expected: 'Windows'}, - {os: 'win', key: 'KeyF', modifiers: ['meta'], expected: 'Windows + F'}, - {os: 'win', key: 'F1', modifiers: ['meta'], expected: 'Windows + F1'}, - {os: 'win', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, - {os: 'win', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, - {os: 'win', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'}, - - {os: 'mac', key: null, modifiers: [], expected: ''}, - {os: 'mac', key: 'KeyF', modifiers: [], expected: 'F'}, - {os: 'mac', key: 'F1', modifiers: [], expected: 'F1'}, - {os: 'mac', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, - {os: 'mac', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, - {os: 'mac', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, - {os: 'mac', key: null, modifiers: ['alt'], expected: 'Opt'}, - {os: 'mac', key: 'KeyF', modifiers: ['alt'], expected: 'Opt + F'}, - {os: 'mac', key: 'F1', modifiers: ['alt'], expected: 'Opt + F1'}, - {os: 'mac', key: null, modifiers: ['shift'], expected: 'Shift'}, - {os: 'mac', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, - {os: 'mac', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, - {os: 'mac', key: null, modifiers: ['meta'], expected: 'Cmd'}, - {os: 'mac', key: 'KeyF', modifiers: ['meta'], expected: 'Cmd + F'}, - {os: 'mac', key: 'F1', modifiers: ['meta'], expected: 'Cmd + F1'}, - {os: 'mac', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, - {os: 'mac', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, - {os: 'mac', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'}, - - {os: 'linux', key: null, modifiers: [], expected: ''}, - {os: 'linux', key: 'KeyF', modifiers: [], expected: 'F'}, - {os: 'linux', key: 'F1', modifiers: [], expected: 'F1'}, - {os: 'linux', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, - {os: 'linux', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, - {os: 'linux', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, - {os: 'linux', key: null, modifiers: ['alt'], expected: 'Alt'}, - {os: 'linux', key: 'KeyF', modifiers: ['alt'], expected: 'Alt + F'}, - {os: 'linux', key: 'F1', modifiers: ['alt'], expected: 'Alt + F1'}, - {os: 'linux', key: null, modifiers: ['shift'], expected: 'Shift'}, - {os: 'linux', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, - {os: 'linux', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, - {os: 'linux', key: null, modifiers: ['meta'], expected: 'Super'}, - {os: 'linux', key: 'KeyF', modifiers: ['meta'], expected: 'Super + F'}, - {os: 'linux', key: 'F1', modifiers: ['meta'], expected: 'Super + F1'}, - {os: 'linux', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, - {os: 'linux', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, - {os: 'linux', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'}, - - {os: 'unknown', key: null, modifiers: [], expected: ''}, - {os: 'unknown', key: 'KeyF', modifiers: [], expected: 'F'}, - {os: 'unknown', key: 'F1', modifiers: [], expected: 'F1'}, - {os: 'unknown', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, - {os: 'unknown', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, - {os: 'unknown', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, - {os: 'unknown', key: null, modifiers: ['alt'], expected: 'Alt'}, - {os: 'unknown', key: 'KeyF', modifiers: ['alt'], expected: 'Alt + F'}, - {os: 'unknown', key: 'F1', modifiers: ['alt'], expected: 'Alt + F1'}, - {os: 'unknown', key: null, modifiers: ['shift'], expected: 'Shift'}, - {os: 'unknown', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, - {os: 'unknown', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, - {os: 'unknown', key: null, modifiers: ['meta'], expected: 'Meta'}, - {os: 'unknown', key: 'KeyF', modifiers: ['meta'], expected: 'Meta + F'}, - {os: 'unknown', key: 'F1', modifiers: ['meta'], expected: 'Meta + F1'}, - {os: 'unknown', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, - {os: 'unknown', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, - {os: 'unknown', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'} - ]; - - const hotkeyUtil = createHotkeyUtil(); - for (const {os, key, modifiers, expected} of data) { - hotkeyUtil.os = os; - const displayName = hotkeyUtil.getInputDisplayValue(key, modifiers); - assert.deepStrictEqual(displayName, expected); - } -} - -/** */ -function testSortModifiers() { - /** @type {{modifiers: import('input').Modifier[], expected: import('input').Modifier[]}[]} */ - const data = [ - {modifiers: [], expected: []}, - {modifiers: ['shift', 'alt', 'ctrl', 'mouse4', 'meta', 'mouse1', 'mouse0'], expected: ['meta', 'ctrl', 'alt', 'shift', 'mouse0', 'mouse1', 'mouse4']} - ]; - - const hotkeyUtil = createHotkeyUtil(); - for (const {modifiers, expected} of data) { - const modifiers2 = hotkeyUtil.sortModifiers(modifiers); - assert.strictEqual(modifiers2, modifiers); - assert.deepStrictEqual(modifiers2, expected); - } -} - - -/** */ -function main() { - testCommandConversions(); - testDisplayNames(); - testSortModifiers(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-japanese-util.js b/test/test-japanese-util.js deleted file mode 100644 index 0a95b858..00000000 --- a/test/test-japanese-util.js +++ /dev/null @@ -1,915 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM(); -vm.execute([ - 'lib/wanakana.min.js', - 'js/language/sandbox/japanese-util.js', - 'js/general/text-source-map.js' -]); -/** @type {[JapaneseUtil: typeof JapaneseUtil, TextSourceMap: typeof TextSourceMap, wanakana: import('wanakana')]} */ -const [JapaneseUtil2, TextSourceMap2, wanakana] = vm.get(['JapaneseUtil', 'TextSourceMap', 'wanakana']); -const jp = new JapaneseUtil2(wanakana); - - -/** */ -function testIsCodePointKanji() { - /** @type {[characters: string, expected: boolean][]} */ - const data = [ - ['力方', true], - ['\u53f1\u{20b9f}', true], - ['かたカタ々kata、。?,.?', false], - ['逸逸', true] - ]; - - for (const [characters, expected] of data) { - for (const character of characters) { - const codePoint = /** @type {number} */ (character.codePointAt(0)); - const actual = jp.isCodePointKanji(codePoint); - assert.strictEqual(actual, expected, `isCodePointKanji failed for ${character} (\\u{${codePoint.toString(16)}})`); - } - } -} - -/** */ -function testIsCodePointKana() { - /** @type {[characters: string, expected: boolean][]} */ - const data = [ - ['かたカタ', true], - ['力方々kata、。?,.?', false], - ['\u53f1\u{20b9f}', false] - ]; - - for (const [characters, expected] of data) { - for (const character of characters) { - const codePoint = /** @type {number} */ (character.codePointAt(0)); - const actual = jp.isCodePointKana(codePoint); - assert.strictEqual(actual, expected, `isCodePointKana failed for ${character} (\\u{${codePoint.toString(16)}})`); - } - } -} - -/** */ -function testIsCodePointJapanese() { - /** @type {[characters: string, expected: boolean][]} */ - const data = [ - ['かたカタ力方々、。?', true], - ['\u53f1\u{20b9f}', true], - ['kata,.?', false], - ['逸逸', true] - ]; - - for (const [characters, expected] of data) { - for (const character of characters) { - const codePoint = /** @type {number} */ (character.codePointAt(0)); - const actual = jp.isCodePointJapanese(codePoint); - assert.strictEqual(actual, expected, `isCodePointJapanese failed for ${character} (\\u{${codePoint.toString(16)}})`); - } - } -} - -/** */ -function testIsStringEntirelyKana() { - /** @type {[string: string, expected: boolean][]} */ - const data = [ - ['かたかな', true], - ['カタカナ', true], - ['ひらがな', true], - ['ヒラガナ', true], - ['カタカナひらがな', true], - ['かたカタ力方々、。?', false], - ['\u53f1\u{20b9f}', false], - ['kata,.?', false], - ['かたカタ力方々、。?invalid', false], - ['\u53f1\u{20b9f}invalid', false], - ['kata,.?かた', false] - ]; - - for (const [string, expected] of data) { - assert.strictEqual(jp.isStringEntirelyKana(string), expected); - } -} - -/** */ -function testIsStringPartiallyJapanese() { - /** @type {[string: string, expected: boolean][]} */ - const data = [ - ['かたかな', true], - ['カタカナ', true], - ['ひらがな', true], - ['ヒラガナ', true], - ['カタカナひらがな', true], - ['かたカタ力方々、。?', true], - ['\u53f1\u{20b9f}', true], - ['kata,.?', false], - ['かたカタ力方々、。?invalid', true], - ['\u53f1\u{20b9f}invalid', true], - ['kata,.?かた', true], - ['逸逸', true] - ]; - - for (const [string, expected] of data) { - assert.strictEqual(jp.isStringPartiallyJapanese(string), expected); - } -} - -/** */ -function testConvertKatakanaToHiragana() { - /** @type {[string: string, expected: string, keepProlongedSoundMarks?: boolean][]} */ - const data = [ - ['かたかな', 'かたかな'], - ['ひらがな', 'ひらがな'], - ['カタカナ', 'かたかな'], - ['ヒラガナ', 'ひらがな'], - ['カタカナかたかな', 'かたかなかたかな'], - ['ヒラガナひらがな', 'ひらがなひらがな'], - ['chikaraちからチカラ力', 'chikaraちからちから力'], - ['katakana', 'katakana'], - ['hiragana', 'hiragana'], - ['カーナー', 'かあなあ'], - ['カーナー', 'かーなー', true] - ]; - - for (const [string, expected, keepProlongedSoundMarks=false] of data) { - assert.strictEqual(jp.convertKatakanaToHiragana(string, keepProlongedSoundMarks), expected); - } -} - -/** */ -function testConvertHiraganaToKatakana() { - /** @type {[string: string, expected: string][]} */ - const data = [ - ['かたかな', 'カタカナ'], - ['ひらがな', 'ヒラガナ'], - ['カタカナ', 'カタカナ'], - ['ヒラガナ', 'ヒラガナ'], - ['カタカナかたかな', 'カタカナカタカナ'], - ['ヒラガナひらがな', 'ヒラガナヒラガナ'], - ['chikaraちからチカラ力', 'chikaraチカラチカラ力'], - ['katakana', 'katakana'], - ['hiragana', 'hiragana'] - ]; - - for (const [string, expected] of data) { - assert.strictEqual(jp.convertHiraganaToKatakana(string), expected); - } -} - -/** */ -function testConvertToRomaji() { - /** @type {[string: string, expected: string][]} */ - const data = [ - ['かたかな', 'katakana'], - ['ひらがな', 'hiragana'], - ['カタカナ', 'katakana'], - ['ヒラガナ', 'hiragana'], - ['カタカナかたかな', 'katakanakatakana'], - ['ヒラガナひらがな', 'hiraganahiragana'], - ['chikaraちからチカラ力', 'chikarachikarachikara力'], - ['katakana', 'katakana'], - ['hiragana', 'hiragana'] - ]; - - for (const [string, expected] of data) { - assert.strictEqual(jp.convertToRomaji(string), expected); - } -} - -/** */ -function testConvertNumericToFullWidth() { - /** @type {[string: string, expected: string][]} */ - const data = [ - ['0123456789', '0123456789'], - ['abcdefghij', 'abcdefghij'], - ['カタカナ', 'カタカナ'], - ['ひらがな', 'ひらがな'] - ]; - - for (const [string, expected] of data) { - assert.strictEqual(jp.convertNumericToFullWidth(string), expected); - } -} - -/** */ -function testConvertHalfWidthKanaToFullWidth() { - /** @type {[string: string, expected: string, expectedSourceMapping?: number[]][]} */ - const data = [ - ['0123456789', '0123456789'], - ['abcdefghij', 'abcdefghij'], - ['カタカナ', 'カタカナ'], - ['ひらがな', 'ひらがな'], - ['カキ', 'カキ', [1, 1]], - ['ガキ', 'ガキ', [2, 1]], - ['ニホン', 'ニホン', [1, 1, 1]], - ['ニッポン', 'ニッポン', [1, 1, 2, 1]] - ]; - - for (const [string, expected, expectedSourceMapping] of data) { - const sourceMap = new TextSourceMap2(string); - const actual1 = jp.convertHalfWidthKanaToFullWidth(string, null); - const actual2 = jp.convertHalfWidthKanaToFullWidth(string, sourceMap); - assert.strictEqual(actual1, expected); - assert.strictEqual(actual2, expected); - if (typeof expectedSourceMapping !== 'undefined') { - assert.ok(sourceMap.equals(new TextSourceMap2(string, expectedSourceMapping))); - } - } -} - -/** */ -function testConvertAlphabeticToKana() { - /** @type {[string: string, expected: string, expectedSourceMapping?: number[]][]} */ - const data = [ - ['0123456789', '0123456789'], - ['abcdefghij', 'あbcでfgひj', [1, 1, 1, 2, 1, 1, 2, 1]], - ['ABCDEFGHIJ', 'あbcでfgひj', [1, 1, 1, 2, 1, 1, 2, 1]], // wanakana.toHiragana converts text to lower case - ['カタカナ', 'カタカナ'], - ['ひらがな', 'ひらがな'], - ['chikara', 'ちから', [3, 2, 2]], - ['CHIKARA', 'ちから', [3, 2, 2]] - ]; - - for (const [string, expected, expectedSourceMapping] of data) { - const sourceMap = new TextSourceMap2(string); - const actual1 = jp.convertAlphabeticToKana(string, null); - const actual2 = jp.convertAlphabeticToKana(string, sourceMap); - assert.strictEqual(actual1, expected); - assert.strictEqual(actual2, expected); - if (typeof expectedSourceMapping !== 'undefined') { - assert.ok(sourceMap.equals(new TextSourceMap2(string, expectedSourceMapping))); - } - } -} - -/** */ -function testDistributeFurigana() { - /** @type {[input: [term: string, reading: string], expected: {text: string, reading: string}[]][]} */ - const data = [ - ([ - ['有り難う', 'ありがとう'], - [ - {text: '有', reading: 'あ'}, - {text: 'り', reading: ''}, - {text: '難', reading: 'がと'}, - {text: 'う', reading: ''} - ] - ]), - [ - ['方々', 'かたがた'], - [ - {text: '方々', reading: 'かたがた'} - ] - ], - [ - ['お祝い', 'おいわい'], - [ - {text: 'お', reading: ''}, - {text: '祝', reading: 'いわ'}, - {text: 'い', reading: ''} - ] - ], - [ - ['美味しい', 'おいしい'], - [ - {text: '美味', reading: 'おい'}, - {text: 'しい', reading: ''} - ] - ], - [ - ['食べ物', 'たべもの'], - [ - {text: '食', reading: 'た'}, - {text: 'べ', reading: ''}, - {text: '物', reading: 'もの'} - ] - ], - [ - ['試し切り', 'ためしぎり'], - [ - {text: '試', reading: 'ため'}, - {text: 'し', reading: ''}, - {text: '切', reading: 'ぎ'}, - {text: 'り', reading: ''} - ] - ], - // Ambiguous - [ - ['飼い犬', 'かいいぬ'], - [ - {text: '飼い犬', reading: 'かいいぬ'} - ] - ], - [ - ['長い間', 'ながいあいだ'], - [ - {text: '長い間', reading: 'ながいあいだ'} - ] - ], - // Same/empty reading - [ - ['飼い犬', ''], - [ - {text: '飼い犬', reading: ''} - ] - ], - [ - ['かいいぬ', 'かいいぬ'], - [ - {text: 'かいいぬ', reading: ''} - ] - ], - [ - ['かいぬ', 'かいぬ'], - [ - {text: 'かいぬ', reading: ''} - ] - ], - // Misc - [ - ['月', 'か'], - [ - {text: '月', reading: 'か'} - ] - ], - [ - ['月', 'カ'], - [ - {text: '月', reading: 'カ'} - ] - ], - // Mismatched kana readings - [ - ['有り難う', 'アリガトウ'], - [ - {text: '有', reading: 'ア'}, - {text: 'り', reading: 'リ'}, - {text: '難', reading: 'ガト'}, - {text: 'う', reading: 'ウ'} - ] - ], - [ - ['ありがとう', 'アリガトウ'], - [ - {text: 'ありがとう', reading: 'アリガトウ'} - ] - ], - // Mismatched kana readings (real examples) - [ - ['カ月', 'かげつ'], - [ - {text: 'カ', reading: 'か'}, - {text: '月', reading: 'げつ'} - ] - ], - [ - ['序ノ口', 'じょのくち'], - [ - {text: '序', reading: 'じょ'}, - {text: 'ノ', reading: 'の'}, - {text: '口', reading: 'くち'} - ] - ], - [ - ['スズメの涙', 'すずめのなみだ'], - [ - {text: 'スズメ', reading: 'すずめ'}, - {text: 'の', reading: ''}, - {text: '涙', reading: 'なみだ'} - ] - ], - [ - ['二カ所', 'にかしょ'], - [ - {text: '二', reading: 'に'}, - {text: 'カ', reading: 'か'}, - {text: '所', reading: 'しょ'} - ] - ], - [ - ['八ツ橋', 'やつはし'], - [ - {text: '八', reading: 'や'}, - {text: 'ツ', reading: 'つ'}, - {text: '橋', reading: 'はし'} - ] - ], - [ - ['八ツ橋', 'やつはし'], - [ - {text: '八', reading: 'や'}, - {text: 'ツ', reading: 'つ'}, - {text: '橋', reading: 'はし'} - ] - ], - [ - ['一カ月', 'いっかげつ'], - [ - {text: '一', reading: 'いっ'}, - {text: 'カ', reading: 'か'}, - {text: '月', reading: 'げつ'} - ] - ], - [ - ['一カ所', 'いっかしょ'], - [ - {text: '一', reading: 'いっ'}, - {text: 'カ', reading: 'か'}, - {text: '所', reading: 'しょ'} - ] - ], - [ - ['カ所', 'かしょ'], - [ - {text: 'カ', reading: 'か'}, - {text: '所', reading: 'しょ'} - ] - ], - [ - ['数カ月', 'すうかげつ'], - [ - {text: '数', reading: 'すう'}, - {text: 'カ', reading: 'か'}, - {text: '月', reading: 'げつ'} - ] - ], - [ - ['くノ一', 'くのいち'], - [ - {text: 'く', reading: ''}, - {text: 'ノ', reading: 'の'}, - {text: '一', reading: 'いち'} - ] - ], - [ - ['くノ一', 'くのいち'], - [ - {text: 'く', reading: ''}, - {text: 'ノ', reading: 'の'}, - {text: '一', reading: 'いち'} - ] - ], - [ - ['数カ国', 'すうかこく'], - [ - {text: '数', reading: 'すう'}, - {text: 'カ', reading: 'か'}, - {text: '国', reading: 'こく'} - ] - ], - [ - ['数カ所', 'すうかしょ'], - [ - {text: '数', reading: 'すう'}, - {text: 'カ', reading: 'か'}, - {text: '所', reading: 'しょ'} - ] - ], - [ - ['壇ノ浦の戦い', 'だんのうらのたたかい'], - [ - {text: '壇', reading: 'だん'}, - {text: 'ノ', reading: 'の'}, - {text: '浦', reading: 'うら'}, - {text: 'の', reading: ''}, - {text: '戦', reading: 'たたか'}, - {text: 'い', reading: ''} - ] - ], - [ - ['壇ノ浦の戦', 'だんのうらのたたかい'], - [ - {text: '壇', reading: 'だん'}, - {text: 'ノ', reading: 'の'}, - {text: '浦', reading: 'うら'}, - {text: 'の', reading: ''}, - {text: '戦', reading: 'たたかい'} - ] - ], - [ - ['序ノ口格', 'じょのくちかく'], - [ - {text: '序', reading: 'じょ'}, - {text: 'ノ', reading: 'の'}, - {text: '口格', reading: 'くちかく'} - ] - ], - [ - ['二カ国語', 'にかこくご'], - [ - {text: '二', reading: 'に'}, - {text: 'カ', reading: 'か'}, - {text: '国語', reading: 'こくご'} - ] - ], - [ - ['カ国', 'かこく'], - [ - {text: 'カ', reading: 'か'}, - {text: '国', reading: 'こく'} - ] - ], - [ - ['カ国語', 'かこくご'], - [ - {text: 'カ', reading: 'か'}, - {text: '国語', reading: 'こくご'} - ] - ], - [ - ['壇ノ浦の合戦', 'だんのうらのかっせん'], - [ - {text: '壇', reading: 'だん'}, - {text: 'ノ', reading: 'の'}, - {text: '浦', reading: 'うら'}, - {text: 'の', reading: ''}, - {text: '合戦', reading: 'かっせん'} - ] - ], - [ - ['一タ偏', 'いちたへん'], - [ - {text: '一', reading: 'いち'}, - {text: 'タ', reading: 'た'}, - {text: '偏', reading: 'へん'} - ] - ], - [ - ['ル又', 'るまた'], - [ - {text: 'ル', reading: 'る'}, - {text: '又', reading: 'また'} - ] - ], - [ - ['ノ木偏', 'のぎへん'], - [ - {text: 'ノ', reading: 'の'}, - {text: '木偏', reading: 'ぎへん'} - ] - ], - [ - ['一ノ貝', 'いちのかい'], - [ - {text: '一', reading: 'いち'}, - {text: 'ノ', reading: 'の'}, - {text: '貝', reading: 'かい'} - ] - ], - [ - ['虎ノ門事件', 'とらのもんじけん'], - [ - {text: '虎', reading: 'とら'}, - {text: 'ノ', reading: 'の'}, - {text: '門事件', reading: 'もんじけん'} - ] - ], - [ - ['教育ニ関スル勅語', 'きょういくにかんするちょくご'], - [ - {text: '教育', reading: 'きょういく'}, - {text: 'ニ', reading: 'に'}, - {text: '関', reading: 'かん'}, - {text: 'スル', reading: 'する'}, - {text: '勅語', reading: 'ちょくご'} - ] - ], - [ - ['二カ年', 'にかねん'], - [ - {text: '二', reading: 'に'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['三カ年', 'さんかねん'], - [ - {text: '三', reading: 'さん'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['四カ年', 'よんかねん'], - [ - {text: '四', reading: 'よん'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['五カ年', 'ごかねん'], - [ - {text: '五', reading: 'ご'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['六カ年', 'ろっかねん'], - [ - {text: '六', reading: 'ろっ'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['七カ年', 'ななかねん'], - [ - {text: '七', reading: 'なな'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['八カ年', 'はちかねん'], - [ - {text: '八', reading: 'はち'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['九カ年', 'きゅうかねん'], - [ - {text: '九', reading: 'きゅう'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['十カ年', 'じゅうかねん'], - [ - {text: '十', reading: 'じゅう'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['鏡ノ間', 'かがみのま'], - [ - {text: '鏡', reading: 'かがみ'}, - {text: 'ノ', reading: 'の'}, - {text: '間', reading: 'ま'} - ] - ], - [ - ['鏡ノ間', 'かがみのま'], - [ - {text: '鏡', reading: 'かがみ'}, - {text: 'ノ', reading: 'の'}, - {text: '間', reading: 'ま'} - ] - ], - [ - ['ページ違反', 'ぺーじいはん'], - [ - {text: 'ペ', reading: 'ぺ'}, - {text: 'ー', reading: ''}, - {text: 'ジ', reading: 'じ'}, - {text: '違反', reading: 'いはん'} - ] - ], - // Mismatched kana - [ - ['サボる', 'サボル'], - [ - {text: 'サボ', reading: ''}, - {text: 'る', reading: 'ル'} - ] - ], - // Reading starts with term, but has remainder characters - [ - ['シック', 'シック・ビルしょうこうぐん'], - [ - {text: 'シック', reading: 'シック・ビルしょうこうぐん'} - ] - ], - // Kanji distribution tests - [ - ['逸らす', 'そらす'], - [ - {text: '逸', reading: 'そ'}, - {text: 'らす', reading: ''} - ] - ], - [ - ['逸らす', 'そらす'], - [ - {text: '逸', reading: 'そ'}, - {text: 'らす', reading: ''} - ] - ] - ]; - - for (const [[term, reading], expected] of data) { - const actual = jp.distributeFurigana(term, reading); - vm.assert.deepStrictEqual(actual, expected); - } -} - -/** */ -function testDistributeFuriganaInflected() { - /** @type {[input: [term: string, reading: string, source: string], expected: {text: string, reading: string}[]][]} */ - const data = [ - [ - ['美味しい', 'おいしい', '美味しかた'], - [ - {text: '美味', reading: 'おい'}, - {text: 'しかた', reading: ''} - ] - ], - [ - ['食べる', 'たべる', '食べた'], - [ - {text: '食', reading: 'た'}, - {text: 'べた', reading: ''} - ] - ], - [ - ['迄に', 'までに', 'までに'], - [ - {text: 'までに', reading: ''} - ] - ], - [ - ['行う', 'おこなう', 'おこなわなかった'], - [ - {text: 'おこなわなかった', reading: ''} - ] - ], - [ - ['いい', 'いい', 'イイ'], - [ - {text: 'イイ', reading: ''} - ] - ], - [ - ['否か', 'いなか', '否カ'], - [ - {text: '否', reading: 'いな'}, - {text: 'カ', reading: 'か'} - ] - ] - ]; - - for (const [[term, reading, source], expected] of data) { - const actual = jp.distributeFuriganaInflected(term, reading, source); - vm.assert.deepStrictEqual(actual, expected); - } -} - -/** */ -function testCollapseEmphaticSequences() { - /** @type {[input: [text: string, fullCollapse: boolean], output: [expected: string, expectedSourceMapping: number[]]][]} */ - const data = [ - [['かこい', false], ['かこい', [1, 1, 1]]], - [['かこい', true], ['かこい', [1, 1, 1]]], - [['かっこい', false], ['かっこい', [1, 1, 1, 1]]], - [['かっこい', true], ['かこい', [2, 1, 1]]], - [['かっっこい', false], ['かっこい', [1, 2, 1, 1]]], - [['かっっこい', true], ['かこい', [3, 1, 1]]], - [['かっっっこい', false], ['かっこい', [1, 3, 1, 1]]], - [['かっっっこい', true], ['かこい', [4, 1, 1]]], - - [['こい', false], ['こい', [1, 1]]], - [['こい', true], ['こい', [1, 1]]], - [['っこい', false], ['っこい', [1, 1, 1]]], - [['っこい', true], ['こい', [2, 1]]], - [['っっこい', false], ['っこい', [2, 1, 1]]], - [['っっこい', true], ['こい', [3, 1]]], - [['っっっこい', false], ['っこい', [3, 1, 1]]], - [['っっっこい', true], ['こい', [4, 1]]], - - [['すごい', false], ['すごい', [1, 1, 1]]], - [['すごい', true], ['すごい', [1, 1, 1]]], - [['すごーい', false], ['すごーい', [1, 1, 1, 1]]], - [['すごーい', true], ['すごい', [1, 2, 1]]], - [['すごーーい', false], ['すごーい', [1, 1, 2, 1]]], - [['すごーーい', true], ['すごい', [1, 3, 1]]], - [['すっごーい', false], ['すっごーい', [1, 1, 1, 1, 1]]], - [['すっごーい', true], ['すごい', [2, 2, 1]]], - [['すっっごーーい', false], ['すっごーい', [1, 2, 1, 2, 1]]], - [['すっっごーーい', true], ['すごい', [3, 3, 1]]], - - [['', false], ['', []]], - [['', true], ['', []]], - [['っ', false], ['っ', [1]]], - [['っ', true], ['', [1]]], - [['っっ', false], ['っ', [2]]], - [['っっ', true], ['', [2]]], - [['っっっ', false], ['っ', [3]]], - [['っっっ', true], ['', [3]]] - ]; - - for (const [[text, fullCollapse], [expected, expectedSourceMapping]] of data) { - const sourceMap = new TextSourceMap2(text); - const actual1 = jp.collapseEmphaticSequences(text, fullCollapse, null); - const actual2 = jp.collapseEmphaticSequences(text, fullCollapse, sourceMap); - assert.strictEqual(actual1, expected); - assert.strictEqual(actual2, expected); - if (typeof expectedSourceMapping !== 'undefined') { - assert.ok(sourceMap.equals(new TextSourceMap2(text, expectedSourceMapping))); - } - } -} - -/** */ -function testIsMoraPitchHigh() { - /** @type {[input: [moraIndex: number, pitchAccentDownstepPosition: number], expected: boolean][]} */ - const data = [ - [[0, 0], false], - [[1, 0], true], - [[2, 0], true], - [[3, 0], true], - - [[0, 1], true], - [[1, 1], false], - [[2, 1], false], - [[3, 1], false], - - [[0, 2], false], - [[1, 2], true], - [[2, 2], false], - [[3, 2], false], - - [[0, 3], false], - [[1, 3], true], - [[2, 3], true], - [[3, 3], false], - - [[0, 4], false], - [[1, 4], true], - [[2, 4], true], - [[3, 4], true] - ]; - - for (const [[moraIndex, pitchAccentDownstepPosition], expected] of data) { - const actual = jp.isMoraPitchHigh(moraIndex, pitchAccentDownstepPosition); - assert.strictEqual(actual, expected); - } -} - -/** */ -function testGetKanaMorae() { - /** @type {[text: string, expected: string[]][]} */ - const data = [ - ['かこ', ['か', 'こ']], - ['かっこ', ['か', 'っ', 'こ']], - ['カコ', ['カ', 'コ']], - ['カッコ', ['カ', 'ッ', 'コ']], - ['コート', ['コ', 'ー', 'ト']], - ['ちゃんと', ['ちゃ', 'ん', 'と']], - ['とうきょう', ['と', 'う', 'きょ', 'う']], - ['ぎゅう', ['ぎゅ', 'う']], - ['ディスコ', ['ディ', 'ス', 'コ']] - ]; - - for (const [text, expected] of data) { - const actual = jp.getKanaMorae(text); - vm.assert.deepStrictEqual(actual, expected); - } -} - - -/** */ -function main() { - testIsCodePointKanji(); - testIsCodePointKana(); - testIsCodePointJapanese(); - testIsStringEntirelyKana(); - testIsStringPartiallyJapanese(); - testConvertKatakanaToHiragana(); - testConvertHiraganaToKatakana(); - testConvertToRomaji(); - testConvertNumericToFullWidth(); - testConvertHalfWidthKanaToFullWidth(); - testConvertAlphabeticToKana(); - testDistributeFurigana(); - testDistributeFuriganaInflected(); - testCollapseEmphaticSequences(); - testIsMoraPitchHigh(); - testGetKanaMorae(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-json-schema.js b/test/test-json-schema.js deleted file mode 100644 index f9cb023c..00000000 --- a/test/test-json-schema.js +++ /dev/null @@ -1,1048 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM(); -vm.execute([ - 'js/core.js', - 'js/core/extension-error.js', - 'js/general/cache-map.js', - 'js/data/json-schema.js' -]); -/** @type {typeof JsonSchema} */ -const JsonSchema2 = vm.getSingle('JsonSchema'); - - -/** - * @param {import('json-schema').Schema} schema - * @param {unknown} value - * @returns {boolean} - */ -function schemaValidate(schema, value) { - return new JsonSchema2(schema).isValid(value); -} - -/** - * @param {import('json-schema').Schema} schema - * @param {unknown} value - * @returns {import('json-schema').Value} - */ -function getValidValueOrDefault(schema, value) { - return new JsonSchema2(schema).getValidValueOrDefault(value); -} - -/** - * @param {import('json-schema').Schema} schema - * @param {import('json-schema').Value} value - * @returns {import('json-schema').Value} - */ -function createProxy(schema, value) { - return new JsonSchema2(schema).createProxy(value); -} - -/** - * @template T - * @param {T} value - * @returns {T} - */ -function clone(value) { - return JSON.parse(JSON.stringify(value)); -} - - -/** */ -function testValidate1() { - /** @type {import('json-schema').Schema} */ - const schema = { - allOf: [ - { - type: 'number' - }, - { - anyOf: [ - {minimum: 10, maximum: 100}, - {minimum: -100, maximum: -10} - ] - }, - { - oneOf: [ - {multipleOf: 3}, - {multipleOf: 5} - ] - }, - { - not: { - anyOf: [ - {multipleOf: 20} - ] - } - } - ] - }; - - /** - * @param {number} value - * @returns {boolean} - */ - const jsValidate = (value) => { - return ( - typeof value === 'number' && - ( - (value >= 10 && value <= 100) || - (value >= -100 && value <= -10) - ) && - ( - ( - (value % 3) === 0 || - (value % 5) === 0 - ) && - (value % 15) !== 0 - ) && - (value % 20) !== 0 - ); - }; - - for (let i = -111; i <= 111; i++) { - const actual = schemaValidate(schema, i); - const expected = jsValidate(i); - assert.strictEqual(actual, expected, `i=${i}; expected=${expected}; actual=${actual}`); - } -} - -/** */ -function testValidate2() { - /** @type {{schema: import('json-schema').Schema, inputs: {expected: boolean, value: unknown}[]}[]} */ - const data = [ - // String tests - { - schema: { - type: 'string' - }, - inputs: [ - {expected: false, value: null}, - {expected: false, value: void 0}, - {expected: false, value: 0}, - {expected: false, value: {}}, - {expected: false, value: []}, - {expected: true, value: ''} - ] - }, - { - schema: { - type: 'string', - minLength: 2 - }, - inputs: [ - {expected: false, value: ''}, - {expected: false, value: '1'}, - {expected: true, value: '12'}, - {expected: true, value: '123'} - ] - }, - { - schema: { - type: 'string', - maxLength: 2 - }, - inputs: [ - {expected: true, value: ''}, - {expected: true, value: '1'}, - {expected: true, value: '12'}, - {expected: false, value: '123'} - ] - }, - { - schema: { - type: 'string', - pattern: 'test' - }, - inputs: [ - {expected: false, value: ''}, - {expected: true, value: 'test'}, - {expected: false, value: 'TEST'}, - {expected: true, value: 'ABCtestDEF'}, - {expected: false, value: 'ABCTESTDEF'} - ] - }, - { - schema: { - type: 'string', - pattern: '^test$' - }, - inputs: [ - {expected: false, value: ''}, - {expected: true, value: 'test'}, - {expected: false, value: 'TEST'}, - {expected: false, value: 'ABCtestDEF'}, - {expected: false, value: 'ABCTESTDEF'} - ] - }, - { - schema: { - type: 'string', - pattern: '^test$', - patternFlags: 'i' - }, - inputs: [ - {expected: false, value: ''}, - {expected: true, value: 'test'}, - {expected: true, value: 'TEST'}, - {expected: false, value: 'ABCtestDEF'}, - {expected: false, value: 'ABCTESTDEF'} - ] - }, - { - schema: { - type: 'string', - pattern: '*' - }, - inputs: [ - {expected: false, value: ''} - ] - }, - { - schema: { - type: 'string', - pattern: '.', - patternFlags: '?' - }, - inputs: [ - {expected: false, value: ''} - ] - }, - - // Const tests - { - schema: { - const: 32 - }, - inputs: [ - {expected: true, value: 32}, - {expected: false, value: 0}, - {expected: false, value: '32'}, - {expected: false, value: null}, - {expected: false, value: {a: 'b'}}, - {expected: false, value: [1, 2, 3]} - ] - }, - { - schema: { - const: '32' - }, - inputs: [ - {expected: false, value: 32}, - {expected: false, value: 0}, - {expected: true, value: '32'}, - {expected: false, value: null}, - {expected: false, value: {a: 'b'}}, - {expected: false, value: [1, 2, 3]} - ] - }, - { - schema: { - const: null - }, - inputs: [ - {expected: false, value: 32}, - {expected: false, value: 0}, - {expected: false, value: '32'}, - {expected: true, value: null}, - {expected: false, value: {a: 'b'}}, - {expected: false, value: [1, 2, 3]} - ] - }, - { - schema: { - const: {a: 'b'} - }, - inputs: [ - {expected: false, value: 32}, - {expected: false, value: 0}, - {expected: false, value: '32'}, - {expected: false, value: null}, - {expected: false, value: {a: 'b'}}, - {expected: false, value: [1, 2, 3]} - ] - }, - { - schema: { - const: [1, 2, 3] - }, - inputs: [ - {expected: false, value: 32}, - {expected: false, value: 0}, - {expected: false, value: '32'}, - {expected: false, value: null}, - {expected: false, value: {a: 'b'}}, - {expected: false, value: [1, 2, 3]} - ] - }, - - // Array contains tests - { - schema: { - type: 'array', - contains: {const: 32} - }, - inputs: [ - {expected: false, value: []}, - {expected: true, value: [32]}, - {expected: true, value: [1, 32]}, - {expected: true, value: [1, 32, 1]}, - {expected: false, value: [33]}, - {expected: false, value: [1, 33]}, - {expected: false, value: [1, 33, 1]} - ] - }, - - // Number limits tests - { - schema: { - type: 'number', - minimum: 0 - }, - inputs: [ - {expected: false, value: -1}, - {expected: true, value: 0}, - {expected: true, value: 1} - ] - }, - { - schema: { - type: 'number', - exclusiveMinimum: 0 - }, - inputs: [ - {expected: false, value: -1}, - {expected: false, value: 0}, - {expected: true, value: 1} - ] - }, - { - schema: { - type: 'number', - maximum: 0 - }, - inputs: [ - {expected: true, value: -1}, - {expected: true, value: 0}, - {expected: false, value: 1} - ] - }, - { - schema: { - type: 'number', - exclusiveMaximum: 0 - }, - inputs: [ - {expected: true, value: -1}, - {expected: false, value: 0}, - {expected: false, value: 1} - ] - }, - - // Integer limits tests - { - schema: { - type: 'integer', - minimum: 0 - }, - inputs: [ - {expected: false, value: -1}, - {expected: true, value: 0}, - {expected: true, value: 1} - ] - }, - { - schema: { - type: 'integer', - exclusiveMinimum: 0 - }, - inputs: [ - {expected: false, value: -1}, - {expected: false, value: 0}, - {expected: true, value: 1} - ] - }, - { - schema: { - type: 'integer', - maximum: 0 - }, - inputs: [ - {expected: true, value: -1}, - {expected: true, value: 0}, - {expected: false, value: 1} - ] - }, - { - schema: { - type: 'integer', - exclusiveMaximum: 0 - }, - inputs: [ - {expected: true, value: -1}, - {expected: false, value: 0}, - {expected: false, value: 1} - ] - }, - { - schema: { - type: 'integer', - multipleOf: 2 - }, - inputs: [ - {expected: true, value: -2}, - {expected: false, value: -1}, - {expected: true, value: 0}, - {expected: false, value: 1}, - {expected: true, value: 2} - ] - }, - - // Numeric type tests - { - schema: { - type: 'number' - }, - inputs: [ - {expected: true, value: 0}, - {expected: true, value: 0.5}, - {expected: true, value: 1}, - {expected: false, value: '0'}, - {expected: false, value: null}, - {expected: false, value: []}, - {expected: false, value: {}} - ] - }, - { - schema: { - type: 'integer' - }, - inputs: [ - {expected: true, value: 0}, - {expected: false, value: 0.5}, - {expected: true, value: 1}, - {expected: false, value: '0'}, - {expected: false, value: null}, - {expected: false, value: []}, - {expected: false, value: {}} - ] - }, - - // Reference tests - { - schema: { - definitions: { - example: { - type: 'number' - } - }, - $ref: '#/definitions/example' - }, - inputs: [ - {expected: true, value: 0}, - {expected: true, value: 0.5}, - {expected: true, value: 1}, - {expected: false, value: '0'}, - {expected: false, value: null}, - {expected: false, value: []}, - {expected: false, value: {}} - ] - }, - { - schema: { - definitions: { - example: { - type: 'integer' - } - }, - $ref: '#/definitions/example' - }, - inputs: [ - {expected: true, value: 0}, - {expected: false, value: 0.5}, - {expected: true, value: 1}, - {expected: false, value: '0'}, - {expected: false, value: null}, - {expected: false, value: []}, - {expected: false, value: {}} - ] - }, - { - schema: { - definitions: { - example: { - type: 'object', - additionalProperties: false, - properties: { - test: { - $ref: '#/definitions/example' - } - } - } - }, - $ref: '#/definitions/example' - }, - inputs: [ - {expected: false, value: 0}, - {expected: false, value: 0.5}, - {expected: false, value: 1}, - {expected: false, value: '0'}, - {expected: false, value: null}, - {expected: false, value: []}, - {expected: true, value: {}}, - {expected: false, value: {test: 0}}, - {expected: false, value: {test: 0.5}}, - {expected: false, value: {test: 1}}, - {expected: false, value: {test: '0'}}, - {expected: false, value: {test: null}}, - {expected: false, value: {test: []}}, - {expected: true, value: {test: {}}}, - {expected: true, value: {test: {test: {}}}}, - {expected: true, value: {test: {test: {test: {}}}}} - ] - } - ]; - - for (const {schema, inputs} of data) { - for (const {expected, value} of inputs) { - const actual = schemaValidate(schema, value); - assert.strictEqual(actual, expected); - } - } -} - - -/** */ -function testGetValidValueOrDefault1() { - /** @type {{schema: import('json-schema').Schema, inputs: [value: unknown, expected: unknown][]}[]} */ - const data = [ - // Test value defaulting on objects with additionalProperties=false - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'string', - default: 'default' - } - }, - additionalProperties: false - }, - inputs: [ - [ - void 0, - {test: 'default'} - ], - [ - null, - {test: 'default'} - ], - [ - 0, - {test: 'default'} - ], - [ - '', - {test: 'default'} - ], - [ - [], - {test: 'default'} - ], - [ - {}, - {test: 'default'} - ], - [ - {test: 'value'}, - {test: 'value'} - ], - [ - {test2: 'value2'}, - {test: 'default'} - ], - [ - {test: 'value', test2: 'value2'}, - {test: 'value'} - ] - ] - }, - - // Test value defaulting on objects with additionalProperties=true - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'string', - default: 'default' - } - }, - additionalProperties: true - }, - inputs: [ - [ - {}, - {test: 'default'} - ], - [ - {test: 'value'}, - {test: 'value'} - ], - [ - {test2: 'value2'}, - {test: 'default', test2: 'value2'} - ], - [ - {test: 'value', test2: 'value2'}, - {test: 'value', test2: 'value2'} - ] - ] - }, - - // Test value defaulting on objects with additionalProperties={schema} - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'string', - default: 'default' - } - }, - additionalProperties: { - type: 'number', - default: 10 - } - }, - inputs: [ - [ - {}, - {test: 'default'} - ], - [ - {test: 'value'}, - {test: 'value'} - ], - [ - {test2: 'value2'}, - {test: 'default', test2: 10} - ], - [ - {test: 'value', test2: 'value2'}, - {test: 'value', test2: 10} - ], - [ - {test2: 2}, - {test: 'default', test2: 2} - ], - [ - {test: 'value', test2: 2}, - {test: 'value', test2: 2} - ], - [ - {test: 'value', test2: 2, test3: null}, - {test: 'value', test2: 2, test3: 10} - ], - [ - {test: 'value', test2: 2, test3: void 0}, - {test: 'value', test2: 2, test3: 10} - ] - ] - }, - - // Test value defaulting where hasOwnProperty is false - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'string', - default: 'default' - } - } - }, - inputs: [ - [ - {}, - {test: 'default'} - ], - [ - {test: 'value'}, - {test: 'value'} - ], - [ - Object.create({test: 'value'}), - {test: 'default'} - ] - ] - }, - { - schema: { - type: 'object', - required: ['toString'], - properties: { - toString: /** @type {import('json-schema').SchemaObject} */ ({ - type: 'string', - default: 'default' - }) - } - }, - inputs: [ - [ - {}, - {toString: 'default'} - ], - [ - {toString: 'value'}, - {toString: 'value'} - ], - [ - Object.create({toString: 'value'}), - {toString: 'default'} - ] - ] - }, - - // Test enum - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'string', - default: 'value1', - enum: ['value1', 'value2', 'value3'] - } - } - }, - inputs: [ - [ - {test: 'value1'}, - {test: 'value1'} - ], - [ - {test: 'value2'}, - {test: 'value2'} - ], - [ - {test: 'value3'}, - {test: 'value3'} - ], - [ - {test: 'value4'}, - {test: 'value1'} - ] - ] - }, - - // Test valid vs invalid default - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'integer', - default: 2, - minimum: 1 - } - } - }, - inputs: [ - [ - {test: -1}, - {test: 2} - ] - ] - }, - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'integer', - default: 1, - minimum: 2 - } - } - }, - inputs: [ - [ - {test: -1}, - {test: -1} - ] - ] - }, - - // Test references - { - schema: { - definitions: { - example: { - type: 'number', - default: 0 - } - }, - $ref: '#/definitions/example' - }, - inputs: [ - [ - 1, - 1 - ], - [ - null, - 0 - ], - [ - 'test', - 0 - ], - [ - {test: 'value'}, - 0 - ] - ] - }, - { - schema: { - definitions: { - example: { - type: 'object', - additionalProperties: false, - properties: { - test: { - $ref: '#/definitions/example' - } - } - } - }, - $ref: '#/definitions/example' - }, - inputs: [ - [ - 1, - {} - ], - [ - null, - {} - ], - [ - 'test', - {} - ], - [ - {}, - {} - ], - [ - {test: {}}, - {test: {}} - ], - [ - {test: 'value'}, - {test: {}} - ], - [ - {test: {test: {}}}, - {test: {test: {}}} - ] - ] - } - ]; - - for (const {schema, inputs} of data) { - for (const [value, expected] of inputs) { - const actual = getValidValueOrDefault(schema, value); - vm.assert.deepStrictEqual(actual, expected); - } - } -} - - -/** */ -function testProxy1() { - /** @type {{schema: import('json-schema').Schema, tests: {error: boolean, value?: import('json-schema').Value, action: (value: import('core').SafeAny) => void}[]}[]} */ - const data = [ - // Object tests - { - schema: { - type: 'object', - required: ['test'], - additionalProperties: false, - properties: { - test: { - type: 'string', - default: 'default' - } - } - }, - tests: [ - {error: false, value: {test: 'default'}, action: (value) => { value.test = 'string'; }}, - {error: true, value: {test: 'default'}, action: (value) => { value.test = null; }}, - {error: true, value: {test: 'default'}, action: (value) => { delete value.test; }}, - {error: true, value: {test: 'default'}, action: (value) => { value.test2 = 'string'; }}, - {error: false, value: {test: 'default'}, action: (value) => { delete value.test2; }} - ] - }, - { - schema: { - type: 'object', - required: ['test'], - additionalProperties: true, - properties: { - test: { - type: 'string', - default: 'default' - } - } - }, - tests: [ - {error: false, value: {test: 'default'}, action: (value) => { value.test = 'string'; }}, - {error: true, value: {test: 'default'}, action: (value) => { value.test = null; }}, - {error: true, value: {test: 'default'}, action: (value) => { delete value.test; }}, - {error: false, value: {test: 'default'}, action: (value) => { value.test2 = 'string'; }}, - {error: false, value: {test: 'default'}, action: (value) => { delete value.test2; }} - ] - }, - { - schema: { - type: 'object', - required: ['test1'], - additionalProperties: false, - properties: { - test1: { - type: 'object', - required: ['test2'], - additionalProperties: false, - properties: { - test2: { - type: 'object', - required: ['test3'], - additionalProperties: false, - properties: { - test3: { - type: 'string', - default: 'default' - } - } - } - } - } - } - }, - tests: [ - {error: false, action: (value) => { value.test1.test2.test3 = 'string'; }}, - {error: true, action: (value) => { value.test1.test2.test3 = null; }}, - {error: true, action: (value) => { delete value.test1.test2.test3; }}, - {error: true, action: (value) => { value.test1.test2 = null; }}, - {error: true, action: (value) => { value.test1 = null; }}, - {error: true, action: (value) => { value.test4 = 'string'; }}, - {error: false, action: (value) => { delete value.test4; }} - ] - }, - - // Array tests - { - schema: { - type: 'array', - items: { - type: 'string', - default: 'default' - } - }, - tests: [ - {error: false, value: ['default'], action: (value) => { value[0] = 'string'; }}, - {error: true, value: ['default'], action: (value) => { value[0] = null; }}, - {error: false, value: ['default'], action: (value) => { delete value[0]; }}, - {error: false, value: ['default'], action: (value) => { value[1] = 'string'; }}, - {error: false, value: ['default'], action: (value) => { - value[1] = 'string'; - if (value.length !== 2) { throw new Error(`Invalid length; expected=2; actual=${value.length}`); } - if (typeof value.push !== 'function') { throw new Error(`Invalid push; expected=function; actual=${typeof value.push}`); } - }} - ] - }, - - // Reference tests - { - schema: { - definitions: { - example: { - type: 'object', - additionalProperties: false, - properties: { - test: { - $ref: '#/definitions/example' - } - } - } - }, - $ref: '#/definitions/example' - }, - tests: [ - {error: false, value: {}, action: (value) => { value.test = {}; }}, - {error: false, value: {}, action: (value) => { value.test = {}; value.test.test = {}; }}, - {error: false, value: {}, action: (value) => { value.test = {test: {}}; }}, - {error: true, value: {}, action: (value) => { value.test = null; }}, - {error: true, value: {}, action: (value) => { value.test = 'string'; }}, - {error: true, value: {}, action: (value) => { value.test = {}; value.test.test = 'string'; }}, - {error: true, value: {}, action: (value) => { value.test = {test: 'string'}; }} - ] - } - ]; - - for (const {schema, tests} of data) { - for (let {error, value, action} of tests) { - if (typeof value === 'undefined') { value = getValidValueOrDefault(schema, void 0); } - value = clone(value); - assert.ok(schemaValidate(schema, value)); - const valueProxy = createProxy(schema, value); - if (error) { - assert.throws(() => action(valueProxy)); - } else { - assert.doesNotThrow(() => action(valueProxy)); - } - } - } -} - - -/** */ -function main() { - testValidate1(); - testValidate2(); - testGetValidValueOrDefault1(); - testProxy1(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-manifest.js b/test/test-manifest.js deleted file mode 100644 index 32a498e1..00000000 --- a/test/test-manifest.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {ManifestUtil} = require('../dev/manifest-util'); - - -/** - * @returns {string} - */ -function loadManifestString() { - const manifestPath = path.join(__dirname, '..', 'ext', 'manifest.json'); - return fs.readFileSync(manifestPath, {encoding: 'utf8'}); -} - -/** */ -function validateManifest() { - const manifestUtil = new ManifestUtil(); - const manifest1 = loadManifestString(); - const manifest2 = ManifestUtil.createManifestString(manifestUtil.getManifest()); - assert.strictEqual(manifest1, manifest2, 'Manifest data does not match.'); -} - - -/** */ -function main() { - validateManifest(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-object-property-accessor.js b/test/test-object-property-accessor.js deleted file mode 100644 index 8826d6a9..00000000 --- a/test/test-object-property-accessor.js +++ /dev/null @@ -1,458 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM({}); -vm.execute('js/general/object-property-accessor.js'); -/** @type {typeof ObjectPropertyAccessor} */ -const ObjectPropertyAccessor2 = vm.getSingle('ObjectPropertyAccessor'); - - -/** - * @returns {import('core').UnknownObject} - */ -function createTestObject() { - return { - 0: null, - value1: { - value2: {}, - value3: [], - value4: null - }, - value5: [ - {}, - [], - null - ] - }; -} - - -/** */ -function testGet1() { - /** @type {[pathArray: (string|number)[], getExpected: (object: import('core').SafeAny) => unknown][]} */ - const data = [ - [[], (object) => object], - [['0'], (object) => object['0']], - [['value1'], (object) => object.value1], - [['value1', 'value2'], (object) => object.value1.value2], - [['value1', 'value3'], (object) => object.value1.value3], - [['value1', 'value4'], (object) => object.value1.value4], - [['value5'], (object) => object.value5], - [['value5', 0], (object) => object.value5[0]], - [['value5', 1], (object) => object.value5[1]], - [['value5', 2], (object) => object.value5[2]] - ]; - - for (const [pathArray, getExpected] of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - const expected = getExpected(object); - - assert.strictEqual(accessor.get(pathArray), expected); - } -} - -/** */ -function testGet2() { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - /** @type {[pathArray: (string|number)[], message: string][]} */ - const data = [ - [[0], 'Invalid path: [0]'], - [['0', 'invalid'], 'Invalid path: ["0"].invalid'], - [['invalid'], 'Invalid path: invalid'], - [['value1', 'invalid'], 'Invalid path: value1.invalid'], - [['value1', 'value2', 'invalid'], 'Invalid path: value1.value2.invalid'], - [['value1', 'value2', 0], 'Invalid path: value1.value2[0]'], - [['value1', 'value3', 'invalid'], 'Invalid path: value1.value3.invalid'], - [['value1', 'value3', 0], 'Invalid path: value1.value3[0]'], - [['value1', 'value4', 'invalid'], 'Invalid path: value1.value4.invalid'], - [['value1', 'value4', 0], 'Invalid path: value1.value4[0]'], - [['value5', 'length'], 'Invalid path: value5.length'], - [['value5', 0, 'invalid'], 'Invalid path: value5[0].invalid'], - [['value5', 0, 0], 'Invalid path: value5[0][0]'], - [['value5', 1, 'invalid'], 'Invalid path: value5[1].invalid'], - [['value5', 1, 0], 'Invalid path: value5[1][0]'], - [['value5', 2, 'invalid'], 'Invalid path: value5[2].invalid'], - [['value5', 2, 0], 'Invalid path: value5[2][0]'], - [['value5', 2, 0, 'invalid'], 'Invalid path: value5[2][0]'], - [['value5', 2.5], 'Invalid index'] - ]; - - for (const [pathArray, message] of data) { - assert.throws(() => accessor.get(pathArray), {message}); - } -} - - -/** */ -function testSet1() { - const testValue = {}; - /** @type {(string|number)[][]} */ - const data = [ - ['0'], - ['value1', 'value2'], - ['value1', 'value3'], - ['value1', 'value4'], - ['value1'], - ['value5', 0], - ['value5', 1], - ['value5', 2], - ['value5'] - ]; - - for (const pathArray of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - accessor.set(pathArray, testValue); - assert.strictEqual(accessor.get(pathArray), testValue); - } -} - -/** */ -function testSet2() { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - const testValue = {}; - /** @type {[pathArray: (string|number)[], message: string][]} */ - const data = [ - [[], 'Invalid path'], - [[0], 'Invalid path: [0]'], - [['0', 'invalid'], 'Invalid path: ["0"].invalid'], - [['value1', 'value2', 0], 'Invalid path: value1.value2[0]'], - [['value1', 'value3', 'invalid'], 'Invalid path: value1.value3.invalid'], - [['value1', 'value4', 'invalid'], 'Invalid path: value1.value4.invalid'], - [['value1', 'value4', 0], 'Invalid path: value1.value4[0]'], - [['value5', 1, 'invalid'], 'Invalid path: value5[1].invalid'], - [['value5', 2, 'invalid'], 'Invalid path: value5[2].invalid'], - [['value5', 2, 0], 'Invalid path: value5[2][0]'], - [['value5', 2, 0, 'invalid'], 'Invalid path: value5[2][0]'], - [['value5', 2.5], 'Invalid index'] - ]; - - for (const [pathArray, message] of data) { - assert.throws(() => accessor.set(pathArray, testValue), {message}); - } -} - - -/** */ -function testDelete1() { - /** - * @param {unknown} object - * @param {string} property - * @returns {boolean} - */ - const hasOwn = (object, property) => Object.prototype.hasOwnProperty.call(object, property); - - /** @type {[pathArray: (string|number)[], validate: (object: import('core').SafeAny) => boolean][]} */ - const data = [ - [['0'], (object) => !hasOwn(object, '0')], - [['value1', 'value2'], (object) => !hasOwn(object.value1, 'value2')], - [['value1', 'value3'], (object) => !hasOwn(object.value1, 'value3')], - [['value1', 'value4'], (object) => !hasOwn(object.value1, 'value4')], - [['value1'], (object) => !hasOwn(object, 'value1')], - [['value5'], (object) => !hasOwn(object, 'value5')] - ]; - - for (const [pathArray, validate] of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - accessor.delete(pathArray); - assert.ok(validate(object)); - } -} - -/** */ -function testDelete2() { - /** @type {[pathArray: (string|number)[], message: string][]} */ - const data = [ - [[], 'Invalid path'], - [[0], 'Invalid path: [0]'], - [['0', 'invalid'], 'Invalid path: ["0"].invalid'], - [['value1', 'value2', 0], 'Invalid path: value1.value2[0]'], - [['value1', 'value3', 'invalid'], 'Invalid path: value1.value3.invalid'], - [['value1', 'value4', 'invalid'], 'Invalid path: value1.value4.invalid'], - [['value1', 'value4', 0], 'Invalid path: value1.value4[0]'], - [['value5', 1, 'invalid'], 'Invalid path: value5[1].invalid'], - [['value5', 2, 'invalid'], 'Invalid path: value5[2].invalid'], - [['value5', 2, 0], 'Invalid path: value5[2][0]'], - [['value5', 2, 0, 'invalid'], 'Invalid path: value5[2][0]'], - [['value5', 2.5], 'Invalid index'], - [['value5', 0], 'Invalid type'], - [['value5', 1], 'Invalid type'], - [['value5', 2], 'Invalid type'] - ]; - - for (const [pathArray, message] of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - assert.throws(() => accessor.delete(pathArray), {message}); - } -} - - -/** */ -function testSwap1() { - /** @type {[pathArray: (string|number)[], compareValues: boolean][]} */ - const data = [ - [['0'], true], - [['value1', 'value2'], true], - [['value1', 'value3'], true], - [['value1', 'value4'], true], - [['value1'], false], - [['value5', 0], true], - [['value5', 1], true], - [['value5', 2], true], - [['value5'], false] - ]; - - for (const [pathArray1, compareValues1] of data) { - for (const [pathArray2, compareValues2] of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - const value1a = accessor.get(pathArray1); - const value2a = accessor.get(pathArray2); - - accessor.swap(pathArray1, pathArray2); - - if (!compareValues1 || !compareValues2) { continue; } - - const value1b = accessor.get(pathArray1); - const value2b = accessor.get(pathArray2); - - assert.deepStrictEqual(value1a, value2b); - assert.deepStrictEqual(value2a, value1b); - } - } -} - -/** */ -function testSwap2() { - /** @type {[pathArray1: (string|number)[], pathArray2: (string|number)[], checkRevert: boolean, message: string][]} */ - const data = [ - [[], [], false, 'Invalid path 1'], - [['0'], [], false, 'Invalid path 2'], - [[], ['0'], false, 'Invalid path 1'], - [[0], ['0'], false, 'Invalid path 1: [0]'], - [['0'], [0], false, 'Invalid path 2: [0]'] - ]; - - for (const [pathArray1, pathArray2, checkRevert, message] of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - let value1a; - let value2a; - if (checkRevert) { - try { - value1a = accessor.get(pathArray1); - value2a = accessor.get(pathArray2); - } catch (e) { - // NOP - } - } - - assert.throws(() => accessor.swap(pathArray1, pathArray2), {message}); - - if (!checkRevert) { continue; } - - const value1b = accessor.get(pathArray1); - const value2b = accessor.get(pathArray2); - - assert.deepStrictEqual(value1a, value1b); - assert.deepStrictEqual(value2a, value2b); - } -} - - -/** */ -function testGetPathString1() { - /** @type {[pathArray: (string|number)[], expected: string][]} */ - const data = [ - [[], ''], - [[0], '[0]'], - [['escape\\'], '["escape\\\\"]'], - [['\'quote\''], '["\'quote\'"]'], - [['"quote"'], '["\\"quote\\""]'], - [['part1', 'part2'], 'part1.part2'], - [['part1', 'part2', 3], 'part1.part2[3]'], - [['part1', 'part2', '3'], 'part1.part2["3"]'], - [['part1', 'part2', '3part'], 'part1.part2["3part"]'], - [['part1', 'part2', '3part', 'part4'], 'part1.part2["3part"].part4'], - [['part1', 'part2', '3part', '4part'], 'part1.part2["3part"]["4part"]'] - ]; - - for (const [pathArray, expected] of data) { - assert.strictEqual(ObjectPropertyAccessor2.getPathString(pathArray), expected); - } -} - -/** */ -function testGetPathString2() { - /** @type {[pathArray: unknown[], message: string][]} */ - const data = [ - [[1.5], 'Invalid index'], - [[null], 'Invalid type: object'] - ]; - - for (const [pathArray, message] of data) { - // @ts-ignore - Throwing is expected - assert.throws(() => ObjectPropertyAccessor2.getPathString(pathArray), {message}); - } -} - - -/** */ -function testGetPathArray1() { - /** @type {[pathString: string, pathArray: (string|number)[]][]} */ - const data = [ - ['', []], - ['[0]', [0]], - ['["escape\\\\"]', ['escape\\']], - ['["\'quote\'"]', ['\'quote\'']], - ['["\\"quote\\""]', ['"quote"']], - ['part1.part2', ['part1', 'part2']], - ['part1.part2[3]', ['part1', 'part2', 3]], - ['part1.part2["3"]', ['part1', 'part2', '3']], - ['part1.part2[\'3\']', ['part1', 'part2', '3']], - ['part1.part2["3part"]', ['part1', 'part2', '3part']], - ['part1.part2[\'3part\']', ['part1', 'part2', '3part']], - ['part1.part2["3part"].part4', ['part1', 'part2', '3part', 'part4']], - ['part1.part2[\'3part\'].part4', ['part1', 'part2', '3part', 'part4']], - ['part1.part2["3part"]["4part"]', ['part1', 'part2', '3part', '4part']], - ['part1.part2[\'3part\'][\'4part\']', ['part1', 'part2', '3part', '4part']] - ]; - - for (const [pathString, expected] of data) { - // @ts-ignore - vm.assert.deepStrictEqual(ObjectPropertyAccessor2.getPathArray(pathString), expected); - } -} - -/** */ -function testGetPathArray2() { - /** @type {[pathString: string, message: string][]} */ - const data = [ - ['?', 'Unexpected character: ?'], - ['.', 'Unexpected character: .'], - ['0', 'Unexpected character: 0'], - ['part1.[0]', 'Unexpected character: ['], - ['part1?', 'Unexpected character: ?'], - ['[part1]', 'Unexpected character: p'], - ['[0a]', 'Unexpected character: a'], - ['["part1"x]', 'Unexpected character: x'], - ['[\'part1\'x]', 'Unexpected character: x'], - ['["part1"]x', 'Unexpected character: x'], - ['[\'part1\']x', 'Unexpected character: x'], - ['part1..part2', 'Unexpected character: .'], - - ['[', 'Path not terminated correctly'], - ['part1.', 'Path not terminated correctly'], - ['part1[', 'Path not terminated correctly'], - ['part1["', 'Path not terminated correctly'], - ['part1[\'', 'Path not terminated correctly'], - ['part1[""', 'Path not terminated correctly'], - ['part1[\'\'', 'Path not terminated correctly'], - ['part1[0', 'Path not terminated correctly'], - ['part1[0].', 'Path not terminated correctly'] - ]; - - for (const [pathString, message] of data) { - assert.throws(() => ObjectPropertyAccessor2.getPathArray(pathString), {message}); - } -} - - -/** */ -function testHasProperty() { - /** @type {[object: unknown, property: unknown, expected: boolean][]} */ - const data = [ - [{}, 'invalid', false], - [{}, 0, false], - [{valid: 0}, 'valid', true], - [{null: 0}, null, false], - [[], 'invalid', false], - [[], 0, false], - [[0], 0, true], - [[0], null, false], - ['string', 0, false], - ['string', 'length', false], - ['string', null, false] - ]; - - for (const [object, property, expected] of data) { - // @ts-ignore - assert.strictEqual(ObjectPropertyAccessor2.hasProperty(object, property), expected); - } -} - -/** */ -function testIsValidPropertyType() { - /** @type {[object: unknown, property: unknown, expected: boolean][]} */ - const data = [ - [{}, 'invalid', true], - [{}, 0, false], - [{valid: 0}, 'valid', true], - [{null: 0}, null, false], - [[], 'invalid', false], - [[], 0, true], - [[0], 0, true], - [[0], null, false], - ['string', 0, false], - ['string', 'length', false], - ['string', null, false] - ]; - - for (const [object, property, expected] of data) { - // @ts-ignore - assert.strictEqual(ObjectPropertyAccessor2.isValidPropertyType(object, property), expected); - } -} - - -/** */ -function main() { - testGet1(); - testGet2(); - testSet1(); - testSet2(); - testDelete1(); - testDelete2(); - testSwap1(); - testSwap2(); - testGetPathString1(); - testGetPathString2(); - testGetPathArray1(); - testGetPathArray2(); - testHasProperty(); - testIsValidPropertyType(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-profile-conditions-util.js b/test/test-profile-conditions-util.js deleted file mode 100644 index 2e6f751f..00000000 --- a/test/test-profile-conditions-util.js +++ /dev/null @@ -1,1136 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - - -const vm = new VM({}); -vm.execute([ - 'js/core.js', - 'js/core/extension-error.js', - 'js/general/cache-map.js', - 'js/data/json-schema.js', - 'js/background/profile-conditions-util.js' -]); -/** @type {typeof ProfileConditionsUtil} */ -const ProfileConditionsUtil2 = vm.getSingle('ProfileConditionsUtil'); - - -/** */ -function testNormalizeContext() { - /** @type {{context: import('settings').OptionsContext, expected: import('profile-conditions-util').NormalizedOptionsContext}[]} */ - const data = [ - // Empty - { - context: {index: 0}, - expected: {index: 0, flags: []} - }, - - // Domain normalization - { - context: {depth: 0, url: ''}, - expected: {depth: 0, url: '', flags: []} - }, - { - context: {depth: 0, url: 'http://example.com/'}, - expected: {depth: 0, url: 'http://example.com/', domain: 'example.com', flags: []} - }, - { - context: {depth: 0, url: 'http://example.com:1234/'}, - expected: {depth: 0, url: 'http://example.com:1234/', domain: 'example.com', flags: []} - }, - { - context: {depth: 0, url: 'http://user@example.com:1234/'}, - expected: {depth: 0, url: 'http://user@example.com:1234/', domain: 'example.com', flags: []} - } - ]; - - for (const {context, expected} of data) { - const profileConditionsUtil = new ProfileConditionsUtil2(); - const actual = profileConditionsUtil.normalizeContext(context); - vm.assert.deepStrictEqual(actual, expected); - } -} - -/** */ -function testSchemas() { - /** @type {{conditionGroups: import('settings').ProfileConditionGroup[], expectedSchema?: import('json-schema').Schema, inputs?: {expected: boolean, context: import('settings').OptionsContext}[]}[]} */ - const data = [ - // Empty - { - conditionGroups: [], - expectedSchema: {}, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - {conditions: []} - ], - expectedSchema: {}, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - {conditions: []}, - {conditions: []} - ], - expectedSchema: {}, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}} - ] - }, - - // popupLevel tests - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'equal', - value: '0' - } - ] - } - ], - expectedSchema: { - properties: { - depth: {const: 0} - }, - required: ['depth'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: false, context: {depth: 1, url: 'http://example.com/'}}, - {expected: false, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'notEqual', - value: '0' - } - ] - } - ], - expectedSchema: { - not: { - anyOf: [ - { - properties: { - depth: {const: 0} - }, - required: ['depth'] - } - ] - } - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'lessThan', - value: '0' - } - ] - } - ], - expectedSchema: { - properties: { - depth: { - type: 'number', - exclusiveMaximum: 0 - } - }, - required: ['depth'] - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/'}}, - {expected: false, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'greaterThan', - value: '0' - } - ] - } - ], - expectedSchema: { - properties: { - depth: { - type: 'number', - exclusiveMinimum: 0 - } - }, - required: ['depth'] - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: false, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'lessThanOrEqual', - value: '0' - } - ] - } - ], - expectedSchema: { - properties: { - depth: { - type: 'number', - maximum: 0 - } - }, - required: ['depth'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: false, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'greaterThanOrEqual', - value: '0' - } - ] - } - ], - expectedSchema: { - properties: { - depth: { - type: 'number', - minimum: 0 - } - }, - required: ['depth'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: false, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - - // url tests - { - conditionGroups: [ - { - conditions: [ - { - type: 'url', - operator: 'matchDomain', - value: 'example.com' - } - ] - } - ], - expectedSchema: { - properties: { - domain: { - oneOf: [ - {const: 'example.com'} - ] - } - }, - required: ['domain'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: false, context: {depth: 0, url: 'http://example1.com/'}}, - {expected: false, context: {depth: 0, url: 'http://example2.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example.com:1234/'}}, - {expected: true, context: {depth: 0, url: 'http://user@example.com:1234/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'url', - operator: 'matchDomain', - value: 'example.com, example1.com, example2.com' - } - ] - } - ], - expectedSchema: { - properties: { - domain: { - oneOf: [ - {const: 'example.com'}, - {const: 'example1.com'}, - {const: 'example2.com'} - ] - } - }, - required: ['domain'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example1.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example2.com/'}}, - {expected: false, context: {depth: 0, url: 'http://example3.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example.com:1234/'}}, - {expected: true, context: {depth: 0, url: 'http://user@example.com:1234/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'url', - operator: 'matchRegExp', - value: '^http://example\\d?\\.com/[\\w\\W]*$' - } - ] - } - ], - expectedSchema: { - properties: { - url: { - type: 'string', - pattern: '^http://example\\d?\\.com/[\\w\\W]*$', - patternFlags: 'i' - } - }, - required: ['url'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example1.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example2.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example3.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example.com/example'}}, - {expected: false, context: {depth: 0, url: 'http://example.com:1234/'}}, - {expected: false, context: {depth: 0, url: 'http://user@example.com:1234/'}}, - {expected: false, context: {depth: 0, url: 'http://example-1.com/'}} - ] - }, - - // modifierKeys tests - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'are', - value: '' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array', - maxItems: 0, - minItems: 0 - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'are', - value: 'alt, shift' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array', - maxItems: 2, - minItems: 2, - allOf: [ - {contains: {const: 'alt'}}, - {contains: {const: 'shift'}} - ] - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'areNot', - value: '' - } - ] - } - ], - expectedSchema: { - not: { - anyOf: [ - { - properties: { - modifierKeys: { - type: 'array', - maxItems: 0, - minItems: 0 - } - }, - required: ['modifierKeys'] - } - ] - } - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'areNot', - value: 'alt, shift' - } - ] - } - ], - expectedSchema: { - not: { - anyOf: [ - { - properties: { - modifierKeys: { - type: 'array', - maxItems: 2, - minItems: 2, - allOf: [ - {contains: {const: 'alt'}}, - {contains: {const: 'shift'}} - ] - } - }, - required: ['modifierKeys'] - } - ] - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'include', - value: '' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array', - minItems: 0 - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'include', - value: 'alt, shift' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array', - minItems: 2, - allOf: [ - {contains: {const: 'alt'}}, - {contains: {const: 'shift'}} - ] - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'notInclude', - value: '' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array' - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'notInclude', - value: 'alt, shift' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array', - not: { - anyOf: [ - {contains: {const: 'alt'}}, - {contains: {const: 'shift'}} - ] - } - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - - // flags tests - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'are', - value: '' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array', - maxItems: 0, - minItems: 0 - } - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: ''}}, - {expected: true, context: {depth: 0, url: '', flags: []}}, - {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'are', - value: 'clipboard, test2' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array', - maxItems: 2, - minItems: 2, - allOf: [ - {contains: {const: 'clipboard'}}, - {contains: {const: 'test2'}} - ] - } - } - }, - inputs: [ - {expected: false, context: {depth: 0, url: ''}}, - {expected: false, context: {depth: 0, url: '', flags: []}}, - {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'areNot', - value: '' - } - ] - } - ], - expectedSchema: { - not: { - anyOf: [ - { - required: ['flags'], - properties: { - flags: { - type: 'array', - maxItems: 0, - minItems: 0 - } - } - } - ] - } - }, - inputs: [ - {expected: false, context: {depth: 0, url: ''}}, - {expected: false, context: {depth: 0, url: '', flags: []}}, - {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'areNot', - value: 'clipboard, test2' - } - ] - } - ], - expectedSchema: { - not: { - anyOf: [ - { - required: ['flags'], - properties: { - flags: { - type: 'array', - maxItems: 2, - minItems: 2, - allOf: [ - {contains: {const: 'clipboard'}}, - {contains: {const: 'test2'}} - ] - } - } - } - ] - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: ''}}, - {expected: true, context: {depth: 0, url: '', flags: []}}, - {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'include', - value: '' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array', - minItems: 0 - } - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: ''}}, - {expected: true, context: {depth: 0, url: '', flags: []}}, - {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'include', - value: 'clipboard, test2' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array', - minItems: 2, - allOf: [ - {contains: {const: 'clipboard'}}, - {contains: {const: 'test2'}} - ] - } - } - }, - inputs: [ - {expected: false, context: {depth: 0, url: ''}}, - {expected: false, context: {depth: 0, url: '', flags: []}}, - {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'notInclude', - value: '' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array' - } - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: ''}}, - {expected: true, context: {depth: 0, url: '', flags: []}}, - {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'notInclude', - value: 'clipboard, test2' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array', - not: { - anyOf: [ - {contains: {const: 'clipboard'}}, - {contains: {const: 'test2'}} - ] - } - } - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: ''}}, - {expected: true, context: {depth: 0, url: '', flags: []}}, - {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - - // Multiple conditions tests - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'greaterThan', - value: '0' - }, - { - type: 'popupLevel', - operator: 'lessThan', - value: '3' - } - ] - } - ], - expectedSchema: { - allOf: [ - { - properties: { - depth: { - type: 'number', - exclusiveMinimum: 0 - } - }, - required: ['depth'] - }, - { - properties: { - depth: { - type: 'number', - exclusiveMaximum: 3 - } - }, - required: ['depth'] - } - ] - }, - inputs: [ - {expected: false, context: {depth: -2, url: 'http://example.com/'}}, - {expected: false, context: {depth: -1, url: 'http://example.com/'}}, - {expected: false, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: 2, url: 'http://example.com/'}}, - {expected: false, context: {depth: 3, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'greaterThan', - value: '0' - }, - { - type: 'popupLevel', - operator: 'lessThan', - value: '3' - } - ] - }, - { - conditions: [ - { - type: 'popupLevel', - operator: 'equal', - value: '0' - } - ] - } - ], - expectedSchema: { - anyOf: [ - { - allOf: [ - { - properties: { - depth: { - type: 'number', - exclusiveMinimum: 0 - } - }, - required: ['depth'] - }, - { - properties: { - depth: { - type: 'number', - exclusiveMaximum: 3 - } - }, - required: ['depth'] - } - ] - }, - { - properties: { - depth: {const: 0} - }, - required: ['depth'] - } - ] - }, - inputs: [ - {expected: false, context: {depth: -2, url: 'http://example.com/'}}, - {expected: false, context: {depth: -1, url: 'http://example.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: 2, url: 'http://example.com/'}}, - {expected: false, context: {depth: 3, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'greaterThan', - value: '0' - }, - { - type: 'popupLevel', - operator: 'lessThan', - value: '3' - } - ] - }, - { - conditions: [ - { - type: 'popupLevel', - operator: 'lessThanOrEqual', - value: '0' - }, - { - type: 'popupLevel', - operator: 'greaterThanOrEqual', - value: '-1' - } - ] - } - ], - expectedSchema: { - anyOf: [ - { - allOf: [ - { - properties: { - depth: { - type: 'number', - exclusiveMinimum: 0 - } - }, - required: ['depth'] - }, - { - properties: { - depth: { - type: 'number', - exclusiveMaximum: 3 - } - }, - required: ['depth'] - } - ] - }, - { - allOf: [ - { - properties: { - depth: { - type: 'number', - maximum: 0 - } - }, - required: ['depth'] - }, - { - properties: { - depth: { - type: 'number', - minimum: -1 - } - }, - required: ['depth'] - } - ] - } - ] - }, - inputs: [ - {expected: false, context: {depth: -2, url: 'http://example.com/'}}, - {expected: true, context: {depth: -1, url: 'http://example.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: 2, url: 'http://example.com/'}}, - {expected: false, context: {depth: 3, url: 'http://example.com/'}} - ] - } - ]; - - for (const {conditionGroups, expectedSchema, inputs} of data) { - const profileConditionsUtil = new ProfileConditionsUtil2(); - const schema = profileConditionsUtil.createSchema(conditionGroups); - if (typeof expectedSchema !== 'undefined') { - vm.assert.deepStrictEqual(schema.schema, expectedSchema); - } - if (Array.isArray(inputs)) { - for (const {expected, context} of inputs) { - const normalizedContext = profileConditionsUtil.normalizeContext(context); - const actual = schema.isValid(normalizedContext); - assert.strictEqual(actual, expected); - } - } - } -} - - -/** */ -function main() { - testNormalizeContext(); - testSchemas(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-text-source-map.js b/test/test-text-source-map.js deleted file mode 100644 index 834a3d07..00000000 --- a/test/test-text-source-map.js +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM(); -vm.execute(['js/general/text-source-map.js']); -/** @type {typeof TextSourceMap} */ -const TextSourceMap2 = vm.getSingle('TextSourceMap'); - - -/** */ -function testSource() { - const data = [ - ['source1'], - ['source2'], - ['source3'] - ]; - - for (const [source] of data) { - const sourceMap = new TextSourceMap2(source); - assert.strictEqual(source, sourceMap.source); - } -} - -/** */ -function testEquals() { - /** @type {[args1: [source1: string, mapping1: ?(number[])], args2: [source2: string, mapping2: ?(number[])], expectedEquals: boolean][]} */ - const data = [ - [['source1', null], ['source1', null], true], - [['source2', null], ['source2', null], true], - [['source3', null], ['source3', null], true], - - [['source1', [1, 1, 1, 1, 1, 1, 1]], ['source1', null], true], - [['source2', [1, 1, 1, 1, 1, 1, 1]], ['source2', null], true], - [['source3', [1, 1, 1, 1, 1, 1, 1]], ['source3', null], true], - - [['source1', null], ['source1', [1, 1, 1, 1, 1, 1, 1]], true], - [['source2', null], ['source2', [1, 1, 1, 1, 1, 1, 1]], true], - [['source3', null], ['source3', [1, 1, 1, 1, 1, 1, 1]], true], - - [['source1', [1, 1, 1, 1, 1, 1, 1]], ['source1', [1, 1, 1, 1, 1, 1, 1]], true], - [['source2', [1, 1, 1, 1, 1, 1, 1]], ['source2', [1, 1, 1, 1, 1, 1, 1]], true], - [['source3', [1, 1, 1, 1, 1, 1, 1]], ['source3', [1, 1, 1, 1, 1, 1, 1]], true], - - [['source1', [1, 2, 1, 3]], ['source1', [1, 2, 1, 3]], true], - [['source2', [1, 2, 1, 3]], ['source2', [1, 2, 1, 3]], true], - [['source3', [1, 2, 1, 3]], ['source3', [1, 2, 1, 3]], true], - - [['source1', [1, 3, 1, 2]], ['source1', [1, 2, 1, 3]], false], - [['source2', [1, 3, 1, 2]], ['source2', [1, 2, 1, 3]], false], - [['source3', [1, 3, 1, 2]], ['source3', [1, 2, 1, 3]], false], - - [['source1', [1, 1, 1, 1, 1, 1, 1]], ['source4', [1, 1, 1, 1, 1, 1, 1]], false], - [['source2', [1, 1, 1, 1, 1, 1, 1]], ['source5', [1, 1, 1, 1, 1, 1, 1]], false], - [['source3', [1, 1, 1, 1, 1, 1, 1]], ['source6', [1, 1, 1, 1, 1, 1, 1]], false] - ]; - - for (const [[source1, mapping1], [source2, mapping2], expectedEquals] of data) { - const sourceMap1 = new TextSourceMap2(source1, mapping1); - const sourceMap2 = new TextSourceMap2(source2, mapping2); - assert.ok(sourceMap1.equals(sourceMap1)); - assert.ok(sourceMap2.equals(sourceMap2)); - assert.strictEqual(sourceMap1.equals(sourceMap2), expectedEquals); - } -} - -/** */ -function testGetSourceLength() { - /** @type {[args: [source: string, mapping: number[]], finalLength: number, expectedValue: number][]} */ - const data = [ - [['source', [1, 1, 1, 1, 1, 1]], 1, 1], - [['source', [1, 1, 1, 1, 1, 1]], 2, 2], - [['source', [1, 1, 1, 1, 1, 1]], 3, 3], - [['source', [1, 1, 1, 1, 1, 1]], 4, 4], - [['source', [1, 1, 1, 1, 1, 1]], 5, 5], - [['source', [1, 1, 1, 1, 1, 1]], 6, 6], - - [['source', [2, 2, 2]], 1, 2], - [['source', [2, 2, 2]], 2, 4], - [['source', [2, 2, 2]], 3, 6], - - [['source', [3, 3]], 1, 3], - [['source', [3, 3]], 2, 6], - - [['source', [6, 6]], 1, 6] - ]; - - for (const [[source, mapping], finalLength, expectedValue] of data) { - const sourceMap = new TextSourceMap2(source, mapping); - assert.strictEqual(sourceMap.getSourceLength(finalLength), expectedValue); - } -} - -/** */ -function testCombineInsert() { - /** @type {[args: [source: string, mapping: ?(number[])], expectedArgs: [expectedSource: string, expectedMapping: ?(number[])], operations: [operation: string, arg1: number, arg2: number][]][]} */ - const data = [ - // No operations - [ - ['source', null], - ['source', [1, 1, 1, 1, 1, 1]], - [] - ], - - // Combine - [ - ['source', null], - ['source', [3, 1, 1, 1]], - [ - ['combine', 0, 2] - ] - ], - [ - ['source', null], - ['source', [1, 1, 1, 3]], - [ - ['combine', 3, 2] - ] - ], - [ - ['source', null], - ['source', [3, 3]], - [ - ['combine', 0, 2], - ['combine', 1, 2] - ] - ], - [ - ['source', null], - ['source', [3, 3]], - [ - ['combine', 3, 2], - ['combine', 0, 2] - ] - ], - - // Insert - [ - ['source', null], - ['source', [0, 1, 1, 1, 1, 1, 1]], - [ - ['insert', 0, 0] - ] - ], - [ - ['source', null], - ['source', [1, 1, 1, 1, 1, 1, 0]], - [ - ['insert', 6, 0] - ] - ], - [ - ['source', null], - ['source', [0, 1, 1, 1, 1, 1, 1, 0]], - [ - ['insert', 0, 0], - ['insert', 7, 0] - ] - ], - [ - ['source', null], - ['source', [0, 1, 1, 1, 1, 1, 1, 0]], - [ - ['insert', 6, 0], - ['insert', 0, 0] - ] - ], - - // Mixed - [ - ['source', null], - ['source', [3, 0, 3]], - [ - ['combine', 0, 2], - ['insert', 1, 0], - ['combine', 2, 2] - ] - ], - [ - ['source', null], - ['source', [3, 0, 3]], - [ - ['combine', 0, 2], - ['combine', 1, 2], - ['insert', 1, 0] - ] - ], - [ - ['source', null], - ['source', [3, 0, 3]], - [ - ['insert', 3, 0], - ['combine', 0, 2], - ['combine', 2, 2] - ] - ] - ]; - - for (const [[source, mapping], [expectedSource, expectedMapping], operations] of data) { - const sourceMap = new TextSourceMap2(source, mapping); - const expectedSourceMap = new TextSourceMap2(expectedSource, expectedMapping); - for (const [operation, ...args] of operations) { - switch (operation) { - case 'combine': - sourceMap.combine(...args); - break; - case 'insert': - sourceMap.insert(...args); - break; - } - } - assert.ok(sourceMap.equals(expectedSourceMap)); - } -} - - -/** */ -function main() { - testSource(); - testEquals(); - testGetSourceLength(); - testCombineInsert(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-translator.js b/test/test-translator.js deleted file mode 100644 index 0c84e0be..00000000 --- a/test/test-translator.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {TranslatorVM} = require('../dev/translator-vm'); - - -/** - * @template T - * @param {T} value - * @returns {T} - */ -function clone(value) { - return JSON.parse(JSON.stringify(value)); -} - - -/** */ -async function main() { - const write = (process.argv[2] === '--write'); - - const translatorVM = new TranslatorVM(); - const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', 'valid-dictionary1'); - await translatorVM.prepare(dictionaryDirectory, 'Test Dictionary 2'); - - const testInputsFilePath = path.join(__dirname, 'data', 'translator-test-inputs.json'); - const {optionsPresets, tests} = JSON.parse(fs.readFileSync(testInputsFilePath, {encoding: 'utf8'})); - - const testResults1FilePath = path.join(__dirname, 'data', 'translator-test-results.json'); - const expectedResults1 = JSON.parse(fs.readFileSync(testResults1FilePath, {encoding: 'utf8'})); - const actualResults1 = []; - - const testResults2FilePath = path.join(__dirname, 'data', 'translator-test-results-note-data1.json'); - const expectedResults2 = JSON.parse(fs.readFileSync(testResults2FilePath, {encoding: 'utf8'})); - const actualResults2 = []; - - for (let i = 0, ii = tests.length; i < ii; ++i) { - const test = tests[i]; - const expected1 = expectedResults1[i]; - const expected2 = expectedResults2[i]; - switch (test.func) { - case 'findTerms': - { - const {name, mode, text} = test; - /** @type {import('translation').FindTermsOptions} */ - const options = translatorVM.buildOptions(optionsPresets, test.options); - const {dictionaryEntries, originalTextLength} = clone(await translatorVM.translator.findTerms(mode, text, options)); - const noteDataList = mode !== 'simple' ? clone(dictionaryEntries.map((dictionaryEntry) => translatorVM.createTestAnkiNoteData(clone(dictionaryEntry), mode))) : null; - actualResults1.push({name, originalTextLength, dictionaryEntries}); - actualResults2.push({name, noteDataList}); - if (!write) { - assert.deepStrictEqual(originalTextLength, expected1.originalTextLength); - assert.deepStrictEqual(dictionaryEntries, expected1.dictionaryEntries); - assert.deepStrictEqual(noteDataList, expected2.noteDataList); - } - } - break; - case 'findKanji': - { - const {name, text} = test; - /** @type {import('translation').FindKanjiOptions} */ - const options = translatorVM.buildOptions(optionsPresets, test.options); - const dictionaryEntries = clone(await translatorVM.translator.findKanji(text, options)); - const noteDataList = clone(dictionaryEntries.map((dictionaryEntry) => translatorVM.createTestAnkiNoteData(clone(dictionaryEntry), 'split'))); - actualResults1.push({name, dictionaryEntries}); - actualResults2.push({name, noteDataList}); - if (!write) { - assert.deepStrictEqual(dictionaryEntries, expected1.dictionaryEntries); - assert.deepStrictEqual(noteDataList, expected2.noteDataList); - } - } - break; - } - } - - if (write) { - // Use 2 indent instead of 4 to save a bit of file size - fs.writeFileSync(testResults1FilePath, JSON.stringify(actualResults1, null, 2), {encoding: 'utf8'}); - fs.writeFileSync(testResults2FilePath, JSON.stringify(actualResults2, null, 2), {encoding: 'utf8'}); - } -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-workers.js b/test/test-workers.js deleted file mode 100644 index 3de7ac48..00000000 --- a/test/test-workers.js +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 Yomichan Authors - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see <https://www.gnu.org/licenses/>. - */ - -const fs = require('fs'); -const path = require('path'); -const {JSDOM} = require('jsdom'); -const {VM} = require('../dev/vm'); -const assert = require('assert'); - - -class StubClass { - /** */ - prepare() { - // NOP - } -} - - -/** - * @returns {import('core').SafeAny} - */ -function loadEslint() { - return JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.eslintrc.json'), {encoding: 'utf8'})); -} - -/** - * @param {string[]} scriptPaths - * @returns {string[]} - */ -function filterScriptPaths(scriptPaths) { - const extDirName = 'ext'; - return scriptPaths.filter((src) => !src.startsWith('/lib/')).map((src) => `${extDirName}${src}`); -} - -/** - * @param {string} fileName - * @returns {string[]} - */ -function getAllHtmlScriptPaths(fileName) { - const domSource = fs.readFileSync(fileName, {encoding: 'utf8'}); - const dom = new JSDOM(domSource); - const {window} = dom; - const {document} = window; - try { - const scripts = document.querySelectorAll('script'); - return [...scripts].map(({src}) => src); - } finally { - window.close(); - } -} - -/** - * @param {string[]} scripts - */ -function convertBackgroundScriptsToServiceWorkerScripts(scripts) { - // Use parse5-based SimpleDOMParser - scripts.splice(0, 0, '/lib/parse5.js'); - const index = scripts.indexOf('/js/dom/native-simple-dom-parser.js'); - assert.ok(index >= 0); - scripts[index] = '/js/dom/simple-dom-parser.js'; -} - -/** - * @param {string} scriptPath - * @param {import('core').UnknownObject} fields - * @returns {string[]} - */ -function getImportedScripts(scriptPath, fields) { - /** @type {string[]} */ - const importedScripts = []; - - /** - * @param {...string} scripts - */ - const importScripts = (...scripts) => { - importedScripts.push(...scripts); - }; - - const vm = new VM(Object.assign({importScripts}, fields)); - vm.context.self = vm.context; - vm.execute([scriptPath]); - - return importedScripts; -} - -/** */ -function testServiceWorker() { - // Verify that sw.js scripts match background.html scripts - const extDir = path.join(__dirname, '..', 'ext'); - const scripts = getAllHtmlScriptPaths(path.join(extDir, 'background.html')); - convertBackgroundScriptsToServiceWorkerScripts(scripts); - const importedScripts = getImportedScripts('sw.js', {}); - assert.deepStrictEqual(scripts, importedScripts); - - // Verify that eslint config lists files correctly - const expectedSwRulesFiles = filterScriptPaths(scripts); - const eslintConfig = loadEslint(); - const swRules = /** @type {import('core').SafeAny[]} */ (eslintConfig.overrides).find((item) => ( - typeof item.env === 'object' && - item.env !== null && - item.env.serviceworker === true - )); - assert.ok(typeof swRules !== 'undefined'); - assert.ok(Array.isArray(swRules.files)); - assert.deepStrictEqual(swRules.files, expectedSwRulesFiles); -} - -/** */ -function testWorkers() { - testWorker( - 'js/language/dictionary-worker-main.js', - {DictionaryWorkerHandler: StubClass} - ); -} - -/** - * @param {string} scriptPath - * @param {import('core').UnknownObject} fields - */ -function testWorker(scriptPath, fields) { - // Get script paths - const scripts = getImportedScripts(scriptPath, fields); - - // Verify that eslint config lists files correctly - const expectedRulesFiles = filterScriptPaths(scripts); - const expectedRulesFilesSet = new Set(expectedRulesFiles); - const eslintConfig = loadEslint(); - const rules = /** @type {import('core').SafeAny[]} */ (eslintConfig.overrides).find((item) => ( - typeof item.env === 'object' && - item.env !== null && - item.env.worker === true - )); - assert.ok(typeof rules !== 'undefined'); - assert.ok(Array.isArray(rules.files)); - assert.deepStrictEqual(/** @type {import('core').SafeAny[]} */ (rules.files).filter((v) => expectedRulesFilesSet.has(v)), expectedRulesFiles); -} - - -/** */ -function main() { - try { - testServiceWorker(); - testWorkers(); - } catch (e) { - console.error(e); - process.exit(-1); - return; - } - process.exit(0); -} - - -if (require.main === module) { main(); } |