summaryrefslogtreecommitdiff
path: root/test
diff options
context:
space:
mode:
Diffstat (limited to 'test')
-rw-r--r--test/data/dictionaries/valid-dictionary1/image.gifbin0 -> 45 bytes
-rw-r--r--test/data/dictionaries/valid-dictionary1/term_bank_1.json3
-rw-r--r--test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json6
-rw-r--r--test/data/html/test-document1.html1
-rw-r--r--test/data/html/test-document2-frame1.html6
-rw-r--r--test/data/html/test-document2-script.js24
-rw-r--r--test/data/html/test-document2.html97
-rw-r--r--test/data/html/test-document3-frame1.html44
-rw-r--r--test/data/html/test-document3-frame2.html62
-rw-r--r--test/data/html/test-document3.html26
-rw-r--r--test/data/html/test-dom-text-scanner.html393
-rw-r--r--test/data/html/test-stylesheet.css15
-rw-r--r--test/test-database.js87
-rw-r--r--test/test-dom-text-scanner.js183
-rw-r--r--test/test-japanese.js9
-rw-r--r--test/test-object-property-accessor.js186
-rw-r--r--test/yomichan-test.js15
17 files changed, 1045 insertions, 112 deletions
diff --git a/test/data/dictionaries/valid-dictionary1/image.gif b/test/data/dictionaries/valid-dictionary1/image.gif
new file mode 100644
index 00000000..f089d07c
--- /dev/null
+++ b/test/data/dictionaries/valid-dictionary1/image.gif
Binary files differ
diff --git a/test/data/dictionaries/valid-dictionary1/term_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_bank_1.json
index 755d9f6a..a62ad117 100644
--- a/test/data/dictionaries/valid-dictionary1/term_bank_1.json
+++ b/test/data/dictionaries/valid-dictionary1/term_bank_1.json
@@ -30,5 +30,6 @@
["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 29, ["definition1a (打ち込む, ぶちこむ)", "definition1b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"],
["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 30, ["definition2a (打ち込む, ぶちこむ)", "definition2b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"],
["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 31, ["definition3a (打ち込む, ぶちこむ)", "definition3b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"],
- ["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 32, ["definition4a (打ち込む, ぶちこむ)", "definition4b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"]
+ ["打ち込む", "ぶちこむ", "tag1 tag2", "v5", 32, ["definition4a (打ち込む, ぶちこむ)", "definition4b (打ち込む, ぶちこむ)"], 6, "tag3 tag4 tag5"],
+ ["画像", "がぞう", "tag1 tag2", "", 33, ["definition1a (画像, がぞう)", {"type": "image", "path": "image.gif", "width": 350, "height": 350, "description": "An image", "pixelated": true}], 7, "tag3 tag4 tag5"]
] \ No newline at end of file
diff --git a/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json b/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json
index 26922394..73d74e68 100644
--- a/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json
+++ b/test/data/dictionaries/valid-dictionary1/term_meta_bank_1.json
@@ -2,6 +2,12 @@
["打", "freq", 1],
["打つ", "freq", 2],
["打ち込む", "freq", 3],
+ ["打", "freq", {"reading": "だ", "frequency": 4}],
+ ["打", "freq", {"reading": "ダース", "frequency": 5}],
+ ["打つ", "freq", {"reading": "うつ", "frequency": 6}],
+ ["打つ", "freq", {"reading": "ぶつ", "frequency": 7}],
+ ["打ち込む", "freq", {"reading": "うちこむ", "frequency": 8}],
+ ["打ち込む", "freq", {"reading": "ぶちこむ", "frequency": 9}],
[
"打ち込む",
"pitch",
diff --git a/test/data/html/test-document1.html b/test/data/html/test-document1.html
index 0754a314..98a6fb44 100644
--- a/test/data/html/test-document1.html
+++ b/test/data/html/test-document1.html
@@ -103,6 +103,7 @@
data-end-node-selector="img"
data-end-offset="0"
data-result-type="TextSourceElement"
+ data-sentence-extent="100"
data-sentence="よみちゃん"
>
<img src="data:image/gif;base64,R0lGODdhBwAHAIABAAAAAP///ywAAAAABwAHAAACDIRvEaC32FpCbEkKCgA7" alt="よみちゃん" title="よみちゃん" style="width: 70px; height: 70px; image-rendering: crisp-edges; image-rendering: pixelated; display: block;" />
diff --git a/test/data/html/test-document2-frame1.html b/test/data/html/test-document2-frame1.html
index 3b85cdc5..e572e3c4 100644
--- a/test/data/html/test-document2-frame1.html
+++ b/test/data/html/test-document2-frame1.html
@@ -33,10 +33,8 @@ a, a:visited {
ありがとう
</div>
<div>
- <a href="#" id="fullscreen-link">Toggle fullscreen</a>
- <script>
-document.querySelector('#fullscreen-link').addEventListener('click', () => toggleFullscreen(document.body), false);
- </script>
+ <a href="#" class="fullscreen-link">Toggle fullscreen</a>
+ <script>setup(document.body, document.body);</script>
</div>
</div></body>
</html> \ No newline at end of file
diff --git a/test/data/html/test-document2-script.js b/test/data/html/test-document2-script.js
index bd5a570d..ab516a4e 100644
--- a/test/data/html/test-document2-script.js
+++ b/test/data/html/test-document2-script.js
@@ -39,3 +39,27 @@ function toggleFullscreen(element) {
requestFullscreen(element);
}
}
+
+function setup(container, fullscreenElement=null) {
+ const fullscreenLink = container.querySelector('.fullscreen-link');
+ if (fullscreenLink !== null) {
+ if (fullscreenElement === null) {
+ fullscreenElement = container.querySelector('.fullscreen-element');
+ }
+ fullscreenLink.addEventListener('click', (e) => {
+ toggleFullscreen(fullscreenElement);
+ e.preventDefault();
+ return false;
+ }, false);
+ }
+
+ 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 content = document.importNode(template.content, true);
+ setup(content);
+ shadow.appendChild(content);
+ }
+}
diff --git a/test/data/html/test-document2.html b/test/data/html/test-document2.html
index 3a22a5bf..6d174571 100644
--- a/test/data/html/test-document2.html
+++ b/test/data/html/test-document2.html
@@ -11,71 +11,78 @@
<body>
<h1>Yomichan Manual Tests</h1>
- <p class="description">Manual tests involving fullscreen elements, &lt;iframe&gt;s, and shadow DOMs.</p>
+ <y-description>Manual tests involving fullscreen elements, &lt;iframe&gt;s, and shadow DOMs.</y-description>
- <div class="test">
- <div class="description">Standard content.</div>
- <div id="fullscreen-element1" style="width: 100%; height: 200px; border: 1px solid #d8d8d8; position: relative;"><div style="background-color: #f8f8f8; padding: 0.5em; position: absolute; left: 0; top: 0; bottom: 0; right: 0;">
+ <y-test>
+ <y-description>Standard content.</y-description>
+ <div class="fullscreen-element" style="width: 100%; height: 200px; border: 1px solid #d8d8d8; position: relative;"><div style="background-color: #f8f8f8; padding: 0.5em; position: absolute; left: 0; top: 0; bottom: 0; right: 0;">
<div>
ありがとう
</div>
<div>
- <a href="#" id="fullscreen-link1">Toggle fullscreen</a>
+ <a href="#" class="fullscreen-link">Toggle fullscreen</a>
</div>
</div></div>
- <script>
-document.querySelector('#fullscreen-link1').addEventListener('click', () => toggleFullscreen(document.querySelector('#fullscreen-element1')), false);
- </script>
- </div>
+ </y-test>
- <div class="test">
- <div class="description">Content inside of a shadow DOM.</div>
- <div id="shadow-content-container"></div>
- <template id="shadow-content-container-content-template">
+ <y-test data-shadow-mode="open">
+ <y-description>Content inside of an open shadow DOM.</y-description>
+ <div class="template-content-container"></div>
+ <template>
<link rel="stylesheet" href="test-stylesheet.css" />
- <div id="fullscreen-element2" style="width: 100%; height: 200px; border: 1px solid #d8d8d8; position: relative;"><div style="background-color: #f8f8f8; padding: 0.5em; position: absolute; left: 0; top: 0; bottom: 0; right: 0;">
+ <div class="fullscreen-element" style="width: 100%; height: 200px; border: 1px solid #d8d8d8; position: relative;"><div style="background-color: #f8f8f8; padding: 0.5em; position: absolute; left: 0; top: 0; bottom: 0; right: 0;">
<div>
ありがとう
</div>
<div>
- <a href="#" id="fullscreen-link2">Toggle fullscreen</a>
+ <a href="#" class="fullscreen-link">Toggle fullscreen</a>
</div>
</div></div>
</template>
- <script>
-(() => {
- const shadowIframeContainer = document.querySelector('#shadow-content-container');
- const shadow = shadowIframeContainer.attachShadow({mode: 'closed'});
- const template = document.querySelector('#shadow-content-container-content-template').content;
- const content = document.importNode(template, true);
- const fullscreenElement = content.querySelector('#fullscreen-element2');
- content.querySelector('#fullscreen-link2').addEventListener('click', () => toggleFullscreen(fullscreenElement), false);
- shadow.appendChild(content);
-})();
- </script>
- </div>
+ </y-test>
- <div class="test">
- <div class="description">&lt;iframe&gt; element.</div>
+ <y-test data-shadow-mode="closed">
+ <y-description>Content inside of a closed shadow DOM.</y-description>
+ <div class="template-content-container"></div>
+ <template>
+ <link rel="stylesheet" href="test-stylesheet.css" />
+ <div class="fullscreen-element" style="width: 100%; height: 200px; border: 1px solid #d8d8d8; position: relative;"><div style="background-color: #f8f8f8; padding: 0.5em; position: absolute; left: 0; top: 0; bottom: 0; right: 0;">
+ <div>
+ ありがとう
+ </div>
+ <div>
+ <a href="#" class="fullscreen-link">Toggle fullscreen</a>
+ </div>
+ </div></div>
+ </template>
+ </y-test>
+
+ <y-test>
+ <y-description>&lt;iframe&gt; element.</y-description>
<iframe src="test-document2-frame1.html" allowfullscreen="true" style="width: 100%; height: 200px; border: 1px solid #d8d8d8;"></iframe>
- </div>
+ </y-test>
+
+ <y-test data-shadow-mode="open">
+ <y-description>&lt;iframe&gt; element inside of an open shadow DOM.</y-description>
+ <div class="template-content-container"></div>
+ <template>
+ <iframe src="test-document2-frame1.html" allowfullscreen="true" style="width: 100%; height: 200px; border: 1px solid #d8d8d8;"></iframe>
+ </template>
+ </y-test>
- <div class="test">
- <div class="description">&lt;iframe&gt; element inside of a shadow DOM.</div>
- <div id="shadow-iframe-container"></div>
- <template id="shadow-iframe-container-content-template">
+ <y-test data-shadow-mode="closed">
+ <y-description>&lt;iframe&gt; element inside of a closed shadow DOM.</y-description>
+ <div class="template-content-container"></div>
+ <template>
<iframe src="test-document2-frame1.html" allowfullscreen="true" style="width: 100%; height: 200px; border: 1px solid #d8d8d8;"></iframe>
</template>
- <script>
-(() => {
- const shadowIframeContainer = document.querySelector('#shadow-iframe-container');
- const shadow = shadowIframeContainer.attachShadow({mode: 'closed'});
- const template = document.querySelector('#shadow-iframe-container-content-template').content;
- const content = document.importNode(template, true);
- shadow.appendChild(content);
-})();
- </script>
- </div>
+ </y-test>
+
+ <script>
+for (const element of document.querySelectorAll('y-test')) {
+ setup(element);
+}
+ </script>
</body>
-</html> \ No newline at end of file
+</html>
diff --git a/test/data/html/test-document3-frame1.html b/test/data/html/test-document3-frame1.html
new file mode 100644
index 00000000..2ae906d2
--- /dev/null
+++ b/test/data/html/test-document3-frame1.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <title>Yomichan Manual Performance Tests</title>
+ <link rel="stylesheet" href="test-stylesheet.css" />
+ </head>
+<body><div class="content">
+
+ <div class="description">Add elements</div>
+
+ <div>
+ <a href="#" id="add-elements-1000">1000</a>
+ <a href="#" id="add-elements-10000">10000</a>
+ <a href="#" id="add-elements-100000">100000</a>
+ <a href="#" id="add-elements-1000000">1000000</a>
+ <script>
+document.querySelector('#add-elements-1000').addEventListener('click', () => addElements(1000), false);
+document.querySelector('#add-elements-10000').addEventListener('click', () => addElements(10000), false);
+document.querySelector('#add-elements-100000').addEventListener('click', () => addElements(100000), false);
+document.querySelector('#add-elements-1000000').addEventListener('click', () => addElements(1000000), false);
+
+let counter = 0;
+
+function addElements(amount) {
+ const container = document.querySelector('#container');
+ for (let i = 0; i < amount; i++) {
+ const element = document.createElement('div');
+ element.textContent = 'ありがとう';
+ container.appendChild(element);
+ }
+
+ counter += amount;
+ document.querySelector('#counter').textContent = counter;
+}
+ </script>
+ </div>
+
+ <div id="counter"></div>
+ <div id="container"></div>
+
+</div></body>
+</html>
diff --git a/test/data/html/test-document3-frame2.html b/test/data/html/test-document3-frame2.html
new file mode 100644
index 00000000..c486e04b
--- /dev/null
+++ b/test/data/html/test-document3-frame2.html
@@ -0,0 +1,62 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <title>Yomichan Manual Performance Tests</title>
+ <link rel="stylesheet" href="test-stylesheet.css" />
+ </head>
+<body><div class="content">
+
+ <div class="description">&lt;iframe&gt; element inside of an open shadow DOM.</div>
+
+ <div id="shadow-iframe-container-open"></div>
+ <template id="shadow-iframe-container-open-content-template">
+ <iframe src="test-document2-frame1.html" allowfullscreen="true" style="width: 100%; height: 50px; border: 1px solid #d8d8d8;"></iframe>
+ </template>
+ <script>
+(() => {
+ const shadowIframeContainer = document.querySelector('#shadow-iframe-container-open');
+ const shadow = shadowIframeContainer.attachShadow({mode: 'open'});
+ const template = document.querySelector('#shadow-iframe-container-open-content-template').content;
+ const content = document.importNode(template, true);
+ shadow.appendChild(content);
+})();
+ </script>
+
+ <div class="description">Add elements</div>
+
+ <div>
+ <a href="#" id="add-elements-1000">1000</a>
+ <a href="#" id="add-elements-10000">10000</a>
+ <a href="#" id="add-elements-100000">100000</a>
+ <a href="#" id="add-elements-1000000">1000000</a>
+ </div>
+
+ <div id="counter"></div>
+ <div id="container"></div>
+ <script>
+(() => {
+ document.querySelector('#add-elements-1000').addEventListener('click', () => addElements(1000), false);
+ document.querySelector('#add-elements-10000').addEventListener('click', () => addElements(10000), false);
+ document.querySelector('#add-elements-100000').addEventListener('click', () => addElements(100000), false);
+ document.querySelector('#add-elements-1000000').addEventListener('click', () => addElements(1000000), false);
+
+ let counter = 0;
+
+ function addElements(amount) {
+ const container = document.querySelector('#container');
+ for (let i = 0; i < amount; i++) {
+ const element = document.createElement('div');
+ element.textContent = 'ありがとう';
+ container.appendChild(element);
+ }
+
+ counter += amount;
+ document.querySelector('#counter').textContent = counter;
+ }
+})();
+ </script>
+
+</div></body>
+</html>
diff --git a/test/data/html/test-document3.html b/test/data/html/test-document3.html
new file mode 100644
index 00000000..3e7d5236
--- /dev/null
+++ b/test/data/html/test-document3.html
@@ -0,0 +1,26 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <title>Yomichan Manual Performance Tests</title>
+ <link rel="icon" type="image/gif" href="data:image/gif;base64,R0lGODlhEAAQAKEBAAAAAP///////////yH5BAEKAAIALAAAAAAQABAAAAImFI6Zpt0B4YkS0TCpq07xbmEgcGVRUpLaI46ZG7ppalY0jDCwUAAAOw==" />
+ <link rel="stylesheet" href="test-stylesheet.css" />
+ </head>
+<body>
+
+ <h1>Yomichan Manual Performance Tests</h1>
+ <p class="description">Testing Yomichan performance with artificially demanding cases in a real browser</p>
+
+ <div class="test">
+ <div class="description">&lt;iframe&gt; element.</div>
+ <iframe src="test-document3-frame1.html" allowfullscreen="true" style="width: 100%; height: 200px; border: 1px solid #d8d8d8;"></iframe>
+ </div>
+
+ <div class="test">
+ <div class="description">&lt;iframe&gt; element containing an &lt;iframe&gt; element inside of an open shadow DOM.</div>
+ <iframe src="test-document3-frame2.html" allowfullscreen="true" style="width: 100%; height: 200px; border: 1px solid #d8d8d8;"></iframe>
+ </div>
+
+</body>
+</html>
diff --git a/test/data/html/test-dom-text-scanner.html b/test/data/html/test-dom-text-scanner.html
new file mode 100644
index 00000000..dc06eb64
--- /dev/null
+++ b/test/data/html/test-dom-text-scanner.html
@@ -0,0 +1,393 @@
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="UTF-8">
+ <meta name="viewport" content="width=device-width,initial-scale=1" />
+ <title>Yomichan DOMTextScanner Tests</title>
+ <link rel="icon" type="image/gif" href="data:image/gif;base64,R0lGODlhEAAQAKEBAAAAAP///////////yH5BAEKAAIALAAAAAAQABAAAAImFI6Zpt0B4YkS0TCpq07xbmEgcGVRUpLaI46ZG7ppalY0jDCwUAAAOw==" />
+ <link rel="stylesheet" href="test-stylesheet.css" />
+ </head>
+<body>
+
+ <h1>Yomichan DOMTextScanner Tests</h1>
+
+ <y-test
+ data-test-data='{
+ "node": "div:nth-of-type(1)",
+ "offset": 0,
+ "length": 15,
+ "expected": {
+ "node": "div:nth-of-type(2)>div::text",
+ "offset": 3,
+ "content": "小ぢん\nまり1\n小ぢん\nまり2"
+ }
+ }'
+ >
+ <y-description>Layout newlines expected due to entering and exiting display:block nodes.</y-description>
+<div><div>小ぢん</div>まり1</div>
+<div>小ぢん<div>まり2</div></div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div:nth-of-type(1)::text",
+ "offset": 0,
+ "length": 13,
+ "expected": {
+ "node": "div:nth-of-type(2)::text",
+ "offset": 6,
+ "content": "小ぢんまり1\n小ぢんまり2"
+ }
+ }'
+ >
+ <y-description>Layout newline expected due to sequential display:block elements.</y-description>
+<div>小ぢんまり1</div><div>小ぢんまり2</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div:nth-of-type(1)::text",
+ "offset": 0,
+ "length": 13,
+ "expected": {
+ "node": "div:nth-of-type(2)::text",
+ "offset": 6,
+ "content": "小ぢんまり1\n小ぢんまり2"
+ }
+ }'
+ >
+ <y-description>Layout newline expected due to sequential display:block elements separated by a newline.</y-description>
+<div>小ぢんまり1</div>
+<div>小ぢんまり2</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "span:nth-of-type(1)::text",
+ "offset": 0,
+ "length": 12,
+ "expected": {
+ "node": "span:nth-of-type(2)::text",
+ "offset": 6,
+ "content": "小ぢんまり1小ぢんまり2"
+ }
+ }'
+ >
+ <y-description>No newlines expected due to display:inline.</y-description>
+<span>小ぢんまり1</span><span>小ぢんまり2</span>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "span:nth-of-type(1)::text",
+ "offset": 0,
+ "length": 13,
+ "expected": {
+ "node": "span:nth-of-type(2)::text",
+ "offset": 6,
+ "content": "小ぢんまり1 小ぢんまり2"
+ }
+ }'
+ >
+ <y-description>No newlines expected due to white-space:normal.</y-description>
+<span>小ぢんまり1</span>
+<span>小ぢんまり2</span>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "span:nth-of-type(1)::text",
+ "offset": 0,
+ "length": 13,
+ "expected": {
+ "node": "span:nth-of-type(2)::text",
+ "offset": 6,
+ "content": "小ぢんまり1\n小ぢんまり2"
+ }
+ }'
+ >
+ <y-description>Newline expected due to white-space:pre.</y-description>
+<pre>
+<span>小ぢんまり1</span>
+<span>小ぢんまり2</span>
+</pre>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "span:nth-of-type(1)::text",
+ "offset": 0,
+ "length": 12,
+ "expected": {
+ "node": "span:nth-of-type(2)::text",
+ "offset": 6,
+ "content": "小ぢんまり1小ぢんまり2"
+ }
+ }'
+ >
+ <y-description>No newlines expected due to display:inline-block. Actual layout flow cannot be determined by DOM/CSS alone.</y-description>
+<span style="display: inline-block;">小ぢんまり1</span><span style="display: inline-block;">小ぢんまり2</span>
+ </y-test>
+
+ <y-test
+ style="position: relative;"
+ data-test-data='{
+ "node": "div:nth-of-type(1)::text",
+ "offset": 0,
+ "length": 13,
+ "expected": {
+ "node": "div:nth-of-type(2)::text",
+ "offset": 6,
+ "content": "小ぢんまり1\n小ぢんまり2"
+ }
+ }'
+ >
+ <y-description>Single newline expected due to display:block layout.</y-description>
+<div>小ぢんまり1</div><div style="position: relative;">小ぢんまり2</div>
+ </y-test>
+
+ <y-test
+ style="position: relative; overflow: hidden;"
+ data-test-data='{
+ "node": "div:nth-of-type(1)::text",
+ "offset": 0,
+ "length": 14,
+ "expected": {
+ "node": "div:nth-of-type(2)::text",
+ "offset": 6,
+ "content": "小ぢんまり1\n\n小ぢんまり2"
+ }
+ }'
+ >
+ <y-description>Two newlines expected due to position:absolute causing a significant layout change.</y-description>
+<div>小ぢんまり1</div><div style="position: absolute;">小ぢんまり2</div>
+ </y-test>
+
+ <y-test
+ style="position: relative; overflow: hidden;"
+ data-test-data='{
+ "node": "div:nth-of-type(1)::text",
+ "offset": 0,
+ "length": 14,
+ "expected": {
+ "node": "div:nth-of-type(2)::text",
+ "offset": 6,
+ "content": "小ぢんまり1\n\n小ぢんまり2"
+ }
+ }'
+ >
+ <y-description>Two newlines expected due to position:fixed causing a significant layout change.</y-description>
+<div>小ぢんまり1</div><div style="position: fixed;">小ぢんまり2</div>
+ </y-test>
+
+ <y-test
+ style="position: relative;"
+ data-test-data='{
+ "node": "div:nth-of-type(1)::text",
+ "offset": 0,
+ "length": 14,
+ "expected": {
+ "node": "div:nth-of-type(2)::text",
+ "offset": 6,
+ "content": "小ぢんまり1\n\n小ぢんまり2"
+ }
+ }'
+ >
+ <y-description>Two newlines expected due to position:sticky being able to cause a significant layout change.</y-description>
+<div>小ぢんまり1</div><div style="position: sticky;">小ぢんまり2</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "rt",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::text",
+ "offset": 5,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Scanning text starting in an &lt;rt&gt; element. Should start scanning at the start of the &lt;ruby&gt; tag instead.</y-description>
+<div><ruby>小<rp>(</rp><rt>こ</rt><rp>)</rp></ruby>ぢんまり1</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::nth-text(2)",
+ "offset": 3,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Skip &lt;script&gt; content.</y-description>
+<div>小ぢん<script>/*comment*/</script>まり1</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::nth-text(2)",
+ "offset": 3,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Skip &lt;style&gt; content.</y-description>
+<div>小ぢん<style>/*comment*/</style>まり1</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::nth-text(2)",
+ "offset": 3,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Skip &lt;textarea&gt; content.</y-description>
+<div>小ぢん<textarea>textarea content</textarea>まり1</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::nth-text(2)",
+ "offset": 3,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Skip &lt;input&gt; content.</y-description>
+<div>小ぢん<input value="content" />まり1</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::nth-text(2)",
+ "offset": 3,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Skip &lt;button&gt; content.</y-description>
+<div>小ぢん<button>content</button>まり1</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::nth-text(2)",
+ "offset": 3,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Skip content with font-size:0.</y-description>
+<div>小ぢん<span style="font-size: 0;">content</span>まり1</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::nth-text(2)",
+ "offset": 3,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Skip content with opacity:0.</y-description>
+<div>小ぢん<span style="opacity: 0;">content</span>まり1</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::nth-text(2)",
+ "offset": 3,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Skip content with visibility:hidden.</y-description>
+<div>小ぢん<span style="visibility: hidden;">content</span>まり1</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::nth-text(2)",
+ "offset": 3,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Skip content with display:none.</y-description>
+<div>小ぢん<span style="display: none;">content</span>まり1</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::nth-text(2)",
+ "offset": 3,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Don't skip content with user-select:none.</y-description>
+<div>小ぢ<span style="user-select: none;">ん</span>まり1</div>
+ </y-test>
+
+ <y-test
+ data-test-data='{
+ "node": "div",
+ "offset": 0,
+ "length": 6,
+ "expected": {
+ "node": "div::nth-text(2)",
+ "offset": 3,
+ "content": "小ぢんまり1"
+ }
+ }'
+ >
+ <y-description>Skip content with user-select:none <em>and</em> a transparent color.</y-description>
+<div>小ぢん<span style="user-select: none; color: rgba(0, 0, 0, 0);">content</span>まり1</div>
+ </y-test>
+
+</body>
+</html> \ No newline at end of file
diff --git a/test/data/html/test-stylesheet.css b/test/data/html/test-stylesheet.css
index f63d2481..b4d2e255 100644
--- a/test/data/html/test-stylesheet.css
+++ b/test/data/html/test-stylesheet.css
@@ -19,7 +19,8 @@ p {
margin: 0.33em 0;
}
-h1+p {
+h1+p,
+h1+y-description {
margin-top: -0.67em;
}
@@ -28,7 +29,9 @@ a, a:visited {
text-decoration: underline;
}
-.test {
+.test,
+y-test {
+ display: block;
background-color: #ffffff;
margin: 1em 0;
padding: 0.5em;
@@ -36,7 +39,8 @@ a, a:visited {
border-radius: 4px;
}
-.test:before {
+.test:before,
+y-test:before {
content: "Test " counter(test-id);
display: block;
counter-increment: test-id;
@@ -45,7 +49,10 @@ a, a:visited {
font-weight: bold;
}
-.description {
+.description,
+y-description {
color: #444444;
font-style: italic;
+ display: block;
+ padding-bottom: 0.5em;
}
diff --git a/test/test-database.js b/test/test-database.js
index d27f92e1..e8a4a343 100644
--- a/test/test-database.js
+++ b/test/test-database.js
@@ -92,13 +92,63 @@ class XMLHttpRequest {
}
}
+class Image {
+ constructor() {
+ this._src = '';
+ this._loadCallbacks = [];
+ }
+
+ get src() {
+ return this._src;
+ }
+
+ set src(value) {
+ this._src = value;
+ this._delayTriggerLoad();
+ }
+
+ get naturalWidth() {
+ return 100;
+ }
+
+ get naturalHeight() {
+ return 100;
+ }
+
+ addEventListener(eventName, callback) {
+ if (eventName === 'load') {
+ this._loadCallbacks.push(callback);
+ }
+ }
+
+ removeEventListener(eventName, callback) {
+ if (eventName === 'load') {
+ const index = this._loadCallbacks.indexOf(callback);
+ if (index >= 0) {
+ this._loadCallbacks.splice(index, 1);
+ }
+ }
+ }
+
+ async _delayTriggerLoad() {
+ await Promise.resolve();
+ for (const callback of this._loadCallbacks) {
+ callback();
+ }
+ }
+}
+
const vm = new VM({
chrome,
+ Image,
XMLHttpRequest,
indexedDB: global.indexedDB,
IDBKeyRange: global.IDBKeyRange,
- JSZip: yomichanTest.JSZip
+ JSZip: yomichanTest.JSZip,
+ addEventListener() {
+ // NOP
+ }
});
vm.context.window = vm.context;
@@ -106,6 +156,7 @@ vm.execute([
'bg/js/json-schema.js',
'bg/js/dictionary.js',
'mixed/js/core.js',
+ 'bg/js/media-utility.js',
'bg/js/request.js',
'bg/js/dictionary-importer.js',
'bg/js/database.js'
@@ -182,10 +233,10 @@ async function testDatabase1() {
let progressEvent = false;
await database.deleteDictionary(
title,
+ {rate: 1000},
() => {
progressEvent = true;
- },
- {rate: 1000}
+ }
);
assert.ok(progressEvent);
@@ -216,10 +267,10 @@ async function testDatabase1() {
const {result, errors} = await dictionaryImporter.import(
database,
testDictionarySource,
+ {prefixWildcardsSupported: true},
() => {
progressEvent = true;
- },
- {prefixWildcardsSupported: true}
+ }
);
vm.assert.deepStrictEqual(errors, []);
vm.assert.deepStrictEqual(result, expectedSummary);
@@ -235,8 +286,8 @@ async function testDatabase1() {
true
);
vm.assert.deepStrictEqual(counts, {
- counts: [{kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 6, tagMeta: 14}],
- total: {kanji: 2, kanjiMeta: 2, terms: 32, termMeta: 6, tagMeta: 14}
+ counts: [{kanji: 2, kanjiMeta: 2, terms: 33, termMeta: 12, tagMeta: 14}],
+ total: {kanji: 2, kanjiMeta: 2, terms: 33, termMeta: 12, tagMeta: 14}
});
// Test find* functions
@@ -626,9 +677,9 @@ async function testFindTermMetaBulk1(database, titles) {
}
],
expectedResults: {
- total: 1,
+ total: 3,
modes: [
- ['freq', 1]
+ ['freq', 3]
]
}
},
@@ -639,9 +690,9 @@ async function testFindTermMetaBulk1(database, titles) {
}
],
expectedResults: {
- total: 1,
+ total: 3,
modes: [
- ['freq', 1]
+ ['freq', 3]
]
}
},
@@ -652,9 +703,9 @@ async function testFindTermMetaBulk1(database, titles) {
}
],
expectedResults: {
- total: 3,
+ total: 5,
modes: [
- ['freq', 1],
+ ['freq', 3],
['pitch', 2]
]
}
@@ -857,7 +908,7 @@ async function testDatabase2() {
// Error: not prepared
await assert.rejects(async () => await database.purge());
- await assert.rejects(async () => await database.deleteDictionary(title, () => {}, {}));
+ await assert.rejects(async () => await database.deleteDictionary(title, {}, () => {}));
await assert.rejects(async () => await database.findTermsBulk(['?'], titles, null));
await assert.rejects(async () => await database.findTermsExactBulk(['?'], ['?'], titles));
await assert.rejects(async () => await database.findTermsBySequenceBulk([1], title));
@@ -868,17 +919,17 @@ async function testDatabase2() {
await assert.rejects(async () => await database.findTagForTitle('tag', title));
await assert.rejects(async () => await database.getDictionaryInfo());
await assert.rejects(async () => await database.getDictionaryCounts(titles, true));
- await assert.rejects(async () => await dictionaryImporter.import(database, testDictionarySource, () => {}, {}));
+ await assert.rejects(async () => await dictionaryImporter.import(database, testDictionarySource, {}, () => {}));
await database.prepare();
// Error: already prepared
await assert.rejects(async () => await database.prepare());
- await dictionaryImporter.import(database, testDictionarySource, () => {}, {});
+ await dictionaryImporter.import(database, testDictionarySource, {}, () => {});
// Error: dictionary already imported
- await assert.rejects(async () => await dictionaryImporter.import(database, testDictionarySource, () => {}, {}));
+ await assert.rejects(async () => await dictionaryImporter.import(database, testDictionarySource, {}, () => {}));
await database.close();
}
@@ -905,7 +956,7 @@ async function testDatabase3() {
let error = null;
try {
- await dictionaryImporter.import(database, testDictionarySource, () => {}, {});
+ await dictionaryImporter.import(database, testDictionarySource, {}, () => {});
} catch (e) {
error = e;
}
diff --git a/test/test-dom-text-scanner.js b/test/test-dom-text-scanner.js
new file mode 100644
index 00000000..7374ff87
--- /dev/null
+++ b/test/test-dom-text-scanner.js
@@ -0,0 +1,183 @@
+/*
+ * Copyright (C) 2020 Yomichan Authors
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+const fs = require('fs');
+const path = require('path');
+const assert = require('assert');
+const {JSDOM} = require('jsdom');
+const {VM} = require('./yomichan-vm');
+
+
+function createJSDOM(fileName) {
+ const domSource = fs.readFileSync(fileName, {encoding: 'utf8'});
+ return new JSDOM(domSource);
+}
+
+function querySelectorTextNode(element, selector) {
+ let textIndex = -1;
+ const match = /::text$|::nth-text\((\d+)\)$/.exec(selector);
+ if (match !== null) {
+ textIndex = (match[1] ? parseInt(match[1], 10) - 1 : 0);
+ selector = selector.substring(0, selector.length - match[0].length);
+ }
+ const result = element.querySelector(selector);
+ if (textIndex < 0) {
+ return result;
+ }
+ for (let n = result.firstChild; n !== null; n = n.nextSibling) {
+ if (n.nodeType === n.constructor.TEXT_NODE) {
+ if (textIndex === 0) {
+ return n;
+ }
+ --textIndex;
+ }
+ }
+ return null;
+}
+
+
+function getComputedFontSizeInPixels(window, getComputedStyle, element) {
+ for (; element !== null; element = element.parentNode) {
+ if (element.nodeType === window.Node.ELEMENT_NODE) {
+ const fontSize = getComputedStyle(element).fontSize;
+ if (fontSize.endsWith('px')) {
+ const value = parseFloat(fontSize.substring(0, fontSize.length - 2));
+ return value;
+ }
+ }
+ }
+ const defaultFontSize = 14;
+ return defaultFontSize;
+}
+
+function createAbsoluteGetComputedStyle(window) {
+ // Wrapper to convert em units to px units
+ const getComputedStyleOld = window.getComputedStyle.bind(window);
+ return (element, ...args) => {
+ const style = getComputedStyleOld(element, ...args);
+ return new Proxy(style, {
+ get: (target, property) => {
+ let result = target[property];
+ if (typeof result === 'string') {
+ result = result.replace(/([-+]?\d(?:\.\d)?(?:[eE][-+]?\d+)?)em/g, (g0, g1) => {
+ const fontSize = getComputedFontSizeInPixels(window, getComputedStyleOld, element);
+ return `${parseFloat(g1) * fontSize}px`;
+ });
+ }
+ return result;
+ }
+ });
+ };
+}
+
+
+async function testDomTextScanner(dom, {DOMTextScanner}) {
+ const document = dom.window.document;
+ for (const testElement of document.querySelectorAll('y-test')) {
+ let testData = JSON.parse(testElement.dataset.testData);
+ if (!Array.isArray(testData)) {
+ testData = [testData];
+ }
+ for (const testDataItem of testData) {
+ let {
+ node,
+ offset,
+ length,
+ forcePreserveWhitespace,
+ generateLayoutContent,
+ reversible,
+ expected: {
+ node: expectedNode,
+ offset: expectedOffset,
+ content: expectedContent,
+ remainder: expectedRemainder
+ }
+ } = testDataItem;
+
+ node = querySelectorTextNode(testElement, node);
+ expectedNode = querySelectorTextNode(testElement, expectedNode);
+
+ // Standard test
+ {
+ const scanner = new DOMTextScanner(node, offset, forcePreserveWhitespace, generateLayoutContent);
+ scanner.seek(length);
+
+ const {node: actualNode1, offset: actualOffset1, content: actualContent1, remainder: actualRemainder1} = scanner;
+ assert.strictEqual(actualContent1, expectedContent);
+ assert.strictEqual(actualOffset1, expectedOffset);
+ assert.strictEqual(actualNode1, expectedNode);
+ assert.strictEqual(actualRemainder1, expectedRemainder || 0);
+ }
+
+ // Substring tests
+ for (let i = 1; i <= length; ++i) {
+ const scanner = new DOMTextScanner(node, offset, forcePreserveWhitespace, generateLayoutContent);
+ scanner.seek(length - i);
+
+ const {content: actualContent} = scanner;
+ assert.strictEqual(actualContent, expectedContent.substring(0, expectedContent.length - i));
+ }
+
+ if (reversible === false) { continue; }
+
+ // Reversed test
+ {
+ const scanner = new DOMTextScanner(expectedNode, expectedOffset, forcePreserveWhitespace, generateLayoutContent);
+ scanner.seek(-length);
+
+ const {content: actualContent} = scanner;
+ assert.strictEqual(actualContent, expectedContent);
+ }
+
+ // Reversed substring tests
+ for (let i = 1; i <= length; ++i) {
+ const scanner = new DOMTextScanner(expectedNode, expectedOffset, forcePreserveWhitespace, generateLayoutContent);
+ scanner.seek(-(length - i));
+
+ const {content: actualContent} = scanner;
+ assert.strictEqual(actualContent, expectedContent.substring(i));
+ }
+ }
+ }
+}
+
+
+async function testDocument1() {
+ const dom = createJSDOM(path.join(__dirname, 'data', 'html', 'test-dom-text-scanner.html'));
+ const window = dom.window;
+ try {
+ const {document, Node, Range} = window;
+
+ window.getComputedStyle = createAbsoluteGetComputedStyle(window);
+
+ const vm = new VM({document, window, Range, Node});
+ vm.execute('fg/js/dom-text-scanner.js');
+ const DOMTextScanner = vm.get('DOMTextScanner');
+
+ await testDomTextScanner(dom, {DOMTextScanner});
+ } finally {
+ window.close();
+ }
+}
+
+
+async function main() {
+ await testDocument1();
+}
+
+
+if (require.main === module) { main(); }
diff --git a/test/test-japanese.js b/test/test-japanese.js
index 87efdfad..39004128 100644
--- a/test/test-japanese.js
+++ b/test/test-japanese.js
@@ -22,8 +22,7 @@ const vm = new VM();
vm.execute([
'mixed/lib/wanakana.min.js',
'mixed/js/japanese.js',
- 'bg/js/text-source-map.js',
- 'bg/js/japanese.js'
+ 'bg/js/text-source-map.js'
]);
const jp = vm.get('jp');
const TextSourceMap = vm.get('TextSourceMap');
@@ -434,17 +433,17 @@ function testIsMoraPitchHigh() {
[[2, 1], false],
[[3, 1], false],
- [[0, 2], true],
+ [[0, 2], false],
[[1, 2], true],
[[2, 2], false],
[[3, 2], false],
- [[0, 3], true],
+ [[0, 3], false],
[[1, 3], true],
[[2, 3], true],
[[3, 3], false],
- [[0, 4], true],
+ [[0, 4], false],
[[1, 4], true],
[[2, 4], true],
[[3, 4], true]
diff --git a/test/test-object-property-accessor.js b/test/test-object-property-accessor.js
index 0773ba6e..1e694946 100644
--- a/test/test-object-property-accessor.js
+++ b/test/test-object-property-accessor.js
@@ -40,29 +40,30 @@ function createTestObject() {
}
-function testGetProperty1() {
- const object = createTestObject();
- const accessor = new ObjectPropertyAccessor(object);
-
+function testGet1() {
const data = [
- [[], object],
- [['0'], object['0']],
- [['value1'], object.value1],
- [['value1', 'value2'], object.value1.value2],
- [['value1', 'value3'], object.value1.value3],
- [['value1', 'value4'], object.value1.value4],
- [['value5'], object.value5],
- [['value5', 0], object.value5[0]],
- [['value5', 1], object.value5[1]],
- [['value5', 2], object.value5[2]]
+ [[], (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, expected] of data) {
- assert.strictEqual(accessor.getProperty(pathArray), expected);
+ for (const [pathArray, getExpected] of data) {
+ const object = createTestObject();
+ const accessor = new ObjectPropertyAccessor(object);
+ const expected = getExpected(object);
+
+ assert.strictEqual(accessor.get(pathArray), expected);
}
}
-function testGetProperty2() {
+function testGet2() {
const object = createTestObject();
const accessor = new ObjectPropertyAccessor(object);
@@ -89,15 +90,12 @@ function testGetProperty2() {
];
for (const [pathArray, message] of data) {
- assert.throws(() => accessor.getProperty(pathArray), {message});
+ assert.throws(() => accessor.get(pathArray), {message});
}
}
-function testSetProperty1() {
- const object = createTestObject();
- const accessor = new ObjectPropertyAccessor(object);
-
+function testSet1() {
const testValue = {};
const data = [
['0'],
@@ -112,17 +110,21 @@ function testSetProperty1() {
];
for (const pathArray of data) {
- accessor.setProperty(pathArray, testValue);
- assert.strictEqual(accessor.getProperty(pathArray), testValue);
+ const object = createTestObject();
+ const accessor = new ObjectPropertyAccessor(object);
+
+ accessor.set(pathArray, testValue);
+ assert.strictEqual(accessor.get(pathArray), testValue);
}
}
-function testSetProperty2() {
+function testSet2() {
const object = createTestObject();
const accessor = new ObjectPropertyAccessor(object);
const testValue = {};
const data = [
+ [[], 'Invalid path'],
[[0], 'Invalid path: [0]'],
[['0', 'invalid'], 'Invalid path: ["0"].invalid'],
[['value1', 'value2', 0], 'Invalid path: value1.value2[0]'],
@@ -137,7 +139,127 @@ function testSetProperty2() {
];
for (const [pathArray, message] of data) {
- assert.throws(() => accessor.setProperty(pathArray, testValue), {message});
+ assert.throws(() => accessor.set(pathArray, testValue), {message});
+ }
+}
+
+
+function testDelete1() {
+ const hasOwn = (object, property) => Object.prototype.hasOwnProperty.call(object, property);
+
+ 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 ObjectPropertyAccessor(object);
+
+ accessor.delete(pathArray);
+ assert.ok(validate(object));
+ }
+}
+
+function testDelete2() {
+ 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 ObjectPropertyAccessor(object);
+
+ assert.throws(() => accessor.delete(pathArray), {message});
+ }
+}
+
+
+function testSwap1() {
+ 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 ObjectPropertyAccessor(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() {
+ 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 ObjectPropertyAccessor(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);
}
}
@@ -272,10 +394,14 @@ function testIsValidPropertyType() {
function main() {
- testGetProperty1();
- testGetProperty2();
- testSetProperty1();
- testSetProperty2();
+ testGet1();
+ testGet2();
+ testSet1();
+ testSet2();
+ testDelete1();
+ testDelete2();
+ testSwap1();
+ testSwap2();
testGetPathString1();
testGetPathString2();
testGetPathArray1();
diff --git a/test/yomichan-test.js b/test/yomichan-test.js
index 3351ecdf..b4f5ac7c 100644
--- a/test/yomichan-test.js
+++ b/test/yomichan-test.js
@@ -38,12 +38,17 @@ function createTestDictionaryArchive(dictionary, dictionaryName) {
const archive = new (getJSZip())();
for (const fileName of fileNames) {
- const source = fs.readFileSync(path.join(dictionaryDirectory, fileName), {encoding: 'utf8'});
- const json = JSON.parse(source);
- if (fileName === 'index.json' && typeof dictionaryName === 'string') {
- json.title = dictionaryName;
+ if (/\.json$/.test(fileName)) {
+ const content = fs.readFileSync(path.join(dictionaryDirectory, fileName), {encoding: 'utf8'});
+ const json = JSON.parse(content);
+ if (fileName === 'index.json' && typeof dictionaryName === 'string') {
+ json.title = dictionaryName;
+ }
+ archive.file(fileName, JSON.stringify(json, null, 0));
+ } else {
+ const content = fs.readFileSync(path.join(dictionaryDirectory, fileName), {encoding: null});
+ archive.file(fileName, content);
}
- archive.file(fileName, JSON.stringify(json, null, 0));
}
return archive;