diff options
-rw-r--r-- | test/data/translator-test-inputs.json | 13 | ||||
-rw-r--r-- | test/dictionary-data.test.js | 8 | ||||
-rw-r--r-- | test/dictionary-data.write.js | 8 | ||||
-rw-r--r-- | test/utilities/translator.js | 146 | ||||
-rw-r--r-- | types/ext/translation.d.ts | 7 | ||||
-rw-r--r-- | types/test/translator.d.ts | 11 |
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; |