yomichan/test/test-options-util.js
toasted-nutbread a9fe2d03b2
Update dictionary settings structure (#1587)
* Update dictionary settings structure to use an array instead of an object

* Update ensureDictionarySettings implementation

* Remove some usage of ObjectPropertyAccessor
2021-04-03 13:02:49 -04:00

940 lines
34 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/*
* Copyright (C) 2020-2021 Yomichan Authors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
const fs = require('fs');
const url = require('url');
const path = require('path');
const assert = require('assert');
const {testMain} = require('../dev/util');
const {VM} = require('../dev/vm');
function createVM(extDir) {
const chrome = {
runtime: {
getURL(path2) {
return url.pathToFileURL(path.join(extDir, path2.replace(/^\//, ''))).href;
}
}
};
async function fetch(url2) {
const filePath = url.fileURLToPath(url2);
await Promise.resolve();
const content = fs.readFileSync(filePath, {encoding: null});
return {
ok: true,
status: 200,
statusText: 'OK',
text: async () => Promise.resolve(content.toString('utf8')),
json: async () => Promise.resolve(JSON.parse(content.toString('utf8')))
};
}
const vm = new VM({chrome, fetch});
vm.execute([
'js/core.js',
'js/general/cache-map.js',
'js/data/json-schema.js',
'js/templates/template-patcher.js',
'js/data/options-util.js'
]);
return vm;
}
function clone(value) {
return JSON.parse(JSON.stringify(value));
}
function createProfileOptionsTestData1() {
return {
version: 14,
general: {
enable: true,
enableClipboardPopups: false,
resultOutputMode: 'group',
debugInfo: false,
maxResults: 32,
showAdvanced: false,
popupDisplayMode: 'default',
popupWidth: 400,
popupHeight: 250,
popupHorizontalOffset: 0,
popupVerticalOffset: 10,
popupHorizontalOffset2: 10,
popupVerticalOffset2: 0,
popupHorizontalTextPosition: 'below',
popupVerticalTextPosition: 'before',
popupScalingFactor: 1,
popupScaleRelativeToPageZoom: false,
popupScaleRelativeToVisualViewport: true,
showGuide: true,
compactTags: false,
compactGlossaries: false,
mainDictionary: '',
popupTheme: 'default',
popupOuterTheme: 'default',
customPopupCss: '',
customPopupOuterCss: '',
enableWanakana: true,
enableClipboardMonitor: false,
showPitchAccentDownstepNotation: true,
showPitchAccentPositionNotation: true,
showPitchAccentGraph: false,
showIframePopupsInRootFrame: false,
useSecurePopupFrameUrl: true,
usePopupShadowDom: true
},
audio: {
enabled: true,
sources: ['jpod101'],
volume: 100,
autoPlay: false,
customSourceUrl: '',
textToSpeechVoice: ''
},
scanning: {
middleMouse: true,
touchInputEnabled: true,
selectText: true,
alphanumeric: true,
autoHideResults: false,
delay: 20,
length: 10,
modifier: 'shift',
deepDomScan: false,
popupNestingMaxDepth: 0,
enablePopupSearch: false,
enableOnPopupExpressions: false,
enableOnSearchPage: true,
enableSearchTags: false,
layoutAwareScan: false
},
translation: {
convertHalfWidthCharacters: 'false',
convertNumericCharacters: 'false',
convertAlphabeticCharacters: 'false',
convertHiraganaToKatakana: 'false',
convertKatakanaToHiragana: 'variant',
collapseEmphaticSequences: 'false'
},
dictionaries: {
'Test Dictionary': {
priority: 0,
enabled: true,
allowSecondarySearches: false
}
},
parsing: {
enableScanningParser: true,
enableMecabParser: false,
selectedParser: null,
termSpacing: true,
readingMode: 'hiragana'
},
anki: {
enable: false,
server: 'http://127.0.0.1:8765',
tags: ['yomichan'],
sentenceExt: 200,
screenshot: {format: 'png', quality: 92},
terms: {deck: '', model: '', fields: {}},
kanji: {deck: '', model: '', fields: {}},
duplicateScope: 'collection',
fieldTemplates: null
}
};
}
function createOptionsTestData1() {
return {
profiles: [
{
name: 'Default',
options: createProfileOptionsTestData1(),
conditionGroups: [
{
conditions: [
{
type: 'popupLevel',
operator: 'equal',
value: 1
},
{
type: 'popupLevel',
operator: 'notEqual',
value: 0
},
{
type: 'popupLevel',
operator: 'lessThan',
value: 3
},
{
type: 'popupLevel',
operator: 'greaterThan',
value: 0
},
{
type: 'popupLevel',
operator: 'lessThanOrEqual',
value: 2
},
{
type: 'popupLevel',
operator: 'greaterThanOrEqual',
value: 1
}
]
},
{
conditions: [
{
type: 'url',
operator: 'matchDomain',
value: 'example.com'
},
{
type: 'url',
operator: 'matchRegExp',
value: 'example\\.com'
}
]
},
{
conditions: [
{
type: 'modifierKeys',
operator: 'are',
value: [
'ctrl',
'shift'
]
},
{
type: 'modifierKeys',
operator: 'areNot',
value: [
'alt',
'shift'
]
},
{
type: 'modifierKeys',
operator: 'include',
value: 'alt'
},
{
type: 'modifierKeys',
operator: 'notInclude',
value: 'ctrl'
}
]
}
]
}
],
profileCurrent: 0,
version: 2,
global: {
database: {
prefixWildcardsSupported: false
}
}
};
}
function createProfileOptionsUpdatedTestData1() {
return {
general: {
enable: true,
resultOutputMode: 'group',
debugInfo: false,
maxResults: 32,
showAdvanced: false,
popupDisplayMode: 'default',
popupWidth: 400,
popupHeight: 250,
popupHorizontalOffset: 0,
popupVerticalOffset: 10,
popupHorizontalOffset2: 10,
popupVerticalOffset2: 0,
popupHorizontalTextPosition: 'below',
popupVerticalTextPosition: 'before',
popupScalingFactor: 1,
popupScaleRelativeToPageZoom: false,
popupScaleRelativeToVisualViewport: true,
showGuide: true,
compactTags: false,
glossaryLayoutMode: 'default',
mainDictionary: '',
popupTheme: 'default',
popupOuterTheme: 'default',
customPopupCss: '',
customPopupOuterCss: '',
enableWanakana: true,
showPitchAccentDownstepNotation: true,
showPitchAccentPositionNotation: true,
showPitchAccentGraph: false,
showIframePopupsInRootFrame: false,
useSecurePopupFrameUrl: true,
usePopupShadowDom: true,
usePopupWindow: false,
popupCurrentIndicatorMode: 'triangle',
popupActionBarVisibility: 'auto',
popupActionBarLocation: 'top',
frequencyDisplayMode: 'split-tags-grouped',
termDisplayMode: 'ruby'
},
audio: {
enabled: true,
sources: ['jpod101'],
volume: 100,
autoPlay: false,
customSourceUrl: '',
customSourceType: 'audio',
textToSpeechVoice: ''
},
scanning: {
touchInputEnabled: true,
selectText: true,
alphanumeric: true,
autoHideResults: false,
delay: 20,
length: 10,
deepDomScan: false,
popupNestingMaxDepth: 0,
enablePopupSearch: false,
enableOnPopupExpressions: false,
enableOnSearchPage: true,
enableSearchTags: false,
layoutAwareScan: false,
hideDelay: 0,
pointerEventsEnabled: false,
preventMiddleMouse: {
onWebPages: false,
onPopupPages: false,
onSearchPages: false,
onSearchQuery: false
},
inputs: [
{
include: 'shift',
exclude: 'mouse0',
types: {
mouse: true,
touch: false,
pen: false
},
options: {
showAdvanced: false,
searchTerms: true,
searchKanji: true,
scanOnTouchMove: true,
scanOnPenHover: true,
scanOnPenPress: true,
scanOnPenRelease: false,
preventTouchScrolling: true
}
},
{
include: 'mouse2',
exclude: '',
types: {
mouse: true,
touch: false,
pen: false
},
options: {
showAdvanced: false,
searchTerms: true,
searchKanji: true,
scanOnTouchMove: true,
scanOnPenHover: true,
scanOnPenPress: true,
scanOnPenRelease: false,
preventTouchScrolling: true
}
},
{
include: '',
exclude: '',
types: {
mouse: false,
touch: true,
pen: true
},
options: {
showAdvanced: false,
searchTerms: true,
searchKanji: true,
scanOnTouchMove: true,
scanOnPenHover: true,
scanOnPenPress: true,
scanOnPenRelease: false,
preventTouchScrolling: true
}
}
]
},
translation: {
convertHalfWidthCharacters: 'false',
convertNumericCharacters: 'false',
convertAlphabeticCharacters: 'false',
convertHiraganaToKatakana: 'false',
convertKatakanaToHiragana: 'variant',
collapseEmphaticSequences: 'false',
textReplacements: {
searchOriginal: true,
groups: []
}
},
dictionaries: [
{
name: 'Test Dictionary',
priority: 0,
enabled: true,
allowSecondarySearches: false,
definitionsCollapsible: 'not-collapsible'
}
],
parsing: {
enableScanningParser: true,
enableMecabParser: false,
selectedParser: null,
termSpacing: true,
readingMode: 'hiragana'
},
anki: {
enable: false,
server: 'http://127.0.0.1:8765',
tags: ['yomichan'],
screenshot: {format: 'png', quality: 92},
terms: {deck: '', model: '', fields: {}},
kanji: {deck: '', model: '', fields: {}},
duplicateScope: 'collection',
checkForDuplicates: true,
fieldTemplates: null,
suspendNewCards: false
},
sentenceParsing: {
scanExtent: 200,
enableTerminationCharacters: true,
terminationCharacters: [
{enabled: true, character1: '「', character2: '」', includeCharacterAtStart: false, includeCharacterAtEnd: false},
{enabled: true, character1: '『', character2: '』', includeCharacterAtStart: false, includeCharacterAtEnd: false},
{enabled: true, character1: '"', character2: '"', includeCharacterAtStart: false, includeCharacterAtEnd: false},
{enabled: true, character1: '\'', character2: '\'', includeCharacterAtStart: false, includeCharacterAtEnd: false},
{enabled: true, character1: '.', character2: null, includeCharacterAtStart: false, includeCharacterAtEnd: true},
{enabled: true, character1: '!', character2: null, includeCharacterAtStart: false, includeCharacterAtEnd: true},
{enabled: true, character1: '?', character2: null, includeCharacterAtStart: false, includeCharacterAtEnd: true},
{enabled: true, character1: '', character2: null, includeCharacterAtStart: false, includeCharacterAtEnd: true},
{enabled: true, character1: '。', character2: null, includeCharacterAtStart: false, includeCharacterAtEnd: true},
{enabled: true, character1: '', character2: null, includeCharacterAtStart: false, includeCharacterAtEnd: true},
{enabled: true, character1: '', character2: null, includeCharacterAtStart: false, includeCharacterAtEnd: true},
{enabled: true, character1: '…', character2: null, includeCharacterAtStart: false, includeCharacterAtEnd: true}
]
},
inputs: {
hotkeys: [
{action: 'close', argument: '', key: 'Escape', modifiers: [], scopes: ['popup'], enabled: true},
{action: 'focusSearchBox', argument: '', key: 'Escape', modifiers: [], scopes: ['search'], enabled: true},
{action: 'previousEntry', argument: '3', key: 'PageUp', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'nextEntry', argument: '3', key: 'PageDown', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'lastEntry', argument: '', key: 'End', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'firstEntry', argument: '', key: 'Home', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'previousEntry', argument: '1', key: 'ArrowUp', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'nextEntry', argument: '1', key: 'ArrowDown', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'historyBackward', argument: '', key: 'KeyB', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'historyForward', argument: '', key: 'KeyF', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'addNoteKanji', argument: '', key: 'KeyK', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'addNoteTermKanji', argument: '', key: 'KeyE', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'addNoteTermKana', argument: '', key: 'KeyR', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'playAudio', argument: '', key: 'KeyP', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'viewNote', argument: '', key: 'KeyV', modifiers: ['alt'], scopes: ['popup', 'search'], enabled: true},
{action: 'copyHostSelection', argument: '', key: 'KeyC', modifiers: ['ctrl'], scopes: ['popup'], enabled: true}
]
},
popupWindow: {
width: 400,
height: 250,
left: 0,
top: 0,
useLeft: false,
useTop: false,
windowType: 'popup',
windowState: 'normal'
},
clipboard: {
enableBackgroundMonitor: false,
enableSearchPageMonitor: false,
autoSearchContent: true,
maximumSearchLength: 1000
}
};
}
function createOptionsUpdatedTestData1() {
return {
profiles: [
{
name: 'Default',
options: createProfileOptionsUpdatedTestData1(),
conditionGroups: [
{
conditions: [
{
type: 'popupLevel',
operator: 'equal',
value: '1'
},
{
type: 'popupLevel',
operator: 'notEqual',
value: '0'
},
{
type: 'popupLevel',
operator: 'lessThan',
value: '3'
},
{
type: 'popupLevel',
operator: 'greaterThan',
value: '0'
},
{
type: 'popupLevel',
operator: 'lessThanOrEqual',
value: '2'
},
{
type: 'popupLevel',
operator: 'greaterThanOrEqual',
value: '1'
}
]
},
{
conditions: [
{
type: 'url',
operator: 'matchDomain',
value: 'example.com'
},
{
type: 'url',
operator: 'matchRegExp',
value: 'example\\.com'
}
]
},
{
conditions: [
{
type: 'modifierKeys',
operator: 'are',
value: 'ctrl, shift'
},
{
type: 'modifierKeys',
operator: 'areNot',
value: 'alt, shift'
},
{
type: 'modifierKeys',
operator: 'include',
value: 'alt'
},
{
type: 'modifierKeys',
operator: 'notInclude',
value: 'ctrl'
}
]
}
]
}
],
profileCurrent: 0,
version: 11,
global: {
database: {
prefixWildcardsSupported: false
}
}
};
}
async function testUpdate(extDir) {
const vm = createVM(extDir);
const [OptionsUtil] = vm.get(['OptionsUtil']);
const optionsUtil = new OptionsUtil();
await optionsUtil.prepare();
const options = createOptionsTestData1();
const optionsUpdated = clone(await optionsUtil.update(options));
const optionsExpected = createOptionsUpdatedTestData1();
assert.deepStrictEqual(optionsUpdated, optionsExpected);
}
async function testDefault(extDir) {
const data = [
(options) => options,
(options) => {
delete options.profiles[0].options.audio.autoPlay;
},
(options) => {
options.profiles[0].options.audio.autoPlay = void 0;
}
];
const vm = createVM(extDir);
const [OptionsUtil] = vm.get(['OptionsUtil']);
const optionsUtil = new OptionsUtil();
await optionsUtil.prepare();
for (const modify of data) {
const options = optionsUtil.getDefault();
const optionsModified = clone(options);
modify(optionsModified);
const optionsUpdated = await optionsUtil.update(clone(optionsModified));
assert.deepStrictEqual(clone(optionsUpdated), clone(options));
}
}
async function testFieldTemplatesUpdate(extDir) {
const vm = createVM(extDir);
const [OptionsUtil, TemplatePatcher] = vm.get(['OptionsUtil', 'TemplatePatcher']);
const optionsUtil = new OptionsUtil();
await optionsUtil.prepare();
const templatePatcher = new TemplatePatcher();
const loadDataFile = (fileName) => {
const content = fs.readFileSync(path.join(extDir, fileName), {encoding: 'utf8'});
return templatePatcher.parsePatch(content).addition;
};
const update2 = loadDataFile('data/templates/anki-field-templates-upgrade-v2.handlebars');
const update4 = loadDataFile('data/templates/anki-field-templates-upgrade-v4.handlebars');
const update6 = loadDataFile('data/templates/anki-field-templates-upgrade-v6.handlebars');
const update8 = loadDataFile('data/templates/anki-field-templates-upgrade-v8.handlebars');
const update10 = loadDataFile('data/templates/anki-field-templates-upgrade-v10.handlebars');
const data = [
// Standard format
{
old: `
{{#*inline "character"}}
{{~definition.character~}}
{{/inline}}
{{~> (lookup . "marker") ~}}`.trimStart(),
expected: `
{{#*inline "character"}}
{{~definition.character~}}
{{/inline}}
${update2}
${update4}
${update6}
${update8}
${update10}
{{~> (lookup . "marker") ~}}`.trimStart()
},
// Non-standard marker format
{
old: `
{{#*inline "character"}}
{{~definition.character~}}
{{/inline}}
{{~> (lookup . "marker2") ~}}`.trimStart(),
expected: `
{{#*inline "character"}}
{{~definition.character~}}
{{/inline}}
{{~> (lookup . "marker2") ~}}
${update2}
${update4}
${update6}
${update8}
${update10}`.trimStart()
},
// Empty test
{
old: `
{{~> (lookup . "marker") ~}}`.trimStart(),
expected: `
${update2}
${update4}
${update6}
${update8}
${update10}
{{~> (lookup . "marker") ~}}`.trimStart()
},
// Definition tags update
{
old: `
{{#*inline "glossary-single"}}
{{~#unless brief~}}
{{~#if definitionTags~}}<i>({{#each definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each}})</i> {{/if~}}
{{~#if only~}}({{#each only}}{{{.}}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}}
{{~/unless~}}
{{/inline}}
{{#*inline "glossary-single2"}}
{{~#unless brief~}}
{{~#if definitionTags~}}<i>({{#each definitionTags}}{{name}}{{#unless @last}}, {{/unless}}{{/each}})</i> {{/if~}}
{{~#if only~}}({{#each only}}{{{.}}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}}
{{~/unless~}}
{{/inline}}
{{#*inline "glossary"}}
{{~> glossary-single definition brief=brief compactGlossaries=compactGlossaries~}}
{{~> glossary-single definition brief=brief compactGlossaries=../compactGlossaries~}}
{{/inline}}
{{~> (lookup . "marker") ~}}
`.trimStart(),
expected: `
{{#*inline "glossary-single"}}
{{~#unless brief~}}
{{~#scope~}}
{{~#set "any" false}}{{/set~}}
{{~#if definitionTags~}}{{#each definitionTags~}}
{{~#if (op "||" (op "!" ../data.compactTags) (op "!" redundant))~}}
{{~#if (get "any")}}, {{else}}<i>({{/if~}}
{{name}}
{{~#set "any" true}}{{/set~}}
{{~/if~}}
{{~/each~}}
{{~#if (get "any")}})</i> {{/if~}}
{{~/if~}}
{{~/scope~}}
{{~#if only~}}({{#each only}}{{{.}}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}}
{{~/unless~}}
{{/inline}}
{{#*inline "glossary-single2"}}
{{~#unless brief~}}
{{~#scope~}}
{{~#set "any" false}}{{/set~}}
{{~#if definitionTags~}}{{#each definitionTags~}}
{{~#if (op "||" (op "!" ../data.compactTags) (op "!" redundant))~}}
{{~#if (get "any")}}, {{else}}<i>({{/if~}}
{{name}}
{{~#set "any" true}}{{/set~}}
{{~/if~}}
{{~/each~}}
{{~#if (get "any")}})</i> {{/if~}}
{{~/if~}}
{{~/scope~}}
{{~#if only~}}({{#each only}}{{{.}}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}}
{{~/unless~}}
{{/inline}}
{{#*inline "glossary"}}
{{~> glossary-single definition brief=brief compactGlossaries=compactGlossaries data=.~}}
{{~> glossary-single definition brief=brief compactGlossaries=../compactGlossaries data=../.~}}
{{/inline}}
${update2}
${update4}
${update6}
${update8}
${update10}
{{~> (lookup . "marker") ~}}
`.trimStart()
},
// glossary and glossary-brief update
{
oldVersion: 7,
old: `
{{#*inline "glossary-single"}}
{{~#unless brief~}}
{{~#scope~}}
{{~#set "any" false}}{{/set~}}
{{~#if definitionTags~}}{{#each definitionTags~}}
{{~#if (op "||" (op "!" ../data.compactTags) (op "!" redundant))~}}
{{~#if (get "any")}}, {{else}}<i>({{/if~}}
{{name}}
{{~#set "any" true}}{{/set~}}
{{~/if~}}
{{~/each~}}
{{~#if (get "any")}})</i> {{/if~}}
{{~/if~}}
{{~/scope~}}
{{~#if only~}}({{#each only}}{{{.}}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}}
{{~/unless~}}
{{~#if glossary.[1]~}}
{{~#if compactGlossaries~}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{#unless @last}} | {{/unless}}{{/each}}
{{~else~}}
<ul>{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}</ul>
{{~/if~}}
{{~else~}}
{{~#multiLine}}{{glossary.[0]}}{{/multiLine~}}
{{~/if~}}
{{/inline}}
{{#*inline "character"}}
{{~definition.character~}}
{{/inline}}
{{#*inline "glossary"}}
<div style="text-align: left;">
{{~#if modeKanji~}}
{{~#if definition.glossary.[1]~}}
<ol>{{#each definition.glossary}}<li>{{.}}</li>{{/each}}</ol>
{{~else~}}
{{definition.glossary.[0]}}
{{~/if~}}
{{~else~}}
{{~#if group~}}
{{~#if definition.definitions.[1]~}}
<ol>{{#each definition.definitions}}<li>{{> glossary-single brief=../brief compactGlossaries=../compactGlossaries data=../.}}</li>{{/each}}</ol>
{{~else~}}
{{~> glossary-single definition.definitions.[0] brief=brief compactGlossaries=compactGlossaries data=.~}}
{{~/if~}}
{{~else if merge~}}
{{~#if definition.definitions.[1]~}}
<ol>{{#each definition.definitions}}<li>{{> glossary-single brief=../brief compactGlossaries=../compactGlossaries data=../.}}</li>{{/each}}</ol>
{{~else~}}
{{~> glossary-single definition.definitions.[0] brief=brief compactGlossaries=compactGlossaries data=.~}}
{{~/if~}}
{{~else~}}
{{~> glossary-single definition brief=brief compactGlossaries=compactGlossaries data=.~}}
{{~/if~}}
{{~/if~}}
</div>
{{/inline}}
{{#*inline "glossary-brief"}}
{{~> glossary brief=true ~}}
{{/inline}}
{{~> (lookup . "marker") ~}}`.trimStart(),
expected: `
{{#*inline "glossary-single"}}
{{~#unless brief~}}
{{~#scope~}}
{{~#set "any" false}}{{/set~}}
{{~#each definitionTags~}}
{{~#if (op "||" (op "!" @root.compactTags) (op "!" redundant))~}}
{{~#if (get "any")}}, {{else}}<i>({{/if~}}
{{name}}
{{~#set "any" true}}{{/set~}}
{{~/if~}}
{{~/each~}}
{{~#unless noDictionaryTag~}}
{{~#if (op "||" (op "!" @root.compactTags) (op "!==" dictionary (get "previousDictionary")))~}}
{{~#if (get "any")}}, {{else}}<i>({{/if~}}
{{dictionary}}
{{~#set "any" true}}{{/set~}}
{{~/if~}}
{{~/unless~}}
{{~#if (get "any")}})</i> {{/if~}}
{{~/scope~}}
{{~#if only~}}({{#each only}}{{.}}{{#unless @last}}, {{/unless}}{{/each}} only) {{/if~}}
{{~/unless~}}
{{~#if (op "<=" glossary.length 1)~}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{/each}}
{{~else if @root.compactGlossaries~}}
{{#each glossary}}{{#multiLine}}{{.}}{{/multiLine}}{{#unless @last}} | {{/unless}}{{/each}}
{{~else~}}
<ul>{{#each glossary}}<li>{{#multiLine}}{{.}}{{/multiLine}}</li>{{/each}}</ul>
{{~/if~}}
{{~#set "previousDictionary" dictionary~}}{{~/set~}}
{{/inline}}
{{#*inline "character"}}
{{~definition.character~}}
{{/inline}}
{{~#*inline "glossary"~}}
<div style="text-align: left;">
{{~#scope~}}
{{~#if (op "===" definition.type "term")~}}
{{~> glossary-single definition brief=brief noDictionaryTag=noDictionaryTag ~}}
{{~else if (op "||" (op "===" definition.type "termGrouped") (op "===" definition.type "termMerged"))~}}
{{~#if (op ">" definition.definitions.length 1)~}}
<ol>{{~#each definition.definitions~}}<li>{{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}}</li>{{~/each~}}</ol>
{{~else~}}
{{~#each definition.definitions~}}{{~> glossary-single . brief=../brief noDictionaryTag=../noDictionaryTag ~}}{{~/each~}}
{{~/if~}}
{{~else if (op "===" definition.type "kanji")~}}
{{~#if (op ">" definition.glossary.length 1)~}}
<ol>{{#each definition.glossary}}<li>{{.}}</li>{{/each}}</ol>
{{~else~}}
{{~#each definition.glossary~}}{{.}}{{~/each~}}
{{~/if~}}
{{~/if~}}
{{~/scope~}}
</div>
{{~/inline~}}
{{#*inline "glossary-no-dictionary"}}
{{~> glossary noDictionaryTag=true ~}}
{{/inline}}
{{#*inline "glossary-brief"}}
{{~> glossary brief=true ~}}
{{/inline}}
${update8}
${update10}
{{~> (lookup . "marker") ~}}`.trimStart()
}
];
for (const {old, expected, oldVersion} of data) {
const options = createOptionsTestData1();
options.profiles[0].options.anki.fieldTemplates = old;
if (typeof oldVersion === 'number') {
options.version = oldVersion;
}
const optionsUpdated = clone(await optionsUtil.update(options));
const fieldTemplatesActual = optionsUpdated.profiles[0].options.anki.fieldTemplates;
assert.deepStrictEqual(fieldTemplatesActual, expected);
}
}
async function main() {
const extDir = path.join(__dirname, '..', 'ext');
await testUpdate(extDir);
await testDefault(extDir);
await testFieldTemplatesUpdate(extDir);
}
if (require.main === module) { testMain(main); }