From 4da4827bcbcdd1ef163f635d9b29416ff272b0bb Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 12:48:14 -0500 Subject: Add JSDoc type annotations to project (rebased) --- test/playwright/visual.spec.js | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) (limited to 'test/playwright') diff --git a/test/playwright/visual.spec.js b/test/playwright/visual.spec.js index 2f46990f..8b48b7c0 100644 --- a/test/playwright/visual.spec.js +++ b/test/playwright/visual.spec.js @@ -48,10 +48,16 @@ test('visual', async ({page, extensionId}) => { // take a screenshot of the settings page with jmdict loaded await expect.soft(page).toHaveScreenshot('settings-jmdict-loaded.png', {mask: [storage_locator]}); + /** + * @param {number} doc_number + * @param {number} test_number + * @param {import('@playwright/test').ElementHandle} el + * @param {{x: number, y: number}} offset + */ const screenshot = async (doc_number, test_number, el, offset) => { const test_name = 'doc' + doc_number + '-test' + test_number; - const box = await el.boundingBox(); + const box = (await el.boundingBox()) || {x: 0, y: 0, width: 0, height: 0}; // find the popup frame if it exists let popup_frame = page.frames().find((f) => f.url().includes('popup.html')); @@ -66,7 +72,7 @@ test('visual', async ({page, extensionId}) => { popup_frame = await frame_attached; // wait for popup to be attached } try { - await (await popup_frame.frameElement()).waitForElementState('visible', {timeout: 500}); // some tests don't have a popup, so don't fail if it's not there; TODO: check if the popup is expected to be there + await (await /** @type {import('@playwright/test').Frame} */ (popup_frame).frameElement()).waitForElementState('visible', {timeout: 500}); // some tests don't have a popup, so don't fail if it's not there; TODO: check if the popup is expected to be there } catch (error) { console.log(test_name + ' has no popup'); } @@ -75,7 +81,7 @@ test('visual', async ({page, extensionId}) => { await expect.soft(page).toHaveScreenshot(test_name + '.png'); await page.mouse.click(0, 0); // click away so popup disappears - await (await popup_frame.frameElement()).waitForElementState('hidden'); // wait for popup to disappear + await (await /** @type {import('@playwright/test').Frame} */ (popup_frame).frameElement()).waitForElementState('hidden'); // wait for popup to disappear }; // Load test-document1.html -- cgit v1.2.3 From a0a6291db8e2be29c4ed13645c250201b4552b9d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 29 Nov 2023 21:04:27 -0500 Subject: Update tests --- test/anki-note-builder.test.js | 42 +++++++++++--- test/cache-map.test.js | 12 ++-- test/core.test.js | 5 ++ test/css-json.test.js | 1 + test/database.test.js | 108 +++++++++++++++++++++++++++++++----- test/playwright/integration.spec.js | 13 +++-- test/playwright/playwright-util.js | 11 ++++ 7 files changed, 164 insertions(+), 28 deletions(-) (limited to 'test/playwright') 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} + */ +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} + */ 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} + */ 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|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} + */ 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), -- cgit v1.2.3