yomichan/test/test-options-util.js
soriac ba3f7b3e96
Show any custom tags on words that have anki cards created (#1628)
* Proof-of-concept for showing card tags (#1626)

* Resolved most PR comments:
- Added a snackbar notification when clicking tag button
- Replaced magnifying glass icon with new tag icon
- Button now contains a span w/icon, to use text color
- Removed unnecessary attributes from button
- Backend now returns full noteInfos object
- Frontend now handles filtering tags

* Add options to show/hide tag button & filter tags

* Do not show tags button if, after filtering, we have zero tags.

* Change tags option to enums, optimize tags intersection check & fix code style.

* Update options-util.js to include new tag options.

* Fix wording on new tag setting.

* Add CSS to remove hidden buttons from the display layout.

* getAnkiNoteInfo extra parameter for additional info.

* Add new tag option to tests.

* Remove unnecessary changes related to anki tags option.

* Code style fixes.
2021-04-30 17:57:53 -04:00

941 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: 'http://localhost/audio.mp3?term={expression}&reading={reading}',
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: 'http://localhost/audio.mp3?term={term}&reading={reading}',
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',
displayTags: 'never',
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); }