diff options
| -rw-r--r-- | package.json | 1 | ||||
| -rw-r--r-- | test/anki-note-builder.test.js | 215 | ||||
| -rw-r--r-- | test/data/json.json | 1 | ||||
| -rw-r--r-- | test/data/vitest.write.config.json | 7 | ||||
| -rw-r--r-- | test/dictionary-data-validate.test.js (renamed from test/dictionary.test.js) | 21 | ||||
| -rw-r--r-- | test/dictionary-data.test.js (renamed from test/translator.test.js) | 54 | ||||
| -rw-r--r-- | test/dictionary-data.write.js | 91 | ||||
| -rw-r--r-- | test/fixtures/translator-test.js | 8 | ||||
| -rw-r--r-- | test/utilities/anki.js | 145 | 
9 files changed, 291 insertions, 252 deletions
| diff --git a/package.json b/package.json index 6be76caf..f4338586 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@          "test-ts-dev": "npx tsc --noEmit --project dev/jsconfig.json",          "test-ts-test": "npx tsc --noEmit --project test/jsconfig.json",          "test-code": "vitest run", +        "test-code-write": "vitest run --config test/data/vitest.write.config.json",          "test-build": "node ./dev/bin/build.js --dry-run --all",          "license-report": "license-report --output=html --only=prod --fields=name --fields=installedVersion --fields=licenseType --fields=link --html.cssFile=dev/data/legal-npm.css > ext/legal-npm.html",          "license-report-markdown": "license-report --output=markdown --only=prod --fields=name --fields=installedVersion --fields=licenseType --fields=link" diff --git a/test/anki-note-builder.test.js b/test/anki-note-builder.test.js deleted file mode 100644 index 35db5107..00000000 --- a/test/anki-note-builder.test.js +++ /dev/null @@ -1,215 +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/>. - */ - -// @vitest-environment jsdom - -import {readFileSync} from 'fs'; -import {fileURLToPath} from 'node:url'; -import path from 'path'; -import {describe} from 'vitest'; -import {parseJson} from '../dev/json.js'; -import {AnkiNoteBuilder} from '../ext/js/data/anki-note-builder.js'; -import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; -import {AnkiTemplateRenderer} from '../ext/js/templates/sandbox/anki-template-renderer.js'; -import {createTranslatorTest} from './fixtures/translator-test.js'; -import {createFindOptions} from './utilities/translator.js'; - -const dirname = path.dirname(fileURLToPath(import.meta.url)); - -/** - * @param {'terms'|'kanji'} type - * @returns {string[]} - */ -function getFieldMarkers(type) { -    switch (type) { -        case 'terms': -            return [ -                'audio', -                'clipboard-image', -                'clipboard-text', -                'cloze-body', -                'cloze-prefix', -                'cloze-suffix', -                'conjugation', -                'dictionary', -                'document-title', -                'expression', -                'frequencies', -                'furigana', -                'furigana-plain', -                'glossary', -                'glossary-brief', -                'glossary-no-dictionary', -                'part-of-speech', -                'pitch-accents', -                'pitch-accent-graphs', -                'pitch-accent-positions', -                'reading', -                'screenshot', -                'search-query', -                'selection-text', -                'sentence', -                'sentence-furigana', -                'tags', -                'url' -            ]; -        case 'kanji': -            return [ -                'character', -                'clipboard-image', -                'clipboard-text', -                'cloze-body', -                'cloze-prefix', -                'cloze-suffix', -                'dictionary', -                'document-title', -                'glossary', -                'kunyomi', -                'onyomi', -                'screenshot', -                'search-query', -                'selection-text', -                'sentence', -                'sentence-furigana', -                'stroke-count', -                'tags', -                'url' -            ]; -        default: -            return []; -    } -} - -/** - * @param {import('dictionary').DictionaryEntry[]} dictionaryEntries - * @param {'terms'|'kanji'} type - * @param {import('settings').ResultOutputMode} mode - * @param {string} template - * @param {import('vitest').ExpectStatic} expect - * @returns {Promise<import('anki').NoteFields[]>} - */ -async function getRenderResults(dictionaryEntries, type, mode, template, expect) { -    const markers = getFieldMarkers(type); -    /** @type {import('anki-note-builder').Field[]} */ -    const fields = []; -    for (const marker of markers) { -        fields.push([marker, `{${marker}}`]); -    } - -    const ankiTemplateRenderer = new AnkiTemplateRenderer(); -    await ankiTemplateRenderer.prepare(); -    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, ankiTemplateRenderer.templateRenderer); -        const context = { -            url: 'url:', -            sentence: { -                text: `${clozePrefix}${source}${clozeSuffix}`, -                offset: clozePrefix.length -            }, -            documentTitle: 'title', -            query: 'query', -            fullQuery: 'fullQuery' -        }; -        /** @type {import('anki-note-builder').CreateNoteDetails} */ -        const details = { -            dictionaryEntry, -            mode: 'test', -            context, -            template, -            deckName: 'deckName', -            modelName: 'modelName', -            fields, -            tags: ['yomitan'], -            checkForDuplicates: true, -            duplicateScope: 'collection', -            duplicateScopeCheckAllModels: false, -            resultOutputMode: mode, -            glossaryLayoutMode: 'default', -            compactTags: false, -            requirements: [], -            mediaOptions: null -        }; -        const {note: {fields: noteFields}, errors} = await ankiNoteBuilder.createNote(details); -        for (const error of errors) { -            console.error(error); -        } -        expect(errors.length).toStrictEqual(0); -        results.push(noteFields); -    } - -    return results; -} - - -const testInputsFilePath = path.join(dirname, 'data/translator-test-inputs.json'); -/** @type {import('test/translator').TranslatorTestInputs} */ -const {optionsPresets, tests} = parseJson(readFileSync(testInputsFilePath, {encoding: 'utf8'})); - -const testResults1FilePath = path.join(dirname, 'data/anki-note-builder-test-results.json'); -/** @type {import('test/translator').AnkiNoteBuilderTestResults} */ -const expectedResults1 = parseJson(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; -            } -        }); -    }); -}); diff --git a/test/data/json.json b/test/data/json.json index 6d806263..1f856033 100644 --- a/test/data/json.json +++ b/test/data/json.json @@ -22,6 +22,7 @@          {"path": "test/data/dictionaries/invalid-dictionary6/term_meta_bank_1.json", "ignore": true},          {"path": "test/data/dictionaries/invalid-dictionary6/index.json", "ignore": true},          {"path": "test/jsconfig.json", "ignore": true}, +        {"path": "test/data/vitest.write.config.json", "ignore": true},          {              "path": "dev/data/manifest-variants.json", diff --git a/test/data/vitest.write.config.json b/test/data/vitest.write.config.json new file mode 100644 index 00000000..ecb4bd84 --- /dev/null +++ b/test/data/vitest.write.config.json @@ -0,0 +1,7 @@ +{ +    "test": { +        "include": [ +            "../**/*.write.js" +        ] +    } +}
\ No newline at end of file diff --git a/test/dictionary.test.js b/test/dictionary-data-validate.test.js index e516bd8e..c54f7dbb 100644 --- a/test/dictionary.test.js +++ b/test/dictionary-data-validate.test.js @@ -18,7 +18,7 @@  import {fileURLToPath} from 'node:url';  import path from 'path'; -import {expect, test} from 'vitest'; +import {describe, test} from 'vitest';  import * as dictionaryValidate from '../dev/dictionary-validate.js';  import {createDictionaryArchive} from '../dev/util.js'; @@ -34,10 +34,8 @@ function createTestDictionaryArchive(dictionary, dictionaryName) {      return createDictionaryArchive(dictionaryDirectory, dictionaryName);  } - -/** */ -async function main() { -    const dictionaries = [ +describe('Dictionary validation', () => { +    const testCases = [          {name: 'valid-dictionary1', valid: true},          {name: 'invalid-dictionary1', valid: false},          {name: 'invalid-dictionary2', valid: false}, @@ -46,20 +44,15 @@ async function main() {          {name: 'invalid-dictionary5', valid: false},          {name: 'invalid-dictionary6', valid: false}      ]; -      const schemas = dictionaryValidate.getSchemas(); - -    for (const {name, valid} of dictionaries) { -        test(`${name} is ${valid ? 'valid' : 'invalid'}`, async () => { +    describe.each(testCases)('Test dictionary $name', ({name, valid}) => { +        test(`Should be ${valid ? 'valid' : 'invalid'}`, async ({expect}) => {              const archive = createTestDictionaryArchive(name); -              if (valid) {                  await expect(dictionaryValidate.validateDictionary(null, archive, schemas)).resolves.not.toThrow();              } else {                  await expect(dictionaryValidate.validateDictionary(null, archive, schemas)).rejects.toThrow();              }          }); -    } -} - -await main(); +    }); +}); diff --git a/test/translator.test.js b/test/dictionary-data.test.js index 42a9076e..b3a9c57d 100644 --- a/test/translator.test.js +++ b/test/dictionary-data.test.js @@ -1,6 +1,5 @@  /*   * 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 @@ -22,29 +21,38 @@ import path from 'path';  import {describe} from 'vitest';  import {parseJson} from '../dev/json.js';  import {createTranslatorTest} from './fixtures/translator-test.js'; -import {createTestAnkiNoteData} from './utilities/anki.js'; +import {createTestAnkiNoteData, getTemplateRenderResults} from './utilities/anki.js';  import {createFindOptions} from './utilities/translator.js';  const dirname = path.dirname(fileURLToPath(import.meta.url)); +const dictionaryName = 'Test Dictionary 2'; +const test = await createTranslatorTest(void 0, path.join(dirname, 'data/dictionaries/valid-dictionary1'), dictionaryName); -const testInputsFilePath = path.join(dirname, 'data/translator-test-inputs.json'); -/** @type {import('test/translator').TranslatorTestInputs} */ -const {optionsPresets, tests} = parseJson(readFileSync(testInputsFilePath, {encoding: 'utf8'})); +describe('Dictionary data', () => { +    const testInputsFilePath = path.join(dirname, 'data/translator-test-inputs.json'); +    /** @type {import('test/translator').TranslatorTestInputs} */ +    const {optionsPresets, tests} = parseJson(readFileSync(testInputsFilePath, {encoding: 'utf8'})); -const testResults1FilePath = path.join(dirname, 'data/translator-test-results.json'); -/** @type {import('test/translator').TranslatorTestResults} */ -const expectedResults1 = parseJson(readFileSync(testResults1FilePath, {encoding: 'utf8'})); +    const testResults1FilePath = path.join(dirname, 'data/translator-test-results.json'); +    const testResults2FilePath = path.join(dirname, 'data/translator-test-results-note-data1.json'); +    const testResults3FilePath = path.join(dirname, 'data/anki-note-builder-test-results.json'); -const testResults2FilePath = path.join(dirname, 'data/translator-test-results-note-data1.json'); -/** @type {import('test/translator').TranslatorTestNoteDataResults} */ -const expectedResults2 = parseJson(readFileSync(testResults2FilePath, {encoding: 'utf8'})); +    /** @type {import('test/translator').TranslatorTestResults} */ +    const expectedResults1 = parseJson(readFileSync(testResults1FilePath, {encoding: 'utf8'})); +    /** @type {import('test/translator').TranslatorTestNoteDataResults} */ +    const expectedResults2 = parseJson(readFileSync(testResults2FilePath, {encoding: 'utf8'})); +    /** @type {import('test/translator').AnkiNoteBuilderTestResults} */ +    const expectedResults3 = parseJson(readFileSync(testResults3FilePath, {encoding: 'utf8'})); -const dictionaryName = 'Test Dictionary 2'; -const test = await createTranslatorTest(void 0, path.join(dirname, 'data/dictionaries/valid-dictionary1'), dictionaryName); +    const template = readFileSync(path.join(dirname, '../ext/data/templates/default-anki-field-templates.handlebars'), {encoding: 'utf8'}); -describe('Translator', () => { -    const testData = tests.map((data, i) => ({data, expected1: expectedResults1[i], expected2: expectedResults2[i]})); -    describe.each(testData)('Test %#: $data.name', ({data, expected1, expected2}) => { +    const testCases = tests.map((data, i) => ({ +        data, +        expected1: expectedResults1[i], +        expected2: expectedResults2[i], +        expected3: expectedResults3[i] +    })); +    describe.each(testCases)('Test %#: $data.name', ({data, expected1, expected2, expected3}) => {          test('Test', async ({translator, ankiNoteDataCreator, expect}) => {              switch (data.func) {                  case 'findTerms': @@ -53,10 +61,12 @@ describe('Translator', () => {                          /** @type {import('translation').FindTermsOptions} */                          const options = createFindOptions(dictionaryName, optionsPresets, data.options);                          const {dictionaryEntries, originalTextLength} = await translator.findTerms(mode, text, options); +                        const renderResults = mode !== 'simple' ? await getTemplateRenderResults(dictionaryEntries, 'terms', mode, template, expect) : null;                          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); +                        expect.soft(originalTextLength).toStrictEqual(expected1.originalTextLength); +                        expect.soft(dictionaryEntries).toStrictEqual(expected1.dictionaryEntries); +                        expect.soft(noteDataList).toEqual(expected2.noteDataList); +                        expect.soft(renderResults).toStrictEqual(expected3.results);                      }                      break;                  case 'findKanji': @@ -65,9 +75,11 @@ describe('Translator', () => {                          /** @type {import('translation').FindKanjiOptions} */                          const options = createFindOptions(dictionaryName, optionsPresets, data.options);                          const dictionaryEntries = await translator.findKanji(text, options); +                        const renderResults = await getTemplateRenderResults(dictionaryEntries, 'kanji', 'split', template, expect);                          const noteDataList = dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, 'split')); -                        expect(dictionaryEntries).toStrictEqual(expected1.dictionaryEntries); -                        expect(noteDataList).toEqual(expected2.noteDataList); +                        expect.soft(dictionaryEntries).toStrictEqual(expected1.dictionaryEntries); +                        expect.soft(noteDataList).toEqual(expected2.noteDataList); +                        expect.soft(renderResults).toStrictEqual(expected3.results);                      }                      break;              } diff --git a/test/dictionary-data.write.js b/test/dictionary-data.write.js new file mode 100644 index 00000000..0f6bbfcb --- /dev/null +++ b/test/dictionary-data.write.js @@ -0,0 +1,91 @@ +/* + * 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 {readFileSync, writeFileSync} from 'fs'; +import {fileURLToPath} from 'node:url'; +import path from 'path'; +import {parseJson} from '../dev/json.js'; +import {createTranslatorTest} from './fixtures/translator-test.js'; +import {createTestAnkiNoteData, getTemplateRenderResults} from './utilities/anki.js'; +import {createFindOptions} from './utilities/translator.js'; + +/** + * @param {string} fileName + * @param {unknown} content + */ +function writeJson(fileName, content) { +    writeFileSync(fileName, JSON.stringify(content, null, 2)); +} + +const dirname = path.dirname(fileURLToPath(import.meta.url)); +const dictionaryName = 'Test Dictionary 2'; +const test = await createTranslatorTest(void 0, path.join(dirname, 'data/dictionaries/valid-dictionary1'), dictionaryName); + +test('Write dictionary data expected data', async ({translator, ankiNoteDataCreator, expect}) => { +    const testInputsFilePath = path.join(dirname, 'data/translator-test-inputs.json'); +    /** @type {import('test/translator').TranslatorTestInputs} */ +    const {optionsPresets, tests} = parseJson(readFileSync(testInputsFilePath, {encoding: 'utf8'})); + +    const testResults1FilePath = path.join(dirname, 'data/translator-test-results.json'); +    const testResults2FilePath = path.join(dirname, 'data/translator-test-results-note-data1.json'); +    const testResults3FilePath = path.join(dirname, 'data/anki-note-builder-test-results.json'); + +    /** @type {import('test/translator').TranslatorTestResults} */ +    const actualResults1 = []; +    /** @type {import('test/translator').TranslatorTestNoteDataResults} */ +    const actualResults2 = []; +    /** @type {import('test/translator').AnkiNoteBuilderTestResults} */ +    const actualResults3 = []; + +    const template = readFileSync(path.join(dirname, '../ext/data/templates/default-anki-field-templates.handlebars'), {encoding: 'utf8'}); + +    for (const data of tests) { +        const {name} = data; +        switch (data.func) { +            case 'findTerms': +                { +                    const {mode, text} = data; +                    /** @type {import('translation').FindTermsOptions} */ +                    const options = createFindOptions(dictionaryName, optionsPresets, data.options); +                    const {dictionaryEntries, originalTextLength} = await translator.findTerms(mode, text, options); +                    const renderResults = mode !== 'simple' ? await getTemplateRenderResults(dictionaryEntries, 'terms', mode, template, null) : null; +                    const noteDataList = mode !== 'simple' ? dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, mode)) : null; +                    actualResults1.push({name, originalTextLength, dictionaryEntries}); +                    actualResults2.push({name, noteDataList}); +                    actualResults3.push({name, results: renderResults}); +                } +                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 renderResults = await getTemplateRenderResults(dictionaryEntries, 'kanji', 'split', template, null); +                    const noteDataList = dictionaryEntries.map((dictionaryEntry) => createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, 'split')); +                    actualResults1.push({name, dictionaryEntries}); +                    actualResults2.push({name, noteDataList}); +                    actualResults3.push({name, results: renderResults}); +                } +                break; +        } +    } + +    expect(() => writeJson(testResults1FilePath, actualResults1)).not.toThrow(); +    expect(() => writeJson(testResults2FilePath, actualResults2)).not.toThrow(); +    expect(() => writeJson(testResults3FilePath, actualResults3)).not.toThrow(); +}); diff --git a/test/fixtures/translator-test.js b/test/fixtures/translator-test.js index 3304c587..0afbe1f0 100644 --- a/test/fixtures/translator-test.js +++ b/test/fixtures/translator-test.js @@ -90,8 +90,12 @@ export async function createTranslatorTest(htmlFilePath, dictionaryDirectory, di          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); } +        ankiNoteDataCreator: async ({window}, use) => { +            // The window property needs to be referenced for it to be initialized. +            // It is needed for DOM access for structured content. +            void window; +            await use(ankiNoteDataCreator); +        }      });      return result;  } diff --git a/test/utilities/anki.js b/test/utilities/anki.js index 0a651c30..4b73f6b9 100644 --- a/test/utilities/anki.js +++ b/test/utilities/anki.js @@ -15,6 +15,10 @@   * along with this program.  If not, see <https://www.gnu.org/licenses/>.   */ +import {AnkiNoteBuilder} from '../../ext/js/data/anki-note-builder.js'; +import {JapaneseUtil} from '../../ext/js/language/sandbox/japanese-util.js'; +import {AnkiTemplateRenderer} from '../../ext/js/templates/sandbox/anki-template-renderer.js'; +  /**   * @param {import('../../ext/js/data/sandbox/anki-note-data-creator.js').AnkiNoteDataCreator} ankiNoteDataCreator   * @param {import('dictionary').DictionaryEntry} dictionaryEntry @@ -42,3 +46,144 @@ export function createTestAnkiNoteData(ankiNoteDataCreator, dictionaryEntry, mod      };      return ankiNoteDataCreator.create(marker, data);  } + +/** + * @param {'terms'|'kanji'} type + * @returns {string[]} + */ +function getFieldMarkers(type) { +    switch (type) { +        case 'terms': +            return [ +                'audio', +                'clipboard-image', +                'clipboard-text', +                'cloze-body', +                'cloze-prefix', +                'cloze-suffix', +                'conjugation', +                'dictionary', +                'document-title', +                'expression', +                'frequencies', +                'furigana', +                'furigana-plain', +                'glossary', +                'glossary-brief', +                'glossary-no-dictionary', +                'part-of-speech', +                'pitch-accents', +                'pitch-accent-graphs', +                'pitch-accent-positions', +                'reading', +                'screenshot', +                'search-query', +                'selection-text', +                'sentence', +                'sentence-furigana', +                'tags', +                'url' +            ]; +        case 'kanji': +            return [ +                'character', +                'clipboard-image', +                'clipboard-text', +                'cloze-body', +                'cloze-prefix', +                'cloze-suffix', +                'dictionary', +                'document-title', +                'glossary', +                'kunyomi', +                'onyomi', +                'screenshot', +                'search-query', +                'selection-text', +                'sentence', +                'sentence-furigana', +                'stroke-count', +                'tags', +                'url' +            ]; +        default: +            return []; +    } +} + +/** + * @param {import('dictionary').DictionaryEntry[]} dictionaryEntries + * @param {'terms'|'kanji'} type + * @param {import('settings').ResultOutputMode} mode + * @param {string} template + * @param {?import('vitest').ExpectStatic} expect + * @returns {Promise<import('anki').NoteFields[]>} + */ +export async function getTemplateRenderResults(dictionaryEntries, type, mode, template, expect) { +    const markers = getFieldMarkers(type); +    /** @type {import('anki-note-builder').Field[]} */ +    const fields = []; +    for (const marker of markers) { +        fields.push([marker, `{${marker}}`]); +    } + +    const ankiTemplateRenderer = new AnkiTemplateRenderer(); +    await ankiTemplateRenderer.prepare(); +    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, ankiTemplateRenderer.templateRenderer); +        const context = { +            url: 'url:', +            sentence: { +                text: `${clozePrefix}${source}${clozeSuffix}`, +                offset: clozePrefix.length +            }, +            documentTitle: 'title', +            query: 'query', +            fullQuery: 'fullQuery' +        }; +        /** @type {import('anki-note-builder').CreateNoteDetails} */ +        const details = { +            dictionaryEntry, +            mode: 'test', +            context, +            template, +            deckName: 'deckName', +            modelName: 'modelName', +            fields, +            tags: ['yomitan'], +            checkForDuplicates: true, +            duplicateScope: 'collection', +            duplicateScopeCheckAllModels: false, +            resultOutputMode: mode, +            glossaryLayoutMode: 'default', +            compactTags: false, +            requirements: [], +            mediaOptions: null +        }; +        const {note: {fields: noteFields}, errors} = await ankiNoteBuilder.createNote(details); +        for (const error of errors) { +            console.error(error); +        } +        if (expect !== null) { +            expect(errors.length).toStrictEqual(0); +        } +        results.push(noteFields); +    } + +    return results; +} |