aboutsummaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2023-11-29 21:04:27 -0500
committertoasted-nutbread <toasted-nutbread@users.noreply.github.com>2023-11-29 21:04:27 -0500
commita0a6291db8e2be29c4ed13645c250201b4552b9d (patch)
tree18a0141f0aed7d87e1a1c0cbf1fd86b9de2ba8f4 /test
parent9ed81f25ab7f462546888a4ddd7fcecdf5ea1806 (diff)
Update tests
Diffstat (limited to 'test')
-rw-r--r--test/anki-note-builder.test.js42
-rw-r--r--test/cache-map.test.js12
-rw-r--r--test/core.test.js5
-rw-r--r--test/css-json.test.js1
-rw-r--r--test/database.test.js108
-rw-r--r--test/playwright/integration.spec.js13
-rw-r--r--test/playwright/playwright-util.js11
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),