aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authortoasted-nutbread <toasted-nutbread@users.noreply.github.com>2024-02-02 21:55:53 -0500
committerGitHub <noreply@github.com>2024-02-03 02:55:53 +0000
commitc4f248b0f95050fb373c898289b506d042a3731b (patch)
treef221b6dfd6b806b5fa0f26b9e88c6feb764f60fd
parent5a2bc4e542b7f22d1e6a4ba318cfcdc33817d34a (diff)
Improve translator test inputs typing (#601)
* Add type identifier * Improve type correctness of translator test utilities
-rw-r--r--test/data/translator-test-inputs.json13
-rw-r--r--test/dictionary-data.test.js8
-rw-r--r--test/dictionary-data.write.js8
-rw-r--r--test/utilities/translator.js146
-rw-r--r--types/ext/translation.d.ts7
-rw-r--r--types/test/translator.d.ts11
6 files changed, 145 insertions, 48 deletions
diff --git a/test/data/translator-test-inputs.json b/test/data/translator-test-inputs.json
index 588929de..2b8bebb3 100644
--- a/test/data/translator-test-inputs.json
+++ b/test/data/translator-test-inputs.json
@@ -1,6 +1,7 @@
{
"optionsPresets": {
"kanji": {
+ "type": "kanji",
"enabledDictionaryMap": [
[
"${title}",
@@ -13,6 +14,7 @@
"removeNonJapaneseCharacters": false
},
"default": {
+ "type": "terms",
"matchType": "exact",
"deinflect": true,
"mainDictionary": "${title}",
@@ -188,6 +190,7 @@
"options": [
"default",
{
+ "type": "terms",
"removeNonJapaneseCharacters": false,
"textReplacements": [
null,
@@ -210,6 +213,7 @@
"options": [
"default",
{
+ "type": "terms",
"removeNonJapaneseCharacters": false,
"textReplacements": [
null,
@@ -232,6 +236,7 @@
"options": [
"default",
{
+ "type": "terms",
"removeNonJapaneseCharacters": false,
"textReplacements": [
null,
@@ -254,6 +259,7 @@
"options": [
"default",
{
+ "type": "terms",
"removeNonJapaneseCharacters": false,
"textReplacements": [
null,
@@ -276,6 +282,7 @@
"options": [
"default",
{
+ "type": "terms",
"removeNonJapaneseCharacters": false,
"textReplacements": [
null,
@@ -361,6 +368,7 @@
"options": [
"default",
{
+ "type": "terms",
"convertNumericCharacters": "true",
"removeNonJapaneseCharacters": false
}
@@ -374,6 +382,7 @@
"options": [
"default",
{
+ "type": "terms",
"convertAlphabeticCharacters": "true",
"removeNonJapaneseCharacters": false
}
@@ -387,6 +396,7 @@
"options": [
"default",
{
+ "type": "terms",
"convertKatakanaToHiragana": "true"
}
]
@@ -399,6 +409,7 @@
"options": [
"default",
{
+ "type": "terms",
"convertHiraganaToKatakana": "true"
}
]
@@ -411,6 +422,7 @@
"options": [
"default",
{
+ "type": "terms",
"convertHalfWidthCharacters": "true",
"convertKatakanaToHiragana": "true"
}
@@ -424,6 +436,7 @@
"options": [
"default",
{
+ "type": "terms",
"collapseEmphaticSequences": "full"
}
]
diff --git a/test/dictionary-data.test.js b/test/dictionary-data.test.js
index 9f8ba6f0..dcc03d72 100644
--- a/test/dictionary-data.test.js
+++ b/test/dictionary-data.test.js
@@ -22,7 +22,7 @@ import {describe} from 'vitest';
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';
+import {createFindKanjiOptions, createFindTermsOptions} from './utilities/translator.js';
const dirname = path.dirname(fileURLToPath(import.meta.url));
const dictionaryName = 'Test Dictionary 2';
@@ -61,8 +61,7 @@ describe('Dictionary data', () => {
case 'findTerms':
{
const {mode, text} = data;
- /** @type {import('translation').FindTermsOptions} */
- const options = createFindOptions(dictionaryName, optionsPresets, data.options);
+ const options = createFindTermsOptions(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(dictionaryEntry, mode)) : null;
@@ -75,8 +74,7 @@ describe('Dictionary data', () => {
case 'findKanji':
{
const {text} = data;
- /** @type {import('translation').FindKanjiOptions} */
- const options = createFindOptions(dictionaryName, optionsPresets, data.options);
+ const options = createFindKanjiOptions(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(dictionaryEntry, 'split'));
diff --git a/test/dictionary-data.write.js b/test/dictionary-data.write.js
index b27007e6..a6e14656 100644
--- a/test/dictionary-data.write.js
+++ b/test/dictionary-data.write.js
@@ -21,7 +21,7 @@ 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';
+import {createFindKanjiOptions, createFindTermsOptions} from './utilities/translator.js';
/**
* @param {string} fileName
@@ -62,8 +62,7 @@ test('Write dictionary data expected data', async ({window, translator, expect})
case 'findTerms':
{
const {mode, text} = data;
- /** @type {import('translation').FindTermsOptions} */
- const options = createFindOptions(dictionaryName, optionsPresets, data.options);
+ const options = createFindTermsOptions(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(dictionaryEntry, mode)) : null;
@@ -75,8 +74,7 @@ test('Write dictionary data expected data', async ({window, translator, expect})
case 'findKanji':
{
const {text} = data;
- /** @type {import('translation').FindKanjiOptions} */
- const options = createFindOptions(dictionaryName, optionsPresets, data.options);
+ const options = createFindKanjiOptions(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(dictionaryEntry, 'split'));
diff --git a/test/utilities/translator.js b/test/utilities/translator.js
index da4ae27d..b77bfb39 100644
--- a/test/utilities/translator.js
+++ b/test/utilities/translator.js
@@ -16,67 +16,139 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
+const placeholder = '${title}';
/**
- * TODO : This function is not very type safe at the moment, could be improved.
- * @template {import('translation').FindTermsOptions|import('translation').FindKanjiOptions} T
- * @param {string} dictionaryName
+ * @template {import('test/translator').OptionsType} T
+ * @param {T} type
* @param {import('test/translator').OptionsPresetObject} optionsPresets
* @param {import('test/translator').OptionsList} optionsArray
- * @returns {T}
+ * @returns {import('test/translator').OptionsPresetGeneric<T>}
* @throws {Error}
*/
-export function createFindOptions(dictionaryName, optionsPresets, optionsArray) {
- /** @type {import('core').UnknownObject} */
- const options = {};
+function getCompositePreset(type, optionsPresets, optionsArray) {
+ const preset = /** @type {import('test/translator').OptionsPresetGeneric<T>} */ ({type});
if (!Array.isArray(optionsArray)) { optionsArray = [optionsArray]; }
for (const entry of optionsArray) {
switch (typeof entry) {
case 'string':
- if (!Object.prototype.hasOwnProperty.call(optionsPresets, entry)) {
- throw new Error('Invalid options preset');
+ {
+ if (!Object.prototype.hasOwnProperty.call(optionsPresets, entry)) {
+ throw new Error('Options preset not found');
+ }
+ const preset2 = optionsPresets[entry];
+ if (preset2.type !== type) {
+ throw new Error('Invalid options preset type');
+ }
+ Object.assign(preset, structuredClone(preset2));
}
- Object.assign(options, structuredClone(optionsPresets[entry]));
break;
case 'object':
- Object.assign(options, structuredClone(entry));
+ if (entry.type !== type) {
+ throw new Error('Invalid options preset type');
+ }
+ Object.assign(preset, structuredClone(entry));
break;
default:
throw new Error('Invalid options type');
}
}
+ return preset;
+}
- // Construct regex
- if (Array.isArray(options.textReplacements)) {
- options.textReplacements = options.textReplacements.map((value) => {
- if (Array.isArray(value)) {
- value = value.map(({pattern, flags, replacement}) => ({pattern: new RegExp(pattern, flags), replacement}));
- }
- return value;
- });
+
+/**
+ * @param {string} dictionaryName
+ * @param {import('test/translator').OptionsPresetObject} optionsPresets
+ * @param {import('test/translator').OptionsList} optionsArray
+ * @returns {import('translation').FindKanjiOptions}
+ */
+export function createFindKanjiOptions(dictionaryName, optionsPresets, optionsArray) {
+ const preset = getCompositePreset('kanji', optionsPresets, optionsArray);
+
+ /** @type {import('translation').KanjiEnabledDictionaryMap} */
+ const enabledDictionaryMap = new Map();
+ const presetEnabledDictionaryMap = preset.enabledDictionaryMap;
+ if (Array.isArray(presetEnabledDictionaryMap)) {
+ for (const [key, value] of presetEnabledDictionaryMap) {
+ enabledDictionaryMap.set(key === placeholder ? dictionaryName : key, value);
+ }
}
- // Update structure
- const placeholder = '${title}';
- if (options.mainDictionary === placeholder) {
- options.mainDictionary = dictionaryName;
+ return {
+ enabledDictionaryMap,
+ removeNonJapaneseCharacters: !!preset.removeNonJapaneseCharacters
+ };
+}
+
+/**
+ * @param {string} dictionaryName
+ * @param {import('test/translator').OptionsPresetObject} optionsPresets
+ * @param {import('test/translator').OptionsList} optionsArray
+ * @returns {import('translation').FindTermsOptions}
+ */
+export function createFindTermsOptions(dictionaryName, optionsPresets, optionsArray) {
+ const preset = getCompositePreset('terms', optionsPresets, optionsArray);
+
+ /** @type {import('translation').TermEnabledDictionaryMap} */
+ const enabledDictionaryMap = new Map();
+ const presetEnabledDictionaryMap = preset.enabledDictionaryMap;
+ if (Array.isArray(presetEnabledDictionaryMap)) {
+ for (const [key, value] of presetEnabledDictionaryMap) {
+ enabledDictionaryMap.set(key === placeholder ? dictionaryName : key, value);
+ }
}
- let {enabledDictionaryMap} = options;
- if (Array.isArray(enabledDictionaryMap)) {
- for (const entry of enabledDictionaryMap) {
- if (entry[0] === placeholder) {
- entry[0] = dictionaryName;
+
+ /** @type {import('translation').FindTermsTextReplacements} */
+ const textReplacements = [];
+ if (Array.isArray(preset.textReplacements)) {
+ for (const value of preset.textReplacements) {
+ if (Array.isArray(value)) {
+ const array = [];
+ for (const {pattern, flags, replacement} of value) {
+ array.push({pattern: new RegExp(pattern, flags), replacement});
+ }
+ textReplacements.push(array);
+ } else {
+ // Null
+ textReplacements.push(value);
}
}
- enabledDictionaryMap = new Map(enabledDictionaryMap);
- options.enabledDictionaryMap = enabledDictionaryMap;
}
- const {excludeDictionaryDefinitions} = options;
- options.excludeDictionaryDefinitions = (
- Array.isArray(excludeDictionaryDefinitions) ?
- new Set(excludeDictionaryDefinitions) :
- null
- );
- return /** @type {T} */ (options);
+ const {
+ matchType,
+ deinflect,
+ mainDictionary,
+ sortFrequencyDictionary,
+ sortFrequencyDictionaryOrder,
+ removeNonJapaneseCharacters,
+ convertHalfWidthCharacters,
+ convertNumericCharacters,
+ convertAlphabeticCharacters,
+ convertHiraganaToKatakana,
+ convertKatakanaToHiragana,
+ collapseEmphaticSequences,
+ excludeDictionaryDefinitions,
+ searchResolution
+ } = preset;
+
+ return {
+ matchType: typeof matchType !== 'undefined' ? matchType : 'exact',
+ deinflect: typeof deinflect !== 'undefined' ? deinflect : true,
+ mainDictionary: typeof mainDictionary !== 'undefined' && mainDictionary !== placeholder ? mainDictionary : dictionaryName,
+ sortFrequencyDictionary: typeof sortFrequencyDictionary !== 'undefined' ? sortFrequencyDictionary : null,
+ sortFrequencyDictionaryOrder: typeof sortFrequencyDictionaryOrder !== 'undefined' ? sortFrequencyDictionaryOrder : 'ascending',
+ removeNonJapaneseCharacters: typeof removeNonJapaneseCharacters !== 'undefined' ? removeNonJapaneseCharacters : false,
+ convertHalfWidthCharacters: typeof convertHalfWidthCharacters !== 'undefined' ? convertHalfWidthCharacters : 'false',
+ convertNumericCharacters: typeof convertNumericCharacters !== 'undefined' ? convertNumericCharacters : 'false',
+ convertAlphabeticCharacters: typeof convertAlphabeticCharacters !== 'undefined' ? convertAlphabeticCharacters : 'false',
+ convertHiraganaToKatakana: typeof convertHiraganaToKatakana !== 'undefined' ? convertHiraganaToKatakana : 'false',
+ convertKatakanaToHiragana: typeof convertKatakanaToHiragana !== 'undefined' ? convertKatakanaToHiragana : 'false',
+ collapseEmphaticSequences: typeof collapseEmphaticSequences !== 'undefined' ? collapseEmphaticSequences : 'false',
+ textReplacements,
+ enabledDictionaryMap,
+ excludeDictionaryDefinitions: Array.isArray(excludeDictionaryDefinitions) ? new Set(excludeDictionaryDefinitions) : null,
+ searchResolution: typeof searchResolution !== 'undefined' ? searchResolution : 'letter'
+ };
}
diff --git a/types/ext/translation.d.ts b/types/ext/translation.d.ts
index 604dbda6..c9a61be0 100644
--- a/types/ext/translation.d.ts
+++ b/types/ext/translation.d.ts
@@ -107,7 +107,7 @@ export type FindTermsOptions = {
/**
* An iterable sequence of text replacements to be applied during the term lookup process.
*/
- textReplacements: (FindTermsTextReplacement[] | null)[];
+ textReplacements: FindTermsTextReplacements;
/**
* The mapping of dictionaries to search for terms in.
* The key is the dictionary name.
@@ -158,6 +158,11 @@ export type FindTermsTextReplacement = {
};
/**
+ * Multiple text replacements.
+ */
+export type FindTermsTextReplacements = (FindTermsTextReplacement[] | null)[];
+
+/**
* Details about a dictionary.
*/
export type FindTermDictionary = {
diff --git a/types/test/translator.d.ts b/types/test/translator.d.ts
index b213f9e0..e3199225 100644
--- a/types/test/translator.d.ts
+++ b/types/test/translator.d.ts
@@ -16,6 +16,7 @@
*/
import type {FindTermsMatchType, FindTermsSortOrder, FindTermsVariantMode, FindTermsEmphaticSequencesMode, FindKanjiDictionary, FindTermDictionary} from '../ext/translation';
+import type {SearchResolution} from 'settings';
import type {FindTermsMode} from 'translator';
import type {DictionaryEntry} from 'dictionary';
import type {NoteData} from 'anki-templates';
@@ -30,11 +31,13 @@ export type OptionsList = string | (string | OptionsPreset)[];
export type OptionsPreset = FindKanjiOptionsPreset | FindTermsOptionsPreset;
export type FindKanjiOptionsPreset = {
+ type: 'kanji';
enabledDictionaryMap?: [key: string, value: FindKanjiDictionary][];
removeNonJapaneseCharacters?: boolean;
};
export type FindTermsOptionsPreset = {
+ type: 'terms';
matchType?: FindTermsMatchType;
deinflect?: boolean;
mainDictionary?: string;
@@ -50,8 +53,16 @@ export type FindTermsOptionsPreset = {
textReplacements?: (FindTermsTextReplacement[] | null)[];
enabledDictionaryMap?: [key: string, value: FindTermDictionary][];
excludeDictionaryDefinitions?: string[] | null;
+ searchResolution?: SearchResolution;
};
+export type OptionsType = OptionsPreset['type'];
+
+export type OptionsPresetGeneric<T extends OptionsType> = {
+ kanji: FindKanjiOptionsPreset;
+ terms: FindTermsOptionsPreset;
+}[T];
+
export type FindTermsTextReplacement = {
pattern: string;
flags: string;