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; |