diff options
| author | Darius Jahandarie <djahandarie@gmail.com> | 2023-11-08 03:11:35 +0900 | 
|---|---|---|
| committer | Darius Jahandarie <djahandarie@gmail.com> | 2023-11-08 03:23:17 +0900 | 
| commit | 0f4d36938fd0d844f548aa5a7f7e7842df8dfb41 (patch) | |
| tree | 5b6be3620a557d0b9177047003f6d742d9d2a32d /test | |
| parent | ef79eab44bfd000792c610b968b5ceefd41e76a0 (diff) | |
Switch to vitest for ESM support; other fixes
Diffstat (limited to 'test')
34 files changed, 5993 insertions, 6453 deletions
diff --git a/test/anki-note-builder.test.js b/test/anki-note-builder.test.js new file mode 100644 index 00000000..90bb3cbe --- /dev/null +++ b/test/anki-note-builder.test.js @@ -0,0 +1,224 @@ +/* + * 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/>. + */ + +import 'fake-indexeddb/auto'; +import fs from 'fs'; +import {fileURLToPath} from 'node:url'; +import path from 'path'; +import url from 'url'; +import {describe, test, vi} from 'vitest'; +import {TranslatorVM} from '../dev/translator-vm.js'; +import {AnkiNoteBuilder} from '../ext/js/data/anki-note-builder.js'; +import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; + +vi.stubGlobal('fetch', async (url2) => { +    const extDir = path.join(__dirname, '..', 'ext'); +    let filePath; +    try { +        filePath = url.fileURLToPath(url2); +    } catch (e) { +        filePath = path.resolve(extDir, url2.replace(/^[/\\]/, '')); +    } +    await Promise.resolve(); +    const content = fs.readFileSync(filePath, {encoding: null}); +    return { +        ok: true, +        status: 200, +        statusText: 'OK', +        text: async () => Promise.resolve(content.toString('utf8')), +        json: async () => Promise.resolve(JSON.parse(content.toString('utf8'))) +    }; +}); +vi.mock('../ext/js/templates/template-renderer-proxy.js'); + +const dirname = path.dirname(fileURLToPath(import.meta.url)); + +async function createVM() { +    const dictionaryDirectory = path.join(dirname, 'data', 'dictionaries', 'valid-dictionary1'); +    const vm = new TranslatorVM(); + +    await vm.prepare(dictionaryDirectory, 'Test Dictionary 2'); + +    return vm; +} + +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 []; +    } +} + +async function getRenderResults(dictionaryEntries, type, mode, template, expect) { +    const markers = getFieldMarkers(type); +    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' +        }; +        const {note: {fields: noteFields}, errors} = await ankiNoteBuilder.createNote({ +            dictionaryEntry, +            mode: null, +            context, +            template, +            deckName: 'deckName', +            modelName: 'modelName', +            fields, +            tags: ['yomichan'], +            checkForDuplicates: true, +            duplicateScope: 'collection', +            duplicateScopeCheckAllModels: false, +            resultOutputMode: mode, +            glossaryLayoutMode: 'default', +            compactTags: false +        }); +        for (const error of errors) { +            console.error(error); +        } +        expect(errors.length).toStrictEqual(0); +        results.push(noteFields); +    } + +    return results; +} + + +async function main() { +    const vm = 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'}); + +    describe.concurrent('AnkiNoteBuilder', () => { +        for (let i = 0, ii = tests.length; i < ii; ++i) { +            const t = tests[i]; +            test(`${t.name}`, async ({expect}) => { +                const expected1 = expectedResults1[i]; +                switch (t.func) { +                    case 'findTerms': +                        { +                            const {name, mode, text} = t; +                            const options = vm.buildOptions(optionsPresets, t.options); +                            const {dictionaryEntries} = structuredClone(await vm.translator.findTerms(mode, text, options)); +                            const results = mode !== 'simple' ? structuredClone(await getRenderResults(dictionaryEntries, 'terms', mode, template, expect)) : null; +                            actualResults1.push({name, results}); +                            expect(results).toStrictEqual(expected1.results); +                        } +                        break; +                    case 'findKanji': +                        { +                            const {name, text} = t; +                            const options = vm.buildOptions(optionsPresets, t.options); +                            const dictionaryEntries = structuredClone(await vm.translator.findKanji(text, options)); +                            const results = structuredClone(await getRenderResults(dictionaryEntries, 'kanji', null, template, expect)); +                            actualResults1.push({name, results}); +                            expect(results).toStrictEqual(expected1.results); +                        } +                        break; +                } +            }); +        } +    }); +} +await main(); diff --git a/test/cache-map.test.js b/test/cache-map.test.js new file mode 100644 index 00000000..9d10a719 --- /dev/null +++ b/test/cache-map.test.js @@ -0,0 +1,128 @@ +/* + * 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/>. + */ + +import {expect, test} from 'vitest'; +import {CacheMap} from '../ext/js/general/cache-map.js'; + +function testConstructor() { +    test('constructor', () => { +        const data = [ +            [false, () => new CacheMap(0)], +            [false, () => new CacheMap(1)], +            [false, () => new CacheMap(Number.MAX_VALUE)], +            [true,  () => new CacheMap(-1)], +            [true,  () => new CacheMap(1.5)], +            [true,  () => new CacheMap(Number.NaN)], +            [true,  () => new CacheMap(Number.POSITIVE_INFINITY)], +            [true,  () => new CacheMap('a')] +        ]; + +        for (const [throws, create] of data) { +            if (throws) { +                expect(create).toThrowError(); +            } else { +                expect(create).not.toThrowError(); +            } +        } +    }); +} + +function testApi() { +    test('api', () => { +        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 CacheMap(maxSize); +            expect(cache.maxSize).toStrictEqual(maxSize); +            for (const call of calls) { +                const {func, args} = call; +                let returnValue; +                switch (func) { +                    case 'get': returnValue = cache.get(...args); break; +                    case 'set': returnValue = cache.set(...args); break; +                    case 'has': returnValue = cache.has(...args); break; +                    case 'clear': returnValue = cache.clear(...args); break; +                } +                if (Object.prototype.hasOwnProperty.call(call, 'returnValue')) { +                    const {returnValue: expectedReturnValue} = call; +                    expect(returnValue).toStrictEqual(expectedReturnValue); +                } +            } +            expect(cache.size).toStrictEqual(expectedSize); +        } +    }); +} + + +function main() { +    testConstructor(); +    testApi(); +} + + +main(); diff --git a/test/core.test.js b/test/core.test.js new file mode 100644 index 00000000..203460f4 --- /dev/null +++ b/test/core.test.js @@ -0,0 +1,288 @@ +/* + * 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/>. + */ + +import {describe, expect, test} from 'vitest'; +import {DynamicProperty, deepEqual} from '../ext/js/core.js'; + +function testDynamicProperty() { +    test('DynamicProperty', () => { +        const data = [ +            { +                initialValue: 0, +                operations: [ +                    { +                        operation: null, +                        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 DynamicProperty(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; +                } +                expect(eventOccurred).toStrictEqual(expeectedEventOccurred); +                expect(property.defaultValue).toStrictEqual(expectedDefaultValue); +                expect(property.value).toStrictEqual(expectedValue); +                expect(property.overrideCount).toStrictEqual(expectedOverrideCount); +            } +            property.off('change', onChange); +        } +    }); +} + +function testDeepEqual() { +    describe('deepEqual', () => { +        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) { +            test(`${index}`, () => { +                const actual1 = deepEqual(value1, value2); +                expect(actual1).toStrictEqual(expected); + +                const actual2 = deepEqual(value2, value1); +                expect(actual2).toStrictEqual(expected); +            }); +            ++index; +        } +    }); +} + + +function main() { +    testDynamicProperty(); +    testDeepEqual(); +} + +main(); diff --git a/test/test-css-json.js b/test/css-json.test.js index ddeee6bd..0aaf7d10 100644 --- a/test/test-css-json.js +++ b/test/css-json.test.js @@ -16,22 +16,18 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -const fs = require('fs'); -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {formatRulesJson, generateRules} = require('../dev/css-to-json-util'); -const {getTargets} = require('../dev/generate-css-json'); - +import fs from 'fs'; +import {expect, test} from 'vitest'; +import {formatRulesJson, generateRules, getTargets} from '../dev/generate-css-json';  function main() { -    for (const {cssFile, overridesCssFile, outputPath} of getTargets()) { -        const actual = fs.readFileSync(outputPath, {encoding: 'utf8'}); -        const expected = formatRulesJson(generateRules(cssFile, overridesCssFile)); -        assert.deepStrictEqual(actual, expected); -    } +    test('css-json', () => { +        for (const {cssFile, overridesCssFile, outputPath} of getTargets()) { +            const actual = fs.readFileSync(outputPath, {encoding: 'utf8'}); +            const expected = formatRulesJson(generateRules(cssFile, overridesCssFile)); +            expect(actual).toStrictEqual(expected); +        } +    });  } - -if (require.main === module) { -    testMain(main, process.argv.slice(2)); -} +main(); diff --git a/test/database.test.js b/test/database.test.js new file mode 100644 index 00000000..b53d0e65 --- /dev/null +++ b/test/database.test.js @@ -0,0 +1,853 @@ +/* + * 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/>. + */ + +import {IDBFactory, IDBKeyRange} from 'fake-indexeddb'; +import path from 'path'; +import {beforeEach, describe, expect, test, vi} from 'vitest'; +import {createDictionaryArchive} from '../dev/util.js'; +import {DictionaryDatabase} from '../ext/js/language/dictionary-database.js'; +import {DictionaryImporterMediaLoader} from '../ext/js/language/dictionary-importer-media-loader.js'; +import {DictionaryImporter} from '../ext/js/language/dictionary-importer.js'; + +vi.stubGlobal('IDBKeyRange', IDBKeyRange); + +vi.mock('../ext/js/language/dictionary-importer-media-loader.js'); + +function createTestDictionaryArchive(dictionary, dictionaryName) { +    const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', dictionary); +    return createDictionaryArchive(dictionaryDirectory, dictionaryName); +} + + +function createDictionaryImporter(onProgress) { +    const dictionaryImporterMediaLoader = new DictionaryImporterMediaLoader(); +    return new DictionaryImporter(dictionaryImporterMediaLoader, (...args) => { +        const {stepIndex, stepCount, index, count} = args[0]; +        expect(stepIndex < stepCount).toBe(true); +        expect(index <= count).toBe(true); +        if (typeof onProgress === 'function') { +            onProgress(...args); +        } +    }); +} + + +function countDictionaryDatabaseEntriesWithTerm(dictionaryDatabaseEntries, term) { +    return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.term === term ? 1 : 0)), 0); +} + +function countDictionaryDatabaseEntriesWithReading(dictionaryDatabaseEntries, reading) { +    return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.reading === reading ? 1 : 0)), 0); +} + +function countMetasWithMode(metas, mode) { +    return metas.reduce((i, v) => (i + (v.mode === mode ? 1 : 0)), 0); +} + +function countKanjiWithCharacter(kanji, character) { +    return kanji.reduce((i, v) => (i + (v.character === character ? 1 : 0)), 0); +} + + + +async function testDatabase1() { +    test('Database1', async () => {    // 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; +                        } +                    ); +                    expect(progressEvent).toBe(true); + +                    await testDatabaseEmpty1(dictionaryDatabase); +                } +            }, +            { +                cleanup: async () => {} +            } +        ]; + +        // Setup database +        const dictionaryDatabase = new DictionaryDatabase(); +        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; +            expect(errors).toStrictEqual([]); +            expect(result).toStrictEqual(expectedSummary); +            expect(progressEvent).toBe(true); + +            // Get info summary +            const info = await dictionaryDatabase.getDictionaryInfo(); +            expect(info).toStrictEqual([expectedSummary]); + +            // Get counts +            const counts = await dictionaryDatabase.getDictionaryCounts( +                info.map((v) => v.title), +                true +            ); +            expect(counts).toStrictEqual({ +                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(); +    }); +} + +async function testDatabaseEmpty1(database) { +    test('DatabaseEmpty1', async () => { +        const info = await database.getDictionaryInfo(); +        expect(info).toStrictEqual([]); + +        const counts = await database.getDictionaryCounts([], true); +        expect(counts).toStrictEqual({ +            counts: [], +            total: {kanji: 0, kanjiMeta: 0, terms: 0, termMeta: 0, tagMeta: 0, media: 0} +        }); +    }); +} + +async function testFindTermsBulkTest1(database, titles) { +    test('FindTermsBulkTest1', async () => { +        const data = [ +            { +                inputs: [ +                    { +                        matchType: null, +                        termList: ['打', '打つ', '打ち込む'] +                    }, +                    { +                        matchType: null, +                        termList: ['だ', 'ダース', 'うつ', 'ぶつ', 'うちこむ', 'ぶちこむ'] +                    }, +                    { +                        matchType: 'prefix', +                        termList: ['打'] +                    } +                ], +                expectedResults: { +                    total: 10, +                    terms: [ +                        ['打', 2], +                        ['打つ', 4], +                        ['打ち込む', 4] +                    ], +                    readings: [ +                        ['だ', 1], +                        ['ダース', 1], +                        ['うつ', 2], +                        ['ぶつ', 2], +                        ['うちこむ', 2], +                        ['ぶちこむ', 2] +                    ] +                } +            }, +            { +                inputs: [ +                    { +                        matchType: null, +                        termList: ['込む'] +                    } +                ], +                expectedResults: { +                    total: 0, +                    terms: [], +                    readings: [] +                } +            }, +            { +                inputs: [ +                    { +                        matchType: 'suffix', +                        termList: ['込む'] +                    } +                ], +                expectedResults: { +                    total: 4, +                    terms: [ +                        ['打ち込む', 4] +                    ], +                    readings: [ +                        ['うちこむ', 2], +                        ['ぶちこむ', 2] +                    ] +                } +            }, +            { +                inputs: [ +                    { +                        matchType: null, +                        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); +                expect(results.length).toStrictEqual(expectedResults.total); +                for (const [term, count] of expectedResults.terms) { +                    expect(countDictionaryDatabaseEntriesWithTerm(results, term)).toStrictEqual(count); +                } +                for (const [reading, count] of expectedResults.readings) { +                    expect(countDictionaryDatabaseEntriesWithReading(results, reading)).toStrictEqual(count); +                } +            } +        } +    }); +} + +async function testTindTermsExactBulk1(database, titles) { +    test('TindTermsExactBulk1', async () => { +        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); +                expect(results.length).toStrictEqual(expectedResults.total); +                for (const [term, count] of expectedResults.terms) { +                    expect(countDictionaryDatabaseEntriesWithTerm(results, term)).toStrictEqual(count); +                } +                for (const [reading, count] of expectedResults.readings) { +                    expect(countDictionaryDatabaseEntriesWithReading(results, reading)).toStrictEqual(count); +                } +            } +        } +    }); +} + +async function testFindTermsBySequenceBulk1(database, mainDictionary) { +    test('FindTermsBySequenceBulk1', async () => { +        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}))); +                expect(results.length).toStrictEqual(expectedResults.total); +                for (const [term, count] of expectedResults.terms) { +                    expect(countDictionaryDatabaseEntriesWithTerm(results, term)).toStrictEqual(count); +                } +                for (const [reading, count] of expectedResults.readings) { +                    expect(countDictionaryDatabaseEntriesWithReading(results, reading)).toStrictEqual(count); +                } +            } +        } +    }); +} + +async function testFindTermMetaBulk1(database, titles) { +    test('FindTermMetaBulk1', async () => { +        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); +                expect(results.length).toStrictEqual(expectedResults.total); +                for (const [mode, count] of expectedResults.modes) { +                    expect(countMetasWithMode(results, mode)).toStrictEqual(count); +                } +            } +        } +    }); +} + +async function testFindKanjiBulk1(database, titles) { +    test('FindKanjiBulk1', async () => { +        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); +                expect(results.length).toStrictEqual(expectedResults.total); +                for (const [kanji, count] of expectedResults.kanji) { +                    expect(countKanjiWithCharacter(results, kanji)).toStrictEqual(count); +                } +            } +        } +    }); +} + +async function testFindKanjiMetaBulk1(database, titles) { +    test('FindKanjiMetaBulk1', async () => { +        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); +                expect(results.length).toStrictEqual(expectedResults.total); +                for (const [mode, count] of expectedResults.modes) { +                    expect(countMetasWithMode(results, mode)).toStrictEqual(count); +                } +            } +        } +    }); +} + +async function testFindTagForTitle1(database, title) { +    test('FindTagForTitle1', async () => { +        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); +                expect(result).toStrictEqual(expectedResults.value); +            } +        } +    }); +} + + +async function testDatabase2() { +    test('Database2', async () => {    // 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 DictionaryDatabase(); + +        // Database not open +        await expect(dictionaryDatabase.deleteDictionary(title, 1000)).rejects.toThrow('Database not open'); +        await expect(dictionaryDatabase.findTermsBulk(['?'], titles, null)).rejects.toThrow('Database not open'); +        await expect(dictionaryDatabase.findTermsExactBulk([{term: '?', reading: '?'}], titles)).rejects.toThrow('Database not open'); +        await expect(dictionaryDatabase.findTermsBySequenceBulk([{query: 1, dictionary: title}])).rejects.toThrow('Database not open'); +        await expect(dictionaryDatabase.findTermMetaBulk(['?'], titles)).rejects.toThrow('Database not open'); +        await expect(dictionaryDatabase.findTermMetaBulk(['?'], titles)).rejects.toThrow('Database not open'); +        await expect(dictionaryDatabase.findKanjiBulk(['?'], titles)).rejects.toThrow('Database not open'); +        await expect(dictionaryDatabase.findKanjiMetaBulk(['?'], titles)).rejects.toThrow('Database not open'); +        await expect(dictionaryDatabase.findTagForTitle('tag', title)).rejects.toThrow('Database not open'); +        await expect(dictionaryDatabase.getDictionaryInfo()).rejects.toThrow('Database not open'); +        await expect(dictionaryDatabase.getDictionaryCounts(titles, true)).rejects.toThrow('Database not open'); +        await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {})).rejects.toThrow('Database is not ready'); + +        await dictionaryDatabase.prepare(); + +        // already prepared +        await expect(dictionaryDatabase.prepare()).rejects.toThrow('Database already open'); + +        await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {}); + +        // dictionary already imported +        await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {})).rejects.toThrow('Dictionary is already imported'); + +        await dictionaryDatabase.close(); +    }); +} + + +async function testDatabase3() { +    const invalidDictionaries = [ +        'invalid-dictionary1', +        'invalid-dictionary2', +        'invalid-dictionary3', +        'invalid-dictionary4', +        'invalid-dictionary5', +        'invalid-dictionary6' +    ]; + + +    describe('Database3', () => { +        for (const invalidDictionary of invalidDictionaries) { +            test(`${invalidDictionary}`, async () => { +                // Setup database +                const dictionaryDatabase = new DictionaryDatabase(); +                await dictionaryDatabase.prepare(); + +                const testDictionary = createTestDictionaryArchive(invalidDictionary); +                const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); + +                await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {})).rejects.toThrow('Dictionary has invalid data'); +                await dictionaryDatabase.close(); +            }); +        } +    }); +} + + +async function main() { +    beforeEach(async () => { +        globalThis.indexedDB = new IDBFactory(); +    }); +    await testDatabase1(); +    await testDatabase2(); +    await testDatabase3(); +} + +await main(); diff --git a/test/test-deinflector.js b/test/deinflector.test.js index a20cfc95..edb85833 100644 --- a/test/test-deinflector.js +++ b/test/deinflector.test.js @@ -16,14 +16,12 @@   * 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 {VM} = require('../dev/vm'); +import fs from 'fs'; +import path from 'path'; +import {describe, expect, test} from 'vitest'; +import {Deinflector} from '../ext/js/language/deinflector.js'; - -function hasTermReasons(Deinflector, deinflector, source, expectedTerm, expectedRule, expectedReasons) { +function hasTermReasons(deinflector, source, expectedTerm, expectedRule, expectedReasons) {      for (const {term, reasons, rules} of deinflector.deinflect(source, source)) {          if (term !== expectedTerm) { continue; }          if (typeof expectedRule !== 'undefined') { @@ -917,30 +915,27 @@ function testDeinflections() {          }      ]; -    const vm = new VM(); -    vm.execute(['js/language/deinflector.js']); -    const [Deinflector] = vm.get(['Deinflector']); -      const deinflectionReasons = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'ext', 'data/deinflect.json')));      const deinflector = new Deinflector(deinflectionReasons); -    for (const {valid, tests} of data) { -        for (const {source, term, rule, reasons} of tests) { -            const {has, reasons: actualReasons} = hasTermReasons(Deinflector, deinflector, source, term, rule, reasons); -            let message = `${source} ${valid ? 'does not have' : 'has'} term candidate ${JSON.stringify(term)}`; -            if (typeof rule !== 'undefined') { -                message += ` with rule ${JSON.stringify(rule)}`; -            } -            if (typeof reasons !== 'undefined') { -                message += (typeof rule !== 'undefined' ? ' and' : ' with'); -                message += ` reasons ${JSON.stringify(reasons)}`; -            } -            if (actualReasons !== null) { -                message += ` (actual reasons: ${JSON.stringify(actualReasons)})`; +    describe('deinflections', () => { +        for (const {valid, tests} of data) { +            for (const {source, term, rule, reasons} of tests) { +                const {has} = hasTermReasons(deinflector, source, term, rule, reasons); +                let message = `${source} ${valid ? 'has' : 'does not have'} term candidate ${JSON.stringify(term)}`; +                if (typeof rule !== 'undefined') { +                    message += ` with rule ${JSON.stringify(rule)}`; +                } +                if (typeof reasons !== 'undefined') { +                    message += (typeof rule !== 'undefined' ? ' and' : ' with'); +                    message += ` reasons ${JSON.stringify(reasons)}`; +                } +                test(`${message}`, () => { +                    expect(has).toStrictEqual(valid); +                });              } -            assert.strictEqual(has, valid, message);          } -    } +    });  } @@ -949,4 +944,4 @@ function main() {  } -if (require.main === module) { testMain(main); } +main(); diff --git a/test/test-dictionary.js b/test/dictionary.test.js index d4390e19..8f160bc1 100644 --- a/test/test-dictionary.js +++ b/test/dictionary.test.js @@ -16,13 +16,16 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -const path = require('path'); -const {createDictionaryArchive, testMain} = require('../dev/util'); -const dictionaryValidate = require('../dev/dictionary-validate'); +import {fileURLToPath} from 'node:url'; +import path from 'path'; +import {expect, test} from 'vitest'; +import * as dictionaryValidate from '../dev/dictionary-validate.js'; +import {createDictionaryArchive} from '../dev/util.js'; +const dirname = path.dirname(fileURLToPath(import.meta.url));  function createTestDictionaryArchive(dictionary, dictionaryName) { -    const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', dictionary); +    const dictionaryDirectory = path.join(dirname, 'data', 'dictionaries', dictionary);      return createDictionaryArchive(dictionaryDirectory, dictionaryName);  } @@ -41,26 +44,16 @@ async function main() {      const schemas = dictionaryValidate.getSchemas();      for (const {name, valid} of dictionaries) { -        const archive = createTestDictionaryArchive(name); +        test(`${name} is ${valid ? 'valid' : 'invalid'}`, async () => { +            const archive = createTestDictionaryArchive(name); -        let error = null; -        try { -            await dictionaryValidate.validateDictionary(null, archive, schemas); -        } catch (e) { -            error = e; -        } - -        if (valid) { -            if (error !== null) { -                throw error; -            } -        } else { -            if (error === null) { -                throw new Error(`Expected dictionary ${name} to be invalid`); +            if (valid) { +                await expect(dictionaryValidate.validateDictionary(null, archive, schemas)).resolves.not.toThrow(); +            } else { +                await expect(dictionaryValidate.validateDictionary(null, archive, schemas)).rejects.toThrow();              } -        } +        });      }  } - -if (require.main === module) { testMain(main); } +await main(); diff --git a/test/document-util.test.js b/test/document-util.test.js new file mode 100644 index 00000000..f2552f78 --- /dev/null +++ b/test/document-util.test.js @@ -0,0 +1,259 @@ +/* + * 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/>. + */ + +import fs from 'fs'; +import {JSDOM} from 'jsdom'; +import {fileURLToPath} from 'node:url'; +import path from 'path'; +import {expect, test} from 'vitest'; +import {DocumentUtil} from '../ext/js/dom/document-util.js'; +import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js'; +import {TextSourceElement} from '../ext/js/dom/text-source-element.js'; +import {TextSourceRange} from '../ext/js/dom/text-source-range.js'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); + +// DOMRect class definition +class DOMRect { +    constructor(x, y, width, height) { +        this._x = x; +        this._y = y; +        this._width = width; +        this._height = height; +    } + +    get x() { return this._x; } +    get y() { return this._y; } +    get width() { return this._width; } +    get height() { return this._height; } +    get left() { return this._x + Math.min(0, this._width); } +    get right() { return this._x + Math.max(0, this._width); } +    get top() { return this._y + Math.min(0, this._height); } +    get bottom() { return this._y + Math.max(0, this._height); } +} + + +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; +} + +function querySelectorChildOrSelf(element, selector) { +    return selector ? element.querySelector(selector) : element; +} + +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); +} + +function getPrototypeOfOrNull(value) { +    try { +        return Object.getPrototypeOf(value); +    } catch (e) { +        return null; +    } +} + +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; + +    try { +        await testDocumentTextScanningFunctions(dom); +        await testTextSourceRangeSeekFunctions(dom); +    } finally { +        window.close(); +    } +} + +async function testDocumentTextScanningFunctions(dom) { +    const document = dom.window.document; + +    test('DocumentTextScanningFunctions', () => { +        for (const testElement of document.querySelectorAll('.test[data-test-type=scan]')) { +        // Get test parameters +            let { +                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)); + +            startOffset = parseInt(startOffset, 10); +            endOffset = parseInt(endOffset, 10); +            sentenceScanExtent = parseInt(sentenceScanExtent, 10); +            terminateAtNewlines = (terminateAtNewlines !== 'false'); + +            expect(elementFromPointValue).not.toStrictEqual(null); +            expect(caretRangeFromPointValue).not.toStrictEqual(null); +            expect(startNode).not.toStrictEqual(null); +            expect(endNode).not.toStrictEqual(null); + +            // Setup functions +            document.elementFromPoint = () => elementFromPointValue; + +            document.caretRangeFromPoint = (x, y) => { +                const imposter = getChildTextNodeOrSelf(dom, findImposterElement(document)); +                expect(!!imposter).toStrictEqual(hasImposter === 'true'); + +                const range = document.createRange(); +                range.setStart(imposter ? imposter : startNode, startOffset); +                range.setEnd(imposter ? imposter : startNode, endOffset); + +                // Override getClientRects to return a rect guaranteed to contain (x, y) +                range.getClientRects = () => [new DOMRect(x - 1, y - 1, 2, 2)]; +                return range; +            }; + +            // Test docRangeFromPoint +            const source = DocumentUtil.getRangeFromPoint(0, 0, { +                deepContentScan: false, +                normalizeCssZoom: true +            }); +            switch (resultType) { +                case 'TextSourceRange': +                    expect(getPrototypeOfOrNull(source)).toStrictEqual(TextSourceRange.prototype); +                    break; +                case 'TextSourceElement': +                    expect(getPrototypeOfOrNull(source)).toStrictEqual(TextSourceElement.prototype); +                    break; +                case 'null': +                    expect(source).toStrictEqual(null); +                    break; +                default: +                    expect.unreachable(); +                    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, +                sentenceScanExtent, +                terminateAtNewlines, +                terminatorMap, +                forwardQuoteMap, +                backwardQuoteMap +            ).text; +            expect(sentenceActual).toStrictEqual(sentence); + +            // Clean +            source.cleanup(); +        } +    }); +} + +async function testTextSourceRangeSeekFunctions(dom) { +    const document = dom.window.document; + +    test('TextSourceRangeSeekFunctions', async () => { +        for (const testElement of document.querySelectorAll('.test[data-test-type=text-source-range-seek]')) { +        // Get test parameters +            let { +                seekNodeSelector, +                seekNodeIsText, +                seekOffset, +                seekLength, +                seekDirection, +                expectedResultNodeSelector, +                expectedResultNodeIsText, +                expectedResultOffset, +                expectedResultContent +            } = testElement.dataset; + +            seekOffset = parseInt(seekOffset, 10); +            seekLength = parseInt(seekLength, 10); +            expectedResultOffset = parseInt(expectedResultOffset, 10); + +            let seekNode = testElement.querySelector(seekNodeSelector); +            if (seekNodeIsText === 'true') { +                seekNode = seekNode.firstChild; +            } + +            let expectedResultNode = testElement.querySelector(expectedResultNodeSelector); +            if (expectedResultNodeIsText === 'true') { +                expectedResultNode = expectedResultNode.firstChild; +            } + +            const {node, offset, content} = ( +            seekDirection === 'forward' ? +            new DOMTextScanner(seekNode, seekOffset, true, false).seek(seekLength) : +            new DOMTextScanner(seekNode, seekOffset, true, false).seek(-seekLength) +            ); + +            expect(node).toStrictEqual(expectedResultNode); +            expect(offset).toStrictEqual(expectedResultOffset); +            expect(content).toStrictEqual(expectedResultContent); +        } +    }); +} + + +async function main() { +    await testDocument1(); +} + +await main(); diff --git a/test/test-dom-text-scanner.js b/test/dom-text-scanner.test.js index 37017b01..d1b31276 100644 --- a/test/test-dom-text-scanner.js +++ b/test/dom-text-scanner.test.js @@ -16,13 +16,11 @@   * 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'); - +import fs from 'fs'; +import {JSDOM} from 'jsdom'; +import path from 'path'; +import {expect, test} from 'vitest'; +import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js';  function createJSDOM(fileName) {      const domSource = fs.readFileSync(fileName, {encoding: 'utf8'}); @@ -87,74 +85,77 @@ function createAbsoluteGetComputedStyle(window) {  } -async function testDomTextScanner(dom, {DOMTextScanner}) { +async function testDomTextScanner(dom) {      const document = dom.window.document; -    for (const testElement of document.querySelectorAll('y-test')) { -        let testData = JSON.parse(testElement.dataset.testData); -        if (!Array.isArray(testData)) { -            testData = [testData]; -        } -        for (const testDataItem of testData) { -            let { -                node, -                offset, -                length, -                forcePreserveWhitespace, -                generateLayoutContent, -                reversible, -                expected: { -                    node: expectedNode, -                    offset: expectedOffset, -                    content: expectedContent, -                    remainder: expectedRemainder -                } -            } = testDataItem; - -            node = querySelectorTextNode(testElement, node); -            expectedNode = querySelectorTextNode(testElement, expectedNode); -            // Standard test -            { -                const scanner = new DOMTextScanner(node, offset, forcePreserveWhitespace, generateLayoutContent); -                scanner.seek(length); - -                const {node: actualNode1, offset: actualOffset1, content: actualContent1, remainder: actualRemainder1} = scanner; -                assert.strictEqual(actualContent1, expectedContent); -                assert.strictEqual(actualOffset1, expectedOffset); -                assert.strictEqual(actualNode1, expectedNode); -                assert.strictEqual(actualRemainder1, expectedRemainder || 0); +    test('DomTextScanner', () => { +        for (const testElement of document.querySelectorAll('y-test')) { +            let testData = JSON.parse(testElement.dataset.testData); +            if (!Array.isArray(testData)) { +                testData = [testData];              } +            for (const testDataItem of testData) { +                let { +                    node, +                    offset, +                    length, +                    forcePreserveWhitespace, +                    generateLayoutContent, +                    reversible, +                    expected: { +                        node: expectedNode, +                        offset: expectedOffset, +                        content: expectedContent, +                        remainder: expectedRemainder +                    } +                } = testDataItem; + +                node = querySelectorTextNode(testElement, node); +                expectedNode = querySelectorTextNode(testElement, expectedNode); + +                // Standard test +                { +                    const scanner = new DOMTextScanner(node, offset, forcePreserveWhitespace, generateLayoutContent); +                    scanner.seek(length); + +                    const {node: actualNode1, offset: actualOffset1, content: actualContent1, remainder: actualRemainder1} = scanner; +                    expect(actualContent1).toStrictEqual(expectedContent); +                    expect(actualOffset1).toStrictEqual(expectedOffset); +                    expect(actualNode1).toStrictEqual(expectedNode); +                    expect(actualRemainder1).toStrictEqual(expectedRemainder || 0); +                } -            // Substring tests -            for (let i = 1; i <= length; ++i) { -                const scanner = new DOMTextScanner(node, offset, forcePreserveWhitespace, generateLayoutContent); -                scanner.seek(length - i); +                // Substring tests +                for (let i = 1; i <= length; ++i) { +                    const scanner = new DOMTextScanner(node, offset, forcePreserveWhitespace, generateLayoutContent); +                    scanner.seek(length - i); -                const {content: actualContent} = scanner; -                assert.strictEqual(actualContent, expectedContent.substring(0, expectedContent.length - i)); -            } +                    const {content: actualContent} = scanner; +                    expect(actualContent).toStrictEqual(expectedContent.substring(0, expectedContent.length - i)); +                } -            if (reversible === false) { continue; } +                if (reversible === false) { continue; } -            // Reversed test -            { -                const scanner = new DOMTextScanner(expectedNode, expectedOffset, forcePreserveWhitespace, generateLayoutContent); -                scanner.seek(-length); +                // Reversed test +                { +                    const scanner = new DOMTextScanner(expectedNode, expectedOffset, forcePreserveWhitespace, generateLayoutContent); +                    scanner.seek(-length); -                const {content: actualContent} = scanner; -                assert.strictEqual(actualContent, expectedContent); -            } +                    const {content: actualContent} = scanner; +                    expect(actualContent).toStrictEqual(expectedContent); +                } -            // Reversed substring tests -            for (let i = 1; i <= length; ++i) { -                const scanner = new DOMTextScanner(expectedNode, expectedOffset, forcePreserveWhitespace, generateLayoutContent); -                scanner.seek(-(length - i)); +                // Reversed substring tests +                for (let i = 1; i <= length; ++i) { +                    const scanner = new DOMTextScanner(expectedNode, expectedOffset, forcePreserveWhitespace, generateLayoutContent); +                    scanner.seek(-(length - i)); -                const {content: actualContent} = scanner; -                assert.strictEqual(actualContent, expectedContent.substring(i)); +                    const {content: actualContent} = scanner; +                    expect(actualContent).toStrictEqual(expectedContent.substring(i)); +                }              }          } -    } +    });  } @@ -162,17 +163,8 @@ async function testDocument1() {      const dom = createJSDOM(path.join(__dirname, 'data', 'html', 'test-dom-text-scanner.html'));      const window = dom.window;      try { -        const {document, Node, Range} = window; -          window.getComputedStyle = createAbsoluteGetComputedStyle(window); -        const vm = new VM({document, window, Range, Node}); -        vm.execute([ -            'js/data/sandbox/string-util.js', -            'js/dom/dom-text-scanner.js' -        ]); -        const DOMTextScanner = vm.get('DOMTextScanner'); -          await testDomTextScanner(dom, {DOMTextScanner});      } finally {          window.close(); @@ -184,5 +176,4 @@ async function main() {      await testDocument1();  } - -if (require.main === module) { testMain(main); } +await main(); diff --git a/test/hotkey-util.test.js b/test/hotkey-util.test.js new file mode 100644 index 00000000..8666b98b --- /dev/null +++ b/test/hotkey-util.test.js @@ -0,0 +1,164 @@ +/* + * 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/>. + */ + +import {expect, test} from 'vitest'; +import {HotkeyUtil} from '../ext/js/input/hotkey-util.js'; + +function testCommandConversions() { +    test('CommandConversions', () => { +        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 = new HotkeyUtil(); +        for (const {command, os, expectedInput, expectedCommand} of data) { +            hotkeyUtil.os = os; +            const input = structuredClone(hotkeyUtil.convertCommandToInput(command)); +            expect(input).toStrictEqual(expectedInput); +            const command2 = hotkeyUtil.convertInputToCommand(input.key, input.modifiers); +            expect(command2).toStrictEqual(expectedCommand); +        } +    }); +} + +function testDisplayNames() { +    test('DisplayNames', () => { +        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 = new HotkeyUtil(); +        for (const {os, key, modifiers, expected} of data) { +            hotkeyUtil.os = os; +            const displayName = hotkeyUtil.getInputDisplayValue(key, modifiers); +            expect(displayName).toStrictEqual(expected); +        } +    }); +} + +function testSortModifiers() { +    test('SortModifiers', () => { +        const data = [ +            {modifiers: [], expected: []}, +            {modifiers: ['shift', 'alt', 'ctrl', 'mouse4', 'meta', 'mouse1', 'mouse0'], expected: ['meta', 'ctrl', 'alt', 'shift', 'mouse0', 'mouse1', 'mouse4']} +        ]; + +        const hotkeyUtil = new HotkeyUtil(); +        for (const {modifiers, expected} of data) { +            const modifiers2 = hotkeyUtil.sortModifiers(modifiers); +            expect(modifiers2).toStrictEqual(modifiers); +            expect(modifiers2).toStrictEqual(expected); +        } +    }); +} + + +function main() { +    testCommandConversions(); +    testDisplayNames(); +    testSortModifiers(); +} + +main(); diff --git a/test/japanese-util.test.js b/test/japanese-util.test.js new file mode 100644 index 00000000..47da4ccb --- /dev/null +++ b/test/japanese-util.test.js @@ -0,0 +1,905 @@ +/* + * 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/>. + */ + +import {expect, test} from 'vitest'; +import {TextSourceMap} from '../ext/js/general/text-source-map.js'; +import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; +import * as wanakana from '../ext/lib/wanakana.js'; + +const jp = new JapaneseUtil(wanakana); + +function testIsCodePointKanji() { +    test('isCodePointKanji', () => { +        const data = [ +            ['力方', true], +            ['\u53f1\u{20b9f}', true], +            ['かたカタ々kata、。?,.?', false], +            ['逸逸', true] +        ]; + +        for (const [characters, expected] of data) { +            for (const character of characters) { +                const codePoint = character.codePointAt(0); +                const actual = jp.isCodePointKanji(codePoint); +                expect(actual).toStrictEqual(expected); // `isCodePointKanji failed for ${character} (\\u{${codePoint.toString(16)}})` +            } +        } +    }); +} + +function testIsCodePointKana() { +    test('isCodePointKana', () => { +        const data = [ +            ['かたカタ', true], +            ['力方々kata、。?,.?', false], +            ['\u53f1\u{20b9f}', false] +        ]; + +        for (const [characters, expected] of data) { +            for (const character of characters) { +                const codePoint = character.codePointAt(0); +                const actual = jp.isCodePointKana(codePoint); +                expect(actual).toStrictEqual(expected); // `isCodePointKana failed for ${character} (\\u{${codePoint.toString(16)}})` +            } +        } +    }); +} + +function testIsCodePointJapanese() { +    test('isCodePointJapanese', () => { +        const data = [ +            ['かたカタ力方々、。?', true], +            ['\u53f1\u{20b9f}', true], +            ['kata,.?', false], +            ['逸逸', true] +        ]; + +        for (const [characters, expected] of data) { +            for (const character of characters) { +                const codePoint = character.codePointAt(0); +                const actual = jp.isCodePointJapanese(codePoint); +                expect(actual).toStrictEqual(expected); // `isCodePointJapanese failed for ${character} (\\u{${codePoint.toString(16)}})` +            } +        } +    }); +} + +function testIsStringEntirelyKana() { +    test('isStringEntirelyKana', () => { +        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) { +            expect(jp.isStringEntirelyKana(string)).toStrictEqual(expected); +        } +    }); +} + +function testIsStringPartiallyJapanese() { +    test('isStringPartiallyJapanese', () => { +        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) { +            expect(jp.isStringPartiallyJapanese(string)).toStrictEqual(expected); +        } +    }); +} + +function testConvertKatakanaToHiragana() { +    test('convertKatakanaToHiragana', () => { +        const data = [ +            ['かたかな', 'かたかな'], +            ['ひらがな', 'ひらがな'], +            ['カタカナ', 'かたかな'], +            ['ヒラガナ', 'ひらがな'], +            ['カタカナかたかな', 'かたかなかたかな'], +            ['ヒラガナひらがな', 'ひらがなひらがな'], +            ['chikaraちからチカラ力', 'chikaraちからちから力'], +            ['katakana', 'katakana'], +            ['hiragana', 'hiragana'], +            ['カーナー', 'かあなあ'], +            ['カーナー', 'かーなー', true] +        ]; + +        for (const [string, expected, keepProlongedSoundMarks=false] of data) { +            expect(jp.convertKatakanaToHiragana(string, keepProlongedSoundMarks)).toStrictEqual(expected); +        } +    }); +} + +function testConvertHiraganaToKatakana() { +    test('ConvertHiraganaToKatakana', () => { +        const data = [ +            ['かたかな', 'カタカナ'], +            ['ひらがな', 'ヒラガナ'], +            ['カタカナ', 'カタカナ'], +            ['ヒラガナ', 'ヒラガナ'], +            ['カタカナかたかな', 'カタカナカタカナ'], +            ['ヒラガナひらがな', 'ヒラガナヒラガナ'], +            ['chikaraちからチカラ力', 'chikaraチカラチカラ力'], +            ['katakana', 'katakana'], +            ['hiragana', 'hiragana'] +        ]; + +        for (const [string, expected] of data) { +            expect(jp.convertHiraganaToKatakana(string)).toStrictEqual(expected); +        } +    }); +} + +function testConvertToRomaji() { +    test('ConvertToRomaji', () => { +        const data = [ +            ['かたかな', 'katakana'], +            ['ひらがな', 'hiragana'], +            ['カタカナ', 'katakana'], +            ['ヒラガナ', 'hiragana'], +            ['カタカナかたかな', 'katakanakatakana'], +            ['ヒラガナひらがな', 'hiraganahiragana'], +            ['chikaraちからチカラ力', 'chikarachikarachikara力'], +            ['katakana', 'katakana'], +            ['hiragana', 'hiragana'] +        ]; + +        for (const [string, expected] of data) { +            expect(jp.convertToRomaji(string)).toStrictEqual(expected); +        } +    }); +} + +function testConvertNumericToFullWidth() { +    test('ConvertNumericToFullWidth', () => { +        const data = [ +            ['0123456789', '0123456789'], +            ['abcdefghij', 'abcdefghij'], +            ['カタカナ', 'カタカナ'], +            ['ひらがな', 'ひらがな'] +        ]; + +        for (const [string, expected] of data) { +            expect(jp.convertNumericToFullWidth(string)).toStrictEqual(expected); +        } +    }); +} + +function testConvertHalfWidthKanaToFullWidth() { +    test('ConvertHalfWidthKanaToFullWidth', () => { +        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 TextSourceMap(string); +            const actual1 = jp.convertHalfWidthKanaToFullWidth(string, null); +            const actual2 = jp.convertHalfWidthKanaToFullWidth(string, sourceMap); +            expect(actual1).toStrictEqual(expected); +            expect(actual2).toStrictEqual(expected); +            if (typeof expectedSourceMapping !== 'undefined') { +                expect(sourceMap.equals(new TextSourceMap(string, expectedSourceMapping))).toBe(true); +            } +        } +    }); +} + +function testConvertAlphabeticToKana() { +    test('ConvertAlphabeticToKana', () => { +        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 TextSourceMap(string); +            const actual1 = jp.convertAlphabeticToKana(string, null); +            const actual2 = jp.convertAlphabeticToKana(string, sourceMap); +            expect(actual1).toStrictEqual(expected); +            expect(actual2).toStrictEqual(expected); +            if (typeof expectedSourceMapping !== 'undefined') { +                expect(sourceMap.equals(new TextSourceMap(string, expectedSourceMapping))).toBe(true); +            } +        } +    }); +} + +function testDistributeFurigana() { +    test('DistributeFurigana', () => { +        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); +            expect(actual).toStrictEqual(expected); +        } +    }); +} + +function testDistributeFuriganaInflected() { +    test('DistributeFuriganaInflected', () => { +        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); +            expect(actual).toStrictEqual(expected); +        } +    }); +} + +function testCollapseEmphaticSequences() { +    test('CollapseEmphaticSequences', () => { +        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 TextSourceMap(text); +            const actual1 = jp.collapseEmphaticSequences(text, fullCollapse, null); +            const actual2 = jp.collapseEmphaticSequences(text, fullCollapse, sourceMap); +            expect(actual1).toStrictEqual(expected); +            expect(actual2).toStrictEqual(expected); +            if (typeof expectedSourceMapping !== 'undefined') { +                expect(sourceMap.equals(new TextSourceMap(text, expectedSourceMapping))).toBe(true); +            } +        } +    }); +} + +function testIsMoraPitchHigh() { +    test('IsMoraPitchHigh', () => { +        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); +            expect(actual).toStrictEqual(expected); +        } +    }); +} + +function testGetKanaMorae() { +    test('GetKanaMorae', () => { +        const data = [ +            ['かこ', ['か', 'こ']], +            ['かっこ', ['か', 'っ', 'こ']], +            ['カコ', ['カ', 'コ']], +            ['カッコ', ['カ', 'ッ', 'コ']], +            ['コート', ['コ', 'ー', 'ト']], +            ['ちゃんと', ['ちゃ', 'ん', 'と']], +            ['とうきょう', ['と', 'う', 'きょ', 'う']], +            ['ぎゅう', ['ぎゅ', 'う']], +            ['ディスコ', ['ディ', 'ス', 'コ']] +        ]; + +        for (const [text, expected] of data) { +            const actual = jp.getKanaMorae(text); +            expect(actual).toStrictEqual(expected); +        } +    }); +} + + +function main() { +    testIsCodePointKanji(); +    testIsCodePointKana(); +    testIsCodePointJapanese(); +    testIsStringEntirelyKana(); +    testIsStringPartiallyJapanese(); +    testConvertKatakanaToHiragana(); +    testConvertHiraganaToKatakana(); +    testConvertToRomaji(); +    testConvertNumericToFullWidth(); +    testConvertHalfWidthKanaToFullWidth(); +    testConvertAlphabeticToKana(); +    testDistributeFurigana(); +    testDistributeFuriganaInflected(); +    testCollapseEmphaticSequences(); +    testIsMoraPitchHigh(); +    testGetKanaMorae(); +} + +main(); diff --git a/test/test-jsdom.js b/test/jsdom.test.js index 5078c240..c53f374e 100644 --- a/test/test-jsdom.js +++ b/test/jsdom.test.js @@ -16,8 +16,8 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -const assert = require('assert'); -const {testMain} = require('../dev/util'); +import {JSDOM} from 'jsdom'; +import {expect, test} from 'vitest';  /**   * This function tests the following bug: @@ -25,17 +25,18 @@ const {testMain} = require('../dev/util');   * - https://github.com/dperini/nwsapi/issues/48   */  function testJSDOMSelectorBug() { +    test('JSDOMSelectorBug', () => {      // nwsapi is used by JSDOM -    const {JSDOM} = require('jsdom'); -    const dom = new JSDOM(); -    const {document} = dom.window; -    const div = document.createElement('div'); -    div.innerHTML = '<div class="b"><div class="c"></div></div>'; -    const c = div.querySelector('.c'); -    assert.doesNotThrow(() => { c.matches('.a:nth-last-of-type(1) .b .c'); }); +        const dom = new JSDOM(); +        const {document} = dom.window; +        const div = document.createElement('div'); +        div.innerHTML = '<div class="b"><div class="c"></div></div>'; +        const c = div.querySelector('.c'); +        expect(() => c.matches('.a:nth-last-of-type(1) .b .c')).not.toThrow(); +    });  } -function testJSDOM() { +export function testJSDOM() {      testJSDOMSelectorBug();  } @@ -43,8 +44,4 @@ function main() {      testJSDOM();  } -module.exports = { -    testJSDOM -}; - -if (require.main === module) { testMain(main); } +main(); diff --git a/test/json-schema.test.js b/test/json-schema.test.js new file mode 100644 index 00000000..5370e8da --- /dev/null +++ b/test/json-schema.test.js @@ -0,0 +1,1009 @@ +/* + * 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/>. + */ + +import {expect, test} from 'vitest'; +import {JsonSchema} from '../ext/js/data/json-schema.js'; + +function schemaValidate(schema, value) { +    return new JsonSchema(schema).isValid(value); +} + +function getValidValueOrDefault(schema, value) { +    return new JsonSchema(schema).getValidValueOrDefault(value); +} + +function createProxy(schema, value) { +    return new JsonSchema(schema).createProxy(value); +} + +function clone(value) { +    return JSON.parse(JSON.stringify(value)); +} + + +function testValidate1() { +    test('Validate1', () => { +        const schema = { +            allOf: [ +                { +                    type: 'number' +                }, +                { +                    anyOf: [ +                        {minimum: 10, maximum: 100}, +                        {minimum: -100, maximum: -10} +                    ] +                }, +                { +                    oneOf: [ +                        {multipleOf: 3}, +                        {multipleOf: 5} +                    ] +                }, +                { +                    not: [ +                        {multipleOf: 20} +                    ] +                } +            ] +        }; + +        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); +            expect(actual).toStrictEqual(expected); +        } +    }); +} + +function testValidate2() { +    test('Validate2', () => { +        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); +                expect(actual).toStrictEqual(expected); +            } +        } +    }); +} + + +function testGetValidValueOrDefault1() { +    test('GetValidValueOrDefault1', () => { +        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: '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); +                expect(actual).toStrictEqual(expected); +            } +        } +    }); +} + + +function testProxy1() { +    test('Proxy1', () => { +        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); +                expect(schemaValidate(schema, value)).toBe(true); +                const valueProxy = createProxy(schema, value); +                if (error) { +                    expect(() => action(valueProxy)).toThrow(); +                } else { +                    expect(() => action(valueProxy)).not.toThrow(); +                } +            } +        } +    }); +} + + +function main() { +    testValidate1(); +    testValidate2(); +    testGetValidValueOrDefault1(); +    testProxy1(); +} + + +main(); diff --git a/test/object-property-accessor.test.js b/test/object-property-accessor.test.js new file mode 100644 index 00000000..a8730093 --- /dev/null +++ b/test/object-property-accessor.test.js @@ -0,0 +1,438 @@ +/* + * 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/>. + */ + +import {expect, test} from 'vitest'; +import {ObjectPropertyAccessor} from '../ext/js/general/object-property-accessor.js'; + +function createTestObject() { +    return { +        0: null, +        value1: { +            value2: {}, +            value3: [], +            value4: null +        }, +        value5: [ +            {}, +            [], +            null +        ] +    }; +} + + +function testGet1() { +    test('Get1', () => { +        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 ObjectPropertyAccessor(object); +            const expected = getExpected(object); + +            expect(accessor.get(pathArray)).toStrictEqual(expected); +        } +    }); +} + +function testGet2() { +    test('Get2', () => { +        const object = createTestObject(); +        const accessor = new ObjectPropertyAccessor(object); + +        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) { +            expect(() => accessor.get(pathArray)).toThrow(message); +        } +    }); +} + + +function testSet1() { +    test('Set1', () => { +        const testValue = {}; +        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 ObjectPropertyAccessor(object); + +            accessor.set(pathArray, testValue); +            expect(accessor.get(pathArray)).toStrictEqual(testValue); +        } +    }); +} + +function testSet2() { +    test('Set2', () => { +        const object = createTestObject(); +        const accessor = new ObjectPropertyAccessor(object); + +        const testValue = {}; +        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) { +            expect(() => accessor.set(pathArray, testValue)).toThrow(message); +        } +    }); +} + + +function testDelete1() { +    test('Delete1', () => { +        const hasOwn = (object, property) => Object.prototype.hasOwnProperty.call(object, property); + +        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 ObjectPropertyAccessor(object); + +            accessor.delete(pathArray); +            expect(validate(object)).toBe(true); +        } +    }); +} + +function testDelete2() { +    test('Delete2', () => { +        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 ObjectPropertyAccessor(object); + +            expect(() => accessor.delete(pathArray)).toThrow(message); +        } +    }); +} + + +function testSwap1() { +    test('Swap1', () => { +        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 ObjectPropertyAccessor(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); + +                expect(value1a).toStrictEqual(value2b); +                expect(value2a).toStrictEqual(value1b); +            } +        } +    }); +} + +function testSwap2() { +    test('Swap2', () => { +        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 ObjectPropertyAccessor(object); + +            let value1a; +            let value2a; +            if (checkRevert) { +                try { +                    value1a = accessor.get(pathArray1); +                    value2a = accessor.get(pathArray2); +                } catch (e) { +                // NOP +                } +            } + +            expect(() => accessor.swap(pathArray1, pathArray2)).toThrow(message); + +            if (!checkRevert) { continue; } + +            const value1b = accessor.get(pathArray1); +            const value2b = accessor.get(pathArray2); + +            expect(value1a).toStrictEqual(value1b); +            expect(value2a).toStrictEqual(value2b); +        } +    }); +} + + +function testGetPathString1() { +    test('GetPathString1', () => { +        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) { +            expect(ObjectPropertyAccessor.getPathString(pathArray)).toStrictEqual(expected); +        } +    }); +} + +function testGetPathString2() { +    test('GetPathString2', () => { +        const data = [ +            [[1.5], 'Invalid index'], +            [[null], 'Invalid type: object'] +        ]; + +        for (const [pathArray, message] of data) { +            expect(() => ObjectPropertyAccessor.getPathString(pathArray)).toThrow(message); +        } +    }); +} + + +function testGetPathArray1() { +    test('GetPathArray1', () => { +        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) { +            expect(ObjectPropertyAccessor.getPathArray(pathString)).toStrictEqual(expected); +        } +    }); +} + +function testGetPathArray2() { +    test('GetPathArray2', () => { +        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) { +            expect(() => ObjectPropertyAccessor.getPathArray(pathString)).toThrow(message); +        } +    }); +} + + +function testHasProperty() { +    test('HasProperty', () => { +        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) { +            expect(ObjectPropertyAccessor.hasProperty(object, property)).toStrictEqual(expected); +        } +    }); +} + +function testIsValidPropertyType() { +    test('IsValidPropertyType', () => { +        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) { +            expect(ObjectPropertyAccessor.isValidPropertyType(object, property)).toStrictEqual(expected); +        } +    }); +} + + +function main() { +    testGet1(); +    testGet2(); +    testSet1(); +    testSet2(); +    testDelete1(); +    testDelete2(); +    testSwap1(); +    testSwap2(); +    testGetPathString1(); +    testGetPathString2(); +    testGetPathArray1(); +    testGetPathArray2(); +    testHasProperty(); +    testIsValidPropertyType(); +} + + +main(); diff --git a/test/test-options-util.js b/test/options-util.test.js index d94028c0..9f49eb28 100644 --- a/test/test-options-util.js +++ b/test/options-util.test.js @@ -16,53 +16,33 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -const fs = require('fs'); -const url = require('url'); -const path = require('path'); -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - - -function createVM(extDir) { -    const chrome = { -        runtime: { -            getURL(path2) { -                return url.pathToFileURL(path.join(extDir, path2.replace(/^\//, ''))).href; -            } -        } +import fs from 'fs'; +import url, {fileURLToPath} from 'node:url'; +import path from 'path'; +import {expect, test, vi} from 'vitest'; +import {OptionsUtil} from '../ext/js/data/options-util.js'; +import {TemplatePatcher} from '../ext/js/templates/template-patcher.js'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +vi.stubGlobal('fetch', async function fetch(url2) { +    const filePath = url.fileURLToPath(url2); +    await Promise.resolve(); +    const content = fs.readFileSync(filePath, {encoding: null}); +    return { +        ok: true, +        status: 200, +        statusText: 'OK', +        text: async () => Promise.resolve(content.toString('utf8')), +        json: async () => Promise.resolve(JSON.parse(content.toString('utf8')))      }; - -    async function fetch(url2) { -        const filePath = url.fileURLToPath(url2); -        await Promise.resolve(); -        const content = fs.readFileSync(filePath, {encoding: null}); -        return { -            ok: true, -            status: 200, -            statusText: 'OK', -            text: async () => Promise.resolve(content.toString('utf8')), -            json: async () => Promise.resolve(JSON.parse(content.toString('utf8'))) -        }; +}); +vi.stubGlobal('chrome', { +    runtime: { +        getURL: (path2) => { +            return url.pathToFileURL(path.join(dirname, '..', 'ext', path2.replace(/^\//, ''))).href; +        }      } - -    const vm = new VM({chrome, fetch}); -    vm.execute([ -        'js/core.js', -        'js/general/cache-map.js', -        'js/data/json-schema.js', -        'js/templates/template-patcher.js', -        'js/data/options-util.js' -    ]); - -    return vm; -} - - -function clone(value) { -    return JSON.parse(JSON.stringify(value)); -} - +});  function createProfileOptionsTestData1() {      return { @@ -632,131 +612,130 @@ function createOptionsUpdatedTestData1() {  } -async function testUpdate(extDir) { -    const vm = createVM(extDir); -    const [OptionsUtil] = vm.get(['OptionsUtil']); -    const optionsUtil = new OptionsUtil(); -    await optionsUtil.prepare(); +async function testUpdate() { +    test('Update', async () => { +        const optionsUtil = new OptionsUtil(); +        await optionsUtil.prepare(); -    const options = createOptionsTestData1(); -    const optionsUpdated = clone(await optionsUtil.update(options)); -    const optionsExpected = createOptionsUpdatedTestData1(); -    assert.deepStrictEqual(optionsUpdated, optionsExpected); +        const options = createOptionsTestData1(); +        const optionsUpdated = structuredClone(await optionsUtil.update(options)); +        const optionsExpected = createOptionsUpdatedTestData1(); +        expect(optionsUpdated).toStrictEqual(optionsExpected); +    });  } -async function testDefault(extDir) { -    const data = [ -        (options) => options, -        (options) => { -            delete options.profiles[0].options.audio.autoPlay; -        }, -        (options) => { -            options.profiles[0].options.audio.autoPlay = void 0; -        } -    ]; +async function testDefault() { +    test('Default', async () => { +        const data = [ +            (options) => options, +            (options) => { +                delete options.profiles[0].options.audio.autoPlay; +            }, +            (options) => { +                options.profiles[0].options.audio.autoPlay = void 0; +            } +        ]; -    const vm = createVM(extDir); -    const [OptionsUtil] = vm.get(['OptionsUtil']); -    const optionsUtil = new OptionsUtil(); -    await optionsUtil.prepare(); +        const optionsUtil = new OptionsUtil(); +        await optionsUtil.prepare(); -    for (const modify of data) { -        const options = optionsUtil.getDefault(); +        for (const modify of data) { +            const options = optionsUtil.getDefault(); -        const optionsModified = clone(options); -        modify(optionsModified); +            const optionsModified = structuredClone(options); +            modify(optionsModified); -        const optionsUpdated = await optionsUtil.update(clone(optionsModified)); -        assert.deepStrictEqual(clone(optionsUpdated), clone(options)); -    } +            const optionsUpdated = await optionsUtil.update(structuredClone(optionsModified)); +            expect(structuredClone(optionsUpdated)).toStrictEqual(structuredClone(options)); +        } +    });  } -async function testFieldTemplatesUpdate(extDir) { -    const vm = createVM(extDir); -    const [OptionsUtil, TemplatePatcher] = vm.get(['OptionsUtil', 'TemplatePatcher']); -    const optionsUtil = new OptionsUtil(); -    await optionsUtil.prepare(); +async function testFieldTemplatesUpdate() { +    test('FieldTemplatesUpdate', async () => { +        const optionsUtil = new OptionsUtil(); +        await optionsUtil.prepare(); -    const templatePatcher = new TemplatePatcher(); -    const loadDataFile = (fileName) => { -        const content = fs.readFileSync(path.join(extDir, fileName), {encoding: 'utf8'}); -        return templatePatcher.parsePatch(content).addition; -    }; -    const updates = [ -        {version: 2,  changes: loadDataFile('data/templates/anki-field-templates-upgrade-v2.handlebars')}, -        {version: 4,  changes: loadDataFile('data/templates/anki-field-templates-upgrade-v4.handlebars')}, -        {version: 6,  changes: loadDataFile('data/templates/anki-field-templates-upgrade-v6.handlebars')}, -        {version: 8,  changes: loadDataFile('data/templates/anki-field-templates-upgrade-v8.handlebars')}, -        {version: 10, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v10.handlebars')}, -        {version: 12, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v12.handlebars')}, -        {version: 13, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v13.handlebars')}, -        {version: 21, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v21.handlebars')} -    ]; -    const getUpdateAdditions = (startVersion, targetVersion) => { -        let value = ''; -        for (const {version, changes} of updates) { -            if (version <= startVersion || version > targetVersion || changes.length === 0) { continue; } -            if (value.length > 0) { value += '\n'; } -            value += changes; -        } -        return value; -    }; +        const templatePatcher = new TemplatePatcher(); +        const loadDataFile = (fileName) => { +            const content = fs.readFileSync(path.join(dirname, '..', 'ext', fileName), {encoding: 'utf8'}); +            return templatePatcher.parsePatch(content).addition; +        }; +        const updates = [ +            {version: 2,  changes: loadDataFile('data/templates/anki-field-templates-upgrade-v2.handlebars')}, +            {version: 4,  changes: loadDataFile('data/templates/anki-field-templates-upgrade-v4.handlebars')}, +            {version: 6,  changes: loadDataFile('data/templates/anki-field-templates-upgrade-v6.handlebars')}, +            {version: 8,  changes: loadDataFile('data/templates/anki-field-templates-upgrade-v8.handlebars')}, +            {version: 10, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v10.handlebars')}, +            {version: 12, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v12.handlebars')}, +            {version: 13, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v13.handlebars')}, +            {version: 21, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v21.handlebars')} +        ]; +        const getUpdateAdditions = (startVersion, targetVersion) => { +            let value = ''; +            for (const {version, changes} of updates) { +                if (version <= startVersion || version > targetVersion || changes.length === 0) { continue; } +                if (value.length > 0) { value += '\n'; } +                value += changes; +            } +            return value; +        }; -    const data = [ +        const data = [          // Standard format -        { -            oldVersion: 0, -            newVersion: 12, -            old: ` +            { +                oldVersion: 0, +                newVersion: 12, +                old: `  {{#*inline "character"}}      {{~definition.character~}}  {{/inline}}  {{~> (lookup . "marker") ~}}`.trimStart(), -            expected: ` +                expected: `  {{#*inline "character"}}      {{~definition.character~}}  {{/inline}}  <<<UPDATE-ADDITIONS>>>  {{~> (lookup . "marker") ~}}`.trimStart() -        }, -        // Non-standard marker format -        { -            oldVersion: 0, -            newVersion: 12, -            old: ` +            }, +            // Non-standard marker format +            { +                oldVersion: 0, +                newVersion: 12, +                old: `  {{#*inline "character"}}      {{~definition.character~}}  {{/inline}}  {{~> (lookup . "marker2") ~}}`.trimStart(), -            expected: ` +                expected: `  {{#*inline "character"}}      {{~definition.character~}}  {{/inline}}  {{~> (lookup . "marker2") ~}}  <<<UPDATE-ADDITIONS>>>`.trimStart() -        }, -        // Empty test -        { -            oldVersion: 0, -            newVersion: 12, -            old: ` +            }, +            // Empty test +            { +                oldVersion: 0, +                newVersion: 12, +                old: `  {{~> (lookup . "marker") ~}}`.trimStart(), -            expected: ` +                expected: `  <<<UPDATE-ADDITIONS>>>  {{~> (lookup . "marker") ~}}`.trimStart() -        }, -        // Definition tags update -        { -            oldVersion: 0, -            newVersion: 12, -            old: ` +            }, +            // Definition tags update +            { +                oldVersion: 0, +                newVersion: 12, +                old: `  {{#*inline "glossary-single"}}      {{~#unless brief~}}          {{~#if definitionTags~}}<i>({{#each definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each}})</i> {{/if~}} @@ -779,7 +758,7 @@ async function testFieldTemplatesUpdate(extDir) {  {{~> (lookup . "marker") ~}}  `.trimStart(), -            expected: ` +                expected: `  {{#*inline "glossary-single"}}      {{~#unless brief~}}          {{~#scope~}} @@ -824,12 +803,12 @@ async function testFieldTemplatesUpdate(extDir) {  <<<UPDATE-ADDITIONS>>>  {{~> (lookup . "marker") ~}}  `.trimStart() -        }, -        // glossary and glossary-brief update -        { -            oldVersion: 7, -            newVersion: 12, -            old: ` +            }, +            // glossary and glossary-brief update +            { +                oldVersion: 7, +                newVersion: 12, +                old: `  {{#*inline "glossary-single"}}      {{~#unless brief~}}          {{~#scope~}} @@ -895,7 +874,7 @@ async function testFieldTemplatesUpdate(extDir) {  {{~> (lookup . "marker") ~}}`.trimStart(), -            expected: ` +                expected: `  {{#*inline "glossary-single"}}      {{~#unless brief~}}          {{~#scope~}} @@ -964,12 +943,12 @@ async function testFieldTemplatesUpdate(extDir) {  <<<UPDATE-ADDITIONS>>>  {{~> (lookup . "marker") ~}}`.trimStart() -        }, -        // formatGlossary update -        { -            oldVersion: 12, -            newVersion: 13, -            old: ` +            }, +            // formatGlossary update +            { +                oldVersion: 12, +                newVersion: 13, +                old: `  {{#*inline "example"}}      {{~#if (op "<=" glossary.length 1)~}}          {{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{/each}} @@ -982,7 +961,7 @@ async function testFieldTemplatesUpdate(extDir) {  {{~> (lookup . "marker") ~}}`.trimStart(), -            expected: ` +                expected: `  {{#*inline "example"}}      {{~#if (op "<=" glossary.length 1)~}}          {{#each glossary}}{{#formatGlossary ../dictionary}}{{{.}}}{{/formatGlossary}}{{/each}} @@ -995,12 +974,12 @@ async function testFieldTemplatesUpdate(extDir) {  <<<UPDATE-ADDITIONS>>>  {{~> (lookup . "marker") ~}}`.trimStart() -        }, -        // hasMedia/getMedia update -        { -            oldVersion: 12, -            newVersion: 13, -            old: ` +            }, +            // hasMedia/getMedia update +            { +                oldVersion: 12, +                newVersion: 13, +                old: `  {{#*inline "audio"}}      {{~#if definition.audioFileName~}}          [sound:{{definition.audioFileName}}] @@ -1023,7 +1002,7 @@ async function testFieldTemplatesUpdate(extDir) {  {{~> (lookup . "marker") ~}}`.trimStart(), -            expected: ` +                expected: `  {{#*inline "audio"}}      {{~#if (hasMedia "audio")~}}          [sound:{{#getMedia "audio"}}{{/getMedia}}] @@ -1048,12 +1027,12 @@ async function testFieldTemplatesUpdate(extDir) {  <<<UPDATE-ADDITIONS>>>  {{~> (lookup . "marker") ~}}`.trimStart() -        }, -        // hasMedia/getMedia update -        { -            oldVersion: 12, -            newVersion: 13, -            old: ` +            }, +            // hasMedia/getMedia update +            { +                oldVersion: 12, +                newVersion: 13, +                old: `  {{! Pitch Accents }}  {{#*inline "pitch-accent-item-downstep-notation"}}      {{~#scope~}} @@ -1166,7 +1145,7 @@ async function testFieldTemplatesUpdate(extDir) {  {{~> (lookup . "marker") ~}}`.trimStart(), -            expected: ` +                expected: `  {{! Pitch Accents }}  {{#*inline "pitch-accent-item"}}      {{~#pronunciation format=format reading=reading downstepPosition=position nasalPositions=nasalPositions devoicePositions=devoicePositions~}}{{~/pronunciation~}} @@ -1216,12 +1195,12 @@ async function testFieldTemplatesUpdate(extDir) {  <<<UPDATE-ADDITIONS>>>  {{~> (lookup . "marker") ~}}`.trimStart() -        }, -        // block helper update: furigana and furiganaPlain -        { -            oldVersion: 20, -            newVersion: 21, -            old: ` +            }, +            // block helper update: furigana and furiganaPlain +            { +                oldVersion: 20, +                newVersion: 21, +                old: `  {{#*inline "furigana"}}      {{~#if merge~}}          {{~#each definition.expressions~}} @@ -1263,7 +1242,7 @@ async function testFieldTemplatesUpdate(extDir) {  {{~> (lookup . "marker") ~}}`.trimStart(), -            expected: ` +                expected: `  {{#*inline "furigana"}}      {{~#if merge~}}          {{~#each definition.expressions~}} @@ -1304,12 +1283,12 @@ async function testFieldTemplatesUpdate(extDir) {  {{/inline}}  {{~> (lookup . "marker") ~}}`.trimStart() -        }, -        // block helper update: formatGlossary -        { -            oldVersion: 20, -            newVersion: 21, -            old: ` +            }, +            // block helper update: formatGlossary +            { +                oldVersion: 20, +                newVersion: 21, +                old: `  {{#*inline "glossary-single"}}      {{~#unless brief~}}          {{~#scope~}} @@ -1344,7 +1323,7 @@ async function testFieldTemplatesUpdate(extDir) {  {{~> (lookup . "marker") ~}}`.trimStart(), -            expected: ` +                expected: `  {{#*inline "glossary-single"}}      {{~#unless brief~}}          {{~#scope~}} @@ -1378,12 +1357,12 @@ async function testFieldTemplatesUpdate(extDir) {  {{/inline}}  {{~> (lookup . "marker") ~}}`.trimStart() -        }, -        // block helper update: set and get -        { -            oldVersion: 20, -            newVersion: 21, -            old: ` +            }, +            // block helper update: set and get +            { +                oldVersion: 20, +                newVersion: 21, +                old: `  {{#*inline "pitch-accent-item-disambiguation"}}      {{~#scope~}}          {{~#set "exclusive" (spread exclusiveExpressions exclusiveReadings)}}{{/set~}} @@ -1432,7 +1411,7 @@ async function testFieldTemplatesUpdate(extDir) {  {{~> (lookup . "marker") ~}}`.trimStart(), -            expected: ` +                expected: `  {{#*inline "pitch-accent-item-disambiguation"}}      {{~#scope~}}          {{~set "exclusive" (spread exclusiveExpressions exclusiveReadings)~}} @@ -1480,12 +1459,12 @@ async function testFieldTemplatesUpdate(extDir) {  {{/inline}}  {{~> (lookup . "marker") ~}}`.trimStart() -        }, -        // block helper update: hasMedia and getMedia -        { -            oldVersion: 20, -            newVersion: 21, -            old: ` +            }, +            // block helper update: hasMedia and getMedia +            { +                oldVersion: 20, +                newVersion: 21, +                old: `  {{#*inline "audio"}}      {{~#if (hasMedia "audio")~}}          [sound:{{#getMedia "audio"}}{{/getMedia}}] @@ -1524,7 +1503,7 @@ async function testFieldTemplatesUpdate(extDir) {  {{~> (lookup . "marker") ~}}`.trimStart(), -            expected: ` +                expected: `  {{#*inline "audio"}}      {{~#if (hasMedia "audio")~}}          [sound:{{getMedia "audio"}}] @@ -1562,48 +1541,47 @@ async function testFieldTemplatesUpdate(extDir) {  {{/inline}}  {{~> (lookup . "marker") ~}}`.trimStart() -        }, -        // block helper update: pronunciation -        { -            oldVersion: 20, -            newVersion: 21, -            old: ` +            }, +            // block helper update: pronunciation +            { +                oldVersion: 20, +                newVersion: 21, +                old: `  {{#*inline "pitch-accent-item"}}      {{~#pronunciation format=format reading=reading downstepPosition=position nasalPositions=nasalPositions devoicePositions=devoicePositions~}}{{~/pronunciation~}}  {{/inline}}  {{~> (lookup . "marker") ~}}`.trimStart(), -            expected: ` +                expected: `  {{#*inline "pitch-accent-item"}}      {{~pronunciation format=format reading=reading downstepPosition=position nasalPositions=nasalPositions devoicePositions=devoicePositions~}}  {{/inline}}  {{~> (lookup . "marker") ~}}`.trimStart() -        } -    ]; +            } +        ]; -    const updatesPattern = /<<<UPDATE-ADDITIONS>>>/g; -    for (const {old, expected, oldVersion, newVersion} of data) { -        const options = createOptionsTestData1(); -        options.profiles[0].options.anki.fieldTemplates = old; -        options.version = oldVersion; +        const updatesPattern = /<<<UPDATE-ADDITIONS>>>/g; +        for (const {old, expected, oldVersion, newVersion} of data) { +            const options = createOptionsTestData1(); +            options.profiles[0].options.anki.fieldTemplates = old; +            options.version = oldVersion; -        const expected2 = expected.replace(updatesPattern, getUpdateAdditions(oldVersion, newVersion)); +            const expected2 = expected.replace(updatesPattern, getUpdateAdditions(oldVersion, newVersion)); -        const optionsUpdated = clone(await optionsUtil.update(options, newVersion)); -        const fieldTemplatesActual = optionsUpdated.profiles[0].options.anki.fieldTemplates; -        assert.deepStrictEqual(fieldTemplatesActual, expected2); -    } +            const optionsUpdated = structuredClone(await optionsUtil.update(options, newVersion)); +            const fieldTemplatesActual = optionsUpdated.profiles[0].options.anki.fieldTemplates; +            expect(fieldTemplatesActual).toStrictEqual(expected2); +        } +    });  }  async function main() { -    const extDir = path.join(__dirname, '..', 'ext'); -    await testUpdate(extDir); -    await testDefault(extDir); -    await testFieldTemplatesUpdate(extDir); +    await testUpdate(); +    await testDefault(); +    await testFieldTemplatesUpdate();  } - -if (require.main === module) { testMain(main); } +await main(); diff --git a/test/profile-conditions-util.test.js b/test/profile-conditions-util.test.js new file mode 100644 index 00000000..ca8b00ef --- /dev/null +++ b/test/profile-conditions-util.test.js @@ -0,0 +1,1090 @@ +/* + * 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/>. + */ + +import {expect, test} from 'vitest'; +import {ProfileConditionsUtil} from '../ext/js/background/profile-conditions-util.js'; + +function testNormalizeContext() { +    test('NormalizeContext', () => { +        const data = [ +        // Empty +            { +                context: {}, +                expected: {flags: []} +            }, + +            // Domain normalization +            { +                context: {url: ''}, +                expected: {url: '', flags: []} +            }, +            { +                context: {url: 'http://example.com/'}, +                expected: {url: 'http://example.com/', domain: 'example.com', flags: []} +            }, +            { +                context: {url: 'http://example.com:1234/'}, +                expected: {url: 'http://example.com:1234/', domain: 'example.com', flags: []} +            }, +            { +                context: {url: 'http://user@example.com:1234/'}, +                expected: {url: 'http://user@example.com:1234/', domain: 'example.com', flags: []} +            } +        ]; + +        for (const {context, expected} of data) { +            const profileConditionsUtil = new ProfileConditionsUtil(); +            const actual = profileConditionsUtil.normalizeContext(context); +            expect(actual).toStrictEqual(expected); +        } +    }); +} + +function testSchemas() { +    test('Schemas', () => { +        const data = [ +        // Empty +            { +                conditionGroups: [], +                expectedSchema: {}, +                inputs: [ +                    {expected: true, context: {url: 'http://example.com/'}} +                ] +            }, +            { +                conditionGroups: [ +                    {conditions: []} +                ], +                expectedSchema: {}, +                inputs: [ +                    {expected: true, context: {url: 'http://example.com/'}} +                ] +            }, +            { +                conditionGroups: [ +                    {conditions: []}, +                    {conditions: []} +                ], +                expectedSchema: {}, +                inputs: [ +                    {expected: true, context: {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: [ +                        { +                            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: [ +                        { +                            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: [ +                        { +                            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: [ +                                {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: {}}, +                    {expected: true,  context: {flags: []}}, +                    {expected: false, context: {flags: ['test1']}}, +                    {expected: false, context: {flags: ['test1', 'test2']}}, +                    {expected: false, context: {flags: ['test1', 'test2', 'test3']}} +                ] +            }, +            { +                conditionGroups: [ +                    { +                        conditions: [ +                            { +                                type: 'flags', +                                operator: 'are', +                                value: 'test1, test2' +                            } +                        ] +                    } +                ], +                expectedSchema: { +                    required: ['flags'], +                    properties: { +                        flags: { +                            type: 'array', +                            maxItems: 2, +                            minItems: 2, +                            allOf: [ +                                {contains: {const: 'test1'}}, +                                {contains: {const: 'test2'}} +                            ] +                        } +                    } +                }, +                inputs: [ +                    {expected: false, context: {}}, +                    {expected: false, context: {flags: []}}, +                    {expected: false, context: {flags: ['test1']}}, +                    {expected: true,  context: {flags: ['test1', 'test2']}}, +                    {expected: false, context: {flags: ['test1', 'test2', 'test3']}} +                ] +            }, +            { +                conditionGroups: [ +                    { +                        conditions: [ +                            { +                                type: 'flags', +                                operator: 'areNot', +                                value: '' +                            } +                        ] +                    } +                ], +                expectedSchema: { +                    not: [ +                        { +                            required: ['flags'], +                            properties: { +                                flags: { +                                    type: 'array', +                                    maxItems: 0, +                                    minItems: 0 +                                } +                            } +                        } +                    ] +                }, +                inputs: [ +                    {expected: false, context: {}}, +                    {expected: false, context: {flags: []}}, +                    {expected: true,  context: {flags: ['test1']}}, +                    {expected: true,  context: {flags: ['test1', 'test2']}}, +                    {expected: true,  context: {flags: ['test1', 'test2', 'test3']}} +                ] +            }, +            { +                conditionGroups: [ +                    { +                        conditions: [ +                            { +                                type: 'flags', +                                operator: 'areNot', +                                value: 'test1, test2' +                            } +                        ] +                    } +                ], +                expectedSchema: { +                    not: [ +                        { +                            required: ['flags'], +                            properties: { +                                flags: { +                                    type: 'array', +                                    maxItems: 2, +                                    minItems: 2, +                                    allOf: [ +                                        {contains: {const: 'test1'}}, +                                        {contains: {const: 'test2'}} +                                    ] +                                } +                            } +                        } +                    ] +                }, +                inputs: [ +                    {expected: true,  context: {}}, +                    {expected: true,  context: {flags: []}}, +                    {expected: true,  context: {flags: ['test1']}}, +                    {expected: false, context: {flags: ['test1', 'test2']}}, +                    {expected: true,  context: {flags: ['test1', 'test2', 'test3']}} +                ] +            }, +            { +                conditionGroups: [ +                    { +                        conditions: [ +                            { +                                type: 'flags', +                                operator: 'include', +                                value: '' +                            } +                        ] +                    } +                ], +                expectedSchema: { +                    required: ['flags'], +                    properties: { +                        flags: { +                            type: 'array', +                            minItems: 0 +                        } +                    } +                }, +                inputs: [ +                    {expected: true,  context: {}}, +                    {expected: true,  context: {flags: []}}, +                    {expected: true,  context: {flags: ['test1']}}, +                    {expected: true,  context: {flags: ['test1', 'test2']}}, +                    {expected: true,  context: {flags: ['test1', 'test2', 'test3']}} +                ] +            }, +            { +                conditionGroups: [ +                    { +                        conditions: [ +                            { +                                type: 'flags', +                                operator: 'include', +                                value: 'test1, test2' +                            } +                        ] +                    } +                ], +                expectedSchema: { +                    required: ['flags'], +                    properties: { +                        flags: { +                            type: 'array', +                            minItems: 2, +                            allOf: [ +                                {contains: {const: 'test1'}}, +                                {contains: {const: 'test2'}} +                            ] +                        } +                    } +                }, +                inputs: [ +                    {expected: false, context: {}}, +                    {expected: false, context: {flags: []}}, +                    {expected: false, context: {flags: ['test1']}}, +                    {expected: true,  context: {flags: ['test1', 'test2']}}, +                    {expected: true,  context: {flags: ['test1', 'test2', 'test3']}} +                ] +            }, +            { +                conditionGroups: [ +                    { +                        conditions: [ +                            { +                                type: 'flags', +                                operator: 'notInclude', +                                value: '' +                            } +                        ] +                    } +                ], +                expectedSchema: { +                    required: ['flags'], +                    properties: { +                        flags: { +                            type: 'array' +                        } +                    } +                }, +                inputs: [ +                    {expected: true,  context: {}}, +                    {expected: true,  context: {flags: []}}, +                    {expected: true,  context: {flags: ['test1']}}, +                    {expected: true,  context: {flags: ['test1', 'test2']}}, +                    {expected: true,  context: {flags: ['test1', 'test2', 'test3']}} +                ] +            }, +            { +                conditionGroups: [ +                    { +                        conditions: [ +                            { +                                type: 'flags', +                                operator: 'notInclude', +                                value: 'test1, test2' +                            } +                        ] +                    } +                ], +                expectedSchema: { +                    required: ['flags'], +                    properties: { +                        flags: { +                            type: 'array', +                            not: [ +                                {contains: {const: 'test1'}}, +                                {contains: {const: 'test2'}} +                            ] +                        } +                    } +                }, +                inputs: [ +                    {expected: true,  context: {}}, +                    {expected: true,  context: {flags: []}}, +                    {expected: false, context: {flags: ['test1']}}, +                    {expected: false, context: {flags: ['test1', 'test2']}}, +                    {expected: false, context: {flags: ['test1', '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 ProfileConditionsUtil(); +            const schema = profileConditionsUtil.createSchema(conditionGroups); +            if (typeof expectedSchema !== 'undefined') { +                expect(schema.schema).toStrictEqual(expectedSchema); +            } +            if (Array.isArray(inputs)) { +                for (const {expected, context} of inputs) { +                    const normalizedContext = profileConditionsUtil.normalizeContext(context); +                    const actual = schema.isValid(normalizedContext); +                    expect(actual).toStrictEqual(expected); +                } +            } +        } +    }); +} + + +function main() { +    testNormalizeContext(); +    testSchemas(); +} + +main(); diff --git a/test/test-all.js b/test/test-all.js deleted file mode 100644 index d187879a..00000000 --- a/test/test-all.js +++ /dev/null @@ -1,67 +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'); - - -function main() { -    const args = getArgs(process.argv.slice(2), new Map([ -        ['skip', []], -        [null, []] -    ])); -    const directories = args.get(null); -    const skip = new Set([__filename, ...args.get('skip')].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 ba937302..00000000 --- a/test/test-anki-note-builder.js +++ /dev/null @@ -1,308 +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'); - - -function clone(value) { -    return JSON.parse(JSON.stringify(value)); -} - -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' -    ]); - -    const [ -        JapaneseUtil, -        AnkiNoteBuilder, -        AnkiTemplateRenderer -    ] = vm.get([ -        'JapaneseUtil', -        'AnkiNoteBuilder', -        'AnkiTemplateRenderer' -    ]); - -    class TemplateRendererProxy { -        constructor() { -            this._preparePromise = null; -            this._ankiTemplateRenderer = new AnkiTemplateRenderer(); -        } - -        async render(template, data, type) { -            await this._prepare(); -            return await this._ankiTemplateRenderer.templateRenderer.render(template, data, type); -        } - -        async renderMulti(items) { -            await this._prepare(); -            return await this._serializeMulti(this._ankiTemplateRenderer.templateRenderer.renderMulti(items)); -        } - -        _prepare() { -            if (this._preparePromise === null) { -                this._preparePromise = this._prepareInternal(); -            } -            return this._preparePromise; -        } - -        async _prepareInternal() { -            await this._ankiTemplateRenderer.prepare(); -        } - -        _serializeError(error) { -            try { -                if (typeof error === 'object' && error !== null) { -                    const result = { -                        name: error.name, -                        message: error.message, -                        stack: error.stack -                    }; -                    if (Object.prototype.hasOwnProperty.call(error, 'data')) { -                        result.data = error.data; -                    } -                    return result; -                } -            } catch (e) { -                // NOP -            } -            return { -                value: error, -                hasValue: true -            }; -        } - -        _serializeMulti(array) { -            for (let i = 0, ii = array.length; i < ii; ++i) { -                const value = array[i]; -                const {error} = value; -                if (typeof error !== 'undefined') { -                    value.error = this._serializeError(error); -                } -            } -            return array; -        } -    } -    vm.set({TemplateRendererProxy}); - -    return {vm, AnkiNoteBuilder, JapaneseUtil}; -} - -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 []; -    } -} - -async function getRenderResults(dictionaryEntries, type, mode, template, AnkiNoteBuilder, JapaneseUtil, write) { -    const markers = getFieldMarkers(type); -    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' -        }; -        const {note: {fields: noteFields}, errors} = await ankiNoteBuilder.createNote({ -            dictionaryEntry, -            mode: null, -            context, -            template, -            deckName: 'deckName', -            modelName: 'modelName', -            fields, -            tags: ['yomichan'], -            checkForDuplicates: true, -            duplicateScope: 'collection', -            duplicateScopeCheckAllModels: false, -            resultOutputMode: mode, -            glossaryLayoutMode: 'default', -            compactTags: false -        }); -        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; -                    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; -                    const options = vm.buildOptions(optionsPresets, test.options); -                    const dictionaryEntries = clone(await vm.translator.findKanji(text, options)); -                    const results = clone(await getRenderResults(dictionaryEntries, 'kanji', null, 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-build-libs.js b/test/test-build-libs.js deleted file mode 100644 index 496f43f8..00000000 --- a/test/test-build-libs.js +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright (C) 2023  Yomitan Authors - * Copyright (C) 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 assert = require('assert'); -const {getBuildTargets} = require('../dev/build-libs'); - -async function main() { -    try { -        for (const {path: path2, build} of getBuildTargets()) { -            let expectedContent = await build(); -            if (typeof expectedContent !== 'string') { -                // Buffer -                expectedContent = expectedContent.toString('utf8'); -            } -            const actualContent = fs.readFileSync(path2, {encoding: 'utf8'}); -            assert.strictEqual(actualContent, expectedContent); -        } -    } catch (e) { -        console.error(e); -        process.exit(-1); -        return; -    } -    process.exit(0); -} - -if (require.main === module) { main(); } diff --git a/test/test-cache-map.js b/test/test-cache-map.js deleted file mode 100644 index 863c6ace..00000000 --- a/test/test-cache-map.js +++ /dev/null @@ -1,132 +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' -]); -const CacheMap = vm.get('CacheMap'); - - -function testConstructor() { -    const data = [ -        [false, () => new CacheMap(0)], -        [false, () => new CacheMap(1)], -        [false, () => new CacheMap(Number.MAX_VALUE)], -        [true,  () => new CacheMap(-1)], -        [true,  () => new CacheMap(1.5)], -        [true,  () => new CacheMap(Number.NaN)], -        [true,  () => new CacheMap(Number.POSITIVE_INFINITY)], -        [true,  () => new CacheMap('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 CacheMap(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); break; -                case 'set': returnValue = cache.set(...args); break; -                case 'has': returnValue = cache.has(...args); break; -                case 'clear': returnValue = cache.clear(...args); 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 b42f8cf2..00000000 --- a/test/test-core.js +++ /dev/null @@ -1,292 +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' -]); -const [DynamicProperty, deepEqual] = vm.get(['DynamicProperty', 'deepEqual']); - - -function testDynamicProperty() { -    const data = [ -        { -            initialValue: 0, -            operations: [ -                { -                    operation: null, -                    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 DynamicProperty(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 = deepEqual(value1, value2); -        assert.strictEqual(actual1, expected, `Failed for test ${index}`); - -        const actual2 = deepEqual(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 c4cd504a..00000000 --- a/test/test-database.js +++ /dev/null @@ -1,887 +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/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' -]); -const DictionaryImporter = vm.get('DictionaryImporter'); -const DictionaryDatabase = vm.get('DictionaryDatabase'); - - -function createTestDictionaryArchive(dictionary, dictionaryName) { -    const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', dictionary); -    return createDictionaryArchive(dictionaryDirectory, dictionaryName); -} - - -function createDictionaryImporter(onProgress) { -    const dictionaryImporterMediaLoader = new DatabaseVMDictionaryImporterMediaLoader(); -    return new DictionaryImporter(dictionaryImporterMediaLoader, (...args) => { -        const {stepIndex, stepCount, index, count} = args[0]; -        assert.ok(stepIndex < stepCount); -        assert.ok(index <= count); -        if (typeof onProgress === 'function') { -            onProgress(...args); -        } -    }); -} - - -function countDictionaryDatabaseEntriesWithTerm(dictionaryDatabaseEntries, term) { -    return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.term === term ? 1 : 0)), 0); -} - -function countDictionaryDatabaseEntriesWithReading(dictionaryDatabaseEntries, reading) { -    return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.reading === reading ? 1 : 0)), 0); -} - -function countMetasWithMode(metas, mode) { -    return metas.reduce((i, v) => (i + (v.mode === mode ? 1 : 0)), 0); -} - -function countKanjiWithCharacter(kanji, character) { -    return kanji.reduce((i, v) => (i + (v.character === character ? 1 : 0)), 0); -} - - -function clearDatabase(timeout) { -    return new Promise((resolve, reject) => { -        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()) { -                await new Promise((resolve2, reject2) => { -                    const request = indexedDB.deleteDatabase(name); -                    request.onerror = (e) => reject2(e); -                    request.onsuccess = () => resolve2(); -                }); -            } -            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 DictionaryDatabase(); -    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(); -} - -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} -    }); -} - -async function testFindTermsBulkTest1(database, titles) { -    const data = [ -        { -            inputs: [ -                { -                    matchType: null, -                    termList: ['打', '打つ', '打ち込む'] -                }, -                { -                    matchType: null, -                    termList: ['だ', 'ダース', 'うつ', 'ぶつ', 'うちこむ', 'ぶちこむ'] -                }, -                { -                    matchType: 'prefix', -                    termList: ['打'] -                } -            ], -            expectedResults: { -                total: 10, -                terms: [ -                    ['打', 2], -                    ['打つ', 4], -                    ['打ち込む', 4] -                ], -                readings: [ -                    ['だ', 1], -                    ['ダース', 1], -                    ['うつ', 2], -                    ['ぶつ', 2], -                    ['うちこむ', 2], -                    ['ぶちこむ', 2] -                ] -            } -        }, -        { -            inputs: [ -                { -                    matchType: null, -                    termList: ['込む'] -                } -            ], -            expectedResults: { -                total: 0, -                terms: [], -                readings: [] -            } -        }, -        { -            inputs: [ -                { -                    matchType: 'suffix', -                    termList: ['込む'] -                } -            ], -            expectedResults: { -                total: 4, -                terms: [ -                    ['打ち込む', 4] -                ], -                readings: [ -                    ['うちこむ', 2], -                    ['ぶちこむ', 2] -                ] -            } -        }, -        { -            inputs: [ -                { -                    matchType: null, -                    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); -            } -        } -    } -} - -async function testTindTermsExactBulk1(database, titles) { -    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); -            } -        } -    } -} - -async function testFindTermsBySequenceBulk1(database, mainDictionary) { -    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); -            } -        } -    } -} - -async function testFindTermMetaBulk1(database, titles) { -    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); -            } -        } -    } -} - -async function testFindKanjiBulk1(database, titles) { -    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); -            } -        } -    } -} - -async function testFindKanjiMetaBulk1(database, titles) { -    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); -            } -        } -    } -} - -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 DictionaryDatabase(); - -    // Error: not prepared -    await assert.rejects(async () => await dictionaryDatabase.deleteDictionary(title, 1000)); -    await assert.rejects(async () => await dictionaryDatabase.findTermsBulk(['?'], titles, null)); -    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, true)); -    await assert.rejects(async () => await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {})); - -    await dictionaryDatabase.prepare(); - -    // Error: already prepared -    await assert.rejects(async () => await dictionaryDatabase.prepare()); - -    await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {}); - -    // Error: dictionary already imported -    await assert.rejects(async () => await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {})); - -    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 DictionaryDatabase(); -    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, {}); -        } 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 = error.message; -            assert.ok(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 a2458b61..00000000 --- a/test/test-document-util.js +++ /dev/null @@ -1,270 +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 { -    constructor(x, y, width, height) { -        this._x = x; -        this._y = y; -        this._width = width; -        this._height = height; -    } - -    get x() { return this._x; } -    get y() { return this._y; } -    get width() { return this._width; } -    get height() { return this._height; } -    get left() { return this._x + Math.min(0, this._width); } -    get right() { return this._x + Math.max(0, this._width); } -    get top() { return this._y + Math.min(0, this._height); } -    get bottom() { return this._y + Math.max(0, this._height); } -} - - -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; -} - -function querySelectorChildOrSelf(element, selector) { -    return selector ? element.querySelector(selector) : element; -} - -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); -} - -function getPrototypeOfOrNull(value) { -    try { -        return Object.getPrototypeOf(value); -    } catch (e) { -        return null; -    } -} - -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' -    ]); -    const [DOMTextScanner, TextSourceRange, TextSourceElement, DocumentUtil] = vm.get([ -        'DOMTextScanner', -        'TextSourceRange', -        'TextSourceElement', -        'DocumentUtil' -    ]); - -    try { -        await testDocumentTextScanningFunctions(dom, {DocumentUtil, TextSourceRange, TextSourceElement}); -        await testTextSourceRangeSeekFunctions(dom, {DOMTextScanner}); -    } finally { -        window.close(); -    } -} - -async function testDocumentTextScanningFunctions(dom, {DocumentUtil, TextSourceRange, TextSourceElement}) { -    const document = dom.window.document; - -    for (const testElement of document.querySelectorAll('.test[data-test-type=scan]')) { -        // Get test parameters -        let { -            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)); - -        startOffset = parseInt(startOffset, 10); -        endOffset = parseInt(endOffset, 10); -        sentenceScanExtent = parseInt(sentenceScanExtent, 10); -        terminateAtNewlines = (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(imposter ? imposter : startNode, startOffset); -            range.setEnd(imposter ? imposter : startNode, endOffset); - -            // Override getClientRects to return a rect guaranteed to contain (x, y) -            range.getClientRects = () => [new DOMRect(x - 1, y - 1, 2, 2)]; -            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, -            sentenceScanExtent, -            terminateAtNewlines, -            terminatorMap, -            forwardQuoteMap, -            backwardQuoteMap -        ).text; -        assert.strictEqual(sentenceActual, sentence); - -        // Clean -        source.cleanup(); -    } -} - -async function testTextSourceRangeSeekFunctions(dom, {DOMTextScanner}) { -    const document = dom.window.document; - -    for (const testElement of document.querySelectorAll('.test[data-test-type=text-source-range-seek]')) { -        // Get test parameters -        let { -            seekNodeSelector, -            seekNodeIsText, -            seekOffset, -            seekLength, -            seekDirection, -            expectedResultNodeSelector, -            expectedResultNodeIsText, -            expectedResultOffset, -            expectedResultContent -        } = testElement.dataset; - -        seekOffset = parseInt(seekOffset, 10); -        seekLength = parseInt(seekLength, 10); -        expectedResultOffset = parseInt(expectedResultOffset, 10); - -        let seekNode = testElement.querySelector(seekNodeSelector); -        if (seekNodeIsText === 'true') { -            seekNode = seekNode.firstChild; -        } - -        let expectedResultNode = testElement.querySelector(expectedResultNodeSelector); -        if (expectedResultNodeIsText === 'true') { -            expectedResultNode = expectedResultNode.firstChild; -        } - -        const {node, offset, content} = ( -            seekDirection === 'forward' ? -            new DOMTextScanner(seekNode, seekOffset, true, false).seek(seekLength) : -            new DOMTextScanner(seekNode, seekOffset, true, false).seek(-seekLength) -        ); - -        assert.strictEqual(node, expectedResultNode); -        assert.strictEqual(offset, expectedResultOffset); -        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 34f1eb26..00000000 --- a/test/test-hotkey-util.js +++ /dev/null @@ -1,173 +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'); - - -function clone(value) { -    return JSON.parse(JSON.stringify(value)); -} - -function createHotkeyUtil() { -    const vm = new VM(); -    vm.execute(['js/input/hotkey-util.js']); -    const [HotkeyUtil] = vm.get(['HotkeyUtil']); -    return new HotkeyUtil(); -} - - -function testCommandConversions() { -    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() { -    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() { -    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 4395a11e..00000000 --- a/test/test-japanese-util.js +++ /dev/null @@ -1,881 +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' -]); -const [JapaneseUtil, TextSourceMap, wanakana] = vm.get(['JapaneseUtil', 'TextSourceMap', 'wanakana']); -const jp = new JapaneseUtil(wanakana); - - -function testIsCodePointKanji() { -    const data = [ -        ['力方', true], -        ['\u53f1\u{20b9f}', true], -        ['かたカタ々kata、。?,.?', false], -        ['逸逸', true] -    ]; - -    for (const [characters, expected] of data) { -        for (const character of characters) { -            const codePoint = character.codePointAt(0); -            const actual = jp.isCodePointKanji(codePoint); -            assert.strictEqual(actual, expected, `isCodePointKanji failed for ${character} (\\u{${codePoint.toString(16)}})`); -        } -    } -} - -function testIsCodePointKana() { -    const data = [ -        ['かたカタ', true], -        ['力方々kata、。?,.?', false], -        ['\u53f1\u{20b9f}', false] -    ]; - -    for (const [characters, expected] of data) { -        for (const character of characters) { -            const codePoint = character.codePointAt(0); -            const actual = jp.isCodePointKana(codePoint); -            assert.strictEqual(actual, expected, `isCodePointKana failed for ${character} (\\u{${codePoint.toString(16)}})`); -        } -    } -} - -function testIsCodePointJapanese() { -    const data = [ -        ['かたカタ力方々、。?', true], -        ['\u53f1\u{20b9f}', true], -        ['kata,.?', false], -        ['逸逸', true] -    ]; - -    for (const [characters, expected] of data) { -        for (const character of characters) { -            const codePoint = character.codePointAt(0); -            const actual = jp.isCodePointJapanese(codePoint); -            assert.strictEqual(actual, expected, `isCodePointJapanese failed for ${character} (\\u{${codePoint.toString(16)}})`); -        } -    } -} - -function testIsStringEntirelyKana() { -    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() { -    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() { -    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() { -    const data = [ -        ['かたかな', 'カタカナ'], -        ['ひらがな', 'ヒラガナ'], -        ['カタカナ', 'カタカナ'], -        ['ヒラガナ', 'ヒラガナ'], -        ['カタカナかたかな', 'カタカナカタカナ'], -        ['ヒラガナひらがな', 'ヒラガナヒラガナ'], -        ['chikaraちからチカラ力', 'chikaraチカラチカラ力'], -        ['katakana', 'katakana'], -        ['hiragana', 'hiragana'] -    ]; - -    for (const [string, expected] of data) { -        assert.strictEqual(jp.convertHiraganaToKatakana(string), expected); -    } -} - -function testConvertToRomaji() { -    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() { -    const data = [ -        ['0123456789', '0123456789'], -        ['abcdefghij', 'abcdefghij'], -        ['カタカナ', 'カタカナ'], -        ['ひらがな', 'ひらがな'] -    ]; - -    for (const [string, expected] of data) { -        assert.strictEqual(jp.convertNumericToFullWidth(string), expected); -    } -} - -function testConvertHalfWidthKanaToFullWidth() { -    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 TextSourceMap(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 TextSourceMap(string, expectedSourceMapping))); -        } -    } -} - -function testConvertAlphabeticToKana() { -    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 TextSourceMap(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 TextSourceMap(string, expectedSourceMapping))); -        } -    } -} - -function testDistributeFurigana() { -    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() { -    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() { -    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 TextSourceMap(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 TextSourceMap(text, expectedSourceMapping))); -        } -    } -} - -function testIsMoraPitchHigh() { -    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() { -    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 35ecc5e9..00000000 --- a/test/test-json-schema.js +++ /dev/null @@ -1,1011 +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/general/cache-map.js', -    'js/data/json-schema.js' -]); -const JsonSchema = vm.get('JsonSchema'); - - -function schemaValidate(schema, value) { -    return new JsonSchema(schema).isValid(value); -} - -function getValidValueOrDefault(schema, value) { -    return new JsonSchema(schema).getValidValueOrDefault(value); -} - -function createProxy(schema, value) { -    return new JsonSchema(schema).createProxy(value); -} - -function clone(value) { -    return JSON.parse(JSON.stringify(value)); -} - - -function testValidate1() { -    const schema = { -        allOf: [ -            { -                type: 'number' -            }, -            { -                anyOf: [ -                    {minimum: 10, maximum: 100}, -                    {minimum: -100, maximum: -10} -                ] -            }, -            { -                oneOf: [ -                    {multipleOf: 3}, -                    {multipleOf: 5} -                ] -            }, -            { -                not: [ -                    {multipleOf: 20} -                ] -            } -        ] -    }; - -    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); -    } -} - -function testValidate2() { -    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() { -    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: '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() { -    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 b52152de..00000000 --- a/test/test-manifest.js +++ /dev/null @@ -1,44 +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'); - - -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 d225e893..00000000 --- a/test/test-object-property-accessor.js +++ /dev/null @@ -1,416 +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'); -const ObjectPropertyAccessor = vm.get('ObjectPropertyAccessor'); - - -function createTestObject() { -    return { -        0: null, -        value1: { -            value2: {}, -            value3: [], -            value4: null -        }, -        value5: [ -            {}, -            [], -            null -        ] -    }; -} - - -function testGet1() { -    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 ObjectPropertyAccessor(object); -        const expected = getExpected(object); - -        assert.strictEqual(accessor.get(pathArray), expected); -    } -} - -function testGet2() { -    const object = createTestObject(); -    const accessor = new ObjectPropertyAccessor(object); - -    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 = {}; -    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 ObjectPropertyAccessor(object); - -        accessor.set(pathArray, testValue); -        assert.strictEqual(accessor.get(pathArray), testValue); -    } -} - -function testSet2() { -    const object = createTestObject(); -    const accessor = new ObjectPropertyAccessor(object); - -    const testValue = {}; -    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() { -    const hasOwn = (object, property) => Object.prototype.hasOwnProperty.call(object, property); - -    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 ObjectPropertyAccessor(object); - -        accessor.delete(pathArray); -        assert.ok(validate(object)); -    } -} - -function testDelete2() { -    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 ObjectPropertyAccessor(object); - -        assert.throws(() => accessor.delete(pathArray), {message}); -    } -} - - -function testSwap1() { -    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 ObjectPropertyAccessor(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() { -    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 ObjectPropertyAccessor(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() { -    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(ObjectPropertyAccessor.getPathString(pathArray), expected); -    } -} - -function testGetPathString2() { -    const data = [ -        [[1.5], 'Invalid index'], -        [[null], 'Invalid type: object'] -    ]; - -    for (const [pathArray, message] of data) { -        assert.throws(() => ObjectPropertyAccessor.getPathString(pathArray), {message}); -    } -} - - -function testGetPathArray1() { -    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) { -        vm.assert.deepStrictEqual(ObjectPropertyAccessor.getPathArray(pathString), expected); -    } -} - -function testGetPathArray2() { -    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(() => ObjectPropertyAccessor.getPathArray(pathString), {message}); -    } -} - - -function testHasProperty() { -    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) { -        assert.strictEqual(ObjectPropertyAccessor.hasProperty(object, property), expected); -    } -} - -function testIsValidPropertyType() { -    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) { -        assert.strictEqual(ObjectPropertyAccessor.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 d5187425..00000000 --- a/test/test-profile-conditions-util.js +++ /dev/null @@ -1,1099 +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/general/cache-map.js', -    'js/data/json-schema.js', -    'js/background/profile-conditions-util.js' -]); -const [ProfileConditionsUtil] = vm.get(['ProfileConditionsUtil']); - - -function testNormalizeContext() { -    const data = [ -        // Empty -        { -            context: {}, -            expected: {flags: []} -        }, - -        // Domain normalization -        { -            context: {url: ''}, -            expected: {url: '', flags: []} -        }, -        { -            context: {url: 'http://example.com/'}, -            expected: {url: 'http://example.com/', domain: 'example.com', flags: []} -        }, -        { -            context: {url: 'http://example.com:1234/'}, -            expected: {url: 'http://example.com:1234/', domain: 'example.com', flags: []} -        }, -        { -            context: {url: 'http://user@example.com:1234/'}, -            expected: {url: 'http://user@example.com:1234/', domain: 'example.com', flags: []} -        } -    ]; - -    for (const {context, expected} of data) { -        const profileConditionsUtil = new ProfileConditionsUtil(); -        const actual = profileConditionsUtil.normalizeContext(context); -        vm.assert.deepStrictEqual(actual, expected); -    } -} - -function testSchemas() { -    const data = [ -        // Empty -        { -            conditionGroups: [], -            expectedSchema: {}, -            inputs: [ -                {expected: true, context: {url: 'http://example.com/'}} -            ] -        }, -        { -            conditionGroups: [ -                {conditions: []} -            ], -            expectedSchema: {}, -            inputs: [ -                {expected: true, context: {url: 'http://example.com/'}} -            ] -        }, -        { -            conditionGroups: [ -                {conditions: []}, -                {conditions: []} -            ], -            expectedSchema: {}, -            inputs: [ -                {expected: true, context: {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: [ -                    { -                        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: [ -                    { -                        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: [ -                    { -                        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: [ -                            {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: {}}, -                {expected: true,  context: {flags: []}}, -                {expected: false, context: {flags: ['test1']}}, -                {expected: false, context: {flags: ['test1', 'test2']}}, -                {expected: false, context: {flags: ['test1', 'test2', 'test3']}} -            ] -        }, -        { -            conditionGroups: [ -                { -                    conditions: [ -                        { -                            type: 'flags', -                            operator: 'are', -                            value: 'test1, test2' -                        } -                    ] -                } -            ], -            expectedSchema: { -                required: ['flags'], -                properties: { -                    flags: { -                        type: 'array', -                        maxItems: 2, -                        minItems: 2, -                        allOf: [ -                            {contains: {const: 'test1'}}, -                            {contains: {const: 'test2'}} -                        ] -                    } -                } -            }, -            inputs: [ -                {expected: false, context: {}}, -                {expected: false, context: {flags: []}}, -                {expected: false, context: {flags: ['test1']}}, -                {expected: true,  context: {flags: ['test1', 'test2']}}, -                {expected: false, context: {flags: ['test1', 'test2', 'test3']}} -            ] -        }, -        { -            conditionGroups: [ -                { -                    conditions: [ -                        { -                            type: 'flags', -                            operator: 'areNot', -                            value: '' -                        } -                    ] -                } -            ], -            expectedSchema: { -                not: [ -                    { -                        required: ['flags'], -                        properties: { -                            flags: { -                                type: 'array', -                                maxItems: 0, -                                minItems: 0 -                            } -                        } -                    } -                ] -            }, -            inputs: [ -                {expected: false, context: {}}, -                {expected: false, context: {flags: []}}, -                {expected: true,  context: {flags: ['test1']}}, -                {expected: true,  context: {flags: ['test1', 'test2']}}, -                {expected: true,  context: {flags: ['test1', 'test2', 'test3']}} -            ] -        }, -        { -            conditionGroups: [ -                { -                    conditions: [ -                        { -                            type: 'flags', -                            operator: 'areNot', -                            value: 'test1, test2' -                        } -                    ] -                } -            ], -            expectedSchema: { -                not: [ -                    { -                        required: ['flags'], -                        properties: { -                            flags: { -                                type: 'array', -                                maxItems: 2, -                                minItems: 2, -                                allOf: [ -                                    {contains: {const: 'test1'}}, -                                    {contains: {const: 'test2'}} -                                ] -                            } -                        } -                    } -                ] -            }, -            inputs: [ -                {expected: true,  context: {}}, -                {expected: true,  context: {flags: []}}, -                {expected: true,  context: {flags: ['test1']}}, -                {expected: false, context: {flags: ['test1', 'test2']}}, -                {expected: true,  context: {flags: ['test1', 'test2', 'test3']}} -            ] -        }, -        { -            conditionGroups: [ -                { -                    conditions: [ -                        { -                            type: 'flags', -                            operator: 'include', -                            value: '' -                        } -                    ] -                } -            ], -            expectedSchema: { -                required: ['flags'], -                properties: { -                    flags: { -                        type: 'array', -                        minItems: 0 -                    } -                } -            }, -            inputs: [ -                {expected: true,  context: {}}, -                {expected: true,  context: {flags: []}}, -                {expected: true,  context: {flags: ['test1']}}, -                {expected: true,  context: {flags: ['test1', 'test2']}}, -                {expected: true,  context: {flags: ['test1', 'test2', 'test3']}} -            ] -        }, -        { -            conditionGroups: [ -                { -                    conditions: [ -                        { -                            type: 'flags', -                            operator: 'include', -                            value: 'test1, test2' -                        } -                    ] -                } -            ], -            expectedSchema: { -                required: ['flags'], -                properties: { -                    flags: { -                        type: 'array', -                        minItems: 2, -                        allOf: [ -                            {contains: {const: 'test1'}}, -                            {contains: {const: 'test2'}} -                        ] -                    } -                } -            }, -            inputs: [ -                {expected: false, context: {}}, -                {expected: false, context: {flags: []}}, -                {expected: false, context: {flags: ['test1']}}, -                {expected: true,  context: {flags: ['test1', 'test2']}}, -                {expected: true,  context: {flags: ['test1', 'test2', 'test3']}} -            ] -        }, -        { -            conditionGroups: [ -                { -                    conditions: [ -                        { -                            type: 'flags', -                            operator: 'notInclude', -                            value: '' -                        } -                    ] -                } -            ], -            expectedSchema: { -                required: ['flags'], -                properties: { -                    flags: { -                        type: 'array' -                    } -                } -            }, -            inputs: [ -                {expected: true,  context: {}}, -                {expected: true,  context: {flags: []}}, -                {expected: true,  context: {flags: ['test1']}}, -                {expected: true,  context: {flags: ['test1', 'test2']}}, -                {expected: true,  context: {flags: ['test1', 'test2', 'test3']}} -            ] -        }, -        { -            conditionGroups: [ -                { -                    conditions: [ -                        { -                            type: 'flags', -                            operator: 'notInclude', -                            value: 'test1, test2' -                        } -                    ] -                } -            ], -            expectedSchema: { -                required: ['flags'], -                properties: { -                    flags: { -                        type: 'array', -                        not: [ -                            {contains: {const: 'test1'}}, -                            {contains: {const: 'test2'}} -                        ] -                    } -                } -            }, -            inputs: [ -                {expected: true,  context: {}}, -                {expected: true,  context: {flags: []}}, -                {expected: false, context: {flags: ['test1']}}, -                {expected: false, context: {flags: ['test1', 'test2']}}, -                {expected: false, context: {flags: ['test1', '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 ProfileConditionsUtil(); -        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 dd8d3bbd..00000000 --- a/test/test-text-source-map.js +++ /dev/null @@ -1,235 +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']); -const TextSourceMap = vm.get('TextSourceMap'); - - -function testSource() { -    const data = [ -        ['source1'], -        ['source2'], -        ['source3'] -    ]; - -    for (const [source] of data) { -        const sourceMap = new TextSourceMap(source); -        assert.strictEqual(source, sourceMap.source); -    } -} - -function testEquals() { -    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 TextSourceMap(source1, mapping1); -        const sourceMap2 = new TextSourceMap(source2, mapping2); -        assert.ok(sourceMap1.equals(sourceMap1)); -        assert.ok(sourceMap2.equals(sourceMap2)); -        assert.strictEqual(sourceMap1.equals(sourceMap2), expectedEquals); -    } -} - -function testGetSourceLength() { -    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 TextSourceMap(source, mapping); -        assert.strictEqual(sourceMap.getSourceLength(finalLength), expectedValue); -    } -} - -function testCombineInsert() { -    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 TextSourceMap(source, mapping); -        const expectedSourceMap = new TextSourceMap(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 485eb665..00000000 --- a/test/test-translator.js +++ /dev/null @@ -1,94 +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'); - - -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; -                    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; -                    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), null))); -                    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 b4ec4d7d..00000000 --- a/test/test-workers.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 fs = require('fs'); -const path = require('path'); -const {JSDOM} = require('jsdom'); -const {VM} = require('../dev/vm'); -const assert = require('assert'); - - -class StubClass { -    prepare() { -        // NOP -    } -} - - -function loadEslint() { -    return JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.eslintrc.json'), {encoding: 'utf8'})); -} - -function filterScriptPaths(scriptPaths) { -    const extDirName = 'ext'; -    return scriptPaths.filter((src) => !src.startsWith('/lib/')).map((src) => `${extDirName}${src}`); -} - -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(); -    } -} - -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'; -} - -function getImportedScripts(scriptPath, fields) { -    const importedScripts = []; - -    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 = 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} -    ); -} - -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 = 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(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(); } diff --git a/test/text-source-map.test.js b/test/text-source-map.test.js new file mode 100644 index 00000000..aeaba000 --- /dev/null +++ b/test/text-source-map.test.js @@ -0,0 +1,237 @@ +/* + * 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/>. + */ + +import {expect, test} from 'vitest'; +import {TextSourceMap} from '../ext/js/general/text-source-map.js'; + +function testSource() { +    test('Source', () => { +        const data = [ +            ['source1'], +            ['source2'], +            ['source3'] +        ]; + +        for (const [source] of data) { +            const sourceMap = new TextSourceMap(source); +            expect(source).toStrictEqual(sourceMap.source); +        } +    }); +} + +function testEquals() { +    test('Equals', () => { +        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 TextSourceMap(source1, mapping1); +            const sourceMap2 = new TextSourceMap(source2, mapping2); +            expect(sourceMap1.equals(sourceMap1)).toBe(true); +            expect(sourceMap2.equals(sourceMap2)).toBe(true); +            expect(sourceMap1.equals(sourceMap2)).toStrictEqual(expectedEquals); +        } +    }); +} + +function testGetSourceLength() { +    test('GetSourceLength', () => { +        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 TextSourceMap(source, mapping); +            expect(sourceMap.getSourceLength(finalLength)).toStrictEqual(expectedValue); +        } +    }); +} + +function testCombineInsert() { +    test('CombineInsert', () => { +        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 TextSourceMap(source, mapping); +            const expectedSourceMap = new TextSourceMap(expectedSource, expectedMapping); +            for (const [operation, ...args] of operations) { +                switch (operation) { +                    case 'combine': +                        sourceMap.combine(...args); +                        break; +                    case 'insert': +                        sourceMap.insert(...args); +                        break; +                } +            } +            expect(sourceMap.equals(expectedSourceMap)).toBe(true); +        } +    }); +} + + +function main() { +    testSource(); +    testEquals(); +    testGetSourceLength(); +    testCombineInsert(); +} + + +main(); diff --git a/test/translator.test.js b/test/translator.test.js new file mode 100644 index 00000000..7a827d39 --- /dev/null +++ b/test/translator.test.js @@ -0,0 +1,83 @@ +/* + * 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/>. + */ + +import {IDBKeyRange, indexedDB} from 'fake-indexeddb'; +import fs from 'fs'; +import {fileURLToPath} from 'node:url'; +import path from 'path'; +import {expect, test, vi} from 'vitest'; +import {TranslatorVM} from '../dev/translator-vm'; + +vi.stubGlobal('indexedDB', indexedDB); +vi.stubGlobal('IDBKeyRange', IDBKeyRange); + +const dirname = path.dirname(fileURLToPath(import.meta.url)); + +async function main() { +    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) { +        test(`${i}`, async () => { +            const t = tests[i]; +            const expected1 = expectedResults1[i]; +            const expected2 = expectedResults2[i]; +            switch (t.func) { +                case 'findTerms': +                    { +                        const {name, mode, text} = t; +                        const options = translatorVM.buildOptions(optionsPresets, t.options); +                        const {dictionaryEntries, originalTextLength} = structuredClone(await translatorVM.translator.findTerms(mode, text, options)); +                        const noteDataList = mode !== 'simple' ? structuredClone(dictionaryEntries.map((dictionaryEntry) => translatorVM.createTestAnkiNoteData(structuredClone(dictionaryEntry), mode))) : null; +                        actualResults1.push({name, originalTextLength, dictionaryEntries}); +                        actualResults2.push({name, noteDataList}); +                        expect(originalTextLength).toStrictEqual(expected1.originalTextLength); +                        expect(dictionaryEntries).toStrictEqual(expected1.dictionaryEntries); +                        expect(noteDataList).toEqual(expected2.noteDataList); +                    } +                    break; +                case 'findKanji': +                    { +                        const {name, text} = t; +                        const options = translatorVM.buildOptions(optionsPresets, t.options); +                        const dictionaryEntries = structuredClone(await translatorVM.translator.findKanji(text, options)); +                        const noteDataList = structuredClone(dictionaryEntries.map((dictionaryEntry) => translatorVM.createTestAnkiNoteData(structuredClone(dictionaryEntry), null))); +                        actualResults1.push({name, dictionaryEntries}); +                        actualResults2.push({name, noteDataList}); +                        expect(dictionaryEntries).toStrictEqual(expected1.dictionaryEntries); +                        expect(noteDataList).toEqual(expected2.noteDataList); +                    } +                    break; +            } +        }); +    } +} + +await main();  |