diff options
| -rw-r--r-- | test/anki-note-builder.test.js | 42 | ||||
| -rw-r--r-- | test/cache-map.test.js | 12 | ||||
| -rw-r--r-- | test/core.test.js | 5 | ||||
| -rw-r--r-- | test/css-json.test.js | 1 | ||||
| -rw-r--r-- | test/database.test.js | 108 | ||||
| -rw-r--r-- | test/playwright/integration.spec.js | 13 | ||||
| -rw-r--r-- | test/playwright/playwright-util.js | 11 | 
7 files changed, 164 insertions, 28 deletions
| diff --git a/test/anki-note-builder.test.js b/test/anki-note-builder.test.js index e21aa993..e4af7943 100644 --- a/test/anki-note-builder.test.js +++ b/test/anki-note-builder.test.js @@ -26,7 +26,11 @@ 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) => { +/** + * @param {string} url2 + * @returns {Promise<import('dev/vm').PseudoFetchResponse>} + */ +async function fetch(url2) {      const extDir = path.join(__dirname, '..', 'ext');      let filePath;      try { @@ -43,11 +47,15 @@ vi.stubGlobal('fetch', async (url2) => {          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');  const dirname = path.dirname(fileURLToPath(import.meta.url)); +/** + * @returns {Promise<TranslatorVM>} + */  async function createVM() {      const dictionaryDirectory = path.join(dirname, 'data', 'dictionaries', 'valid-dictionary1');      const vm = new TranslatorVM(); @@ -57,6 +65,10 @@ async function createVM() {      return vm;  } +/** + * @param {'terms'|'kanji'} type + * @returns {string[]} + */  function getFieldMarkers(type) {      switch (type) {          case 'terms': @@ -117,8 +129,17 @@ function getFieldMarkers(type) {      }  } +/** + * @param {import('dictionary').DictionaryEntry[]} dictionaryEntries + * @param {'terms'|'kanji'} type + * @param {import('settings').ResultOutputMode} mode + * @param {string} template + * @param {import('@vitest/expect').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}}`]); @@ -151,9 +172,10 @@ async function getRenderResults(dictionaryEntries, type, mode, template, expect)              query: 'query',              fullQuery: 'fullQuery'          }; -        const {note: {fields: noteFields}, errors} = await ankiNoteBuilder.createNote({ +        /** @type {import('anki-note-builder').CreateNoteDetails} */ +        const details = {              dictionaryEntry, -            mode: null, +            mode: 'test',              context,              template,              deckName: 'deckName', @@ -165,8 +187,11 @@ async function getRenderResults(dictionaryEntries, type, mode, template, expect)              duplicateScopeCheckAllModels: false,              resultOutputMode: mode,              glossaryLayoutMode: 'default', -            compactTags: false -        }); +            compactTags: false, +            requirements: [], +            mediaOptions: null +        }; +        const {note: {fields: noteFields}, errors} = await ankiNoteBuilder.createNote(details);          for (const error of errors) {              console.error(error);          } @@ -178,6 +203,7 @@ async function getRenderResults(dictionaryEntries, type, mode, template, expect)  } +/** */  async function main() {      const vm = await createVM(); @@ -199,6 +225,7 @@ async function main() {                      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; @@ -209,9 +236,10 @@ async function main() {                      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', null, template, expect)); +                            const results = structuredClone(await getRenderResults(dictionaryEntries, 'kanji', 'split', template, expect));                              actualResults1.push({name, results});                              expect(results).toStrictEqual(expected1.results);                          } diff --git a/test/cache-map.test.js b/test/cache-map.test.js index 9d10a719..3e7def1f 100644 --- a/test/cache-map.test.js +++ b/test/cache-map.test.js @@ -19,6 +19,7 @@  import {expect, test} from 'vitest';  import {CacheMap} from '../ext/js/general/cache-map.js'; +/** */  function testConstructor() {      test('constructor', () => {          const data = [ @@ -29,6 +30,7 @@ function testConstructor() {              [true,  () => new CacheMap(1.5)],              [true,  () => new CacheMap(Number.NaN)],              [true,  () => new CacheMap(Number.POSITIVE_INFINITY)], +            // @ts-ignore - Ignore because it should throw an error              [true,  () => new CacheMap('a')]          ]; @@ -42,6 +44,7 @@ function testConstructor() {      });  } +/** */  function testApi() {      test('api', () => {          const data = [ @@ -103,10 +106,10 @@ function testApi() {                  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; +                    case 'get': returnValue = cache.get(args[0]); break; +                    case 'set': returnValue = cache.set(args[0], args[1]); break; +                    case 'has': returnValue = cache.has(args[0]); break; +                    case 'clear': returnValue = cache.clear(); break;                  }                  if (Object.prototype.hasOwnProperty.call(call, 'returnValue')) {                      const {returnValue: expectedReturnValue} = call; @@ -119,6 +122,7 @@ function testApi() {  } +/** */  function main() {      testConstructor();      testApi(); diff --git a/test/core.test.js b/test/core.test.js index 203460f4..685bf9dc 100644 --- a/test/core.test.js +++ b/test/core.test.js @@ -19,14 +19,17 @@  import {describe, expect, test} from 'vitest';  import {DynamicProperty, deepEqual} from '../ext/js/core.js'; +/** */  function testDynamicProperty() {      test('DynamicProperty', () => {          const data = [              {                  initialValue: 0, +                /** @type {{operation: ?string, expectedDefaultValue: number, expectedValue: number, expectedOverrideCount: number, expeectedEventOccurred: boolean, args: [value: number, priority?: number]}[]} */                  operations: [                      {                          operation: null, +                        args: [0],                          expectedDefaultValue: 0,                          expectedValue: 0,                          expectedOverrideCount: 0, @@ -147,6 +150,7 @@ function testDynamicProperty() {      });  } +/** */  function testDeepEqual() {      describe('deepEqual', () => {          const data = [ @@ -280,6 +284,7 @@ function testDeepEqual() {  } +/** */  function main() {      testDynamicProperty();      testDeepEqual(); diff --git a/test/css-json.test.js b/test/css-json.test.js index 0aaf7d10..66ecfeba 100644 --- a/test/css-json.test.js +++ b/test/css-json.test.js @@ -20,6 +20,7 @@ import fs from 'fs';  import {expect, test} from 'vitest';  import {formatRulesJson, generateRules, getTargets} from '../dev/generate-css-json'; +/** */  function main() {      test('css-json', () => {          for (const {cssFile, overridesCssFile, outputPath} of getTargets()) { diff --git a/test/database.test.js b/test/database.test.js index b53d0e65..ee818467 100644 --- a/test/database.test.js +++ b/test/database.test.js @@ -28,12 +28,21 @@ vi.stubGlobal('IDBKeyRange', IDBKeyRange);  vi.mock('../ext/js/language/dictionary-importer-media-loader.js'); +/** + * @param {string} dictionary + * @param {string} [dictionaryName] + * @returns {import('jszip')} + */  function createTestDictionaryArchive(dictionary, dictionaryName) {      const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', dictionary);      return createDictionaryArchive(dictionaryDirectory, dictionaryName);  } +/** + * @param {import('dictionary-importer').OnProgressCallback} [onProgress] + * @returns {DictionaryImporter} + */  function createDictionaryImporter(onProgress) {      const dictionaryImporterMediaLoader = new DictionaryImporterMediaLoader();      return new DictionaryImporter(dictionaryImporterMediaLoader, (...args) => { @@ -47,24 +56,53 @@ function createDictionaryImporter(onProgress) {  } +/** + * @param {import('dictionary-database').TermEntry[]} dictionaryDatabaseEntries + * @param {string} term + * @returns {number} + */  function countDictionaryDatabaseEntriesWithTerm(dictionaryDatabaseEntries, term) {      return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.term === term ? 1 : 0)), 0);  } +/** + * @param {import('dictionary-database').TermEntry[]} dictionaryDatabaseEntries + * @param {string} reading + * @returns {number} + */  function countDictionaryDatabaseEntriesWithReading(dictionaryDatabaseEntries, reading) {      return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.reading === reading ? 1 : 0)), 0);  } +/** + * @param {import('dictionary-database').TermMeta[]|import('dictionary-database').KanjiMeta[]} metas + * @param {import('dictionary-database').TermMetaType|import('dictionary-database').KanjiMetaType} mode + * @returns {number} + */  function countMetasWithMode(metas, mode) { -    return metas.reduce((i, v) => (i + (v.mode === mode ? 1 : 0)), 0); +    let i = 0; +    for (const item of metas) { +        if (item.mode === mode) { ++i; } +    } +    return i;  } +/** + * @param {import('dictionary-database').KanjiEntry[]} kanji + * @param {string} character + * @returns {number} + */  function countKanjiWithCharacter(kanji, character) { -    return kanji.reduce((i, v) => (i + (v.character === character ? 1 : 0)), 0); +    let i = 0; +    for (const item of kanji) { +        if (item.character === character) { ++i; } +    } +    return i;  } +/** */  async function testDatabase1() {      test('Database1', async () => {    // Load dictionary data          const testDictionary = createTestDictionaryArchive('valid-dictionary1'); @@ -172,6 +210,9 @@ async function testDatabase1() {      });  } +/** + * @param {DictionaryDatabase} database + */  async function testDatabaseEmpty1(database) {      test('DatabaseEmpty1', async () => {          const info = await database.getDictionaryInfo(); @@ -185,17 +226,22 @@ async function testDatabaseEmpty1(database) {      });  } +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */  async function testFindTermsBulkTest1(database, titles) {      test('FindTermsBulkTest1', async () => { +        /** @type {{inputs: {matchType: import('dictionary-database').MatchType, termList: string[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */          const data = [              {                  inputs: [                      { -                        matchType: null, +                        matchType: 'exact',                          termList: ['打', '打つ', '打ち込む']                      },                      { -                        matchType: null, +                        matchType: 'exact',                          termList: ['だ', 'ダース', 'うつ', 'ぶつ', 'うちこむ', 'ぶちこむ']                      },                      { @@ -223,7 +269,7 @@ async function testFindTermsBulkTest1(database, titles) {              {                  inputs: [                      { -                        matchType: null, +                        matchType: 'exact',                          termList: ['込む']                      }                  ], @@ -254,7 +300,7 @@ async function testFindTermsBulkTest1(database, titles) {              {                  inputs: [                      { -                        matchType: null, +                        matchType: 'exact',                          termList: []                      }                  ], @@ -281,8 +327,13 @@ async function testFindTermsBulkTest1(database, titles) {      });  } +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */  async function testTindTermsExactBulk1(database, titles) {      test('TindTermsExactBulk1', async () => { +        /** @type {{inputs: {termList: {term: string, reading: string}[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */          const data = [              {                  inputs: [ @@ -387,8 +438,13 @@ async function testTindTermsExactBulk1(database, titles) {      });  } +/** + * @param {DictionaryDatabase} database + * @param {string} mainDictionary + */  async function testFindTermsBySequenceBulk1(database, mainDictionary) {      test('FindTermsBySequenceBulk1', async () => { +        /** @type {{inputs: {sequenceList: number[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */          const data = [              {                  inputs: [ @@ -538,8 +594,13 @@ async function testFindTermsBySequenceBulk1(database, mainDictionary) {      });  } +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */  async function testFindTermMetaBulk1(database, titles) {      test('FindTermMetaBulk1', async () => { +        /** @type {{inputs: {termList: string[]}[], expectedResults: {total: number, modes: [key: import('dictionary-database').TermMetaType, count: number][]}}[]} */          const data = [              {                  inputs: [ @@ -606,8 +667,13 @@ async function testFindTermMetaBulk1(database, titles) {      });  } +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */  async function testFindKanjiBulk1(database, titles) {      test('FindKanjiBulk1', async () => { +        /** @type {{inputs: {kanjiList: string[]}[], expectedResults: {total: number, kanji: [key: string, count: number][]}}[]} */          const data = [              {                  inputs: [ @@ -660,8 +726,13 @@ async function testFindKanjiBulk1(database, titles) {      });  } +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */  async function testFindKanjiMetaBulk1(database, titles) {      test('FindKanjiMetaBulk1', async () => { +        /** @type {{inputs: {kanjiList: string[]}[], expectedResults: {total: number, modes: [key: import('dictionary-database').KanjiMetaType, count: number][]}}[]} */          const data = [              {                  inputs: [ @@ -714,6 +785,10 @@ async function testFindKanjiMetaBulk1(database, titles) {      });  } +/** + * @param {DictionaryDatabase} database + * @param {string} title + */  async function testFindTagForTitle1(database, title) {      test('FindTagForTitle1', async () => {          const data = [ @@ -769,6 +844,7 @@ async function testFindTagForTitle1(database, title) {  } +/** */  async function testDatabase2() {      test('Database2', async () => {    // Load dictionary data          const testDictionary = createTestDictionaryArchive('valid-dictionary1'); @@ -782,10 +858,12 @@ async function testDatabase2() {          // Setup database          const dictionaryDatabase = new DictionaryDatabase(); +        /** @type {import('dictionary-importer').ImportDetails} */ +        const detaultImportDetails = {prefixWildcardsSupported: false};          // 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.deleteDictionary(title, 1000, () => {})).rejects.toThrow('Database not open'); +        await expect(dictionaryDatabase.findTermsBulk(['?'], titles, 'exact')).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'); @@ -794,24 +872,25 @@ async function testDatabase2() {          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 expect(dictionaryDatabase.getDictionaryCounts([...titles.keys()], true)).rejects.toThrow('Database not open'); +        await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)).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, {}); +        await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails);          // dictionary already imported -        await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {})).rejects.toThrow('Dictionary is already imported'); +        await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)).rejects.toThrow('Dictionary is already imported');          await dictionaryDatabase.close();      });  } +/** */  async function testDatabase3() {      const invalidDictionaries = [          'invalid-dictionary1', @@ -828,12 +907,14 @@ async function testDatabase3() {              test(`${invalidDictionary}`, async () => {                  // Setup database                  const dictionaryDatabase = new DictionaryDatabase(); +                /** @type {import('dictionary-importer').ImportDetails} */ +                const detaultImportDetails = {prefixWildcardsSupported: false};                  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 expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)).rejects.toThrow('Dictionary has invalid data');                  await dictionaryDatabase.close();              });          } @@ -841,6 +922,7 @@ async function testDatabase3() {  } +/** */  async function main() {      beforeEach(async () => {          globalThis.indexedDB = new IDBFactory(); diff --git a/test/playwright/integration.spec.js b/test/playwright/integration.spec.js index b9a86d84..c8cf2f6d 100644 --- a/test/playwright/integration.spec.js +++ b/test/playwright/integration.spec.js @@ -45,7 +45,8 @@ test('search clipboard', async ({page, extensionId}) => {  test('anki add', async ({context, page, extensionId}) => {      // mock anki routes -    let resolve; +    /** @type {?(value: unknown) => void} */ +    let resolve = null;      const addNotePromise = new Promise((res) => {          resolve = res;      }); @@ -53,7 +54,7 @@ test('anki add', async ({context, page, extensionId}) => {          mockAnkiRouteHandler(route);          const req = route.request();          if (req.url().includes('127.0.0.1:8765') && req.postDataJSON().action === 'addNote') { -            resolve(req.postDataJSON()); +            /** @type {(value: unknown) => void} */ (resolve)(req.postDataJSON());          }      }); @@ -63,7 +64,11 @@ test('anki add', async ({context, page, extensionId}) => {      // load in test dictionary      const dictionary = createDictionaryArchive(path.join(root, 'test/data/dictionaries/valid-dictionary1'), 'valid-dictionary1');      const testDictionarySource = await dictionary.generateAsync({type: 'arraybuffer'}); -    await page.locator('input[id="dictionary-import-file-input"]').setInputFiles({name: 'valid-dictionary1.zip', buffer: Buffer.from(testDictionarySource)}); +    await page.locator('input[id="dictionary-import-file-input"]').setInputFiles({ +        name: 'valid-dictionary1.zip', +        mimeType: 'application/x-zip', +        buffer: Buffer.from(testDictionarySource) +    });      await expect(page.locator('id=dictionaries')).toHaveText('Dictionaries (1 installed, 1 enabled)', {timeout: 5 * 60 * 1000});      // connect to anki @@ -75,7 +80,7 @@ test('anki add', async ({context, page, extensionId}) => {      await page.locator('select.anki-card-deck').selectOption('Mock Deck');      await page.locator('select.anki-card-model').selectOption('Mock Model');      for (const modelField of mockModelFieldNames) { -        await page.locator(`[data-setting="anki.terms.fields.${modelField}"]`).fill(mockModelFieldsToAnkiValues[modelField]); +        await page.locator(`[data-setting="anki.terms.fields.${modelField}"]`).fill(/** @type {string} */ (mockModelFieldsToAnkiValues[modelField]));      }      await page.locator('#anki-cards-modal > div > div.modal-footer > button:nth-child(2)').click();      await writeToClipboardFromPage(page, '読むの例文'); diff --git a/test/playwright/playwright-util.js b/test/playwright/playwright-util.js index 5ceb92fd..ac68db4d 100644 --- a/test/playwright/playwright-util.js +++ b/test/playwright/playwright-util.js @@ -55,6 +55,7 @@ export const mockModelFieldNames = [      'Sentence'  ]; +/** @type {{[key: string]: string|undefined}} */  export const mockModelFieldsToAnkiValues = {      'Word': '{expression}',      'Reading': '{furigana-plain}', @@ -62,6 +63,10 @@ export const mockModelFieldsToAnkiValues = {      'Audio': '{audio}'  }; +/** + * @param {import('playwright').Route} route + * @returns {Promise<void>|undefined} + */  export const mockAnkiRouteHandler = (route) => {      const reqBody = route.request().postDataJSON();      const respBody = ankiRouteResponses[reqBody.action]; @@ -71,6 +76,11 @@ export const mockAnkiRouteHandler = (route) => {      route.fulfill(respBody);  }; +/** + * @param {import('playwright').Page} page + * @param {string} text + * @returns {Promise<void>} + */  export const writeToClipboardFromPage = async (page, text) => {      await page.evaluate(`navigator.clipboard.writeText('${text}')`);  }; @@ -100,6 +110,7 @@ const baseAnkiResp = {      contentType: 'text/json'  }; +/** @type {{[key: string]: import('core').SerializableObject}} */  const ankiRouteResponses = {      'version': Object.assign({body: JSON.stringify(6)}, baseAnkiResp),      'deckNames': Object.assign({body: JSON.stringify(['Mock Deck'])}, baseAnkiResp), |