From 4da4827bcbcdd1ef163f635d9b29416ff272b0bb Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 12:48:14 -0500 Subject: Add JSDoc type annotations to project (rebased) --- test/data/anki-note-builder-test-results.json | 48 +- test/data/html/test-document2-script.js | 40 +- test/data/translator-test-results-note-data1.json | 280 ++--- test/data/translator-test-results.json | 236 ++--- test/dictionary.test.js | 6 + test/jsconfig.json | 39 + test/playwright/visual.spec.js | 12 +- test/test-all.js | 71 ++ test/test-anki-note-builder.js | 322 ++++++ test/test-cache-map.js | 137 +++ test/test-core.js | 300 ++++++ test/test-database.js | 982 ++++++++++++++++++ test/test-document-util.js | 339 ++++++ test/test-hotkey-util.js | 189 ++++ test/test-japanese-util.js | 915 +++++++++++++++++ test/test-json-schema.js | 1048 +++++++++++++++++++ test/test-manifest.js | 49 + test/test-object-property-accessor.js | 458 +++++++++ test/test-profile-conditions-util.js | 1136 +++++++++++++++++++++ test/test-text-source-map.js | 244 +++++ test/test-translator.js | 102 ++ test/test-workers.js | 168 +++ 22 files changed, 6833 insertions(+), 288 deletions(-) create mode 100644 test/jsconfig.json create mode 100644 test/test-all.js create mode 100644 test/test-anki-note-builder.js create mode 100644 test/test-cache-map.js create mode 100644 test/test-core.js create mode 100644 test/test-database.js create mode 100644 test/test-document-util.js create mode 100644 test/test-hotkey-util.js create mode 100644 test/test-japanese-util.js create mode 100644 test/test-json-schema.js create mode 100644 test/test-manifest.js create mode 100644 test/test-object-property-accessor.js create mode 100644 test/test-profile-conditions-util.js create mode 100644 test/test-text-source-map.js create mode 100644 test/test-translator.js create mode 100644 test/test-workers.js (limited to 'test') diff --git a/test/data/anki-note-builder-test-results.json b/test/data/anki-note-builder-test-results.json index b752e878..49542e39 100644 --- a/test/data/anki-note-builder-test-results.json +++ b/test/data/anki-note-builder-test-results.json @@ -194,7 +194,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[う]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -224,7 +224,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[ぶ]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -379,7 +379,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打ち込む", - "frequencies": "", + "frequencies": "", "furigana": "む", "furigana-plain": "打[う]ち 込[こ]む", "glossary": "
(vt, Test Dictionary 2)
", @@ -409,7 +409,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打ち込む", - "frequencies": "", + "frequencies": "", "furigana": "む", "furigana-plain": "打[ぶ]ち 込[こ]む", "glossary": "
(vt, Test Dictionary 2)
", @@ -499,7 +499,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[う]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -529,7 +529,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[ぶ]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -759,7 +759,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[う]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -824,7 +824,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[ぶ]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -889,7 +889,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打ち込む", - "frequencies": "", + "frequencies": "", "furigana": "む", "furigana-plain": "打[う]ち 込[こ]む", "glossary": "
(vt, Test Dictionary 2)
", @@ -949,7 +949,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[う]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -1014,7 +1014,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打ち込む", - "frequencies": "", + "frequencies": "", "furigana": "む", "furigana-plain": "打[ぶ]ち 込[こ]む", "glossary": "
(vt, Test Dictionary 2)
", @@ -1074,7 +1074,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[ぶ]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -1526,7 +1526,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打ち込む", - "frequencies": "", + "frequencies": "", "furigana": "む", "furigana-plain": "打[う]ち 込[こ]む", "glossary": "
(vt, Test Dictionary 2)
", @@ -1556,7 +1556,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打ち込む", - "frequencies": "", + "frequencies": "", "furigana": "む", "furigana-plain": "打[ぶ]ち 込[こ]む", "glossary": "
(vt, Test Dictionary 2)
", @@ -1646,7 +1646,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[う]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -1676,7 +1676,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[ぶ]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -1831,7 +1831,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打ち込む", - "frequencies": "", + "frequencies": "", "furigana": "む", "furigana-plain": "打[う]ち 込[こ]む", "glossary": "
(vt, Test Dictionary 2)
", @@ -1861,7 +1861,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打ち込む", - "frequencies": "", + "frequencies": "", "furigana": "む", "furigana-plain": "打[ぶ]ち 込[こ]む", "glossary": "
(vt, Test Dictionary 2)
", @@ -1951,7 +1951,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[う]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -1981,7 +1981,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[ぶ]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -2136,7 +2136,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打ち込む", - "frequencies": "", + "frequencies": "", "furigana": "む", "furigana-plain": "打[う]ち 込[こ]む", "glossary": "
(vt, Test Dictionary 2)
", @@ -2166,7 +2166,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打ち込む", - "frequencies": "", + "frequencies": "", "furigana": "む", "furigana-plain": "打[ぶ]ち 込[こ]む", "glossary": "
(vt, Test Dictionary 2)
", @@ -2256,7 +2256,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[う]つ", "glossary": "
(vt, Test Dictionary 2)
", @@ -2286,7 +2286,7 @@ "dictionary": "Test Dictionary 2", "document-title": "title", "expression": "打つ", - "frequencies": "", + "frequencies": "", "furigana": "つ", "furigana-plain": "打[ぶ]つ", "glossary": "
(vt, Test Dictionary 2)
", diff --git a/test/data/html/test-document2-script.js b/test/data/html/test-document2-script.js index 8a183019..5a6ad4d1 100644 --- a/test/data/html/test-document2-script.js +++ b/test/data/html/test-document2-script.js @@ -16,40 +16,65 @@ * along with this program. If not, see . */ +/** + * @param {Element} element + */ function requestFullscreen(element) { if (element.requestFullscreen) { element.requestFullscreen(); + // @ts-ignore - Browser compatibility } else if (element.mozRequestFullScreen) { + // @ts-ignore - Browser compatibility element.mozRequestFullScreen(); + // @ts-ignore - Browser compatibility } else if (element.webkitRequestFullscreen) { + // @ts-ignore - Browser compatibility element.webkitRequestFullscreen(); + // @ts-ignore - Browser compatibility } else if (element.msRequestFullscreen) { + // @ts-ignore - Browser compatibility element.msRequestFullscreen(); } } +/** */ function exitFullscreen() { if (document.exitFullscreen) { document.exitFullscreen(); + // @ts-ignore - Browser compatibility } else if (document.mozCancelFullScreen) { + // @ts-ignore - Browser compatibility document.mozCancelFullScreen(); + // @ts-ignore - Browser compatibility } else if (document.webkitExitFullscreen) { + // @ts-ignore - Browser compatibility document.webkitExitFullscreen(); + // @ts-ignore - Browser compatibility } else if (document.msExitFullscreen) { + // @ts-ignore - Browser compatibility document.msExitFullscreen(); } } +/** + * @returns {?Element} + */ function getFullscreenElement() { return ( document.fullscreenElement || + // @ts-ignore - Browser compatibility document.msFullscreenElement || + // @ts-ignore - Browser compatibility document.mozFullScreenElement || + // @ts-ignore - Browser compatibility document.webkitFullscreenElement || null ); } +/** + * @param {Element} element + */ function toggleFullscreen(element) { if (getFullscreenElement()) { exitFullscreen(); @@ -58,6 +83,10 @@ function toggleFullscreen(element) { } } +/** + * @param {HTMLElement|DocumentFragment} container + * @param {?Element} [fullscreenElement] + */ function setup(container, fullscreenElement=null) { const fullscreenLink = container.querySelector('.fullscreen-link'); if (fullscreenLink !== null) { @@ -65,6 +94,7 @@ function setup(container, fullscreenElement=null) { fullscreenElement = container.querySelector('.fullscreen-element'); } fullscreenLink.addEventListener('click', (e) => { + if (fullscreenElement === null) { return; } toggleFullscreen(fullscreenElement); e.preventDefault(); return false; @@ -74,11 +104,15 @@ function setup(container, fullscreenElement=null) { const template = container.querySelector('template'); const templateContentContainer = container.querySelector('.template-content-container'); if (template !== null && templateContentContainer !== null) { - const mode = container.dataset.shadowMode; - const shadow = templateContentContainer.attachShadow({mode}); + const mode = (container instanceof HTMLElement ? container.dataset.shadowMode : void 0); + const shadow = templateContentContainer.attachShadow({ + mode: (mode === 'open' || mode === 'closed' ? mode : 'open') + }); const containerStyles = document.querySelector('#container-styles'); - shadow.appendChild(containerStyles.cloneNode(true)); + if (containerStyles !== null) { + shadow.appendChild(containerStyles.cloneNode(true)); + } const content = document.importNode(template.content, true); setup(content); diff --git a/test/data/translator-test-results-note-data1.json b/test/data/translator-test-results-note-data1.json index d686563c..34f7c21a 100644 --- a/test/data/translator-test-results-note-data1.json +++ b/test/data/translator-test-results-note-data1.json @@ -1659,7 +1659,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -1672,7 +1672,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -1791,7 +1791,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -1804,7 +1804,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -1963,7 +1963,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -1976,7 +1976,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -1989,7 +1989,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -2095,7 +2095,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -2108,7 +2108,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -2121,7 +2121,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -3651,7 +3651,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -3677,7 +3677,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 0 + "frequency": "eighteen" }, { "index": 4, @@ -3690,7 +3690,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 24 + "frequency": "twenty-four (24)" }, { "index": 5, @@ -3813,7 +3813,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -3839,7 +3839,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 0 + "frequency": "eighteen" }, { "index": 4, @@ -3852,7 +3852,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 24 + "frequency": "twenty-four (24)" }, { "index": 5, @@ -4045,7 +4045,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -4071,7 +4071,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 0 + "frequency": "nineteen" }, { "index": 4, @@ -4084,7 +4084,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 25 + "frequency": "twenty-five (25)" }, { "index": 5, @@ -4097,7 +4097,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 31 + "frequency": "thirty-one" } ], "pitches": [ @@ -4207,7 +4207,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -4233,7 +4233,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 0 + "frequency": "nineteen" }, { "index": 4, @@ -4246,7 +4246,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 25 + "frequency": "twenty-five (25)" }, { "index": 5, @@ -4259,7 +4259,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 31 + "frequency": "thirty-one" } ], "pitches": [ @@ -5079,7 +5079,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -5092,7 +5092,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -5211,7 +5211,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -5224,7 +5224,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -5385,7 +5385,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -5398,7 +5398,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -5411,7 +5411,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -5517,7 +5517,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -5530,7 +5530,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -5543,7 +5543,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -7394,7 +7394,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -7407,7 +7407,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -7526,7 +7526,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -7539,7 +7539,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -8007,7 +8007,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -8020,7 +8020,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -8033,7 +8033,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -8139,7 +8139,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -8152,7 +8152,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -8165,7 +8165,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -8684,7 +8684,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -8710,7 +8710,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 0 + "frequency": "eighteen" }, { "index": 4, @@ -8723,7 +8723,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 24 + "frequency": "twenty-four (24)" }, { "index": 5, @@ -8846,7 +8846,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -8872,7 +8872,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 0 + "frequency": "eighteen" }, { "index": 4, @@ -8885,7 +8885,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 24 + "frequency": "twenty-four (24)" }, { "index": 5, @@ -9412,7 +9412,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -9425,7 +9425,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -9544,7 +9544,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -9557,7 +9557,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -10089,7 +10089,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -10115,7 +10115,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 0 + "frequency": "nineteen" }, { "index": 4, @@ -10128,7 +10128,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 25 + "frequency": "twenty-five (25)" }, { "index": 5, @@ -10141,7 +10141,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 31 + "frequency": "thirty-one" } ], "pitches": [ @@ -10251,7 +10251,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -10277,7 +10277,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 0 + "frequency": "nineteen" }, { "index": 4, @@ -10290,7 +10290,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 25 + "frequency": "twenty-five (25)" }, { "index": 5, @@ -10303,7 +10303,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 31 + "frequency": "thirty-one" } ], "pitches": [ @@ -10817,7 +10817,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -10830,7 +10830,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -10843,7 +10843,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -10949,7 +10949,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -10962,7 +10962,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -10975,7 +10975,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -16097,7 +16097,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -16123,7 +16123,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 0 + "frequency": "eighteen" }, { "index": 4, @@ -16136,7 +16136,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 24 + "frequency": "twenty-four (24)" }, { "index": 5, @@ -16259,7 +16259,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -16285,7 +16285,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 0 + "frequency": "eighteen" }, { "index": 4, @@ -16298,7 +16298,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 24 + "frequency": "twenty-four (24)" }, { "index": 5, @@ -16495,7 +16495,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -16521,7 +16521,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 0 + "frequency": "nineteen" }, { "index": 4, @@ -16534,7 +16534,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 25 + "frequency": "twenty-five (25)" }, { "index": 5, @@ -16547,7 +16547,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 31 + "frequency": "thirty-one" } ], "pitches": [ @@ -16657,7 +16657,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -16683,7 +16683,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 0 + "frequency": "nineteen" }, { "index": 4, @@ -16696,7 +16696,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 25 + "frequency": "twenty-five (25)" }, { "index": 5, @@ -16709,7 +16709,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 31 + "frequency": "thirty-one" } ], "pitches": [ @@ -17529,7 +17529,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -17542,7 +17542,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -17661,7 +17661,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -17674,7 +17674,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -17835,7 +17835,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -17848,7 +17848,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -17861,7 +17861,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -17967,7 +17967,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -17980,7 +17980,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -17993,7 +17993,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -19523,7 +19523,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -19549,7 +19549,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 0 + "frequency": "eighteen" }, { "index": 4, @@ -19562,7 +19562,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 24 + "frequency": "twenty-four (24)" }, { "index": 5, @@ -19685,7 +19685,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -19711,7 +19711,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 0 + "frequency": "eighteen" }, { "index": 4, @@ -19724,7 +19724,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 24 + "frequency": "twenty-four (24)" }, { "index": 5, @@ -19917,7 +19917,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -19943,7 +19943,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 0 + "frequency": "nineteen" }, { "index": 4, @@ -19956,7 +19956,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 25 + "frequency": "twenty-five (25)" }, { "index": 5, @@ -19969,7 +19969,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 31 + "frequency": "thirty-one" } ], "pitches": [ @@ -20079,7 +20079,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -20105,7 +20105,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 0 + "frequency": "nineteen" }, { "index": 4, @@ -20118,7 +20118,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 25 + "frequency": "twenty-five (25)" }, { "index": 5, @@ -20131,7 +20131,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 31 + "frequency": "thirty-one" } ], "pitches": [ @@ -20951,7 +20951,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -20964,7 +20964,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -21083,7 +21083,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -21096,7 +21096,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -21257,7 +21257,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -21270,7 +21270,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -21283,7 +21283,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -21389,7 +21389,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -21402,7 +21402,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -21415,7 +21415,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -22945,7 +22945,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -22971,7 +22971,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 0 + "frequency": "eighteen" }, { "index": 4, @@ -22984,7 +22984,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 24 + "frequency": "twenty-four (24)" }, { "index": 5, @@ -23107,7 +23107,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -23133,7 +23133,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 0 + "frequency": "eighteen" }, { "index": 4, @@ -23146,7 +23146,7 @@ "expression": "打ち込む", "reading": "うちこむ", "hasReading": true, - "frequency": 24 + "frequency": "twenty-four (24)" }, { "index": 5, @@ -23339,7 +23339,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -23365,7 +23365,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 0 + "frequency": "nineteen" }, { "index": 4, @@ -23378,7 +23378,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 25 + "frequency": "twenty-five (25)" }, { "index": 5, @@ -23391,7 +23391,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 31 + "frequency": "thirty-one" } ], "pitches": [ @@ -23501,7 +23501,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": false, - "frequency": 7 + "frequency": "seven" }, { "index": 2, @@ -23527,7 +23527,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 0 + "frequency": "nineteen" }, { "index": 4, @@ -23540,7 +23540,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 25 + "frequency": "twenty-five (25)" }, { "index": 5, @@ -23553,7 +23553,7 @@ "expression": "打ち込む", "reading": "ぶちこむ", "hasReading": true, - "frequency": 31 + "frequency": "thirty-one" } ], "pitches": [ @@ -24373,7 +24373,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -24386,7 +24386,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -24505,7 +24505,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 0 + "frequency": "sixteen" }, { "index": 4, @@ -24518,7 +24518,7 @@ "expression": "打つ", "reading": "うつ", "hasReading": true, - "frequency": 22 + "frequency": "twenty-two (22)" }, { "index": 5, @@ -24679,7 +24679,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -24692,7 +24692,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -24705,7 +24705,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], @@ -24811,7 +24811,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 0 + "frequency": "seventeen" }, { "index": 4, @@ -24824,7 +24824,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 23 + "frequency": "twenty-three (23)" }, { "index": 5, @@ -24837,7 +24837,7 @@ "expression": "打つ", "reading": "ぶつ", "hasReading": true, - "frequency": 29 + "frequency": "twenty-nine" } ], "pitches": [], diff --git a/test/data/translator-test-results.json b/test/data/translator-test-results.json index 98db0ef4..0a7155b8 100644 --- a/test/data/translator-test-results.json +++ b/test/data/translator-test-results.json @@ -1101,8 +1101,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "sixteen", + "displayValueParsed": true }, { "index": 4, @@ -1112,8 +1112,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 22, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-two (22)", + "displayValueParsed": true }, { "index": 5, @@ -1266,8 +1266,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "seventeen", + "displayValueParsed": true }, { "index": 4, @@ -1277,8 +1277,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 23, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-three (23)", + "displayValueParsed": true }, { "index": 5, @@ -1288,7 +1288,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 29, - "displayValue": null, + "displayValue": "twenty-nine", "displayValueParsed": false } ] @@ -2150,7 +2150,7 @@ "dictionaryPriority": 0, "hasReading": false, "frequency": 7, - "displayValue": null, + "displayValue": "seven", "displayValueParsed": false }, { @@ -2172,8 +2172,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "eighteen", + "displayValueParsed": true }, { "index": 4, @@ -2183,8 +2183,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 24, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-four (24)", + "displayValueParsed": true }, { "index": 5, @@ -2337,7 +2337,7 @@ "dictionaryPriority": 0, "hasReading": false, "frequency": 7, - "displayValue": null, + "displayValue": "seven", "displayValueParsed": false }, { @@ -2359,8 +2359,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "nineteen", + "displayValueParsed": true }, { "index": 4, @@ -2370,8 +2370,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 25, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-five (25)", + "displayValueParsed": true }, { "index": 5, @@ -2381,7 +2381,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 31, - "displayValue": null, + "displayValue": "thirty-one", "displayValueParsed": false } ] @@ -2860,8 +2860,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "sixteen", + "displayValueParsed": true }, { "index": 4, @@ -2871,8 +2871,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 22, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-two (22)", + "displayValueParsed": true }, { "index": 5, @@ -3027,8 +3027,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "seventeen", + "displayValueParsed": true }, { "index": 4, @@ -3038,8 +3038,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 23, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-three (23)", + "displayValueParsed": true }, { "index": 5, @@ -3049,7 +3049,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 29, - "displayValue": null, + "displayValue": "twenty-nine", "displayValueParsed": false } ] @@ -4166,8 +4166,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "sixteen", + "displayValueParsed": true }, { "index": 4, @@ -4177,8 +4177,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 22, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-two (22)", + "displayValueParsed": true }, { "index": 5, @@ -4502,8 +4502,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "seventeen", + "displayValueParsed": true }, { "index": 4, @@ -4513,8 +4513,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 23, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-three (23)", + "displayValueParsed": true }, { "index": 5, @@ -4524,7 +4524,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 29, - "displayValue": null, + "displayValue": "twenty-nine", "displayValueParsed": false } ] @@ -4860,7 +4860,7 @@ "dictionaryPriority": 0, "hasReading": false, "frequency": 7, - "displayValue": null, + "displayValue": "seven", "displayValueParsed": false }, { @@ -4882,8 +4882,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "eighteen", + "displayValueParsed": true }, { "index": 4, @@ -4893,8 +4893,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 24, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-four (24)", + "displayValueParsed": true }, { "index": 5, @@ -5216,8 +5216,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "sixteen", + "displayValueParsed": true }, { "index": 4, @@ -5227,8 +5227,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 22, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-two (22)", + "displayValueParsed": true }, { "index": 5, @@ -5574,7 +5574,7 @@ "dictionaryPriority": 0, "hasReading": false, "frequency": 7, - "displayValue": null, + "displayValue": "seven", "displayValueParsed": false }, { @@ -5596,8 +5596,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "nineteen", + "displayValueParsed": true }, { "index": 4, @@ -5607,8 +5607,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 25, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-five (25)", + "displayValueParsed": true }, { "index": 5, @@ -5618,7 +5618,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 31, - "displayValue": null, + "displayValue": "thirty-one", "displayValueParsed": false } ] @@ -5930,8 +5930,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "seventeen", + "displayValueParsed": true }, { "index": 4, @@ -5941,8 +5941,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 23, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-three (23)", + "displayValueParsed": true }, { "index": 5, @@ -5952,7 +5952,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 29, - "displayValue": null, + "displayValue": "twenty-nine", "displayValueParsed": false } ] @@ -9645,7 +9645,7 @@ "dictionaryPriority": 0, "hasReading": false, "frequency": 7, - "displayValue": null, + "displayValue": "seven", "displayValueParsed": false }, { @@ -9667,8 +9667,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "eighteen", + "displayValueParsed": true }, { "index": 4, @@ -9678,8 +9678,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 24, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-four (24)", + "displayValueParsed": true }, { "index": 5, @@ -9836,7 +9836,7 @@ "dictionaryPriority": 0, "hasReading": false, "frequency": 7, - "displayValue": null, + "displayValue": "seven", "displayValueParsed": false }, { @@ -9858,8 +9858,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "nineteen", + "displayValueParsed": true }, { "index": 4, @@ -9869,8 +9869,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 25, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-five (25)", + "displayValueParsed": true }, { "index": 5, @@ -9880,7 +9880,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 31, - "displayValue": null, + "displayValue": "thirty-one", "displayValueParsed": false } ] @@ -10359,8 +10359,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "sixteen", + "displayValueParsed": true }, { "index": 4, @@ -10370,8 +10370,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 22, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-two (22)", + "displayValueParsed": true }, { "index": 5, @@ -10526,8 +10526,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "seventeen", + "displayValueParsed": true }, { "index": 4, @@ -10537,8 +10537,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 23, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-three (23)", + "displayValueParsed": true }, { "index": 5, @@ -10548,7 +10548,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 29, - "displayValue": null, + "displayValue": "twenty-nine", "displayValueParsed": false } ] @@ -11410,7 +11410,7 @@ "dictionaryPriority": 0, "hasReading": false, "frequency": 7, - "displayValue": null, + "displayValue": "seven", "displayValueParsed": false }, { @@ -11432,8 +11432,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "eighteen", + "displayValueParsed": true }, { "index": 4, @@ -11443,8 +11443,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 24, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-four (24)", + "displayValueParsed": true }, { "index": 5, @@ -11597,7 +11597,7 @@ "dictionaryPriority": 0, "hasReading": false, "frequency": 7, - "displayValue": null, + "displayValue": "seven", "displayValueParsed": false }, { @@ -11619,8 +11619,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "nineteen", + "displayValueParsed": true }, { "index": 4, @@ -11630,8 +11630,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 25, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-five (25)", + "displayValueParsed": true }, { "index": 5, @@ -11641,7 +11641,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 31, - "displayValue": null, + "displayValue": "thirty-one", "displayValueParsed": false } ] @@ -12120,8 +12120,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "sixteen", + "displayValueParsed": true }, { "index": 4, @@ -12131,8 +12131,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 22, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-two (22)", + "displayValueParsed": true }, { "index": 5, @@ -12287,8 +12287,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "seventeen", + "displayValueParsed": true }, { "index": 4, @@ -12298,8 +12298,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 23, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-three (23)", + "displayValueParsed": true }, { "index": 5, @@ -12309,7 +12309,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 29, - "displayValue": null, + "displayValue": "twenty-nine", "displayValueParsed": false } ] @@ -13171,7 +13171,7 @@ "dictionaryPriority": 0, "hasReading": false, "frequency": 7, - "displayValue": null, + "displayValue": "seven", "displayValueParsed": false }, { @@ -13193,8 +13193,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "eighteen", + "displayValueParsed": true }, { "index": 4, @@ -13204,8 +13204,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 24, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-four (24)", + "displayValueParsed": true }, { "index": 5, @@ -13358,7 +13358,7 @@ "dictionaryPriority": 0, "hasReading": false, "frequency": 7, - "displayValue": null, + "displayValue": "seven", "displayValueParsed": false }, { @@ -13380,8 +13380,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "nineteen", + "displayValueParsed": true }, { "index": 4, @@ -13391,8 +13391,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 25, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-five (25)", + "displayValueParsed": true }, { "index": 5, @@ -13402,7 +13402,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 31, - "displayValue": null, + "displayValue": "thirty-one", "displayValueParsed": false } ] @@ -13881,8 +13881,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "sixteen", + "displayValueParsed": true }, { "index": 4, @@ -13892,8 +13892,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 22, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-two (22)", + "displayValueParsed": true }, { "index": 5, @@ -14048,8 +14048,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 0, - "displayValue": null, - "displayValueParsed": false + "displayValue": "seventeen", + "displayValueParsed": true }, { "index": 4, @@ -14059,8 +14059,8 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 23, - "displayValue": null, - "displayValueParsed": false + "displayValue": "twenty-three (23)", + "displayValueParsed": true }, { "index": 5, @@ -14070,7 +14070,7 @@ "dictionaryPriority": 0, "hasReading": true, "frequency": 29, - "displayValue": null, + "displayValue": "twenty-nine", "displayValueParsed": false } ] diff --git a/test/dictionary.test.js b/test/dictionary.test.js index 8f160bc1..e516bd8e 100644 --- a/test/dictionary.test.js +++ b/test/dictionary.test.js @@ -24,12 +24,18 @@ import {createDictionaryArchive} from '../dev/util.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); +/** + * @param {string} dictionary + * @param {string} [dictionaryName] + * @returns {import('jszip')} + */ function createTestDictionaryArchive(dictionary, dictionaryName) { const dictionaryDirectory = path.join(dirname, 'data', 'dictionaries', dictionary); return createDictionaryArchive(dictionaryDirectory, dictionaryName); } +/** */ async function main() { const dictionaries = [ {name: 'valid-dictionary1', valid: true}, diff --git a/test/jsconfig.json b/test/jsconfig.json new file mode 100644 index 00000000..b025918c --- /dev/null +++ b/test/jsconfig.json @@ -0,0 +1,39 @@ +{ + "compilerOptions": { + "module": "ES2015", + "target": "ES2022", + "checkJs": true, + "moduleResolution": "node", + "strict": true, + "strictNullChecks": true, + "noImplicitAny": true, + "strictPropertyInitialization": true, + "suppressImplicitAnyIndexErrors": false, + "skipLibCheck": false, + "baseUrl": ".", + "paths": { + "*": ["../types/ext/*"], + "dev/*": ["../types/dev/*"], + "test/*": ["../types/test/*"] + }, + "types": [ + "chrome", + "firefox-webext-browser", + "handlebars", + "jszip", + "parse5", + "wanakana" + ] + }, + "include": [ + "**/*.js", + "../ext/**/*.js", + "../types/ext/**/*.ts", + "../types/dev/**/*.ts", + "../types/other/web-set-timeout.d.ts" + ], + "exclude": [ + "../node_modules", + "../ext/lib" + ] +} \ No newline at end of file diff --git a/test/playwright/visual.spec.js b/test/playwright/visual.spec.js index 2f46990f..8b48b7c0 100644 --- a/test/playwright/visual.spec.js +++ b/test/playwright/visual.spec.js @@ -48,10 +48,16 @@ test('visual', async ({page, extensionId}) => { // take a screenshot of the settings page with jmdict loaded await expect.soft(page).toHaveScreenshot('settings-jmdict-loaded.png', {mask: [storage_locator]}); + /** + * @param {number} doc_number + * @param {number} test_number + * @param {import('@playwright/test').ElementHandle} el + * @param {{x: number, y: number}} offset + */ const screenshot = async (doc_number, test_number, el, offset) => { const test_name = 'doc' + doc_number + '-test' + test_number; - const box = await el.boundingBox(); + const box = (await el.boundingBox()) || {x: 0, y: 0, width: 0, height: 0}; // find the popup frame if it exists let popup_frame = page.frames().find((f) => f.url().includes('popup.html')); @@ -66,7 +72,7 @@ test('visual', async ({page, extensionId}) => { popup_frame = await frame_attached; // wait for popup to be attached } try { - await (await popup_frame.frameElement()).waitForElementState('visible', {timeout: 500}); // some tests don't have a popup, so don't fail if it's not there; TODO: check if the popup is expected to be there + await (await /** @type {import('@playwright/test').Frame} */ (popup_frame).frameElement()).waitForElementState('visible', {timeout: 500}); // some tests don't have a popup, so don't fail if it's not there; TODO: check if the popup is expected to be there } catch (error) { console.log(test_name + ' has no popup'); } @@ -75,7 +81,7 @@ test('visual', async ({page, extensionId}) => { await expect.soft(page).toHaveScreenshot(test_name + '.png'); await page.mouse.click(0, 0); // click away so popup disappears - await (await popup_frame.frameElement()).waitForElementState('hidden'); // wait for popup to disappear + await (await /** @type {import('@playwright/test').Frame} */ (popup_frame).frameElement()).waitForElementState('hidden'); // wait for popup to disappear }; // Load test-document1.html diff --git a/test/test-all.js b/test/test-all.js new file mode 100644 index 00000000..9219d278 --- /dev/null +++ b/test/test-all.js @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const fs = require('fs'); +const path = require('path'); +const {spawnSync} = require('child_process'); +const {getArgs} = require('../dev/util'); + + +/** + * @throws {Error} + */ +function main() { + const args = getArgs(process.argv.slice(2), new Map(/** @type {[key: string, value: (boolean|null|number|string|string[])][]} */ ([ + ['skip', []], + [null, []] + ]))); + const directories = /** @type {string[]} */ (args.get(null)); + const skipArg = /** @type {string[]} */ (args.get('skip')); + const skip = new Set([__filename, ...skipArg].map((value) => path.resolve(value))); + + const node = process.execPath; + const fileNamePattern = /\.js$/i; + + let first = true; + for (const directory of directories) { + const fileNames = fs.readdirSync(directory); + for (const fileName of fileNames) { + if (!fileNamePattern.test(fileName)) { continue; } + + const fullFileName = path.resolve(path.join(directory, fileName)); + if (skip.has(fullFileName)) { continue; } + + const stats = fs.lstatSync(fullFileName); + if (!stats.isFile()) { continue; } + + process.stdout.write(`${first ? '' : '\n'}Running ${fileName}...\n`); + first = false; + + const {error, status} = spawnSync(node, [fileName], {cwd: directory, stdio: 'inherit'}); + + if (status !== null && status !== 0) { + process.exit(status); + return; + } + if (error) { + throw error; + } + } + } + + process.exit(0); +} + + +if (require.main === module) { main(); } diff --git a/test/test-anki-note-builder.js b/test/test-anki-note-builder.js new file mode 100644 index 00000000..8e0ab9d9 --- /dev/null +++ b/test/test-anki-note-builder.js @@ -0,0 +1,322 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2021-2022 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 . + */ + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const {JSDOM} = require('jsdom'); +const {testMain} = require('../dev/util'); +const {TranslatorVM} = require('../dev/translator-vm'); + + +/** + * @template T + * @param {T} value + * @returns {T} + */ +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + +/** + * @returns {Promise<{vm: TranslatorVM, AnkiNoteBuilder: typeof AnkiNoteBuilder2, JapaneseUtil: typeof JapaneseUtil2}>} + */ +async function createVM() { + const dom = new JSDOM(); + const {Node, NodeFilter, document} = dom.window; + + const vm = new TranslatorVM({ + Node, + NodeFilter, + document, + location: new URL('https://yomichan.test/') + }); + + const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', 'valid-dictionary1'); + await vm.prepare(dictionaryDirectory, 'Test Dictionary 2'); + + vm.execute([ + 'js/data/anki-note-builder.js', + 'js/data/anki-util.js', + 'js/dom/sandbox/css-style-applier.js', + 'js/display/sandbox/pronunciation-generator.js', + 'js/display/sandbox/structured-content-generator.js', + 'js/templates/sandbox/anki-template-renderer.js', + 'js/templates/sandbox/anki-template-renderer-content-manager.js', + 'js/templates/sandbox/template-renderer.js', + 'js/templates/sandbox/template-renderer-media-provider.js', + 'lib/handlebars.min.js' + ]); + + /** @type {[typeof JapaneseUtil, typeof AnkiNoteBuilder, typeof AnkiTemplateRenderer]} */ + const [ + JapaneseUtil2, + AnkiNoteBuilder2, + AnkiTemplateRenderer2 + ] = vm.get([ + 'JapaneseUtil', + 'AnkiNoteBuilder', + 'AnkiTemplateRenderer' + ]); + + class TemplateRendererProxy { + constructor() { + /** @type {?Promise} */ + this._preparePromise = null; + /** @type {AnkiTemplateRenderer} */ + this._ankiTemplateRenderer = new AnkiTemplateRenderer2(); + } + + /** + * @param {string} template + * @param {import('template-renderer').PartialOrCompositeRenderData} data + * @param {import('anki-templates').RenderMode} type + * @returns {Promise} + */ + async render(template, data, type) { + await this._prepare(); + return await this._ankiTemplateRenderer.templateRenderer.render(template, data, type); + } + + /** + * @param {import('template-renderer').RenderMultiItem[]} items + * @returns {Promise[]>} + */ + async renderMulti(items) { + await this._prepare(); + return await this._ankiTemplateRenderer.templateRenderer.renderMulti(items); + } + + /** + * @returns {Promise} + */ + _prepare() { + if (this._preparePromise === null) { + this._preparePromise = this._prepareInternal(); + } + return this._preparePromise; + } + + /** */ + async _prepareInternal() { + await this._ankiTemplateRenderer.prepare(); + } + } + vm.set({TemplateRendererProxy}); + + return {vm, AnkiNoteBuilder: AnkiNoteBuilder2, JapaneseUtil: JapaneseUtil2}; +} + +/** + * @param {'terms'|'kanji'} type + * @returns {string[]} + */ +function getFieldMarkers(type) { + switch (type) { + case 'terms': + return [ + 'audio', + 'clipboard-image', + 'clipboard-text', + 'cloze-body', + 'cloze-prefix', + 'cloze-suffix', + 'conjugation', + 'dictionary', + 'document-title', + 'expression', + 'frequencies', + 'furigana', + 'furigana-plain', + 'glossary', + 'glossary-brief', + 'glossary-no-dictionary', + 'part-of-speech', + 'pitch-accents', + 'pitch-accent-graphs', + 'pitch-accent-positions', + 'reading', + 'screenshot', + 'search-query', + 'selection-text', + 'sentence', + 'sentence-furigana', + 'tags', + 'url' + ]; + case 'kanji': + return [ + 'character', + 'clipboard-image', + 'clipboard-text', + 'cloze-body', + 'cloze-prefix', + 'cloze-suffix', + 'dictionary', + 'document-title', + 'glossary', + 'kunyomi', + 'onyomi', + 'screenshot', + 'search-query', + 'selection-text', + 'sentence', + 'sentence-furigana', + 'stroke-count', + 'tags', + 'url' + ]; + default: + return []; + } +} + +/** + * @param {import('dictionary').DictionaryEntry[]} dictionaryEntries + * @param {'terms'|'kanji'} type + * @param {import('settings').ResultOutputMode} mode + * @param {string} template + * @param {typeof AnkiNoteBuilder} AnkiNoteBuilder + * @param {typeof JapaneseUtil} JapaneseUtil + * @param {boolean} write + * @returns {Promise} + */ +async function getRenderResults(dictionaryEntries, type, mode, template, AnkiNoteBuilder, JapaneseUtil, write) { + const markers = getFieldMarkers(type); + /** @type {import('anki-note-builder').Field[]} */ + const fields = []; + for (const marker of markers) { + fields.push([marker, `{${marker}}`]); + } + + const japaneseUtil = new JapaneseUtil(null); + const clozePrefix = 'cloze-prefix'; + const clozeSuffix = 'cloze-suffix'; + const results = []; + for (const dictionaryEntry of dictionaryEntries) { + let source = ''; + switch (dictionaryEntry.type) { + case 'kanji': + source = dictionaryEntry.character; + break; + case 'term': + if (dictionaryEntry.headwords.length > 0 && dictionaryEntry.headwords[0].sources.length > 0) { + source = dictionaryEntry.headwords[0].sources[0].originalText; + } + break; + } + const ankiNoteBuilder = new AnkiNoteBuilder({japaneseUtil}); + const context = { + url: 'url:', + sentence: { + text: `${clozePrefix}${source}${clozeSuffix}`, + offset: clozePrefix.length + }, + documentTitle: 'title', + query: 'query', + fullQuery: 'fullQuery' + }; + /** @type {import('anki-note-builder').CreateNoteDetails} */ + const details = { + dictionaryEntry, + mode: 'test', + context, + template, + deckName: 'deckName', + modelName: 'modelName', + fields, + tags: ['yomichan'], + checkForDuplicates: true, + duplicateScope: 'collection', + duplicateScopeCheckAllModels: false, + resultOutputMode: mode, + glossaryLayoutMode: 'default', + compactTags: false, + requirements: [], + mediaOptions: null + }; + const {note: {fields: noteFields}, errors} = await ankiNoteBuilder.createNote(details); + if (!write) { + for (const error of errors) { + console.error(error); + } + assert.strictEqual(errors.length, 0); + } + results.push(noteFields); + } + + return results; +} + + +/** */ +async function main() { + const write = (process.argv[2] === '--write'); + + const {vm, AnkiNoteBuilder, JapaneseUtil} = await createVM(); + + const testInputsFilePath = path.join(__dirname, 'data', 'translator-test-inputs.json'); + const {optionsPresets, tests} = JSON.parse(fs.readFileSync(testInputsFilePath, {encoding: 'utf8'})); + + const testResults1FilePath = path.join(__dirname, 'data', 'anki-note-builder-test-results.json'); + const expectedResults1 = JSON.parse(fs.readFileSync(testResults1FilePath, {encoding: 'utf8'})); + const actualResults1 = []; + + const template = fs.readFileSync(path.join(__dirname, '..', 'ext', 'data/templates/default-anki-field-templates.handlebars'), {encoding: 'utf8'}); + + for (let i = 0, ii = tests.length; i < ii; ++i) { + const test = tests[i]; + const expected1 = expectedResults1[i]; + switch (test.func) { + case 'findTerms': + { + const {name, mode, text} = test; + /** @type {import('translation').FindTermsOptions} */ + const options = vm.buildOptions(optionsPresets, test.options); + const {dictionaryEntries} = clone(await vm.translator.findTerms(mode, text, options)); + const results = mode !== 'simple' ? clone(await getRenderResults(dictionaryEntries, 'terms', mode, template, AnkiNoteBuilder, JapaneseUtil, write)) : null; + actualResults1.push({name, results}); + if (!write) { + assert.deepStrictEqual(results, expected1.results); + } + } + break; + case 'findKanji': + { + const {name, text} = test; + /** @type {import('translation').FindKanjiOptions} */ + const options = vm.buildOptions(optionsPresets, test.options); + const dictionaryEntries = clone(await vm.translator.findKanji(text, options)); + const results = clone(await getRenderResults(dictionaryEntries, 'kanji', 'split', template, AnkiNoteBuilder, JapaneseUtil, write)); + actualResults1.push({name, results}); + if (!write) { + assert.deepStrictEqual(results, expected1.results); + } + } + break; + } + } + + if (write) { + // Use 2 indent instead of 4 to save a bit of file size + fs.writeFileSync(testResults1FilePath, JSON.stringify(actualResults1, null, 2), {encoding: 'utf8'}); + } +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-cache-map.js b/test/test-cache-map.js new file mode 100644 index 00000000..56a31898 --- /dev/null +++ b/test/test-cache-map.js @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const assert = require('assert'); +const {testMain} = require('../dev/util'); +const {VM} = require('../dev/vm'); + +const vm = new VM({console}); +vm.execute([ + 'js/general/cache-map.js' +]); +/** @type {typeof CacheMap} */ +const CacheMap2 = vm.getSingle('CacheMap'); + + +/** */ +function testConstructor() { + const data = /** @type {[throws: boolean, create: () => void][]} */ ([ + [false, () => new CacheMap2(0)], + [false, () => new CacheMap2(1)], + [false, () => new CacheMap2(Number.MAX_VALUE)], + [true, () => new CacheMap2(-1)], + [true, () => new CacheMap2(1.5)], + [true, () => new CacheMap2(Number.NaN)], + [true, () => new CacheMap2(Number.POSITIVE_INFINITY)], + // @ts-ignore - Ignore because it should throw an error + [true, () => new CacheMap2('a')] + ]); + + for (const [throws, create] of data) { + if (throws) { + assert.throws(create); + } else { + assert.doesNotThrow(create); + } + } +} + +/** */ +function testApi() { + const data = [ + { + maxSize: 1, + expectedSize: 0, + calls: [] + }, + { + maxSize: 10, + expectedSize: 1, + calls: [ + {func: 'get', args: ['a1-b-c'], returnValue: void 0}, + {func: 'has', args: ['a1-b-c'], returnValue: false}, + {func: 'set', args: ['a1-b-c', 32], returnValue: void 0}, + {func: 'get', args: ['a1-b-c'], returnValue: 32}, + {func: 'has', args: ['a1-b-c'], returnValue: true} + ] + }, + { + maxSize: 10, + expectedSize: 2, + calls: [ + {func: 'set', args: ['a1-b-c', 32], returnValue: void 0}, + {func: 'get', args: ['a1-b-c'], returnValue: 32}, + {func: 'set', args: ['a1-b-c', 64], returnValue: void 0}, + {func: 'get', args: ['a1-b-c'], returnValue: 64}, + {func: 'set', args: ['a2-b-c', 96], returnValue: void 0}, + {func: 'get', args: ['a2-b-c'], returnValue: 96} + ] + }, + { + maxSize: 2, + expectedSize: 2, + calls: [ + {func: 'has', args: ['a1-b-c'], returnValue: false}, + {func: 'has', args: ['a2-b-c'], returnValue: false}, + {func: 'has', args: ['a3-b-c'], returnValue: false}, + {func: 'set', args: ['a1-b-c', 1], returnValue: void 0}, + {func: 'has', args: ['a1-b-c'], returnValue: true}, + {func: 'has', args: ['a2-b-c'], returnValue: false}, + {func: 'has', args: ['a3-b-c'], returnValue: false}, + {func: 'set', args: ['a2-b-c', 2], returnValue: void 0}, + {func: 'has', args: ['a1-b-c'], returnValue: true}, + {func: 'has', args: ['a2-b-c'], returnValue: true}, + {func: 'has', args: ['a3-b-c'], returnValue: false}, + {func: 'set', args: ['a3-b-c', 3], returnValue: void 0}, + {func: 'has', args: ['a1-b-c'], returnValue: false}, + {func: 'has', args: ['a2-b-c'], returnValue: true}, + {func: 'has', args: ['a3-b-c'], returnValue: true} + ] + } + ]; + + for (const {maxSize, expectedSize, calls} of data) { + const cache = new CacheMap2(maxSize); + assert.strictEqual(cache.maxSize, maxSize); + for (const call of calls) { + const {func, args} = call; + let returnValue; + switch (func) { + case 'get': returnValue = cache.get(args[0]); break; + case 'set': returnValue = cache.set(args[0], args[1]); break; + case 'has': returnValue = cache.has(args[0]); break; + case 'clear': returnValue = cache.clear(); break; + } + if (Object.prototype.hasOwnProperty.call(call, 'returnValue')) { + const {returnValue: expectedReturnValue} = call; + assert.deepStrictEqual(returnValue, expectedReturnValue); + } + } + assert.strictEqual(cache.size, expectedSize); + } +} + + +/** */ +function main() { + testConstructor(); + testApi(); +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-core.js b/test/test-core.js new file mode 100644 index 00000000..2c48ac20 --- /dev/null +++ b/test/test-core.js @@ -0,0 +1,300 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const assert = require('assert'); +const {testMain} = require('../dev/util'); +const {VM} = require('../dev/vm'); + +const vm = new VM(); +vm.execute([ + 'js/core.js', + 'js/core/extension-error.js' +]); +const values = vm.get(['DynamicProperty', 'deepEqual']); +const DynamicProperty2 = /** @type {typeof DynamicProperty} */ (values[0]); +const deepEqual2 = /** @type {typeof deepEqual} */ (values[1]); + + +/** */ +function testDynamicProperty() { + const data = [ + { + initialValue: 0, + /** @type {{operation: ?string, expectedDefaultValue: number, expectedValue: number, expectedOverrideCount: number, expeectedEventOccurred: boolean, args: [value: number, priority?: number]}[]} */ + operations: [ + { + operation: null, + args: [0], + expectedDefaultValue: 0, + expectedValue: 0, + expectedOverrideCount: 0, + expeectedEventOccurred: false + }, + { + operation: 'set.defaultValue', + args: [1], + expectedDefaultValue: 1, + expectedValue: 1, + expectedOverrideCount: 0, + expeectedEventOccurred: true + }, + { + operation: 'set.defaultValue', + args: [1], + expectedDefaultValue: 1, + expectedValue: 1, + expectedOverrideCount: 0, + expeectedEventOccurred: false + }, + { + operation: 'set.defaultValue', + args: [0], + expectedDefaultValue: 0, + expectedValue: 0, + expectedOverrideCount: 0, + expeectedEventOccurred: true + }, + { + operation: 'setOverride', + args: [8], + expectedDefaultValue: 0, + expectedValue: 8, + expectedOverrideCount: 1, + expeectedEventOccurred: true + }, + { + operation: 'setOverride', + args: [16], + expectedDefaultValue: 0, + expectedValue: 8, + expectedOverrideCount: 2, + expeectedEventOccurred: false + }, + { + operation: 'setOverride', + args: [32, 1], + expectedDefaultValue: 0, + expectedValue: 32, + expectedOverrideCount: 3, + expeectedEventOccurred: true + }, + { + operation: 'setOverride', + args: [64, -1], + expectedDefaultValue: 0, + expectedValue: 32, + expectedOverrideCount: 4, + expeectedEventOccurred: false + }, + { + operation: 'clearOverride', + args: [-4], + expectedDefaultValue: 0, + expectedValue: 32, + expectedOverrideCount: 3, + expeectedEventOccurred: false + }, + { + operation: 'clearOverride', + args: [-3], + expectedDefaultValue: 0, + expectedValue: 32, + expectedOverrideCount: 2, + expeectedEventOccurred: false + }, + { + operation: 'clearOverride', + args: [-2], + expectedDefaultValue: 0, + expectedValue: 64, + expectedOverrideCount: 1, + expeectedEventOccurred: true + }, + { + operation: 'clearOverride', + args: [-1], + expectedDefaultValue: 0, + expectedValue: 0, + expectedOverrideCount: 0, + expeectedEventOccurred: true + } + ] + } + ]; + + for (const {initialValue, operations} of data) { + const property = new DynamicProperty2(initialValue); + const overrideTokens = []; + let eventOccurred = false; + const onChange = () => { eventOccurred = true; }; + property.on('change', onChange); + for (const {operation, args, expectedDefaultValue, expectedValue, expectedOverrideCount, expeectedEventOccurred} of operations) { + eventOccurred = false; + switch (operation) { + case 'set.defaultValue': property.defaultValue = args[0]; break; + case 'setOverride': overrideTokens.push(property.setOverride(...args)); break; + case 'clearOverride': property.clearOverride(overrideTokens[overrideTokens.length + args[0]]); break; + } + assert.strictEqual(eventOccurred, expeectedEventOccurred); + assert.strictEqual(property.defaultValue, expectedDefaultValue); + assert.strictEqual(property.value, expectedValue); + assert.strictEqual(property.overrideCount, expectedOverrideCount); + } + property.off('change', onChange); + } +} + +/** */ +function testDeepEqual() { + const data = [ + // Simple tests + { + value1: 0, + value2: 0, + expected: true + }, + { + value1: null, + value2: null, + expected: true + }, + { + value1: 'test', + value2: 'test', + expected: true + }, + { + value1: true, + value2: true, + expected: true + }, + { + value1: 0, + value2: 1, + expected: false + }, + { + value1: null, + value2: false, + expected: false + }, + { + value1: 'test1', + value2: 'test2', + expected: false + }, + { + value1: true, + value2: false, + expected: false + }, + + // Simple object tests + { + value1: {}, + value2: {}, + expected: true + }, + { + value1: {}, + value2: [], + expected: false + }, + { + value1: [], + value2: [], + expected: true + }, + { + value1: {}, + value2: null, + expected: false + }, + + // Complex object tests + { + value1: [1], + value2: [], + expected: false + }, + { + value1: [1], + value2: [1], + expected: true + }, + { + value1: [1], + value2: [2], + expected: false + }, + + { + value1: {}, + value2: {test: 1}, + expected: false + }, + { + value1: {test: 1}, + value2: {test: 1}, + expected: true + }, + { + value1: {test: 1}, + value2: {test: {test2: false}}, + expected: false + }, + { + value1: {test: {test2: true}}, + value2: {test: {test2: false}}, + expected: false + }, + { + value1: {test: {test2: [true]}}, + value2: {test: {test2: [true]}}, + expected: true + }, + + // Recursive + { + value1: (() => { const x = {}; x.x = x; return x; })(), + value2: (() => { const x = {}; x.x = x; return x; })(), + expected: false + } + ]; + + let index = 0; + for (const {value1, value2, expected} of data) { + const actual1 = deepEqual2(value1, value2); + assert.strictEqual(actual1, expected, `Failed for test ${index}`); + + const actual2 = deepEqual2(value2, value1); + assert.strictEqual(actual2, expected, `Failed for test ${index}`); + + ++index; + } +} + + +/** */ +function main() { + testDynamicProperty(); + testDeepEqual(); +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-database.js b/test/test-database.js new file mode 100644 index 00000000..947e369b --- /dev/null +++ b/test/test-database.js @@ -0,0 +1,982 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const path = require('path'); +const assert = require('assert'); +const {createDictionaryArchive, testMain} = require('../dev/util'); +const {DatabaseVM, DatabaseVMDictionaryImporterMediaLoader} = require('../dev/database-vm'); + + +const vm = new DatabaseVM(); +vm.execute([ + 'js/core.js', + 'js/core/extension-error.js', + 'js/general/cache-map.js', + 'js/data/json-schema.js', + 'js/media/media-util.js', + 'js/language/dictionary-importer.js', + 'js/data/database.js', + 'js/language/dictionary-database.js' +]); +/** @type {typeof DictionaryImporter} */ +const DictionaryImporter2 = vm.getSingle('DictionaryImporter'); +/** @type {typeof DictionaryDatabase} */ +const DictionaryDatabase2 = vm.getSingle('DictionaryDatabase'); + + +/** + * @param {string} dictionary + * @param {string} [dictionaryName] + * @returns {import('jszip')} + */ +function createTestDictionaryArchive(dictionary, dictionaryName) { + const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', dictionary); + return createDictionaryArchive(dictionaryDirectory, dictionaryName); +} + + +/** + * @param {import('dictionary-importer').OnProgressCallback} [onProgress] + * @returns {DictionaryImporter} + */ +function createDictionaryImporter(onProgress) { + const dictionaryImporterMediaLoader = new DatabaseVMDictionaryImporterMediaLoader(); + return new DictionaryImporter2(dictionaryImporterMediaLoader, (...args) => { + const {stepIndex, stepCount, index, count} = args[0]; + assert.ok(stepIndex < stepCount); + assert.ok(index <= count); + if (typeof onProgress === 'function') { + onProgress(...args); + } + }); +} + + +/** + * @param {import('dictionary-database').TermEntry[]} dictionaryDatabaseEntries + * @param {string} term + * @returns {number} + */ +function countDictionaryDatabaseEntriesWithTerm(dictionaryDatabaseEntries, term) { + return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.term === term ? 1 : 0)), 0); +} + +/** + * @param {import('dictionary-database').TermEntry[]} dictionaryDatabaseEntries + * @param {string} reading + * @returns {number} + */ +function countDictionaryDatabaseEntriesWithReading(dictionaryDatabaseEntries, reading) { + return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.reading === reading ? 1 : 0)), 0); +} + +/** + * @param {import('dictionary-database').TermMeta[]|import('dictionary-database').KanjiMeta[]} metas + * @param {import('dictionary-database').TermMetaType|import('dictionary-database').KanjiMetaType} mode + * @returns {number} + */ +function countMetasWithMode(metas, mode) { + let i = 0; + for (const item of metas) { + if (item.mode === mode) { ++i; } + } + return i; +} + +/** + * @param {import('dictionary-database').KanjiEntry[]} kanji + * @param {string} character + * @returns {number} + */ +function countKanjiWithCharacter(kanji, character) { + let i = 0; + for (const item of kanji) { + if (item.character === character) { ++i; } + } + return i; +} + + +/** + * @param {number} timeout + * @returns {Promise} + */ +function clearDatabase(timeout) { + return new Promise((resolve, reject) => { + /** @type {?number} */ + let timer = setTimeout(() => { + timer = null; + reject(new Error(`clearDatabase failed to resolve after ${timeout}ms`)); + }, timeout); + + (async () => { + const indexedDB = vm.indexedDB; + for (const {name} of await indexedDB.databases()) { + if (typeof name !== 'string') { continue; } + /** @type {Promise} */ + const promise2 = new Promise((resolve2, reject2) => { + const request = indexedDB.deleteDatabase(name); + request.onerror = (e) => reject2(e); + request.onsuccess = () => resolve2(); + }); + await promise2; + } + if (timer !== null) { + clearTimeout(timer); + } + resolve(); + })(); + }); +} + + +/** */ +async function testDatabase1() { + // Load dictionary data + const testDictionary = createTestDictionaryArchive('valid-dictionary1'); + const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); + const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); + + const title = testDictionaryIndex.title; + const titles = new Map([ + [title, {priority: 0, allowSecondarySearches: false}] + ]); + + // Setup iteration data + const iterations = [ + { + cleanup: async () => { + // Test purge + await dictionaryDatabase.purge(); + await testDatabaseEmpty1(dictionaryDatabase); + } + }, + { + cleanup: async () => { + // Test deleteDictionary + let progressEvent = false; + await dictionaryDatabase.deleteDictionary( + title, + 1000, + () => { + progressEvent = true; + } + ); + assert.ok(progressEvent); + + await testDatabaseEmpty1(dictionaryDatabase); + } + }, + { + cleanup: async () => {} + } + ]; + + // Setup database + const dictionaryDatabase = new DictionaryDatabase2(); + await dictionaryDatabase.prepare(); + + for (const {cleanup} of iterations) { + const expectedSummary = { + title, + revision: 'test', + sequenced: true, + version: 3, + importDate: 0, + prefixWildcardsSupported: true, + counts: { + kanji: {total: 2}, + kanjiMeta: {total: 6, freq: 6}, + media: {total: 4}, + tagMeta: {total: 15}, + termMeta: {total: 38, freq: 31, pitch: 7}, + terms: {total: 21} + } + }; + + // Import data + let progressEvent = false; + const dictionaryImporter = createDictionaryImporter(() => { progressEvent = true; }); + const {result, errors} = await dictionaryImporter.importDictionary( + dictionaryDatabase, + testDictionarySource, + {prefixWildcardsSupported: true} + ); + expectedSummary.importDate = result.importDate; + vm.assert.deepStrictEqual(errors, []); + vm.assert.deepStrictEqual(result, expectedSummary); + assert.ok(progressEvent); + + // Get info summary + const info = await dictionaryDatabase.getDictionaryInfo(); + vm.assert.deepStrictEqual(info, [expectedSummary]); + + // Get counts + const counts = await dictionaryDatabase.getDictionaryCounts( + info.map((v) => v.title), + true + ); + vm.assert.deepStrictEqual(counts, { + counts: [{kanji: 2, kanjiMeta: 6, terms: 21, termMeta: 38, tagMeta: 15, media: 4}], + total: {kanji: 2, kanjiMeta: 6, terms: 21, termMeta: 38, tagMeta: 15, media: 4} + }); + + // Test find* functions + await testFindTermsBulkTest1(dictionaryDatabase, titles); + await testTindTermsExactBulk1(dictionaryDatabase, titles); + await testFindTermsBySequenceBulk1(dictionaryDatabase, title); + await testFindTermMetaBulk1(dictionaryDatabase, titles); + await testFindKanjiBulk1(dictionaryDatabase, titles); + await testFindKanjiMetaBulk1(dictionaryDatabase, titles); + await testFindTagForTitle1(dictionaryDatabase, title); + + // Cleanup + await cleanup(); + } + + await dictionaryDatabase.close(); +} + +/** + * @param {DictionaryDatabase} database + */ +async function testDatabaseEmpty1(database) { + const info = await database.getDictionaryInfo(); + vm.assert.deepStrictEqual(info, []); + + const counts = await database.getDictionaryCounts([], true); + vm.assert.deepStrictEqual(counts, { + counts: [], + total: {kanji: 0, kanjiMeta: 0, terms: 0, termMeta: 0, tagMeta: 0, media: 0} + }); +} + +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */ +async function testFindTermsBulkTest1(database, titles) { + /** @type {{inputs: {matchType: import('dictionary-database').MatchType, termList: string[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ + const data = [ + { + inputs: [ + { + matchType: 'exact', + termList: ['打', '打つ', '打ち込む'] + }, + { + matchType: 'exact', + termList: ['だ', 'ダース', 'うつ', 'ぶつ', 'うちこむ', 'ぶちこむ'] + }, + { + matchType: 'prefix', + termList: ['打'] + } + ], + expectedResults: { + total: 10, + terms: [ + ['打', 2], + ['打つ', 4], + ['打ち込む', 4] + ], + readings: [ + ['だ', 1], + ['ダース', 1], + ['うつ', 2], + ['ぶつ', 2], + ['うちこむ', 2], + ['ぶちこむ', 2] + ] + } + }, + { + inputs: [ + { + matchType: 'exact', + termList: ['込む'] + } + ], + expectedResults: { + total: 0, + terms: [], + readings: [] + } + }, + { + inputs: [ + { + matchType: 'suffix', + termList: ['込む'] + } + ], + expectedResults: { + total: 4, + terms: [ + ['打ち込む', 4] + ], + readings: [ + ['うちこむ', 2], + ['ぶちこむ', 2] + ] + } + }, + { + inputs: [ + { + matchType: 'exact', + termList: [] + } + ], + expectedResults: { + total: 0, + terms: [], + readings: [] + } + } + ]; + + for (const {inputs, expectedResults} of data) { + for (const {termList, matchType} of inputs) { + const results = await database.findTermsBulk(termList, titles, matchType); + assert.strictEqual(results.length, expectedResults.total); + for (const [term, count] of expectedResults.terms) { + assert.strictEqual(countDictionaryDatabaseEntriesWithTerm(results, term), count); + } + for (const [reading, count] of expectedResults.readings) { + assert.strictEqual(countDictionaryDatabaseEntriesWithReading(results, reading), count); + } + } + } +} + +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */ +async function testTindTermsExactBulk1(database, titles) { + /** @type {{inputs: {termList: {term: string, reading: string}[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ + const data = [ + { + inputs: [ + { + termList: [ + {term: '打', reading: 'だ'}, + {term: '打つ', reading: 'うつ'}, + {term: '打ち込む', reading: 'うちこむ'} + ] + } + ], + expectedResults: { + total: 5, + terms: [ + ['打', 1], + ['打つ', 2], + ['打ち込む', 2] + ], + readings: [ + ['だ', 1], + ['うつ', 2], + ['うちこむ', 2] + ] + } + }, + { + inputs: [ + { + termList: [ + {term: '打', reading: 'だ?'}, + {term: '打つ', reading: 'うつ?'}, + {term: '打ち込む', reading: 'うちこむ?'} + ] + } + ], + expectedResults: { + total: 0, + terms: [], + readings: [] + } + }, + { + inputs: [ + { + termList: [ + {term: '打つ', reading: 'うつ'}, + {term: '打つ', reading: 'ぶつ'} + ] + } + ], + expectedResults: { + total: 4, + terms: [ + ['打つ', 4] + ], + readings: [ + ['うつ', 2], + ['ぶつ', 2] + ] + } + }, + { + inputs: [ + { + termList: [ + {term: '打つ', reading: 'うちこむ'} + ] + } + ], + expectedResults: { + total: 0, + terms: [], + readings: [] + } + }, + { + inputs: [ + { + termList: [] + } + ], + expectedResults: { + total: 0, + terms: [], + readings: [] + } + } + ]; + + for (const {inputs, expectedResults} of data) { + for (const {termList} of inputs) { + const results = await database.findTermsExactBulk(termList, titles); + assert.strictEqual(results.length, expectedResults.total); + for (const [term, count] of expectedResults.terms) { + assert.strictEqual(countDictionaryDatabaseEntriesWithTerm(results, term), count); + } + for (const [reading, count] of expectedResults.readings) { + assert.strictEqual(countDictionaryDatabaseEntriesWithReading(results, reading), count); + } + } + } +} + +/** + * @param {DictionaryDatabase} database + * @param {string} mainDictionary + */ +async function testFindTermsBySequenceBulk1(database, mainDictionary) { + /** @type {{inputs: {sequenceList: number[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ + const data = [ + { + inputs: [ + { + sequenceList: [1, 2, 3, 4, 5] + } + ], + expectedResults: { + total: 11, + terms: [ + ['打', 2], + ['打つ', 4], + ['打ち込む', 4], + ['画像', 1] + ], + readings: [ + ['だ', 1], + ['ダース', 1], + ['うつ', 2], + ['ぶつ', 2], + ['うちこむ', 2], + ['ぶちこむ', 2], + ['がぞう', 1] + ] + } + }, + { + inputs: [ + { + sequenceList: [1] + } + ], + expectedResults: { + total: 1, + terms: [ + ['打', 1] + ], + readings: [ + ['だ', 1] + ] + } + }, + { + inputs: [ + { + sequenceList: [2] + } + ], + expectedResults: { + total: 1, + terms: [ + ['打', 1] + ], + readings: [ + ['ダース', 1] + ] + } + }, + { + inputs: [ + { + sequenceList: [3] + } + ], + expectedResults: { + total: 4, + terms: [ + ['打つ', 4] + ], + readings: [ + ['うつ', 2], + ['ぶつ', 2] + ] + } + }, + { + inputs: [ + { + sequenceList: [4] + } + ], + expectedResults: { + total: 4, + terms: [ + ['打ち込む', 4] + ], + readings: [ + ['うちこむ', 2], + ['ぶちこむ', 2] + ] + } + }, + { + inputs: [ + { + sequenceList: [5] + } + ], + expectedResults: { + total: 1, + terms: [ + ['画像', 1] + ], + readings: [ + ['がぞう', 1] + ] + } + }, + { + inputs: [ + { + sequenceList: [-1] + } + ], + expectedResults: { + total: 0, + terms: [], + readings: [] + } + }, + { + inputs: [ + { + sequenceList: [] + } + ], + expectedResults: { + total: 0, + terms: [], + readings: [] + } + } + ]; + + for (const {inputs, expectedResults} of data) { + for (const {sequenceList} of inputs) { + const results = await database.findTermsBySequenceBulk(sequenceList.map((query) => ({query, dictionary: mainDictionary}))); + assert.strictEqual(results.length, expectedResults.total); + for (const [term, count] of expectedResults.terms) { + assert.strictEqual(countDictionaryDatabaseEntriesWithTerm(results, term), count); + } + for (const [reading, count] of expectedResults.readings) { + assert.strictEqual(countDictionaryDatabaseEntriesWithReading(results, reading), count); + } + } + } +} + +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */ +async function testFindTermMetaBulk1(database, titles) { + /** @type {{inputs: {termList: string[]}[], expectedResults: {total: number, modes: [key: import('dictionary-database').TermMetaType, count: number][]}}[]} */ + const data = [ + { + inputs: [ + { + termList: ['打'] + } + ], + expectedResults: { + total: 11, + modes: [ + ['freq', 11] + ] + } + }, + { + inputs: [ + { + termList: ['打つ'] + } + ], + expectedResults: { + total: 10, + modes: [ + ['freq', 10] + ] + } + }, + { + inputs: [ + { + termList: ['打ち込む'] + } + ], + expectedResults: { + total: 12, + modes: [ + ['freq', 10], + ['pitch', 2] + ] + } + }, + { + inputs: [ + { + termList: ['?'] + } + ], + expectedResults: { + total: 0, + modes: [] + } + } + ]; + + for (const {inputs, expectedResults} of data) { + for (const {termList} of inputs) { + const results = await database.findTermMetaBulk(termList, titles); + assert.strictEqual(results.length, expectedResults.total); + for (const [mode, count] of expectedResults.modes) { + assert.strictEqual(countMetasWithMode(results, mode), count); + } + } + } +} + +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */ +async function testFindKanjiBulk1(database, titles) { + /** @type {{inputs: {kanjiList: string[]}[], expectedResults: {total: number, kanji: [key: string, count: number][]}}[]} */ + const data = [ + { + inputs: [ + { + kanjiList: ['打'] + } + ], + expectedResults: { + total: 1, + kanji: [ + ['打', 1] + ] + } + }, + { + inputs: [ + { + kanjiList: ['込'] + } + ], + expectedResults: { + total: 1, + kanji: [ + ['込', 1] + ] + } + }, + { + inputs: [ + { + kanjiList: ['?'] + } + ], + expectedResults: { + total: 0, + kanji: [] + } + } + ]; + + for (const {inputs, expectedResults} of data) { + for (const {kanjiList} of inputs) { + const results = await database.findKanjiBulk(kanjiList, titles); + assert.strictEqual(results.length, expectedResults.total); + for (const [kanji, count] of expectedResults.kanji) { + assert.strictEqual(countKanjiWithCharacter(results, kanji), count); + } + } + } +} + +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */ +async function testFindKanjiMetaBulk1(database, titles) { + /** @type {{inputs: {kanjiList: string[]}[], expectedResults: {total: number, modes: [key: import('dictionary-database').KanjiMetaType, count: number][]}}[]} */ + const data = [ + { + inputs: [ + { + kanjiList: ['打'] + } + ], + expectedResults: { + total: 3, + modes: [ + ['freq', 3] + ] + } + }, + { + inputs: [ + { + kanjiList: ['込'] + } + ], + expectedResults: { + total: 3, + modes: [ + ['freq', 3] + ] + } + }, + { + inputs: [ + { + kanjiList: ['?'] + } + ], + expectedResults: { + total: 0, + modes: [] + } + } + ]; + + for (const {inputs, expectedResults} of data) { + for (const {kanjiList} of inputs) { + const results = await database.findKanjiMetaBulk(kanjiList, titles); + assert.strictEqual(results.length, expectedResults.total); + for (const [mode, count] of expectedResults.modes) { + assert.strictEqual(countMetasWithMode(results, mode), count); + } + } + } +} + +/** + * @param {DictionaryDatabase} database + * @param {string} title + */ +async function testFindTagForTitle1(database, title) { + const data = [ + { + inputs: [ + { + name: 'E1' + } + ], + expectedResults: { + value: {category: 'default', dictionary: title, name: 'E1', notes: 'example tag 1', order: 0, score: 0} + } + }, + { + inputs: [ + { + name: 'K1' + } + ], + expectedResults: { + value: {category: 'default', dictionary: title, name: 'K1', notes: 'example kanji tag 1', order: 0, score: 0} + } + }, + { + inputs: [ + { + name: 'kstat1' + } + ], + expectedResults: { + value: {category: 'class', dictionary: title, name: 'kstat1', notes: 'kanji stat 1', order: 0, score: 0} + } + }, + { + inputs: [ + { + name: 'invalid' + } + ], + expectedResults: { + value: null + } + } + ]; + + for (const {inputs, expectedResults} of data) { + for (const {name} of inputs) { + const result = await database.findTagForTitle(name, title); + vm.assert.deepStrictEqual(result, expectedResults.value); + } + } +} + + +/** */ +async function testDatabase2() { + // Load dictionary data + const testDictionary = createTestDictionaryArchive('valid-dictionary1'); + const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); + const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); + + const title = testDictionaryIndex.title; + const titles = new Map([ + [title, {priority: 0, allowSecondarySearches: false}] + ]); + + // Setup database + const dictionaryDatabase = new DictionaryDatabase2(); + /** @type {import('dictionary-importer').ImportDetails} */ + const detaultImportDetails = {prefixWildcardsSupported: false}; + + // Error: not prepared + await assert.rejects(async () => await dictionaryDatabase.deleteDictionary(title, 1000, () => {})); + await assert.rejects(async () => await dictionaryDatabase.findTermsBulk(['?'], titles, 'exact')); + await assert.rejects(async () => await dictionaryDatabase.findTermsExactBulk([{term: '?', reading: '?'}], titles)); + await assert.rejects(async () => await dictionaryDatabase.findTermsBySequenceBulk([{query: 1, dictionary: title}])); + await assert.rejects(async () => await dictionaryDatabase.findTermMetaBulk(['?'], titles)); + await assert.rejects(async () => await dictionaryDatabase.findTermMetaBulk(['?'], titles)); + await assert.rejects(async () => await dictionaryDatabase.findKanjiBulk(['?'], titles)); + await assert.rejects(async () => await dictionaryDatabase.findKanjiMetaBulk(['?'], titles)); + await assert.rejects(async () => await dictionaryDatabase.findTagForTitle('tag', title)); + await assert.rejects(async () => await dictionaryDatabase.getDictionaryInfo()); + await assert.rejects(async () => await dictionaryDatabase.getDictionaryCounts([...titles.keys()], true)); + await assert.rejects(async () => await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)); + + await dictionaryDatabase.prepare(); + + // Error: already prepared + await assert.rejects(async () => await dictionaryDatabase.prepare()); + + await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails); + + // Error: dictionary already imported + await assert.rejects(async () => await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)); + + await dictionaryDatabase.close(); +} + + +/** */ +async function testDatabase3() { + const invalidDictionaries = [ + 'invalid-dictionary1', + 'invalid-dictionary2', + 'invalid-dictionary3', + 'invalid-dictionary4', + 'invalid-dictionary5', + 'invalid-dictionary6' + ]; + + // Setup database + const dictionaryDatabase = new DictionaryDatabase2(); + /** @type {import('dictionary-importer').ImportDetails} */ + const detaultImportDetails = {prefixWildcardsSupported: false}; + await dictionaryDatabase.prepare(); + + for (const invalidDictionary of invalidDictionaries) { + const testDictionary = createTestDictionaryArchive(invalidDictionary); + const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); + + let error = null; + try { + await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails); + } catch (e) { + error = e; + } + + if (error === null) { + assert.ok(false, `Expected an error while importing ${invalidDictionary}`); + } else { + const prefix = 'Dictionary has invalid data'; + const message = /** @type {import('core').UnknownObject} */ (error).message; + assert.ok(typeof message, 'string'); + if (typeof message === 'string') { + assert.ok(message.startsWith(prefix), `Expected error message to start with '${prefix}': ${message}`); + } + } + } + + await dictionaryDatabase.close(); +} + + +/** */ +async function main() { + const clearTimeout = 5000; + try { + await testDatabase1(); + await clearDatabase(clearTimeout); + + await testDatabase2(); + await clearDatabase(clearTimeout); + + await testDatabase3(); + await clearDatabase(clearTimeout); + } catch (e) { + console.log(e); + process.exit(-1); + throw e; + } +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-document-util.js b/test/test-document-util.js new file mode 100644 index 00000000..93ce1669 --- /dev/null +++ b/test/test-document-util.js @@ -0,0 +1,339 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const {JSDOM} = require('jsdom'); +const {testMain} = require('../dev/util'); +const {VM} = require('../dev/vm'); + + +// DOMRect class definition +class DOMRect { + /** + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ + constructor(x, y, width, height) { + /** @type {number} */ + this._x = x; + /** @type {number} */ + this._y = y; + /** @type {number} */ + this._width = width; + /** @type {number} */ + this._height = height; + } + + /** @type {number} */ + get x() { return this._x; } + /** @type {number} */ + get y() { return this._y; } + /** @type {number} */ + get width() { return this._width; } + /** @type {number} */ + get height() { return this._height; } + /** @type {number} */ + get left() { return this._x + Math.min(0, this._width); } + /** @type {number} */ + get right() { return this._x + Math.max(0, this._width); } + /** @type {number} */ + get top() { return this._y + Math.min(0, this._height); } + /** @type {number} */ + get bottom() { return this._y + Math.max(0, this._height); } + /** @returns {string} */ + toJSON() { return ''; } +} + + +/** + * @param {string} fileName + * @returns {JSDOM} + */ +function createJSDOM(fileName) { + const domSource = fs.readFileSync(fileName, {encoding: 'utf8'}); + const dom = new JSDOM(domSource); + const document = dom.window.document; + const window = dom.window; + + // Define innerText setter as an alias for textContent setter + Object.defineProperty(window.HTMLDivElement.prototype, 'innerText', { + set(value) { this.textContent = value; } + }); + + // Placeholder for feature detection + document.caretRangeFromPoint = () => null; + + return dom; +} + +/** + * @param {Element} element + * @param {string|undefined} selector + * @returns {?Element} + */ +function querySelectorChildOrSelf(element, selector) { + return selector ? element.querySelector(selector) : element; +} + +/** + * @param {JSDOM} dom + * @param {?Node} node + * @returns {?Text|Node} + */ +function getChildTextNodeOrSelf(dom, node) { + if (node === null) { return null; } + const Node = dom.window.Node; + const childNode = node.firstChild; + return (childNode !== null && childNode.nodeType === Node.TEXT_NODE ? childNode : node); +} + +/** + * @param {unknown} value + * @returns {unknown} + */ +function getPrototypeOfOrNull(value) { + try { + return Object.getPrototypeOf(value); + } catch (e) { + return null; + } +} + +/** + * @param {Document} document + * @returns {?Element} + */ +function findImposterElement(document) { + // Finds the imposter element based on it's z-index style + return document.querySelector('div[style*="2147483646"]>*'); +} + + +/** */ +async function testDocument1() { + const dom = createJSDOM(path.join(__dirname, 'data', 'html', 'test-document1.html')); + const window = dom.window; + const document = window.document; + const Node = window.Node; + const Range = window.Range; + + const vm = new VM({document, window, Range, Node}); + vm.execute([ + 'js/data/sandbox/string-util.js', + 'js/dom/dom-text-scanner.js', + 'js/dom/text-source-range.js', + 'js/dom/text-source-element.js', + 'js/dom/document-util.js' + ]); + /** @type {[DOMTextScanner: typeof DOMTextScanner, TextSourceRange: typeof TextSourceRange, TextSourceElement: typeof TextSourceElement, DocumentUtil: typeof DocumentUtil]} */ + const [DOMTextScanner2, TextSourceRange2, TextSourceElement2, DocumentUtil2] = vm.get([ + 'DOMTextScanner', + 'TextSourceRange', + 'TextSourceElement', + 'DocumentUtil' + ]); + + try { + await testDocumentTextScanningFunctions(dom, {DocumentUtil: DocumentUtil2, TextSourceRange: TextSourceRange2, TextSourceElement: TextSourceElement2}); + await testTextSourceRangeSeekFunctions(dom, {DOMTextScanner: DOMTextScanner2}); + } finally { + window.close(); + } +} + +/** + * @param {JSDOM} dom + * @param {{DocumentUtil: typeof DocumentUtil, TextSourceRange: typeof TextSourceRange, TextSourceElement: typeof TextSourceElement}} details + */ +async function testDocumentTextScanningFunctions(dom, {DocumentUtil, TextSourceRange, TextSourceElement}) { + const document = dom.window.document; + + for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('.test[data-test-type=scan]'))) { + // Get test parameters + const { + elementFromPointSelector, + caretRangeFromPointSelector, + startNodeSelector, + startOffset, + endNodeSelector, + endOffset, + resultType, + sentenceScanExtent, + sentence, + hasImposter, + terminateAtNewlines + } = testElement.dataset; + + const elementFromPointValue = querySelectorChildOrSelf(testElement, elementFromPointSelector); + const caretRangeFromPointValue = querySelectorChildOrSelf(testElement, caretRangeFromPointSelector); + const startNode = getChildTextNodeOrSelf(dom, querySelectorChildOrSelf(testElement, startNodeSelector)); + const endNode = getChildTextNodeOrSelf(dom, querySelectorChildOrSelf(testElement, endNodeSelector)); + + const startOffset2 = parseInt(/** @type {string} */ (startOffset), 10); + const endOffset2 = parseInt(/** @type {string} */ (endOffset), 10); + const sentenceScanExtent2 = parseInt(/** @type {string} */ (sentenceScanExtent), 10); + const terminateAtNewlines2 = (terminateAtNewlines !== 'false'); + + assert.notStrictEqual(elementFromPointValue, null); + assert.notStrictEqual(caretRangeFromPointValue, null); + assert.notStrictEqual(startNode, null); + assert.notStrictEqual(endNode, null); + + // Setup functions + document.elementFromPoint = () => elementFromPointValue; + + document.caretRangeFromPoint = (x, y) => { + const imposter = getChildTextNodeOrSelf(dom, findImposterElement(document)); + assert.strictEqual(!!imposter, hasImposter === 'true'); + + const range = document.createRange(); + range.setStart(/** @type {Node} */ (imposter ? imposter : startNode), startOffset2); + range.setEnd(/** @type {Node} */ (imposter ? imposter : startNode), endOffset2); + + // Override getClientRects to return a rect guaranteed to contain (x, y) + range.getClientRects = () => { + /** @type {import('test/document-types').PseudoDOMRectList} */ + const domRectList = Object.assign( + [new DOMRect(x - 1, y - 1, 2, 2)], + { + /** + * @this {DOMRect[]} + * @param {number} index + * @returns {DOMRect} + */ + item: function item(index) { return this[index]; } + } + ); + return domRectList; + }; + return range; + }; + + // Test docRangeFromPoint + const source = DocumentUtil.getRangeFromPoint(0, 0, { + deepContentScan: false, + normalizeCssZoom: true + }); + switch (resultType) { + case 'TextSourceRange': + assert.strictEqual(getPrototypeOfOrNull(source), TextSourceRange.prototype); + break; + case 'TextSourceElement': + assert.strictEqual(getPrototypeOfOrNull(source), TextSourceElement.prototype); + break; + case 'null': + assert.strictEqual(source, null); + break; + default: + assert.ok(false); + break; + } + if (source === null) { continue; } + + // Sentence info + const terminatorString = '…。..??!!'; + const terminatorMap = new Map(); + for (const char of terminatorString) { + terminatorMap.set(char, [false, true]); + } + const quoteArray = [['「', '」'], ['『', '』'], ['\'', '\''], ['"', '"']]; + const forwardQuoteMap = new Map(); + const backwardQuoteMap = new Map(); + for (const [char1, char2] of quoteArray) { + forwardQuoteMap.set(char1, [char2, false]); + backwardQuoteMap.set(char2, [char1, false]); + } + + // Test docSentenceExtract + const sentenceActual = DocumentUtil.extractSentence( + source, + false, + sentenceScanExtent2, + terminateAtNewlines2, + terminatorMap, + forwardQuoteMap, + backwardQuoteMap + ).text; + assert.strictEqual(sentenceActual, sentence); + + // Clean + source.cleanup(); + } +} + +/** + * @param {JSDOM} dom + * @param {{DOMTextScanner: typeof DOMTextScanner}} details + */ +async function testTextSourceRangeSeekFunctions(dom, {DOMTextScanner}) { + const document = dom.window.document; + + for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('.test[data-test-type=text-source-range-seek]'))) { + // Get test parameters + const { + seekNodeSelector, + seekNodeIsText, + seekOffset, + seekLength, + seekDirection, + expectedResultNodeSelector, + expectedResultNodeIsText, + expectedResultOffset, + expectedResultContent + } = testElement.dataset; + + const seekOffset2 = parseInt(/** @type {string} */ (seekOffset), 10); + const seekLength2 = parseInt(/** @type {string} */ (seekLength), 10); + const expectedResultOffset2 = parseInt(/** @type {string} */ (expectedResultOffset), 10); + + /** @type {?Node} */ + let seekNode = testElement.querySelector(/** @type {string} */ (seekNodeSelector)); + if (seekNodeIsText === 'true' && seekNode !== null) { + seekNode = seekNode.firstChild; + } + + /** @type {?Node} */ + let expectedResultNode = testElement.querySelector(/** @type {string} */ (expectedResultNodeSelector)); + if (expectedResultNodeIsText === 'true' && expectedResultNode !== null) { + expectedResultNode = expectedResultNode.firstChild; + } + + const {node, offset, content} = ( + seekDirection === 'forward' ? + new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset2, true, false).seek(seekLength2) : + new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset2, true, false).seek(-seekLength2) + ); + + assert.strictEqual(node, expectedResultNode); + assert.strictEqual(offset, expectedResultOffset2); + assert.strictEqual(content, expectedResultContent); + } +} + + +/** */ +async function main() { + await testDocument1(); +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-hotkey-util.js b/test/test-hotkey-util.js new file mode 100644 index 00000000..88f5a8d2 --- /dev/null +++ b/test/test-hotkey-util.js @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const assert = require('assert'); +const {testMain} = require('../dev/util'); +const {VM} = require('../dev/vm'); + + +/** + * @template T + * @param {T} value + * @returns {T} + */ +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + +/** + * @returns {HotkeyUtil} + */ +function createHotkeyUtil() { + const vm = new VM(); + vm.execute(['js/input/hotkey-util.js']); + /** @type {typeof HotkeyUtil} */ + const HotkeyUtil2 = vm.getSingle('HotkeyUtil'); + return new HotkeyUtil2(); +} + + +/** */ +function testCommandConversions() { + /** @type {{os: import('environment').OperatingSystem, command: string, expectedCommand: string, expectedInput: {key: string, modifiers: import('input').Modifier[]}}[]} */ + const data = [ + {os: 'win', command: 'Alt+F', expectedCommand: 'Alt+F', expectedInput: {key: 'KeyF', modifiers: ['alt']}}, + {os: 'win', command: 'F1', expectedCommand: 'F1', expectedInput: {key: 'F1', modifiers: []}}, + + {os: 'win', command: 'Ctrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, + {os: 'win', command: 'MacCtrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, + {os: 'win', command: 'Command+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}}, + + {os: 'mac', command: 'Ctrl+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}}, + {os: 'mac', command: 'MacCtrl+Alt+Shift+F1', expectedCommand: 'MacCtrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, + {os: 'mac', command: 'Command+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}}, + + {os: 'linux', command: 'Ctrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, + {os: 'linux', command: 'MacCtrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, + {os: 'linux', command: 'Command+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}} + ]; + + const hotkeyUtil = createHotkeyUtil(); + for (const {command, os, expectedInput, expectedCommand} of data) { + hotkeyUtil.os = os; + const input = clone(hotkeyUtil.convertCommandToInput(command)); + assert.deepStrictEqual(input, expectedInput); + const command2 = hotkeyUtil.convertInputToCommand(input.key, input.modifiers); + assert.deepStrictEqual(command2, expectedCommand); + } +} + +/** */ +function testDisplayNames() { + /** @type {{os: import('environment').OperatingSystem, key: ?string, modifiers: import('input').Modifier[], expected: string}[]} */ + const data = [ + {os: 'win', key: null, modifiers: [], expected: ''}, + {os: 'win', key: 'KeyF', modifiers: [], expected: 'F'}, + {os: 'win', key: 'F1', modifiers: [], expected: 'F1'}, + {os: 'win', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, + {os: 'win', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, + {os: 'win', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, + {os: 'win', key: null, modifiers: ['alt'], expected: 'Alt'}, + {os: 'win', key: 'KeyF', modifiers: ['alt'], expected: 'Alt + F'}, + {os: 'win', key: 'F1', modifiers: ['alt'], expected: 'Alt + F1'}, + {os: 'win', key: null, modifiers: ['shift'], expected: 'Shift'}, + {os: 'win', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, + {os: 'win', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, + {os: 'win', key: null, modifiers: ['meta'], expected: 'Windows'}, + {os: 'win', key: 'KeyF', modifiers: ['meta'], expected: 'Windows + F'}, + {os: 'win', key: 'F1', modifiers: ['meta'], expected: 'Windows + F1'}, + {os: 'win', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, + {os: 'win', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, + {os: 'win', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'}, + + {os: 'mac', key: null, modifiers: [], expected: ''}, + {os: 'mac', key: 'KeyF', modifiers: [], expected: 'F'}, + {os: 'mac', key: 'F1', modifiers: [], expected: 'F1'}, + {os: 'mac', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, + {os: 'mac', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, + {os: 'mac', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, + {os: 'mac', key: null, modifiers: ['alt'], expected: 'Opt'}, + {os: 'mac', key: 'KeyF', modifiers: ['alt'], expected: 'Opt + F'}, + {os: 'mac', key: 'F1', modifiers: ['alt'], expected: 'Opt + F1'}, + {os: 'mac', key: null, modifiers: ['shift'], expected: 'Shift'}, + {os: 'mac', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, + {os: 'mac', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, + {os: 'mac', key: null, modifiers: ['meta'], expected: 'Cmd'}, + {os: 'mac', key: 'KeyF', modifiers: ['meta'], expected: 'Cmd + F'}, + {os: 'mac', key: 'F1', modifiers: ['meta'], expected: 'Cmd + F1'}, + {os: 'mac', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, + {os: 'mac', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, + {os: 'mac', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'}, + + {os: 'linux', key: null, modifiers: [], expected: ''}, + {os: 'linux', key: 'KeyF', modifiers: [], expected: 'F'}, + {os: 'linux', key: 'F1', modifiers: [], expected: 'F1'}, + {os: 'linux', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, + {os: 'linux', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, + {os: 'linux', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, + {os: 'linux', key: null, modifiers: ['alt'], expected: 'Alt'}, + {os: 'linux', key: 'KeyF', modifiers: ['alt'], expected: 'Alt + F'}, + {os: 'linux', key: 'F1', modifiers: ['alt'], expected: 'Alt + F1'}, + {os: 'linux', key: null, modifiers: ['shift'], expected: 'Shift'}, + {os: 'linux', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, + {os: 'linux', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, + {os: 'linux', key: null, modifiers: ['meta'], expected: 'Super'}, + {os: 'linux', key: 'KeyF', modifiers: ['meta'], expected: 'Super + F'}, + {os: 'linux', key: 'F1', modifiers: ['meta'], expected: 'Super + F1'}, + {os: 'linux', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, + {os: 'linux', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, + {os: 'linux', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'}, + + {os: 'unknown', key: null, modifiers: [], expected: ''}, + {os: 'unknown', key: 'KeyF', modifiers: [], expected: 'F'}, + {os: 'unknown', key: 'F1', modifiers: [], expected: 'F1'}, + {os: 'unknown', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, + {os: 'unknown', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, + {os: 'unknown', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, + {os: 'unknown', key: null, modifiers: ['alt'], expected: 'Alt'}, + {os: 'unknown', key: 'KeyF', modifiers: ['alt'], expected: 'Alt + F'}, + {os: 'unknown', key: 'F1', modifiers: ['alt'], expected: 'Alt + F1'}, + {os: 'unknown', key: null, modifiers: ['shift'], expected: 'Shift'}, + {os: 'unknown', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, + {os: 'unknown', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, + {os: 'unknown', key: null, modifiers: ['meta'], expected: 'Meta'}, + {os: 'unknown', key: 'KeyF', modifiers: ['meta'], expected: 'Meta + F'}, + {os: 'unknown', key: 'F1', modifiers: ['meta'], expected: 'Meta + F1'}, + {os: 'unknown', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, + {os: 'unknown', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, + {os: 'unknown', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'} + ]; + + const hotkeyUtil = createHotkeyUtil(); + for (const {os, key, modifiers, expected} of data) { + hotkeyUtil.os = os; + const displayName = hotkeyUtil.getInputDisplayValue(key, modifiers); + assert.deepStrictEqual(displayName, expected); + } +} + +/** */ +function testSortModifiers() { + /** @type {{modifiers: import('input').Modifier[], expected: import('input').Modifier[]}[]} */ + const data = [ + {modifiers: [], expected: []}, + {modifiers: ['shift', 'alt', 'ctrl', 'mouse4', 'meta', 'mouse1', 'mouse0'], expected: ['meta', 'ctrl', 'alt', 'shift', 'mouse0', 'mouse1', 'mouse4']} + ]; + + const hotkeyUtil = createHotkeyUtil(); + for (const {modifiers, expected} of data) { + const modifiers2 = hotkeyUtil.sortModifiers(modifiers); + assert.strictEqual(modifiers2, modifiers); + assert.deepStrictEqual(modifiers2, expected); + } +} + + +/** */ +function main() { + testCommandConversions(); + testDisplayNames(); + testSortModifiers(); +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-japanese-util.js b/test/test-japanese-util.js new file mode 100644 index 00000000..0a95b858 --- /dev/null +++ b/test/test-japanese-util.js @@ -0,0 +1,915 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const assert = require('assert'); +const {testMain} = require('../dev/util'); +const {VM} = require('../dev/vm'); + +const vm = new VM(); +vm.execute([ + 'lib/wanakana.min.js', + 'js/language/sandbox/japanese-util.js', + 'js/general/text-source-map.js' +]); +/** @type {[JapaneseUtil: typeof JapaneseUtil, TextSourceMap: typeof TextSourceMap, wanakana: import('wanakana')]} */ +const [JapaneseUtil2, TextSourceMap2, wanakana] = vm.get(['JapaneseUtil', 'TextSourceMap', 'wanakana']); +const jp = new JapaneseUtil2(wanakana); + + +/** */ +function testIsCodePointKanji() { + /** @type {[characters: string, expected: boolean][]} */ + const data = [ + ['力方', true], + ['\u53f1\u{20b9f}', true], + ['かたカタ々kata、。?,.?', false], + ['逸逸', true] + ]; + + for (const [characters, expected] of data) { + for (const character of characters) { + const codePoint = /** @type {number} */ (character.codePointAt(0)); + const actual = jp.isCodePointKanji(codePoint); + assert.strictEqual(actual, expected, `isCodePointKanji failed for ${character} (\\u{${codePoint.toString(16)}})`); + } + } +} + +/** */ +function testIsCodePointKana() { + /** @type {[characters: string, expected: boolean][]} */ + const data = [ + ['かたカタ', true], + ['力方々kata、。?,.?', false], + ['\u53f1\u{20b9f}', false] + ]; + + for (const [characters, expected] of data) { + for (const character of characters) { + const codePoint = /** @type {number} */ (character.codePointAt(0)); + const actual = jp.isCodePointKana(codePoint); + assert.strictEqual(actual, expected, `isCodePointKana failed for ${character} (\\u{${codePoint.toString(16)}})`); + } + } +} + +/** */ +function testIsCodePointJapanese() { + /** @type {[characters: string, expected: boolean][]} */ + const data = [ + ['かたカタ力方々、。?', true], + ['\u53f1\u{20b9f}', true], + ['kata,.?', false], + ['逸逸', true] + ]; + + for (const [characters, expected] of data) { + for (const character of characters) { + const codePoint = /** @type {number} */ (character.codePointAt(0)); + const actual = jp.isCodePointJapanese(codePoint); + assert.strictEqual(actual, expected, `isCodePointJapanese failed for ${character} (\\u{${codePoint.toString(16)}})`); + } + } +} + +/** */ +function testIsStringEntirelyKana() { + /** @type {[string: string, expected: boolean][]} */ + const data = [ + ['かたかな', true], + ['カタカナ', true], + ['ひらがな', true], + ['ヒラガナ', true], + ['カタカナひらがな', true], + ['かたカタ力方々、。?', false], + ['\u53f1\u{20b9f}', false], + ['kata,.?', false], + ['かたカタ力方々、。?invalid', false], + ['\u53f1\u{20b9f}invalid', false], + ['kata,.?かた', false] + ]; + + for (const [string, expected] of data) { + assert.strictEqual(jp.isStringEntirelyKana(string), expected); + } +} + +/** */ +function testIsStringPartiallyJapanese() { + /** @type {[string: string, expected: boolean][]} */ + const data = [ + ['かたかな', true], + ['カタカナ', true], + ['ひらがな', true], + ['ヒラガナ', true], + ['カタカナひらがな', true], + ['かたカタ力方々、。?', true], + ['\u53f1\u{20b9f}', true], + ['kata,.?', false], + ['かたカタ力方々、。?invalid', true], + ['\u53f1\u{20b9f}invalid', true], + ['kata,.?かた', true], + ['逸逸', true] + ]; + + for (const [string, expected] of data) { + assert.strictEqual(jp.isStringPartiallyJapanese(string), expected); + } +} + +/** */ +function testConvertKatakanaToHiragana() { + /** @type {[string: string, expected: string, keepProlongedSoundMarks?: boolean][]} */ + const data = [ + ['かたかな', 'かたかな'], + ['ひらがな', 'ひらがな'], + ['カタカナ', 'かたかな'], + ['ヒラガナ', 'ひらがな'], + ['カタカナかたかな', 'かたかなかたかな'], + ['ヒラガナひらがな', 'ひらがなひらがな'], + ['chikaraちからチカラ力', 'chikaraちからちから力'], + ['katakana', 'katakana'], + ['hiragana', 'hiragana'], + ['カーナー', 'かあなあ'], + ['カーナー', 'かーなー', true] + ]; + + for (const [string, expected, keepProlongedSoundMarks=false] of data) { + assert.strictEqual(jp.convertKatakanaToHiragana(string, keepProlongedSoundMarks), expected); + } +} + +/** */ +function testConvertHiraganaToKatakana() { + /** @type {[string: string, expected: string][]} */ + const data = [ + ['かたかな', 'カタカナ'], + ['ひらがな', 'ヒラガナ'], + ['カタカナ', 'カタカナ'], + ['ヒラガナ', 'ヒラガナ'], + ['カタカナかたかな', 'カタカナカタカナ'], + ['ヒラガナひらがな', 'ヒラガナヒラガナ'], + ['chikaraちからチカラ力', 'chikaraチカラチカラ力'], + ['katakana', 'katakana'], + ['hiragana', 'hiragana'] + ]; + + for (const [string, expected] of data) { + assert.strictEqual(jp.convertHiraganaToKatakana(string), expected); + } +} + +/** */ +function testConvertToRomaji() { + /** @type {[string: string, expected: string][]} */ + const data = [ + ['かたかな', 'katakana'], + ['ひらがな', 'hiragana'], + ['カタカナ', 'katakana'], + ['ヒラガナ', 'hiragana'], + ['カタカナかたかな', 'katakanakatakana'], + ['ヒラガナひらがな', 'hiraganahiragana'], + ['chikaraちからチカラ力', 'chikarachikarachikara力'], + ['katakana', 'katakana'], + ['hiragana', 'hiragana'] + ]; + + for (const [string, expected] of data) { + assert.strictEqual(jp.convertToRomaji(string), expected); + } +} + +/** */ +function testConvertNumericToFullWidth() { + /** @type {[string: string, expected: string][]} */ + const data = [ + ['0123456789', '0123456789'], + ['abcdefghij', 'abcdefghij'], + ['カタカナ', 'カタカナ'], + ['ひらがな', 'ひらがな'] + ]; + + for (const [string, expected] of data) { + assert.strictEqual(jp.convertNumericToFullWidth(string), expected); + } +} + +/** */ +function testConvertHalfWidthKanaToFullWidth() { + /** @type {[string: string, expected: string, expectedSourceMapping?: number[]][]} */ + const data = [ + ['0123456789', '0123456789'], + ['abcdefghij', 'abcdefghij'], + ['カタカナ', 'カタカナ'], + ['ひらがな', 'ひらがな'], + ['カキ', 'カキ', [1, 1]], + ['ガキ', 'ガキ', [2, 1]], + ['ニホン', 'ニホン', [1, 1, 1]], + ['ニッポン', 'ニッポン', [1, 1, 2, 1]] + ]; + + for (const [string, expected, expectedSourceMapping] of data) { + const sourceMap = new TextSourceMap2(string); + const actual1 = jp.convertHalfWidthKanaToFullWidth(string, null); + const actual2 = jp.convertHalfWidthKanaToFullWidth(string, sourceMap); + assert.strictEqual(actual1, expected); + assert.strictEqual(actual2, expected); + if (typeof expectedSourceMapping !== 'undefined') { + assert.ok(sourceMap.equals(new TextSourceMap2(string, expectedSourceMapping))); + } + } +} + +/** */ +function testConvertAlphabeticToKana() { + /** @type {[string: string, expected: string, expectedSourceMapping?: number[]][]} */ + const data = [ + ['0123456789', '0123456789'], + ['abcdefghij', 'あbcでfgひj', [1, 1, 1, 2, 1, 1, 2, 1]], + ['ABCDEFGHIJ', 'あbcでfgひj', [1, 1, 1, 2, 1, 1, 2, 1]], // wanakana.toHiragana converts text to lower case + ['カタカナ', 'カタカナ'], + ['ひらがな', 'ひらがな'], + ['chikara', 'ちから', [3, 2, 2]], + ['CHIKARA', 'ちから', [3, 2, 2]] + ]; + + for (const [string, expected, expectedSourceMapping] of data) { + const sourceMap = new TextSourceMap2(string); + const actual1 = jp.convertAlphabeticToKana(string, null); + const actual2 = jp.convertAlphabeticToKana(string, sourceMap); + assert.strictEqual(actual1, expected); + assert.strictEqual(actual2, expected); + if (typeof expectedSourceMapping !== 'undefined') { + assert.ok(sourceMap.equals(new TextSourceMap2(string, expectedSourceMapping))); + } + } +} + +/** */ +function testDistributeFurigana() { + /** @type {[input: [term: string, reading: string], expected: {text: string, reading: string}[]][]} */ + const data = [ + ([ + ['有り難う', 'ありがとう'], + [ + {text: '有', reading: 'あ'}, + {text: 'り', reading: ''}, + {text: '難', reading: 'がと'}, + {text: 'う', reading: ''} + ] + ]), + [ + ['方々', 'かたがた'], + [ + {text: '方々', reading: 'かたがた'} + ] + ], + [ + ['お祝い', 'おいわい'], + [ + {text: 'お', reading: ''}, + {text: '祝', reading: 'いわ'}, + {text: 'い', reading: ''} + ] + ], + [ + ['美味しい', 'おいしい'], + [ + {text: '美味', reading: 'おい'}, + {text: 'しい', reading: ''} + ] + ], + [ + ['食べ物', 'たべもの'], + [ + {text: '食', reading: 'た'}, + {text: 'べ', reading: ''}, + {text: '物', reading: 'もの'} + ] + ], + [ + ['試し切り', 'ためしぎり'], + [ + {text: '試', reading: 'ため'}, + {text: 'し', reading: ''}, + {text: '切', reading: 'ぎ'}, + {text: 'り', reading: ''} + ] + ], + // Ambiguous + [ + ['飼い犬', 'かいいぬ'], + [ + {text: '飼い犬', reading: 'かいいぬ'} + ] + ], + [ + ['長い間', 'ながいあいだ'], + [ + {text: '長い間', reading: 'ながいあいだ'} + ] + ], + // Same/empty reading + [ + ['飼い犬', ''], + [ + {text: '飼い犬', reading: ''} + ] + ], + [ + ['かいいぬ', 'かいいぬ'], + [ + {text: 'かいいぬ', reading: ''} + ] + ], + [ + ['かいぬ', 'かいぬ'], + [ + {text: 'かいぬ', reading: ''} + ] + ], + // Misc + [ + ['月', 'か'], + [ + {text: '月', reading: 'か'} + ] + ], + [ + ['月', 'カ'], + [ + {text: '月', reading: 'カ'} + ] + ], + // Mismatched kana readings + [ + ['有り難う', 'アリガトウ'], + [ + {text: '有', reading: 'ア'}, + {text: 'り', reading: 'リ'}, + {text: '難', reading: 'ガト'}, + {text: 'う', reading: 'ウ'} + ] + ], + [ + ['ありがとう', 'アリガトウ'], + [ + {text: 'ありがとう', reading: 'アリガトウ'} + ] + ], + // Mismatched kana readings (real examples) + [ + ['カ月', 'かげつ'], + [ + {text: 'カ', reading: 'か'}, + {text: '月', reading: 'げつ'} + ] + ], + [ + ['序ノ口', 'じょのくち'], + [ + {text: '序', reading: 'じょ'}, + {text: 'ノ', reading: 'の'}, + {text: '口', reading: 'くち'} + ] + ], + [ + ['スズメの涙', 'すずめのなみだ'], + [ + {text: 'スズメ', reading: 'すずめ'}, + {text: 'の', reading: ''}, + {text: '涙', reading: 'なみだ'} + ] + ], + [ + ['二カ所', 'にかしょ'], + [ + {text: '二', reading: 'に'}, + {text: 'カ', reading: 'か'}, + {text: '所', reading: 'しょ'} + ] + ], + [ + ['八ツ橋', 'やつはし'], + [ + {text: '八', reading: 'や'}, + {text: 'ツ', reading: 'つ'}, + {text: '橋', reading: 'はし'} + ] + ], + [ + ['八ツ橋', 'やつはし'], + [ + {text: '八', reading: 'や'}, + {text: 'ツ', reading: 'つ'}, + {text: '橋', reading: 'はし'} + ] + ], + [ + ['一カ月', 'いっかげつ'], + [ + {text: '一', reading: 'いっ'}, + {text: 'カ', reading: 'か'}, + {text: '月', reading: 'げつ'} + ] + ], + [ + ['一カ所', 'いっかしょ'], + [ + {text: '一', reading: 'いっ'}, + {text: 'カ', reading: 'か'}, + {text: '所', reading: 'しょ'} + ] + ], + [ + ['カ所', 'かしょ'], + [ + {text: 'カ', reading: 'か'}, + {text: '所', reading: 'しょ'} + ] + ], + [ + ['数カ月', 'すうかげつ'], + [ + {text: '数', reading: 'すう'}, + {text: 'カ', reading: 'か'}, + {text: '月', reading: 'げつ'} + ] + ], + [ + ['くノ一', 'くのいち'], + [ + {text: 'く', reading: ''}, + {text: 'ノ', reading: 'の'}, + {text: '一', reading: 'いち'} + ] + ], + [ + ['くノ一', 'くのいち'], + [ + {text: 'く', reading: ''}, + {text: 'ノ', reading: 'の'}, + {text: '一', reading: 'いち'} + ] + ], + [ + ['数カ国', 'すうかこく'], + [ + {text: '数', reading: 'すう'}, + {text: 'カ', reading: 'か'}, + {text: '国', reading: 'こく'} + ] + ], + [ + ['数カ所', 'すうかしょ'], + [ + {text: '数', reading: 'すう'}, + {text: 'カ', reading: 'か'}, + {text: '所', reading: 'しょ'} + ] + ], + [ + ['壇ノ浦の戦い', 'だんのうらのたたかい'], + [ + {text: '壇', reading: 'だん'}, + {text: 'ノ', reading: 'の'}, + {text: '浦', reading: 'うら'}, + {text: 'の', reading: ''}, + {text: '戦', reading: 'たたか'}, + {text: 'い', reading: ''} + ] + ], + [ + ['壇ノ浦の戦', 'だんのうらのたたかい'], + [ + {text: '壇', reading: 'だん'}, + {text: 'ノ', reading: 'の'}, + {text: '浦', reading: 'うら'}, + {text: 'の', reading: ''}, + {text: '戦', reading: 'たたかい'} + ] + ], + [ + ['序ノ口格', 'じょのくちかく'], + [ + {text: '序', reading: 'じょ'}, + {text: 'ノ', reading: 'の'}, + {text: '口格', reading: 'くちかく'} + ] + ], + [ + ['二カ国語', 'にかこくご'], + [ + {text: '二', reading: 'に'}, + {text: 'カ', reading: 'か'}, + {text: '国語', reading: 'こくご'} + ] + ], + [ + ['カ国', 'かこく'], + [ + {text: 'カ', reading: 'か'}, + {text: '国', reading: 'こく'} + ] + ], + [ + ['カ国語', 'かこくご'], + [ + {text: 'カ', reading: 'か'}, + {text: '国語', reading: 'こくご'} + ] + ], + [ + ['壇ノ浦の合戦', 'だんのうらのかっせん'], + [ + {text: '壇', reading: 'だん'}, + {text: 'ノ', reading: 'の'}, + {text: '浦', reading: 'うら'}, + {text: 'の', reading: ''}, + {text: '合戦', reading: 'かっせん'} + ] + ], + [ + ['一タ偏', 'いちたへん'], + [ + {text: '一', reading: 'いち'}, + {text: 'タ', reading: 'た'}, + {text: '偏', reading: 'へん'} + ] + ], + [ + ['ル又', 'るまた'], + [ + {text: 'ル', reading: 'る'}, + {text: '又', reading: 'また'} + ] + ], + [ + ['ノ木偏', 'のぎへん'], + [ + {text: 'ノ', reading: 'の'}, + {text: '木偏', reading: 'ぎへん'} + ] + ], + [ + ['一ノ貝', 'いちのかい'], + [ + {text: '一', reading: 'いち'}, + {text: 'ノ', reading: 'の'}, + {text: '貝', reading: 'かい'} + ] + ], + [ + ['虎ノ門事件', 'とらのもんじけん'], + [ + {text: '虎', reading: 'とら'}, + {text: 'ノ', reading: 'の'}, + {text: '門事件', reading: 'もんじけん'} + ] + ], + [ + ['教育ニ関スル勅語', 'きょういくにかんするちょくご'], + [ + {text: '教育', reading: 'きょういく'}, + {text: 'ニ', reading: 'に'}, + {text: '関', reading: 'かん'}, + {text: 'スル', reading: 'する'}, + {text: '勅語', reading: 'ちょくご'} + ] + ], + [ + ['二カ年', 'にかねん'], + [ + {text: '二', reading: 'に'}, + {text: 'カ', reading: 'か'}, + {text: '年', reading: 'ねん'} + ] + ], + [ + ['三カ年', 'さんかねん'], + [ + {text: '三', reading: 'さん'}, + {text: 'カ', reading: 'か'}, + {text: '年', reading: 'ねん'} + ] + ], + [ + ['四カ年', 'よんかねん'], + [ + {text: '四', reading: 'よん'}, + {text: 'カ', reading: 'か'}, + {text: '年', reading: 'ねん'} + ] + ], + [ + ['五カ年', 'ごかねん'], + [ + {text: '五', reading: 'ご'}, + {text: 'カ', reading: 'か'}, + {text: '年', reading: 'ねん'} + ] + ], + [ + ['六カ年', 'ろっかねん'], + [ + {text: '六', reading: 'ろっ'}, + {text: 'カ', reading: 'か'}, + {text: '年', reading: 'ねん'} + ] + ], + [ + ['七カ年', 'ななかねん'], + [ + {text: '七', reading: 'なな'}, + {text: 'カ', reading: 'か'}, + {text: '年', reading: 'ねん'} + ] + ], + [ + ['八カ年', 'はちかねん'], + [ + {text: '八', reading: 'はち'}, + {text: 'カ', reading: 'か'}, + {text: '年', reading: 'ねん'} + ] + ], + [ + ['九カ年', 'きゅうかねん'], + [ + {text: '九', reading: 'きゅう'}, + {text: 'カ', reading: 'か'}, + {text: '年', reading: 'ねん'} + ] + ], + [ + ['十カ年', 'じゅうかねん'], + [ + {text: '十', reading: 'じゅう'}, + {text: 'カ', reading: 'か'}, + {text: '年', reading: 'ねん'} + ] + ], + [ + ['鏡ノ間', 'かがみのま'], + [ + {text: '鏡', reading: 'かがみ'}, + {text: 'ノ', reading: 'の'}, + {text: '間', reading: 'ま'} + ] + ], + [ + ['鏡ノ間', 'かがみのま'], + [ + {text: '鏡', reading: 'かがみ'}, + {text: 'ノ', reading: 'の'}, + {text: '間', reading: 'ま'} + ] + ], + [ + ['ページ違反', 'ぺーじいはん'], + [ + {text: 'ペ', reading: 'ぺ'}, + {text: 'ー', reading: ''}, + {text: 'ジ', reading: 'じ'}, + {text: '違反', reading: 'いはん'} + ] + ], + // Mismatched kana + [ + ['サボる', 'サボル'], + [ + {text: 'サボ', reading: ''}, + {text: 'る', reading: 'ル'} + ] + ], + // Reading starts with term, but has remainder characters + [ + ['シック', 'シック・ビルしょうこうぐん'], + [ + {text: 'シック', reading: 'シック・ビルしょうこうぐん'} + ] + ], + // Kanji distribution tests + [ + ['逸らす', 'そらす'], + [ + {text: '逸', reading: 'そ'}, + {text: 'らす', reading: ''} + ] + ], + [ + ['逸らす', 'そらす'], + [ + {text: '逸', reading: 'そ'}, + {text: 'らす', reading: ''} + ] + ] + ]; + + for (const [[term, reading], expected] of data) { + const actual = jp.distributeFurigana(term, reading); + vm.assert.deepStrictEqual(actual, expected); + } +} + +/** */ +function testDistributeFuriganaInflected() { + /** @type {[input: [term: string, reading: string, source: string], expected: {text: string, reading: string}[]][]} */ + const data = [ + [ + ['美味しい', 'おいしい', '美味しかた'], + [ + {text: '美味', reading: 'おい'}, + {text: 'しかた', reading: ''} + ] + ], + [ + ['食べる', 'たべる', '食べた'], + [ + {text: '食', reading: 'た'}, + {text: 'べた', reading: ''} + ] + ], + [ + ['迄に', 'までに', 'までに'], + [ + {text: 'までに', reading: ''} + ] + ], + [ + ['行う', 'おこなう', 'おこなわなかった'], + [ + {text: 'おこなわなかった', reading: ''} + ] + ], + [ + ['いい', 'いい', 'イイ'], + [ + {text: 'イイ', reading: ''} + ] + ], + [ + ['否か', 'いなか', '否カ'], + [ + {text: '否', reading: 'いな'}, + {text: 'カ', reading: 'か'} + ] + ] + ]; + + for (const [[term, reading, source], expected] of data) { + const actual = jp.distributeFuriganaInflected(term, reading, source); + vm.assert.deepStrictEqual(actual, expected); + } +} + +/** */ +function testCollapseEmphaticSequences() { + /** @type {[input: [text: string, fullCollapse: boolean], output: [expected: string, expectedSourceMapping: number[]]][]} */ + const data = [ + [['かこい', false], ['かこい', [1, 1, 1]]], + [['かこい', true], ['かこい', [1, 1, 1]]], + [['かっこい', false], ['かっこい', [1, 1, 1, 1]]], + [['かっこい', true], ['かこい', [2, 1, 1]]], + [['かっっこい', false], ['かっこい', [1, 2, 1, 1]]], + [['かっっこい', true], ['かこい', [3, 1, 1]]], + [['かっっっこい', false], ['かっこい', [1, 3, 1, 1]]], + [['かっっっこい', true], ['かこい', [4, 1, 1]]], + + [['こい', false], ['こい', [1, 1]]], + [['こい', true], ['こい', [1, 1]]], + [['っこい', false], ['っこい', [1, 1, 1]]], + [['っこい', true], ['こい', [2, 1]]], + [['っっこい', false], ['っこい', [2, 1, 1]]], + [['っっこい', true], ['こい', [3, 1]]], + [['っっっこい', false], ['っこい', [3, 1, 1]]], + [['っっっこい', true], ['こい', [4, 1]]], + + [['すごい', false], ['すごい', [1, 1, 1]]], + [['すごい', true], ['すごい', [1, 1, 1]]], + [['すごーい', false], ['すごーい', [1, 1, 1, 1]]], + [['すごーい', true], ['すごい', [1, 2, 1]]], + [['すごーーい', false], ['すごーい', [1, 1, 2, 1]]], + [['すごーーい', true], ['すごい', [1, 3, 1]]], + [['すっごーい', false], ['すっごーい', [1, 1, 1, 1, 1]]], + [['すっごーい', true], ['すごい', [2, 2, 1]]], + [['すっっごーーい', false], ['すっごーい', [1, 2, 1, 2, 1]]], + [['すっっごーーい', true], ['すごい', [3, 3, 1]]], + + [['', false], ['', []]], + [['', true], ['', []]], + [['っ', false], ['っ', [1]]], + [['っ', true], ['', [1]]], + [['っっ', false], ['っ', [2]]], + [['っっ', true], ['', [2]]], + [['っっっ', false], ['っ', [3]]], + [['っっっ', true], ['', [3]]] + ]; + + for (const [[text, fullCollapse], [expected, expectedSourceMapping]] of data) { + const sourceMap = new TextSourceMap2(text); + const actual1 = jp.collapseEmphaticSequences(text, fullCollapse, null); + const actual2 = jp.collapseEmphaticSequences(text, fullCollapse, sourceMap); + assert.strictEqual(actual1, expected); + assert.strictEqual(actual2, expected); + if (typeof expectedSourceMapping !== 'undefined') { + assert.ok(sourceMap.equals(new TextSourceMap2(text, expectedSourceMapping))); + } + } +} + +/** */ +function testIsMoraPitchHigh() { + /** @type {[input: [moraIndex: number, pitchAccentDownstepPosition: number], expected: boolean][]} */ + const data = [ + [[0, 0], false], + [[1, 0], true], + [[2, 0], true], + [[3, 0], true], + + [[0, 1], true], + [[1, 1], false], + [[2, 1], false], + [[3, 1], false], + + [[0, 2], false], + [[1, 2], true], + [[2, 2], false], + [[3, 2], false], + + [[0, 3], false], + [[1, 3], true], + [[2, 3], true], + [[3, 3], false], + + [[0, 4], false], + [[1, 4], true], + [[2, 4], true], + [[3, 4], true] + ]; + + for (const [[moraIndex, pitchAccentDownstepPosition], expected] of data) { + const actual = jp.isMoraPitchHigh(moraIndex, pitchAccentDownstepPosition); + assert.strictEqual(actual, expected); + } +} + +/** */ +function testGetKanaMorae() { + /** @type {[text: string, expected: string[]][]} */ + const data = [ + ['かこ', ['か', 'こ']], + ['かっこ', ['か', 'っ', 'こ']], + ['カコ', ['カ', 'コ']], + ['カッコ', ['カ', 'ッ', 'コ']], + ['コート', ['コ', 'ー', 'ト']], + ['ちゃんと', ['ちゃ', 'ん', 'と']], + ['とうきょう', ['と', 'う', 'きょ', 'う']], + ['ぎゅう', ['ぎゅ', 'う']], + ['ディスコ', ['ディ', 'ス', 'コ']] + ]; + + for (const [text, expected] of data) { + const actual = jp.getKanaMorae(text); + vm.assert.deepStrictEqual(actual, expected); + } +} + + +/** */ +function main() { + testIsCodePointKanji(); + testIsCodePointKana(); + testIsCodePointJapanese(); + testIsStringEntirelyKana(); + testIsStringPartiallyJapanese(); + testConvertKatakanaToHiragana(); + testConvertHiraganaToKatakana(); + testConvertToRomaji(); + testConvertNumericToFullWidth(); + testConvertHalfWidthKanaToFullWidth(); + testConvertAlphabeticToKana(); + testDistributeFurigana(); + testDistributeFuriganaInflected(); + testCollapseEmphaticSequences(); + testIsMoraPitchHigh(); + testGetKanaMorae(); +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-json-schema.js b/test/test-json-schema.js new file mode 100644 index 00000000..f9cb023c --- /dev/null +++ b/test/test-json-schema.js @@ -0,0 +1,1048 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const assert = require('assert'); +const {testMain} = require('../dev/util'); +const {VM} = require('../dev/vm'); + +const vm = new VM(); +vm.execute([ + 'js/core.js', + 'js/core/extension-error.js', + 'js/general/cache-map.js', + 'js/data/json-schema.js' +]); +/** @type {typeof JsonSchema} */ +const JsonSchema2 = vm.getSingle('JsonSchema'); + + +/** + * @param {import('json-schema').Schema} schema + * @param {unknown} value + * @returns {boolean} + */ +function schemaValidate(schema, value) { + return new JsonSchema2(schema).isValid(value); +} + +/** + * @param {import('json-schema').Schema} schema + * @param {unknown} value + * @returns {import('json-schema').Value} + */ +function getValidValueOrDefault(schema, value) { + return new JsonSchema2(schema).getValidValueOrDefault(value); +} + +/** + * @param {import('json-schema').Schema} schema + * @param {import('json-schema').Value} value + * @returns {import('json-schema').Value} + */ +function createProxy(schema, value) { + return new JsonSchema2(schema).createProxy(value); +} + +/** + * @template T + * @param {T} value + * @returns {T} + */ +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + + +/** */ +function testValidate1() { + /** @type {import('json-schema').Schema} */ + const schema = { + allOf: [ + { + type: 'number' + }, + { + anyOf: [ + {minimum: 10, maximum: 100}, + {minimum: -100, maximum: -10} + ] + }, + { + oneOf: [ + {multipleOf: 3}, + {multipleOf: 5} + ] + }, + { + not: { + anyOf: [ + {multipleOf: 20} + ] + } + } + ] + }; + + /** + * @param {number} value + * @returns {boolean} + */ + const jsValidate = (value) => { + return ( + typeof value === 'number' && + ( + (value >= 10 && value <= 100) || + (value >= -100 && value <= -10) + ) && + ( + ( + (value % 3) === 0 || + (value % 5) === 0 + ) && + (value % 15) !== 0 + ) && + (value % 20) !== 0 + ); + }; + + for (let i = -111; i <= 111; i++) { + const actual = schemaValidate(schema, i); + const expected = jsValidate(i); + assert.strictEqual(actual, expected, `i=${i}; expected=${expected}; actual=${actual}`); + } +} + +/** */ +function testValidate2() { + /** @type {{schema: import('json-schema').Schema, inputs: {expected: boolean, value: unknown}[]}[]} */ + const data = [ + // String tests + { + schema: { + type: 'string' + }, + inputs: [ + {expected: false, value: null}, + {expected: false, value: void 0}, + {expected: false, value: 0}, + {expected: false, value: {}}, + {expected: false, value: []}, + {expected: true, value: ''} + ] + }, + { + schema: { + type: 'string', + minLength: 2 + }, + inputs: [ + {expected: false, value: ''}, + {expected: false, value: '1'}, + {expected: true, value: '12'}, + {expected: true, value: '123'} + ] + }, + { + schema: { + type: 'string', + maxLength: 2 + }, + inputs: [ + {expected: true, value: ''}, + {expected: true, value: '1'}, + {expected: true, value: '12'}, + {expected: false, value: '123'} + ] + }, + { + schema: { + type: 'string', + pattern: 'test' + }, + inputs: [ + {expected: false, value: ''}, + {expected: true, value: 'test'}, + {expected: false, value: 'TEST'}, + {expected: true, value: 'ABCtestDEF'}, + {expected: false, value: 'ABCTESTDEF'} + ] + }, + { + schema: { + type: 'string', + pattern: '^test$' + }, + inputs: [ + {expected: false, value: ''}, + {expected: true, value: 'test'}, + {expected: false, value: 'TEST'}, + {expected: false, value: 'ABCtestDEF'}, + {expected: false, value: 'ABCTESTDEF'} + ] + }, + { + schema: { + type: 'string', + pattern: '^test$', + patternFlags: 'i' + }, + inputs: [ + {expected: false, value: ''}, + {expected: true, value: 'test'}, + {expected: true, value: 'TEST'}, + {expected: false, value: 'ABCtestDEF'}, + {expected: false, value: 'ABCTESTDEF'} + ] + }, + { + schema: { + type: 'string', + pattern: '*' + }, + inputs: [ + {expected: false, value: ''} + ] + }, + { + schema: { + type: 'string', + pattern: '.', + patternFlags: '?' + }, + inputs: [ + {expected: false, value: ''} + ] + }, + + // Const tests + { + schema: { + const: 32 + }, + inputs: [ + {expected: true, value: 32}, + {expected: false, value: 0}, + {expected: false, value: '32'}, + {expected: false, value: null}, + {expected: false, value: {a: 'b'}}, + {expected: false, value: [1, 2, 3]} + ] + }, + { + schema: { + const: '32' + }, + inputs: [ + {expected: false, value: 32}, + {expected: false, value: 0}, + {expected: true, value: '32'}, + {expected: false, value: null}, + {expected: false, value: {a: 'b'}}, + {expected: false, value: [1, 2, 3]} + ] + }, + { + schema: { + const: null + }, + inputs: [ + {expected: false, value: 32}, + {expected: false, value: 0}, + {expected: false, value: '32'}, + {expected: true, value: null}, + {expected: false, value: {a: 'b'}}, + {expected: false, value: [1, 2, 3]} + ] + }, + { + schema: { + const: {a: 'b'} + }, + inputs: [ + {expected: false, value: 32}, + {expected: false, value: 0}, + {expected: false, value: '32'}, + {expected: false, value: null}, + {expected: false, value: {a: 'b'}}, + {expected: false, value: [1, 2, 3]} + ] + }, + { + schema: { + const: [1, 2, 3] + }, + inputs: [ + {expected: false, value: 32}, + {expected: false, value: 0}, + {expected: false, value: '32'}, + {expected: false, value: null}, + {expected: false, value: {a: 'b'}}, + {expected: false, value: [1, 2, 3]} + ] + }, + + // Array contains tests + { + schema: { + type: 'array', + contains: {const: 32} + }, + inputs: [ + {expected: false, value: []}, + {expected: true, value: [32]}, + {expected: true, value: [1, 32]}, + {expected: true, value: [1, 32, 1]}, + {expected: false, value: [33]}, + {expected: false, value: [1, 33]}, + {expected: false, value: [1, 33, 1]} + ] + }, + + // Number limits tests + { + schema: { + type: 'number', + minimum: 0 + }, + inputs: [ + {expected: false, value: -1}, + {expected: true, value: 0}, + {expected: true, value: 1} + ] + }, + { + schema: { + type: 'number', + exclusiveMinimum: 0 + }, + inputs: [ + {expected: false, value: -1}, + {expected: false, value: 0}, + {expected: true, value: 1} + ] + }, + { + schema: { + type: 'number', + maximum: 0 + }, + inputs: [ + {expected: true, value: -1}, + {expected: true, value: 0}, + {expected: false, value: 1} + ] + }, + { + schema: { + type: 'number', + exclusiveMaximum: 0 + }, + inputs: [ + {expected: true, value: -1}, + {expected: false, value: 0}, + {expected: false, value: 1} + ] + }, + + // Integer limits tests + { + schema: { + type: 'integer', + minimum: 0 + }, + inputs: [ + {expected: false, value: -1}, + {expected: true, value: 0}, + {expected: true, value: 1} + ] + }, + { + schema: { + type: 'integer', + exclusiveMinimum: 0 + }, + inputs: [ + {expected: false, value: -1}, + {expected: false, value: 0}, + {expected: true, value: 1} + ] + }, + { + schema: { + type: 'integer', + maximum: 0 + }, + inputs: [ + {expected: true, value: -1}, + {expected: true, value: 0}, + {expected: false, value: 1} + ] + }, + { + schema: { + type: 'integer', + exclusiveMaximum: 0 + }, + inputs: [ + {expected: true, value: -1}, + {expected: false, value: 0}, + {expected: false, value: 1} + ] + }, + { + schema: { + type: 'integer', + multipleOf: 2 + }, + inputs: [ + {expected: true, value: -2}, + {expected: false, value: -1}, + {expected: true, value: 0}, + {expected: false, value: 1}, + {expected: true, value: 2} + ] + }, + + // Numeric type tests + { + schema: { + type: 'number' + }, + inputs: [ + {expected: true, value: 0}, + {expected: true, value: 0.5}, + {expected: true, value: 1}, + {expected: false, value: '0'}, + {expected: false, value: null}, + {expected: false, value: []}, + {expected: false, value: {}} + ] + }, + { + schema: { + type: 'integer' + }, + inputs: [ + {expected: true, value: 0}, + {expected: false, value: 0.5}, + {expected: true, value: 1}, + {expected: false, value: '0'}, + {expected: false, value: null}, + {expected: false, value: []}, + {expected: false, value: {}} + ] + }, + + // Reference tests + { + schema: { + definitions: { + example: { + type: 'number' + } + }, + $ref: '#/definitions/example' + }, + inputs: [ + {expected: true, value: 0}, + {expected: true, value: 0.5}, + {expected: true, value: 1}, + {expected: false, value: '0'}, + {expected: false, value: null}, + {expected: false, value: []}, + {expected: false, value: {}} + ] + }, + { + schema: { + definitions: { + example: { + type: 'integer' + } + }, + $ref: '#/definitions/example' + }, + inputs: [ + {expected: true, value: 0}, + {expected: false, value: 0.5}, + {expected: true, value: 1}, + {expected: false, value: '0'}, + {expected: false, value: null}, + {expected: false, value: []}, + {expected: false, value: {}} + ] + }, + { + schema: { + definitions: { + example: { + type: 'object', + additionalProperties: false, + properties: { + test: { + $ref: '#/definitions/example' + } + } + } + }, + $ref: '#/definitions/example' + }, + inputs: [ + {expected: false, value: 0}, + {expected: false, value: 0.5}, + {expected: false, value: 1}, + {expected: false, value: '0'}, + {expected: false, value: null}, + {expected: false, value: []}, + {expected: true, value: {}}, + {expected: false, value: {test: 0}}, + {expected: false, value: {test: 0.5}}, + {expected: false, value: {test: 1}}, + {expected: false, value: {test: '0'}}, + {expected: false, value: {test: null}}, + {expected: false, value: {test: []}}, + {expected: true, value: {test: {}}}, + {expected: true, value: {test: {test: {}}}}, + {expected: true, value: {test: {test: {test: {}}}}} + ] + } + ]; + + for (const {schema, inputs} of data) { + for (const {expected, value} of inputs) { + const actual = schemaValidate(schema, value); + assert.strictEqual(actual, expected); + } + } +} + + +/** */ +function testGetValidValueOrDefault1() { + /** @type {{schema: import('json-schema').Schema, inputs: [value: unknown, expected: unknown][]}[]} */ + const data = [ + // Test value defaulting on objects with additionalProperties=false + { + schema: { + type: 'object', + required: ['test'], + properties: { + test: { + type: 'string', + default: 'default' + } + }, + additionalProperties: false + }, + inputs: [ + [ + void 0, + {test: 'default'} + ], + [ + null, + {test: 'default'} + ], + [ + 0, + {test: 'default'} + ], + [ + '', + {test: 'default'} + ], + [ + [], + {test: 'default'} + ], + [ + {}, + {test: 'default'} + ], + [ + {test: 'value'}, + {test: 'value'} + ], + [ + {test2: 'value2'}, + {test: 'default'} + ], + [ + {test: 'value', test2: 'value2'}, + {test: 'value'} + ] + ] + }, + + // Test value defaulting on objects with additionalProperties=true + { + schema: { + type: 'object', + required: ['test'], + properties: { + test: { + type: 'string', + default: 'default' + } + }, + additionalProperties: true + }, + inputs: [ + [ + {}, + {test: 'default'} + ], + [ + {test: 'value'}, + {test: 'value'} + ], + [ + {test2: 'value2'}, + {test: 'default', test2: 'value2'} + ], + [ + {test: 'value', test2: 'value2'}, + {test: 'value', test2: 'value2'} + ] + ] + }, + + // Test value defaulting on objects with additionalProperties={schema} + { + schema: { + type: 'object', + required: ['test'], + properties: { + test: { + type: 'string', + default: 'default' + } + }, + additionalProperties: { + type: 'number', + default: 10 + } + }, + inputs: [ + [ + {}, + {test: 'default'} + ], + [ + {test: 'value'}, + {test: 'value'} + ], + [ + {test2: 'value2'}, + {test: 'default', test2: 10} + ], + [ + {test: 'value', test2: 'value2'}, + {test: 'value', test2: 10} + ], + [ + {test2: 2}, + {test: 'default', test2: 2} + ], + [ + {test: 'value', test2: 2}, + {test: 'value', test2: 2} + ], + [ + {test: 'value', test2: 2, test3: null}, + {test: 'value', test2: 2, test3: 10} + ], + [ + {test: 'value', test2: 2, test3: void 0}, + {test: 'value', test2: 2, test3: 10} + ] + ] + }, + + // Test value defaulting where hasOwnProperty is false + { + schema: { + type: 'object', + required: ['test'], + properties: { + test: { + type: 'string', + default: 'default' + } + } + }, + inputs: [ + [ + {}, + {test: 'default'} + ], + [ + {test: 'value'}, + {test: 'value'} + ], + [ + Object.create({test: 'value'}), + {test: 'default'} + ] + ] + }, + { + schema: { + type: 'object', + required: ['toString'], + properties: { + toString: /** @type {import('json-schema').SchemaObject} */ ({ + type: 'string', + default: 'default' + }) + } + }, + inputs: [ + [ + {}, + {toString: 'default'} + ], + [ + {toString: 'value'}, + {toString: 'value'} + ], + [ + Object.create({toString: 'value'}), + {toString: 'default'} + ] + ] + }, + + // Test enum + { + schema: { + type: 'object', + required: ['test'], + properties: { + test: { + type: 'string', + default: 'value1', + enum: ['value1', 'value2', 'value3'] + } + } + }, + inputs: [ + [ + {test: 'value1'}, + {test: 'value1'} + ], + [ + {test: 'value2'}, + {test: 'value2'} + ], + [ + {test: 'value3'}, + {test: 'value3'} + ], + [ + {test: 'value4'}, + {test: 'value1'} + ] + ] + }, + + // Test valid vs invalid default + { + schema: { + type: 'object', + required: ['test'], + properties: { + test: { + type: 'integer', + default: 2, + minimum: 1 + } + } + }, + inputs: [ + [ + {test: -1}, + {test: 2} + ] + ] + }, + { + schema: { + type: 'object', + required: ['test'], + properties: { + test: { + type: 'integer', + default: 1, + minimum: 2 + } + } + }, + inputs: [ + [ + {test: -1}, + {test: -1} + ] + ] + }, + + // Test references + { + schema: { + definitions: { + example: { + type: 'number', + default: 0 + } + }, + $ref: '#/definitions/example' + }, + inputs: [ + [ + 1, + 1 + ], + [ + null, + 0 + ], + [ + 'test', + 0 + ], + [ + {test: 'value'}, + 0 + ] + ] + }, + { + schema: { + definitions: { + example: { + type: 'object', + additionalProperties: false, + properties: { + test: { + $ref: '#/definitions/example' + } + } + } + }, + $ref: '#/definitions/example' + }, + inputs: [ + [ + 1, + {} + ], + [ + null, + {} + ], + [ + 'test', + {} + ], + [ + {}, + {} + ], + [ + {test: {}}, + {test: {}} + ], + [ + {test: 'value'}, + {test: {}} + ], + [ + {test: {test: {}}}, + {test: {test: {}}} + ] + ] + } + ]; + + for (const {schema, inputs} of data) { + for (const [value, expected] of inputs) { + const actual = getValidValueOrDefault(schema, value); + vm.assert.deepStrictEqual(actual, expected); + } + } +} + + +/** */ +function testProxy1() { + /** @type {{schema: import('json-schema').Schema, tests: {error: boolean, value?: import('json-schema').Value, action: (value: import('core').SafeAny) => void}[]}[]} */ + const data = [ + // Object tests + { + schema: { + type: 'object', + required: ['test'], + additionalProperties: false, + properties: { + test: { + type: 'string', + default: 'default' + } + } + }, + tests: [ + {error: false, value: {test: 'default'}, action: (value) => { value.test = 'string'; }}, + {error: true, value: {test: 'default'}, action: (value) => { value.test = null; }}, + {error: true, value: {test: 'default'}, action: (value) => { delete value.test; }}, + {error: true, value: {test: 'default'}, action: (value) => { value.test2 = 'string'; }}, + {error: false, value: {test: 'default'}, action: (value) => { delete value.test2; }} + ] + }, + { + schema: { + type: 'object', + required: ['test'], + additionalProperties: true, + properties: { + test: { + type: 'string', + default: 'default' + } + } + }, + tests: [ + {error: false, value: {test: 'default'}, action: (value) => { value.test = 'string'; }}, + {error: true, value: {test: 'default'}, action: (value) => { value.test = null; }}, + {error: true, value: {test: 'default'}, action: (value) => { delete value.test; }}, + {error: false, value: {test: 'default'}, action: (value) => { value.test2 = 'string'; }}, + {error: false, value: {test: 'default'}, action: (value) => { delete value.test2; }} + ] + }, + { + schema: { + type: 'object', + required: ['test1'], + additionalProperties: false, + properties: { + test1: { + type: 'object', + required: ['test2'], + additionalProperties: false, + properties: { + test2: { + type: 'object', + required: ['test3'], + additionalProperties: false, + properties: { + test3: { + type: 'string', + default: 'default' + } + } + } + } + } + } + }, + tests: [ + {error: false, action: (value) => { value.test1.test2.test3 = 'string'; }}, + {error: true, action: (value) => { value.test1.test2.test3 = null; }}, + {error: true, action: (value) => { delete value.test1.test2.test3; }}, + {error: true, action: (value) => { value.test1.test2 = null; }}, + {error: true, action: (value) => { value.test1 = null; }}, + {error: true, action: (value) => { value.test4 = 'string'; }}, + {error: false, action: (value) => { delete value.test4; }} + ] + }, + + // Array tests + { + schema: { + type: 'array', + items: { + type: 'string', + default: 'default' + } + }, + tests: [ + {error: false, value: ['default'], action: (value) => { value[0] = 'string'; }}, + {error: true, value: ['default'], action: (value) => { value[0] = null; }}, + {error: false, value: ['default'], action: (value) => { delete value[0]; }}, + {error: false, value: ['default'], action: (value) => { value[1] = 'string'; }}, + {error: false, value: ['default'], action: (value) => { + value[1] = 'string'; + if (value.length !== 2) { throw new Error(`Invalid length; expected=2; actual=${value.length}`); } + if (typeof value.push !== 'function') { throw new Error(`Invalid push; expected=function; actual=${typeof value.push}`); } + }} + ] + }, + + // Reference tests + { + schema: { + definitions: { + example: { + type: 'object', + additionalProperties: false, + properties: { + test: { + $ref: '#/definitions/example' + } + } + } + }, + $ref: '#/definitions/example' + }, + tests: [ + {error: false, value: {}, action: (value) => { value.test = {}; }}, + {error: false, value: {}, action: (value) => { value.test = {}; value.test.test = {}; }}, + {error: false, value: {}, action: (value) => { value.test = {test: {}}; }}, + {error: true, value: {}, action: (value) => { value.test = null; }}, + {error: true, value: {}, action: (value) => { value.test = 'string'; }}, + {error: true, value: {}, action: (value) => { value.test = {}; value.test.test = 'string'; }}, + {error: true, value: {}, action: (value) => { value.test = {test: 'string'}; }} + ] + } + ]; + + for (const {schema, tests} of data) { + for (let {error, value, action} of tests) { + if (typeof value === 'undefined') { value = getValidValueOrDefault(schema, void 0); } + value = clone(value); + assert.ok(schemaValidate(schema, value)); + const valueProxy = createProxy(schema, value); + if (error) { + assert.throws(() => action(valueProxy)); + } else { + assert.doesNotThrow(() => action(valueProxy)); + } + } + } +} + + +/** */ +function main() { + testValidate1(); + testValidate2(); + testGetValidValueOrDefault1(); + testProxy1(); +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-manifest.js b/test/test-manifest.js new file mode 100644 index 00000000..32a498e1 --- /dev/null +++ b/test/test-manifest.js @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const {testMain} = require('../dev/util'); +const {ManifestUtil} = require('../dev/manifest-util'); + + +/** + * @returns {string} + */ +function loadManifestString() { + const manifestPath = path.join(__dirname, '..', 'ext', 'manifest.json'); + return fs.readFileSync(manifestPath, {encoding: 'utf8'}); +} + +/** */ +function validateManifest() { + const manifestUtil = new ManifestUtil(); + const manifest1 = loadManifestString(); + const manifest2 = ManifestUtil.createManifestString(manifestUtil.getManifest()); + assert.strictEqual(manifest1, manifest2, 'Manifest data does not match.'); +} + + +/** */ +function main() { + validateManifest(); +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-object-property-accessor.js b/test/test-object-property-accessor.js new file mode 100644 index 00000000..8826d6a9 --- /dev/null +++ b/test/test-object-property-accessor.js @@ -0,0 +1,458 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const assert = require('assert'); +const {testMain} = require('../dev/util'); +const {VM} = require('../dev/vm'); + +const vm = new VM({}); +vm.execute('js/general/object-property-accessor.js'); +/** @type {typeof ObjectPropertyAccessor} */ +const ObjectPropertyAccessor2 = vm.getSingle('ObjectPropertyAccessor'); + + +/** + * @returns {import('core').UnknownObject} + */ +function createTestObject() { + return { + 0: null, + value1: { + value2: {}, + value3: [], + value4: null + }, + value5: [ + {}, + [], + null + ] + }; +} + + +/** */ +function testGet1() { + /** @type {[pathArray: (string|number)[], getExpected: (object: import('core').SafeAny) => unknown][]} */ + const data = [ + [[], (object) => object], + [['0'], (object) => object['0']], + [['value1'], (object) => object.value1], + [['value1', 'value2'], (object) => object.value1.value2], + [['value1', 'value3'], (object) => object.value1.value3], + [['value1', 'value4'], (object) => object.value1.value4], + [['value5'], (object) => object.value5], + [['value5', 0], (object) => object.value5[0]], + [['value5', 1], (object) => object.value5[1]], + [['value5', 2], (object) => object.value5[2]] + ]; + + for (const [pathArray, getExpected] of data) { + const object = createTestObject(); + const accessor = new ObjectPropertyAccessor2(object); + const expected = getExpected(object); + + assert.strictEqual(accessor.get(pathArray), expected); + } +} + +/** */ +function testGet2() { + const object = createTestObject(); + const accessor = new ObjectPropertyAccessor2(object); + + /** @type {[pathArray: (string|number)[], message: string][]} */ + const data = [ + [[0], 'Invalid path: [0]'], + [['0', 'invalid'], 'Invalid path: ["0"].invalid'], + [['invalid'], 'Invalid path: invalid'], + [['value1', 'invalid'], 'Invalid path: value1.invalid'], + [['value1', 'value2', 'invalid'], 'Invalid path: value1.value2.invalid'], + [['value1', 'value2', 0], 'Invalid path: value1.value2[0]'], + [['value1', 'value3', 'invalid'], 'Invalid path: value1.value3.invalid'], + [['value1', 'value3', 0], 'Invalid path: value1.value3[0]'], + [['value1', 'value4', 'invalid'], 'Invalid path: value1.value4.invalid'], + [['value1', 'value4', 0], 'Invalid path: value1.value4[0]'], + [['value5', 'length'], 'Invalid path: value5.length'], + [['value5', 0, 'invalid'], 'Invalid path: value5[0].invalid'], + [['value5', 0, 0], 'Invalid path: value5[0][0]'], + [['value5', 1, 'invalid'], 'Invalid path: value5[1].invalid'], + [['value5', 1, 0], 'Invalid path: value5[1][0]'], + [['value5', 2, 'invalid'], 'Invalid path: value5[2].invalid'], + [['value5', 2, 0], 'Invalid path: value5[2][0]'], + [['value5', 2, 0, 'invalid'], 'Invalid path: value5[2][0]'], + [['value5', 2.5], 'Invalid index'] + ]; + + for (const [pathArray, message] of data) { + assert.throws(() => accessor.get(pathArray), {message}); + } +} + + +/** */ +function testSet1() { + const testValue = {}; + /** @type {(string|number)[][]} */ + const data = [ + ['0'], + ['value1', 'value2'], + ['value1', 'value3'], + ['value1', 'value4'], + ['value1'], + ['value5', 0], + ['value5', 1], + ['value5', 2], + ['value5'] + ]; + + for (const pathArray of data) { + const object = createTestObject(); + const accessor = new ObjectPropertyAccessor2(object); + + accessor.set(pathArray, testValue); + assert.strictEqual(accessor.get(pathArray), testValue); + } +} + +/** */ +function testSet2() { + const object = createTestObject(); + const accessor = new ObjectPropertyAccessor2(object); + + const testValue = {}; + /** @type {[pathArray: (string|number)[], message: string][]} */ + const data = [ + [[], 'Invalid path'], + [[0], 'Invalid path: [0]'], + [['0', 'invalid'], 'Invalid path: ["0"].invalid'], + [['value1', 'value2', 0], 'Invalid path: value1.value2[0]'], + [['value1', 'value3', 'invalid'], 'Invalid path: value1.value3.invalid'], + [['value1', 'value4', 'invalid'], 'Invalid path: value1.value4.invalid'], + [['value1', 'value4', 0], 'Invalid path: value1.value4[0]'], + [['value5', 1, 'invalid'], 'Invalid path: value5[1].invalid'], + [['value5', 2, 'invalid'], 'Invalid path: value5[2].invalid'], + [['value5', 2, 0], 'Invalid path: value5[2][0]'], + [['value5', 2, 0, 'invalid'], 'Invalid path: value5[2][0]'], + [['value5', 2.5], 'Invalid index'] + ]; + + for (const [pathArray, message] of data) { + assert.throws(() => accessor.set(pathArray, testValue), {message}); + } +} + + +/** */ +function testDelete1() { + /** + * @param {unknown} object + * @param {string} property + * @returns {boolean} + */ + const hasOwn = (object, property) => Object.prototype.hasOwnProperty.call(object, property); + + /** @type {[pathArray: (string|number)[], validate: (object: import('core').SafeAny) => boolean][]} */ + const data = [ + [['0'], (object) => !hasOwn(object, '0')], + [['value1', 'value2'], (object) => !hasOwn(object.value1, 'value2')], + [['value1', 'value3'], (object) => !hasOwn(object.value1, 'value3')], + [['value1', 'value4'], (object) => !hasOwn(object.value1, 'value4')], + [['value1'], (object) => !hasOwn(object, 'value1')], + [['value5'], (object) => !hasOwn(object, 'value5')] + ]; + + for (const [pathArray, validate] of data) { + const object = createTestObject(); + const accessor = new ObjectPropertyAccessor2(object); + + accessor.delete(pathArray); + assert.ok(validate(object)); + } +} + +/** */ +function testDelete2() { + /** @type {[pathArray: (string|number)[], message: string][]} */ + const data = [ + [[], 'Invalid path'], + [[0], 'Invalid path: [0]'], + [['0', 'invalid'], 'Invalid path: ["0"].invalid'], + [['value1', 'value2', 0], 'Invalid path: value1.value2[0]'], + [['value1', 'value3', 'invalid'], 'Invalid path: value1.value3.invalid'], + [['value1', 'value4', 'invalid'], 'Invalid path: value1.value4.invalid'], + [['value1', 'value4', 0], 'Invalid path: value1.value4[0]'], + [['value5', 1, 'invalid'], 'Invalid path: value5[1].invalid'], + [['value5', 2, 'invalid'], 'Invalid path: value5[2].invalid'], + [['value5', 2, 0], 'Invalid path: value5[2][0]'], + [['value5', 2, 0, 'invalid'], 'Invalid path: value5[2][0]'], + [['value5', 2.5], 'Invalid index'], + [['value5', 0], 'Invalid type'], + [['value5', 1], 'Invalid type'], + [['value5', 2], 'Invalid type'] + ]; + + for (const [pathArray, message] of data) { + const object = createTestObject(); + const accessor = new ObjectPropertyAccessor2(object); + + assert.throws(() => accessor.delete(pathArray), {message}); + } +} + + +/** */ +function testSwap1() { + /** @type {[pathArray: (string|number)[], compareValues: boolean][]} */ + const data = [ + [['0'], true], + [['value1', 'value2'], true], + [['value1', 'value3'], true], + [['value1', 'value4'], true], + [['value1'], false], + [['value5', 0], true], + [['value5', 1], true], + [['value5', 2], true], + [['value5'], false] + ]; + + for (const [pathArray1, compareValues1] of data) { + for (const [pathArray2, compareValues2] of data) { + const object = createTestObject(); + const accessor = new ObjectPropertyAccessor2(object); + + const value1a = accessor.get(pathArray1); + const value2a = accessor.get(pathArray2); + + accessor.swap(pathArray1, pathArray2); + + if (!compareValues1 || !compareValues2) { continue; } + + const value1b = accessor.get(pathArray1); + const value2b = accessor.get(pathArray2); + + assert.deepStrictEqual(value1a, value2b); + assert.deepStrictEqual(value2a, value1b); + } + } +} + +/** */ +function testSwap2() { + /** @type {[pathArray1: (string|number)[], pathArray2: (string|number)[], checkRevert: boolean, message: string][]} */ + const data = [ + [[], [], false, 'Invalid path 1'], + [['0'], [], false, 'Invalid path 2'], + [[], ['0'], false, 'Invalid path 1'], + [[0], ['0'], false, 'Invalid path 1: [0]'], + [['0'], [0], false, 'Invalid path 2: [0]'] + ]; + + for (const [pathArray1, pathArray2, checkRevert, message] of data) { + const object = createTestObject(); + const accessor = new ObjectPropertyAccessor2(object); + + let value1a; + let value2a; + if (checkRevert) { + try { + value1a = accessor.get(pathArray1); + value2a = accessor.get(pathArray2); + } catch (e) { + // NOP + } + } + + assert.throws(() => accessor.swap(pathArray1, pathArray2), {message}); + + if (!checkRevert) { continue; } + + const value1b = accessor.get(pathArray1); + const value2b = accessor.get(pathArray2); + + assert.deepStrictEqual(value1a, value1b); + assert.deepStrictEqual(value2a, value2b); + } +} + + +/** */ +function testGetPathString1() { + /** @type {[pathArray: (string|number)[], expected: string][]} */ + const data = [ + [[], ''], + [[0], '[0]'], + [['escape\\'], '["escape\\\\"]'], + [['\'quote\''], '["\'quote\'"]'], + [['"quote"'], '["\\"quote\\""]'], + [['part1', 'part2'], 'part1.part2'], + [['part1', 'part2', 3], 'part1.part2[3]'], + [['part1', 'part2', '3'], 'part1.part2["3"]'], + [['part1', 'part2', '3part'], 'part1.part2["3part"]'], + [['part1', 'part2', '3part', 'part4'], 'part1.part2["3part"].part4'], + [['part1', 'part2', '3part', '4part'], 'part1.part2["3part"]["4part"]'] + ]; + + for (const [pathArray, expected] of data) { + assert.strictEqual(ObjectPropertyAccessor2.getPathString(pathArray), expected); + } +} + +/** */ +function testGetPathString2() { + /** @type {[pathArray: unknown[], message: string][]} */ + const data = [ + [[1.5], 'Invalid index'], + [[null], 'Invalid type: object'] + ]; + + for (const [pathArray, message] of data) { + // @ts-ignore - Throwing is expected + assert.throws(() => ObjectPropertyAccessor2.getPathString(pathArray), {message}); + } +} + + +/** */ +function testGetPathArray1() { + /** @type {[pathString: string, pathArray: (string|number)[]][]} */ + const data = [ + ['', []], + ['[0]', [0]], + ['["escape\\\\"]', ['escape\\']], + ['["\'quote\'"]', ['\'quote\'']], + ['["\\"quote\\""]', ['"quote"']], + ['part1.part2', ['part1', 'part2']], + ['part1.part2[3]', ['part1', 'part2', 3]], + ['part1.part2["3"]', ['part1', 'part2', '3']], + ['part1.part2[\'3\']', ['part1', 'part2', '3']], + ['part1.part2["3part"]', ['part1', 'part2', '3part']], + ['part1.part2[\'3part\']', ['part1', 'part2', '3part']], + ['part1.part2["3part"].part4', ['part1', 'part2', '3part', 'part4']], + ['part1.part2[\'3part\'].part4', ['part1', 'part2', '3part', 'part4']], + ['part1.part2["3part"]["4part"]', ['part1', 'part2', '3part', '4part']], + ['part1.part2[\'3part\'][\'4part\']', ['part1', 'part2', '3part', '4part']] + ]; + + for (const [pathString, expected] of data) { + // @ts-ignore + vm.assert.deepStrictEqual(ObjectPropertyAccessor2.getPathArray(pathString), expected); + } +} + +/** */ +function testGetPathArray2() { + /** @type {[pathString: string, message: string][]} */ + const data = [ + ['?', 'Unexpected character: ?'], + ['.', 'Unexpected character: .'], + ['0', 'Unexpected character: 0'], + ['part1.[0]', 'Unexpected character: ['], + ['part1?', 'Unexpected character: ?'], + ['[part1]', 'Unexpected character: p'], + ['[0a]', 'Unexpected character: a'], + ['["part1"x]', 'Unexpected character: x'], + ['[\'part1\'x]', 'Unexpected character: x'], + ['["part1"]x', 'Unexpected character: x'], + ['[\'part1\']x', 'Unexpected character: x'], + ['part1..part2', 'Unexpected character: .'], + + ['[', 'Path not terminated correctly'], + ['part1.', 'Path not terminated correctly'], + ['part1[', 'Path not terminated correctly'], + ['part1["', 'Path not terminated correctly'], + ['part1[\'', 'Path not terminated correctly'], + ['part1[""', 'Path not terminated correctly'], + ['part1[\'\'', 'Path not terminated correctly'], + ['part1[0', 'Path not terminated correctly'], + ['part1[0].', 'Path not terminated correctly'] + ]; + + for (const [pathString, message] of data) { + assert.throws(() => ObjectPropertyAccessor2.getPathArray(pathString), {message}); + } +} + + +/** */ +function testHasProperty() { + /** @type {[object: unknown, property: unknown, expected: boolean][]} */ + const data = [ + [{}, 'invalid', false], + [{}, 0, false], + [{valid: 0}, 'valid', true], + [{null: 0}, null, false], + [[], 'invalid', false], + [[], 0, false], + [[0], 0, true], + [[0], null, false], + ['string', 0, false], + ['string', 'length', false], + ['string', null, false] + ]; + + for (const [object, property, expected] of data) { + // @ts-ignore + assert.strictEqual(ObjectPropertyAccessor2.hasProperty(object, property), expected); + } +} + +/** */ +function testIsValidPropertyType() { + /** @type {[object: unknown, property: unknown, expected: boolean][]} */ + const data = [ + [{}, 'invalid', true], + [{}, 0, false], + [{valid: 0}, 'valid', true], + [{null: 0}, null, false], + [[], 'invalid', false], + [[], 0, true], + [[0], 0, true], + [[0], null, false], + ['string', 0, false], + ['string', 'length', false], + ['string', null, false] + ]; + + for (const [object, property, expected] of data) { + // @ts-ignore + assert.strictEqual(ObjectPropertyAccessor2.isValidPropertyType(object, property), expected); + } +} + + +/** */ +function main() { + testGet1(); + testGet2(); + testSet1(); + testSet2(); + testDelete1(); + testDelete2(); + testSwap1(); + testSwap2(); + testGetPathString1(); + testGetPathString2(); + testGetPathArray1(); + testGetPathArray2(); + testHasProperty(); + testIsValidPropertyType(); +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-profile-conditions-util.js b/test/test-profile-conditions-util.js new file mode 100644 index 00000000..2e6f751f --- /dev/null +++ b/test/test-profile-conditions-util.js @@ -0,0 +1,1136 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const assert = require('assert'); +const {testMain} = require('../dev/util'); +const {VM} = require('../dev/vm'); + + +const vm = new VM({}); +vm.execute([ + 'js/core.js', + 'js/core/extension-error.js', + 'js/general/cache-map.js', + 'js/data/json-schema.js', + 'js/background/profile-conditions-util.js' +]); +/** @type {typeof ProfileConditionsUtil} */ +const ProfileConditionsUtil2 = vm.getSingle('ProfileConditionsUtil'); + + +/** */ +function testNormalizeContext() { + /** @type {{context: import('settings').OptionsContext, expected: import('profile-conditions-util').NormalizedOptionsContext}[]} */ + const data = [ + // Empty + { + context: {index: 0}, + expected: {index: 0, flags: []} + }, + + // Domain normalization + { + context: {depth: 0, url: ''}, + expected: {depth: 0, url: '', flags: []} + }, + { + context: {depth: 0, url: 'http://example.com/'}, + expected: {depth: 0, url: 'http://example.com/', domain: 'example.com', flags: []} + }, + { + context: {depth: 0, url: 'http://example.com:1234/'}, + expected: {depth: 0, url: 'http://example.com:1234/', domain: 'example.com', flags: []} + }, + { + context: {depth: 0, url: 'http://user@example.com:1234/'}, + expected: {depth: 0, url: 'http://user@example.com:1234/', domain: 'example.com', flags: []} + } + ]; + + for (const {context, expected} of data) { + const profileConditionsUtil = new ProfileConditionsUtil2(); + const actual = profileConditionsUtil.normalizeContext(context); + vm.assert.deepStrictEqual(actual, expected); + } +} + +/** */ +function testSchemas() { + /** @type {{conditionGroups: import('settings').ProfileConditionGroup[], expectedSchema?: import('json-schema').Schema, inputs?: {expected: boolean, context: import('settings').OptionsContext}[]}[]} */ + const data = [ + // Empty + { + conditionGroups: [], + expectedSchema: {}, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/'}} + ] + }, + { + conditionGroups: [ + {conditions: []} + ], + expectedSchema: {}, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/'}} + ] + }, + { + conditionGroups: [ + {conditions: []}, + {conditions: []} + ], + expectedSchema: {}, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/'}} + ] + }, + + // popupLevel tests + { + conditionGroups: [ + { + conditions: [ + { + type: 'popupLevel', + operator: 'equal', + value: '0' + } + ] + } + ], + expectedSchema: { + properties: { + depth: {const: 0} + }, + required: ['depth'] + }, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/'}}, + {expected: false, context: {depth: 1, url: 'http://example.com/'}}, + {expected: false, context: {depth: -1, url: 'http://example.com/'}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'popupLevel', + operator: 'notEqual', + value: '0' + } + ] + } + ], + expectedSchema: { + not: { + anyOf: [ + { + properties: { + depth: {const: 0} + }, + required: ['depth'] + } + ] + } + }, + inputs: [ + {expected: false, context: {depth: 0, url: 'http://example.com/'}}, + {expected: true, context: {depth: 1, url: 'http://example.com/'}}, + {expected: true, context: {depth: -1, url: 'http://example.com/'}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'popupLevel', + operator: 'lessThan', + value: '0' + } + ] + } + ], + expectedSchema: { + properties: { + depth: { + type: 'number', + exclusiveMaximum: 0 + } + }, + required: ['depth'] + }, + inputs: [ + {expected: false, context: {depth: 0, url: 'http://example.com/'}}, + {expected: false, context: {depth: 1, url: 'http://example.com/'}}, + {expected: true, context: {depth: -1, url: 'http://example.com/'}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'popupLevel', + operator: 'greaterThan', + value: '0' + } + ] + } + ], + expectedSchema: { + properties: { + depth: { + type: 'number', + exclusiveMinimum: 0 + } + }, + required: ['depth'] + }, + inputs: [ + {expected: false, context: {depth: 0, url: 'http://example.com/'}}, + {expected: true, context: {depth: 1, url: 'http://example.com/'}}, + {expected: false, context: {depth: -1, url: 'http://example.com/'}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'popupLevel', + operator: 'lessThanOrEqual', + value: '0' + } + ] + } + ], + expectedSchema: { + properties: { + depth: { + type: 'number', + maximum: 0 + } + }, + required: ['depth'] + }, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/'}}, + {expected: false, context: {depth: 1, url: 'http://example.com/'}}, + {expected: true, context: {depth: -1, url: 'http://example.com/'}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'popupLevel', + operator: 'greaterThanOrEqual', + value: '0' + } + ] + } + ], + expectedSchema: { + properties: { + depth: { + type: 'number', + minimum: 0 + } + }, + required: ['depth'] + }, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/'}}, + {expected: true, context: {depth: 1, url: 'http://example.com/'}}, + {expected: false, context: {depth: -1, url: 'http://example.com/'}} + ] + }, + + // url tests + { + conditionGroups: [ + { + conditions: [ + { + type: 'url', + operator: 'matchDomain', + value: 'example.com' + } + ] + } + ], + expectedSchema: { + properties: { + domain: { + oneOf: [ + {const: 'example.com'} + ] + } + }, + required: ['domain'] + }, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/'}}, + {expected: false, context: {depth: 0, url: 'http://example1.com/'}}, + {expected: false, context: {depth: 0, url: 'http://example2.com/'}}, + {expected: true, context: {depth: 0, url: 'http://example.com:1234/'}}, + {expected: true, context: {depth: 0, url: 'http://user@example.com:1234/'}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'url', + operator: 'matchDomain', + value: 'example.com, example1.com, example2.com' + } + ] + } + ], + expectedSchema: { + properties: { + domain: { + oneOf: [ + {const: 'example.com'}, + {const: 'example1.com'}, + {const: 'example2.com'} + ] + } + }, + required: ['domain'] + }, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/'}}, + {expected: true, context: {depth: 0, url: 'http://example1.com/'}}, + {expected: true, context: {depth: 0, url: 'http://example2.com/'}}, + {expected: false, context: {depth: 0, url: 'http://example3.com/'}}, + {expected: true, context: {depth: 0, url: 'http://example.com:1234/'}}, + {expected: true, context: {depth: 0, url: 'http://user@example.com:1234/'}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'url', + operator: 'matchRegExp', + value: '^http://example\\d?\\.com/[\\w\\W]*$' + } + ] + } + ], + expectedSchema: { + properties: { + url: { + type: 'string', + pattern: '^http://example\\d?\\.com/[\\w\\W]*$', + patternFlags: 'i' + } + }, + required: ['url'] + }, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/'}}, + {expected: true, context: {depth: 0, url: 'http://example1.com/'}}, + {expected: true, context: {depth: 0, url: 'http://example2.com/'}}, + {expected: true, context: {depth: 0, url: 'http://example3.com/'}}, + {expected: true, context: {depth: 0, url: 'http://example.com/example'}}, + {expected: false, context: {depth: 0, url: 'http://example.com:1234/'}}, + {expected: false, context: {depth: 0, url: 'http://user@example.com:1234/'}}, + {expected: false, context: {depth: 0, url: 'http://example-1.com/'}} + ] + }, + + // modifierKeys tests + { + conditionGroups: [ + { + conditions: [ + { + type: 'modifierKeys', + operator: 'are', + value: '' + } + ] + } + ], + expectedSchema: { + properties: { + modifierKeys: { + type: 'array', + maxItems: 0, + minItems: 0 + } + }, + required: ['modifierKeys'] + }, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'modifierKeys', + operator: 'are', + value: 'alt, shift' + } + ] + } + ], + expectedSchema: { + properties: { + modifierKeys: { + type: 'array', + maxItems: 2, + minItems: 2, + allOf: [ + {contains: {const: 'alt'}}, + {contains: {const: 'shift'}} + ] + } + }, + required: ['modifierKeys'] + }, + inputs: [ + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'modifierKeys', + operator: 'areNot', + value: '' + } + ] + } + ], + expectedSchema: { + not: { + anyOf: [ + { + properties: { + modifierKeys: { + type: 'array', + maxItems: 0, + minItems: 0 + } + }, + required: ['modifierKeys'] + } + ] + } + }, + inputs: [ + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'modifierKeys', + operator: 'areNot', + value: 'alt, shift' + } + ] + } + ], + expectedSchema: { + not: { + anyOf: [ + { + properties: { + modifierKeys: { + type: 'array', + maxItems: 2, + minItems: 2, + allOf: [ + {contains: {const: 'alt'}}, + {contains: {const: 'shift'}} + ] + } + }, + required: ['modifierKeys'] + } + ] + } + }, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'modifierKeys', + operator: 'include', + value: '' + } + ] + } + ], + expectedSchema: { + properties: { + modifierKeys: { + type: 'array', + minItems: 0 + } + }, + required: ['modifierKeys'] + }, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'modifierKeys', + operator: 'include', + value: 'alt, shift' + } + ] + } + ], + expectedSchema: { + properties: { + modifierKeys: { + type: 'array', + minItems: 2, + allOf: [ + {contains: {const: 'alt'}}, + {contains: {const: 'shift'}} + ] + } + }, + required: ['modifierKeys'] + }, + inputs: [ + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'modifierKeys', + operator: 'notInclude', + value: '' + } + ] + } + ], + expectedSchema: { + properties: { + modifierKeys: { + type: 'array' + } + }, + required: ['modifierKeys'] + }, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'modifierKeys', + operator: 'notInclude', + value: 'alt, shift' + } + ] + } + ], + expectedSchema: { + properties: { + modifierKeys: { + type: 'array', + not: { + anyOf: [ + {contains: {const: 'alt'}}, + {contains: {const: 'shift'}} + ] + } + } + }, + required: ['modifierKeys'] + }, + inputs: [ + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} + ] + }, + + // flags tests + { + conditionGroups: [ + { + conditions: [ + { + type: 'flags', + operator: 'are', + value: '' + } + ] + } + ], + expectedSchema: { + required: ['flags'], + properties: { + flags: { + type: 'array', + maxItems: 0, + minItems: 0 + } + } + }, + inputs: [ + {expected: true, context: {depth: 0, url: ''}}, + {expected: true, context: {depth: 0, url: '', flags: []}}, + {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'flags', + operator: 'are', + value: 'clipboard, test2' + } + ] + } + ], + expectedSchema: { + required: ['flags'], + properties: { + flags: { + type: 'array', + maxItems: 2, + minItems: 2, + allOf: [ + {contains: {const: 'clipboard'}}, + {contains: {const: 'test2'}} + ] + } + } + }, + inputs: [ + {expected: false, context: {depth: 0, url: ''}}, + {expected: false, context: {depth: 0, url: '', flags: []}}, + {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'flags', + operator: 'areNot', + value: '' + } + ] + } + ], + expectedSchema: { + not: { + anyOf: [ + { + required: ['flags'], + properties: { + flags: { + type: 'array', + maxItems: 0, + minItems: 0 + } + } + } + ] + } + }, + inputs: [ + {expected: false, context: {depth: 0, url: ''}}, + {expected: false, context: {depth: 0, url: '', flags: []}}, + {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'flags', + operator: 'areNot', + value: 'clipboard, test2' + } + ] + } + ], + expectedSchema: { + not: { + anyOf: [ + { + required: ['flags'], + properties: { + flags: { + type: 'array', + maxItems: 2, + minItems: 2, + allOf: [ + {contains: {const: 'clipboard'}}, + {contains: {const: 'test2'}} + ] + } + } + } + ] + } + }, + inputs: [ + {expected: true, context: {depth: 0, url: ''}}, + {expected: true, context: {depth: 0, url: '', flags: []}}, + {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'flags', + operator: 'include', + value: '' + } + ] + } + ], + expectedSchema: { + required: ['flags'], + properties: { + flags: { + type: 'array', + minItems: 0 + } + } + }, + inputs: [ + {expected: true, context: {depth: 0, url: ''}}, + {expected: true, context: {depth: 0, url: '', flags: []}}, + {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'flags', + operator: 'include', + value: 'clipboard, test2' + } + ] + } + ], + expectedSchema: { + required: ['flags'], + properties: { + flags: { + type: 'array', + minItems: 2, + allOf: [ + {contains: {const: 'clipboard'}}, + {contains: {const: 'test2'}} + ] + } + } + }, + inputs: [ + {expected: false, context: {depth: 0, url: ''}}, + {expected: false, context: {depth: 0, url: '', flags: []}}, + {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'flags', + operator: 'notInclude', + value: '' + } + ] + } + ], + expectedSchema: { + required: ['flags'], + properties: { + flags: { + type: 'array' + } + } + }, + inputs: [ + {expected: true, context: {depth: 0, url: ''}}, + {expected: true, context: {depth: 0, url: '', flags: []}}, + {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'flags', + operator: 'notInclude', + value: 'clipboard, test2' + } + ] + } + ], + expectedSchema: { + required: ['flags'], + properties: { + flags: { + type: 'array', + not: { + anyOf: [ + {contains: {const: 'clipboard'}}, + {contains: {const: 'test2'}} + ] + } + } + } + }, + inputs: [ + {expected: true, context: {depth: 0, url: ''}}, + {expected: true, context: {depth: 0, url: '', flags: []}}, + {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} + ] + }, + + // Multiple conditions tests + { + conditionGroups: [ + { + conditions: [ + { + type: 'popupLevel', + operator: 'greaterThan', + value: '0' + }, + { + type: 'popupLevel', + operator: 'lessThan', + value: '3' + } + ] + } + ], + expectedSchema: { + allOf: [ + { + properties: { + depth: { + type: 'number', + exclusiveMinimum: 0 + } + }, + required: ['depth'] + }, + { + properties: { + depth: { + type: 'number', + exclusiveMaximum: 3 + } + }, + required: ['depth'] + } + ] + }, + inputs: [ + {expected: false, context: {depth: -2, url: 'http://example.com/'}}, + {expected: false, context: {depth: -1, url: 'http://example.com/'}}, + {expected: false, context: {depth: 0, url: 'http://example.com/'}}, + {expected: true, context: {depth: 1, url: 'http://example.com/'}}, + {expected: true, context: {depth: 2, url: 'http://example.com/'}}, + {expected: false, context: {depth: 3, url: 'http://example.com/'}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'popupLevel', + operator: 'greaterThan', + value: '0' + }, + { + type: 'popupLevel', + operator: 'lessThan', + value: '3' + } + ] + }, + { + conditions: [ + { + type: 'popupLevel', + operator: 'equal', + value: '0' + } + ] + } + ], + expectedSchema: { + anyOf: [ + { + allOf: [ + { + properties: { + depth: { + type: 'number', + exclusiveMinimum: 0 + } + }, + required: ['depth'] + }, + { + properties: { + depth: { + type: 'number', + exclusiveMaximum: 3 + } + }, + required: ['depth'] + } + ] + }, + { + properties: { + depth: {const: 0} + }, + required: ['depth'] + } + ] + }, + inputs: [ + {expected: false, context: {depth: -2, url: 'http://example.com/'}}, + {expected: false, context: {depth: -1, url: 'http://example.com/'}}, + {expected: true, context: {depth: 0, url: 'http://example.com/'}}, + {expected: true, context: {depth: 1, url: 'http://example.com/'}}, + {expected: true, context: {depth: 2, url: 'http://example.com/'}}, + {expected: false, context: {depth: 3, url: 'http://example.com/'}} + ] + }, + { + conditionGroups: [ + { + conditions: [ + { + type: 'popupLevel', + operator: 'greaterThan', + value: '0' + }, + { + type: 'popupLevel', + operator: 'lessThan', + value: '3' + } + ] + }, + { + conditions: [ + { + type: 'popupLevel', + operator: 'lessThanOrEqual', + value: '0' + }, + { + type: 'popupLevel', + operator: 'greaterThanOrEqual', + value: '-1' + } + ] + } + ], + expectedSchema: { + anyOf: [ + { + allOf: [ + { + properties: { + depth: { + type: 'number', + exclusiveMinimum: 0 + } + }, + required: ['depth'] + }, + { + properties: { + depth: { + type: 'number', + exclusiveMaximum: 3 + } + }, + required: ['depth'] + } + ] + }, + { + allOf: [ + { + properties: { + depth: { + type: 'number', + maximum: 0 + } + }, + required: ['depth'] + }, + { + properties: { + depth: { + type: 'number', + minimum: -1 + } + }, + required: ['depth'] + } + ] + } + ] + }, + inputs: [ + {expected: false, context: {depth: -2, url: 'http://example.com/'}}, + {expected: true, context: {depth: -1, url: 'http://example.com/'}}, + {expected: true, context: {depth: 0, url: 'http://example.com/'}}, + {expected: true, context: {depth: 1, url: 'http://example.com/'}}, + {expected: true, context: {depth: 2, url: 'http://example.com/'}}, + {expected: false, context: {depth: 3, url: 'http://example.com/'}} + ] + } + ]; + + for (const {conditionGroups, expectedSchema, inputs} of data) { + const profileConditionsUtil = new ProfileConditionsUtil2(); + const schema = profileConditionsUtil.createSchema(conditionGroups); + if (typeof expectedSchema !== 'undefined') { + vm.assert.deepStrictEqual(schema.schema, expectedSchema); + } + if (Array.isArray(inputs)) { + for (const {expected, context} of inputs) { + const normalizedContext = profileConditionsUtil.normalizeContext(context); + const actual = schema.isValid(normalizedContext); + assert.strictEqual(actual, expected); + } + } + } +} + + +/** */ +function main() { + testNormalizeContext(); + testSchemas(); +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-text-source-map.js b/test/test-text-source-map.js new file mode 100644 index 00000000..834a3d07 --- /dev/null +++ b/test/test-text-source-map.js @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const assert = require('assert'); +const {testMain} = require('../dev/util'); +const {VM} = require('../dev/vm'); + +const vm = new VM(); +vm.execute(['js/general/text-source-map.js']); +/** @type {typeof TextSourceMap} */ +const TextSourceMap2 = vm.getSingle('TextSourceMap'); + + +/** */ +function testSource() { + const data = [ + ['source1'], + ['source2'], + ['source3'] + ]; + + for (const [source] of data) { + const sourceMap = new TextSourceMap2(source); + assert.strictEqual(source, sourceMap.source); + } +} + +/** */ +function testEquals() { + /** @type {[args1: [source1: string, mapping1: ?(number[])], args2: [source2: string, mapping2: ?(number[])], expectedEquals: boolean][]} */ + const data = [ + [['source1', null], ['source1', null], true], + [['source2', null], ['source2', null], true], + [['source3', null], ['source3', null], true], + + [['source1', [1, 1, 1, 1, 1, 1, 1]], ['source1', null], true], + [['source2', [1, 1, 1, 1, 1, 1, 1]], ['source2', null], true], + [['source3', [1, 1, 1, 1, 1, 1, 1]], ['source3', null], true], + + [['source1', null], ['source1', [1, 1, 1, 1, 1, 1, 1]], true], + [['source2', null], ['source2', [1, 1, 1, 1, 1, 1, 1]], true], + [['source3', null], ['source3', [1, 1, 1, 1, 1, 1, 1]], true], + + [['source1', [1, 1, 1, 1, 1, 1, 1]], ['source1', [1, 1, 1, 1, 1, 1, 1]], true], + [['source2', [1, 1, 1, 1, 1, 1, 1]], ['source2', [1, 1, 1, 1, 1, 1, 1]], true], + [['source3', [1, 1, 1, 1, 1, 1, 1]], ['source3', [1, 1, 1, 1, 1, 1, 1]], true], + + [['source1', [1, 2, 1, 3]], ['source1', [1, 2, 1, 3]], true], + [['source2', [1, 2, 1, 3]], ['source2', [1, 2, 1, 3]], true], + [['source3', [1, 2, 1, 3]], ['source3', [1, 2, 1, 3]], true], + + [['source1', [1, 3, 1, 2]], ['source1', [1, 2, 1, 3]], false], + [['source2', [1, 3, 1, 2]], ['source2', [1, 2, 1, 3]], false], + [['source3', [1, 3, 1, 2]], ['source3', [1, 2, 1, 3]], false], + + [['source1', [1, 1, 1, 1, 1, 1, 1]], ['source4', [1, 1, 1, 1, 1, 1, 1]], false], + [['source2', [1, 1, 1, 1, 1, 1, 1]], ['source5', [1, 1, 1, 1, 1, 1, 1]], false], + [['source3', [1, 1, 1, 1, 1, 1, 1]], ['source6', [1, 1, 1, 1, 1, 1, 1]], false] + ]; + + for (const [[source1, mapping1], [source2, mapping2], expectedEquals] of data) { + const sourceMap1 = new TextSourceMap2(source1, mapping1); + const sourceMap2 = new TextSourceMap2(source2, mapping2); + assert.ok(sourceMap1.equals(sourceMap1)); + assert.ok(sourceMap2.equals(sourceMap2)); + assert.strictEqual(sourceMap1.equals(sourceMap2), expectedEquals); + } +} + +/** */ +function testGetSourceLength() { + /** @type {[args: [source: string, mapping: number[]], finalLength: number, expectedValue: number][]} */ + const data = [ + [['source', [1, 1, 1, 1, 1, 1]], 1, 1], + [['source', [1, 1, 1, 1, 1, 1]], 2, 2], + [['source', [1, 1, 1, 1, 1, 1]], 3, 3], + [['source', [1, 1, 1, 1, 1, 1]], 4, 4], + [['source', [1, 1, 1, 1, 1, 1]], 5, 5], + [['source', [1, 1, 1, 1, 1, 1]], 6, 6], + + [['source', [2, 2, 2]], 1, 2], + [['source', [2, 2, 2]], 2, 4], + [['source', [2, 2, 2]], 3, 6], + + [['source', [3, 3]], 1, 3], + [['source', [3, 3]], 2, 6], + + [['source', [6, 6]], 1, 6] + ]; + + for (const [[source, mapping], finalLength, expectedValue] of data) { + const sourceMap = new TextSourceMap2(source, mapping); + assert.strictEqual(sourceMap.getSourceLength(finalLength), expectedValue); + } +} + +/** */ +function testCombineInsert() { + /** @type {[args: [source: string, mapping: ?(number[])], expectedArgs: [expectedSource: string, expectedMapping: ?(number[])], operations: [operation: string, arg1: number, arg2: number][]][]} */ + const data = [ + // No operations + [ + ['source', null], + ['source', [1, 1, 1, 1, 1, 1]], + [] + ], + + // Combine + [ + ['source', null], + ['source', [3, 1, 1, 1]], + [ + ['combine', 0, 2] + ] + ], + [ + ['source', null], + ['source', [1, 1, 1, 3]], + [ + ['combine', 3, 2] + ] + ], + [ + ['source', null], + ['source', [3, 3]], + [ + ['combine', 0, 2], + ['combine', 1, 2] + ] + ], + [ + ['source', null], + ['source', [3, 3]], + [ + ['combine', 3, 2], + ['combine', 0, 2] + ] + ], + + // Insert + [ + ['source', null], + ['source', [0, 1, 1, 1, 1, 1, 1]], + [ + ['insert', 0, 0] + ] + ], + [ + ['source', null], + ['source', [1, 1, 1, 1, 1, 1, 0]], + [ + ['insert', 6, 0] + ] + ], + [ + ['source', null], + ['source', [0, 1, 1, 1, 1, 1, 1, 0]], + [ + ['insert', 0, 0], + ['insert', 7, 0] + ] + ], + [ + ['source', null], + ['source', [0, 1, 1, 1, 1, 1, 1, 0]], + [ + ['insert', 6, 0], + ['insert', 0, 0] + ] + ], + + // Mixed + [ + ['source', null], + ['source', [3, 0, 3]], + [ + ['combine', 0, 2], + ['insert', 1, 0], + ['combine', 2, 2] + ] + ], + [ + ['source', null], + ['source', [3, 0, 3]], + [ + ['combine', 0, 2], + ['combine', 1, 2], + ['insert', 1, 0] + ] + ], + [ + ['source', null], + ['source', [3, 0, 3]], + [ + ['insert', 3, 0], + ['combine', 0, 2], + ['combine', 2, 2] + ] + ] + ]; + + for (const [[source, mapping], [expectedSource, expectedMapping], operations] of data) { + const sourceMap = new TextSourceMap2(source, mapping); + const expectedSourceMap = new TextSourceMap2(expectedSource, expectedMapping); + for (const [operation, ...args] of operations) { + switch (operation) { + case 'combine': + sourceMap.combine(...args); + break; + case 'insert': + sourceMap.insert(...args); + break; + } + } + assert.ok(sourceMap.equals(expectedSourceMap)); + } +} + + +/** */ +function main() { + testSource(); + testEquals(); + testGetSourceLength(); + testCombineInsert(); +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-translator.js b/test/test-translator.js new file mode 100644 index 00000000..0c84e0be --- /dev/null +++ b/test/test-translator.js @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const {testMain} = require('../dev/util'); +const {TranslatorVM} = require('../dev/translator-vm'); + + +/** + * @template T + * @param {T} value + * @returns {T} + */ +function clone(value) { + return JSON.parse(JSON.stringify(value)); +} + + +/** */ +async function main() { + const write = (process.argv[2] === '--write'); + + const translatorVM = new TranslatorVM(); + const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', 'valid-dictionary1'); + await translatorVM.prepare(dictionaryDirectory, 'Test Dictionary 2'); + + const testInputsFilePath = path.join(__dirname, 'data', 'translator-test-inputs.json'); + const {optionsPresets, tests} = JSON.parse(fs.readFileSync(testInputsFilePath, {encoding: 'utf8'})); + + const testResults1FilePath = path.join(__dirname, 'data', 'translator-test-results.json'); + const expectedResults1 = JSON.parse(fs.readFileSync(testResults1FilePath, {encoding: 'utf8'})); + const actualResults1 = []; + + const testResults2FilePath = path.join(__dirname, 'data', 'translator-test-results-note-data1.json'); + const expectedResults2 = JSON.parse(fs.readFileSync(testResults2FilePath, {encoding: 'utf8'})); + const actualResults2 = []; + + for (let i = 0, ii = tests.length; i < ii; ++i) { + const test = tests[i]; + const expected1 = expectedResults1[i]; + const expected2 = expectedResults2[i]; + switch (test.func) { + case 'findTerms': + { + const {name, mode, text} = test; + /** @type {import('translation').FindTermsOptions} */ + const options = translatorVM.buildOptions(optionsPresets, test.options); + const {dictionaryEntries, originalTextLength} = clone(await translatorVM.translator.findTerms(mode, text, options)); + const noteDataList = mode !== 'simple' ? clone(dictionaryEntries.map((dictionaryEntry) => translatorVM.createTestAnkiNoteData(clone(dictionaryEntry), mode))) : null; + actualResults1.push({name, originalTextLength, dictionaryEntries}); + actualResults2.push({name, noteDataList}); + if (!write) { + assert.deepStrictEqual(originalTextLength, expected1.originalTextLength); + assert.deepStrictEqual(dictionaryEntries, expected1.dictionaryEntries); + assert.deepStrictEqual(noteDataList, expected2.noteDataList); + } + } + break; + case 'findKanji': + { + const {name, text} = test; + /** @type {import('translation').FindKanjiOptions} */ + const options = translatorVM.buildOptions(optionsPresets, test.options); + const dictionaryEntries = clone(await translatorVM.translator.findKanji(text, options)); + const noteDataList = clone(dictionaryEntries.map((dictionaryEntry) => translatorVM.createTestAnkiNoteData(clone(dictionaryEntry), 'split'))); + actualResults1.push({name, dictionaryEntries}); + actualResults2.push({name, noteDataList}); + if (!write) { + assert.deepStrictEqual(dictionaryEntries, expected1.dictionaryEntries); + assert.deepStrictEqual(noteDataList, expected2.noteDataList); + } + } + break; + } + } + + if (write) { + // Use 2 indent instead of 4 to save a bit of file size + fs.writeFileSync(testResults1FilePath, JSON.stringify(actualResults1, null, 2), {encoding: 'utf8'}); + fs.writeFileSync(testResults2FilePath, JSON.stringify(actualResults2, null, 2), {encoding: 'utf8'}); + } +} + + +if (require.main === module) { testMain(main); } diff --git a/test/test-workers.js b/test/test-workers.js new file mode 100644 index 00000000..3de7ac48 --- /dev/null +++ b/test/test-workers.js @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2023 Yomitan Authors + * Copyright (C) 2020-2022 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 . + */ + +const fs = require('fs'); +const path = require('path'); +const {JSDOM} = require('jsdom'); +const {VM} = require('../dev/vm'); +const assert = require('assert'); + + +class StubClass { + /** */ + prepare() { + // NOP + } +} + + +/** + * @returns {import('core').SafeAny} + */ +function loadEslint() { + return JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.eslintrc.json'), {encoding: 'utf8'})); +} + +/** + * @param {string[]} scriptPaths + * @returns {string[]} + */ +function filterScriptPaths(scriptPaths) { + const extDirName = 'ext'; + return scriptPaths.filter((src) => !src.startsWith('/lib/')).map((src) => `${extDirName}${src}`); +} + +/** + * @param {string} fileName + * @returns {string[]} + */ +function getAllHtmlScriptPaths(fileName) { + const domSource = fs.readFileSync(fileName, {encoding: 'utf8'}); + const dom = new JSDOM(domSource); + const {window} = dom; + const {document} = window; + try { + const scripts = document.querySelectorAll('script'); + return [...scripts].map(({src}) => src); + } finally { + window.close(); + } +} + +/** + * @param {string[]} scripts + */ +function convertBackgroundScriptsToServiceWorkerScripts(scripts) { + // Use parse5-based SimpleDOMParser + scripts.splice(0, 0, '/lib/parse5.js'); + const index = scripts.indexOf('/js/dom/native-simple-dom-parser.js'); + assert.ok(index >= 0); + scripts[index] = '/js/dom/simple-dom-parser.js'; +} + +/** + * @param {string} scriptPath + * @param {import('core').UnknownObject} fields + * @returns {string[]} + */ +function getImportedScripts(scriptPath, fields) { + /** @type {string[]} */ + const importedScripts = []; + + /** + * @param {...string} scripts + */ + const importScripts = (...scripts) => { + importedScripts.push(...scripts); + }; + + const vm = new VM(Object.assign({importScripts}, fields)); + vm.context.self = vm.context; + vm.execute([scriptPath]); + + return importedScripts; +} + +/** */ +function testServiceWorker() { + // Verify that sw.js scripts match background.html scripts + const extDir = path.join(__dirname, '..', 'ext'); + const scripts = getAllHtmlScriptPaths(path.join(extDir, 'background.html')); + convertBackgroundScriptsToServiceWorkerScripts(scripts); + const importedScripts = getImportedScripts('sw.js', {}); + assert.deepStrictEqual(scripts, importedScripts); + + // Verify that eslint config lists files correctly + const expectedSwRulesFiles = filterScriptPaths(scripts); + const eslintConfig = loadEslint(); + const swRules = /** @type {import('core').SafeAny[]} */ (eslintConfig.overrides).find((item) => ( + typeof item.env === 'object' && + item.env !== null && + item.env.serviceworker === true + )); + assert.ok(typeof swRules !== 'undefined'); + assert.ok(Array.isArray(swRules.files)); + assert.deepStrictEqual(swRules.files, expectedSwRulesFiles); +} + +/** */ +function testWorkers() { + testWorker( + 'js/language/dictionary-worker-main.js', + {DictionaryWorkerHandler: StubClass} + ); +} + +/** + * @param {string} scriptPath + * @param {import('core').UnknownObject} fields + */ +function testWorker(scriptPath, fields) { + // Get script paths + const scripts = getImportedScripts(scriptPath, fields); + + // Verify that eslint config lists files correctly + const expectedRulesFiles = filterScriptPaths(scripts); + const expectedRulesFilesSet = new Set(expectedRulesFiles); + const eslintConfig = loadEslint(); + const rules = /** @type {import('core').SafeAny[]} */ (eslintConfig.overrides).find((item) => ( + typeof item.env === 'object' && + item.env !== null && + item.env.worker === true + )); + assert.ok(typeof rules !== 'undefined'); + assert.ok(Array.isArray(rules.files)); + assert.deepStrictEqual(/** @type {import('core').SafeAny[]} */ (rules.files).filter((v) => expectedRulesFilesSet.has(v)), expectedRulesFiles); +} + + +/** */ +function main() { + try { + testServiceWorker(); + testWorkers(); + } catch (e) { + console.error(e); + process.exit(-1); + return; + } + process.exit(0); +} + + +if (require.main === module) { main(); } -- cgit v1.2.3 From d5b1217df3fe7480fc5f58fe194f5bbf73281094 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 19:23:17 -0500 Subject: Use import map --- dev/jsconfig.json | 1 + ext/action-popup.html | 8 ++++++++ ext/background.html | 8 ++++++++ ext/info.html | 8 ++++++++ ext/issues.html | 8 ++++++++ ext/js/background/backend.js | 2 +- ext/js/background/offscreen.js | 2 +- ext/js/display/search-display-controller.js | 2 +- ext/js/display/search-main.js | 2 +- ext/js/dom/simple-dom-parser.js | 2 +- ext/js/language/dictionary-importer.js | 4 ++-- ext/js/pages/settings/backup-controller.js | 2 +- ext/js/pages/settings/popup-preview-frame.js | 2 +- ext/js/templates/sandbox/anki-template-renderer.js | 2 +- ext/js/templates/sandbox/template-renderer-media-provider.js | 2 +- ext/js/templates/sandbox/template-renderer.js | 2 +- ext/legal.html | 8 ++++++++ ext/offscreen.html | 8 ++++++++ ext/permissions.html | 8 ++++++++ ext/popup-preview.html | 8 ++++++++ ext/popup.html | 8 ++++++++ ext/search.html | 8 ++++++++ ext/settings.html | 8 ++++++++ ext/template-renderer.html | 8 ++++++++ ext/welcome.html | 8 ++++++++ jsconfig.json | 3 ++- test/japanese-util.test.js | 2 +- test/jsconfig.json | 1 + 28 files changed, 121 insertions(+), 14 deletions(-) (limited to 'test') diff --git a/dev/jsconfig.json b/dev/jsconfig.json index a012f32f..518f97ad 100644 --- a/dev/jsconfig.json +++ b/dev/jsconfig.json @@ -12,6 +12,7 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { + "zip.js": ["@zip.js/zip.js"], "anki-templates": ["../types/ext/anki-templates"], "anki-templates-internal": ["../types/ext/anki-templates-internal"], "cache-map": ["../types/ext/cache-map"], diff --git a/ext/action-popup.html b/ext/action-popup.html index b60e7055..5c6bfce4 100644 --- a/ext/action-popup.html +++ b/ext/action-popup.html @@ -12,6 +12,14 @@ + diff --git a/ext/background.html b/ext/background.html index dc88f397..6f9ee5f6 100644 --- a/ext/background.html +++ b/ext/background.html @@ -12,6 +12,14 @@ + diff --git a/ext/info.html b/ext/info.html index cb80053d..9e71ffd4 100644 --- a/ext/info.html +++ b/ext/info.html @@ -13,6 +13,14 @@ + diff --git a/ext/issues.html b/ext/issues.html index 904fbf16..c75683dd 100644 --- a/ext/issues.html +++ b/ext/issues.html @@ -13,6 +13,14 @@ + diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index be68ecf4..44f5a42d 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {AccessibilityController} from '../accessibility/accessibility-controller.js'; import {AnkiConnect} from '../comm/anki-connect.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 1b68887b..45345c01 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {ClipboardReader} from '../comm/clipboard-reader.js'; import {invokeMessageHandler} from '../core.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index a9bf2217..b93757c2 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; import {EventListenerCollection, invokeMessageHandler} from '../core.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/display/search-main.js b/ext/js/display/search-main.js index c20cc135..c1445e37 100644 --- a/ext/js/display/search-main.js +++ b/ext/js/display/search-main.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {log} from '../core.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; diff --git a/ext/js/dom/simple-dom-parser.js b/ext/js/dom/simple-dom-parser.js index a1f63890..7ee28d51 100644 --- a/ext/js/dom/simple-dom-parser.js +++ b/ext/js/dom/simple-dom-parser.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as parse5 from '../../lib/parse5.js'; +import * as parse5 from 'parse5'; /** * @augments import('simple-dom-parser').ISimpleDomParser diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index 115e0726..5a4d7257 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -16,14 +16,14 @@ * along with this program. If not, see . */ -import * as ajvSchemas0 from '../../lib/validate-schemas.js'; +import * as ajvSchemas0 from 'validate-schemas'; import { BlobWriter as BlobWriter0, TextWriter as TextWriter0, Uint8ArrayReader as Uint8ArrayReader0, ZipReader as ZipReader0, configure -} from '../../lib/zip.js'; +} from 'zip.js'; import {stringReverse} from '../core.js'; import {MediaUtil} from '../media/media-util.js'; import {ExtensionError} from '../core/extension-error.js'; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 52c5f418..aeff2a97 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Dexie} from '../../../lib/dexie.js'; +import {Dexie} from 'dexie'; import {isObject, log} from '../../core.js'; import {OptionsUtil} from '../../data/options-util.js'; import {ArrayBufferUtil} from '../../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index c1a0d706..bb00829f 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from '../../../lib/wanakana.js'; +import * as wanakana from 'wanakana'; import {Frontend} from '../../app/frontend.js'; import {TextSourceRange} from '../../dom/text-source-range.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js index 9f4bf6ff..b0dc8223 100644 --- a/ext/js/templates/sandbox/anki-template-renderer.js +++ b/ext/js/templates/sandbox/anki-template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from '../../../lib/handlebars.js'; +import {Handlebars} from 'handlebars'; import {AnkiNoteDataCreator} from '../../data/sandbox/anki-note-data-creator.js'; import {PronunciationGenerator} from '../../display/sandbox/pronunciation-generator.js'; import {StructuredContentGenerator} from '../../display/sandbox/structured-content-generator.js'; diff --git a/ext/js/templates/sandbox/template-renderer-media-provider.js b/ext/js/templates/sandbox/template-renderer-media-provider.js index d8a0a16d..23f334e1 100644 --- a/ext/js/templates/sandbox/template-renderer-media-provider.js +++ b/ext/js/templates/sandbox/template-renderer-media-provider.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from '../../../lib/handlebars.js'; +import {Handlebars} from 'handlebars'; export class TemplateRendererMediaProvider { constructor() { diff --git a/ext/js/templates/sandbox/template-renderer.js b/ext/js/templates/sandbox/template-renderer.js index fe240b5f..d4aebd64 100644 --- a/ext/js/templates/sandbox/template-renderer.js +++ b/ext/js/templates/sandbox/template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from '../../../lib/handlebars.js'; +import {Handlebars} from 'handlebars'; import {ExtensionError} from '../../core/extension-error.js'; export class TemplateRenderer { diff --git a/ext/legal.html b/ext/legal.html index 94912c7e..b853f3e8 100644 --- a/ext/legal.html +++ b/ext/legal.html @@ -14,6 +14,14 @@ + diff --git a/ext/offscreen.html b/ext/offscreen.html index afb7eb44..cfab53ee 100644 --- a/ext/offscreen.html +++ b/ext/offscreen.html @@ -12,6 +12,14 @@ + diff --git a/ext/permissions.html b/ext/permissions.html index 61b0d363..7baff14e 100644 --- a/ext/permissions.html +++ b/ext/permissions.html @@ -14,6 +14,14 @@ + diff --git a/ext/popup-preview.html b/ext/popup-preview.html index 15810242..7bd54470 100644 --- a/ext/popup-preview.html +++ b/ext/popup-preview.html @@ -13,6 +13,14 @@ + diff --git a/ext/popup.html b/ext/popup.html index 30e8a8c0..6bc8d690 100644 --- a/ext/popup.html +++ b/ext/popup.html @@ -15,6 +15,14 @@ + diff --git a/ext/search.html b/ext/search.html index 8c595cc4..377a966a 100644 --- a/ext/search.html +++ b/ext/search.html @@ -16,6 +16,14 @@ + diff --git a/ext/settings.html b/ext/settings.html index 346cc1d7..276a7222 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -14,6 +14,14 @@ + diff --git a/ext/template-renderer.html b/ext/template-renderer.html index 116f1a0c..d1747e99 100644 --- a/ext/template-renderer.html +++ b/ext/template-renderer.html @@ -11,6 +11,14 @@ + diff --git a/ext/welcome.html b/ext/welcome.html index 40639881..355bbc5f 100644 --- a/ext/welcome.html +++ b/ext/welcome.html @@ -13,6 +13,14 @@ + diff --git a/jsconfig.json b/jsconfig.json index ace0c2aa..4f316174 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -12,6 +12,7 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { + "zip.js": ["@zip.js/zip.js"], "*": ["./types/ext/*"] }, "types": [ @@ -29,7 +30,7 @@ "include": [ "ext/**/*.js", "types/ext/**/*.ts", - "types/other/web-set-timeout.d.ts" + "types/other/globals.d.ts" ], "exclude": [ "node_modules", diff --git a/test/japanese-util.test.js b/test/japanese-util.test.js index 47da4ccb..ae5b1a16 100644 --- a/test/japanese-util.test.js +++ b/test/japanese-util.test.js @@ -16,10 +16,10 @@ * along with this program. If not, see . */ +import * as wanakana from 'wanakana'; import {expect, test} from 'vitest'; import {TextSourceMap} from '../ext/js/general/text-source-map.js'; import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; -import * as wanakana from '../ext/lib/wanakana.js'; const jp = new JapaneseUtil(wanakana); diff --git a/test/jsconfig.json b/test/jsconfig.json index b025918c..261ec345 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -12,6 +12,7 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { + "zip.js": ["@zip.js/zip.js"], "*": ["../types/ext/*"], "dev/*": ["../types/dev/*"], "test/*": ["../types/test/*"] -- cgit v1.2.3 From 14d12f6ba20b837a04c638b935773f3120e194ff Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 19:33:01 -0500 Subject: Update timer types and such --- dev/jsconfig.json | 2 +- ext/js/app/frontend.js | 11 +++++++++-- ext/js/background/backend.js | 4 ++-- ext/js/background/offscreen.js | 4 +++- ext/js/comm/api.js | 2 +- ext/js/comm/clipboard-monitor.js | 2 +- ext/js/comm/cross-frame-api.js | 21 +++++++++++++-------- ext/js/comm/frame-ancestry-handler.js | 2 +- ext/js/comm/frame-client.js | 2 +- ext/js/comm/mecab.js | 2 +- ext/js/core.js | 2 +- ext/js/display/display-audio.js | 2 +- ext/js/display/display-notification.js | 2 +- ext/js/display/display.js | 2 +- ext/js/display/element-overflow-controller.js | 8 ++++---- ext/js/display/option-toggle-hotkey-handler.js | 2 +- ext/js/display/search-display-controller.js | 2 +- ext/js/dom/document-util.js | 2 +- ext/js/dom/panel-element.js | 2 +- ext/js/language/text-scanner.js | 6 +++--- ext/js/media/audio-downloader.js | 2 +- ext/js/pages/settings/popup-preview-frame.js | 2 +- test/jsconfig.json | 2 +- types/ext/core.d.ts | 2 ++ types/ext/cross-frame-api.d.ts | 2 +- types/ext/offscreen.d.ts | 2 +- types/other/globals.d.ts | 22 ++++++++++++++++++++++ types/other/web-set-timeout.d.ts | 24 ------------------------ 28 files changed, 77 insertions(+), 63 deletions(-) create mode 100644 types/other/globals.d.ts delete mode 100644 types/other/web-set-timeout.d.ts (limited to 'test') diff --git a/dev/jsconfig.json b/dev/jsconfig.json index 518f97ad..d4efe694 100644 --- a/dev/jsconfig.json +++ b/dev/jsconfig.json @@ -71,7 +71,7 @@ "../ext/js/language/translator.js", "../ext/js/media/media-util.js", "../types/dev/**/*.ts", - "../types/other/web-set-timeout.d.ts" + "../types/other/globals.d.ts" ], "exclude": [ "../node_modules" diff --git a/ext/js/app/frontend.js b/ext/js/app/frontend.js index 628c504e..e1f8d8c9 100644 --- a/ext/js/app/frontend.js +++ b/ext/js/app/frontend.js @@ -99,7 +99,7 @@ export class Frontend { this._popupEventListeners = new EventListenerCollection(); /** @type {?import('core').TokenObject} */ this._updatePopupToken = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._clearSelectionTimer = null; /** @type {boolean} */ this._isPointerOverPopup = false; @@ -840,7 +840,7 @@ export class Frontend { */ async _waitForFrontendReady(frameId, timeout) { return new Promise((resolve, reject) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timeoutId = null; const cleanup = () => { @@ -973,6 +973,13 @@ export class Frontend { await yomitan.api.loadExtensionScripts([ '/js/accessibility/google-docs-util.js' ]); + this._prepareGoogleDocs2(); + } + + /** + * @returns {Promise} + */ + async _prepareGoogleDocs2() { if (typeof GoogleDocsUtil === 'undefined') { return; } DocumentUtil.registerGetRangeFromPointHandler(GoogleDocsUtil.getRangeFromPoint.bind(GoogleDocsUtil)); } diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index 44f5a42d..f1a47e7f 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -138,7 +138,7 @@ export class Backend { /** @type {?string} */ this._defaultBrowserActionTitle = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._badgePrepareDelayTimer = null; /** @type {?import('log').LogLevel} */ this._logErrorLevel = null; @@ -1981,7 +1981,7 @@ export class Backend { */ _waitUntilTabFrameIsReady(tabId, frameId, timeout=null) { return new Promise((resolve, reject) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; /** @type {?import('extension').ChromeRuntimeOnMessageCallback} */ let onMessage = (message, sender) => { diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 45345c01..8da661ad 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -51,7 +51,7 @@ export class Offscreen { }); /** @type {import('offscreen').MessageHandlerMap} */ - this._messageHandlers = new Map([ + const messageHandlers = new Map([ ['clipboardGetTextOffscreen', {async: true, handler: this._getTextHandler.bind(this)}], ['clipboardGetImageOffscreen', {async: true, handler: this._getImageHandler.bind(this)}], ['clipboardSetBrowserOffscreen', {async: false, handler: this._setClipboardBrowser.bind(this)}], @@ -65,6 +65,8 @@ export class Offscreen { ['getTermFrequenciesOffscreen', {async: true, handler: this._getTermFrequenciesHandler.bind(this)}], ['clearDatabaseCachesOffscreen', {async: false, handler: this._clearDatabaseCachesHandler.bind(this)}] ]); + /** @type {import('offscreen').MessageHandlerMap} */ + this._messageHandlers = messageHandlers; const onMessage = this._onMessage.bind(this); chrome.runtime.onMessage.addListener(onMessage); diff --git a/ext/js/comm/api.js b/ext/js/comm/api.js index 0cfdba59..26218595 100644 --- a/ext/js/comm/api.js +++ b/ext/js/comm/api.js @@ -431,7 +431,7 @@ export class API { */ _createActionPort(timeout) { return new Promise((resolve, reject) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; const portDetails = deferPromise(); diff --git a/ext/js/comm/clipboard-monitor.js b/ext/js/comm/clipboard-monitor.js index 06e95438..3b3a56a9 100644 --- a/ext/js/comm/clipboard-monitor.js +++ b/ext/js/comm/clipboard-monitor.js @@ -31,7 +31,7 @@ export class ClipboardMonitor extends EventDispatcher { this._japaneseUtil = japaneseUtil; /** @type {import('clipboard-monitor').ClipboardReaderLike} */ this._clipboardReader = clipboardReader; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._timerId = null; /** @type {?import('core').TokenObject} */ this._timerToken = null; diff --git a/ext/js/comm/cross-frame-api.js b/ext/js/comm/cross-frame-api.js index 34f3f36a..3ac38cf2 100644 --- a/ext/js/comm/cross-frame-api.js +++ b/ext/js/comm/cross-frame-api.js @@ -25,14 +25,14 @@ import {yomitan} from '../yomitan.js'; */ class CrossFrameAPIPort extends EventDispatcher { /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @param {chrome.runtime.Port} port * @param {import('core').MessageHandlerMap} messageHandlers */ constructor(otherTabId, otherFrameId, port, messageHandlers) { super(); - /** @type {?number} */ + /** @type {number} */ this._otherTabId = otherTabId; /** @type {number} */ this._otherFrameId = otherFrameId; @@ -48,7 +48,7 @@ class CrossFrameAPIPort extends EventDispatcher { this._eventListeners = new EventListenerCollection(); } - /** @type {?number} */ + /** @type {number} */ get otherTabId() { return this._otherTabId; } @@ -299,7 +299,7 @@ export class CrossFrameAPI { this._ackTimeout = 3000; // 3 seconds /** @type {number} */ this._responseTimeout = 10000; // 10 seconds - /** @type {Map>} */ + /** @type {Map>} */ this._commPorts = new Map(); /** @type {import('core').MessageHandlerMap} */ this._messageHandlers = new Map(); @@ -339,7 +339,12 @@ export class CrossFrameAPI { * @returns {Promise} */ async invokeTab(targetTabId, targetFrameId, action, params) { - if (typeof targetTabId !== 'number') { targetTabId = this._tabId; } + if (typeof targetTabId !== 'number') { + targetTabId = this._tabId; + if (typeof targetTabId !== 'number') { + throw new Error('Unknown target tab id for invocation'); + } + } const commPort = await this._getOrCreateCommPort(targetTabId, targetFrameId); return await commPort.invoke(action, params, this._ackTimeout, this._responseTimeout); } @@ -405,7 +410,7 @@ export class CrossFrameAPI { } /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @returns {Promise} */ @@ -420,7 +425,7 @@ export class CrossFrameAPI { return await this._createCommPort(otherTabId, otherFrameId); } /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @returns {Promise} */ @@ -438,7 +443,7 @@ export class CrossFrameAPI { } /** - * @param {?number} otherTabId + * @param {number} otherTabId * @param {number} otherFrameId * @param {chrome.runtime.Port} port * @returns {CrossFrameAPIPort} diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js index 49c96c22..687ec368 100644 --- a/ext/js/comm/frame-ancestry-handler.js +++ b/ext/js/comm/frame-ancestry-handler.js @@ -122,7 +122,7 @@ export class FrameAncestryHandler { const responseMessageId = `${this._responseMessageIdBase}${uniqueId}`; /** @type {number[]} */ const results = []; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; const cleanup = () => { diff --git a/ext/js/comm/frame-client.js b/ext/js/comm/frame-client.js index 1519cf7f..8aa8c6d6 100644 --- a/ext/js/comm/frame-client.js +++ b/ext/js/comm/frame-client.js @@ -83,7 +83,7 @@ export class FrameClient { _connectInternal(frame, targetOrigin, hostFrameId, setupFrame, timeout) { return new Promise((resolve, reject) => { const tokenMap = new Map(); - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; const deferPromiseDetails = /** @type {import('core').DeferredPromiseDetails} */ (deferPromise()); const frameLoadedPromise = deferPromiseDetails.promise; diff --git a/ext/js/comm/mecab.js b/ext/js/comm/mecab.js index 072b42f3..0a87463b 100644 --- a/ext/js/comm/mecab.js +++ b/ext/js/comm/mecab.js @@ -31,7 +31,7 @@ export class Mecab { this._port = null; /** @type {number} */ this._sequence = 0; - /** @type {Map void, reject: (reason?: unknown) => void, timer: number}>} */ + /** @type {Map void, reject: (reason?: unknown) => void, timer: import('core').Timeout}>} */ this._invocations = new Map(); /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); diff --git a/ext/js/core.js b/ext/js/core.js index cdac2020..a53d5572 100644 --- a/ext/js/core.js +++ b/ext/js/core.js @@ -307,7 +307,7 @@ export function promiseAnimationFrame(timeout) { return; } - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timer = null; /** @type {?number} */ let frameRequest = null; diff --git a/ext/js/display/display-audio.js b/ext/js/display/display-audio.js index 3fbfc3c8..3576decb 100644 --- a/ext/js/display/display-audio.js +++ b/ext/js/display/display-audio.js @@ -36,7 +36,7 @@ export class DisplayAudio { this._playbackVolume = 1.0; /** @type {boolean} */ this._autoPlay = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._autoPlayAudioTimer = null; /** @type {number} */ this._autoPlayAudioDelay = 400; diff --git a/ext/js/display/display-notification.js b/ext/js/display/display-notification.js index b3f20700..d1cfa537 100644 --- a/ext/js/display/display-notification.js +++ b/ext/js/display/display-notification.js @@ -34,7 +34,7 @@ export class DisplayNotification { this._closeButton = /** @type {HTMLElement} */ (node.querySelector('.footer-notification-close-button')); /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._closeTimer = null; } diff --git a/ext/js/display/display.js b/ext/js/display/display.js index f14291d1..6e1450c3 100644 --- a/ext/js/display/display.js +++ b/ext/js/display/display.js @@ -117,7 +117,7 @@ export class Display extends EventDispatcher { this._queryOffset = 0; /** @type {HTMLElement} */ this._progressIndicator = /** @type {HTMLElement} */ (document.querySelector('#progress-indicator')); - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._progressIndicatorTimer = null; /** @type {DynamicProperty} */ this._progressIndicatorVisible = new DynamicProperty(false); diff --git a/ext/js/display/element-overflow-controller.js b/ext/js/display/element-overflow-controller.js index 1d2c808f..35277fa5 100644 --- a/ext/js/display/element-overflow-controller.js +++ b/ext/js/display/element-overflow-controller.js @@ -22,7 +22,7 @@ export class ElementOverflowController { constructor() { /** @type {Element[]} */ this._elements = []; - /** @type {?number} */ + /** @type {?(number|import('core').Timeout)} */ this._checkTimer = null; /** @type {EventListenerCollection} */ this._eventListeners = new EventListenerCollection(); @@ -154,7 +154,7 @@ export class ElementOverflowController { /** * @param {() => void} callback * @param {number} timeout - * @returns {number} + * @returns {number|import('core').Timeout} */ _requestIdleCallback(callback, timeout) { if (typeof requestIdleCallback === 'function') { @@ -165,11 +165,11 @@ export class ElementOverflowController { } /** - * @param {number} handle + * @param {number|import('core').Timeout} handle */ _cancelIdleCallback(handle) { if (typeof cancelIdleCallback === 'function') { - cancelIdleCallback(handle); + cancelIdleCallback(/** @type {number} */ (handle)); } else { clearTimeout(handle); } diff --git a/ext/js/display/option-toggle-hotkey-handler.js b/ext/js/display/option-toggle-hotkey-handler.js index 531e208d..edd7de5b 100644 --- a/ext/js/display/option-toggle-hotkey-handler.js +++ b/ext/js/display/option-toggle-hotkey-handler.js @@ -29,7 +29,7 @@ export class OptionToggleHotkeyHandler { this._display = display; /** @type {?import('./display-notification.js').DisplayNotification} */ this._notification = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._notificationHideTimer = null; /** @type {number} */ this._notificationHideTimeout = 5000; diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index b93757c2..98a4666b 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -63,7 +63,7 @@ export class SearchDisplayController { this._wanakanaBound = false; /** @type {boolean} */ this._introVisible = true; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._introAnimationTimer = null; /** @type {boolean} */ this._clipboardMonitorEnabled = false; diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index cf58d39f..549a8195 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -360,7 +360,7 @@ export class DocumentUtil { /** * Adds a fullscreen change event listener. This function handles all of the browser-specific variants. * @param {EventListener} onFullscreenChanged The event callback. - * @param {?EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. + * @param {?import('../core.js').EventListenerCollection} eventListenerCollection An optional `EventListenerCollection` to add the registration to. */ static addFullscreenChangeEventListener(onFullscreenChanged, eventListenerCollection=null) { const target = document; diff --git a/ext/js/dom/panel-element.js b/ext/js/dom/panel-element.js index 314eb2fd..748c3a36 100644 --- a/ext/js/dom/panel-element.js +++ b/ext/js/dom/panel-element.js @@ -37,7 +37,7 @@ export class PanelElement extends EventDispatcher { this._mutationObserver = null; /** @type {boolean} */ this._visible = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._closeTimer = null; } diff --git a/ext/js/language/text-scanner.js b/ext/js/language/text-scanner.js index f6bcde8d..d1b033e6 100644 --- a/ext/js/language/text-scanner.js +++ b/ext/js/language/text-scanner.js @@ -120,7 +120,7 @@ export class TextScanner extends EventDispatcher { /** @type {boolean} */ this._preventNextClickScan = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._preventNextClickScanTimer = null; /** @type {number} */ this._preventNextClickScanTimerDuration = 50; @@ -145,7 +145,7 @@ export class TextScanner extends EventDispatcher { /** @type {boolean} */ this._canClearSelection = true; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._textSelectionTimer = null; /** @type {boolean} */ this._yomitanIsChangingTextSelectionNow = false; @@ -995,7 +995,7 @@ export class TextScanner extends EventDispatcher { async _scanTimerWait() { const delay = this._delay; const promise = /** @type {Promise} */ (new Promise((resolve) => { - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let timeout = setTimeout(() => { timeout = null; resolve(true); diff --git a/ext/js/media/audio-downloader.js b/ext/js/media/audio-downloader.js index 0847d479..e041cc67 100644 --- a/ext/js/media/audio-downloader.js +++ b/ext/js/media/audio-downloader.js @@ -316,7 +316,7 @@ export class AudioDownloader { let signal; /** @type {?import('request-builder.js').ProgressCallback} */ let onProgress = null; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ let idleTimer = null; if (typeof idleTimeout === 'number') { const abortController = new AbortController(); diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index bb00829f..dab21f4d 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -43,7 +43,7 @@ export class PopupPreviewFrame { this._apiOptionsGetOld = null; /** @type {boolean} */ this._popupShown = false; - /** @type {?number} */ + /** @type {?import('core').Timeout} */ this._themeChangeTimeout = null; /** @type {?import('text-source').TextSource} */ this._textSource = null; diff --git a/test/jsconfig.json b/test/jsconfig.json index 261ec345..934aab81 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -31,7 +31,7 @@ "../ext/**/*.js", "../types/ext/**/*.ts", "../types/dev/**/*.ts", - "../types/other/web-set-timeout.d.ts" + "../types/other/globals.d.ts" ], "exclude": [ "../node_modules", diff --git a/types/ext/core.d.ts b/types/ext/core.d.ts index ce3a09f0..b83e6a74 100644 --- a/types/ext/core.d.ts +++ b/types/ext/core.d.ts @@ -100,3 +100,5 @@ export type MessageHandlerDetails = { export type MessageHandlerMap = Map; export type MessageHandlerArray = [key: string, handlerDetails: MessageHandlerDetails][]; + +export type Timeout = number | NodeJS.Timeout; diff --git a/types/ext/cross-frame-api.d.ts b/types/ext/cross-frame-api.d.ts index 5cedbec9..88ce59a7 100644 --- a/types/ext/cross-frame-api.d.ts +++ b/types/ext/cross-frame-api.d.ts @@ -50,5 +50,5 @@ export type Invocation = { responseTimeout: number; action: string; ack: boolean; - timer: number | null; + timer: Core.Timeout | null; }; diff --git a/types/ext/offscreen.d.ts b/types/ext/offscreen.d.ts index 7dd64d1e..ddf7eadc 100644 --- a/types/ext/offscreen.d.ts +++ b/types/ext/offscreen.d.ts @@ -112,4 +112,4 @@ export type MessageHandler< details: MessageDetailsMap[TMessage], ) => (TIsAsync extends true ? Promise> : MessageReturn); -export type MessageHandlerMap = Map; +export type MessageHandlerMap = Map; diff --git a/types/other/globals.d.ts b/types/other/globals.d.ts new file mode 100644 index 00000000..330f16c2 --- /dev/null +++ b/types/other/globals.d.ts @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2023 Yomitan 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 . + */ + +declare global { + function clearTimeout(timeoutId: NodeJS.Timeout | string | number | undefined): void; +} + +export {}; diff --git a/types/other/web-set-timeout.d.ts b/types/other/web-set-timeout.d.ts deleted file mode 100644 index 98e40dab..00000000 --- a/types/other/web-set-timeout.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan 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 . - */ - -declare module 'web-set-timeout' { - global { - // These types are used to ensure that setTimeout returns a number - function setTimeout(callback: (...args: TArgs) => void, ms?: number, ...args: TArgs): number; - function setTimeout(callback: (args: void) => void, ms?: number): number; - } -} -- cgit v1.2.3 From 58ae2ab871591eea82895b1ab2a18753521eab1f Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 27 Nov 2023 22:07:28 -0500 Subject: Revert "Use import map" --- dev/jsconfig.json | 1 - ext/action-popup.html | 8 -------- ext/background.html | 8 -------- ext/info.html | 8 -------- ext/issues.html | 8 -------- ext/js/background/backend.js | 2 +- ext/js/background/offscreen.js | 2 +- ext/js/display/search-display-controller.js | 2 +- ext/js/display/search-main.js | 2 +- ext/js/dom/simple-dom-parser.js | 2 +- ext/js/language/dictionary-importer.js | 4 ++-- ext/js/pages/settings/backup-controller.js | 2 +- ext/js/pages/settings/popup-preview-frame.js | 2 +- ext/js/templates/sandbox/anki-template-renderer.js | 2 +- ext/js/templates/sandbox/template-renderer-media-provider.js | 2 +- ext/js/templates/sandbox/template-renderer.js | 2 +- ext/legal.html | 8 -------- ext/offscreen.html | 8 -------- ext/permissions.html | 8 -------- ext/popup-preview.html | 8 -------- ext/popup.html | 8 -------- ext/search.html | 8 -------- ext/settings.html | 8 -------- ext/template-renderer.html | 8 -------- ext/welcome.html | 8 -------- jsconfig.json | 1 - test/japanese-util.test.js | 2 +- test/jsconfig.json | 1 - 28 files changed, 13 insertions(+), 120 deletions(-) (limited to 'test') diff --git a/dev/jsconfig.json b/dev/jsconfig.json index d4efe694..5b1c450c 100644 --- a/dev/jsconfig.json +++ b/dev/jsconfig.json @@ -12,7 +12,6 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { - "zip.js": ["@zip.js/zip.js"], "anki-templates": ["../types/ext/anki-templates"], "anki-templates-internal": ["../types/ext/anki-templates-internal"], "cache-map": ["../types/ext/cache-map"], diff --git a/ext/action-popup.html b/ext/action-popup.html index 5c6bfce4..b60e7055 100644 --- a/ext/action-popup.html +++ b/ext/action-popup.html @@ -12,14 +12,6 @@ - diff --git a/ext/background.html b/ext/background.html index 6f9ee5f6..dc88f397 100644 --- a/ext/background.html +++ b/ext/background.html @@ -12,14 +12,6 @@ - diff --git a/ext/info.html b/ext/info.html index 9e71ffd4..cb80053d 100644 --- a/ext/info.html +++ b/ext/info.html @@ -13,14 +13,6 @@ - diff --git a/ext/issues.html b/ext/issues.html index c75683dd..904fbf16 100644 --- a/ext/issues.html +++ b/ext/issues.html @@ -13,14 +13,6 @@ - diff --git a/ext/js/background/backend.js b/ext/js/background/backend.js index f1a47e7f..3eefed53 100644 --- a/ext/js/background/backend.js +++ b/ext/js/background/backend.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {AccessibilityController} from '../accessibility/accessibility-controller.js'; import {AnkiConnect} from '../comm/anki-connect.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; diff --git a/ext/js/background/offscreen.js b/ext/js/background/offscreen.js index 8da661ad..4b57514d 100644 --- a/ext/js/background/offscreen.js +++ b/ext/js/background/offscreen.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {ClipboardReader} from '../comm/clipboard-reader.js'; import {invokeMessageHandler} from '../core.js'; import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/display/search-display-controller.js b/ext/js/display/search-display-controller.js index 98a4666b..2778c4cd 100644 --- a/ext/js/display/search-display-controller.js +++ b/ext/js/display/search-display-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {ClipboardMonitor} from '../comm/clipboard-monitor.js'; import {EventListenerCollection, invokeMessageHandler} from '../core.js'; import {yomitan} from '../yomitan.js'; diff --git a/ext/js/display/search-main.js b/ext/js/display/search-main.js index c1445e37..c20cc135 100644 --- a/ext/js/display/search-main.js +++ b/ext/js/display/search-main.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../lib/wanakana.js'; import {log} from '../core.js'; import {DocumentFocusController} from '../dom/document-focus-controller.js'; import {HotkeyHandler} from '../input/hotkey-handler.js'; diff --git a/ext/js/dom/simple-dom-parser.js b/ext/js/dom/simple-dom-parser.js index 7ee28d51..a1f63890 100644 --- a/ext/js/dom/simple-dom-parser.js +++ b/ext/js/dom/simple-dom-parser.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as parse5 from 'parse5'; +import * as parse5 from '../../lib/parse5.js'; /** * @augments import('simple-dom-parser').ISimpleDomParser diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index 5a4d7257..115e0726 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -16,14 +16,14 @@ * along with this program. If not, see . */ -import * as ajvSchemas0 from 'validate-schemas'; +import * as ajvSchemas0 from '../../lib/validate-schemas.js'; import { BlobWriter as BlobWriter0, TextWriter as TextWriter0, Uint8ArrayReader as Uint8ArrayReader0, ZipReader as ZipReader0, configure -} from 'zip.js'; +} from '../../lib/zip.js'; import {stringReverse} from '../core.js'; import {MediaUtil} from '../media/media-util.js'; import {ExtensionError} from '../core/extension-error.js'; diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index aeff2a97..52c5f418 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Dexie} from 'dexie'; +import {Dexie} from '../../../lib/dexie.js'; import {isObject, log} from '../../core.js'; import {OptionsUtil} from '../../data/options-util.js'; import {ArrayBufferUtil} from '../../data/sandbox/array-buffer-util.js'; diff --git a/ext/js/pages/settings/popup-preview-frame.js b/ext/js/pages/settings/popup-preview-frame.js index dab21f4d..60d264fa 100644 --- a/ext/js/pages/settings/popup-preview-frame.js +++ b/ext/js/pages/settings/popup-preview-frame.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; +import * as wanakana from '../../../lib/wanakana.js'; import {Frontend} from '../../app/frontend.js'; import {TextSourceRange} from '../../dom/text-source-range.js'; import {yomitan} from '../../yomitan.js'; diff --git a/ext/js/templates/sandbox/anki-template-renderer.js b/ext/js/templates/sandbox/anki-template-renderer.js index b0dc8223..9f4bf6ff 100644 --- a/ext/js/templates/sandbox/anki-template-renderer.js +++ b/ext/js/templates/sandbox/anki-template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from 'handlebars'; +import {Handlebars} from '../../../lib/handlebars.js'; import {AnkiNoteDataCreator} from '../../data/sandbox/anki-note-data-creator.js'; import {PronunciationGenerator} from '../../display/sandbox/pronunciation-generator.js'; import {StructuredContentGenerator} from '../../display/sandbox/structured-content-generator.js'; diff --git a/ext/js/templates/sandbox/template-renderer-media-provider.js b/ext/js/templates/sandbox/template-renderer-media-provider.js index 23f334e1..d8a0a16d 100644 --- a/ext/js/templates/sandbox/template-renderer-media-provider.js +++ b/ext/js/templates/sandbox/template-renderer-media-provider.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from 'handlebars'; +import {Handlebars} from '../../../lib/handlebars.js'; export class TemplateRendererMediaProvider { constructor() { diff --git a/ext/js/templates/sandbox/template-renderer.js b/ext/js/templates/sandbox/template-renderer.js index d4aebd64..fe240b5f 100644 --- a/ext/js/templates/sandbox/template-renderer.js +++ b/ext/js/templates/sandbox/template-renderer.js @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import {Handlebars} from 'handlebars'; +import {Handlebars} from '../../../lib/handlebars.js'; import {ExtensionError} from '../../core/extension-error.js'; export class TemplateRenderer { diff --git a/ext/legal.html b/ext/legal.html index b853f3e8..94912c7e 100644 --- a/ext/legal.html +++ b/ext/legal.html @@ -14,14 +14,6 @@ - diff --git a/ext/offscreen.html b/ext/offscreen.html index cfab53ee..afb7eb44 100644 --- a/ext/offscreen.html +++ b/ext/offscreen.html @@ -12,14 +12,6 @@ - diff --git a/ext/permissions.html b/ext/permissions.html index 7baff14e..61b0d363 100644 --- a/ext/permissions.html +++ b/ext/permissions.html @@ -14,14 +14,6 @@ - diff --git a/ext/popup-preview.html b/ext/popup-preview.html index 7bd54470..15810242 100644 --- a/ext/popup-preview.html +++ b/ext/popup-preview.html @@ -13,14 +13,6 @@ - diff --git a/ext/popup.html b/ext/popup.html index 6bc8d690..30e8a8c0 100644 --- a/ext/popup.html +++ b/ext/popup.html @@ -15,14 +15,6 @@ - diff --git a/ext/search.html b/ext/search.html index 377a966a..8c595cc4 100644 --- a/ext/search.html +++ b/ext/search.html @@ -16,14 +16,6 @@ - diff --git a/ext/settings.html b/ext/settings.html index 276a7222..346cc1d7 100644 --- a/ext/settings.html +++ b/ext/settings.html @@ -14,14 +14,6 @@ - diff --git a/ext/template-renderer.html b/ext/template-renderer.html index d1747e99..116f1a0c 100644 --- a/ext/template-renderer.html +++ b/ext/template-renderer.html @@ -11,14 +11,6 @@ - diff --git a/ext/welcome.html b/ext/welcome.html index 355bbc5f..40639881 100644 --- a/ext/welcome.html +++ b/ext/welcome.html @@ -13,14 +13,6 @@ - diff --git a/jsconfig.json b/jsconfig.json index 4f316174..0f780ead 100644 --- a/jsconfig.json +++ b/jsconfig.json @@ -12,7 +12,6 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { - "zip.js": ["@zip.js/zip.js"], "*": ["./types/ext/*"] }, "types": [ diff --git a/test/japanese-util.test.js b/test/japanese-util.test.js index ae5b1a16..47da4ccb 100644 --- a/test/japanese-util.test.js +++ b/test/japanese-util.test.js @@ -16,10 +16,10 @@ * along with this program. If not, see . */ -import * as wanakana from 'wanakana'; import {expect, test} from 'vitest'; import {TextSourceMap} from '../ext/js/general/text-source-map.js'; import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; +import * as wanakana from '../ext/lib/wanakana.js'; const jp = new JapaneseUtil(wanakana); diff --git a/test/jsconfig.json b/test/jsconfig.json index 934aab81..2461fda9 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -12,7 +12,6 @@ "skipLibCheck": false, "baseUrl": ".", "paths": { - "zip.js": ["@zip.js/zip.js"], "*": ["../types/ext/*"], "dev/*": ["../types/dev/*"], "test/*": ["../types/test/*"] -- cgit v1.2.3 From fd8be74abfd8ed46f984a5d0b70adcc8c641b617 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 29 Nov 2023 20:31:02 -0500 Subject: Update types --- .eslintrc.json | 8 ++++++++ dev/bin/build.js | 2 +- dev/bin/dictionary-validate.js | 1 + dev/bin/schema-validate.js | 1 + dev/build-libs.js | 1 + test/jsconfig.json | 2 +- 6 files changed, 13 insertions(+), 2 deletions(-) (limited to 'test') diff --git a/.eslintrc.json b/.eslintrc.json index 58262714..b16032fc 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -771,6 +771,14 @@ "jsdoc/require-jsdoc": "off", "jsdoc/no-undefined-types": "off" } + }, + { + "files": [ + "dev/lib/**/*.js" + ], + "extends": [ + "plugin:@typescript-eslint/disable-type-checked" + ] } ] } diff --git a/dev/bin/build.js b/dev/bin/build.js index c5814dd3..47c08f3c 100644 --- a/dev/bin/build.js +++ b/dev/bin/build.js @@ -254,7 +254,7 @@ export async function main(argv) { await build(buildDir, extDir, manifestUtil, variantNames, manifestPath, dryRun, dryRunBuildZip, yomitanVersion); } finally { // Restore manifest - const manifestName = (!args.get('default') && args.get('manifest') !== null) ? args.get('manifest') : null; + const manifestName = /** @type {?string} */ ((!args.get('default') && args.get('manifest') !== null) ? args.get('manifest') : null); const restoreManifest = manifestUtil.getManifest(manifestName); process.stdout.write('Restoring manifest...\n'); if (!dryRun) { diff --git a/dev/bin/dictionary-validate.js b/dev/bin/dictionary-validate.js index 0affb919..dc01815e 100644 --- a/dev/bin/dictionary-validate.js +++ b/dev/bin/dictionary-validate.js @@ -18,6 +18,7 @@ import {testDictionaryFiles} from '../dictionary-validate.js'; +/** */ async function main() { const dictionaryFileNames = process.argv.slice(2); if (dictionaryFileNames.length === 0) { diff --git a/dev/bin/schema-validate.js b/dev/bin/schema-validate.js index 319c0d2c..206f26ca 100644 --- a/dev/bin/schema-validate.js +++ b/dev/bin/schema-validate.js @@ -20,6 +20,7 @@ import fs from 'fs'; import {performance} from 'perf_hooks'; import {createJsonSchema} from '../schema-validate.js'; +/** */ function main() { const args = process.argv.slice(2); if (args.length < 2) { diff --git a/dev/build-libs.js b/dev/build-libs.js index eee007f6..5caabec7 100644 --- a/dev/build-libs.js +++ b/dev/build-libs.js @@ -45,6 +45,7 @@ async function buildLib(scriptPath) { }); } +/** */ export async function buildLibs() { const devLibPath = path.join(dirname, 'lib'); const files = await fs.promises.readdir(devLibPath, { diff --git a/test/jsconfig.json b/test/jsconfig.json index 2461fda9..1881fbce 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -34,6 +34,6 @@ ], "exclude": [ "../node_modules", - "../ext/lib" + "../dev/lib" ] } \ No newline at end of file -- cgit v1.2.3 From 4fc7d818fde17ff62d2065b0d23567423992bd01 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 29 Nov 2023 20:36:48 -0500 Subject: Remove old tests --- test/test-all.js | 71 --- test/test-anki-note-builder.js | 322 ---------- test/test-cache-map.js | 137 ---- test/test-core.js | 300 --------- test/test-database.js | 982 ---------------------------- test/test-document-util.js | 339 ---------- test/test-hotkey-util.js | 189 ------ test/test-japanese-util.js | 915 -------------------------- test/test-json-schema.js | 1048 ------------------------------ test/test-manifest.js | 49 -- test/test-object-property-accessor.js | 458 ------------- test/test-profile-conditions-util.js | 1136 --------------------------------- test/test-text-source-map.js | 244 ------- test/test-translator.js | 102 --- test/test-workers.js | 168 ----- 15 files changed, 6460 deletions(-) delete mode 100644 test/test-all.js delete mode 100644 test/test-anki-note-builder.js delete mode 100644 test/test-cache-map.js delete mode 100644 test/test-core.js delete mode 100644 test/test-database.js delete mode 100644 test/test-document-util.js delete mode 100644 test/test-hotkey-util.js delete mode 100644 test/test-japanese-util.js delete mode 100644 test/test-json-schema.js delete mode 100644 test/test-manifest.js delete mode 100644 test/test-object-property-accessor.js delete mode 100644 test/test-profile-conditions-util.js delete mode 100644 test/test-text-source-map.js delete mode 100644 test/test-translator.js delete mode 100644 test/test-workers.js (limited to 'test') diff --git a/test/test-all.js b/test/test-all.js deleted file mode 100644 index 9219d278..00000000 --- a/test/test-all.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const fs = require('fs'); -const path = require('path'); -const {spawnSync} = require('child_process'); -const {getArgs} = require('../dev/util'); - - -/** - * @throws {Error} - */ -function main() { - const args = getArgs(process.argv.slice(2), new Map(/** @type {[key: string, value: (boolean|null|number|string|string[])][]} */ ([ - ['skip', []], - [null, []] - ]))); - const directories = /** @type {string[]} */ (args.get(null)); - const skipArg = /** @type {string[]} */ (args.get('skip')); - const skip = new Set([__filename, ...skipArg].map((value) => path.resolve(value))); - - const node = process.execPath; - const fileNamePattern = /\.js$/i; - - let first = true; - for (const directory of directories) { - const fileNames = fs.readdirSync(directory); - for (const fileName of fileNames) { - if (!fileNamePattern.test(fileName)) { continue; } - - const fullFileName = path.resolve(path.join(directory, fileName)); - if (skip.has(fullFileName)) { continue; } - - const stats = fs.lstatSync(fullFileName); - if (!stats.isFile()) { continue; } - - process.stdout.write(`${first ? '' : '\n'}Running ${fileName}...\n`); - first = false; - - const {error, status} = spawnSync(node, [fileName], {cwd: directory, stdio: 'inherit'}); - - if (status !== null && status !== 0) { - process.exit(status); - return; - } - if (error) { - throw error; - } - } - } - - process.exit(0); -} - - -if (require.main === module) { main(); } diff --git a/test/test-anki-note-builder.js b/test/test-anki-note-builder.js deleted file mode 100644 index 8e0ab9d9..00000000 --- a/test/test-anki-note-builder.js +++ /dev/null @@ -1,322 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2021-2022 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 . - */ - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); -const {JSDOM} = require('jsdom'); -const {testMain} = require('../dev/util'); -const {TranslatorVM} = require('../dev/translator-vm'); - - -/** - * @template T - * @param {T} value - * @returns {T} - */ -function clone(value) { - return JSON.parse(JSON.stringify(value)); -} - -/** - * @returns {Promise<{vm: TranslatorVM, AnkiNoteBuilder: typeof AnkiNoteBuilder2, JapaneseUtil: typeof JapaneseUtil2}>} - */ -async function createVM() { - const dom = new JSDOM(); - const {Node, NodeFilter, document} = dom.window; - - const vm = new TranslatorVM({ - Node, - NodeFilter, - document, - location: new URL('https://yomichan.test/') - }); - - const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', 'valid-dictionary1'); - await vm.prepare(dictionaryDirectory, 'Test Dictionary 2'); - - vm.execute([ - 'js/data/anki-note-builder.js', - 'js/data/anki-util.js', - 'js/dom/sandbox/css-style-applier.js', - 'js/display/sandbox/pronunciation-generator.js', - 'js/display/sandbox/structured-content-generator.js', - 'js/templates/sandbox/anki-template-renderer.js', - 'js/templates/sandbox/anki-template-renderer-content-manager.js', - 'js/templates/sandbox/template-renderer.js', - 'js/templates/sandbox/template-renderer-media-provider.js', - 'lib/handlebars.min.js' - ]); - - /** @type {[typeof JapaneseUtil, typeof AnkiNoteBuilder, typeof AnkiTemplateRenderer]} */ - const [ - JapaneseUtil2, - AnkiNoteBuilder2, - AnkiTemplateRenderer2 - ] = vm.get([ - 'JapaneseUtil', - 'AnkiNoteBuilder', - 'AnkiTemplateRenderer' - ]); - - class TemplateRendererProxy { - constructor() { - /** @type {?Promise} */ - this._preparePromise = null; - /** @type {AnkiTemplateRenderer} */ - this._ankiTemplateRenderer = new AnkiTemplateRenderer2(); - } - - /** - * @param {string} template - * @param {import('template-renderer').PartialOrCompositeRenderData} data - * @param {import('anki-templates').RenderMode} type - * @returns {Promise} - */ - async render(template, data, type) { - await this._prepare(); - return await this._ankiTemplateRenderer.templateRenderer.render(template, data, type); - } - - /** - * @param {import('template-renderer').RenderMultiItem[]} items - * @returns {Promise[]>} - */ - async renderMulti(items) { - await this._prepare(); - return await this._ankiTemplateRenderer.templateRenderer.renderMulti(items); - } - - /** - * @returns {Promise} - */ - _prepare() { - if (this._preparePromise === null) { - this._preparePromise = this._prepareInternal(); - } - return this._preparePromise; - } - - /** */ - async _prepareInternal() { - await this._ankiTemplateRenderer.prepare(); - } - } - vm.set({TemplateRendererProxy}); - - return {vm, AnkiNoteBuilder: AnkiNoteBuilder2, JapaneseUtil: JapaneseUtil2}; -} - -/** - * @param {'terms'|'kanji'} type - * @returns {string[]} - */ -function getFieldMarkers(type) { - switch (type) { - case 'terms': - return [ - 'audio', - 'clipboard-image', - 'clipboard-text', - 'cloze-body', - 'cloze-prefix', - 'cloze-suffix', - 'conjugation', - 'dictionary', - 'document-title', - 'expression', - 'frequencies', - 'furigana', - 'furigana-plain', - 'glossary', - 'glossary-brief', - 'glossary-no-dictionary', - 'part-of-speech', - 'pitch-accents', - 'pitch-accent-graphs', - 'pitch-accent-positions', - 'reading', - 'screenshot', - 'search-query', - 'selection-text', - 'sentence', - 'sentence-furigana', - 'tags', - 'url' - ]; - case 'kanji': - return [ - 'character', - 'clipboard-image', - 'clipboard-text', - 'cloze-body', - 'cloze-prefix', - 'cloze-suffix', - 'dictionary', - 'document-title', - 'glossary', - 'kunyomi', - 'onyomi', - 'screenshot', - 'search-query', - 'selection-text', - 'sentence', - 'sentence-furigana', - 'stroke-count', - 'tags', - 'url' - ]; - default: - return []; - } -} - -/** - * @param {import('dictionary').DictionaryEntry[]} dictionaryEntries - * @param {'terms'|'kanji'} type - * @param {import('settings').ResultOutputMode} mode - * @param {string} template - * @param {typeof AnkiNoteBuilder} AnkiNoteBuilder - * @param {typeof JapaneseUtil} JapaneseUtil - * @param {boolean} write - * @returns {Promise} - */ -async function getRenderResults(dictionaryEntries, type, mode, template, AnkiNoteBuilder, JapaneseUtil, write) { - const markers = getFieldMarkers(type); - /** @type {import('anki-note-builder').Field[]} */ - const fields = []; - for (const marker of markers) { - fields.push([marker, `{${marker}}`]); - } - - const japaneseUtil = new JapaneseUtil(null); - const clozePrefix = 'cloze-prefix'; - const clozeSuffix = 'cloze-suffix'; - const results = []; - for (const dictionaryEntry of dictionaryEntries) { - let source = ''; - switch (dictionaryEntry.type) { - case 'kanji': - source = dictionaryEntry.character; - break; - case 'term': - if (dictionaryEntry.headwords.length > 0 && dictionaryEntry.headwords[0].sources.length > 0) { - source = dictionaryEntry.headwords[0].sources[0].originalText; - } - break; - } - const ankiNoteBuilder = new AnkiNoteBuilder({japaneseUtil}); - const context = { - url: 'url:', - sentence: { - text: `${clozePrefix}${source}${clozeSuffix}`, - offset: clozePrefix.length - }, - documentTitle: 'title', - query: 'query', - fullQuery: 'fullQuery' - }; - /** @type {import('anki-note-builder').CreateNoteDetails} */ - const details = { - dictionaryEntry, - mode: 'test', - context, - template, - deckName: 'deckName', - modelName: 'modelName', - fields, - tags: ['yomichan'], - checkForDuplicates: true, - duplicateScope: 'collection', - duplicateScopeCheckAllModels: false, - resultOutputMode: mode, - glossaryLayoutMode: 'default', - compactTags: false, - requirements: [], - mediaOptions: null - }; - const {note: {fields: noteFields}, errors} = await ankiNoteBuilder.createNote(details); - if (!write) { - for (const error of errors) { - console.error(error); - } - assert.strictEqual(errors.length, 0); - } - results.push(noteFields); - } - - return results; -} - - -/** */ -async function main() { - const write = (process.argv[2] === '--write'); - - const {vm, AnkiNoteBuilder, JapaneseUtil} = await createVM(); - - const testInputsFilePath = path.join(__dirname, 'data', 'translator-test-inputs.json'); - const {optionsPresets, tests} = JSON.parse(fs.readFileSync(testInputsFilePath, {encoding: 'utf8'})); - - const testResults1FilePath = path.join(__dirname, 'data', 'anki-note-builder-test-results.json'); - const expectedResults1 = JSON.parse(fs.readFileSync(testResults1FilePath, {encoding: 'utf8'})); - const actualResults1 = []; - - const template = fs.readFileSync(path.join(__dirname, '..', 'ext', 'data/templates/default-anki-field-templates.handlebars'), {encoding: 'utf8'}); - - for (let i = 0, ii = tests.length; i < ii; ++i) { - const test = tests[i]; - const expected1 = expectedResults1[i]; - switch (test.func) { - case 'findTerms': - { - const {name, mode, text} = test; - /** @type {import('translation').FindTermsOptions} */ - const options = vm.buildOptions(optionsPresets, test.options); - const {dictionaryEntries} = clone(await vm.translator.findTerms(mode, text, options)); - const results = mode !== 'simple' ? clone(await getRenderResults(dictionaryEntries, 'terms', mode, template, AnkiNoteBuilder, JapaneseUtil, write)) : null; - actualResults1.push({name, results}); - if (!write) { - assert.deepStrictEqual(results, expected1.results); - } - } - break; - case 'findKanji': - { - const {name, text} = test; - /** @type {import('translation').FindKanjiOptions} */ - const options = vm.buildOptions(optionsPresets, test.options); - const dictionaryEntries = clone(await vm.translator.findKanji(text, options)); - const results = clone(await getRenderResults(dictionaryEntries, 'kanji', 'split', template, AnkiNoteBuilder, JapaneseUtil, write)); - actualResults1.push({name, results}); - if (!write) { - assert.deepStrictEqual(results, expected1.results); - } - } - break; - } - } - - if (write) { - // Use 2 indent instead of 4 to save a bit of file size - fs.writeFileSync(testResults1FilePath, JSON.stringify(actualResults1, null, 2), {encoding: 'utf8'}); - } -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-cache-map.js b/test/test-cache-map.js deleted file mode 100644 index 56a31898..00000000 --- a/test/test-cache-map.js +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM({console}); -vm.execute([ - 'js/general/cache-map.js' -]); -/** @type {typeof CacheMap} */ -const CacheMap2 = vm.getSingle('CacheMap'); - - -/** */ -function testConstructor() { - const data = /** @type {[throws: boolean, create: () => void][]} */ ([ - [false, () => new CacheMap2(0)], - [false, () => new CacheMap2(1)], - [false, () => new CacheMap2(Number.MAX_VALUE)], - [true, () => new CacheMap2(-1)], - [true, () => new CacheMap2(1.5)], - [true, () => new CacheMap2(Number.NaN)], - [true, () => new CacheMap2(Number.POSITIVE_INFINITY)], - // @ts-ignore - Ignore because it should throw an error - [true, () => new CacheMap2('a')] - ]); - - for (const [throws, create] of data) { - if (throws) { - assert.throws(create); - } else { - assert.doesNotThrow(create); - } - } -} - -/** */ -function testApi() { - const data = [ - { - maxSize: 1, - expectedSize: 0, - calls: [] - }, - { - maxSize: 10, - expectedSize: 1, - calls: [ - {func: 'get', args: ['a1-b-c'], returnValue: void 0}, - {func: 'has', args: ['a1-b-c'], returnValue: false}, - {func: 'set', args: ['a1-b-c', 32], returnValue: void 0}, - {func: 'get', args: ['a1-b-c'], returnValue: 32}, - {func: 'has', args: ['a1-b-c'], returnValue: true} - ] - }, - { - maxSize: 10, - expectedSize: 2, - calls: [ - {func: 'set', args: ['a1-b-c', 32], returnValue: void 0}, - {func: 'get', args: ['a1-b-c'], returnValue: 32}, - {func: 'set', args: ['a1-b-c', 64], returnValue: void 0}, - {func: 'get', args: ['a1-b-c'], returnValue: 64}, - {func: 'set', args: ['a2-b-c', 96], returnValue: void 0}, - {func: 'get', args: ['a2-b-c'], returnValue: 96} - ] - }, - { - maxSize: 2, - expectedSize: 2, - calls: [ - {func: 'has', args: ['a1-b-c'], returnValue: false}, - {func: 'has', args: ['a2-b-c'], returnValue: false}, - {func: 'has', args: ['a3-b-c'], returnValue: false}, - {func: 'set', args: ['a1-b-c', 1], returnValue: void 0}, - {func: 'has', args: ['a1-b-c'], returnValue: true}, - {func: 'has', args: ['a2-b-c'], returnValue: false}, - {func: 'has', args: ['a3-b-c'], returnValue: false}, - {func: 'set', args: ['a2-b-c', 2], returnValue: void 0}, - {func: 'has', args: ['a1-b-c'], returnValue: true}, - {func: 'has', args: ['a2-b-c'], returnValue: true}, - {func: 'has', args: ['a3-b-c'], returnValue: false}, - {func: 'set', args: ['a3-b-c', 3], returnValue: void 0}, - {func: 'has', args: ['a1-b-c'], returnValue: false}, - {func: 'has', args: ['a2-b-c'], returnValue: true}, - {func: 'has', args: ['a3-b-c'], returnValue: true} - ] - } - ]; - - for (const {maxSize, expectedSize, calls} of data) { - const cache = new CacheMap2(maxSize); - assert.strictEqual(cache.maxSize, maxSize); - for (const call of calls) { - const {func, args} = call; - let returnValue; - switch (func) { - case 'get': returnValue = cache.get(args[0]); break; - case 'set': returnValue = cache.set(args[0], args[1]); break; - case 'has': returnValue = cache.has(args[0]); break; - case 'clear': returnValue = cache.clear(); break; - } - if (Object.prototype.hasOwnProperty.call(call, 'returnValue')) { - const {returnValue: expectedReturnValue} = call; - assert.deepStrictEqual(returnValue, expectedReturnValue); - } - } - assert.strictEqual(cache.size, expectedSize); - } -} - - -/** */ -function main() { - testConstructor(); - testApi(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-core.js b/test/test-core.js deleted file mode 100644 index 2c48ac20..00000000 --- a/test/test-core.js +++ /dev/null @@ -1,300 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM(); -vm.execute([ - 'js/core.js', - 'js/core/extension-error.js' -]); -const values = vm.get(['DynamicProperty', 'deepEqual']); -const DynamicProperty2 = /** @type {typeof DynamicProperty} */ (values[0]); -const deepEqual2 = /** @type {typeof deepEqual} */ (values[1]); - - -/** */ -function testDynamicProperty() { - const data = [ - { - initialValue: 0, - /** @type {{operation: ?string, expectedDefaultValue: number, expectedValue: number, expectedOverrideCount: number, expeectedEventOccurred: boolean, args: [value: number, priority?: number]}[]} */ - operations: [ - { - operation: null, - args: [0], - expectedDefaultValue: 0, - expectedValue: 0, - expectedOverrideCount: 0, - expeectedEventOccurred: false - }, - { - operation: 'set.defaultValue', - args: [1], - expectedDefaultValue: 1, - expectedValue: 1, - expectedOverrideCount: 0, - expeectedEventOccurred: true - }, - { - operation: 'set.defaultValue', - args: [1], - expectedDefaultValue: 1, - expectedValue: 1, - expectedOverrideCount: 0, - expeectedEventOccurred: false - }, - { - operation: 'set.defaultValue', - args: [0], - expectedDefaultValue: 0, - expectedValue: 0, - expectedOverrideCount: 0, - expeectedEventOccurred: true - }, - { - operation: 'setOverride', - args: [8], - expectedDefaultValue: 0, - expectedValue: 8, - expectedOverrideCount: 1, - expeectedEventOccurred: true - }, - { - operation: 'setOverride', - args: [16], - expectedDefaultValue: 0, - expectedValue: 8, - expectedOverrideCount: 2, - expeectedEventOccurred: false - }, - { - operation: 'setOverride', - args: [32, 1], - expectedDefaultValue: 0, - expectedValue: 32, - expectedOverrideCount: 3, - expeectedEventOccurred: true - }, - { - operation: 'setOverride', - args: [64, -1], - expectedDefaultValue: 0, - expectedValue: 32, - expectedOverrideCount: 4, - expeectedEventOccurred: false - }, - { - operation: 'clearOverride', - args: [-4], - expectedDefaultValue: 0, - expectedValue: 32, - expectedOverrideCount: 3, - expeectedEventOccurred: false - }, - { - operation: 'clearOverride', - args: [-3], - expectedDefaultValue: 0, - expectedValue: 32, - expectedOverrideCount: 2, - expeectedEventOccurred: false - }, - { - operation: 'clearOverride', - args: [-2], - expectedDefaultValue: 0, - expectedValue: 64, - expectedOverrideCount: 1, - expeectedEventOccurred: true - }, - { - operation: 'clearOverride', - args: [-1], - expectedDefaultValue: 0, - expectedValue: 0, - expectedOverrideCount: 0, - expeectedEventOccurred: true - } - ] - } - ]; - - for (const {initialValue, operations} of data) { - const property = new DynamicProperty2(initialValue); - const overrideTokens = []; - let eventOccurred = false; - const onChange = () => { eventOccurred = true; }; - property.on('change', onChange); - for (const {operation, args, expectedDefaultValue, expectedValue, expectedOverrideCount, expeectedEventOccurred} of operations) { - eventOccurred = false; - switch (operation) { - case 'set.defaultValue': property.defaultValue = args[0]; break; - case 'setOverride': overrideTokens.push(property.setOverride(...args)); break; - case 'clearOverride': property.clearOverride(overrideTokens[overrideTokens.length + args[0]]); break; - } - assert.strictEqual(eventOccurred, expeectedEventOccurred); - assert.strictEqual(property.defaultValue, expectedDefaultValue); - assert.strictEqual(property.value, expectedValue); - assert.strictEqual(property.overrideCount, expectedOverrideCount); - } - property.off('change', onChange); - } -} - -/** */ -function testDeepEqual() { - const data = [ - // Simple tests - { - value1: 0, - value2: 0, - expected: true - }, - { - value1: null, - value2: null, - expected: true - }, - { - value1: 'test', - value2: 'test', - expected: true - }, - { - value1: true, - value2: true, - expected: true - }, - { - value1: 0, - value2: 1, - expected: false - }, - { - value1: null, - value2: false, - expected: false - }, - { - value1: 'test1', - value2: 'test2', - expected: false - }, - { - value1: true, - value2: false, - expected: false - }, - - // Simple object tests - { - value1: {}, - value2: {}, - expected: true - }, - { - value1: {}, - value2: [], - expected: false - }, - { - value1: [], - value2: [], - expected: true - }, - { - value1: {}, - value2: null, - expected: false - }, - - // Complex object tests - { - value1: [1], - value2: [], - expected: false - }, - { - value1: [1], - value2: [1], - expected: true - }, - { - value1: [1], - value2: [2], - expected: false - }, - - { - value1: {}, - value2: {test: 1}, - expected: false - }, - { - value1: {test: 1}, - value2: {test: 1}, - expected: true - }, - { - value1: {test: 1}, - value2: {test: {test2: false}}, - expected: false - }, - { - value1: {test: {test2: true}}, - value2: {test: {test2: false}}, - expected: false - }, - { - value1: {test: {test2: [true]}}, - value2: {test: {test2: [true]}}, - expected: true - }, - - // Recursive - { - value1: (() => { const x = {}; x.x = x; return x; })(), - value2: (() => { const x = {}; x.x = x; return x; })(), - expected: false - } - ]; - - let index = 0; - for (const {value1, value2, expected} of data) { - const actual1 = deepEqual2(value1, value2); - assert.strictEqual(actual1, expected, `Failed for test ${index}`); - - const actual2 = deepEqual2(value2, value1); - assert.strictEqual(actual2, expected, `Failed for test ${index}`); - - ++index; - } -} - - -/** */ -function main() { - testDynamicProperty(); - testDeepEqual(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-database.js b/test/test-database.js deleted file mode 100644 index 947e369b..00000000 --- a/test/test-database.js +++ /dev/null @@ -1,982 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const path = require('path'); -const assert = require('assert'); -const {createDictionaryArchive, testMain} = require('../dev/util'); -const {DatabaseVM, DatabaseVMDictionaryImporterMediaLoader} = require('../dev/database-vm'); - - -const vm = new DatabaseVM(); -vm.execute([ - 'js/core.js', - 'js/core/extension-error.js', - 'js/general/cache-map.js', - 'js/data/json-schema.js', - 'js/media/media-util.js', - 'js/language/dictionary-importer.js', - 'js/data/database.js', - 'js/language/dictionary-database.js' -]); -/** @type {typeof DictionaryImporter} */ -const DictionaryImporter2 = vm.getSingle('DictionaryImporter'); -/** @type {typeof DictionaryDatabase} */ -const DictionaryDatabase2 = vm.getSingle('DictionaryDatabase'); - - -/** - * @param {string} dictionary - * @param {string} [dictionaryName] - * @returns {import('jszip')} - */ -function createTestDictionaryArchive(dictionary, dictionaryName) { - const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', dictionary); - return createDictionaryArchive(dictionaryDirectory, dictionaryName); -} - - -/** - * @param {import('dictionary-importer').OnProgressCallback} [onProgress] - * @returns {DictionaryImporter} - */ -function createDictionaryImporter(onProgress) { - const dictionaryImporterMediaLoader = new DatabaseVMDictionaryImporterMediaLoader(); - return new DictionaryImporter2(dictionaryImporterMediaLoader, (...args) => { - const {stepIndex, stepCount, index, count} = args[0]; - assert.ok(stepIndex < stepCount); - assert.ok(index <= count); - if (typeof onProgress === 'function') { - onProgress(...args); - } - }); -} - - -/** - * @param {import('dictionary-database').TermEntry[]} dictionaryDatabaseEntries - * @param {string} term - * @returns {number} - */ -function countDictionaryDatabaseEntriesWithTerm(dictionaryDatabaseEntries, term) { - return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.term === term ? 1 : 0)), 0); -} - -/** - * @param {import('dictionary-database').TermEntry[]} dictionaryDatabaseEntries - * @param {string} reading - * @returns {number} - */ -function countDictionaryDatabaseEntriesWithReading(dictionaryDatabaseEntries, reading) { - return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.reading === reading ? 1 : 0)), 0); -} - -/** - * @param {import('dictionary-database').TermMeta[]|import('dictionary-database').KanjiMeta[]} metas - * @param {import('dictionary-database').TermMetaType|import('dictionary-database').KanjiMetaType} mode - * @returns {number} - */ -function countMetasWithMode(metas, mode) { - let i = 0; - for (const item of metas) { - if (item.mode === mode) { ++i; } - } - return i; -} - -/** - * @param {import('dictionary-database').KanjiEntry[]} kanji - * @param {string} character - * @returns {number} - */ -function countKanjiWithCharacter(kanji, character) { - let i = 0; - for (const item of kanji) { - if (item.character === character) { ++i; } - } - return i; -} - - -/** - * @param {number} timeout - * @returns {Promise} - */ -function clearDatabase(timeout) { - return new Promise((resolve, reject) => { - /** @type {?number} */ - let timer = setTimeout(() => { - timer = null; - reject(new Error(`clearDatabase failed to resolve after ${timeout}ms`)); - }, timeout); - - (async () => { - const indexedDB = vm.indexedDB; - for (const {name} of await indexedDB.databases()) { - if (typeof name !== 'string') { continue; } - /** @type {Promise} */ - const promise2 = new Promise((resolve2, reject2) => { - const request = indexedDB.deleteDatabase(name); - request.onerror = (e) => reject2(e); - request.onsuccess = () => resolve2(); - }); - await promise2; - } - if (timer !== null) { - clearTimeout(timer); - } - resolve(); - })(); - }); -} - - -/** */ -async function testDatabase1() { - // Load dictionary data - const testDictionary = createTestDictionaryArchive('valid-dictionary1'); - const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); - const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); - - const title = testDictionaryIndex.title; - const titles = new Map([ - [title, {priority: 0, allowSecondarySearches: false}] - ]); - - // Setup iteration data - const iterations = [ - { - cleanup: async () => { - // Test purge - await dictionaryDatabase.purge(); - await testDatabaseEmpty1(dictionaryDatabase); - } - }, - { - cleanup: async () => { - // Test deleteDictionary - let progressEvent = false; - await dictionaryDatabase.deleteDictionary( - title, - 1000, - () => { - progressEvent = true; - } - ); - assert.ok(progressEvent); - - await testDatabaseEmpty1(dictionaryDatabase); - } - }, - { - cleanup: async () => {} - } - ]; - - // Setup database - const dictionaryDatabase = new DictionaryDatabase2(); - await dictionaryDatabase.prepare(); - - for (const {cleanup} of iterations) { - const expectedSummary = { - title, - revision: 'test', - sequenced: true, - version: 3, - importDate: 0, - prefixWildcardsSupported: true, - counts: { - kanji: {total: 2}, - kanjiMeta: {total: 6, freq: 6}, - media: {total: 4}, - tagMeta: {total: 15}, - termMeta: {total: 38, freq: 31, pitch: 7}, - terms: {total: 21} - } - }; - - // Import data - let progressEvent = false; - const dictionaryImporter = createDictionaryImporter(() => { progressEvent = true; }); - const {result, errors} = await dictionaryImporter.importDictionary( - dictionaryDatabase, - testDictionarySource, - {prefixWildcardsSupported: true} - ); - expectedSummary.importDate = result.importDate; - vm.assert.deepStrictEqual(errors, []); - vm.assert.deepStrictEqual(result, expectedSummary); - assert.ok(progressEvent); - - // Get info summary - const info = await dictionaryDatabase.getDictionaryInfo(); - vm.assert.deepStrictEqual(info, [expectedSummary]); - - // Get counts - const counts = await dictionaryDatabase.getDictionaryCounts( - info.map((v) => v.title), - true - ); - vm.assert.deepStrictEqual(counts, { - counts: [{kanji: 2, kanjiMeta: 6, terms: 21, termMeta: 38, tagMeta: 15, media: 4}], - total: {kanji: 2, kanjiMeta: 6, terms: 21, termMeta: 38, tagMeta: 15, media: 4} - }); - - // Test find* functions - await testFindTermsBulkTest1(dictionaryDatabase, titles); - await testTindTermsExactBulk1(dictionaryDatabase, titles); - await testFindTermsBySequenceBulk1(dictionaryDatabase, title); - await testFindTermMetaBulk1(dictionaryDatabase, titles); - await testFindKanjiBulk1(dictionaryDatabase, titles); - await testFindKanjiMetaBulk1(dictionaryDatabase, titles); - await testFindTagForTitle1(dictionaryDatabase, title); - - // Cleanup - await cleanup(); - } - - await dictionaryDatabase.close(); -} - -/** - * @param {DictionaryDatabase} database - */ -async function testDatabaseEmpty1(database) { - const info = await database.getDictionaryInfo(); - vm.assert.deepStrictEqual(info, []); - - const counts = await database.getDictionaryCounts([], true); - vm.assert.deepStrictEqual(counts, { - counts: [], - total: {kanji: 0, kanjiMeta: 0, terms: 0, termMeta: 0, tagMeta: 0, media: 0} - }); -} - -/** - * @param {DictionaryDatabase} database - * @param {import('dictionary-database').DictionarySet} titles - */ -async function testFindTermsBulkTest1(database, titles) { - /** @type {{inputs: {matchType: import('dictionary-database').MatchType, termList: string[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - matchType: 'exact', - termList: ['打', '打つ', '打ち込む'] - }, - { - matchType: 'exact', - termList: ['だ', 'ダース', 'うつ', 'ぶつ', 'うちこむ', 'ぶちこむ'] - }, - { - matchType: 'prefix', - termList: ['打'] - } - ], - expectedResults: { - total: 10, - terms: [ - ['打', 2], - ['打つ', 4], - ['打ち込む', 4] - ], - readings: [ - ['だ', 1], - ['ダース', 1], - ['うつ', 2], - ['ぶつ', 2], - ['うちこむ', 2], - ['ぶちこむ', 2] - ] - } - }, - { - inputs: [ - { - matchType: 'exact', - termList: ['込む'] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - }, - { - inputs: [ - { - matchType: 'suffix', - termList: ['込む'] - } - ], - expectedResults: { - total: 4, - terms: [ - ['打ち込む', 4] - ], - readings: [ - ['うちこむ', 2], - ['ぶちこむ', 2] - ] - } - }, - { - inputs: [ - { - matchType: 'exact', - termList: [] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {termList, matchType} of inputs) { - const results = await database.findTermsBulk(termList, titles, matchType); - assert.strictEqual(results.length, expectedResults.total); - for (const [term, count] of expectedResults.terms) { - assert.strictEqual(countDictionaryDatabaseEntriesWithTerm(results, term), count); - } - for (const [reading, count] of expectedResults.readings) { - assert.strictEqual(countDictionaryDatabaseEntriesWithReading(results, reading), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {import('dictionary-database').DictionarySet} titles - */ -async function testTindTermsExactBulk1(database, titles) { - /** @type {{inputs: {termList: {term: string, reading: string}[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - termList: [ - {term: '打', reading: 'だ'}, - {term: '打つ', reading: 'うつ'}, - {term: '打ち込む', reading: 'うちこむ'} - ] - } - ], - expectedResults: { - total: 5, - terms: [ - ['打', 1], - ['打つ', 2], - ['打ち込む', 2] - ], - readings: [ - ['だ', 1], - ['うつ', 2], - ['うちこむ', 2] - ] - } - }, - { - inputs: [ - { - termList: [ - {term: '打', reading: 'だ?'}, - {term: '打つ', reading: 'うつ?'}, - {term: '打ち込む', reading: 'うちこむ?'} - ] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - }, - { - inputs: [ - { - termList: [ - {term: '打つ', reading: 'うつ'}, - {term: '打つ', reading: 'ぶつ'} - ] - } - ], - expectedResults: { - total: 4, - terms: [ - ['打つ', 4] - ], - readings: [ - ['うつ', 2], - ['ぶつ', 2] - ] - } - }, - { - inputs: [ - { - termList: [ - {term: '打つ', reading: 'うちこむ'} - ] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - }, - { - inputs: [ - { - termList: [] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {termList} of inputs) { - const results = await database.findTermsExactBulk(termList, titles); - assert.strictEqual(results.length, expectedResults.total); - for (const [term, count] of expectedResults.terms) { - assert.strictEqual(countDictionaryDatabaseEntriesWithTerm(results, term), count); - } - for (const [reading, count] of expectedResults.readings) { - assert.strictEqual(countDictionaryDatabaseEntriesWithReading(results, reading), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {string} mainDictionary - */ -async function testFindTermsBySequenceBulk1(database, mainDictionary) { - /** @type {{inputs: {sequenceList: number[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - sequenceList: [1, 2, 3, 4, 5] - } - ], - expectedResults: { - total: 11, - terms: [ - ['打', 2], - ['打つ', 4], - ['打ち込む', 4], - ['画像', 1] - ], - readings: [ - ['だ', 1], - ['ダース', 1], - ['うつ', 2], - ['ぶつ', 2], - ['うちこむ', 2], - ['ぶちこむ', 2], - ['がぞう', 1] - ] - } - }, - { - inputs: [ - { - sequenceList: [1] - } - ], - expectedResults: { - total: 1, - terms: [ - ['打', 1] - ], - readings: [ - ['だ', 1] - ] - } - }, - { - inputs: [ - { - sequenceList: [2] - } - ], - expectedResults: { - total: 1, - terms: [ - ['打', 1] - ], - readings: [ - ['ダース', 1] - ] - } - }, - { - inputs: [ - { - sequenceList: [3] - } - ], - expectedResults: { - total: 4, - terms: [ - ['打つ', 4] - ], - readings: [ - ['うつ', 2], - ['ぶつ', 2] - ] - } - }, - { - inputs: [ - { - sequenceList: [4] - } - ], - expectedResults: { - total: 4, - terms: [ - ['打ち込む', 4] - ], - readings: [ - ['うちこむ', 2], - ['ぶちこむ', 2] - ] - } - }, - { - inputs: [ - { - sequenceList: [5] - } - ], - expectedResults: { - total: 1, - terms: [ - ['画像', 1] - ], - readings: [ - ['がぞう', 1] - ] - } - }, - { - inputs: [ - { - sequenceList: [-1] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - }, - { - inputs: [ - { - sequenceList: [] - } - ], - expectedResults: { - total: 0, - terms: [], - readings: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {sequenceList} of inputs) { - const results = await database.findTermsBySequenceBulk(sequenceList.map((query) => ({query, dictionary: mainDictionary}))); - assert.strictEqual(results.length, expectedResults.total); - for (const [term, count] of expectedResults.terms) { - assert.strictEqual(countDictionaryDatabaseEntriesWithTerm(results, term), count); - } - for (const [reading, count] of expectedResults.readings) { - assert.strictEqual(countDictionaryDatabaseEntriesWithReading(results, reading), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {import('dictionary-database').DictionarySet} titles - */ -async function testFindTermMetaBulk1(database, titles) { - /** @type {{inputs: {termList: string[]}[], expectedResults: {total: number, modes: [key: import('dictionary-database').TermMetaType, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - termList: ['打'] - } - ], - expectedResults: { - total: 11, - modes: [ - ['freq', 11] - ] - } - }, - { - inputs: [ - { - termList: ['打つ'] - } - ], - expectedResults: { - total: 10, - modes: [ - ['freq', 10] - ] - } - }, - { - inputs: [ - { - termList: ['打ち込む'] - } - ], - expectedResults: { - total: 12, - modes: [ - ['freq', 10], - ['pitch', 2] - ] - } - }, - { - inputs: [ - { - termList: ['?'] - } - ], - expectedResults: { - total: 0, - modes: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {termList} of inputs) { - const results = await database.findTermMetaBulk(termList, titles); - assert.strictEqual(results.length, expectedResults.total); - for (const [mode, count] of expectedResults.modes) { - assert.strictEqual(countMetasWithMode(results, mode), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {import('dictionary-database').DictionarySet} titles - */ -async function testFindKanjiBulk1(database, titles) { - /** @type {{inputs: {kanjiList: string[]}[], expectedResults: {total: number, kanji: [key: string, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - kanjiList: ['打'] - } - ], - expectedResults: { - total: 1, - kanji: [ - ['打', 1] - ] - } - }, - { - inputs: [ - { - kanjiList: ['込'] - } - ], - expectedResults: { - total: 1, - kanji: [ - ['込', 1] - ] - } - }, - { - inputs: [ - { - kanjiList: ['?'] - } - ], - expectedResults: { - total: 0, - kanji: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {kanjiList} of inputs) { - const results = await database.findKanjiBulk(kanjiList, titles); - assert.strictEqual(results.length, expectedResults.total); - for (const [kanji, count] of expectedResults.kanji) { - assert.strictEqual(countKanjiWithCharacter(results, kanji), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {import('dictionary-database').DictionarySet} titles - */ -async function testFindKanjiMetaBulk1(database, titles) { - /** @type {{inputs: {kanjiList: string[]}[], expectedResults: {total: number, modes: [key: import('dictionary-database').KanjiMetaType, count: number][]}}[]} */ - const data = [ - { - inputs: [ - { - kanjiList: ['打'] - } - ], - expectedResults: { - total: 3, - modes: [ - ['freq', 3] - ] - } - }, - { - inputs: [ - { - kanjiList: ['込'] - } - ], - expectedResults: { - total: 3, - modes: [ - ['freq', 3] - ] - } - }, - { - inputs: [ - { - kanjiList: ['?'] - } - ], - expectedResults: { - total: 0, - modes: [] - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {kanjiList} of inputs) { - const results = await database.findKanjiMetaBulk(kanjiList, titles); - assert.strictEqual(results.length, expectedResults.total); - for (const [mode, count] of expectedResults.modes) { - assert.strictEqual(countMetasWithMode(results, mode), count); - } - } - } -} - -/** - * @param {DictionaryDatabase} database - * @param {string} title - */ -async function testFindTagForTitle1(database, title) { - const data = [ - { - inputs: [ - { - name: 'E1' - } - ], - expectedResults: { - value: {category: 'default', dictionary: title, name: 'E1', notes: 'example tag 1', order: 0, score: 0} - } - }, - { - inputs: [ - { - name: 'K1' - } - ], - expectedResults: { - value: {category: 'default', dictionary: title, name: 'K1', notes: 'example kanji tag 1', order: 0, score: 0} - } - }, - { - inputs: [ - { - name: 'kstat1' - } - ], - expectedResults: { - value: {category: 'class', dictionary: title, name: 'kstat1', notes: 'kanji stat 1', order: 0, score: 0} - } - }, - { - inputs: [ - { - name: 'invalid' - } - ], - expectedResults: { - value: null - } - } - ]; - - for (const {inputs, expectedResults} of data) { - for (const {name} of inputs) { - const result = await database.findTagForTitle(name, title); - vm.assert.deepStrictEqual(result, expectedResults.value); - } - } -} - - -/** */ -async function testDatabase2() { - // Load dictionary data - const testDictionary = createTestDictionaryArchive('valid-dictionary1'); - const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); - const testDictionaryIndex = JSON.parse(await testDictionary.files['index.json'].async('string')); - - const title = testDictionaryIndex.title; - const titles = new Map([ - [title, {priority: 0, allowSecondarySearches: false}] - ]); - - // Setup database - const dictionaryDatabase = new DictionaryDatabase2(); - /** @type {import('dictionary-importer').ImportDetails} */ - const detaultImportDetails = {prefixWildcardsSupported: false}; - - // Error: not prepared - await assert.rejects(async () => await dictionaryDatabase.deleteDictionary(title, 1000, () => {})); - await assert.rejects(async () => await dictionaryDatabase.findTermsBulk(['?'], titles, 'exact')); - await assert.rejects(async () => await dictionaryDatabase.findTermsExactBulk([{term: '?', reading: '?'}], titles)); - await assert.rejects(async () => await dictionaryDatabase.findTermsBySequenceBulk([{query: 1, dictionary: title}])); - await assert.rejects(async () => await dictionaryDatabase.findTermMetaBulk(['?'], titles)); - await assert.rejects(async () => await dictionaryDatabase.findTermMetaBulk(['?'], titles)); - await assert.rejects(async () => await dictionaryDatabase.findKanjiBulk(['?'], titles)); - await assert.rejects(async () => await dictionaryDatabase.findKanjiMetaBulk(['?'], titles)); - await assert.rejects(async () => await dictionaryDatabase.findTagForTitle('tag', title)); - await assert.rejects(async () => await dictionaryDatabase.getDictionaryInfo()); - await assert.rejects(async () => await dictionaryDatabase.getDictionaryCounts([...titles.keys()], true)); - await assert.rejects(async () => await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)); - - await dictionaryDatabase.prepare(); - - // Error: already prepared - await assert.rejects(async () => await dictionaryDatabase.prepare()); - - await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails); - - // Error: dictionary already imported - await assert.rejects(async () => await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)); - - await dictionaryDatabase.close(); -} - - -/** */ -async function testDatabase3() { - const invalidDictionaries = [ - 'invalid-dictionary1', - 'invalid-dictionary2', - 'invalid-dictionary3', - 'invalid-dictionary4', - 'invalid-dictionary5', - 'invalid-dictionary6' - ]; - - // Setup database - const dictionaryDatabase = new DictionaryDatabase2(); - /** @type {import('dictionary-importer').ImportDetails} */ - const detaultImportDetails = {prefixWildcardsSupported: false}; - await dictionaryDatabase.prepare(); - - for (const invalidDictionary of invalidDictionaries) { - const testDictionary = createTestDictionaryArchive(invalidDictionary); - const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); - - let error = null; - try { - await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails); - } catch (e) { - error = e; - } - - if (error === null) { - assert.ok(false, `Expected an error while importing ${invalidDictionary}`); - } else { - const prefix = 'Dictionary has invalid data'; - const message = /** @type {import('core').UnknownObject} */ (error).message; - assert.ok(typeof message, 'string'); - if (typeof message === 'string') { - assert.ok(message.startsWith(prefix), `Expected error message to start with '${prefix}': ${message}`); - } - } - } - - await dictionaryDatabase.close(); -} - - -/** */ -async function main() { - const clearTimeout = 5000; - try { - await testDatabase1(); - await clearDatabase(clearTimeout); - - await testDatabase2(); - await clearDatabase(clearTimeout); - - await testDatabase3(); - await clearDatabase(clearTimeout); - } catch (e) { - console.log(e); - process.exit(-1); - throw e; - } -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-document-util.js b/test/test-document-util.js deleted file mode 100644 index 93ce1669..00000000 --- a/test/test-document-util.js +++ /dev/null @@ -1,339 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); -const {JSDOM} = require('jsdom'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - - -// DOMRect class definition -class DOMRect { - /** - * @param {number} x - * @param {number} y - * @param {number} width - * @param {number} height - */ - constructor(x, y, width, height) { - /** @type {number} */ - this._x = x; - /** @type {number} */ - this._y = y; - /** @type {number} */ - this._width = width; - /** @type {number} */ - this._height = height; - } - - /** @type {number} */ - get x() { return this._x; } - /** @type {number} */ - get y() { return this._y; } - /** @type {number} */ - get width() { return this._width; } - /** @type {number} */ - get height() { return this._height; } - /** @type {number} */ - get left() { return this._x + Math.min(0, this._width); } - /** @type {number} */ - get right() { return this._x + Math.max(0, this._width); } - /** @type {number} */ - get top() { return this._y + Math.min(0, this._height); } - /** @type {number} */ - get bottom() { return this._y + Math.max(0, this._height); } - /** @returns {string} */ - toJSON() { return ''; } -} - - -/** - * @param {string} fileName - * @returns {JSDOM} - */ -function createJSDOM(fileName) { - const domSource = fs.readFileSync(fileName, {encoding: 'utf8'}); - const dom = new JSDOM(domSource); - const document = dom.window.document; - const window = dom.window; - - // Define innerText setter as an alias for textContent setter - Object.defineProperty(window.HTMLDivElement.prototype, 'innerText', { - set(value) { this.textContent = value; } - }); - - // Placeholder for feature detection - document.caretRangeFromPoint = () => null; - - return dom; -} - -/** - * @param {Element} element - * @param {string|undefined} selector - * @returns {?Element} - */ -function querySelectorChildOrSelf(element, selector) { - return selector ? element.querySelector(selector) : element; -} - -/** - * @param {JSDOM} dom - * @param {?Node} node - * @returns {?Text|Node} - */ -function getChildTextNodeOrSelf(dom, node) { - if (node === null) { return null; } - const Node = dom.window.Node; - const childNode = node.firstChild; - return (childNode !== null && childNode.nodeType === Node.TEXT_NODE ? childNode : node); -} - -/** - * @param {unknown} value - * @returns {unknown} - */ -function getPrototypeOfOrNull(value) { - try { - return Object.getPrototypeOf(value); - } catch (e) { - return null; - } -} - -/** - * @param {Document} document - * @returns {?Element} - */ -function findImposterElement(document) { - // Finds the imposter element based on it's z-index style - return document.querySelector('div[style*="2147483646"]>*'); -} - - -/** */ -async function testDocument1() { - const dom = createJSDOM(path.join(__dirname, 'data', 'html', 'test-document1.html')); - const window = dom.window; - const document = window.document; - const Node = window.Node; - const Range = window.Range; - - const vm = new VM({document, window, Range, Node}); - vm.execute([ - 'js/data/sandbox/string-util.js', - 'js/dom/dom-text-scanner.js', - 'js/dom/text-source-range.js', - 'js/dom/text-source-element.js', - 'js/dom/document-util.js' - ]); - /** @type {[DOMTextScanner: typeof DOMTextScanner, TextSourceRange: typeof TextSourceRange, TextSourceElement: typeof TextSourceElement, DocumentUtil: typeof DocumentUtil]} */ - const [DOMTextScanner2, TextSourceRange2, TextSourceElement2, DocumentUtil2] = vm.get([ - 'DOMTextScanner', - 'TextSourceRange', - 'TextSourceElement', - 'DocumentUtil' - ]); - - try { - await testDocumentTextScanningFunctions(dom, {DocumentUtil: DocumentUtil2, TextSourceRange: TextSourceRange2, TextSourceElement: TextSourceElement2}); - await testTextSourceRangeSeekFunctions(dom, {DOMTextScanner: DOMTextScanner2}); - } finally { - window.close(); - } -} - -/** - * @param {JSDOM} dom - * @param {{DocumentUtil: typeof DocumentUtil, TextSourceRange: typeof TextSourceRange, TextSourceElement: typeof TextSourceElement}} details - */ -async function testDocumentTextScanningFunctions(dom, {DocumentUtil, TextSourceRange, TextSourceElement}) { - const document = dom.window.document; - - for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('.test[data-test-type=scan]'))) { - // Get test parameters - const { - elementFromPointSelector, - caretRangeFromPointSelector, - startNodeSelector, - startOffset, - endNodeSelector, - endOffset, - resultType, - sentenceScanExtent, - sentence, - hasImposter, - terminateAtNewlines - } = testElement.dataset; - - const elementFromPointValue = querySelectorChildOrSelf(testElement, elementFromPointSelector); - const caretRangeFromPointValue = querySelectorChildOrSelf(testElement, caretRangeFromPointSelector); - const startNode = getChildTextNodeOrSelf(dom, querySelectorChildOrSelf(testElement, startNodeSelector)); - const endNode = getChildTextNodeOrSelf(dom, querySelectorChildOrSelf(testElement, endNodeSelector)); - - const startOffset2 = parseInt(/** @type {string} */ (startOffset), 10); - const endOffset2 = parseInt(/** @type {string} */ (endOffset), 10); - const sentenceScanExtent2 = parseInt(/** @type {string} */ (sentenceScanExtent), 10); - const terminateAtNewlines2 = (terminateAtNewlines !== 'false'); - - assert.notStrictEqual(elementFromPointValue, null); - assert.notStrictEqual(caretRangeFromPointValue, null); - assert.notStrictEqual(startNode, null); - assert.notStrictEqual(endNode, null); - - // Setup functions - document.elementFromPoint = () => elementFromPointValue; - - document.caretRangeFromPoint = (x, y) => { - const imposter = getChildTextNodeOrSelf(dom, findImposterElement(document)); - assert.strictEqual(!!imposter, hasImposter === 'true'); - - const range = document.createRange(); - range.setStart(/** @type {Node} */ (imposter ? imposter : startNode), startOffset2); - range.setEnd(/** @type {Node} */ (imposter ? imposter : startNode), endOffset2); - - // Override getClientRects to return a rect guaranteed to contain (x, y) - range.getClientRects = () => { - /** @type {import('test/document-types').PseudoDOMRectList} */ - const domRectList = Object.assign( - [new DOMRect(x - 1, y - 1, 2, 2)], - { - /** - * @this {DOMRect[]} - * @param {number} index - * @returns {DOMRect} - */ - item: function item(index) { return this[index]; } - } - ); - return domRectList; - }; - return range; - }; - - // Test docRangeFromPoint - const source = DocumentUtil.getRangeFromPoint(0, 0, { - deepContentScan: false, - normalizeCssZoom: true - }); - switch (resultType) { - case 'TextSourceRange': - assert.strictEqual(getPrototypeOfOrNull(source), TextSourceRange.prototype); - break; - case 'TextSourceElement': - assert.strictEqual(getPrototypeOfOrNull(source), TextSourceElement.prototype); - break; - case 'null': - assert.strictEqual(source, null); - break; - default: - assert.ok(false); - break; - } - if (source === null) { continue; } - - // Sentence info - const terminatorString = '…。..??!!'; - const terminatorMap = new Map(); - for (const char of terminatorString) { - terminatorMap.set(char, [false, true]); - } - const quoteArray = [['「', '」'], ['『', '』'], ['\'', '\''], ['"', '"']]; - const forwardQuoteMap = new Map(); - const backwardQuoteMap = new Map(); - for (const [char1, char2] of quoteArray) { - forwardQuoteMap.set(char1, [char2, false]); - backwardQuoteMap.set(char2, [char1, false]); - } - - // Test docSentenceExtract - const sentenceActual = DocumentUtil.extractSentence( - source, - false, - sentenceScanExtent2, - terminateAtNewlines2, - terminatorMap, - forwardQuoteMap, - backwardQuoteMap - ).text; - assert.strictEqual(sentenceActual, sentence); - - // Clean - source.cleanup(); - } -} - -/** - * @param {JSDOM} dom - * @param {{DOMTextScanner: typeof DOMTextScanner}} details - */ -async function testTextSourceRangeSeekFunctions(dom, {DOMTextScanner}) { - const document = dom.window.document; - - for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('.test[data-test-type=text-source-range-seek]'))) { - // Get test parameters - const { - seekNodeSelector, - seekNodeIsText, - seekOffset, - seekLength, - seekDirection, - expectedResultNodeSelector, - expectedResultNodeIsText, - expectedResultOffset, - expectedResultContent - } = testElement.dataset; - - const seekOffset2 = parseInt(/** @type {string} */ (seekOffset), 10); - const seekLength2 = parseInt(/** @type {string} */ (seekLength), 10); - const expectedResultOffset2 = parseInt(/** @type {string} */ (expectedResultOffset), 10); - - /** @type {?Node} */ - let seekNode = testElement.querySelector(/** @type {string} */ (seekNodeSelector)); - if (seekNodeIsText === 'true' && seekNode !== null) { - seekNode = seekNode.firstChild; - } - - /** @type {?Node} */ - let expectedResultNode = testElement.querySelector(/** @type {string} */ (expectedResultNodeSelector)); - if (expectedResultNodeIsText === 'true' && expectedResultNode !== null) { - expectedResultNode = expectedResultNode.firstChild; - } - - const {node, offset, content} = ( - seekDirection === 'forward' ? - new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset2, true, false).seek(seekLength2) : - new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset2, true, false).seek(-seekLength2) - ); - - assert.strictEqual(node, expectedResultNode); - assert.strictEqual(offset, expectedResultOffset2); - assert.strictEqual(content, expectedResultContent); - } -} - - -/** */ -async function main() { - await testDocument1(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-hotkey-util.js b/test/test-hotkey-util.js deleted file mode 100644 index 88f5a8d2..00000000 --- a/test/test-hotkey-util.js +++ /dev/null @@ -1,189 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - - -/** - * @template T - * @param {T} value - * @returns {T} - */ -function clone(value) { - return JSON.parse(JSON.stringify(value)); -} - -/** - * @returns {HotkeyUtil} - */ -function createHotkeyUtil() { - const vm = new VM(); - vm.execute(['js/input/hotkey-util.js']); - /** @type {typeof HotkeyUtil} */ - const HotkeyUtil2 = vm.getSingle('HotkeyUtil'); - return new HotkeyUtil2(); -} - - -/** */ -function testCommandConversions() { - /** @type {{os: import('environment').OperatingSystem, command: string, expectedCommand: string, expectedInput: {key: string, modifiers: import('input').Modifier[]}}[]} */ - const data = [ - {os: 'win', command: 'Alt+F', expectedCommand: 'Alt+F', expectedInput: {key: 'KeyF', modifiers: ['alt']}}, - {os: 'win', command: 'F1', expectedCommand: 'F1', expectedInput: {key: 'F1', modifiers: []}}, - - {os: 'win', command: 'Ctrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, - {os: 'win', command: 'MacCtrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, - {os: 'win', command: 'Command+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}}, - - {os: 'mac', command: 'Ctrl+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}}, - {os: 'mac', command: 'MacCtrl+Alt+Shift+F1', expectedCommand: 'MacCtrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, - {os: 'mac', command: 'Command+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}}, - - {os: 'linux', command: 'Ctrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, - {os: 'linux', command: 'MacCtrl+Alt+Shift+F1', expectedCommand: 'Ctrl+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['ctrl', 'alt', 'shift']}}, - {os: 'linux', command: 'Command+Alt+Shift+F1', expectedCommand: 'Command+Alt+Shift+F1', expectedInput: {key: 'F1', modifiers: ['meta', 'alt', 'shift']}} - ]; - - const hotkeyUtil = createHotkeyUtil(); - for (const {command, os, expectedInput, expectedCommand} of data) { - hotkeyUtil.os = os; - const input = clone(hotkeyUtil.convertCommandToInput(command)); - assert.deepStrictEqual(input, expectedInput); - const command2 = hotkeyUtil.convertInputToCommand(input.key, input.modifiers); - assert.deepStrictEqual(command2, expectedCommand); - } -} - -/** */ -function testDisplayNames() { - /** @type {{os: import('environment').OperatingSystem, key: ?string, modifiers: import('input').Modifier[], expected: string}[]} */ - const data = [ - {os: 'win', key: null, modifiers: [], expected: ''}, - {os: 'win', key: 'KeyF', modifiers: [], expected: 'F'}, - {os: 'win', key: 'F1', modifiers: [], expected: 'F1'}, - {os: 'win', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, - {os: 'win', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, - {os: 'win', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, - {os: 'win', key: null, modifiers: ['alt'], expected: 'Alt'}, - {os: 'win', key: 'KeyF', modifiers: ['alt'], expected: 'Alt + F'}, - {os: 'win', key: 'F1', modifiers: ['alt'], expected: 'Alt + F1'}, - {os: 'win', key: null, modifiers: ['shift'], expected: 'Shift'}, - {os: 'win', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, - {os: 'win', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, - {os: 'win', key: null, modifiers: ['meta'], expected: 'Windows'}, - {os: 'win', key: 'KeyF', modifiers: ['meta'], expected: 'Windows + F'}, - {os: 'win', key: 'F1', modifiers: ['meta'], expected: 'Windows + F1'}, - {os: 'win', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, - {os: 'win', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, - {os: 'win', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'}, - - {os: 'mac', key: null, modifiers: [], expected: ''}, - {os: 'mac', key: 'KeyF', modifiers: [], expected: 'F'}, - {os: 'mac', key: 'F1', modifiers: [], expected: 'F1'}, - {os: 'mac', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, - {os: 'mac', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, - {os: 'mac', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, - {os: 'mac', key: null, modifiers: ['alt'], expected: 'Opt'}, - {os: 'mac', key: 'KeyF', modifiers: ['alt'], expected: 'Opt + F'}, - {os: 'mac', key: 'F1', modifiers: ['alt'], expected: 'Opt + F1'}, - {os: 'mac', key: null, modifiers: ['shift'], expected: 'Shift'}, - {os: 'mac', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, - {os: 'mac', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, - {os: 'mac', key: null, modifiers: ['meta'], expected: 'Cmd'}, - {os: 'mac', key: 'KeyF', modifiers: ['meta'], expected: 'Cmd + F'}, - {os: 'mac', key: 'F1', modifiers: ['meta'], expected: 'Cmd + F1'}, - {os: 'mac', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, - {os: 'mac', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, - {os: 'mac', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'}, - - {os: 'linux', key: null, modifiers: [], expected: ''}, - {os: 'linux', key: 'KeyF', modifiers: [], expected: 'F'}, - {os: 'linux', key: 'F1', modifiers: [], expected: 'F1'}, - {os: 'linux', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, - {os: 'linux', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, - {os: 'linux', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, - {os: 'linux', key: null, modifiers: ['alt'], expected: 'Alt'}, - {os: 'linux', key: 'KeyF', modifiers: ['alt'], expected: 'Alt + F'}, - {os: 'linux', key: 'F1', modifiers: ['alt'], expected: 'Alt + F1'}, - {os: 'linux', key: null, modifiers: ['shift'], expected: 'Shift'}, - {os: 'linux', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, - {os: 'linux', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, - {os: 'linux', key: null, modifiers: ['meta'], expected: 'Super'}, - {os: 'linux', key: 'KeyF', modifiers: ['meta'], expected: 'Super + F'}, - {os: 'linux', key: 'F1', modifiers: ['meta'], expected: 'Super + F1'}, - {os: 'linux', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, - {os: 'linux', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, - {os: 'linux', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'}, - - {os: 'unknown', key: null, modifiers: [], expected: ''}, - {os: 'unknown', key: 'KeyF', modifiers: [], expected: 'F'}, - {os: 'unknown', key: 'F1', modifiers: [], expected: 'F1'}, - {os: 'unknown', key: null, modifiers: ['ctrl'], expected: 'Ctrl'}, - {os: 'unknown', key: 'KeyF', modifiers: ['ctrl'], expected: 'Ctrl + F'}, - {os: 'unknown', key: 'F1', modifiers: ['ctrl'], expected: 'Ctrl + F1'}, - {os: 'unknown', key: null, modifiers: ['alt'], expected: 'Alt'}, - {os: 'unknown', key: 'KeyF', modifiers: ['alt'], expected: 'Alt + F'}, - {os: 'unknown', key: 'F1', modifiers: ['alt'], expected: 'Alt + F1'}, - {os: 'unknown', key: null, modifiers: ['shift'], expected: 'Shift'}, - {os: 'unknown', key: 'KeyF', modifiers: ['shift'], expected: 'Shift + F'}, - {os: 'unknown', key: 'F1', modifiers: ['shift'], expected: 'Shift + F1'}, - {os: 'unknown', key: null, modifiers: ['meta'], expected: 'Meta'}, - {os: 'unknown', key: 'KeyF', modifiers: ['meta'], expected: 'Meta + F'}, - {os: 'unknown', key: 'F1', modifiers: ['meta'], expected: 'Meta + F1'}, - {os: 'unknown', key: null, modifiers: ['mouse1'], expected: 'Mouse 1'}, - {os: 'unknown', key: 'KeyF', modifiers: ['mouse1'], expected: 'Mouse 1 + F'}, - {os: 'unknown', key: 'F1', modifiers: ['mouse1'], expected: 'Mouse 1 + F1'} - ]; - - const hotkeyUtil = createHotkeyUtil(); - for (const {os, key, modifiers, expected} of data) { - hotkeyUtil.os = os; - const displayName = hotkeyUtil.getInputDisplayValue(key, modifiers); - assert.deepStrictEqual(displayName, expected); - } -} - -/** */ -function testSortModifiers() { - /** @type {{modifiers: import('input').Modifier[], expected: import('input').Modifier[]}[]} */ - const data = [ - {modifiers: [], expected: []}, - {modifiers: ['shift', 'alt', 'ctrl', 'mouse4', 'meta', 'mouse1', 'mouse0'], expected: ['meta', 'ctrl', 'alt', 'shift', 'mouse0', 'mouse1', 'mouse4']} - ]; - - const hotkeyUtil = createHotkeyUtil(); - for (const {modifiers, expected} of data) { - const modifiers2 = hotkeyUtil.sortModifiers(modifiers); - assert.strictEqual(modifiers2, modifiers); - assert.deepStrictEqual(modifiers2, expected); - } -} - - -/** */ -function main() { - testCommandConversions(); - testDisplayNames(); - testSortModifiers(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-japanese-util.js b/test/test-japanese-util.js deleted file mode 100644 index 0a95b858..00000000 --- a/test/test-japanese-util.js +++ /dev/null @@ -1,915 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM(); -vm.execute([ - 'lib/wanakana.min.js', - 'js/language/sandbox/japanese-util.js', - 'js/general/text-source-map.js' -]); -/** @type {[JapaneseUtil: typeof JapaneseUtil, TextSourceMap: typeof TextSourceMap, wanakana: import('wanakana')]} */ -const [JapaneseUtil2, TextSourceMap2, wanakana] = vm.get(['JapaneseUtil', 'TextSourceMap', 'wanakana']); -const jp = new JapaneseUtil2(wanakana); - - -/** */ -function testIsCodePointKanji() { - /** @type {[characters: string, expected: boolean][]} */ - const data = [ - ['力方', true], - ['\u53f1\u{20b9f}', true], - ['かたカタ々kata、。?,.?', false], - ['逸逸', true] - ]; - - for (const [characters, expected] of data) { - for (const character of characters) { - const codePoint = /** @type {number} */ (character.codePointAt(0)); - const actual = jp.isCodePointKanji(codePoint); - assert.strictEqual(actual, expected, `isCodePointKanji failed for ${character} (\\u{${codePoint.toString(16)}})`); - } - } -} - -/** */ -function testIsCodePointKana() { - /** @type {[characters: string, expected: boolean][]} */ - const data = [ - ['かたカタ', true], - ['力方々kata、。?,.?', false], - ['\u53f1\u{20b9f}', false] - ]; - - for (const [characters, expected] of data) { - for (const character of characters) { - const codePoint = /** @type {number} */ (character.codePointAt(0)); - const actual = jp.isCodePointKana(codePoint); - assert.strictEqual(actual, expected, `isCodePointKana failed for ${character} (\\u{${codePoint.toString(16)}})`); - } - } -} - -/** */ -function testIsCodePointJapanese() { - /** @type {[characters: string, expected: boolean][]} */ - const data = [ - ['かたカタ力方々、。?', true], - ['\u53f1\u{20b9f}', true], - ['kata,.?', false], - ['逸逸', true] - ]; - - for (const [characters, expected] of data) { - for (const character of characters) { - const codePoint = /** @type {number} */ (character.codePointAt(0)); - const actual = jp.isCodePointJapanese(codePoint); - assert.strictEqual(actual, expected, `isCodePointJapanese failed for ${character} (\\u{${codePoint.toString(16)}})`); - } - } -} - -/** */ -function testIsStringEntirelyKana() { - /** @type {[string: string, expected: boolean][]} */ - const data = [ - ['かたかな', true], - ['カタカナ', true], - ['ひらがな', true], - ['ヒラガナ', true], - ['カタカナひらがな', true], - ['かたカタ力方々、。?', false], - ['\u53f1\u{20b9f}', false], - ['kata,.?', false], - ['かたカタ力方々、。?invalid', false], - ['\u53f1\u{20b9f}invalid', false], - ['kata,.?かた', false] - ]; - - for (const [string, expected] of data) { - assert.strictEqual(jp.isStringEntirelyKana(string), expected); - } -} - -/** */ -function testIsStringPartiallyJapanese() { - /** @type {[string: string, expected: boolean][]} */ - const data = [ - ['かたかな', true], - ['カタカナ', true], - ['ひらがな', true], - ['ヒラガナ', true], - ['カタカナひらがな', true], - ['かたカタ力方々、。?', true], - ['\u53f1\u{20b9f}', true], - ['kata,.?', false], - ['かたカタ力方々、。?invalid', true], - ['\u53f1\u{20b9f}invalid', true], - ['kata,.?かた', true], - ['逸逸', true] - ]; - - for (const [string, expected] of data) { - assert.strictEqual(jp.isStringPartiallyJapanese(string), expected); - } -} - -/** */ -function testConvertKatakanaToHiragana() { - /** @type {[string: string, expected: string, keepProlongedSoundMarks?: boolean][]} */ - const data = [ - ['かたかな', 'かたかな'], - ['ひらがな', 'ひらがな'], - ['カタカナ', 'かたかな'], - ['ヒラガナ', 'ひらがな'], - ['カタカナかたかな', 'かたかなかたかな'], - ['ヒラガナひらがな', 'ひらがなひらがな'], - ['chikaraちからチカラ力', 'chikaraちからちから力'], - ['katakana', 'katakana'], - ['hiragana', 'hiragana'], - ['カーナー', 'かあなあ'], - ['カーナー', 'かーなー', true] - ]; - - for (const [string, expected, keepProlongedSoundMarks=false] of data) { - assert.strictEqual(jp.convertKatakanaToHiragana(string, keepProlongedSoundMarks), expected); - } -} - -/** */ -function testConvertHiraganaToKatakana() { - /** @type {[string: string, expected: string][]} */ - const data = [ - ['かたかな', 'カタカナ'], - ['ひらがな', 'ヒラガナ'], - ['カタカナ', 'カタカナ'], - ['ヒラガナ', 'ヒラガナ'], - ['カタカナかたかな', 'カタカナカタカナ'], - ['ヒラガナひらがな', 'ヒラガナヒラガナ'], - ['chikaraちからチカラ力', 'chikaraチカラチカラ力'], - ['katakana', 'katakana'], - ['hiragana', 'hiragana'] - ]; - - for (const [string, expected] of data) { - assert.strictEqual(jp.convertHiraganaToKatakana(string), expected); - } -} - -/** */ -function testConvertToRomaji() { - /** @type {[string: string, expected: string][]} */ - const data = [ - ['かたかな', 'katakana'], - ['ひらがな', 'hiragana'], - ['カタカナ', 'katakana'], - ['ヒラガナ', 'hiragana'], - ['カタカナかたかな', 'katakanakatakana'], - ['ヒラガナひらがな', 'hiraganahiragana'], - ['chikaraちからチカラ力', 'chikarachikarachikara力'], - ['katakana', 'katakana'], - ['hiragana', 'hiragana'] - ]; - - for (const [string, expected] of data) { - assert.strictEqual(jp.convertToRomaji(string), expected); - } -} - -/** */ -function testConvertNumericToFullWidth() { - /** @type {[string: string, expected: string][]} */ - const data = [ - ['0123456789', '0123456789'], - ['abcdefghij', 'abcdefghij'], - ['カタカナ', 'カタカナ'], - ['ひらがな', 'ひらがな'] - ]; - - for (const [string, expected] of data) { - assert.strictEqual(jp.convertNumericToFullWidth(string), expected); - } -} - -/** */ -function testConvertHalfWidthKanaToFullWidth() { - /** @type {[string: string, expected: string, expectedSourceMapping?: number[]][]} */ - const data = [ - ['0123456789', '0123456789'], - ['abcdefghij', 'abcdefghij'], - ['カタカナ', 'カタカナ'], - ['ひらがな', 'ひらがな'], - ['カキ', 'カキ', [1, 1]], - ['ガキ', 'ガキ', [2, 1]], - ['ニホン', 'ニホン', [1, 1, 1]], - ['ニッポン', 'ニッポン', [1, 1, 2, 1]] - ]; - - for (const [string, expected, expectedSourceMapping] of data) { - const sourceMap = new TextSourceMap2(string); - const actual1 = jp.convertHalfWidthKanaToFullWidth(string, null); - const actual2 = jp.convertHalfWidthKanaToFullWidth(string, sourceMap); - assert.strictEqual(actual1, expected); - assert.strictEqual(actual2, expected); - if (typeof expectedSourceMapping !== 'undefined') { - assert.ok(sourceMap.equals(new TextSourceMap2(string, expectedSourceMapping))); - } - } -} - -/** */ -function testConvertAlphabeticToKana() { - /** @type {[string: string, expected: string, expectedSourceMapping?: number[]][]} */ - const data = [ - ['0123456789', '0123456789'], - ['abcdefghij', 'あbcでfgひj', [1, 1, 1, 2, 1, 1, 2, 1]], - ['ABCDEFGHIJ', 'あbcでfgひj', [1, 1, 1, 2, 1, 1, 2, 1]], // wanakana.toHiragana converts text to lower case - ['カタカナ', 'カタカナ'], - ['ひらがな', 'ひらがな'], - ['chikara', 'ちから', [3, 2, 2]], - ['CHIKARA', 'ちから', [3, 2, 2]] - ]; - - for (const [string, expected, expectedSourceMapping] of data) { - const sourceMap = new TextSourceMap2(string); - const actual1 = jp.convertAlphabeticToKana(string, null); - const actual2 = jp.convertAlphabeticToKana(string, sourceMap); - assert.strictEqual(actual1, expected); - assert.strictEqual(actual2, expected); - if (typeof expectedSourceMapping !== 'undefined') { - assert.ok(sourceMap.equals(new TextSourceMap2(string, expectedSourceMapping))); - } - } -} - -/** */ -function testDistributeFurigana() { - /** @type {[input: [term: string, reading: string], expected: {text: string, reading: string}[]][]} */ - const data = [ - ([ - ['有り難う', 'ありがとう'], - [ - {text: '有', reading: 'あ'}, - {text: 'り', reading: ''}, - {text: '難', reading: 'がと'}, - {text: 'う', reading: ''} - ] - ]), - [ - ['方々', 'かたがた'], - [ - {text: '方々', reading: 'かたがた'} - ] - ], - [ - ['お祝い', 'おいわい'], - [ - {text: 'お', reading: ''}, - {text: '祝', reading: 'いわ'}, - {text: 'い', reading: ''} - ] - ], - [ - ['美味しい', 'おいしい'], - [ - {text: '美味', reading: 'おい'}, - {text: 'しい', reading: ''} - ] - ], - [ - ['食べ物', 'たべもの'], - [ - {text: '食', reading: 'た'}, - {text: 'べ', reading: ''}, - {text: '物', reading: 'もの'} - ] - ], - [ - ['試し切り', 'ためしぎり'], - [ - {text: '試', reading: 'ため'}, - {text: 'し', reading: ''}, - {text: '切', reading: 'ぎ'}, - {text: 'り', reading: ''} - ] - ], - // Ambiguous - [ - ['飼い犬', 'かいいぬ'], - [ - {text: '飼い犬', reading: 'かいいぬ'} - ] - ], - [ - ['長い間', 'ながいあいだ'], - [ - {text: '長い間', reading: 'ながいあいだ'} - ] - ], - // Same/empty reading - [ - ['飼い犬', ''], - [ - {text: '飼い犬', reading: ''} - ] - ], - [ - ['かいいぬ', 'かいいぬ'], - [ - {text: 'かいいぬ', reading: ''} - ] - ], - [ - ['かいぬ', 'かいぬ'], - [ - {text: 'かいぬ', reading: ''} - ] - ], - // Misc - [ - ['月', 'か'], - [ - {text: '月', reading: 'か'} - ] - ], - [ - ['月', 'カ'], - [ - {text: '月', reading: 'カ'} - ] - ], - // Mismatched kana readings - [ - ['有り難う', 'アリガトウ'], - [ - {text: '有', reading: 'ア'}, - {text: 'り', reading: 'リ'}, - {text: '難', reading: 'ガト'}, - {text: 'う', reading: 'ウ'} - ] - ], - [ - ['ありがとう', 'アリガトウ'], - [ - {text: 'ありがとう', reading: 'アリガトウ'} - ] - ], - // Mismatched kana readings (real examples) - [ - ['カ月', 'かげつ'], - [ - {text: 'カ', reading: 'か'}, - {text: '月', reading: 'げつ'} - ] - ], - [ - ['序ノ口', 'じょのくち'], - [ - {text: '序', reading: 'じょ'}, - {text: 'ノ', reading: 'の'}, - {text: '口', reading: 'くち'} - ] - ], - [ - ['スズメの涙', 'すずめのなみだ'], - [ - {text: 'スズメ', reading: 'すずめ'}, - {text: 'の', reading: ''}, - {text: '涙', reading: 'なみだ'} - ] - ], - [ - ['二カ所', 'にかしょ'], - [ - {text: '二', reading: 'に'}, - {text: 'カ', reading: 'か'}, - {text: '所', reading: 'しょ'} - ] - ], - [ - ['八ツ橋', 'やつはし'], - [ - {text: '八', reading: 'や'}, - {text: 'ツ', reading: 'つ'}, - {text: '橋', reading: 'はし'} - ] - ], - [ - ['八ツ橋', 'やつはし'], - [ - {text: '八', reading: 'や'}, - {text: 'ツ', reading: 'つ'}, - {text: '橋', reading: 'はし'} - ] - ], - [ - ['一カ月', 'いっかげつ'], - [ - {text: '一', reading: 'いっ'}, - {text: 'カ', reading: 'か'}, - {text: '月', reading: 'げつ'} - ] - ], - [ - ['一カ所', 'いっかしょ'], - [ - {text: '一', reading: 'いっ'}, - {text: 'カ', reading: 'か'}, - {text: '所', reading: 'しょ'} - ] - ], - [ - ['カ所', 'かしょ'], - [ - {text: 'カ', reading: 'か'}, - {text: '所', reading: 'しょ'} - ] - ], - [ - ['数カ月', 'すうかげつ'], - [ - {text: '数', reading: 'すう'}, - {text: 'カ', reading: 'か'}, - {text: '月', reading: 'げつ'} - ] - ], - [ - ['くノ一', 'くのいち'], - [ - {text: 'く', reading: ''}, - {text: 'ノ', reading: 'の'}, - {text: '一', reading: 'いち'} - ] - ], - [ - ['くノ一', 'くのいち'], - [ - {text: 'く', reading: ''}, - {text: 'ノ', reading: 'の'}, - {text: '一', reading: 'いち'} - ] - ], - [ - ['数カ国', 'すうかこく'], - [ - {text: '数', reading: 'すう'}, - {text: 'カ', reading: 'か'}, - {text: '国', reading: 'こく'} - ] - ], - [ - ['数カ所', 'すうかしょ'], - [ - {text: '数', reading: 'すう'}, - {text: 'カ', reading: 'か'}, - {text: '所', reading: 'しょ'} - ] - ], - [ - ['壇ノ浦の戦い', 'だんのうらのたたかい'], - [ - {text: '壇', reading: 'だん'}, - {text: 'ノ', reading: 'の'}, - {text: '浦', reading: 'うら'}, - {text: 'の', reading: ''}, - {text: '戦', reading: 'たたか'}, - {text: 'い', reading: ''} - ] - ], - [ - ['壇ノ浦の戦', 'だんのうらのたたかい'], - [ - {text: '壇', reading: 'だん'}, - {text: 'ノ', reading: 'の'}, - {text: '浦', reading: 'うら'}, - {text: 'の', reading: ''}, - {text: '戦', reading: 'たたかい'} - ] - ], - [ - ['序ノ口格', 'じょのくちかく'], - [ - {text: '序', reading: 'じょ'}, - {text: 'ノ', reading: 'の'}, - {text: '口格', reading: 'くちかく'} - ] - ], - [ - ['二カ国語', 'にかこくご'], - [ - {text: '二', reading: 'に'}, - {text: 'カ', reading: 'か'}, - {text: '国語', reading: 'こくご'} - ] - ], - [ - ['カ国', 'かこく'], - [ - {text: 'カ', reading: 'か'}, - {text: '国', reading: 'こく'} - ] - ], - [ - ['カ国語', 'かこくご'], - [ - {text: 'カ', reading: 'か'}, - {text: '国語', reading: 'こくご'} - ] - ], - [ - ['壇ノ浦の合戦', 'だんのうらのかっせん'], - [ - {text: '壇', reading: 'だん'}, - {text: 'ノ', reading: 'の'}, - {text: '浦', reading: 'うら'}, - {text: 'の', reading: ''}, - {text: '合戦', reading: 'かっせん'} - ] - ], - [ - ['一タ偏', 'いちたへん'], - [ - {text: '一', reading: 'いち'}, - {text: 'タ', reading: 'た'}, - {text: '偏', reading: 'へん'} - ] - ], - [ - ['ル又', 'るまた'], - [ - {text: 'ル', reading: 'る'}, - {text: '又', reading: 'また'} - ] - ], - [ - ['ノ木偏', 'のぎへん'], - [ - {text: 'ノ', reading: 'の'}, - {text: '木偏', reading: 'ぎへん'} - ] - ], - [ - ['一ノ貝', 'いちのかい'], - [ - {text: '一', reading: 'いち'}, - {text: 'ノ', reading: 'の'}, - {text: '貝', reading: 'かい'} - ] - ], - [ - ['虎ノ門事件', 'とらのもんじけん'], - [ - {text: '虎', reading: 'とら'}, - {text: 'ノ', reading: 'の'}, - {text: '門事件', reading: 'もんじけん'} - ] - ], - [ - ['教育ニ関スル勅語', 'きょういくにかんするちょくご'], - [ - {text: '教育', reading: 'きょういく'}, - {text: 'ニ', reading: 'に'}, - {text: '関', reading: 'かん'}, - {text: 'スル', reading: 'する'}, - {text: '勅語', reading: 'ちょくご'} - ] - ], - [ - ['二カ年', 'にかねん'], - [ - {text: '二', reading: 'に'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['三カ年', 'さんかねん'], - [ - {text: '三', reading: 'さん'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['四カ年', 'よんかねん'], - [ - {text: '四', reading: 'よん'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['五カ年', 'ごかねん'], - [ - {text: '五', reading: 'ご'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['六カ年', 'ろっかねん'], - [ - {text: '六', reading: 'ろっ'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['七カ年', 'ななかねん'], - [ - {text: '七', reading: 'なな'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['八カ年', 'はちかねん'], - [ - {text: '八', reading: 'はち'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['九カ年', 'きゅうかねん'], - [ - {text: '九', reading: 'きゅう'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['十カ年', 'じゅうかねん'], - [ - {text: '十', reading: 'じゅう'}, - {text: 'カ', reading: 'か'}, - {text: '年', reading: 'ねん'} - ] - ], - [ - ['鏡ノ間', 'かがみのま'], - [ - {text: '鏡', reading: 'かがみ'}, - {text: 'ノ', reading: 'の'}, - {text: '間', reading: 'ま'} - ] - ], - [ - ['鏡ノ間', 'かがみのま'], - [ - {text: '鏡', reading: 'かがみ'}, - {text: 'ノ', reading: 'の'}, - {text: '間', reading: 'ま'} - ] - ], - [ - ['ページ違反', 'ぺーじいはん'], - [ - {text: 'ペ', reading: 'ぺ'}, - {text: 'ー', reading: ''}, - {text: 'ジ', reading: 'じ'}, - {text: '違反', reading: 'いはん'} - ] - ], - // Mismatched kana - [ - ['サボる', 'サボル'], - [ - {text: 'サボ', reading: ''}, - {text: 'る', reading: 'ル'} - ] - ], - // Reading starts with term, but has remainder characters - [ - ['シック', 'シック・ビルしょうこうぐん'], - [ - {text: 'シック', reading: 'シック・ビルしょうこうぐん'} - ] - ], - // Kanji distribution tests - [ - ['逸らす', 'そらす'], - [ - {text: '逸', reading: 'そ'}, - {text: 'らす', reading: ''} - ] - ], - [ - ['逸らす', 'そらす'], - [ - {text: '逸', reading: 'そ'}, - {text: 'らす', reading: ''} - ] - ] - ]; - - for (const [[term, reading], expected] of data) { - const actual = jp.distributeFurigana(term, reading); - vm.assert.deepStrictEqual(actual, expected); - } -} - -/** */ -function testDistributeFuriganaInflected() { - /** @type {[input: [term: string, reading: string, source: string], expected: {text: string, reading: string}[]][]} */ - const data = [ - [ - ['美味しい', 'おいしい', '美味しかた'], - [ - {text: '美味', reading: 'おい'}, - {text: 'しかた', reading: ''} - ] - ], - [ - ['食べる', 'たべる', '食べた'], - [ - {text: '食', reading: 'た'}, - {text: 'べた', reading: ''} - ] - ], - [ - ['迄に', 'までに', 'までに'], - [ - {text: 'までに', reading: ''} - ] - ], - [ - ['行う', 'おこなう', 'おこなわなかった'], - [ - {text: 'おこなわなかった', reading: ''} - ] - ], - [ - ['いい', 'いい', 'イイ'], - [ - {text: 'イイ', reading: ''} - ] - ], - [ - ['否か', 'いなか', '否カ'], - [ - {text: '否', reading: 'いな'}, - {text: 'カ', reading: 'か'} - ] - ] - ]; - - for (const [[term, reading, source], expected] of data) { - const actual = jp.distributeFuriganaInflected(term, reading, source); - vm.assert.deepStrictEqual(actual, expected); - } -} - -/** */ -function testCollapseEmphaticSequences() { - /** @type {[input: [text: string, fullCollapse: boolean], output: [expected: string, expectedSourceMapping: number[]]][]} */ - const data = [ - [['かこい', false], ['かこい', [1, 1, 1]]], - [['かこい', true], ['かこい', [1, 1, 1]]], - [['かっこい', false], ['かっこい', [1, 1, 1, 1]]], - [['かっこい', true], ['かこい', [2, 1, 1]]], - [['かっっこい', false], ['かっこい', [1, 2, 1, 1]]], - [['かっっこい', true], ['かこい', [3, 1, 1]]], - [['かっっっこい', false], ['かっこい', [1, 3, 1, 1]]], - [['かっっっこい', true], ['かこい', [4, 1, 1]]], - - [['こい', false], ['こい', [1, 1]]], - [['こい', true], ['こい', [1, 1]]], - [['っこい', false], ['っこい', [1, 1, 1]]], - [['っこい', true], ['こい', [2, 1]]], - [['っっこい', false], ['っこい', [2, 1, 1]]], - [['っっこい', true], ['こい', [3, 1]]], - [['っっっこい', false], ['っこい', [3, 1, 1]]], - [['っっっこい', true], ['こい', [4, 1]]], - - [['すごい', false], ['すごい', [1, 1, 1]]], - [['すごい', true], ['すごい', [1, 1, 1]]], - [['すごーい', false], ['すごーい', [1, 1, 1, 1]]], - [['すごーい', true], ['すごい', [1, 2, 1]]], - [['すごーーい', false], ['すごーい', [1, 1, 2, 1]]], - [['すごーーい', true], ['すごい', [1, 3, 1]]], - [['すっごーい', false], ['すっごーい', [1, 1, 1, 1, 1]]], - [['すっごーい', true], ['すごい', [2, 2, 1]]], - [['すっっごーーい', false], ['すっごーい', [1, 2, 1, 2, 1]]], - [['すっっごーーい', true], ['すごい', [3, 3, 1]]], - - [['', false], ['', []]], - [['', true], ['', []]], - [['っ', false], ['っ', [1]]], - [['っ', true], ['', [1]]], - [['っっ', false], ['っ', [2]]], - [['っっ', true], ['', [2]]], - [['っっっ', false], ['っ', [3]]], - [['っっっ', true], ['', [3]]] - ]; - - for (const [[text, fullCollapse], [expected, expectedSourceMapping]] of data) { - const sourceMap = new TextSourceMap2(text); - const actual1 = jp.collapseEmphaticSequences(text, fullCollapse, null); - const actual2 = jp.collapseEmphaticSequences(text, fullCollapse, sourceMap); - assert.strictEqual(actual1, expected); - assert.strictEqual(actual2, expected); - if (typeof expectedSourceMapping !== 'undefined') { - assert.ok(sourceMap.equals(new TextSourceMap2(text, expectedSourceMapping))); - } - } -} - -/** */ -function testIsMoraPitchHigh() { - /** @type {[input: [moraIndex: number, pitchAccentDownstepPosition: number], expected: boolean][]} */ - const data = [ - [[0, 0], false], - [[1, 0], true], - [[2, 0], true], - [[3, 0], true], - - [[0, 1], true], - [[1, 1], false], - [[2, 1], false], - [[3, 1], false], - - [[0, 2], false], - [[1, 2], true], - [[2, 2], false], - [[3, 2], false], - - [[0, 3], false], - [[1, 3], true], - [[2, 3], true], - [[3, 3], false], - - [[0, 4], false], - [[1, 4], true], - [[2, 4], true], - [[3, 4], true] - ]; - - for (const [[moraIndex, pitchAccentDownstepPosition], expected] of data) { - const actual = jp.isMoraPitchHigh(moraIndex, pitchAccentDownstepPosition); - assert.strictEqual(actual, expected); - } -} - -/** */ -function testGetKanaMorae() { - /** @type {[text: string, expected: string[]][]} */ - const data = [ - ['かこ', ['か', 'こ']], - ['かっこ', ['か', 'っ', 'こ']], - ['カコ', ['カ', 'コ']], - ['カッコ', ['カ', 'ッ', 'コ']], - ['コート', ['コ', 'ー', 'ト']], - ['ちゃんと', ['ちゃ', 'ん', 'と']], - ['とうきょう', ['と', 'う', 'きょ', 'う']], - ['ぎゅう', ['ぎゅ', 'う']], - ['ディスコ', ['ディ', 'ス', 'コ']] - ]; - - for (const [text, expected] of data) { - const actual = jp.getKanaMorae(text); - vm.assert.deepStrictEqual(actual, expected); - } -} - - -/** */ -function main() { - testIsCodePointKanji(); - testIsCodePointKana(); - testIsCodePointJapanese(); - testIsStringEntirelyKana(); - testIsStringPartiallyJapanese(); - testConvertKatakanaToHiragana(); - testConvertHiraganaToKatakana(); - testConvertToRomaji(); - testConvertNumericToFullWidth(); - testConvertHalfWidthKanaToFullWidth(); - testConvertAlphabeticToKana(); - testDistributeFurigana(); - testDistributeFuriganaInflected(); - testCollapseEmphaticSequences(); - testIsMoraPitchHigh(); - testGetKanaMorae(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-json-schema.js b/test/test-json-schema.js deleted file mode 100644 index f9cb023c..00000000 --- a/test/test-json-schema.js +++ /dev/null @@ -1,1048 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM(); -vm.execute([ - 'js/core.js', - 'js/core/extension-error.js', - 'js/general/cache-map.js', - 'js/data/json-schema.js' -]); -/** @type {typeof JsonSchema} */ -const JsonSchema2 = vm.getSingle('JsonSchema'); - - -/** - * @param {import('json-schema').Schema} schema - * @param {unknown} value - * @returns {boolean} - */ -function schemaValidate(schema, value) { - return new JsonSchema2(schema).isValid(value); -} - -/** - * @param {import('json-schema').Schema} schema - * @param {unknown} value - * @returns {import('json-schema').Value} - */ -function getValidValueOrDefault(schema, value) { - return new JsonSchema2(schema).getValidValueOrDefault(value); -} - -/** - * @param {import('json-schema').Schema} schema - * @param {import('json-schema').Value} value - * @returns {import('json-schema').Value} - */ -function createProxy(schema, value) { - return new JsonSchema2(schema).createProxy(value); -} - -/** - * @template T - * @param {T} value - * @returns {T} - */ -function clone(value) { - return JSON.parse(JSON.stringify(value)); -} - - -/** */ -function testValidate1() { - /** @type {import('json-schema').Schema} */ - const schema = { - allOf: [ - { - type: 'number' - }, - { - anyOf: [ - {minimum: 10, maximum: 100}, - {minimum: -100, maximum: -10} - ] - }, - { - oneOf: [ - {multipleOf: 3}, - {multipleOf: 5} - ] - }, - { - not: { - anyOf: [ - {multipleOf: 20} - ] - } - } - ] - }; - - /** - * @param {number} value - * @returns {boolean} - */ - const jsValidate = (value) => { - return ( - typeof value === 'number' && - ( - (value >= 10 && value <= 100) || - (value >= -100 && value <= -10) - ) && - ( - ( - (value % 3) === 0 || - (value % 5) === 0 - ) && - (value % 15) !== 0 - ) && - (value % 20) !== 0 - ); - }; - - for (let i = -111; i <= 111; i++) { - const actual = schemaValidate(schema, i); - const expected = jsValidate(i); - assert.strictEqual(actual, expected, `i=${i}; expected=${expected}; actual=${actual}`); - } -} - -/** */ -function testValidate2() { - /** @type {{schema: import('json-schema').Schema, inputs: {expected: boolean, value: unknown}[]}[]} */ - const data = [ - // String tests - { - schema: { - type: 'string' - }, - inputs: [ - {expected: false, value: null}, - {expected: false, value: void 0}, - {expected: false, value: 0}, - {expected: false, value: {}}, - {expected: false, value: []}, - {expected: true, value: ''} - ] - }, - { - schema: { - type: 'string', - minLength: 2 - }, - inputs: [ - {expected: false, value: ''}, - {expected: false, value: '1'}, - {expected: true, value: '12'}, - {expected: true, value: '123'} - ] - }, - { - schema: { - type: 'string', - maxLength: 2 - }, - inputs: [ - {expected: true, value: ''}, - {expected: true, value: '1'}, - {expected: true, value: '12'}, - {expected: false, value: '123'} - ] - }, - { - schema: { - type: 'string', - pattern: 'test' - }, - inputs: [ - {expected: false, value: ''}, - {expected: true, value: 'test'}, - {expected: false, value: 'TEST'}, - {expected: true, value: 'ABCtestDEF'}, - {expected: false, value: 'ABCTESTDEF'} - ] - }, - { - schema: { - type: 'string', - pattern: '^test$' - }, - inputs: [ - {expected: false, value: ''}, - {expected: true, value: 'test'}, - {expected: false, value: 'TEST'}, - {expected: false, value: 'ABCtestDEF'}, - {expected: false, value: 'ABCTESTDEF'} - ] - }, - { - schema: { - type: 'string', - pattern: '^test$', - patternFlags: 'i' - }, - inputs: [ - {expected: false, value: ''}, - {expected: true, value: 'test'}, - {expected: true, value: 'TEST'}, - {expected: false, value: 'ABCtestDEF'}, - {expected: false, value: 'ABCTESTDEF'} - ] - }, - { - schema: { - type: 'string', - pattern: '*' - }, - inputs: [ - {expected: false, value: ''} - ] - }, - { - schema: { - type: 'string', - pattern: '.', - patternFlags: '?' - }, - inputs: [ - {expected: false, value: ''} - ] - }, - - // Const tests - { - schema: { - const: 32 - }, - inputs: [ - {expected: true, value: 32}, - {expected: false, value: 0}, - {expected: false, value: '32'}, - {expected: false, value: null}, - {expected: false, value: {a: 'b'}}, - {expected: false, value: [1, 2, 3]} - ] - }, - { - schema: { - const: '32' - }, - inputs: [ - {expected: false, value: 32}, - {expected: false, value: 0}, - {expected: true, value: '32'}, - {expected: false, value: null}, - {expected: false, value: {a: 'b'}}, - {expected: false, value: [1, 2, 3]} - ] - }, - { - schema: { - const: null - }, - inputs: [ - {expected: false, value: 32}, - {expected: false, value: 0}, - {expected: false, value: '32'}, - {expected: true, value: null}, - {expected: false, value: {a: 'b'}}, - {expected: false, value: [1, 2, 3]} - ] - }, - { - schema: { - const: {a: 'b'} - }, - inputs: [ - {expected: false, value: 32}, - {expected: false, value: 0}, - {expected: false, value: '32'}, - {expected: false, value: null}, - {expected: false, value: {a: 'b'}}, - {expected: false, value: [1, 2, 3]} - ] - }, - { - schema: { - const: [1, 2, 3] - }, - inputs: [ - {expected: false, value: 32}, - {expected: false, value: 0}, - {expected: false, value: '32'}, - {expected: false, value: null}, - {expected: false, value: {a: 'b'}}, - {expected: false, value: [1, 2, 3]} - ] - }, - - // Array contains tests - { - schema: { - type: 'array', - contains: {const: 32} - }, - inputs: [ - {expected: false, value: []}, - {expected: true, value: [32]}, - {expected: true, value: [1, 32]}, - {expected: true, value: [1, 32, 1]}, - {expected: false, value: [33]}, - {expected: false, value: [1, 33]}, - {expected: false, value: [1, 33, 1]} - ] - }, - - // Number limits tests - { - schema: { - type: 'number', - minimum: 0 - }, - inputs: [ - {expected: false, value: -1}, - {expected: true, value: 0}, - {expected: true, value: 1} - ] - }, - { - schema: { - type: 'number', - exclusiveMinimum: 0 - }, - inputs: [ - {expected: false, value: -1}, - {expected: false, value: 0}, - {expected: true, value: 1} - ] - }, - { - schema: { - type: 'number', - maximum: 0 - }, - inputs: [ - {expected: true, value: -1}, - {expected: true, value: 0}, - {expected: false, value: 1} - ] - }, - { - schema: { - type: 'number', - exclusiveMaximum: 0 - }, - inputs: [ - {expected: true, value: -1}, - {expected: false, value: 0}, - {expected: false, value: 1} - ] - }, - - // Integer limits tests - { - schema: { - type: 'integer', - minimum: 0 - }, - inputs: [ - {expected: false, value: -1}, - {expected: true, value: 0}, - {expected: true, value: 1} - ] - }, - { - schema: { - type: 'integer', - exclusiveMinimum: 0 - }, - inputs: [ - {expected: false, value: -1}, - {expected: false, value: 0}, - {expected: true, value: 1} - ] - }, - { - schema: { - type: 'integer', - maximum: 0 - }, - inputs: [ - {expected: true, value: -1}, - {expected: true, value: 0}, - {expected: false, value: 1} - ] - }, - { - schema: { - type: 'integer', - exclusiveMaximum: 0 - }, - inputs: [ - {expected: true, value: -1}, - {expected: false, value: 0}, - {expected: false, value: 1} - ] - }, - { - schema: { - type: 'integer', - multipleOf: 2 - }, - inputs: [ - {expected: true, value: -2}, - {expected: false, value: -1}, - {expected: true, value: 0}, - {expected: false, value: 1}, - {expected: true, value: 2} - ] - }, - - // Numeric type tests - { - schema: { - type: 'number' - }, - inputs: [ - {expected: true, value: 0}, - {expected: true, value: 0.5}, - {expected: true, value: 1}, - {expected: false, value: '0'}, - {expected: false, value: null}, - {expected: false, value: []}, - {expected: false, value: {}} - ] - }, - { - schema: { - type: 'integer' - }, - inputs: [ - {expected: true, value: 0}, - {expected: false, value: 0.5}, - {expected: true, value: 1}, - {expected: false, value: '0'}, - {expected: false, value: null}, - {expected: false, value: []}, - {expected: false, value: {}} - ] - }, - - // Reference tests - { - schema: { - definitions: { - example: { - type: 'number' - } - }, - $ref: '#/definitions/example' - }, - inputs: [ - {expected: true, value: 0}, - {expected: true, value: 0.5}, - {expected: true, value: 1}, - {expected: false, value: '0'}, - {expected: false, value: null}, - {expected: false, value: []}, - {expected: false, value: {}} - ] - }, - { - schema: { - definitions: { - example: { - type: 'integer' - } - }, - $ref: '#/definitions/example' - }, - inputs: [ - {expected: true, value: 0}, - {expected: false, value: 0.5}, - {expected: true, value: 1}, - {expected: false, value: '0'}, - {expected: false, value: null}, - {expected: false, value: []}, - {expected: false, value: {}} - ] - }, - { - schema: { - definitions: { - example: { - type: 'object', - additionalProperties: false, - properties: { - test: { - $ref: '#/definitions/example' - } - } - } - }, - $ref: '#/definitions/example' - }, - inputs: [ - {expected: false, value: 0}, - {expected: false, value: 0.5}, - {expected: false, value: 1}, - {expected: false, value: '0'}, - {expected: false, value: null}, - {expected: false, value: []}, - {expected: true, value: {}}, - {expected: false, value: {test: 0}}, - {expected: false, value: {test: 0.5}}, - {expected: false, value: {test: 1}}, - {expected: false, value: {test: '0'}}, - {expected: false, value: {test: null}}, - {expected: false, value: {test: []}}, - {expected: true, value: {test: {}}}, - {expected: true, value: {test: {test: {}}}}, - {expected: true, value: {test: {test: {test: {}}}}} - ] - } - ]; - - for (const {schema, inputs} of data) { - for (const {expected, value} of inputs) { - const actual = schemaValidate(schema, value); - assert.strictEqual(actual, expected); - } - } -} - - -/** */ -function testGetValidValueOrDefault1() { - /** @type {{schema: import('json-schema').Schema, inputs: [value: unknown, expected: unknown][]}[]} */ - const data = [ - // Test value defaulting on objects with additionalProperties=false - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'string', - default: 'default' - } - }, - additionalProperties: false - }, - inputs: [ - [ - void 0, - {test: 'default'} - ], - [ - null, - {test: 'default'} - ], - [ - 0, - {test: 'default'} - ], - [ - '', - {test: 'default'} - ], - [ - [], - {test: 'default'} - ], - [ - {}, - {test: 'default'} - ], - [ - {test: 'value'}, - {test: 'value'} - ], - [ - {test2: 'value2'}, - {test: 'default'} - ], - [ - {test: 'value', test2: 'value2'}, - {test: 'value'} - ] - ] - }, - - // Test value defaulting on objects with additionalProperties=true - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'string', - default: 'default' - } - }, - additionalProperties: true - }, - inputs: [ - [ - {}, - {test: 'default'} - ], - [ - {test: 'value'}, - {test: 'value'} - ], - [ - {test2: 'value2'}, - {test: 'default', test2: 'value2'} - ], - [ - {test: 'value', test2: 'value2'}, - {test: 'value', test2: 'value2'} - ] - ] - }, - - // Test value defaulting on objects with additionalProperties={schema} - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'string', - default: 'default' - } - }, - additionalProperties: { - type: 'number', - default: 10 - } - }, - inputs: [ - [ - {}, - {test: 'default'} - ], - [ - {test: 'value'}, - {test: 'value'} - ], - [ - {test2: 'value2'}, - {test: 'default', test2: 10} - ], - [ - {test: 'value', test2: 'value2'}, - {test: 'value', test2: 10} - ], - [ - {test2: 2}, - {test: 'default', test2: 2} - ], - [ - {test: 'value', test2: 2}, - {test: 'value', test2: 2} - ], - [ - {test: 'value', test2: 2, test3: null}, - {test: 'value', test2: 2, test3: 10} - ], - [ - {test: 'value', test2: 2, test3: void 0}, - {test: 'value', test2: 2, test3: 10} - ] - ] - }, - - // Test value defaulting where hasOwnProperty is false - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'string', - default: 'default' - } - } - }, - inputs: [ - [ - {}, - {test: 'default'} - ], - [ - {test: 'value'}, - {test: 'value'} - ], - [ - Object.create({test: 'value'}), - {test: 'default'} - ] - ] - }, - { - schema: { - type: 'object', - required: ['toString'], - properties: { - toString: /** @type {import('json-schema').SchemaObject} */ ({ - type: 'string', - default: 'default' - }) - } - }, - inputs: [ - [ - {}, - {toString: 'default'} - ], - [ - {toString: 'value'}, - {toString: 'value'} - ], - [ - Object.create({toString: 'value'}), - {toString: 'default'} - ] - ] - }, - - // Test enum - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'string', - default: 'value1', - enum: ['value1', 'value2', 'value3'] - } - } - }, - inputs: [ - [ - {test: 'value1'}, - {test: 'value1'} - ], - [ - {test: 'value2'}, - {test: 'value2'} - ], - [ - {test: 'value3'}, - {test: 'value3'} - ], - [ - {test: 'value4'}, - {test: 'value1'} - ] - ] - }, - - // Test valid vs invalid default - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'integer', - default: 2, - minimum: 1 - } - } - }, - inputs: [ - [ - {test: -1}, - {test: 2} - ] - ] - }, - { - schema: { - type: 'object', - required: ['test'], - properties: { - test: { - type: 'integer', - default: 1, - minimum: 2 - } - } - }, - inputs: [ - [ - {test: -1}, - {test: -1} - ] - ] - }, - - // Test references - { - schema: { - definitions: { - example: { - type: 'number', - default: 0 - } - }, - $ref: '#/definitions/example' - }, - inputs: [ - [ - 1, - 1 - ], - [ - null, - 0 - ], - [ - 'test', - 0 - ], - [ - {test: 'value'}, - 0 - ] - ] - }, - { - schema: { - definitions: { - example: { - type: 'object', - additionalProperties: false, - properties: { - test: { - $ref: '#/definitions/example' - } - } - } - }, - $ref: '#/definitions/example' - }, - inputs: [ - [ - 1, - {} - ], - [ - null, - {} - ], - [ - 'test', - {} - ], - [ - {}, - {} - ], - [ - {test: {}}, - {test: {}} - ], - [ - {test: 'value'}, - {test: {}} - ], - [ - {test: {test: {}}}, - {test: {test: {}}} - ] - ] - } - ]; - - for (const {schema, inputs} of data) { - for (const [value, expected] of inputs) { - const actual = getValidValueOrDefault(schema, value); - vm.assert.deepStrictEqual(actual, expected); - } - } -} - - -/** */ -function testProxy1() { - /** @type {{schema: import('json-schema').Schema, tests: {error: boolean, value?: import('json-schema').Value, action: (value: import('core').SafeAny) => void}[]}[]} */ - const data = [ - // Object tests - { - schema: { - type: 'object', - required: ['test'], - additionalProperties: false, - properties: { - test: { - type: 'string', - default: 'default' - } - } - }, - tests: [ - {error: false, value: {test: 'default'}, action: (value) => { value.test = 'string'; }}, - {error: true, value: {test: 'default'}, action: (value) => { value.test = null; }}, - {error: true, value: {test: 'default'}, action: (value) => { delete value.test; }}, - {error: true, value: {test: 'default'}, action: (value) => { value.test2 = 'string'; }}, - {error: false, value: {test: 'default'}, action: (value) => { delete value.test2; }} - ] - }, - { - schema: { - type: 'object', - required: ['test'], - additionalProperties: true, - properties: { - test: { - type: 'string', - default: 'default' - } - } - }, - tests: [ - {error: false, value: {test: 'default'}, action: (value) => { value.test = 'string'; }}, - {error: true, value: {test: 'default'}, action: (value) => { value.test = null; }}, - {error: true, value: {test: 'default'}, action: (value) => { delete value.test; }}, - {error: false, value: {test: 'default'}, action: (value) => { value.test2 = 'string'; }}, - {error: false, value: {test: 'default'}, action: (value) => { delete value.test2; }} - ] - }, - { - schema: { - type: 'object', - required: ['test1'], - additionalProperties: false, - properties: { - test1: { - type: 'object', - required: ['test2'], - additionalProperties: false, - properties: { - test2: { - type: 'object', - required: ['test3'], - additionalProperties: false, - properties: { - test3: { - type: 'string', - default: 'default' - } - } - } - } - } - } - }, - tests: [ - {error: false, action: (value) => { value.test1.test2.test3 = 'string'; }}, - {error: true, action: (value) => { value.test1.test2.test3 = null; }}, - {error: true, action: (value) => { delete value.test1.test2.test3; }}, - {error: true, action: (value) => { value.test1.test2 = null; }}, - {error: true, action: (value) => { value.test1 = null; }}, - {error: true, action: (value) => { value.test4 = 'string'; }}, - {error: false, action: (value) => { delete value.test4; }} - ] - }, - - // Array tests - { - schema: { - type: 'array', - items: { - type: 'string', - default: 'default' - } - }, - tests: [ - {error: false, value: ['default'], action: (value) => { value[0] = 'string'; }}, - {error: true, value: ['default'], action: (value) => { value[0] = null; }}, - {error: false, value: ['default'], action: (value) => { delete value[0]; }}, - {error: false, value: ['default'], action: (value) => { value[1] = 'string'; }}, - {error: false, value: ['default'], action: (value) => { - value[1] = 'string'; - if (value.length !== 2) { throw new Error(`Invalid length; expected=2; actual=${value.length}`); } - if (typeof value.push !== 'function') { throw new Error(`Invalid push; expected=function; actual=${typeof value.push}`); } - }} - ] - }, - - // Reference tests - { - schema: { - definitions: { - example: { - type: 'object', - additionalProperties: false, - properties: { - test: { - $ref: '#/definitions/example' - } - } - } - }, - $ref: '#/definitions/example' - }, - tests: [ - {error: false, value: {}, action: (value) => { value.test = {}; }}, - {error: false, value: {}, action: (value) => { value.test = {}; value.test.test = {}; }}, - {error: false, value: {}, action: (value) => { value.test = {test: {}}; }}, - {error: true, value: {}, action: (value) => { value.test = null; }}, - {error: true, value: {}, action: (value) => { value.test = 'string'; }}, - {error: true, value: {}, action: (value) => { value.test = {}; value.test.test = 'string'; }}, - {error: true, value: {}, action: (value) => { value.test = {test: 'string'}; }} - ] - } - ]; - - for (const {schema, tests} of data) { - for (let {error, value, action} of tests) { - if (typeof value === 'undefined') { value = getValidValueOrDefault(schema, void 0); } - value = clone(value); - assert.ok(schemaValidate(schema, value)); - const valueProxy = createProxy(schema, value); - if (error) { - assert.throws(() => action(valueProxy)); - } else { - assert.doesNotThrow(() => action(valueProxy)); - } - } - } -} - - -/** */ -function main() { - testValidate1(); - testValidate2(); - testGetValidValueOrDefault1(); - testProxy1(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-manifest.js b/test/test-manifest.js deleted file mode 100644 index 32a498e1..00000000 --- a/test/test-manifest.js +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {ManifestUtil} = require('../dev/manifest-util'); - - -/** - * @returns {string} - */ -function loadManifestString() { - const manifestPath = path.join(__dirname, '..', 'ext', 'manifest.json'); - return fs.readFileSync(manifestPath, {encoding: 'utf8'}); -} - -/** */ -function validateManifest() { - const manifestUtil = new ManifestUtil(); - const manifest1 = loadManifestString(); - const manifest2 = ManifestUtil.createManifestString(manifestUtil.getManifest()); - assert.strictEqual(manifest1, manifest2, 'Manifest data does not match.'); -} - - -/** */ -function main() { - validateManifest(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-object-property-accessor.js b/test/test-object-property-accessor.js deleted file mode 100644 index 8826d6a9..00000000 --- a/test/test-object-property-accessor.js +++ /dev/null @@ -1,458 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM({}); -vm.execute('js/general/object-property-accessor.js'); -/** @type {typeof ObjectPropertyAccessor} */ -const ObjectPropertyAccessor2 = vm.getSingle('ObjectPropertyAccessor'); - - -/** - * @returns {import('core').UnknownObject} - */ -function createTestObject() { - return { - 0: null, - value1: { - value2: {}, - value3: [], - value4: null - }, - value5: [ - {}, - [], - null - ] - }; -} - - -/** */ -function testGet1() { - /** @type {[pathArray: (string|number)[], getExpected: (object: import('core').SafeAny) => unknown][]} */ - const data = [ - [[], (object) => object], - [['0'], (object) => object['0']], - [['value1'], (object) => object.value1], - [['value1', 'value2'], (object) => object.value1.value2], - [['value1', 'value3'], (object) => object.value1.value3], - [['value1', 'value4'], (object) => object.value1.value4], - [['value5'], (object) => object.value5], - [['value5', 0], (object) => object.value5[0]], - [['value5', 1], (object) => object.value5[1]], - [['value5', 2], (object) => object.value5[2]] - ]; - - for (const [pathArray, getExpected] of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - const expected = getExpected(object); - - assert.strictEqual(accessor.get(pathArray), expected); - } -} - -/** */ -function testGet2() { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - /** @type {[pathArray: (string|number)[], message: string][]} */ - const data = [ - [[0], 'Invalid path: [0]'], - [['0', 'invalid'], 'Invalid path: ["0"].invalid'], - [['invalid'], 'Invalid path: invalid'], - [['value1', 'invalid'], 'Invalid path: value1.invalid'], - [['value1', 'value2', 'invalid'], 'Invalid path: value1.value2.invalid'], - [['value1', 'value2', 0], 'Invalid path: value1.value2[0]'], - [['value1', 'value3', 'invalid'], 'Invalid path: value1.value3.invalid'], - [['value1', 'value3', 0], 'Invalid path: value1.value3[0]'], - [['value1', 'value4', 'invalid'], 'Invalid path: value1.value4.invalid'], - [['value1', 'value4', 0], 'Invalid path: value1.value4[0]'], - [['value5', 'length'], 'Invalid path: value5.length'], - [['value5', 0, 'invalid'], 'Invalid path: value5[0].invalid'], - [['value5', 0, 0], 'Invalid path: value5[0][0]'], - [['value5', 1, 'invalid'], 'Invalid path: value5[1].invalid'], - [['value5', 1, 0], 'Invalid path: value5[1][0]'], - [['value5', 2, 'invalid'], 'Invalid path: value5[2].invalid'], - [['value5', 2, 0], 'Invalid path: value5[2][0]'], - [['value5', 2, 0, 'invalid'], 'Invalid path: value5[2][0]'], - [['value5', 2.5], 'Invalid index'] - ]; - - for (const [pathArray, message] of data) { - assert.throws(() => accessor.get(pathArray), {message}); - } -} - - -/** */ -function testSet1() { - const testValue = {}; - /** @type {(string|number)[][]} */ - const data = [ - ['0'], - ['value1', 'value2'], - ['value1', 'value3'], - ['value1', 'value4'], - ['value1'], - ['value5', 0], - ['value5', 1], - ['value5', 2], - ['value5'] - ]; - - for (const pathArray of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - accessor.set(pathArray, testValue); - assert.strictEqual(accessor.get(pathArray), testValue); - } -} - -/** */ -function testSet2() { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - const testValue = {}; - /** @type {[pathArray: (string|number)[], message: string][]} */ - const data = [ - [[], 'Invalid path'], - [[0], 'Invalid path: [0]'], - [['0', 'invalid'], 'Invalid path: ["0"].invalid'], - [['value1', 'value2', 0], 'Invalid path: value1.value2[0]'], - [['value1', 'value3', 'invalid'], 'Invalid path: value1.value3.invalid'], - [['value1', 'value4', 'invalid'], 'Invalid path: value1.value4.invalid'], - [['value1', 'value4', 0], 'Invalid path: value1.value4[0]'], - [['value5', 1, 'invalid'], 'Invalid path: value5[1].invalid'], - [['value5', 2, 'invalid'], 'Invalid path: value5[2].invalid'], - [['value5', 2, 0], 'Invalid path: value5[2][0]'], - [['value5', 2, 0, 'invalid'], 'Invalid path: value5[2][0]'], - [['value5', 2.5], 'Invalid index'] - ]; - - for (const [pathArray, message] of data) { - assert.throws(() => accessor.set(pathArray, testValue), {message}); - } -} - - -/** */ -function testDelete1() { - /** - * @param {unknown} object - * @param {string} property - * @returns {boolean} - */ - const hasOwn = (object, property) => Object.prototype.hasOwnProperty.call(object, property); - - /** @type {[pathArray: (string|number)[], validate: (object: import('core').SafeAny) => boolean][]} */ - const data = [ - [['0'], (object) => !hasOwn(object, '0')], - [['value1', 'value2'], (object) => !hasOwn(object.value1, 'value2')], - [['value1', 'value3'], (object) => !hasOwn(object.value1, 'value3')], - [['value1', 'value4'], (object) => !hasOwn(object.value1, 'value4')], - [['value1'], (object) => !hasOwn(object, 'value1')], - [['value5'], (object) => !hasOwn(object, 'value5')] - ]; - - for (const [pathArray, validate] of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - accessor.delete(pathArray); - assert.ok(validate(object)); - } -} - -/** */ -function testDelete2() { - /** @type {[pathArray: (string|number)[], message: string][]} */ - const data = [ - [[], 'Invalid path'], - [[0], 'Invalid path: [0]'], - [['0', 'invalid'], 'Invalid path: ["0"].invalid'], - [['value1', 'value2', 0], 'Invalid path: value1.value2[0]'], - [['value1', 'value3', 'invalid'], 'Invalid path: value1.value3.invalid'], - [['value1', 'value4', 'invalid'], 'Invalid path: value1.value4.invalid'], - [['value1', 'value4', 0], 'Invalid path: value1.value4[0]'], - [['value5', 1, 'invalid'], 'Invalid path: value5[1].invalid'], - [['value5', 2, 'invalid'], 'Invalid path: value5[2].invalid'], - [['value5', 2, 0], 'Invalid path: value5[2][0]'], - [['value5', 2, 0, 'invalid'], 'Invalid path: value5[2][0]'], - [['value5', 2.5], 'Invalid index'], - [['value5', 0], 'Invalid type'], - [['value5', 1], 'Invalid type'], - [['value5', 2], 'Invalid type'] - ]; - - for (const [pathArray, message] of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - assert.throws(() => accessor.delete(pathArray), {message}); - } -} - - -/** */ -function testSwap1() { - /** @type {[pathArray: (string|number)[], compareValues: boolean][]} */ - const data = [ - [['0'], true], - [['value1', 'value2'], true], - [['value1', 'value3'], true], - [['value1', 'value4'], true], - [['value1'], false], - [['value5', 0], true], - [['value5', 1], true], - [['value5', 2], true], - [['value5'], false] - ]; - - for (const [pathArray1, compareValues1] of data) { - for (const [pathArray2, compareValues2] of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - const value1a = accessor.get(pathArray1); - const value2a = accessor.get(pathArray2); - - accessor.swap(pathArray1, pathArray2); - - if (!compareValues1 || !compareValues2) { continue; } - - const value1b = accessor.get(pathArray1); - const value2b = accessor.get(pathArray2); - - assert.deepStrictEqual(value1a, value2b); - assert.deepStrictEqual(value2a, value1b); - } - } -} - -/** */ -function testSwap2() { - /** @type {[pathArray1: (string|number)[], pathArray2: (string|number)[], checkRevert: boolean, message: string][]} */ - const data = [ - [[], [], false, 'Invalid path 1'], - [['0'], [], false, 'Invalid path 2'], - [[], ['0'], false, 'Invalid path 1'], - [[0], ['0'], false, 'Invalid path 1: [0]'], - [['0'], [0], false, 'Invalid path 2: [0]'] - ]; - - for (const [pathArray1, pathArray2, checkRevert, message] of data) { - const object = createTestObject(); - const accessor = new ObjectPropertyAccessor2(object); - - let value1a; - let value2a; - if (checkRevert) { - try { - value1a = accessor.get(pathArray1); - value2a = accessor.get(pathArray2); - } catch (e) { - // NOP - } - } - - assert.throws(() => accessor.swap(pathArray1, pathArray2), {message}); - - if (!checkRevert) { continue; } - - const value1b = accessor.get(pathArray1); - const value2b = accessor.get(pathArray2); - - assert.deepStrictEqual(value1a, value1b); - assert.deepStrictEqual(value2a, value2b); - } -} - - -/** */ -function testGetPathString1() { - /** @type {[pathArray: (string|number)[], expected: string][]} */ - const data = [ - [[], ''], - [[0], '[0]'], - [['escape\\'], '["escape\\\\"]'], - [['\'quote\''], '["\'quote\'"]'], - [['"quote"'], '["\\"quote\\""]'], - [['part1', 'part2'], 'part1.part2'], - [['part1', 'part2', 3], 'part1.part2[3]'], - [['part1', 'part2', '3'], 'part1.part2["3"]'], - [['part1', 'part2', '3part'], 'part1.part2["3part"]'], - [['part1', 'part2', '3part', 'part4'], 'part1.part2["3part"].part4'], - [['part1', 'part2', '3part', '4part'], 'part1.part2["3part"]["4part"]'] - ]; - - for (const [pathArray, expected] of data) { - assert.strictEqual(ObjectPropertyAccessor2.getPathString(pathArray), expected); - } -} - -/** */ -function testGetPathString2() { - /** @type {[pathArray: unknown[], message: string][]} */ - const data = [ - [[1.5], 'Invalid index'], - [[null], 'Invalid type: object'] - ]; - - for (const [pathArray, message] of data) { - // @ts-ignore - Throwing is expected - assert.throws(() => ObjectPropertyAccessor2.getPathString(pathArray), {message}); - } -} - - -/** */ -function testGetPathArray1() { - /** @type {[pathString: string, pathArray: (string|number)[]][]} */ - const data = [ - ['', []], - ['[0]', [0]], - ['["escape\\\\"]', ['escape\\']], - ['["\'quote\'"]', ['\'quote\'']], - ['["\\"quote\\""]', ['"quote"']], - ['part1.part2', ['part1', 'part2']], - ['part1.part2[3]', ['part1', 'part2', 3]], - ['part1.part2["3"]', ['part1', 'part2', '3']], - ['part1.part2[\'3\']', ['part1', 'part2', '3']], - ['part1.part2["3part"]', ['part1', 'part2', '3part']], - ['part1.part2[\'3part\']', ['part1', 'part2', '3part']], - ['part1.part2["3part"].part4', ['part1', 'part2', '3part', 'part4']], - ['part1.part2[\'3part\'].part4', ['part1', 'part2', '3part', 'part4']], - ['part1.part2["3part"]["4part"]', ['part1', 'part2', '3part', '4part']], - ['part1.part2[\'3part\'][\'4part\']', ['part1', 'part2', '3part', '4part']] - ]; - - for (const [pathString, expected] of data) { - // @ts-ignore - vm.assert.deepStrictEqual(ObjectPropertyAccessor2.getPathArray(pathString), expected); - } -} - -/** */ -function testGetPathArray2() { - /** @type {[pathString: string, message: string][]} */ - const data = [ - ['?', 'Unexpected character: ?'], - ['.', 'Unexpected character: .'], - ['0', 'Unexpected character: 0'], - ['part1.[0]', 'Unexpected character: ['], - ['part1?', 'Unexpected character: ?'], - ['[part1]', 'Unexpected character: p'], - ['[0a]', 'Unexpected character: a'], - ['["part1"x]', 'Unexpected character: x'], - ['[\'part1\'x]', 'Unexpected character: x'], - ['["part1"]x', 'Unexpected character: x'], - ['[\'part1\']x', 'Unexpected character: x'], - ['part1..part2', 'Unexpected character: .'], - - ['[', 'Path not terminated correctly'], - ['part1.', 'Path not terminated correctly'], - ['part1[', 'Path not terminated correctly'], - ['part1["', 'Path not terminated correctly'], - ['part1[\'', 'Path not terminated correctly'], - ['part1[""', 'Path not terminated correctly'], - ['part1[\'\'', 'Path not terminated correctly'], - ['part1[0', 'Path not terminated correctly'], - ['part1[0].', 'Path not terminated correctly'] - ]; - - for (const [pathString, message] of data) { - assert.throws(() => ObjectPropertyAccessor2.getPathArray(pathString), {message}); - } -} - - -/** */ -function testHasProperty() { - /** @type {[object: unknown, property: unknown, expected: boolean][]} */ - const data = [ - [{}, 'invalid', false], - [{}, 0, false], - [{valid: 0}, 'valid', true], - [{null: 0}, null, false], - [[], 'invalid', false], - [[], 0, false], - [[0], 0, true], - [[0], null, false], - ['string', 0, false], - ['string', 'length', false], - ['string', null, false] - ]; - - for (const [object, property, expected] of data) { - // @ts-ignore - assert.strictEqual(ObjectPropertyAccessor2.hasProperty(object, property), expected); - } -} - -/** */ -function testIsValidPropertyType() { - /** @type {[object: unknown, property: unknown, expected: boolean][]} */ - const data = [ - [{}, 'invalid', true], - [{}, 0, false], - [{valid: 0}, 'valid', true], - [{null: 0}, null, false], - [[], 'invalid', false], - [[], 0, true], - [[0], 0, true], - [[0], null, false], - ['string', 0, false], - ['string', 'length', false], - ['string', null, false] - ]; - - for (const [object, property, expected] of data) { - // @ts-ignore - assert.strictEqual(ObjectPropertyAccessor2.isValidPropertyType(object, property), expected); - } -} - - -/** */ -function main() { - testGet1(); - testGet2(); - testSet1(); - testSet2(); - testDelete1(); - testDelete2(); - testSwap1(); - testSwap2(); - testGetPathString1(); - testGetPathString2(); - testGetPathArray1(); - testGetPathArray2(); - testHasProperty(); - testIsValidPropertyType(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-profile-conditions-util.js b/test/test-profile-conditions-util.js deleted file mode 100644 index 2e6f751f..00000000 --- a/test/test-profile-conditions-util.js +++ /dev/null @@ -1,1136 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - - -const vm = new VM({}); -vm.execute([ - 'js/core.js', - 'js/core/extension-error.js', - 'js/general/cache-map.js', - 'js/data/json-schema.js', - 'js/background/profile-conditions-util.js' -]); -/** @type {typeof ProfileConditionsUtil} */ -const ProfileConditionsUtil2 = vm.getSingle('ProfileConditionsUtil'); - - -/** */ -function testNormalizeContext() { - /** @type {{context: import('settings').OptionsContext, expected: import('profile-conditions-util').NormalizedOptionsContext}[]} */ - const data = [ - // Empty - { - context: {index: 0}, - expected: {index: 0, flags: []} - }, - - // Domain normalization - { - context: {depth: 0, url: ''}, - expected: {depth: 0, url: '', flags: []} - }, - { - context: {depth: 0, url: 'http://example.com/'}, - expected: {depth: 0, url: 'http://example.com/', domain: 'example.com', flags: []} - }, - { - context: {depth: 0, url: 'http://example.com:1234/'}, - expected: {depth: 0, url: 'http://example.com:1234/', domain: 'example.com', flags: []} - }, - { - context: {depth: 0, url: 'http://user@example.com:1234/'}, - expected: {depth: 0, url: 'http://user@example.com:1234/', domain: 'example.com', flags: []} - } - ]; - - for (const {context, expected} of data) { - const profileConditionsUtil = new ProfileConditionsUtil2(); - const actual = profileConditionsUtil.normalizeContext(context); - vm.assert.deepStrictEqual(actual, expected); - } -} - -/** */ -function testSchemas() { - /** @type {{conditionGroups: import('settings').ProfileConditionGroup[], expectedSchema?: import('json-schema').Schema, inputs?: {expected: boolean, context: import('settings').OptionsContext}[]}[]} */ - const data = [ - // Empty - { - conditionGroups: [], - expectedSchema: {}, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - {conditions: []} - ], - expectedSchema: {}, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - {conditions: []}, - {conditions: []} - ], - expectedSchema: {}, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}} - ] - }, - - // popupLevel tests - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'equal', - value: '0' - } - ] - } - ], - expectedSchema: { - properties: { - depth: {const: 0} - }, - required: ['depth'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: false, context: {depth: 1, url: 'http://example.com/'}}, - {expected: false, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'notEqual', - value: '0' - } - ] - } - ], - expectedSchema: { - not: { - anyOf: [ - { - properties: { - depth: {const: 0} - }, - required: ['depth'] - } - ] - } - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'lessThan', - value: '0' - } - ] - } - ], - expectedSchema: { - properties: { - depth: { - type: 'number', - exclusiveMaximum: 0 - } - }, - required: ['depth'] - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/'}}, - {expected: false, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'greaterThan', - value: '0' - } - ] - } - ], - expectedSchema: { - properties: { - depth: { - type: 'number', - exclusiveMinimum: 0 - } - }, - required: ['depth'] - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: false, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'lessThanOrEqual', - value: '0' - } - ] - } - ], - expectedSchema: { - properties: { - depth: { - type: 'number', - maximum: 0 - } - }, - required: ['depth'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: false, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'greaterThanOrEqual', - value: '0' - } - ] - } - ], - expectedSchema: { - properties: { - depth: { - type: 'number', - minimum: 0 - } - }, - required: ['depth'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: false, context: {depth: -1, url: 'http://example.com/'}} - ] - }, - - // url tests - { - conditionGroups: [ - { - conditions: [ - { - type: 'url', - operator: 'matchDomain', - value: 'example.com' - } - ] - } - ], - expectedSchema: { - properties: { - domain: { - oneOf: [ - {const: 'example.com'} - ] - } - }, - required: ['domain'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: false, context: {depth: 0, url: 'http://example1.com/'}}, - {expected: false, context: {depth: 0, url: 'http://example2.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example.com:1234/'}}, - {expected: true, context: {depth: 0, url: 'http://user@example.com:1234/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'url', - operator: 'matchDomain', - value: 'example.com, example1.com, example2.com' - } - ] - } - ], - expectedSchema: { - properties: { - domain: { - oneOf: [ - {const: 'example.com'}, - {const: 'example1.com'}, - {const: 'example2.com'} - ] - } - }, - required: ['domain'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example1.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example2.com/'}}, - {expected: false, context: {depth: 0, url: 'http://example3.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example.com:1234/'}}, - {expected: true, context: {depth: 0, url: 'http://user@example.com:1234/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'url', - operator: 'matchRegExp', - value: '^http://example\\d?\\.com/[\\w\\W]*$' - } - ] - } - ], - expectedSchema: { - properties: { - url: { - type: 'string', - pattern: '^http://example\\d?\\.com/[\\w\\W]*$', - patternFlags: 'i' - } - }, - required: ['url'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example1.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example2.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example3.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example.com/example'}}, - {expected: false, context: {depth: 0, url: 'http://example.com:1234/'}}, - {expected: false, context: {depth: 0, url: 'http://user@example.com:1234/'}}, - {expected: false, context: {depth: 0, url: 'http://example-1.com/'}} - ] - }, - - // modifierKeys tests - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'are', - value: '' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array', - maxItems: 0, - minItems: 0 - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'are', - value: 'alt, shift' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array', - maxItems: 2, - minItems: 2, - allOf: [ - {contains: {const: 'alt'}}, - {contains: {const: 'shift'}} - ] - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'areNot', - value: '' - } - ] - } - ], - expectedSchema: { - not: { - anyOf: [ - { - properties: { - modifierKeys: { - type: 'array', - maxItems: 0, - minItems: 0 - } - }, - required: ['modifierKeys'] - } - ] - } - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'areNot', - value: 'alt, shift' - } - ] - } - ], - expectedSchema: { - not: { - anyOf: [ - { - properties: { - modifierKeys: { - type: 'array', - maxItems: 2, - minItems: 2, - allOf: [ - {contains: {const: 'alt'}}, - {contains: {const: 'shift'}} - ] - } - }, - required: ['modifierKeys'] - } - ] - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'include', - value: '' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array', - minItems: 0 - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'include', - value: 'alt, shift' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array', - minItems: 2, - allOf: [ - {contains: {const: 'alt'}}, - {contains: {const: 'shift'}} - ] - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'notInclude', - value: '' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array' - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'modifierKeys', - operator: 'notInclude', - value: 'alt, shift' - } - ] - } - ], - expectedSchema: { - properties: { - modifierKeys: { - type: 'array', - not: { - anyOf: [ - {contains: {const: 'alt'}}, - {contains: {const: 'shift'}} - ] - } - } - }, - required: ['modifierKeys'] - }, - inputs: [ - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} - ] - }, - - // flags tests - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'are', - value: '' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array', - maxItems: 0, - minItems: 0 - } - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: ''}}, - {expected: true, context: {depth: 0, url: '', flags: []}}, - {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'are', - value: 'clipboard, test2' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array', - maxItems: 2, - minItems: 2, - allOf: [ - {contains: {const: 'clipboard'}}, - {contains: {const: 'test2'}} - ] - } - } - }, - inputs: [ - {expected: false, context: {depth: 0, url: ''}}, - {expected: false, context: {depth: 0, url: '', flags: []}}, - {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'areNot', - value: '' - } - ] - } - ], - expectedSchema: { - not: { - anyOf: [ - { - required: ['flags'], - properties: { - flags: { - type: 'array', - maxItems: 0, - minItems: 0 - } - } - } - ] - } - }, - inputs: [ - {expected: false, context: {depth: 0, url: ''}}, - {expected: false, context: {depth: 0, url: '', flags: []}}, - {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'areNot', - value: 'clipboard, test2' - } - ] - } - ], - expectedSchema: { - not: { - anyOf: [ - { - required: ['flags'], - properties: { - flags: { - type: 'array', - maxItems: 2, - minItems: 2, - allOf: [ - {contains: {const: 'clipboard'}}, - {contains: {const: 'test2'}} - ] - } - } - } - ] - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: ''}}, - {expected: true, context: {depth: 0, url: '', flags: []}}, - {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'include', - value: '' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array', - minItems: 0 - } - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: ''}}, - {expected: true, context: {depth: 0, url: '', flags: []}}, - {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'include', - value: 'clipboard, test2' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array', - minItems: 2, - allOf: [ - {contains: {const: 'clipboard'}}, - {contains: {const: 'test2'}} - ] - } - } - }, - inputs: [ - {expected: false, context: {depth: 0, url: ''}}, - {expected: false, context: {depth: 0, url: '', flags: []}}, - {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'notInclude', - value: '' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array' - } - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: ''}}, - {expected: true, context: {depth: 0, url: '', flags: []}}, - {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'flags', - operator: 'notInclude', - value: 'clipboard, test2' - } - ] - } - ], - expectedSchema: { - required: ['flags'], - properties: { - flags: { - type: 'array', - not: { - anyOf: [ - {contains: {const: 'clipboard'}}, - {contains: {const: 'test2'}} - ] - } - } - } - }, - inputs: [ - {expected: true, context: {depth: 0, url: ''}}, - {expected: true, context: {depth: 0, url: '', flags: []}}, - {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes - {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} - ] - }, - - // Multiple conditions tests - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'greaterThan', - value: '0' - }, - { - type: 'popupLevel', - operator: 'lessThan', - value: '3' - } - ] - } - ], - expectedSchema: { - allOf: [ - { - properties: { - depth: { - type: 'number', - exclusiveMinimum: 0 - } - }, - required: ['depth'] - }, - { - properties: { - depth: { - type: 'number', - exclusiveMaximum: 3 - } - }, - required: ['depth'] - } - ] - }, - inputs: [ - {expected: false, context: {depth: -2, url: 'http://example.com/'}}, - {expected: false, context: {depth: -1, url: 'http://example.com/'}}, - {expected: false, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: 2, url: 'http://example.com/'}}, - {expected: false, context: {depth: 3, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'greaterThan', - value: '0' - }, - { - type: 'popupLevel', - operator: 'lessThan', - value: '3' - } - ] - }, - { - conditions: [ - { - type: 'popupLevel', - operator: 'equal', - value: '0' - } - ] - } - ], - expectedSchema: { - anyOf: [ - { - allOf: [ - { - properties: { - depth: { - type: 'number', - exclusiveMinimum: 0 - } - }, - required: ['depth'] - }, - { - properties: { - depth: { - type: 'number', - exclusiveMaximum: 3 - } - }, - required: ['depth'] - } - ] - }, - { - properties: { - depth: {const: 0} - }, - required: ['depth'] - } - ] - }, - inputs: [ - {expected: false, context: {depth: -2, url: 'http://example.com/'}}, - {expected: false, context: {depth: -1, url: 'http://example.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: 2, url: 'http://example.com/'}}, - {expected: false, context: {depth: 3, url: 'http://example.com/'}} - ] - }, - { - conditionGroups: [ - { - conditions: [ - { - type: 'popupLevel', - operator: 'greaterThan', - value: '0' - }, - { - type: 'popupLevel', - operator: 'lessThan', - value: '3' - } - ] - }, - { - conditions: [ - { - type: 'popupLevel', - operator: 'lessThanOrEqual', - value: '0' - }, - { - type: 'popupLevel', - operator: 'greaterThanOrEqual', - value: '-1' - } - ] - } - ], - expectedSchema: { - anyOf: [ - { - allOf: [ - { - properties: { - depth: { - type: 'number', - exclusiveMinimum: 0 - } - }, - required: ['depth'] - }, - { - properties: { - depth: { - type: 'number', - exclusiveMaximum: 3 - } - }, - required: ['depth'] - } - ] - }, - { - allOf: [ - { - properties: { - depth: { - type: 'number', - maximum: 0 - } - }, - required: ['depth'] - }, - { - properties: { - depth: { - type: 'number', - minimum: -1 - } - }, - required: ['depth'] - } - ] - } - ] - }, - inputs: [ - {expected: false, context: {depth: -2, url: 'http://example.com/'}}, - {expected: true, context: {depth: -1, url: 'http://example.com/'}}, - {expected: true, context: {depth: 0, url: 'http://example.com/'}}, - {expected: true, context: {depth: 1, url: 'http://example.com/'}}, - {expected: true, context: {depth: 2, url: 'http://example.com/'}}, - {expected: false, context: {depth: 3, url: 'http://example.com/'}} - ] - } - ]; - - for (const {conditionGroups, expectedSchema, inputs} of data) { - const profileConditionsUtil = new ProfileConditionsUtil2(); - const schema = profileConditionsUtil.createSchema(conditionGroups); - if (typeof expectedSchema !== 'undefined') { - vm.assert.deepStrictEqual(schema.schema, expectedSchema); - } - if (Array.isArray(inputs)) { - for (const {expected, context} of inputs) { - const normalizedContext = profileConditionsUtil.normalizeContext(context); - const actual = schema.isValid(normalizedContext); - assert.strictEqual(actual, expected); - } - } - } -} - - -/** */ -function main() { - testNormalizeContext(); - testSchemas(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-text-source-map.js b/test/test-text-source-map.js deleted file mode 100644 index 834a3d07..00000000 --- a/test/test-text-source-map.js +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {VM} = require('../dev/vm'); - -const vm = new VM(); -vm.execute(['js/general/text-source-map.js']); -/** @type {typeof TextSourceMap} */ -const TextSourceMap2 = vm.getSingle('TextSourceMap'); - - -/** */ -function testSource() { - const data = [ - ['source1'], - ['source2'], - ['source3'] - ]; - - for (const [source] of data) { - const sourceMap = new TextSourceMap2(source); - assert.strictEqual(source, sourceMap.source); - } -} - -/** */ -function testEquals() { - /** @type {[args1: [source1: string, mapping1: ?(number[])], args2: [source2: string, mapping2: ?(number[])], expectedEquals: boolean][]} */ - const data = [ - [['source1', null], ['source1', null], true], - [['source2', null], ['source2', null], true], - [['source3', null], ['source3', null], true], - - [['source1', [1, 1, 1, 1, 1, 1, 1]], ['source1', null], true], - [['source2', [1, 1, 1, 1, 1, 1, 1]], ['source2', null], true], - [['source3', [1, 1, 1, 1, 1, 1, 1]], ['source3', null], true], - - [['source1', null], ['source1', [1, 1, 1, 1, 1, 1, 1]], true], - [['source2', null], ['source2', [1, 1, 1, 1, 1, 1, 1]], true], - [['source3', null], ['source3', [1, 1, 1, 1, 1, 1, 1]], true], - - [['source1', [1, 1, 1, 1, 1, 1, 1]], ['source1', [1, 1, 1, 1, 1, 1, 1]], true], - [['source2', [1, 1, 1, 1, 1, 1, 1]], ['source2', [1, 1, 1, 1, 1, 1, 1]], true], - [['source3', [1, 1, 1, 1, 1, 1, 1]], ['source3', [1, 1, 1, 1, 1, 1, 1]], true], - - [['source1', [1, 2, 1, 3]], ['source1', [1, 2, 1, 3]], true], - [['source2', [1, 2, 1, 3]], ['source2', [1, 2, 1, 3]], true], - [['source3', [1, 2, 1, 3]], ['source3', [1, 2, 1, 3]], true], - - [['source1', [1, 3, 1, 2]], ['source1', [1, 2, 1, 3]], false], - [['source2', [1, 3, 1, 2]], ['source2', [1, 2, 1, 3]], false], - [['source3', [1, 3, 1, 2]], ['source3', [1, 2, 1, 3]], false], - - [['source1', [1, 1, 1, 1, 1, 1, 1]], ['source4', [1, 1, 1, 1, 1, 1, 1]], false], - [['source2', [1, 1, 1, 1, 1, 1, 1]], ['source5', [1, 1, 1, 1, 1, 1, 1]], false], - [['source3', [1, 1, 1, 1, 1, 1, 1]], ['source6', [1, 1, 1, 1, 1, 1, 1]], false] - ]; - - for (const [[source1, mapping1], [source2, mapping2], expectedEquals] of data) { - const sourceMap1 = new TextSourceMap2(source1, mapping1); - const sourceMap2 = new TextSourceMap2(source2, mapping2); - assert.ok(sourceMap1.equals(sourceMap1)); - assert.ok(sourceMap2.equals(sourceMap2)); - assert.strictEqual(sourceMap1.equals(sourceMap2), expectedEquals); - } -} - -/** */ -function testGetSourceLength() { - /** @type {[args: [source: string, mapping: number[]], finalLength: number, expectedValue: number][]} */ - const data = [ - [['source', [1, 1, 1, 1, 1, 1]], 1, 1], - [['source', [1, 1, 1, 1, 1, 1]], 2, 2], - [['source', [1, 1, 1, 1, 1, 1]], 3, 3], - [['source', [1, 1, 1, 1, 1, 1]], 4, 4], - [['source', [1, 1, 1, 1, 1, 1]], 5, 5], - [['source', [1, 1, 1, 1, 1, 1]], 6, 6], - - [['source', [2, 2, 2]], 1, 2], - [['source', [2, 2, 2]], 2, 4], - [['source', [2, 2, 2]], 3, 6], - - [['source', [3, 3]], 1, 3], - [['source', [3, 3]], 2, 6], - - [['source', [6, 6]], 1, 6] - ]; - - for (const [[source, mapping], finalLength, expectedValue] of data) { - const sourceMap = new TextSourceMap2(source, mapping); - assert.strictEqual(sourceMap.getSourceLength(finalLength), expectedValue); - } -} - -/** */ -function testCombineInsert() { - /** @type {[args: [source: string, mapping: ?(number[])], expectedArgs: [expectedSource: string, expectedMapping: ?(number[])], operations: [operation: string, arg1: number, arg2: number][]][]} */ - const data = [ - // No operations - [ - ['source', null], - ['source', [1, 1, 1, 1, 1, 1]], - [] - ], - - // Combine - [ - ['source', null], - ['source', [3, 1, 1, 1]], - [ - ['combine', 0, 2] - ] - ], - [ - ['source', null], - ['source', [1, 1, 1, 3]], - [ - ['combine', 3, 2] - ] - ], - [ - ['source', null], - ['source', [3, 3]], - [ - ['combine', 0, 2], - ['combine', 1, 2] - ] - ], - [ - ['source', null], - ['source', [3, 3]], - [ - ['combine', 3, 2], - ['combine', 0, 2] - ] - ], - - // Insert - [ - ['source', null], - ['source', [0, 1, 1, 1, 1, 1, 1]], - [ - ['insert', 0, 0] - ] - ], - [ - ['source', null], - ['source', [1, 1, 1, 1, 1, 1, 0]], - [ - ['insert', 6, 0] - ] - ], - [ - ['source', null], - ['source', [0, 1, 1, 1, 1, 1, 1, 0]], - [ - ['insert', 0, 0], - ['insert', 7, 0] - ] - ], - [ - ['source', null], - ['source', [0, 1, 1, 1, 1, 1, 1, 0]], - [ - ['insert', 6, 0], - ['insert', 0, 0] - ] - ], - - // Mixed - [ - ['source', null], - ['source', [3, 0, 3]], - [ - ['combine', 0, 2], - ['insert', 1, 0], - ['combine', 2, 2] - ] - ], - [ - ['source', null], - ['source', [3, 0, 3]], - [ - ['combine', 0, 2], - ['combine', 1, 2], - ['insert', 1, 0] - ] - ], - [ - ['source', null], - ['source', [3, 0, 3]], - [ - ['insert', 3, 0], - ['combine', 0, 2], - ['combine', 2, 2] - ] - ] - ]; - - for (const [[source, mapping], [expectedSource, expectedMapping], operations] of data) { - const sourceMap = new TextSourceMap2(source, mapping); - const expectedSourceMap = new TextSourceMap2(expectedSource, expectedMapping); - for (const [operation, ...args] of operations) { - switch (operation) { - case 'combine': - sourceMap.combine(...args); - break; - case 'insert': - sourceMap.insert(...args); - break; - } - } - assert.ok(sourceMap.equals(expectedSourceMap)); - } -} - - -/** */ -function main() { - testSource(); - testEquals(); - testGetSourceLength(); - testCombineInsert(); -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-translator.js b/test/test-translator.js deleted file mode 100644 index 0c84e0be..00000000 --- a/test/test-translator.js +++ /dev/null @@ -1,102 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const fs = require('fs'); -const path = require('path'); -const assert = require('assert'); -const {testMain} = require('../dev/util'); -const {TranslatorVM} = require('../dev/translator-vm'); - - -/** - * @template T - * @param {T} value - * @returns {T} - */ -function clone(value) { - return JSON.parse(JSON.stringify(value)); -} - - -/** */ -async function main() { - const write = (process.argv[2] === '--write'); - - const translatorVM = new TranslatorVM(); - const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', 'valid-dictionary1'); - await translatorVM.prepare(dictionaryDirectory, 'Test Dictionary 2'); - - const testInputsFilePath = path.join(__dirname, 'data', 'translator-test-inputs.json'); - const {optionsPresets, tests} = JSON.parse(fs.readFileSync(testInputsFilePath, {encoding: 'utf8'})); - - const testResults1FilePath = path.join(__dirname, 'data', 'translator-test-results.json'); - const expectedResults1 = JSON.parse(fs.readFileSync(testResults1FilePath, {encoding: 'utf8'})); - const actualResults1 = []; - - const testResults2FilePath = path.join(__dirname, 'data', 'translator-test-results-note-data1.json'); - const expectedResults2 = JSON.parse(fs.readFileSync(testResults2FilePath, {encoding: 'utf8'})); - const actualResults2 = []; - - for (let i = 0, ii = tests.length; i < ii; ++i) { - const test = tests[i]; - const expected1 = expectedResults1[i]; - const expected2 = expectedResults2[i]; - switch (test.func) { - case 'findTerms': - { - const {name, mode, text} = test; - /** @type {import('translation').FindTermsOptions} */ - const options = translatorVM.buildOptions(optionsPresets, test.options); - const {dictionaryEntries, originalTextLength} = clone(await translatorVM.translator.findTerms(mode, text, options)); - const noteDataList = mode !== 'simple' ? clone(dictionaryEntries.map((dictionaryEntry) => translatorVM.createTestAnkiNoteData(clone(dictionaryEntry), mode))) : null; - actualResults1.push({name, originalTextLength, dictionaryEntries}); - actualResults2.push({name, noteDataList}); - if (!write) { - assert.deepStrictEqual(originalTextLength, expected1.originalTextLength); - assert.deepStrictEqual(dictionaryEntries, expected1.dictionaryEntries); - assert.deepStrictEqual(noteDataList, expected2.noteDataList); - } - } - break; - case 'findKanji': - { - const {name, text} = test; - /** @type {import('translation').FindKanjiOptions} */ - const options = translatorVM.buildOptions(optionsPresets, test.options); - const dictionaryEntries = clone(await translatorVM.translator.findKanji(text, options)); - const noteDataList = clone(dictionaryEntries.map((dictionaryEntry) => translatorVM.createTestAnkiNoteData(clone(dictionaryEntry), 'split'))); - actualResults1.push({name, dictionaryEntries}); - actualResults2.push({name, noteDataList}); - if (!write) { - assert.deepStrictEqual(dictionaryEntries, expected1.dictionaryEntries); - assert.deepStrictEqual(noteDataList, expected2.noteDataList); - } - } - break; - } - } - - if (write) { - // Use 2 indent instead of 4 to save a bit of file size - fs.writeFileSync(testResults1FilePath, JSON.stringify(actualResults1, null, 2), {encoding: 'utf8'}); - fs.writeFileSync(testResults2FilePath, JSON.stringify(actualResults2, null, 2), {encoding: 'utf8'}); - } -} - - -if (require.main === module) { testMain(main); } diff --git a/test/test-workers.js b/test/test-workers.js deleted file mode 100644 index 3de7ac48..00000000 --- a/test/test-workers.js +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright (C) 2023 Yomitan Authors - * Copyright (C) 2020-2022 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 . - */ - -const fs = require('fs'); -const path = require('path'); -const {JSDOM} = require('jsdom'); -const {VM} = require('../dev/vm'); -const assert = require('assert'); - - -class StubClass { - /** */ - prepare() { - // NOP - } -} - - -/** - * @returns {import('core').SafeAny} - */ -function loadEslint() { - return JSON.parse(fs.readFileSync(path.join(__dirname, '..', '.eslintrc.json'), {encoding: 'utf8'})); -} - -/** - * @param {string[]} scriptPaths - * @returns {string[]} - */ -function filterScriptPaths(scriptPaths) { - const extDirName = 'ext'; - return scriptPaths.filter((src) => !src.startsWith('/lib/')).map((src) => `${extDirName}${src}`); -} - -/** - * @param {string} fileName - * @returns {string[]} - */ -function getAllHtmlScriptPaths(fileName) { - const domSource = fs.readFileSync(fileName, {encoding: 'utf8'}); - const dom = new JSDOM(domSource); - const {window} = dom; - const {document} = window; - try { - const scripts = document.querySelectorAll('script'); - return [...scripts].map(({src}) => src); - } finally { - window.close(); - } -} - -/** - * @param {string[]} scripts - */ -function convertBackgroundScriptsToServiceWorkerScripts(scripts) { - // Use parse5-based SimpleDOMParser - scripts.splice(0, 0, '/lib/parse5.js'); - const index = scripts.indexOf('/js/dom/native-simple-dom-parser.js'); - assert.ok(index >= 0); - scripts[index] = '/js/dom/simple-dom-parser.js'; -} - -/** - * @param {string} scriptPath - * @param {import('core').UnknownObject} fields - * @returns {string[]} - */ -function getImportedScripts(scriptPath, fields) { - /** @type {string[]} */ - const importedScripts = []; - - /** - * @param {...string} scripts - */ - const importScripts = (...scripts) => { - importedScripts.push(...scripts); - }; - - const vm = new VM(Object.assign({importScripts}, fields)); - vm.context.self = vm.context; - vm.execute([scriptPath]); - - return importedScripts; -} - -/** */ -function testServiceWorker() { - // Verify that sw.js scripts match background.html scripts - const extDir = path.join(__dirname, '..', 'ext'); - const scripts = getAllHtmlScriptPaths(path.join(extDir, 'background.html')); - convertBackgroundScriptsToServiceWorkerScripts(scripts); - const importedScripts = getImportedScripts('sw.js', {}); - assert.deepStrictEqual(scripts, importedScripts); - - // Verify that eslint config lists files correctly - const expectedSwRulesFiles = filterScriptPaths(scripts); - const eslintConfig = loadEslint(); - const swRules = /** @type {import('core').SafeAny[]} */ (eslintConfig.overrides).find((item) => ( - typeof item.env === 'object' && - item.env !== null && - item.env.serviceworker === true - )); - assert.ok(typeof swRules !== 'undefined'); - assert.ok(Array.isArray(swRules.files)); - assert.deepStrictEqual(swRules.files, expectedSwRulesFiles); -} - -/** */ -function testWorkers() { - testWorker( - 'js/language/dictionary-worker-main.js', - {DictionaryWorkerHandler: StubClass} - ); -} - -/** - * @param {string} scriptPath - * @param {import('core').UnknownObject} fields - */ -function testWorker(scriptPath, fields) { - // Get script paths - const scripts = getImportedScripts(scriptPath, fields); - - // Verify that eslint config lists files correctly - const expectedRulesFiles = filterScriptPaths(scripts); - const expectedRulesFilesSet = new Set(expectedRulesFiles); - const eslintConfig = loadEslint(); - const rules = /** @type {import('core').SafeAny[]} */ (eslintConfig.overrides).find((item) => ( - typeof item.env === 'object' && - item.env !== null && - item.env.worker === true - )); - assert.ok(typeof rules !== 'undefined'); - assert.ok(Array.isArray(rules.files)); - assert.deepStrictEqual(/** @type {import('core').SafeAny[]} */ (rules.files).filter((v) => expectedRulesFilesSet.has(v)), expectedRulesFiles); -} - - -/** */ -function main() { - try { - testServiceWorker(); - testWorkers(); - } catch (e) { - console.error(e); - process.exit(-1); - return; - } - process.exit(0); -} - - -if (require.main === module) { main(); } -- cgit v1.2.3 From 9ed81f25ab7f462546888a4ddd7fcecdf5ea1806 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 29 Nov 2023 20:36:54 -0500 Subject: Update config --- .eslintrc.json | 9 --------- test/jsconfig.json | 4 ++-- 2 files changed, 2 insertions(+), 11 deletions(-) (limited to 'test') diff --git a/.eslintrc.json b/.eslintrc.json index b16032fc..a46aff4b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -763,15 +763,6 @@ }, "env": {} }, - { - "files": [ - "test/**/*.js" - ], - "rules": { - "jsdoc/require-jsdoc": "off", - "jsdoc/no-undefined-types": "off" - } - }, { "files": [ "dev/lib/**/*.js" diff --git a/test/jsconfig.json b/test/jsconfig.json index 1881fbce..7dbf3950 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -1,9 +1,9 @@ { "compilerOptions": { - "module": "ES2015", + "module": "ES2022", "target": "ES2022", "checkJs": true, - "moduleResolution": "node", + "moduleResolution": "bundler", "strict": true, "strictNullChecks": true, "noImplicitAny": true, -- cgit v1.2.3 From a0a6291db8e2be29c4ed13645c250201b4552b9d Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 29 Nov 2023 21:04:27 -0500 Subject: Update tests --- test/anki-note-builder.test.js | 42 +++++++++++--- test/cache-map.test.js | 12 ++-- test/core.test.js | 5 ++ test/css-json.test.js | 1 + test/database.test.js | 108 +++++++++++++++++++++++++++++++----- test/playwright/integration.spec.js | 13 +++-- test/playwright/playwright-util.js | 11 ++++ 7 files changed, 164 insertions(+), 28 deletions(-) (limited to 'test') diff --git a/test/anki-note-builder.test.js b/test/anki-note-builder.test.js index e21aa993..e4af7943 100644 --- a/test/anki-note-builder.test.js +++ b/test/anki-note-builder.test.js @@ -26,7 +26,11 @@ import {TranslatorVM} from '../dev/translator-vm.js'; import {AnkiNoteBuilder} from '../ext/js/data/anki-note-builder.js'; import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; -vi.stubGlobal('fetch', async (url2) => { +/** + * @param {string} url2 + * @returns {Promise} + */ +async function fetch(url2) { const extDir = path.join(__dirname, '..', 'ext'); let filePath; try { @@ -43,11 +47,15 @@ vi.stubGlobal('fetch', async (url2) => { text: async () => Promise.resolve(content.toString('utf8')), json: async () => Promise.resolve(JSON.parse(content.toString('utf8'))) }; -}); +} +vi.stubGlobal('fetch', fetch); vi.mock('../ext/js/templates/template-renderer-proxy.js'); const dirname = path.dirname(fileURLToPath(import.meta.url)); +/** + * @returns {Promise} + */ async function createVM() { const dictionaryDirectory = path.join(dirname, 'data', 'dictionaries', 'valid-dictionary1'); const vm = new TranslatorVM(); @@ -57,6 +65,10 @@ async function createVM() { return vm; } +/** + * @param {'terms'|'kanji'} type + * @returns {string[]} + */ function getFieldMarkers(type) { switch (type) { case 'terms': @@ -117,8 +129,17 @@ function getFieldMarkers(type) { } } +/** + * @param {import('dictionary').DictionaryEntry[]} dictionaryEntries + * @param {'terms'|'kanji'} type + * @param {import('settings').ResultOutputMode} mode + * @param {string} template + * @param {import('@vitest/expect').ExpectStatic} expect + * @returns {Promise} + */ async function getRenderResults(dictionaryEntries, type, mode, template, expect) { const markers = getFieldMarkers(type); + /** @type {import('anki-note-builder').Field[]} */ const fields = []; for (const marker of markers) { fields.push([marker, `{${marker}}`]); @@ -151,9 +172,10 @@ async function getRenderResults(dictionaryEntries, type, mode, template, expect) query: 'query', fullQuery: 'fullQuery' }; - const {note: {fields: noteFields}, errors} = await ankiNoteBuilder.createNote({ + /** @type {import('anki-note-builder').CreateNoteDetails} */ + const details = { dictionaryEntry, - mode: null, + mode: 'test', context, template, deckName: 'deckName', @@ -165,8 +187,11 @@ async function getRenderResults(dictionaryEntries, type, mode, template, expect) duplicateScopeCheckAllModels: false, resultOutputMode: mode, glossaryLayoutMode: 'default', - compactTags: false - }); + compactTags: false, + requirements: [], + mediaOptions: null + }; + const {note: {fields: noteFields}, errors} = await ankiNoteBuilder.createNote(details); for (const error of errors) { console.error(error); } @@ -178,6 +203,7 @@ async function getRenderResults(dictionaryEntries, type, mode, template, expect) } +/** */ async function main() { const vm = await createVM(); @@ -199,6 +225,7 @@ async function main() { case 'findTerms': { const {name, mode, text} = t; + /** @type {import('translation').FindTermsOptions} */ const options = vm.buildOptions(optionsPresets, t.options); const {dictionaryEntries} = structuredClone(await vm.translator.findTerms(mode, text, options)); const results = mode !== 'simple' ? structuredClone(await getRenderResults(dictionaryEntries, 'terms', mode, template, expect)) : null; @@ -209,9 +236,10 @@ async function main() { case 'findKanji': { const {name, text} = t; + /** @type {import('translation').FindKanjiOptions} */ const options = vm.buildOptions(optionsPresets, t.options); const dictionaryEntries = structuredClone(await vm.translator.findKanji(text, options)); - const results = structuredClone(await getRenderResults(dictionaryEntries, 'kanji', null, template, expect)); + const results = structuredClone(await getRenderResults(dictionaryEntries, 'kanji', 'split', template, expect)); actualResults1.push({name, results}); expect(results).toStrictEqual(expected1.results); } diff --git a/test/cache-map.test.js b/test/cache-map.test.js index 9d10a719..3e7def1f 100644 --- a/test/cache-map.test.js +++ b/test/cache-map.test.js @@ -19,6 +19,7 @@ import {expect, test} from 'vitest'; import {CacheMap} from '../ext/js/general/cache-map.js'; +/** */ function testConstructor() { test('constructor', () => { const data = [ @@ -29,6 +30,7 @@ function testConstructor() { [true, () => new CacheMap(1.5)], [true, () => new CacheMap(Number.NaN)], [true, () => new CacheMap(Number.POSITIVE_INFINITY)], + // @ts-ignore - Ignore because it should throw an error [true, () => new CacheMap('a')] ]; @@ -42,6 +44,7 @@ function testConstructor() { }); } +/** */ function testApi() { test('api', () => { const data = [ @@ -103,10 +106,10 @@ function testApi() { const {func, args} = call; let returnValue; switch (func) { - case 'get': returnValue = cache.get(...args); break; - case 'set': returnValue = cache.set(...args); break; - case 'has': returnValue = cache.has(...args); break; - case 'clear': returnValue = cache.clear(...args); break; + case 'get': returnValue = cache.get(args[0]); break; + case 'set': returnValue = cache.set(args[0], args[1]); break; + case 'has': returnValue = cache.has(args[0]); break; + case 'clear': returnValue = cache.clear(); break; } if (Object.prototype.hasOwnProperty.call(call, 'returnValue')) { const {returnValue: expectedReturnValue} = call; @@ -119,6 +122,7 @@ function testApi() { } +/** */ function main() { testConstructor(); testApi(); diff --git a/test/core.test.js b/test/core.test.js index 203460f4..685bf9dc 100644 --- a/test/core.test.js +++ b/test/core.test.js @@ -19,14 +19,17 @@ import {describe, expect, test} from 'vitest'; import {DynamicProperty, deepEqual} from '../ext/js/core.js'; +/** */ function testDynamicProperty() { test('DynamicProperty', () => { const data = [ { initialValue: 0, + /** @type {{operation: ?string, expectedDefaultValue: number, expectedValue: number, expectedOverrideCount: number, expeectedEventOccurred: boolean, args: [value: number, priority?: number]}[]} */ operations: [ { operation: null, + args: [0], expectedDefaultValue: 0, expectedValue: 0, expectedOverrideCount: 0, @@ -147,6 +150,7 @@ function testDynamicProperty() { }); } +/** */ function testDeepEqual() { describe('deepEqual', () => { const data = [ @@ -280,6 +284,7 @@ function testDeepEqual() { } +/** */ function main() { testDynamicProperty(); testDeepEqual(); diff --git a/test/css-json.test.js b/test/css-json.test.js index 0aaf7d10..66ecfeba 100644 --- a/test/css-json.test.js +++ b/test/css-json.test.js @@ -20,6 +20,7 @@ import fs from 'fs'; import {expect, test} from 'vitest'; import {formatRulesJson, generateRules, getTargets} from '../dev/generate-css-json'; +/** */ function main() { test('css-json', () => { for (const {cssFile, overridesCssFile, outputPath} of getTargets()) { diff --git a/test/database.test.js b/test/database.test.js index b53d0e65..ee818467 100644 --- a/test/database.test.js +++ b/test/database.test.js @@ -28,12 +28,21 @@ vi.stubGlobal('IDBKeyRange', IDBKeyRange); vi.mock('../ext/js/language/dictionary-importer-media-loader.js'); +/** + * @param {string} dictionary + * @param {string} [dictionaryName] + * @returns {import('jszip')} + */ function createTestDictionaryArchive(dictionary, dictionaryName) { const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', dictionary); return createDictionaryArchive(dictionaryDirectory, dictionaryName); } +/** + * @param {import('dictionary-importer').OnProgressCallback} [onProgress] + * @returns {DictionaryImporter} + */ function createDictionaryImporter(onProgress) { const dictionaryImporterMediaLoader = new DictionaryImporterMediaLoader(); return new DictionaryImporter(dictionaryImporterMediaLoader, (...args) => { @@ -47,24 +56,53 @@ function createDictionaryImporter(onProgress) { } +/** + * @param {import('dictionary-database').TermEntry[]} dictionaryDatabaseEntries + * @param {string} term + * @returns {number} + */ function countDictionaryDatabaseEntriesWithTerm(dictionaryDatabaseEntries, term) { return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.term === term ? 1 : 0)), 0); } +/** + * @param {import('dictionary-database').TermEntry[]} dictionaryDatabaseEntries + * @param {string} reading + * @returns {number} + */ function countDictionaryDatabaseEntriesWithReading(dictionaryDatabaseEntries, reading) { return dictionaryDatabaseEntries.reduce((i, v) => (i + (v.reading === reading ? 1 : 0)), 0); } +/** + * @param {import('dictionary-database').TermMeta[]|import('dictionary-database').KanjiMeta[]} metas + * @param {import('dictionary-database').TermMetaType|import('dictionary-database').KanjiMetaType} mode + * @returns {number} + */ function countMetasWithMode(metas, mode) { - return metas.reduce((i, v) => (i + (v.mode === mode ? 1 : 0)), 0); + let i = 0; + for (const item of metas) { + if (item.mode === mode) { ++i; } + } + return i; } +/** + * @param {import('dictionary-database').KanjiEntry[]} kanji + * @param {string} character + * @returns {number} + */ function countKanjiWithCharacter(kanji, character) { - return kanji.reduce((i, v) => (i + (v.character === character ? 1 : 0)), 0); + let i = 0; + for (const item of kanji) { + if (item.character === character) { ++i; } + } + return i; } +/** */ async function testDatabase1() { test('Database1', async () => { // Load dictionary data const testDictionary = createTestDictionaryArchive('valid-dictionary1'); @@ -172,6 +210,9 @@ async function testDatabase1() { }); } +/** + * @param {DictionaryDatabase} database + */ async function testDatabaseEmpty1(database) { test('DatabaseEmpty1', async () => { const info = await database.getDictionaryInfo(); @@ -185,17 +226,22 @@ async function testDatabaseEmpty1(database) { }); } +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */ async function testFindTermsBulkTest1(database, titles) { test('FindTermsBulkTest1', async () => { + /** @type {{inputs: {matchType: import('dictionary-database').MatchType, termList: string[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ const data = [ { inputs: [ { - matchType: null, + matchType: 'exact', termList: ['打', '打つ', '打ち込む'] }, { - matchType: null, + matchType: 'exact', termList: ['だ', 'ダース', 'うつ', 'ぶつ', 'うちこむ', 'ぶちこむ'] }, { @@ -223,7 +269,7 @@ async function testFindTermsBulkTest1(database, titles) { { inputs: [ { - matchType: null, + matchType: 'exact', termList: ['込む'] } ], @@ -254,7 +300,7 @@ async function testFindTermsBulkTest1(database, titles) { { inputs: [ { - matchType: null, + matchType: 'exact', termList: [] } ], @@ -281,8 +327,13 @@ async function testFindTermsBulkTest1(database, titles) { }); } +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */ async function testTindTermsExactBulk1(database, titles) { test('TindTermsExactBulk1', async () => { + /** @type {{inputs: {termList: {term: string, reading: string}[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ const data = [ { inputs: [ @@ -387,8 +438,13 @@ async function testTindTermsExactBulk1(database, titles) { }); } +/** + * @param {DictionaryDatabase} database + * @param {string} mainDictionary + */ async function testFindTermsBySequenceBulk1(database, mainDictionary) { test('FindTermsBySequenceBulk1', async () => { + /** @type {{inputs: {sequenceList: number[]}[], expectedResults: {total: number, terms: [key: string, count: number][], readings: [key: string, count: number][]}}[]} */ const data = [ { inputs: [ @@ -538,8 +594,13 @@ async function testFindTermsBySequenceBulk1(database, mainDictionary) { }); } +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */ async function testFindTermMetaBulk1(database, titles) { test('FindTermMetaBulk1', async () => { + /** @type {{inputs: {termList: string[]}[], expectedResults: {total: number, modes: [key: import('dictionary-database').TermMetaType, count: number][]}}[]} */ const data = [ { inputs: [ @@ -606,8 +667,13 @@ async function testFindTermMetaBulk1(database, titles) { }); } +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */ async function testFindKanjiBulk1(database, titles) { test('FindKanjiBulk1', async () => { + /** @type {{inputs: {kanjiList: string[]}[], expectedResults: {total: number, kanji: [key: string, count: number][]}}[]} */ const data = [ { inputs: [ @@ -660,8 +726,13 @@ async function testFindKanjiBulk1(database, titles) { }); } +/** + * @param {DictionaryDatabase} database + * @param {import('dictionary-database').DictionarySet} titles + */ async function testFindKanjiMetaBulk1(database, titles) { test('FindKanjiMetaBulk1', async () => { + /** @type {{inputs: {kanjiList: string[]}[], expectedResults: {total: number, modes: [key: import('dictionary-database').KanjiMetaType, count: number][]}}[]} */ const data = [ { inputs: [ @@ -714,6 +785,10 @@ async function testFindKanjiMetaBulk1(database, titles) { }); } +/** + * @param {DictionaryDatabase} database + * @param {string} title + */ async function testFindTagForTitle1(database, title) { test('FindTagForTitle1', async () => { const data = [ @@ -769,6 +844,7 @@ async function testFindTagForTitle1(database, title) { } +/** */ async function testDatabase2() { test('Database2', async () => { // Load dictionary data const testDictionary = createTestDictionaryArchive('valid-dictionary1'); @@ -782,10 +858,12 @@ async function testDatabase2() { // Setup database const dictionaryDatabase = new DictionaryDatabase(); + /** @type {import('dictionary-importer').ImportDetails} */ + const detaultImportDetails = {prefixWildcardsSupported: false}; // Database not open - await expect(dictionaryDatabase.deleteDictionary(title, 1000)).rejects.toThrow('Database not open'); - await expect(dictionaryDatabase.findTermsBulk(['?'], titles, null)).rejects.toThrow('Database not open'); + await expect(dictionaryDatabase.deleteDictionary(title, 1000, () => {})).rejects.toThrow('Database not open'); + await expect(dictionaryDatabase.findTermsBulk(['?'], titles, 'exact')).rejects.toThrow('Database not open'); await expect(dictionaryDatabase.findTermsExactBulk([{term: '?', reading: '?'}], titles)).rejects.toThrow('Database not open'); await expect(dictionaryDatabase.findTermsBySequenceBulk([{query: 1, dictionary: title}])).rejects.toThrow('Database not open'); await expect(dictionaryDatabase.findTermMetaBulk(['?'], titles)).rejects.toThrow('Database not open'); @@ -794,24 +872,25 @@ async function testDatabase2() { await expect(dictionaryDatabase.findKanjiMetaBulk(['?'], titles)).rejects.toThrow('Database not open'); await expect(dictionaryDatabase.findTagForTitle('tag', title)).rejects.toThrow('Database not open'); await expect(dictionaryDatabase.getDictionaryInfo()).rejects.toThrow('Database not open'); - await expect(dictionaryDatabase.getDictionaryCounts(titles, true)).rejects.toThrow('Database not open'); - await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {})).rejects.toThrow('Database is not ready'); + await expect(dictionaryDatabase.getDictionaryCounts([...titles.keys()], true)).rejects.toThrow('Database not open'); + await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)).rejects.toThrow('Database is not ready'); await dictionaryDatabase.prepare(); // already prepared await expect(dictionaryDatabase.prepare()).rejects.toThrow('Database already open'); - await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {}); + await createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails); // dictionary already imported - await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {})).rejects.toThrow('Dictionary is already imported'); + await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)).rejects.toThrow('Dictionary is already imported'); await dictionaryDatabase.close(); }); } +/** */ async function testDatabase3() { const invalidDictionaries = [ 'invalid-dictionary1', @@ -828,12 +907,14 @@ async function testDatabase3() { test(`${invalidDictionary}`, async () => { // Setup database const dictionaryDatabase = new DictionaryDatabase(); + /** @type {import('dictionary-importer').ImportDetails} */ + const detaultImportDetails = {prefixWildcardsSupported: false}; await dictionaryDatabase.prepare(); const testDictionary = createTestDictionaryArchive(invalidDictionary); const testDictionarySource = await testDictionary.generateAsync({type: 'arraybuffer'}); - await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, {})).rejects.toThrow('Dictionary has invalid data'); + await expect(createDictionaryImporter().importDictionary(dictionaryDatabase, testDictionarySource, detaultImportDetails)).rejects.toThrow('Dictionary has invalid data'); await dictionaryDatabase.close(); }); } @@ -841,6 +922,7 @@ async function testDatabase3() { } +/** */ async function main() { beforeEach(async () => { globalThis.indexedDB = new IDBFactory(); diff --git a/test/playwright/integration.spec.js b/test/playwright/integration.spec.js index b9a86d84..c8cf2f6d 100644 --- a/test/playwright/integration.spec.js +++ b/test/playwright/integration.spec.js @@ -45,7 +45,8 @@ test('search clipboard', async ({page, extensionId}) => { test('anki add', async ({context, page, extensionId}) => { // mock anki routes - let resolve; + /** @type {?(value: unknown) => void} */ + let resolve = null; const addNotePromise = new Promise((res) => { resolve = res; }); @@ -53,7 +54,7 @@ test('anki add', async ({context, page, extensionId}) => { mockAnkiRouteHandler(route); const req = route.request(); if (req.url().includes('127.0.0.1:8765') && req.postDataJSON().action === 'addNote') { - resolve(req.postDataJSON()); + /** @type {(value: unknown) => void} */ (resolve)(req.postDataJSON()); } }); @@ -63,7 +64,11 @@ test('anki add', async ({context, page, extensionId}) => { // load in test dictionary const dictionary = createDictionaryArchive(path.join(root, 'test/data/dictionaries/valid-dictionary1'), 'valid-dictionary1'); const testDictionarySource = await dictionary.generateAsync({type: 'arraybuffer'}); - await page.locator('input[id="dictionary-import-file-input"]').setInputFiles({name: 'valid-dictionary1.zip', buffer: Buffer.from(testDictionarySource)}); + await page.locator('input[id="dictionary-import-file-input"]').setInputFiles({ + name: 'valid-dictionary1.zip', + mimeType: 'application/x-zip', + buffer: Buffer.from(testDictionarySource) + }); await expect(page.locator('id=dictionaries')).toHaveText('Dictionaries (1 installed, 1 enabled)', {timeout: 5 * 60 * 1000}); // connect to anki @@ -75,7 +80,7 @@ test('anki add', async ({context, page, extensionId}) => { await page.locator('select.anki-card-deck').selectOption('Mock Deck'); await page.locator('select.anki-card-model').selectOption('Mock Model'); for (const modelField of mockModelFieldNames) { - await page.locator(`[data-setting="anki.terms.fields.${modelField}"]`).fill(mockModelFieldsToAnkiValues[modelField]); + await page.locator(`[data-setting="anki.terms.fields.${modelField}"]`).fill(/** @type {string} */ (mockModelFieldsToAnkiValues[modelField])); } await page.locator('#anki-cards-modal > div > div.modal-footer > button:nth-child(2)').click(); await writeToClipboardFromPage(page, '読むの例文'); diff --git a/test/playwright/playwright-util.js b/test/playwright/playwright-util.js index 5ceb92fd..ac68db4d 100644 --- a/test/playwright/playwright-util.js +++ b/test/playwright/playwright-util.js @@ -55,6 +55,7 @@ export const mockModelFieldNames = [ 'Sentence' ]; +/** @type {{[key: string]: string|undefined}} */ export const mockModelFieldsToAnkiValues = { 'Word': '{expression}', 'Reading': '{furigana-plain}', @@ -62,6 +63,10 @@ export const mockModelFieldsToAnkiValues = { 'Audio': '{audio}' }; +/** + * @param {import('playwright').Route} route + * @returns {Promise|undefined} + */ export const mockAnkiRouteHandler = (route) => { const reqBody = route.request().postDataJSON(); const respBody = ankiRouteResponses[reqBody.action]; @@ -71,6 +76,11 @@ export const mockAnkiRouteHandler = (route) => { route.fulfill(respBody); }; +/** + * @param {import('playwright').Page} page + * @param {string} text + * @returns {Promise} + */ export const writeToClipboardFromPage = async (page, text) => { await page.evaluate(`navigator.clipboard.writeText('${text}')`); }; @@ -100,6 +110,7 @@ const baseAnkiResp = { contentType: 'text/json' }; +/** @type {{[key: string]: import('core').SerializableObject}} */ const ankiRouteResponses = { 'version': Object.assign({body: JSON.stringify(6)}, baseAnkiResp), 'deckNames': Object.assign({body: JSON.stringify(['Mock Deck'])}, baseAnkiResp), -- cgit v1.2.3 From bfcc9d3fba4a3ff85be167eb771a4c0f26f9cc49 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 29 Nov 2023 21:52:53 -0500 Subject: Fix rollup type issue --- dev/jsconfig.json | 5 +++-- test/jsconfig.json | 5 +++-- types/other/rollup-parse-ast.d.ts | 25 +++++++++++++++++++++++++ 3 files changed, 31 insertions(+), 4 deletions(-) create mode 100644 types/other/rollup-parse-ast.d.ts (limited to 'test') diff --git a/dev/jsconfig.json b/dev/jsconfig.json index e0074980..a52153a8 100644 --- a/dev/jsconfig.json +++ b/dev/jsconfig.json @@ -3,7 +3,7 @@ "module": "ES2022", "target": "ES2022", "checkJs": true, - "moduleResolution": "bundler", + "moduleResolution": "node", "strict": true, "strictNullChecks": true, "noImplicitAny": true, @@ -36,7 +36,8 @@ "translator": ["../types/ext/translator"], "translation": ["../types/ext/translation"], "translation-internal": ["../types/ext/translation-internal"], - "dev/*": ["../types/dev/*"] + "dev/*": ["../types/dev/*"], + "rollup/parseAst": ["../types/other/rollup-parse-ast"] }, "types": [ "node", diff --git a/test/jsconfig.json b/test/jsconfig.json index 7dbf3950..c587abe6 100644 --- a/test/jsconfig.json +++ b/test/jsconfig.json @@ -3,7 +3,7 @@ "module": "ES2022", "target": "ES2022", "checkJs": true, - "moduleResolution": "bundler", + "moduleResolution": "node", "strict": true, "strictNullChecks": true, "noImplicitAny": true, @@ -14,7 +14,8 @@ "paths": { "*": ["../types/ext/*"], "dev/*": ["../types/dev/*"], - "test/*": ["../types/test/*"] + "test/*": ["../types/test/*"], + "rollup/parseAst": ["../types/other/rollup-parse-ast"] }, "types": [ "chrome", diff --git a/types/other/rollup-parse-ast.d.ts b/types/other/rollup-parse-ast.d.ts new file mode 100644 index 00000000..52a5ec98 --- /dev/null +++ b/types/other/rollup-parse-ast.d.ts @@ -0,0 +1,25 @@ +/* + * Copyright (C) 2023 Yomitan 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 . + */ + +// Patch for type definitions that aren't exported for rollup/parseAst +// https://github.com/vitest-dev/vitest/issues/4567 +// https://github.com/rollup/rollup/issues/5199 + +import type {ParseAst, ParseAstAsync} from 'rollup'; + +export const parseAst: ParseAst; +export const parseAstAsync: ParseAstAsync; -- cgit v1.2.3 From f0c5ef69ca1758d5ef9201bc8a4375e5933888be Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 29 Nov 2023 21:53:22 -0500 Subject: Update types --- test/deinflector.test.js | 14 ++++- test/document-util.test.js | 118 ++++++++++++++++++++++++++++++++---------- test/dom-text-scanner.test.js | 43 ++++++++++++--- test/hotkey-util.test.js | 7 +++ 4 files changed, 147 insertions(+), 35 deletions(-) (limited to 'test') diff --git a/test/deinflector.test.js b/test/deinflector.test.js index edb85833..77184799 100644 --- a/test/deinflector.test.js +++ b/test/deinflector.test.js @@ -21,8 +21,16 @@ import path from 'path'; import {describe, expect, test} from 'vitest'; import {Deinflector} from '../ext/js/language/deinflector.js'; +/** + * @param {Deinflector} deinflector + * @param {string} source + * @param {string} expectedTerm + * @param {string} expectedRule + * @param {string[]|undefined} expectedReasons + * @returns {{has: false, reasons: null, rules: null}|{has: true, reasons: string[], rules: number}} + */ function hasTermReasons(deinflector, source, expectedTerm, expectedRule, expectedReasons) { - for (const {term, reasons, rules} of deinflector.deinflect(source, source)) { + for (const {term, reasons, rules} of deinflector.deinflect(source)) { if (term !== expectedTerm) { continue; } if (typeof expectedRule !== 'undefined') { const expectedFlags = Deinflector.rulesToRuleFlags([expectedRule]); @@ -46,6 +54,7 @@ function hasTermReasons(deinflector, source, expectedTerm, expectedRule, expecte } +/** */ function testDeinflections() { const data = [ { @@ -915,7 +924,7 @@ function testDeinflections() { } ]; - const deinflectionReasons = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'ext', 'data/deinflect.json'))); + const deinflectionReasons = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'ext', 'data/deinflect.json'), {encoding: 'utf8'})); const deinflector = new Deinflector(deinflectionReasons); describe('deinflections', () => { @@ -939,6 +948,7 @@ function testDeinflections() { } +/** */ function main() { testDeinflections(); } diff --git a/test/document-util.test.js b/test/document-util.test.js index f2552f78..8c6ab69b 100644 --- a/test/document-util.test.js +++ b/test/document-util.test.js @@ -30,24 +30,48 @@ const dirname = path.dirname(fileURLToPath(import.meta.url)); // DOMRect class definition class DOMRect { + /** + * @param {number} x + * @param {number} y + * @param {number} width + * @param {number} height + */ constructor(x, y, width, height) { + /** @type {number} */ this._x = x; + /** @type {number} */ this._y = y; + /** @type {number} */ this._width = width; + /** @type {number} */ this._height = height; } + /** @type {number} */ get x() { return this._x; } + /** @type {number} */ get y() { return this._y; } + /** @type {number} */ get width() { return this._width; } + /** @type {number} */ get height() { return this._height; } + /** @type {number} */ get left() { return this._x + Math.min(0, this._width); } + /** @type {number} */ get right() { return this._x + Math.max(0, this._width); } + /** @type {number} */ get top() { return this._y + Math.min(0, this._height); } + /** @type {number} */ get bottom() { return this._y + Math.max(0, this._height); } + /** @returns {string} */ + toJSON() { return ''; } } +/** + * @param {string} fileName + * @returns {JSDOM} + */ function createJSDOM(fileName) { const domSource = fs.readFileSync(fileName, {encoding: 'utf8'}); const dom = new JSDOM(domSource); @@ -65,10 +89,20 @@ function createJSDOM(fileName) { return dom; } +/** + * @param {Element} element + * @param {string|undefined} selector + * @returns {?Element} + */ function querySelectorChildOrSelf(element, selector) { return selector ? element.querySelector(selector) : element; } +/** + * @param {JSDOM} dom + * @param {?Node} node + * @returns {?Text|Node} + */ function getChildTextNodeOrSelf(dom, node) { if (node === null) { return null; } const Node = dom.window.Node; @@ -76,6 +110,10 @@ function getChildTextNodeOrSelf(dom, node) { return (childNode !== null && childNode.nodeType === Node.TEXT_NODE ? childNode : node); } +/** + * @param {unknown} value + * @returns {unknown} + */ function getPrototypeOfOrNull(value) { try { return Object.getPrototypeOf(value); @@ -84,12 +122,17 @@ function getPrototypeOfOrNull(value) { } } +/** + * @param {Document} document + * @returns {?Element} + */ function findImposterElement(document) { // Finds the imposter element based on it's z-index style return document.querySelector('div[style*="2147483646"]>*'); } +/** */ async function testDocument1() { const dom = createJSDOM(path.join(dirname, 'data', 'html', 'test-document1.html')); const window = dom.window; @@ -102,13 +145,16 @@ async function testDocument1() { } } +/** + * @param {JSDOM} dom + */ async function testDocumentTextScanningFunctions(dom) { const document = dom.window.document; test('DocumentTextScanningFunctions', () => { - for (const testElement of document.querySelectorAll('.test[data-test-type=scan]')) { - // Get test parameters - let { + for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('.test[data-test-type=scan]'))) { + // Get test parameters + const { elementFromPointSelector, caretRangeFromPointSelector, startNodeSelector, @@ -127,10 +173,10 @@ async function testDocumentTextScanningFunctions(dom) { const startNode = getChildTextNodeOrSelf(dom, querySelectorChildOrSelf(testElement, startNodeSelector)); const endNode = getChildTextNodeOrSelf(dom, querySelectorChildOrSelf(testElement, endNodeSelector)); - startOffset = parseInt(startOffset, 10); - endOffset = parseInt(endOffset, 10); - sentenceScanExtent = parseInt(sentenceScanExtent, 10); - terminateAtNewlines = (terminateAtNewlines !== 'false'); + const startOffset2 = parseInt(/** @type {string} */ (startOffset), 10); + const endOffset2 = parseInt(/** @type {string} */ (endOffset), 10); + const sentenceScanExtent2 = parseInt(/** @type {string} */ (sentenceScanExtent), 10); + const terminateAtNewlines2 = (terminateAtNewlines !== 'false'); expect(elementFromPointValue).not.toStrictEqual(null); expect(caretRangeFromPointValue).not.toStrictEqual(null); @@ -145,11 +191,25 @@ async function testDocumentTextScanningFunctions(dom) { expect(!!imposter).toStrictEqual(hasImposter === 'true'); const range = document.createRange(); - range.setStart(imposter ? imposter : startNode, startOffset); - range.setEnd(imposter ? imposter : startNode, endOffset); + range.setStart(/** @type {Node} */ (imposter ? imposter : startNode), startOffset2); + range.setEnd(/** @type {Node} */ (imposter ? imposter : startNode), endOffset2); // Override getClientRects to return a rect guaranteed to contain (x, y) - range.getClientRects = () => [new DOMRect(x - 1, y - 1, 2, 2)]; + range.getClientRects = () => { + /** @type {import('test/document-types').PseudoDOMRectList} */ + const domRectList = Object.assign( + [new DOMRect(x - 1, y - 1, 2, 2)], + { + /** + * @this {DOMRect[]} + * @param {number} index + * @returns {DOMRect} + */ + item: function item(index) { return this[index]; } + } + ); + return domRectList; + }; return range; }; @@ -192,8 +252,8 @@ async function testDocumentTextScanningFunctions(dom) { const sentenceActual = DocumentUtil.extractSentence( source, false, - sentenceScanExtent, - terminateAtNewlines, + sentenceScanExtent2, + terminateAtNewlines2, terminatorMap, forwardQuoteMap, backwardQuoteMap @@ -206,13 +266,16 @@ async function testDocumentTextScanningFunctions(dom) { }); } +/** + * @param {JSDOM} dom + */ async function testTextSourceRangeSeekFunctions(dom) { const document = dom.window.document; test('TextSourceRangeSeekFunctions', async () => { - for (const testElement of document.querySelectorAll('.test[data-test-type=text-source-range-seek]')) { - // Get test parameters - let { + for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('.test[data-test-type=text-source-range-seek]'))) { + // Get test parameters + const { seekNodeSelector, seekNodeIsText, seekOffset, @@ -224,34 +287,37 @@ async function testTextSourceRangeSeekFunctions(dom) { expectedResultContent } = testElement.dataset; - seekOffset = parseInt(seekOffset, 10); - seekLength = parseInt(seekLength, 10); - expectedResultOffset = parseInt(expectedResultOffset, 10); + const seekOffset2 = parseInt(/** @type {string} */ (seekOffset), 10); + const seekLength2 = parseInt(/** @type {string} */ (seekLength), 10); + const expectedResultOffset2 = parseInt(/** @type {string} */ (expectedResultOffset), 10); - let seekNode = testElement.querySelector(seekNodeSelector); - if (seekNodeIsText === 'true') { + /** @type {?Node} */ + let seekNode = testElement.querySelector(/** @type {string} */ (seekNodeSelector)); + if (seekNodeIsText === 'true' && seekNode !== null) { seekNode = seekNode.firstChild; } - let expectedResultNode = testElement.querySelector(expectedResultNodeSelector); - if (expectedResultNodeIsText === 'true') { + /** @type {?Node} */ + let expectedResultNode = testElement.querySelector(/** @type {string} */ (expectedResultNodeSelector)); + if (expectedResultNodeIsText === 'true' && expectedResultNode !== null) { expectedResultNode = expectedResultNode.firstChild; } const {node, offset, content} = ( - seekDirection === 'forward' ? - new DOMTextScanner(seekNode, seekOffset, true, false).seek(seekLength) : - new DOMTextScanner(seekNode, seekOffset, true, false).seek(-seekLength) + seekDirection === 'forward' ? + new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset2, true, false).seek(seekLength2) : + new DOMTextScanner(/** @type {Node} */ (seekNode), seekOffset2, true, false).seek(-seekLength2) ); expect(node).toStrictEqual(expectedResultNode); - expect(offset).toStrictEqual(expectedResultOffset); + expect(offset).toStrictEqual(expectedResultOffset2); expect(content).toStrictEqual(expectedResultContent); } }); } +/** */ async function main() { await testDocument1(); } diff --git a/test/dom-text-scanner.test.js b/test/dom-text-scanner.test.js index d1b31276..6d0c047b 100644 --- a/test/dom-text-scanner.test.js +++ b/test/dom-text-scanner.test.js @@ -22,11 +22,21 @@ import path from 'path'; import {expect, test} from 'vitest'; import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js'; + +/** + * @param {string} fileName + * @returns {JSDOM} + */ function createJSDOM(fileName) { const domSource = fs.readFileSync(fileName, {encoding: 'utf8'}); return new JSDOM(domSource); } +/** + * @param {Element} element + * @param {string} selector + * @returns {?Node} + */ function querySelectorTextNode(element, selector) { let textIndex = -1; const match = /::text$|::nth-text\((\d+)\)$/.exec(selector); @@ -35,13 +45,16 @@ function querySelectorTextNode(element, selector) { selector = selector.substring(0, selector.length - match[0].length); } const result = element.querySelector(selector); + if (result === null) { + return null; + } if (textIndex < 0) { return result; } for (let n = result.firstChild; n !== null; n = n.nextSibling) { - if (n.nodeType === n.constructor.TEXT_NODE) { + if (n.nodeType === /** @type {typeof Node} */ (n.constructor).TEXT_NODE) { if (textIndex === 0) { - return n; + return /** @type {Text} */ (n); } --textIndex; } @@ -50,10 +63,16 @@ function querySelectorTextNode(element, selector) { } +/** + * @param {import('jsdom').DOMWindow} window + * @param {(element: Element) => CSSStyleDeclaration} getComputedStyle + * @param {?Node} element + * @returns {number} + */ function getComputedFontSizeInPixels(window, getComputedStyle, element) { for (; element !== null; element = element.parentNode) { if (element.nodeType === window.Node.ELEMENT_NODE) { - const fontSize = getComputedStyle(element).fontSize; + const fontSize = getComputedStyle(/** @type {Element} */ (element)).fontSize; if (fontSize.endsWith('px')) { const value = parseFloat(fontSize.substring(0, fontSize.length - 2)); return value; @@ -64,14 +83,19 @@ function getComputedFontSizeInPixels(window, getComputedStyle, element) { return defaultFontSize; } +/** + * @param {import('jsdom').DOMWindow} window + * @returns {(element: Element, pseudoElement?: ?string) => CSSStyleDeclaration} + */ function createAbsoluteGetComputedStyle(window) { // Wrapper to convert em units to px units const getComputedStyleOld = window.getComputedStyle.bind(window); + /** @type {(element: Element, pseudoElement?: ?string) => CSSStyleDeclaration} */ return (element, ...args) => { const style = getComputedStyleOld(element, ...args); return new Proxy(style, { get: (target, property) => { - let result = target[property]; + let result = /** @type {import('core').SafeAny} */ (target)[property]; if (typeof result === 'string') { result = result.replace(/([-+]?\d(?:\.\d)?(?:[eE][-+]?\d+)?)em/g, (g0, g1) => { const fontSize = getComputedFontSizeInPixels(window, getComputedStyleOld, element); @@ -85,12 +109,15 @@ function createAbsoluteGetComputedStyle(window) { } +/** + * @param {JSDOM} dom + */ async function testDomTextScanner(dom) { const document = dom.window.document; test('DomTextScanner', () => { - for (const testElement of document.querySelectorAll('y-test')) { - let testData = JSON.parse(testElement.dataset.testData); + for (const testElement of /** @type {NodeListOf} */ (document.querySelectorAll('y-test'))) { + let testData = JSON.parse(/** @type {string} */ (testElement.dataset.testData)); if (!Array.isArray(testData)) { testData = [testData]; } @@ -159,19 +186,21 @@ async function testDomTextScanner(dom) { } +/** */ async function testDocument1() { const dom = createJSDOM(path.join(__dirname, 'data', 'html', 'test-dom-text-scanner.html')); const window = dom.window; try { window.getComputedStyle = createAbsoluteGetComputedStyle(window); - await testDomTextScanner(dom, {DOMTextScanner}); + await testDomTextScanner(dom); } finally { window.close(); } } +/** */ async function main() { await testDocument1(); } diff --git a/test/hotkey-util.test.js b/test/hotkey-util.test.js index 8666b98b..02622c40 100644 --- a/test/hotkey-util.test.js +++ b/test/hotkey-util.test.js @@ -19,8 +19,10 @@ import {expect, test} from 'vitest'; import {HotkeyUtil} from '../ext/js/input/hotkey-util.js'; +/** */ function testCommandConversions() { test('CommandConversions', () => { + /** @type {{os: import('environment').OperatingSystem, command: string, expectedCommand: string, expectedInput: {key: string, modifiers: import('input').Modifier[]}}[]} */ const data = [ {os: 'win', command: 'Alt+F', expectedCommand: 'Alt+F', expectedInput: {key: 'KeyF', modifiers: ['alt']}}, {os: 'win', command: 'F1', expectedCommand: 'F1', expectedInput: {key: 'F1', modifiers: []}}, @@ -49,8 +51,10 @@ function testCommandConversions() { }); } +/** */ function testDisplayNames() { test('DisplayNames', () => { + /** @type {{os: import('environment').OperatingSystem, key: ?string, modifiers: import('input').Modifier[], expected: string}[]} */ const data = [ {os: 'win', key: null, modifiers: [], expected: ''}, {os: 'win', key: 'KeyF', modifiers: [], expected: 'F'}, @@ -138,8 +142,10 @@ function testDisplayNames() { }); } +/** */ function testSortModifiers() { test('SortModifiers', () => { + /** @type {{modifiers: import('input').Modifier[], expected: import('input').Modifier[]}[]} */ const data = [ {modifiers: [], expected: []}, {modifiers: ['shift', 'alt', 'ctrl', 'mouse4', 'meta', 'mouse1', 'mouse0'], expected: ['meta', 'ctrl', 'alt', 'shift', 'mouse0', 'mouse1', 'mouse4']} @@ -155,6 +161,7 @@ function testSortModifiers() { } +/** */ function main() { testCommandConversions(); testDisplayNames(); -- cgit v1.2.3 From 01142ef3d62e388a1c952ad9ea3a01ed38b518b6 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 29 Nov 2023 22:06:38 -0500 Subject: Update types --- test/japanese-util.test.js | 39 +++- test/jsdom.test.js | 9 +- test/json-schema.test.js | 71 +++++-- test/options-util.test.js | 49 ++++- test/profile-conditions-util.test.js | 357 +++++++++++++++++++---------------- test/text-source-map.test.js | 10 +- test/translator.test.js | 5 +- 7 files changed, 347 insertions(+), 193 deletions(-) (limited to 'test') diff --git a/test/japanese-util.test.js b/test/japanese-util.test.js index 47da4ccb..a0078da0 100644 --- a/test/japanese-util.test.js +++ b/test/japanese-util.test.js @@ -23,8 +23,10 @@ import * as wanakana from '../ext/lib/wanakana.js'; const jp = new JapaneseUtil(wanakana); +/** */ function testIsCodePointKanji() { test('isCodePointKanji', () => { + /** @type {[characters: string, expected: boolean][]} */ const data = [ ['力方', true], ['\u53f1\u{20b9f}', true], @@ -34,7 +36,7 @@ function testIsCodePointKanji() { for (const [characters, expected] of data) { for (const character of characters) { - const codePoint = character.codePointAt(0); + const codePoint = /** @type {number} */ (character.codePointAt(0)); const actual = jp.isCodePointKanji(codePoint); expect(actual).toStrictEqual(expected); // `isCodePointKanji failed for ${character} (\\u{${codePoint.toString(16)}})` } @@ -42,8 +44,10 @@ function testIsCodePointKanji() { }); } +/** */ function testIsCodePointKana() { test('isCodePointKana', () => { + /** @type {[characters: string, expected: boolean][]} */ const data = [ ['かたカタ', true], ['力方々kata、。?,.?', false], @@ -52,7 +56,7 @@ function testIsCodePointKana() { for (const [characters, expected] of data) { for (const character of characters) { - const codePoint = character.codePointAt(0); + const codePoint = /** @type {number} */ (character.codePointAt(0)); const actual = jp.isCodePointKana(codePoint); expect(actual).toStrictEqual(expected); // `isCodePointKana failed for ${character} (\\u{${codePoint.toString(16)}})` } @@ -60,8 +64,10 @@ function testIsCodePointKana() { }); } +/** */ function testIsCodePointJapanese() { test('isCodePointJapanese', () => { + /** @type {[characters: string, expected: boolean][]} */ const data = [ ['かたカタ力方々、。?', true], ['\u53f1\u{20b9f}', true], @@ -71,7 +77,7 @@ function testIsCodePointJapanese() { for (const [characters, expected] of data) { for (const character of characters) { - const codePoint = character.codePointAt(0); + const codePoint = /** @type {number} */ (character.codePointAt(0)); const actual = jp.isCodePointJapanese(codePoint); expect(actual).toStrictEqual(expected); // `isCodePointJapanese failed for ${character} (\\u{${codePoint.toString(16)}})` } @@ -79,8 +85,10 @@ function testIsCodePointJapanese() { }); } +/** */ function testIsStringEntirelyKana() { test('isStringEntirelyKana', () => { + /** @type {[string: string, expected: boolean][]} */ const data = [ ['かたかな', true], ['カタカナ', true], @@ -101,8 +109,10 @@ function testIsStringEntirelyKana() { }); } +/** */ function testIsStringPartiallyJapanese() { test('isStringPartiallyJapanese', () => { + /** @type {[string: string, expected: boolean][]} */ const data = [ ['かたかな', true], ['カタカナ', true], @@ -124,8 +134,10 @@ function testIsStringPartiallyJapanese() { }); } +/** */ function testConvertKatakanaToHiragana() { test('convertKatakanaToHiragana', () => { + /** @type {[string: string, expected: string, keepProlongedSoundMarks?: boolean][]} */ const data = [ ['かたかな', 'かたかな'], ['ひらがな', 'ひらがな'], @@ -146,8 +158,10 @@ function testConvertKatakanaToHiragana() { }); } +/** */ function testConvertHiraganaToKatakana() { test('ConvertHiraganaToKatakana', () => { + /** @type {[string: string, expected: string][]} */ const data = [ ['かたかな', 'カタカナ'], ['ひらがな', 'ヒラガナ'], @@ -166,8 +180,10 @@ function testConvertHiraganaToKatakana() { }); } +/** */ function testConvertToRomaji() { test('ConvertToRomaji', () => { + /** @type {[string: string, expected: string][]} */ const data = [ ['かたかな', 'katakana'], ['ひらがな', 'hiragana'], @@ -186,8 +202,10 @@ function testConvertToRomaji() { }); } +/** */ function testConvertNumericToFullWidth() { test('ConvertNumericToFullWidth', () => { + /** @type {[string: string, expected: string][]} */ const data = [ ['0123456789', '0123456789'], ['abcdefghij', 'abcdefghij'], @@ -201,8 +219,10 @@ function testConvertNumericToFullWidth() { }); } +/** */ function testConvertHalfWidthKanaToFullWidth() { test('ConvertHalfWidthKanaToFullWidth', () => { + /** @type {[string: string, expected: string, expectedSourceMapping?: number[]][]} */ const data = [ ['0123456789', '0123456789'], ['abcdefghij', 'abcdefghij'], @@ -227,8 +247,10 @@ function testConvertHalfWidthKanaToFullWidth() { }); } +/** */ function testConvertAlphabeticToKana() { test('ConvertAlphabeticToKana', () => { + /** @type {[string: string, expected: string, expectedSourceMapping?: number[]][]} */ const data = [ ['0123456789', '0123456789'], ['abcdefghij', 'あbcでfgひj', [1, 1, 1, 2, 1, 1, 2, 1]], @@ -252,8 +274,10 @@ function testConvertAlphabeticToKana() { }); } +/** */ function testDistributeFurigana() { test('DistributeFurigana', () => { + /** @type {[input: [term: string, reading: string], expected: {text: string, reading: string}[]][]} */ const data = [ [ ['有り難う', 'ありがとう'], @@ -719,8 +743,10 @@ function testDistributeFurigana() { }); } +/** */ function testDistributeFuriganaInflected() { test('DistributeFuriganaInflected', () => { + /** @type {[input: [term: string, reading: string, source: string], expected: {text: string, reading: string}[]][]} */ const data = [ [ ['美味しい', 'おいしい', '美味しかた'], @@ -770,8 +796,10 @@ function testDistributeFuriganaInflected() { }); } +/** */ function testCollapseEmphaticSequences() { test('CollapseEmphaticSequences', () => { + /** @type {[input: [text: string, fullCollapse: boolean], output: [expected: string, expectedSourceMapping: number[]]][]} */ const data = [ [['かこい', false], ['かこい', [1, 1, 1]]], [['かこい', true], ['かこい', [1, 1, 1]]], @@ -825,8 +853,10 @@ function testCollapseEmphaticSequences() { }); } +/** */ function testIsMoraPitchHigh() { test('IsMoraPitchHigh', () => { + /** @type {[input: [moraIndex: number, pitchAccentDownstepPosition: number], expected: boolean][]} */ const data = [ [[0, 0], false], [[1, 0], true], @@ -861,8 +891,10 @@ function testIsMoraPitchHigh() { }); } +/** */ function testGetKanaMorae() { test('GetKanaMorae', () => { + /** @type {[text: string, expected: string[]][]} */ const data = [ ['かこ', ['か', 'こ']], ['かっこ', ['か', 'っ', 'こ']], @@ -883,6 +915,7 @@ function testGetKanaMorae() { } +/** */ function main() { testIsCodePointKanji(); testIsCodePointKana(); diff --git a/test/jsdom.test.js b/test/jsdom.test.js index c53f374e..6c2e00ee 100644 --- a/test/jsdom.test.js +++ b/test/jsdom.test.js @@ -26,20 +26,25 @@ import {expect, test} from 'vitest'; */ function testJSDOMSelectorBug() { test('JSDOMSelectorBug', () => { - // nwsapi is used by JSDOM + // nwsapi is used by JSDOM const dom = new JSDOM(); const {document} = dom.window; const div = document.createElement('div'); div.innerHTML = '
'; const c = div.querySelector('.c'); - expect(() => c.matches('.a:nth-last-of-type(1) .b .c')).not.toThrow(); + expect(() => { + if (c === null) { throw new Error('Element not found'); } + c.matches('.a:nth-last-of-type(1) .b .c'); + }).not.toThrow(); }); } +/** */ export function testJSDOM() { testJSDOMSelectorBug(); } +/** */ function main() { testJSDOM(); } diff --git a/test/json-schema.test.js b/test/json-schema.test.js index 5370e8da..e534f538 100644 --- a/test/json-schema.test.js +++ b/test/json-schema.test.js @@ -19,25 +19,47 @@ import {expect, test} from 'vitest'; import {JsonSchema} from '../ext/js/data/json-schema.js'; +/** + * @param {import('json-schema').Schema} schema + * @param {unknown} value + * @returns {boolean} + */ function schemaValidate(schema, value) { return new JsonSchema(schema).isValid(value); } +/** + * @param {import('json-schema').Schema} schema + * @param {unknown} value + * @returns {import('json-schema').Value} + */ function getValidValueOrDefault(schema, value) { return new JsonSchema(schema).getValidValueOrDefault(value); } +/** + * @param {import('json-schema').Schema} schema + * @param {import('json-schema').Value} value + * @returns {import('json-schema').Value} + */ function createProxy(schema, value) { return new JsonSchema(schema).createProxy(value); } +/** + * @template T + * @param {T} value + * @returns {T} + */ function clone(value) { return JSON.parse(JSON.stringify(value)); } +/** */ function testValidate1() { test('Validate1', () => { + /** @type {import('json-schema').Schema} */ const schema = { allOf: [ { @@ -56,28 +78,34 @@ function testValidate1() { ] }, { - not: [ - {multipleOf: 20} - ] + not: { + anyOf: [ + {multipleOf: 20} + ] + } } ] }; + /** + * @param {number} value + * @returns {boolean} + */ const jsValidate = (value) => { return ( typeof value === 'number' && - ( - (value >= 10 && value <= 100) || - (value >= -100 && value <= -10) - ) && - ( ( - (value % 3) === 0 || - (value % 5) === 0 + (value >= 10 && value <= 100) || + (value >= -100 && value <= -10) + ) && + ( + ( + (value % 3) === 0 || + (value % 5) === 0 + ) && + (value % 15) !== 0 ) && - (value % 15) !== 0 - ) && - (value % 20) !== 0 + (value % 20) !== 0 ); }; @@ -89,10 +117,12 @@ function testValidate1() { }); } +/** */ function testValidate2() { test('Validate2', () => { + /** @type {{schema: import('json-schema').Schema, inputs: {expected: boolean, value: unknown}[]}[]} */ const data = [ - // String tests + // String tests { schema: { type: 'string' @@ -494,10 +524,12 @@ function testValidate2() { } +/** */ function testGetValidValueOrDefault1() { test('GetValidValueOrDefault1', () => { + /** @type {{schema: import('json-schema').Schema, inputs: [value: unknown, expected: unknown][]}[]} */ const data = [ - // Test value defaulting on objects with additionalProperties=false + // Test value defaulting on objects with additionalProperties=false { schema: { type: 'object', @@ -667,10 +699,10 @@ function testGetValidValueOrDefault1() { type: 'object', required: ['toString'], properties: { - toString: { + toString: /** @type {import('json-schema').SchemaObject} */ ({ type: 'string', default: 'default' - } + }) } }, inputs: [ @@ -850,10 +882,12 @@ function testGetValidValueOrDefault1() { } +/** */ function testProxy1() { test('Proxy1', () => { + /** @type {{schema: import('json-schema').Schema, tests: {error: boolean, value?: import('json-schema').Value, action: (value: import('core').SafeAny) => void}[]}[]} */ const data = [ - // Object tests + // Object tests { schema: { type: 'object', @@ -998,6 +1032,7 @@ function testProxy1() { } +/** */ function main() { testValidate1(); testValidate2(); diff --git a/test/options-util.test.js b/test/options-util.test.js index ff5b6713..7845d759 100644 --- a/test/options-util.test.js +++ b/test/options-util.test.js @@ -24,7 +24,12 @@ import {OptionsUtil} from '../ext/js/data/options-util.js'; import {TemplatePatcher} from '../ext/js/templates/template-patcher.js'; const dirname = path.dirname(fileURLToPath(import.meta.url)); -vi.stubGlobal('fetch', async function fetch(url2) { + +/** + * @param {string} url2 + * @returns {Promise} + */ +async function fetch(url2) { const filePath = url.fileURLToPath(url2); await Promise.resolve(); const content = fs.readFileSync(filePath, {encoding: null}); @@ -35,15 +40,22 @@ vi.stubGlobal('fetch', async function fetch(url2) { text: async () => Promise.resolve(content.toString('utf8')), json: async () => Promise.resolve(JSON.parse(content.toString('utf8'))) }; -}); -vi.stubGlobal('chrome', { +} +/** @type {import('dev/vm').PseudoChrome} */ +const chrome = { runtime: { - getURL: (path2) => { + getURL(path2) { return url.pathToFileURL(path.join(dirname, '..', 'ext', path2.replace(/^\//, ''))).href; } } -}); +}; + +vi.stubGlobal('fetch', fetch); +vi.stubGlobal('chrome', chrome); +/** + * @returns {unknown} + */ function createProfileOptionsTestData1() { return { version: 14, @@ -144,6 +156,9 @@ function createProfileOptionsTestData1() { }; } +/** + * @returns {unknown} + */ function createOptionsTestData1() { return { profiles: [ @@ -243,6 +258,9 @@ function createOptionsTestData1() { } +/** + * @returns {unknown} + */ function createProfileOptionsUpdatedTestData1() { return { general: { @@ -519,6 +537,9 @@ function createProfileOptionsUpdatedTestData1() { }; } +/** + * @returns {unknown} + */ function createOptionsUpdatedTestData1() { return { profiles: [ @@ -612,6 +633,7 @@ function createOptionsUpdatedTestData1() { } +/** */ async function testUpdate() { test('Update', async () => { const optionsUtil = new OptionsUtil(); @@ -624,8 +646,10 @@ async function testUpdate() { }); } +/** */ async function testDefault() { test('Default', async () => { + /** @type {((options: import('options-util').IntermediateOptions) => void)[]} */ const data = [ (options) => options, (options) => { @@ -651,12 +675,17 @@ async function testDefault() { }); } +/** */ async function testFieldTemplatesUpdate() { test('FieldTemplatesUpdate', async () => { const optionsUtil = new OptionsUtil(); await optionsUtil.prepare(); const templatePatcher = new TemplatePatcher(); + /** + * @param {string} fileName + * @returns {string} + */ const loadDataFile = (fileName) => { const content = fs.readFileSync(path.join(dirname, '..', 'ext', fileName), {encoding: 'utf8'}); return templatePatcher.parsePatch(content).addition; @@ -671,6 +700,11 @@ async function testFieldTemplatesUpdate() { {version: 13, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v13.handlebars')}, {version: 21, changes: loadDataFile('data/templates/anki-field-templates-upgrade-v21.handlebars')} ]; + /** + * @param {number} startVersion + * @param {number} targetVersion + * @returns {string} + */ const getUpdateAdditions = (startVersion, targetVersion) => { let value = ''; for (const {version, changes} of updates) { @@ -682,7 +716,7 @@ async function testFieldTemplatesUpdate() { }; const data = [ - // Standard format + // Standard format { oldVersion: 0, newVersion: 12, @@ -1564,7 +1598,7 @@ async function testFieldTemplatesUpdate() { const updatesPattern = /<<>>/g; for (const {old, expected, oldVersion, newVersion} of data) { - const options = createOptionsTestData1(); + const options = /** @type {import('core').SafeAny} */ (createOptionsTestData1()); options.profiles[0].options.anki.fieldTemplates = old; options.version = oldVersion; @@ -1578,6 +1612,7 @@ async function testFieldTemplatesUpdate() { } +/** */ async function main() { await testUpdate(); await testDefault(); diff --git a/test/profile-conditions-util.test.js b/test/profile-conditions-util.test.js index ca8b00ef..30052b34 100644 --- a/test/profile-conditions-util.test.js +++ b/test/profile-conditions-util.test.js @@ -19,31 +19,33 @@ import {expect, test} from 'vitest'; import {ProfileConditionsUtil} from '../ext/js/background/profile-conditions-util.js'; +/** */ function testNormalizeContext() { test('NormalizeContext', () => { + /** @type {{context: import('settings').OptionsContext, expected: import('profile-conditions-util').NormalizedOptionsContext}[]} */ const data = [ - // Empty + // Empty { - context: {}, - expected: {flags: []} + context: {index: 0}, + expected: {index: 0, flags: []} }, // Domain normalization { - context: {url: ''}, - expected: {url: '', flags: []} + context: {depth: 0, url: ''}, + expected: {depth: 0, url: '', flags: []} }, { - context: {url: 'http://example.com/'}, - expected: {url: 'http://example.com/', domain: 'example.com', flags: []} + context: {depth: 0, url: 'http://example.com/'}, + expected: {depth: 0, url: 'http://example.com/', domain: 'example.com', flags: []} }, { - context: {url: 'http://example.com:1234/'}, - expected: {url: 'http://example.com:1234/', domain: 'example.com', flags: []} + context: {depth: 0, url: 'http://example.com:1234/'}, + expected: {depth: 0, url: 'http://example.com:1234/', domain: 'example.com', flags: []} }, { - context: {url: 'http://user@example.com:1234/'}, - expected: {url: 'http://user@example.com:1234/', domain: 'example.com', flags: []} + context: {depth: 0, url: 'http://user@example.com:1234/'}, + expected: {depth: 0, url: 'http://user@example.com:1234/', domain: 'example.com', flags: []} } ]; @@ -55,15 +57,17 @@ function testNormalizeContext() { }); } +/** */ function testSchemas() { test('Schemas', () => { + /** @type {{conditionGroups: import('settings').ProfileConditionGroup[], expectedSchema?: import('json-schema').Schema, inputs?: {expected: boolean, context: import('settings').OptionsContext}[]}[]} */ const data = [ - // Empty + // Empty { conditionGroups: [], expectedSchema: {}, inputs: [ - {expected: true, context: {url: 'http://example.com/'}} + {expected: true, context: {depth: 0, url: 'http://example.com/'}} ] }, { @@ -72,7 +76,7 @@ function testSchemas() { ], expectedSchema: {}, inputs: [ - {expected: true, context: {url: 'http://example.com/'}} + {expected: true, context: {depth: 0, url: 'http://example.com/'}} ] }, { @@ -82,7 +86,7 @@ function testSchemas() { ], expectedSchema: {}, inputs: [ - {expected: true, context: {url: 'http://example.com/'}} + {expected: true, context: {depth: 0, url: 'http://example.com/'}} ] }, @@ -124,14 +128,16 @@ function testSchemas() { } ], expectedSchema: { - not: [ - { - properties: { - depth: {const: 0} - }, - required: ['depth'] - } - ] + not: { + anyOf: [ + { + properties: { + depth: {const: 0} + }, + required: ['depth'] + } + ] + } }, inputs: [ {expected: false, context: {depth: 0, url: 'http://example.com/'}}, @@ -371,9 +377,9 @@ function testSchemas() { }, inputs: [ {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift', 'Ctrl']}} + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} ] }, { @@ -383,7 +389,7 @@ function testSchemas() { { type: 'modifierKeys', operator: 'are', - value: 'Alt, Shift' + value: 'alt, shift' } ] } @@ -395,8 +401,8 @@ function testSchemas() { maxItems: 2, minItems: 2, allOf: [ - {contains: {const: 'Alt'}}, - {contains: {const: 'Shift'}} + {contains: {const: 'alt'}}, + {contains: {const: 'shift'}} ] } }, @@ -404,9 +410,9 @@ function testSchemas() { }, inputs: [ {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift', 'Ctrl']}} + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} ] }, { @@ -422,24 +428,26 @@ function testSchemas() { } ], expectedSchema: { - not: [ - { - properties: { - modifierKeys: { - type: 'array', - maxItems: 0, - minItems: 0 - } - }, - required: ['modifierKeys'] - } - ] + not: { + anyOf: [ + { + properties: { + modifierKeys: { + type: 'array', + maxItems: 0, + minItems: 0 + } + }, + required: ['modifierKeys'] + } + ] + } }, inputs: [ {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift', 'Ctrl']}} + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} ] }, { @@ -449,34 +457,36 @@ function testSchemas() { { type: 'modifierKeys', operator: 'areNot', - value: 'Alt, Shift' + value: 'alt, shift' } ] } ], expectedSchema: { - not: [ - { - properties: { - modifierKeys: { - type: 'array', - maxItems: 2, - minItems: 2, - allOf: [ - {contains: {const: 'Alt'}}, - {contains: {const: 'Shift'}} - ] - } - }, - required: ['modifierKeys'] - } - ] + not: { + anyOf: [ + { + properties: { + modifierKeys: { + type: 'array', + maxItems: 2, + minItems: 2, + allOf: [ + {contains: {const: 'alt'}}, + {contains: {const: 'shift'}} + ] + } + }, + required: ['modifierKeys'] + } + ] + } }, inputs: [ {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift', 'Ctrl']}} + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} ] }, { @@ -502,9 +512,9 @@ function testSchemas() { }, inputs: [ {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift', 'Ctrl']}} + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} ] }, { @@ -514,7 +524,7 @@ function testSchemas() { { type: 'modifierKeys', operator: 'include', - value: 'Alt, Shift' + value: 'alt, shift' } ] } @@ -525,8 +535,8 @@ function testSchemas() { type: 'array', minItems: 2, allOf: [ - {contains: {const: 'Alt'}}, - {contains: {const: 'Shift'}} + {contains: {const: 'alt'}}, + {contains: {const: 'shift'}} ] } }, @@ -534,9 +544,9 @@ function testSchemas() { }, inputs: [ {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift', 'Ctrl']}} + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} ] }, { @@ -561,9 +571,9 @@ function testSchemas() { }, inputs: [ {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift']}}, - {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift', 'Ctrl']}} + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} ] }, { @@ -573,7 +583,7 @@ function testSchemas() { { type: 'modifierKeys', operator: 'notInclude', - value: 'Alt, Shift' + value: 'alt, shift' } ] } @@ -582,19 +592,21 @@ function testSchemas() { properties: { modifierKeys: { type: 'array', - not: [ - {contains: {const: 'Alt'}}, - {contains: {const: 'Shift'}} - ] + not: { + anyOf: [ + {contains: {const: 'alt'}}, + {contains: {const: 'shift'}} + ] + } } }, required: ['modifierKeys'] }, inputs: [ {expected: true, context: {depth: 0, url: 'http://example.com/', modifierKeys: []}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift']}}, - {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['Alt', 'Shift', 'Ctrl']}} + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift']}}, + {expected: false, context: {depth: 0, url: 'http://example.com/', modifierKeys: ['alt', 'shift', 'ctrl']}} ] }, @@ -622,11 +634,13 @@ function testSchemas() { } }, inputs: [ - {expected: true, context: {}}, - {expected: true, context: {flags: []}}, - {expected: false, context: {flags: ['test1']}}, - {expected: false, context: {flags: ['test1', 'test2']}}, - {expected: false, context: {flags: ['test1', 'test2', 'test3']}} + {expected: true, context: {depth: 0, url: ''}}, + {expected: true, context: {depth: 0, url: '', flags: []}}, + {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, { @@ -636,7 +650,7 @@ function testSchemas() { { type: 'flags', operator: 'are', - value: 'test1, test2' + value: 'clipboard, test2' } ] } @@ -649,18 +663,20 @@ function testSchemas() { maxItems: 2, minItems: 2, allOf: [ - {contains: {const: 'test1'}}, + {contains: {const: 'clipboard'}}, {contains: {const: 'test2'}} ] } } }, inputs: [ - {expected: false, context: {}}, - {expected: false, context: {flags: []}}, - {expected: false, context: {flags: ['test1']}}, - {expected: true, context: {flags: ['test1', 'test2']}}, - {expected: false, context: {flags: ['test1', 'test2', 'test3']}} + {expected: false, context: {depth: 0, url: ''}}, + {expected: false, context: {depth: 0, url: '', flags: []}}, + {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, { @@ -676,25 +692,29 @@ function testSchemas() { } ], expectedSchema: { - not: [ - { - required: ['flags'], - properties: { - flags: { - type: 'array', - maxItems: 0, - minItems: 0 + not: { + anyOf: [ + { + required: ['flags'], + properties: { + flags: { + type: 'array', + maxItems: 0, + minItems: 0 + } } } - } - ] + ] + } }, inputs: [ - {expected: false, context: {}}, - {expected: false, context: {flags: []}}, - {expected: true, context: {flags: ['test1']}}, - {expected: true, context: {flags: ['test1', 'test2']}}, - {expected: true, context: {flags: ['test1', 'test2', 'test3']}} + {expected: false, context: {depth: 0, url: ''}}, + {expected: false, context: {depth: 0, url: '', flags: []}}, + {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, { @@ -704,35 +724,39 @@ function testSchemas() { { type: 'flags', operator: 'areNot', - value: 'test1, test2' + value: 'clipboard, test2' } ] } ], expectedSchema: { - not: [ - { - required: ['flags'], - properties: { - flags: { - type: 'array', - maxItems: 2, - minItems: 2, - allOf: [ - {contains: {const: 'test1'}}, - {contains: {const: 'test2'}} - ] + not: { + anyOf: [ + { + required: ['flags'], + properties: { + flags: { + type: 'array', + maxItems: 2, + minItems: 2, + allOf: [ + {contains: {const: 'clipboard'}}, + {contains: {const: 'test2'}} + ] + } } } - } - ] + ] + } }, inputs: [ - {expected: true, context: {}}, - {expected: true, context: {flags: []}}, - {expected: true, context: {flags: ['test1']}}, - {expected: false, context: {flags: ['test1', 'test2']}}, - {expected: true, context: {flags: ['test1', 'test2', 'test3']}} + {expected: true, context: {depth: 0, url: ''}}, + {expected: true, context: {depth: 0, url: '', flags: []}}, + {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, { @@ -757,11 +781,13 @@ function testSchemas() { } }, inputs: [ - {expected: true, context: {}}, - {expected: true, context: {flags: []}}, - {expected: true, context: {flags: ['test1']}}, - {expected: true, context: {flags: ['test1', 'test2']}}, - {expected: true, context: {flags: ['test1', 'test2', 'test3']}} + {expected: true, context: {depth: 0, url: ''}}, + {expected: true, context: {depth: 0, url: '', flags: []}}, + {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, { @@ -771,7 +797,7 @@ function testSchemas() { { type: 'flags', operator: 'include', - value: 'test1, test2' + value: 'clipboard, test2' } ] } @@ -783,18 +809,20 @@ function testSchemas() { type: 'array', minItems: 2, allOf: [ - {contains: {const: 'test1'}}, + {contains: {const: 'clipboard'}}, {contains: {const: 'test2'}} ] } } }, inputs: [ - {expected: false, context: {}}, - {expected: false, context: {flags: []}}, - {expected: false, context: {flags: ['test1']}}, - {expected: true, context: {flags: ['test1', 'test2']}}, - {expected: true, context: {flags: ['test1', 'test2', 'test3']}} + {expected: false, context: {depth: 0, url: ''}}, + {expected: false, context: {depth: 0, url: '', flags: []}}, + {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, { @@ -818,11 +846,13 @@ function testSchemas() { } }, inputs: [ - {expected: true, context: {}}, - {expected: true, context: {flags: []}}, - {expected: true, context: {flags: ['test1']}}, - {expected: true, context: {flags: ['test1', 'test2']}}, - {expected: true, context: {flags: ['test1', 'test2', 'test3']}} + {expected: true, context: {depth: 0, url: ''}}, + {expected: true, context: {depth: 0, url: '', flags: []}}, + {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, { @@ -832,7 +862,7 @@ function testSchemas() { { type: 'flags', operator: 'notInclude', - value: 'test1, test2' + value: 'clipboard, test2' } ] } @@ -842,19 +872,23 @@ function testSchemas() { properties: { flags: { type: 'array', - not: [ - {contains: {const: 'test1'}}, - {contains: {const: 'test2'}} - ] + not: { + anyOf: [ + {contains: {const: 'clipboard'}}, + {contains: {const: 'test2'}} + ] + } } } }, inputs: [ - {expected: true, context: {}}, - {expected: true, context: {flags: []}}, - {expected: false, context: {flags: ['test1']}}, - {expected: false, context: {flags: ['test1', 'test2']}}, - {expected: false, context: {flags: ['test1', 'test2', 'test3']}} + {expected: true, context: {depth: 0, url: ''}}, + {expected: true, context: {depth: 0, url: '', flags: []}}, + {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, + // @ts-ignore - Ignore type for string flag for testing purposes + {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -1082,6 +1116,7 @@ function testSchemas() { } +/** */ function main() { testNormalizeContext(); testSchemas(); diff --git a/test/text-source-map.test.js b/test/text-source-map.test.js index aeaba000..54b39319 100644 --- a/test/text-source-map.test.js +++ b/test/text-source-map.test.js @@ -19,6 +19,7 @@ import {expect, test} from 'vitest'; import {TextSourceMap} from '../ext/js/general/text-source-map.js'; +/** */ function testSource() { test('Source', () => { const data = [ @@ -34,8 +35,10 @@ function testSource() { }); } +/** */ function testEquals() { test('Equals', () => { + /** @type {[args1: [source1: string, mapping1: ?(number[])], args2: [source2: string, mapping2: ?(number[])], expectedEquals: boolean][]} */ const data = [ [['source1', null], ['source1', null], true], [['source2', null], ['source2', null], true], @@ -76,8 +79,10 @@ function testEquals() { }); } +/** */ function testGetSourceLength() { test('GetSourceLength', () => { + /** @type {[args: [source: string, mapping: number[]], finalLength: number, expectedValue: number][]} */ const data = [ [['source', [1, 1, 1, 1, 1, 1]], 1, 1], [['source', [1, 1, 1, 1, 1, 1]], 2, 2], @@ -103,10 +108,12 @@ function testGetSourceLength() { }); } +/** */ function testCombineInsert() { test('CombineInsert', () => { + /** @type {[args: [source: string, mapping: ?(number[])], expectedArgs: [expectedSource: string, expectedMapping: ?(number[])], operations: [operation: string, arg1: number, arg2: number][]][]} */ const data = [ - // No operations + // No operations [ ['source', null], ['source', [1, 1, 1, 1, 1, 1]], @@ -226,6 +233,7 @@ function testCombineInsert() { } +/** */ function main() { testSource(); testEquals(); diff --git a/test/translator.test.js b/test/translator.test.js index 7a827d39..3db560a7 100644 --- a/test/translator.test.js +++ b/test/translator.test.js @@ -28,6 +28,7 @@ vi.stubGlobal('IDBKeyRange', IDBKeyRange); const dirname = path.dirname(fileURLToPath(import.meta.url)); +/** */ async function main() { const translatorVM = new TranslatorVM(); const dictionaryDirectory = path.join(dirname, 'data', 'dictionaries', 'valid-dictionary1'); @@ -53,6 +54,7 @@ async function main() { case 'findTerms': { const {name, mode, text} = t; + /** @type {import('translation').FindTermsOptions} */ const options = translatorVM.buildOptions(optionsPresets, t.options); const {dictionaryEntries, originalTextLength} = structuredClone(await translatorVM.translator.findTerms(mode, text, options)); const noteDataList = mode !== 'simple' ? structuredClone(dictionaryEntries.map((dictionaryEntry) => translatorVM.createTestAnkiNoteData(structuredClone(dictionaryEntry), mode))) : null; @@ -66,9 +68,10 @@ async function main() { case 'findKanji': { const {name, text} = t; + /** @type {import('translation').FindKanjiOptions} */ const options = translatorVM.buildOptions(optionsPresets, t.options); const dictionaryEntries = structuredClone(await translatorVM.translator.findKanji(text, options)); - const noteDataList = structuredClone(dictionaryEntries.map((dictionaryEntry) => translatorVM.createTestAnkiNoteData(structuredClone(dictionaryEntry), null))); + const noteDataList = structuredClone(dictionaryEntries.map((dictionaryEntry) => translatorVM.createTestAnkiNoteData(structuredClone(dictionaryEntry), 'split'))); actualResults1.push({name, dictionaryEntries}); actualResults2.push({name, noteDataList}); expect(dictionaryEntries).toStrictEqual(expected1.dictionaryEntries); -- cgit v1.2.3 From 1c1c18f41296da657ac58a0583596a2cb1b4054e Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Wed, 29 Nov 2023 22:10:17 -0500 Subject: Update types --- test/object-property-accessor.test.js | 40 +++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) (limited to 'test') diff --git a/test/object-property-accessor.test.js b/test/object-property-accessor.test.js index a8730093..4f1fa87b 100644 --- a/test/object-property-accessor.test.js +++ b/test/object-property-accessor.test.js @@ -19,6 +19,9 @@ import {expect, test} from 'vitest'; import {ObjectPropertyAccessor} from '../ext/js/general/object-property-accessor.js'; +/** + * @returns {import('core').UnknownObject} + */ function createTestObject() { return { 0: null, @@ -36,8 +39,10 @@ function createTestObject() { } +/** */ function testGet1() { test('Get1', () => { + /** @type {[pathArray: (string|number)[], getExpected: (object: import('core').SafeAny) => unknown][]} */ const data = [ [[], (object) => object], [['0'], (object) => object['0']], @@ -61,11 +66,13 @@ function testGet1() { }); } +/** */ function testGet2() { test('Get2', () => { const object = createTestObject(); const accessor = new ObjectPropertyAccessor(object); + /** @type {[pathArray: (string|number)[], message: string][]} */ const data = [ [[0], 'Invalid path: [0]'], [['0', 'invalid'], 'Invalid path: ["0"].invalid'], @@ -95,9 +102,11 @@ function testGet2() { } +/** */ function testSet1() { test('Set1', () => { const testValue = {}; + /** @type {(string|number)[][]} */ const data = [ ['0'], ['value1', 'value2'], @@ -120,12 +129,14 @@ function testSet1() { }); } +/** */ function testSet2() { test('Set2', () => { const object = createTestObject(); const accessor = new ObjectPropertyAccessor(object); const testValue = {}; + /** @type {[pathArray: (string|number)[], message: string][]} */ const data = [ [[], 'Invalid path'], [[0], 'Invalid path: [0]'], @@ -148,10 +159,17 @@ function testSet2() { } +/** */ function testDelete1() { test('Delete1', () => { + /** + * @param {unknown} object + * @param {string} property + * @returns {boolean} + */ const hasOwn = (object, property) => Object.prototype.hasOwnProperty.call(object, property); + /** @type {[pathArray: (string|number)[], validate: (object: import('core').SafeAny) => boolean][]} */ const data = [ [['0'], (object) => !hasOwn(object, '0')], [['value1', 'value2'], (object) => !hasOwn(object.value1, 'value2')], @@ -171,8 +189,10 @@ function testDelete1() { }); } +/** */ function testDelete2() { test('Delete2', () => { + /** @type {[pathArray: (string|number)[], message: string][]} */ const data = [ [[], 'Invalid path'], [[0], 'Invalid path: [0]'], @@ -201,8 +221,10 @@ function testDelete2() { } +/** */ function testSwap1() { test('Swap1', () => { + /** @type {[pathArray: (string|number)[], compareValues: boolean][]} */ const data = [ [['0'], true], [['value1', 'value2'], true], @@ -237,8 +259,10 @@ function testSwap1() { }); } +/** */ function testSwap2() { test('Swap2', () => { + /** @type {[pathArray1: (string|number)[], pathArray2: (string|number)[], checkRevert: boolean, message: string][]} */ const data = [ [[], [], false, 'Invalid path 1'], [['0'], [], false, 'Invalid path 2'], @@ -276,8 +300,10 @@ function testSwap2() { } +/** */ function testGetPathString1() { test('GetPathString1', () => { + /** @type {[pathArray: (string|number)[], expected: string][]} */ const data = [ [[], ''], [[0], '[0]'], @@ -298,22 +324,27 @@ function testGetPathString1() { }); } +/** */ function testGetPathString2() { test('GetPathString2', () => { + /** @type {[pathArray: unknown[], message: string][]} */ const data = [ [[1.5], 'Invalid index'], [[null], 'Invalid type: object'] ]; for (const [pathArray, message] of data) { + // @ts-ignore - Throwing is expected expect(() => ObjectPropertyAccessor.getPathString(pathArray)).toThrow(message); } }); } +/** */ function testGetPathArray1() { test('GetPathArray1', () => { + /** @type {[pathString: string, pathArray: (string|number)[]][]} */ const data = [ ['', []], ['[0]', [0]], @@ -338,8 +369,10 @@ function testGetPathArray1() { }); } +/** */ function testGetPathArray2() { test('GetPathArray2', () => { + /** @type {[pathString: string, message: string][]} */ const data = [ ['?', 'Unexpected character: ?'], ['.', 'Unexpected character: .'], @@ -372,8 +405,10 @@ function testGetPathArray2() { } +/** */ function testHasProperty() { test('HasProperty', () => { + /** @type {[object: unknown, property: unknown, expected: boolean][]} */ const data = [ [{}, 'invalid', false], [{}, 0, false], @@ -389,13 +424,16 @@ function testHasProperty() { ]; for (const [object, property, expected] of data) { + // @ts-ignore - Ignore potentially property types expect(ObjectPropertyAccessor.hasProperty(object, property)).toStrictEqual(expected); } }); } +/** */ function testIsValidPropertyType() { test('IsValidPropertyType', () => { + /** @type {[object: unknown, property: unknown, expected: boolean][]} */ const data = [ [{}, 'invalid', true], [{}, 0, false], @@ -411,12 +449,14 @@ function testIsValidPropertyType() { ]; for (const [object, property, expected] of data) { + // @ts-ignore - Ignore potentially property types expect(ObjectPropertyAccessor.isValidPropertyType(object, property)).toStrictEqual(expected); } }); } +/** */ function main() { testGet1(); testGet2(); -- cgit v1.2.3 From 338d210b66006189a0b775e8bd524c1cc11c2d9a Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Thu, 30 Nov 2023 20:00:46 -0500 Subject: Update paths --- dev/dictionary-validate.js | 5 ++++- dev/generate-css-json.js | 15 +++++++++------ test/anki-note-builder.test.js | 6 +++--- test/database.test.js | 5 ++++- test/deinflector.test.js | 5 ++++- test/dom-text-scanner.test.js | 4 +++- 6 files changed, 27 insertions(+), 13 deletions(-) (limited to 'test') diff --git a/dev/dictionary-validate.js b/dev/dictionary-validate.js index b3654e75..a6948bfe 100644 --- a/dev/dictionary-validate.js +++ b/dev/dictionary-validate.js @@ -20,14 +20,17 @@ import fs from 'fs'; import JSZip from 'jszip'; import path from 'path'; import {performance} from 'perf_hooks'; +import {fileURLToPath} from 'url'; import {createJsonSchema} from './schema-validate.js'; +const dirname = path.dirname(fileURLToPath(import.meta.url)); + /** * @param {string} relativeFileName * @returns {import('dev/dictionary-validate').Schema} */ function readSchema(relativeFileName) { - const fileName = path.join(__dirname, relativeFileName); + const fileName = path.join(dirname, relativeFileName); const source = fs.readFileSync(fileName, {encoding: 'utf8'}); return JSON.parse(source); } diff --git a/dev/generate-css-json.js b/dev/generate-css-json.js index 02e54530..e5d4d7f0 100644 --- a/dev/generate-css-json.js +++ b/dev/generate-css-json.js @@ -19,6 +19,9 @@ import css from 'css'; import fs from 'fs'; import path from 'path'; +import {fileURLToPath} from 'url'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); /** * @returns {{cssFile: string, overridesCssFile: string, outputPath: string}[]} @@ -26,14 +29,14 @@ import path from 'path'; export function getTargets() { return [ { - cssFile: path.join(__dirname, '..', 'ext/css/structured-content.css'), - overridesCssFile: path.join(__dirname, 'data/structured-content-overrides.css'), - outputPath: path.join(__dirname, '..', 'ext/data/structured-content-style.json') + cssFile: path.join(dirname, '..', 'ext/css/structured-content.css'), + overridesCssFile: path.join(dirname, 'data/structured-content-overrides.css'), + outputPath: path.join(dirname, '..', 'ext/data/structured-content-style.json') }, { - cssFile: path.join(__dirname, '..', 'ext/css/display-pronunciation.css'), - overridesCssFile: path.join(__dirname, 'data/display-pronunciation-overrides.css'), - outputPath: path.join(__dirname, '..', 'ext/data/pronunciation-style.json') + cssFile: path.join(dirname, '..', 'ext/css/display-pronunciation.css'), + overridesCssFile: path.join(dirname, 'data/display-pronunciation-overrides.css'), + outputPath: path.join(dirname, '..', 'ext/data/pronunciation-style.json') } ]; } diff --git a/test/anki-note-builder.test.js b/test/anki-note-builder.test.js index e4af7943..96dacab6 100644 --- a/test/anki-note-builder.test.js +++ b/test/anki-note-builder.test.js @@ -26,12 +26,14 @@ import {TranslatorVM} from '../dev/translator-vm.js'; import {AnkiNoteBuilder} from '../ext/js/data/anki-note-builder.js'; import {JapaneseUtil} from '../ext/js/language/sandbox/japanese-util.js'; +const dirname = path.dirname(fileURLToPath(import.meta.url)); + /** * @param {string} url2 * @returns {Promise} */ async function fetch(url2) { - const extDir = path.join(__dirname, '..', 'ext'); + const extDir = path.join(dirname, '..', 'ext'); let filePath; try { filePath = url.fileURLToPath(url2); @@ -51,8 +53,6 @@ async function fetch(url2) { vi.stubGlobal('fetch', fetch); vi.mock('../ext/js/templates/template-renderer-proxy.js'); -const dirname = path.dirname(fileURLToPath(import.meta.url)); - /** * @returns {Promise} */ diff --git a/test/database.test.js b/test/database.test.js index ee818467..30854d55 100644 --- a/test/database.test.js +++ b/test/database.test.js @@ -18,12 +18,15 @@ import {IDBFactory, IDBKeyRange} from 'fake-indexeddb'; import path from 'path'; +import {fileURLToPath} from 'node:url'; import {beforeEach, describe, expect, test, vi} from 'vitest'; import {createDictionaryArchive} from '../dev/util.js'; import {DictionaryDatabase} from '../ext/js/language/dictionary-database.js'; import {DictionaryImporterMediaLoader} from '../ext/js/language/dictionary-importer-media-loader.js'; import {DictionaryImporter} from '../ext/js/language/dictionary-importer.js'; +const dirname = path.dirname(fileURLToPath(import.meta.url)); + vi.stubGlobal('IDBKeyRange', IDBKeyRange); vi.mock('../ext/js/language/dictionary-importer-media-loader.js'); @@ -34,7 +37,7 @@ vi.mock('../ext/js/language/dictionary-importer-media-loader.js'); * @returns {import('jszip')} */ function createTestDictionaryArchive(dictionary, dictionaryName) { - const dictionaryDirectory = path.join(__dirname, 'data', 'dictionaries', dictionary); + const dictionaryDirectory = path.join(dirname, 'data', 'dictionaries', dictionary); return createDictionaryArchive(dictionaryDirectory, dictionaryName); } diff --git a/test/deinflector.test.js b/test/deinflector.test.js index 77184799..a69f8e56 100644 --- a/test/deinflector.test.js +++ b/test/deinflector.test.js @@ -20,6 +20,9 @@ import fs from 'fs'; import path from 'path'; import {describe, expect, test} from 'vitest'; import {Deinflector} from '../ext/js/language/deinflector.js'; +import {fileURLToPath} from 'node:url'; + +const dirname = path.dirname(fileURLToPath(import.meta.url)); /** * @param {Deinflector} deinflector @@ -924,7 +927,7 @@ function testDeinflections() { } ]; - const deinflectionReasons = JSON.parse(fs.readFileSync(path.join(__dirname, '..', 'ext', 'data/deinflect.json'), {encoding: 'utf8'})); + const deinflectionReasons = JSON.parse(fs.readFileSync(path.join(dirname, '..', 'ext', 'data/deinflect.json'), {encoding: 'utf8'})); const deinflector = new Deinflector(deinflectionReasons); describe('deinflections', () => { diff --git a/test/dom-text-scanner.test.js b/test/dom-text-scanner.test.js index 6d0c047b..30aec33e 100644 --- a/test/dom-text-scanner.test.js +++ b/test/dom-text-scanner.test.js @@ -19,9 +19,11 @@ import fs from 'fs'; import {JSDOM} from 'jsdom'; import path from 'path'; +import {fileURLToPath} from 'node:url'; import {expect, test} from 'vitest'; import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js'; +const dirname = path.dirname(fileURLToPath(import.meta.url)); /** * @param {string} fileName @@ -188,7 +190,7 @@ async function testDomTextScanner(dom) { /** */ async function testDocument1() { - const dom = createJSDOM(path.join(__dirname, 'data', 'html', 'test-dom-text-scanner.html')); + const dom = createJSDOM(path.join(dirname, 'data', 'html', 'test-dom-text-scanner.html')); const window = dom.window; try { window.getComputedStyle = createAbsoluteGetComputedStyle(window); -- cgit v1.2.3 From 083b4749139213c6fefe80166d73f54604a85267 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Sun, 3 Dec 2023 10:45:08 -0500 Subject: Fix some import orderings --- dev/bin/build.js | 4 ++-- ext/js/background/offscreen-proxy.js | 2 +- ext/js/language/dictionary-importer.js | 2 +- test/database.test.js | 2 +- test/deinflector.test.js | 2 +- test/dom-text-scanner.test.js | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) (limited to 'test') diff --git a/dev/bin/build.js b/dev/bin/build.js index 47c08f3c..deb82618 100644 --- a/dev/bin/build.js +++ b/dev/bin/build.js @@ -19,10 +19,10 @@ import assert from 'assert'; import childProcess from 'child_process'; import fs from 'fs'; -import path from 'path'; -import readline from 'readline'; import JSZip from 'jszip'; import {fileURLToPath} from 'node:url'; +import path from 'path'; +import readline from 'readline'; import {buildLibs} from '../build-libs.js'; import {ManifestUtil} from '../manifest-util.js'; import {getAllFiles, getArgs, testMain} from '../util.js'; diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js index 757d78d5..7b504855 100644 --- a/ext/js/background/offscreen-proxy.js +++ b/ext/js/background/offscreen-proxy.js @@ -17,8 +17,8 @@ */ import {isObject} from '../core.js'; -import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; import {ExtensionError} from '../core/extension-error.js'; +import {ArrayBufferUtil} from '../data/sandbox/array-buffer-util.js'; export class OffscreenProxy { constructor() { diff --git a/ext/js/language/dictionary-importer.js b/ext/js/language/dictionary-importer.js index 115e0726..08fcf86b 100644 --- a/ext/js/language/dictionary-importer.js +++ b/ext/js/language/dictionary-importer.js @@ -25,8 +25,8 @@ import { configure } from '../../lib/zip.js'; import {stringReverse} from '../core.js'; -import {MediaUtil} from '../media/media-util.js'; import {ExtensionError} from '../core/extension-error.js'; +import {MediaUtil} from '../media/media-util.js'; const ajvSchemas = /** @type {import('dictionary-importer').CompiledSchemaValidators} */ (/** @type {unknown} */ (ajvSchemas0)); const BlobWriter = /** @type {typeof import('@zip.js/zip.js').BlobWriter} */ (/** @type {unknown} */ (BlobWriter0)); diff --git a/test/database.test.js b/test/database.test.js index 30854d55..80871f95 100644 --- a/test/database.test.js +++ b/test/database.test.js @@ -17,8 +17,8 @@ */ import {IDBFactory, IDBKeyRange} from 'fake-indexeddb'; -import path from 'path'; import {fileURLToPath} from 'node:url'; +import path from 'path'; import {beforeEach, describe, expect, test, vi} from 'vitest'; import {createDictionaryArchive} from '../dev/util.js'; import {DictionaryDatabase} from '../ext/js/language/dictionary-database.js'; diff --git a/test/deinflector.test.js b/test/deinflector.test.js index a69f8e56..bd538428 100644 --- a/test/deinflector.test.js +++ b/test/deinflector.test.js @@ -17,10 +17,10 @@ */ import fs from 'fs'; +import {fileURLToPath} from 'node:url'; import path from 'path'; import {describe, expect, test} from 'vitest'; import {Deinflector} from '../ext/js/language/deinflector.js'; -import {fileURLToPath} from 'node:url'; const dirname = path.dirname(fileURLToPath(import.meta.url)); diff --git a/test/dom-text-scanner.test.js b/test/dom-text-scanner.test.js index 30aec33e..f6a7410a 100644 --- a/test/dom-text-scanner.test.js +++ b/test/dom-text-scanner.test.js @@ -18,8 +18,8 @@ import fs from 'fs'; import {JSDOM} from 'jsdom'; -import path from 'path'; import {fileURLToPath} from 'node:url'; +import path from 'path'; import {expect, test} from 'vitest'; import {DOMTextScanner} from '../ext/js/dom/dom-text-scanner.js'; -- cgit v1.2.3 From dcddbee07e20163ae167dd67fe58f0776f9acb64 Mon Sep 17 00:00:00 2001 From: toasted-nutbread Date: Mon, 4 Dec 2023 18:20:05 -0500 Subject: Update how ts comments are handled --- .eslintrc.json | 9 ++++++++- dev/translator-vm.js | 2 +- ext/js/accessibility/google-docs.js | 6 +++--- ext/js/app/popup.js | 2 +- ext/js/background/offscreen-proxy.js | 8 ++++---- ext/js/comm/frame-ancestry-handler.js | 4 ++-- ext/js/dom/document-util.js | 14 ++++++------- ext/js/dom/dom-text-scanner.js | 5 ++--- ext/js/dom/simple-dom-parser.js | 3 +-- ext/js/pages/settings/backup-controller.js | 4 ++-- ext/js/yomitan.js | 4 ++-- test/cache-map.test.js | 2 +- test/data/html/test-document2-script.js | 30 ++++++++++++++-------------- test/object-property-accessor.test.js | 6 +++--- test/profile-conditions-util.test.js | 32 +++++++++++++++--------------- vitest.config.js | 2 +- 16 files changed, 69 insertions(+), 64 deletions(-) (limited to 'test') diff --git a/.eslintrc.json b/.eslintrc.json index a46aff4b..83e7ffe6 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -350,7 +350,14 @@ "allowAllPropertiesOnSameLine": true } ], - "@typescript-eslint/ban-ts-comment": "off", + "@typescript-eslint/ban-ts-comment": [ + "error", + { + "ts-expect-error": { + "descriptionFormat": "^ - .+$" + } + } + ], "@typescript-eslint/ban-types": [ "error", { diff --git a/dev/translator-vm.js b/dev/translator-vm.js index 4022d465..7fdda879 100644 --- a/dev/translator-vm.js +++ b/dev/translator-vm.js @@ -42,7 +42,7 @@ export class TranslatorVM { } } }; - // @ts-ignore - Overwriting a global + // @ts-expect-error - Overwriting a global global.chrome = chrome; /** @type {?JapaneseUtil} */ diff --git a/ext/js/accessibility/google-docs.js b/ext/js/accessibility/google-docs.js index da6ab994..27841b6d 100644 --- a/ext/js/accessibility/google-docs.js +++ b/ext/js/accessibility/google-docs.js @@ -18,9 +18,9 @@ (async () => { // Reentrant check - // @ts-ignore : Checking a property to the global object + // @ts-expect-error - Checking a property to the global object if (self.googleDocsAccessibilitySetup) { return; } - // @ts-ignore : Adding a property to the global object + // @ts-expect-error - Adding a property to the global object self.googleDocsAccessibilitySetup = true; /** @@ -57,7 +57,7 @@ // The extension ID below is on an allow-list that is used on the Google Docs webpage. /* eslint-disable */ - // @ts-ignore : Adding a property to the global object + // @ts-expect-error : Adding a property to the global object const inject = () => { window._docs_annotate_canvas_by_ext = 'ogmnaimimemjmbakcfefmnahgdfhfami'; }; /* eslint-enable */ diff --git a/ext/js/app/popup.js b/ext/js/app/popup.js index 4f201fc3..7419785b 100644 --- a/ext/js/app/popup.js +++ b/ext/js/app/popup.js @@ -745,7 +745,7 @@ export class Popup extends EventDispatcher { if ( fullscreenElement === null || fullscreenElement.shadowRoot || - // @ts-ignore - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions + // @ts-expect-error - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions fullscreenElement.openOrClosedShadowRoot ) { return defaultParent; diff --git a/ext/js/background/offscreen-proxy.js b/ext/js/background/offscreen-proxy.js index 7b504855..63f619fa 100644 --- a/ext/js/background/offscreen-proxy.js +++ b/ext/js/background/offscreen-proxy.js @@ -54,16 +54,16 @@ export class OffscreenProxy { */ async _hasOffscreenDocument() { const offscreenUrl = chrome.runtime.getURL('offscreen.html'); - // @ts-ignore - API not defined yet + // @ts-expect-error - API not defined yet if (!chrome.runtime.getContexts) { // chrome version below 116 // Clients: https://developer.mozilla.org/en-US/docs/Web/API/ServiceWorkerGlobalScope/clients - // @ts-ignore - Types not set up for service workers yet + // @ts-expect-error - Types not set up for service workers yet const matchedClients = await clients.matchAll(); - // @ts-ignore - Types not set up for service workers yet + // @ts-expect-error - Types not set up for service workers yet return await matchedClients.some((client) => client.url === offscreenUrl); } - // @ts-ignore - API not defined yet + // @ts-expect-error - API not defined yet const contexts = await chrome.runtime.getContexts({ contextTypes: ['OFFSCREEN_DOCUMENT'], documentUrls: [offscreenUrl] diff --git a/ext/js/comm/frame-ancestry-handler.js b/ext/js/comm/frame-ancestry-handler.js index 687ec368..e4d08f28 100644 --- a/ext/js/comm/frame-ancestry-handler.js +++ b/ext/js/comm/frame-ancestry-handler.js @@ -295,7 +295,7 @@ export class FrameAncestryHandler { while (walker.nextNode()) { const element = /** @type {Element} */ (walker.currentNode); - // @ts-ignore - this is more simple to elide any type checks or casting + // @ts-expect-error - this is more simple to elide any type checks or casting if (element.contentWindow === contentWindow) { return element; } @@ -303,7 +303,7 @@ export class FrameAncestryHandler { /** @type {?ShadowRoot|undefined} */ const shadowRoot = ( element.shadowRoot || - // @ts-ignore - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions + // @ts-expect-error - openOrClosedShadowRoot is available to Firefox 63+ for WebExtensions element.openOrClosedShadowRoot ); if (shadowRoot) { diff --git a/ext/js/dom/document-util.js b/ext/js/dom/document-util.js index 549a8195..b2aa8f81 100644 --- a/ext/js/dom/document-util.js +++ b/ext/js/dom/document-util.js @@ -210,7 +210,7 @@ export class DocumentUtil { */ static computeZoomScale(node) { if (this._cssZoomSupported === null) { - // @ts-ignore - zoom is a non-standard property that exists in Chromium-based browsers + // @ts-expect-error - zoom is a non-standard property that exists in Chromium-based browsers this._cssZoomSupported = (typeof document.createElement('div').style.zoom === 'string'); } if (!this._cssZoomSupported) { return 1; } @@ -387,11 +387,11 @@ export class DocumentUtil { static getFullscreenElement() { return ( document.fullscreenElement || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix document.msFullscreenElement || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix document.mozFullScreenElement || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix document.webkitFullscreenElement || null ); @@ -808,7 +808,7 @@ export class DocumentUtil { return document.caretRangeFromPoint(x, y); } - // @ts-ignore - caretPositionFromPoint is non-standard + // @ts-expect-error - caretPositionFromPoint is non-standard if (typeof document.caretPositionFromPoint === 'function') { // Firefox return this._caretPositionFromPoint(x, y); @@ -824,7 +824,7 @@ export class DocumentUtil { * @returns {?Range} */ static _caretPositionFromPoint(x, y) { - // @ts-ignore - caretPositionFromPoint is non-standard + // @ts-expect-error - caretPositionFromPoint is non-standard const position = /** @type {(x: number, y: number) => ?{offsetNode: Node, offset: number}} */ (document.caretPositionFromPoint)(x, y); if (position === null) { return null; @@ -876,7 +876,7 @@ export class DocumentUtil { nextElement.style.setProperty('user-select', 'text', 'important'); } - // @ts-ignore - caretPositionFromPoint is non-standard + // @ts-expect-error - caretPositionFromPoint is non-standard const position = /** @type {(x: number, y: number) => ?{offsetNode: Node, offset: number}} */ (document.caretPositionFromPoint)(x, y); if (position === null) { return null; diff --git a/ext/js/dom/dom-text-scanner.js b/ext/js/dom/dom-text-scanner.js index 3a785680..42e0acc9 100644 --- a/ext/js/dom/dom-text-scanner.js +++ b/ext/js/dom/dom-text-scanner.js @@ -520,11 +520,10 @@ export class DOMTextScanner { static isStyleSelectable(style) { return !( style.userSelect === 'none' || - // @ts-ignore - vendor prefix style.webkitUserSelect === 'none' || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix style.MozUserSelect === 'none' || - // @ts-ignore - vendor prefix + // @ts-expect-error - vendor prefix style.msUserSelect === 'none' ); } diff --git a/ext/js/dom/simple-dom-parser.js b/ext/js/dom/simple-dom-parser.js index a1f63890..bca1cd88 100644 --- a/ext/js/dom/simple-dom-parser.js +++ b/ext/js/dom/simple-dom-parser.js @@ -27,7 +27,7 @@ export class SimpleDOMParser { */ constructor(content) { /** @type {import('parse5')} */ - // @ts-ignore - parse5 global is not defined in typescript declaration + // @ts-expect-error - parse5 global is not defined in typescript declaration this._parse5Lib = /** @type {import('parse5')} */ (parse5); /** @type {import('parse5').TreeAdapter} */ this._treeAdapter = this._parse5Lib.defaultTreeAdapter; @@ -131,7 +131,6 @@ export class SimpleDOMParser { * @returns {boolean} */ static isSupported() { - // @ts-ignore - parse5 global is not defined in typescript declaration return typeof parse5 !== 'undefined'; } diff --git a/ext/js/pages/settings/backup-controller.js b/ext/js/pages/settings/backup-controller.js index 52c5f418..bf44bb90 100644 --- a/ext/js/pages/settings/backup-controller.js +++ b/ext/js/pages/settings/backup-controller.js @@ -165,9 +165,9 @@ export class BackupController { _saveBlob(blob, fileName) { if ( typeof navigator === 'object' && navigator !== null && - // @ts-ignore - call for legacy Edge + // @ts-expect-error - call for legacy Edge typeof navigator.msSaveBlob === 'function' && - // @ts-ignore - call for legacy Edge + // @ts-expect-error - call for legacy Edge navigator.msSaveBlob(blob) ) { return; diff --git a/ext/js/yomitan.js b/ext/js/yomitan.js index 3c0f7cb9..7cf67aec 100644 --- a/ext/js/yomitan.js +++ b/ext/js/yomitan.js @@ -37,7 +37,7 @@ if ((() => { } return (hasBrowser && !hasChrome); })()) { - // @ts-ignore - objects should have roughly the same interface + // @ts-expect-error - objects should have roughly the same interface chrome = browser; } @@ -182,7 +182,7 @@ export class Yomitan extends EventDispatcher { */ sendMessage(...args) { try { - // @ts-ignore - issue with type conversion, somewhat difficult to resolve in pure JS + // @ts-expect-error - issue with type conversion, somewhat difficult to resolve in pure JS chrome.runtime.sendMessage(...args); } catch (e) { this.triggerExtensionUnloaded(); diff --git a/test/cache-map.test.js b/test/cache-map.test.js index 3e7def1f..df891188 100644 --- a/test/cache-map.test.js +++ b/test/cache-map.test.js @@ -30,7 +30,7 @@ function testConstructor() { [true, () => new CacheMap(1.5)], [true, () => new CacheMap(Number.NaN)], [true, () => new CacheMap(Number.POSITIVE_INFINITY)], - // @ts-ignore - Ignore because it should throw an error + // @ts-expect-error - Ignore because it should throw an error [true, () => new CacheMap('a')] ]; diff --git a/test/data/html/test-document2-script.js b/test/data/html/test-document2-script.js index 5a6ad4d1..f6082802 100644 --- a/test/data/html/test-document2-script.js +++ b/test/data/html/test-document2-script.js @@ -22,17 +22,17 @@ function requestFullscreen(element) { if (element.requestFullscreen) { element.requestFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (element.mozRequestFullScreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility element.mozRequestFullScreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (element.webkitRequestFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility element.webkitRequestFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (element.msRequestFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility element.msRequestFullscreen(); } } @@ -41,17 +41,17 @@ function requestFullscreen(element) { function exitFullscreen() { if (document.exitFullscreen) { document.exitFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (document.mozCancelFullScreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.mozCancelFullScreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (document.webkitExitFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.webkitExitFullscreen(); - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility } else if (document.msExitFullscreen) { - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.msExitFullscreen(); } } @@ -62,11 +62,11 @@ function exitFullscreen() { function getFullscreenElement() { return ( document.fullscreenElement || - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.msFullscreenElement || - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.mozFullScreenElement || - // @ts-ignore - Browser compatibility + // @ts-expect-error - Browser compatibility document.webkitFullscreenElement || null ); diff --git a/test/object-property-accessor.test.js b/test/object-property-accessor.test.js index 4f1fa87b..4d50b1e9 100644 --- a/test/object-property-accessor.test.js +++ b/test/object-property-accessor.test.js @@ -334,7 +334,7 @@ function testGetPathString2() { ]; for (const [pathArray, message] of data) { - // @ts-ignore - Throwing is expected + // @ts-expect-error - Throwing is expected expect(() => ObjectPropertyAccessor.getPathString(pathArray)).toThrow(message); } }); @@ -424,7 +424,7 @@ function testHasProperty() { ]; for (const [object, property, expected] of data) { - // @ts-ignore - Ignore potentially property types + // @ts-expect-error - Ignore potentially property types expect(ObjectPropertyAccessor.hasProperty(object, property)).toStrictEqual(expected); } }); @@ -449,7 +449,7 @@ function testIsValidPropertyType() { ]; for (const [object, property, expected] of data) { - // @ts-ignore - Ignore potentially property types + // @ts-expect-error - Ignore potentially property types expect(ObjectPropertyAccessor.isValidPropertyType(object, property)).toStrictEqual(expected); } }); diff --git a/test/profile-conditions-util.test.js b/test/profile-conditions-util.test.js index 30052b34..62b21555 100644 --- a/test/profile-conditions-util.test.js +++ b/test/profile-conditions-util.test.js @@ -637,9 +637,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -673,9 +673,9 @@ function testSchemas() { {expected: false, context: {depth: 0, url: ''}}, {expected: false, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -711,9 +711,9 @@ function testSchemas() { {expected: false, context: {depth: 0, url: ''}}, {expected: false, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -753,9 +753,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -784,9 +784,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -819,9 +819,9 @@ function testSchemas() { {expected: false, context: {depth: 0, url: ''}}, {expected: false, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -849,9 +849,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: true, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: true, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, @@ -885,9 +885,9 @@ function testSchemas() { {expected: true, context: {depth: 0, url: ''}}, {expected: true, context: {depth: 0, url: '', flags: []}}, {expected: false, context: {depth: 0, url: '', flags: ['clipboard']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2']}}, - // @ts-ignore - Ignore type for string flag for testing purposes + // @ts-expect-error - Ignore type for string flag for testing purposes {expected: false, context: {depth: 0, url: '', flags: ['clipboard', 'test2', 'test3']}} ] }, diff --git a/vitest.config.js b/vitest.config.js index 3b6cdde0..025eec17 100644 --- a/vitest.config.js +++ b/vitest.config.js @@ -24,7 +24,7 @@ export default defineConfig({ 'test/playwright/**' ], environment: 'jsdom', - // @ts-ignore - Appears to not be defined in the type definitions (https://vitest.dev/advanced/pool) + // @ts-expect-error - Appears to not be defined in the type definitions (https://vitest.dev/advanced/pool) poolOptions: { threads: { useAtomics: true -- cgit v1.2.3