diff options
| -rw-r--r-- | test/anki-note-builder.test.js | 130 | ||||
| -rw-r--r-- | test/document-util.test.js | 10 | ||||
| -rw-r--r-- | test/dom-text-scanner.test.js | 7 | ||||
| -rw-r--r-- | test/fixtures/dom-test.js (renamed from test/document-test.js) | 4 | ||||
| -rw-r--r-- | test/fixtures/translator-test.js | 125 | ||||
| -rw-r--r-- | test/jsconfig.json | 1 | ||||
| -rw-r--r-- | test/translator.test.js | 71 | ||||
| -rw-r--r-- | test/utilities/anki.js | 44 | ||||
| -rw-r--r-- | test/utilities/translator.js | 81 | ||||
| -rw-r--r-- | types/test/anki-note-builder.d.ts | 41 | 
10 files changed, 376 insertions, 138 deletions
| diff --git a/test/anki-note-builder.test.js b/test/anki-note-builder.test.js index 42ee2290..cc136957 100644 --- a/test/anki-note-builder.test.js +++ b/test/anki-note-builder.test.js @@ -18,56 +18,20 @@  // @vitest-environment jsdom -import 'fake-indexeddb/auto'; -import fs from 'fs'; +import {readFileSync} 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 {describe, vi} from 'vitest';  import {AnkiNoteBuilder} from '../ext/js/data/anki-note-builder.js';  import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; +import {createTranslatorTest} from './fixtures/translator-test.js'; +import {createFindOptions} from './utilities/translator.js';  const dirname = path.dirname(fileURLToPath(import.meta.url)); -/** - * @param {string} url2 - * @returns {Promise<import('dev/vm').PseudoFetchResponse>} - */ -async function fetch(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.stubGlobal('fetch', fetch);  vi.mock('../ext/js/templates/template-renderer-proxy.js', async () => await import('../test/mocks/template-renderer-proxy.js'));  /** - * @returns {Promise<TranslatorVM>} - */ -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; -} - -/**   * @param {'terms'|'kanji'} type   * @returns {string[]}   */ @@ -205,50 +169,44 @@ async function getRenderResults(dictionaryEntries, type, mode, template, expect)  } -/** */ -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; -                            /** @type {import('translation').FindTermsOptions} */ -                            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; -                            /** @type {import('translation').FindKanjiOptions} */ -                            const options = vm.buildOptions(optionsPresets, t.options); -                            const dictionaryEntries = structuredClone(await vm.translator.findKanji(text, options)); -                            const results = structuredClone(await getRenderResults(dictionaryEntries, 'kanji', 'split', template, expect)); -                            actualResults1.push({name, results}); -                            expect(results).toStrictEqual(expected1.results); -                        } -                        break; -                } -            }); -        } +const testInputsFilePath = path.join(dirname, 'data/translator-test-inputs.json'); +/** @type {import('test/anki-note-builder').TranslatorTestInputs} */ +const {optionsPresets, tests} = JSON.parse(readFileSync(testInputsFilePath, {encoding: 'utf8'})); + +const testResults1FilePath = path.join(dirname, 'data/anki-note-builder-test-results.json'); +const expectedResults1 = JSON.parse(readFileSync(testResults1FilePath, {encoding: 'utf8'})); + +const template = readFileSync(path.join(dirname, '../ext/data/templates/default-anki-field-templates.handlebars'), {encoding: 'utf8'}); + +const dictionaryName = 'Test Dictionary 2'; +const test = await createTranslatorTest(void 0, path.join(dirname, 'data/dictionaries/valid-dictionary1'), dictionaryName); + +describe('AnkiNoteBuilder', () => { +    const testData = tests.map((data, i) => ({data, expected1: expectedResults1[i]})); +    describe.each(testData)('Test %#: $data.name', ({data, expected1}) => { +        test('Test', async ({expect, translator}) => { +            switch (data.func) { +                case 'findTerms': +                    { +                        const {mode, text} = data; +                        /** @type {import('translation').FindTermsOptions} */ +                        const options = createFindOptions(dictionaryName, optionsPresets, data.options); +                        const {dictionaryEntries} = await translator.findTerms(mode, text, options); +                        const results = mode !== 'simple' ? await getRenderResults(dictionaryEntries, 'terms', mode, template, expect) : null; +                        expect(results).toStrictEqual(expected1.results); +                    } +                    break; +                case 'findKanji': +                    { +                        const {text} = data; +                        /** @type {import('translation').FindKanjiOptions} */ +                        const options = createFindOptions(dictionaryName, optionsPresets, data.options); +                        const dictionaryEntries = await translator.findKanji(text, options); +                        const results = await getRenderResults(dictionaryEntries, 'kanji', 'split', template, expect); +                        expect(results).toStrictEqual(expected1.results); +                    } +                    break; +            } +        });      }); -} -await main(); +}); diff --git a/test/document-util.test.js b/test/document-util.test.js index 10857df9..51872422 100644 --- a/test/document-util.test.js +++ b/test/document-util.test.js @@ -23,7 +23,7 @@ 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'; -import {domTest} from './document-test.js'; +import {createDomTest} from './fixtures/dom-test.js';  const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -109,9 +109,10 @@ function findImposterElement(document) {      return document.querySelector('div[style*="2147483646"]>*');  } +const test = createDomTest(path.join(dirname, 'data/html/test-document1.html')); +  describe('DocumentUtil', () => { -    const testDoc = domTest(path.join(dirname, 'data/html/test-document1.html')); -    testDoc('Text scanning functions', ({window}) => { +    test('Text scanning functions', ({window}) => {          const {document} = window;          for (const testElement of /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.test[data-test-type=scan]'))) {              // Get test parameters @@ -228,8 +229,7 @@ describe('DocumentUtil', () => {  });  describe('DOMTextScanner', () => { -    const testDoc = domTest(path.join(dirname, 'data/html/test-document1.html')); -    testDoc('Seek functions', async ({window}) => { +    test('Seek functions', async ({window}) => {          const {document} = window;          for (const testElement of /** @type {NodeListOf<HTMLElement>} */ (document.querySelectorAll('.test[data-test-type=text-source-range-seek]'))) {              // Get test parameters diff --git a/test/dom-text-scanner.test.js b/test/dom-text-scanner.test.js index 76e95a09..d62e334d 100644 --- a/test/dom-text-scanner.test.js +++ b/test/dom-text-scanner.test.js @@ -20,7 +20,7 @@ import {fileURLToPath} from 'node:url';  import path from 'path';  import {describe, expect} from 'vitest';  import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js'; -import {domTest} from './document-test.js'; +import {createDomTest} from './fixtures/dom-test.js';  const dirname = path.dirname(fileURLToPath(import.meta.url)); @@ -101,9 +101,10 @@ function createAbsoluteGetComputedStyle(window) {  } +const test = createDomTest(path.join(dirname, 'data/html/test-dom-text-scanner.html')); +  describe('DOMTextScanner', () => { -    const testDoc = domTest(path.join(dirname, 'data/html/test-dom-text-scanner.html')); -    testDoc('Seek tests', ({window}) => { +    test('Seek tests', ({window}) => {          const {document} = window;          window.getComputedStyle = createAbsoluteGetComputedStyle(window); diff --git a/test/document-test.js b/test/fixtures/dom-test.js index 9d763816..8cfe80a9 100644 --- a/test/document-test.js +++ b/test/fixtures/dom-test.js @@ -39,11 +39,11 @@ function prepareWindow(window) {   * @param {string} [htmlFilePath]   * @returns {import('vitest').TestAPI<{window: import('jsdom').DOMWindow}>}   */ -export function domTest(htmlFilePath) { +export function createDomTest(htmlFilePath) { +    const html = typeof htmlFilePath === 'string' ? fs.readFileSync(htmlFilePath, {encoding: 'utf8'}) : '<!DOCTYPE html>';      return test.extend({          // eslint-disable-next-line no-empty-pattern          window: async ({}, use) => { -            const html = typeof htmlFilePath === 'string' ? fs.readFileSync(htmlFilePath, {encoding: 'utf8'}) : '<!DOCTYPE html>';              const env = builtinEnvironments.jsdom;              const {teardown} = await env.setup(global, {jsdom: {html}});              const window = /** @type {import('jsdom').DOMWindow} */ (/** @type {unknown} */ (global.window)); diff --git a/test/fixtures/translator-test.js b/test/fixtures/translator-test.js new file mode 100644 index 00000000..b17c37d9 --- /dev/null +++ b/test/fixtures/translator-test.js @@ -0,0 +1,125 @@ +/* + * 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 {readFileSync} from 'fs'; +import {fileURLToPath, pathToFileURL} from 'node:url'; +import {dirname, join, resolve} from 'path'; +import {expect, vi} from 'vitest'; +import {createDictionaryArchive} from '../../dev/util.js'; +import {AnkiNoteDataCreator} from '../../ext/js/data/sandbox/anki-note-data-creator.js'; +import {DictionaryDatabase} from '../../ext/js/language/dictionary-database.js'; +import {DictionaryImporter} from '../../ext/js/language/dictionary-importer.js'; +import {JapaneseUtil} from '../../ext/js/language/sandbox/japanese-util.js'; +import {Translator} from '../../ext/js/language/translator.js'; +import {DictionaryImporterMediaLoader} from '../mocks/dictionary-importer-media-loader.js'; +import {createDomTest} from './dom-test.js'; + +const extDir = join(dirname(fileURLToPath(import.meta.url)), '../../ext'); +const deinflectionReasonsPath = join(extDir, 'data/deinflect.json'); + +/** @type {import('dev/vm').PseudoChrome} */ +const chrome = { +    runtime: { +        getURL: (path) => { +            return pathToFileURL(join(extDir, path.replace(/^\//, ''))).href; +        } +    } +}; + +/** + * @param {string} url + * @returns {Promise<import('dev/vm').PseudoFetchResponse>} + */ +async function fetch(url) { +    let filePath; +    try { +        filePath = fileURLToPath(url); +    } catch (e) { +        filePath = resolve(extDir, url.replace(/^[/\\]/, '')); +    } +    await Promise.resolve(); +    const content = readFileSync(filePath, {encoding: null}); +    return { +        ok: true, +        status: 200, +        statusText: 'OK', +        text: async () => content.toString('utf8'), +        json: async () => JSON.parse(content.toString('utf8')) +    }; +} + +vi.stubGlobal('indexedDB', indexedDB); +vi.stubGlobal('IDBKeyRange', IDBKeyRange); +vi.stubGlobal('fetch', fetch); +vi.stubGlobal('chrome', chrome); + +/** + * @param {string} dictionaryDirectory + * @param {string} dictionaryName + * @returns {Promise<{translator: Translator, ankiNoteDataCreator: AnkiNoteDataCreator}>} + */ +async function createTranslatorContext(dictionaryDirectory, dictionaryName) { +    // Dictionary +    const testDictionary = createDictionaryArchive(dictionaryDirectory, dictionaryName); +    const testDictionaryContent = await testDictionary.generateAsync({type: 'arraybuffer'}); + +    // Setup database +    const dictionaryImporterMediaLoader = new DictionaryImporterMediaLoader(); +    const dictionaryImporter = new DictionaryImporter(dictionaryImporterMediaLoader); +    const dictionaryDatabase = new DictionaryDatabase(); +    await dictionaryDatabase.prepare(); + +    const {errors} = await dictionaryImporter.importDictionary( +        dictionaryDatabase, +        testDictionaryContent, +        {prefixWildcardsSupported: true} +    ); + +    expect(errors.length).toEqual(0); + +    // Setup translator +    const japaneseUtil = new JapaneseUtil(null); +    const translator = new Translator({japaneseUtil, database: dictionaryDatabase}); +    const deinflectionReasons = JSON.parse(readFileSync(deinflectionReasonsPath, {encoding: 'utf8'})); +    translator.prepare(deinflectionReasons); + +    // Assign properties +    const ankiNoteDataCreator = new AnkiNoteDataCreator(japaneseUtil); +    return {translator, ankiNoteDataCreator}; +} + +/** + * @param {string|undefined} htmlFilePath + * @param {string} dictionaryDirectory + * @param {string} dictionaryName + * @returns {Promise<import('vitest').TestAPI<{window: import('jsdom').DOMWindow, translator: Translator, ankiNoteDataCreator: AnkiNoteDataCreator}>>} + */ +export async function createTranslatorTest(htmlFilePath, dictionaryDirectory, dictionaryName) { +    const test = createDomTest(htmlFilePath); +    const {translator, ankiNoteDataCreator} = await createTranslatorContext(dictionaryDirectory, dictionaryName); +    /** @type {import('vitest').TestAPI<{window: import('jsdom').DOMWindow, translator: Translator, ankiNoteDataCreator: AnkiNoteDataCreator}>} */ +    const result = test.extend({ +        window: async ({window}, use) => { await use(window); }, +        // eslint-disable-next-line no-empty-pattern +        translator: async ({}, use) => { await use(translator); }, +        // eslint-disable-next-line no-empty-pattern +        ankiNoteDataCreator: async ({}, use) => { await use(ankiNoteDataCreator); } +    }); +    return result; +} diff --git a/test/jsconfig.json b/test/jsconfig.json index c587abe6..9ab0c332 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -31,6 +31,7 @@          "../ext/**/*.js",          "../types/ext/**/*.ts",          "../types/dev/**/*.ts", +        "../types/test/**/*.ts",          "../types/other/globals.d.ts"      ],      "exclude": [ diff --git a/test/translator.test.js b/test/translator.test.js index 3db560a7..59887d7e 100644 --- a/test/translator.test.js +++ b/test/translator.test.js @@ -16,50 +16,41 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ -import {IDBKeyRange, indexedDB} from 'fake-indexeddb'; -import fs from 'fs'; +import {readFileSync} 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); +import {describe} from 'vitest'; +import {createTranslatorTest} from './fixtures/translator-test.js'; +import {createTestAnkiNoteData} from './utilities/anki.js'; +import {createFindOptions} from './utilities/translator.js';  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'); +/** @type {import('test/anki-note-builder').TranslatorTestInputs} */ +const {optionsPresets, tests} = JSON.parse(readFileSync(testInputsFilePath, {encoding: 'utf8'})); -    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(readFileSync(testResults1FilePath, {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(readFileSync(testResults2FilePath, {encoding: 'utf8'})); -    const testResults2FilePath = path.join(dirname, 'data', 'translator-test-results-note-data1.json'); -    const expectedResults2 = JSON.parse(fs.readFileSync(testResults2FilePath, {encoding: 'utf8'})); -    const actualResults2 = []; +const dictionaryName = 'Test Dictionary 2'; +const test = await createTranslatorTest(void 0, path.join(dirname, 'data/dictionaries/valid-dictionary1'), dictionaryName); -    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) { +describe('Translator', () => { +    const testData = tests.map((data, i) => ({data, expected1: expectedResults1[i], expected2: expectedResults2[i]})); +    describe.each(testData)('Test %#: $data.name', ({data, expected1, expected2}) => { +        test('Test', async ({translator, ankiNoteDataCreator, expect}) => { +            switch (data.func) {                  case 'findTerms':                      { -                        const {name, mode, text} = t; +                        const {mode, text} = data;                          /** @type {import('translation').FindTermsOptions} */ -                        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}); +                        const options = createFindOptions(dictionaryName, optionsPresets, data.options); +                        const {dictionaryEntries, originalTextLength} = await translator.findTerms(mode, text, options); +                        const noteDataList = mode !== 'simple' ? dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, mode)) : null;                          expect(originalTextLength).toStrictEqual(expected1.originalTextLength);                          expect(dictionaryEntries).toStrictEqual(expected1.dictionaryEntries);                          expect(noteDataList).toEqual(expected2.noteDataList); @@ -67,20 +58,16 @@ async function main() {                      break;                  case 'findKanji':                      { -                        const {name, text} = t; +                        const {text} = data;                          /** @type {import('translation').FindKanjiOptions} */ -                        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), 'split'))); -                        actualResults1.push({name, dictionaryEntries}); -                        actualResults2.push({name, noteDataList}); +                        const options = createFindOptions(dictionaryName, optionsPresets, data.options); +                        const dictionaryEntries = await translator.findKanji(text, options); +                        const noteDataList = dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, 'split'));                          expect(dictionaryEntries).toStrictEqual(expected1.dictionaryEntries);                          expect(noteDataList).toEqual(expected2.noteDataList);                      }                      break;              }          }); -    } -} - -await main(); +    }); +}); diff --git a/test/utilities/anki.js b/test/utilities/anki.js new file mode 100644 index 00000000..0a651c30 --- /dev/null +++ b/test/utilities/anki.js @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2023  Yomitan 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/>. + */ + +/** + * @param {import('../../ext/js/data/sandbox/anki-note-data-creator.js').AnkiNoteDataCreator} ankiNoteDataCreator + * @param {import('dictionary').DictionaryEntry} dictionaryEntry + * @param {import('settings').ResultOutputMode} mode + * @returns {import('anki-templates').NoteData} + * @throws {Error} + */ +export function createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, mode) { +    const marker = '{marker}'; +    /** @type {import('anki-templates-internal').CreateDetails} */ +    const data = { +        dictionaryEntry, +        resultOutputMode: mode, +        mode: 'test', +        glossaryLayoutMode: 'default', +        compactTags: false, +        context: { +            url: 'url:', +            sentence: {text: '', offset: 0}, +            documentTitle: 'title', +            query: 'query', +            fullQuery: 'fullQuery' +        }, +        media: {} +    }; +    return ankiNoteDataCreator.create(marker, data); +} diff --git a/test/utilities/translator.js b/test/utilities/translator.js new file mode 100644 index 00000000..9073b206 --- /dev/null +++ b/test/utilities/translator.js @@ -0,0 +1,81 @@ +/* + * 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/>. + */ + + +/** + * @template {import('translation').FindTermsOptions|import('translation').FindKanjiOptions} T + * @param {string} dictionaryName + * @param {import('dev/vm').OptionsPresetObject} optionsPresets + * @param {string|import('dev/vm').OptionsPresetObject|(string|import('dev/vm').OptionsPresetObject)[]} optionsArray + * @returns {T} + * @throws {Error} + */ +export function createFindOptions(dictionaryName, optionsPresets, optionsArray) { +    /** @type {import('core').UnknownObject} */ +    const options = {}; +    if (!Array.isArray(optionsArray)) { optionsArray = [optionsArray]; } +    for (const entry of optionsArray) { +        switch (typeof entry) { +            case 'string': +                if (!Object.prototype.hasOwnProperty.call(optionsPresets, entry)) { +                    throw new Error('Invalid options preset'); +                } +                Object.assign(options, structuredClone(optionsPresets[entry])); +                break; +            case 'object': +                Object.assign(options, structuredClone(entry)); +                break; +            default: +                throw new Error('Invalid options type'); +        } +    } + +    // Construct regex +    if (Array.isArray(options.textReplacements)) { +        options.textReplacements = options.textReplacements.map((value) => { +            if (Array.isArray(value)) { +                value = value.map(({pattern, flags, replacement}) => ({pattern: new RegExp(pattern, flags), replacement})); +            } +            return value; +        }); +    } + +    // Update structure +    const placeholder = '${title}'; +    if (options.mainDictionary === placeholder) { +        options.mainDictionary = dictionaryName; +    } +    let {enabledDictionaryMap} = options; +    if (Array.isArray(enabledDictionaryMap)) { +        for (const entry of enabledDictionaryMap) { +            if (entry[0] === placeholder) { +                entry[0] = dictionaryName; +            } +        } +        enabledDictionaryMap = new Map(enabledDictionaryMap); +        options.enabledDictionaryMap = enabledDictionaryMap; +    } +    const {excludeDictionaryDefinitions} = options; +    options.excludeDictionaryDefinitions = ( +        Array.isArray(excludeDictionaryDefinitions) ? +        new Set(excludeDictionaryDefinitions) : +        null +    ); + +    return /** @type {T} */ (options); +} diff --git a/types/test/anki-note-builder.d.ts b/types/test/anki-note-builder.d.ts new file mode 100644 index 00000000..0ccb25e9 --- /dev/null +++ b/types/test/anki-note-builder.d.ts @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2023  Yomitan 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 type {OptionsPresetObject} from 'dev/vm'; +import type {FindTermsMode} from 'translator'; + +export type TranslatorTestInputs = { +    optionsPresets: OptionsPresetObject; +    tests: TestInput[]; +}; + +export type TestInput = TestInputFindKanji | TestInputFindTerm; + +export type TestInputFindKanji = { +    func: 'findKanji'; +    name: string; +    text: string; +    options: string; +}; + +export type TestInputFindTerm = { +    func: 'findTerms'; +    name: string; +    mode: FindTermsMode; +    text: string; +    options: string; +}; |