diff options
Diffstat (limited to 'test')
-rw-r--r-- | test/data/html/test-document1.html | 264 | ||||
-rw-r--r-- | test/data/html/test-stylesheet.css | 32 | ||||
-rw-r--r-- | test/test-document.js | 224 | ||||
-rw-r--r-- | test/test-stylesheet.css | 32 |
4 files changed, 552 insertions, 0 deletions
diff --git a/test/data/html/test-document1.html b/test/data/html/test-document1.html new file mode 100644 index 00000000..0754a314 --- /dev/null +++ b/test/data/html/test-document1.html @@ -0,0 +1,264 @@ +<!DOCTYPE html> +<html> + <head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width,initial-scale=1" /> + <title>Yomichan 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 Tests</h1> + + <div + class="test" + data-test-type="scan" + data-element-from-point-selector="span" + data-caret-range-from-point-selector="span" + data-start-node-selector="span" + data-start-offset="0" + data-end-node-selector="span" + data-end-offset="0" + data-result-type="TextSourceRange", + data-sentence-extent="100" + data-sentence="真白「心配してくださって、ありがとございます」" + > + <span>真白「心配してくださって、ありがとございます」</span> + </div> + + <div + class="test" + data-test-type="scan" + data-element-from-point-selector="span" + data-caret-range-from-point-selector="span" + data-start-node-selector="span" + data-start-offset="5" + data-end-node-selector="span" + data-end-offset="5" + data-result-type="TextSourceRange", + data-sentence-extent="100" + data-sentence="心配してくださって、ありがとございます" + > + <span>真白「心配してくださって、ありがとございます」</span> + </div> + + <div + class="test" + data-test-type="scan" + data-element-from-point-selector="input" + data-caret-range-from-point-selector="input" + data-start-node-selector="input" + data-start-offset="0" + data-end-node-selector="input" + data-end-offset="0" + data-result-type="TextSourceRange", + data-sentence-extent="100" + data-sentence="真白「心配してくださって、ありがとございます」" + data-has-imposter="true" + > + <input type="text" value="真白「心配してくださって、ありがとございます」" style="width: 100%; box-sizing: border-box; font-family: inherit; font-size: inherit; border: 1px solid #d8d8d8; padding: 0.2em;" /> + </div> + + <div + class="test" + data-test-type="scan" + data-element-from-point-selector="textarea" + data-caret-range-from-point-selector="textarea" + data-start-node-selector="textarea" + data-start-offset="0" + data-end-node-selector="textarea" + data-end-offset="0" + data-result-type="TextSourceRange", + data-sentence-extent="100" + data-sentence="真白「心配してくださって、ありがとございます」" + data-has-imposter="true" + > + <textarea style="width: 100%; height: 3em; box-sizing: border-box; font-family: inherit; font-size: inherit; border: 1px solid #d8d8d8; padding: 0.2em;">真白「心配してくださって、ありがとございます」</textarea> + </div> + + <div + class="test" + data-test-type="scan" + data-element-from-point-selector="button" + data-caret-range-from-point-selector="button" + data-start-node-selector="button" + data-start-offset="0" + data-end-node-selector="button" + data-end-offset="0" + data-result-type="TextSourceElement", + data-sentence-extent="100" + data-sentence="よみちゃん" + > + <button style="width: 100%; box-sizing: border-box; font-family: inherit; font-size: inherit; border: 1px solid #d8d8d8; background-color: #f0f0f0; padding: 0.2em;">よみちゃん</button> + </div> + + <div + class="test" + data-test-type="scan" + data-element-from-point-selector="img" + data-caret-range-from-point-selector="img" + data-start-node-selector="img" + data-start-offset="0" + data-end-node-selector="img" + data-end-offset="0" + data-result-type="TextSourceElement" + 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;" /> + </div> + + <div + class="test" + data-test-type="text-source-range-seek" + data-seek-node-selector="span:nth-of-type(1)" + data-seek-node-is-text="true" + data-seek-offset="0" + data-seek-length="149" + data-seek-direction="forward" + data-expected-result-node-selector="span:nth-of-type(1)" + data-expected-result-node-is-text="true" + data-expected-result-offset="149" + data-expected-result-content=" + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + " + > + <span> + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + </span><span>trailing content</span> + </div> + + <div + class="test" + data-test-type="text-source-range-seek" + data-seek-node-selector="span:nth-of-type(1)" + data-seek-node-is-text="true" + data-seek-offset="149" + data-seek-length="149" + data-seek-direction="backward" + data-expected-result-node-selector="span:nth-of-type(1)" + data-expected-result-node-is-text="true" + data-expected-result-offset="0" + data-expected-result-content=" + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + " + > + <span> + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + </span><span>trailing content</span> + </div> + + <div + class="test" + data-test-type="text-source-range-seek" + data-seek-node-selector="span:nth-of-type(1)" + data-seek-node-is-text="true" + data-seek-offset="0" + data-seek-length="150" + data-seek-direction="forward" + data-expected-result-node-selector="span:nth-of-type(2)" + data-expected-result-node-is-text="true" + data-expected-result-offset="1" + data-expected-result-content=" + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + t" + > + <span> + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + </span><span>trailing content</span> + </div> + + <div + class="test" + data-test-type="text-source-range-seek" + data-seek-node-selector="span:nth-of-type(2)" + data-seek-node-is-text="true" + data-seek-offset="1" + data-seek-length="150" + data-seek-direction="backward" + data-expected-result-node-selector="span:nth-of-type(1)" + data-expected-result-node-is-text="true" + data-expected-result-offset="0" + data-expected-result-content=" + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + t" + > + <span> + あいうえお + かきくけこ + さしすせそ + たちつてと + なにぬねの + はひふへほ + まみむめも + や ゆ よ + らりるれろ + わゐ ゑを + </span><span>trailing content</span> + </div> + +</body> +</html>
\ No newline at end of file diff --git a/test/data/html/test-stylesheet.css b/test/data/html/test-stylesheet.css new file mode 100644 index 00000000..ab25732e --- /dev/null +++ b/test/data/html/test-stylesheet.css @@ -0,0 +1,32 @@ +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + max-width: 680px; + padding: 0 1em; + box-sizing: border-box; + margin: 0 auto; + background-color: #f8f8f8; + counter-reset: test-id; +} + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +.test { + background-color: #ffffff; + margin: 1em 0; + padding: 0.5em; + box-shadow: rgba(64, 64, 64, 0.3) 0px 1px 2px 0px, rgba(64, 64, 64, 0.15) 0px 1px 3px 1px; + border-radius: 4px; +} + +.test:before { + content: "Test " counter(test-id); + display: block; + counter-increment: test-id; + margin-bottom: 0.5em; + border-bottom: 1px solid #d8d8d8; + font-weight: bold; +} diff --git a/test/test-document.js b/test/test-document.js new file mode 100644 index 00000000..edf6bbea --- /dev/null +++ b/test/test-document.js @@ -0,0 +1,224 @@ +const fs = require('fs'); +const path = require('path'); +const assert = require('assert'); +const {JSDOM} = require('jsdom'); +const yomichanTest = require('./yomichan-test'); + + +// DOMRect class definition +class DOMRect { + constructor(x, y, width, height) { + this._x = x; + this._y = y; + this._width = width; + this._height = height; + } + + get x() { return this._x; } + get y() { return this._y; } + get width() { return this._width; } + get height() { return this._height; } + get left() { return this._x + Math.min(0, this._width); } + get right() { return this._x + Math.max(0, this._width); } + get top() { return this._y + Math.min(0, this._height); } + get bottom() { return this._y + Math.max(0, this._height); } +} + + +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; +} + +function querySelectorChildOrSelf(element, selector) { + return selector ? element.querySelector(selector) : element; +} + +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); +} + +function getPrototypeOfOrNull(value) { + try { + return Object.getPrototypeOf(value); + } catch (e) { + return null; + } +} + +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 {DOM} = yomichanTest.requireScript( + 'ext/mixed/js/dom.js', + ['DOM'] + ); + const {TextSourceRange, TextSourceElement} = yomichanTest.requireScript( + 'ext/fg/js/source.js', + ['TextSourceRange', 'TextSourceElement'], + {document, window, Range, Node} + ); + const {docRangeFromPoint, docSentenceExtract} = yomichanTest.requireScript( + 'ext/fg/js/document.js', + ['docRangeFromPoint', 'docSentenceExtract'], + {document, window, Node, TextSourceElement, TextSourceRange, DOM} + ); + + try { + await testDocumentTextScanningFunctions(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement}); + await testTextSourceRangeSeekFunctions(dom, {TextSourceRange}); + } finally { + window.close(); + } +} + +async function testDocumentTextScanningFunctions(dom, {docRangeFromPoint, docSentenceExtract, TextSourceRange, TextSourceElement}) { + const document = dom.window.document; + + for (const testElement of document.querySelectorAll('.test[data-test-type=scan]')) { + // Get test parameters + let { + elementFromPointSelector, + caretRangeFromPointSelector, + startNodeSelector, + startOffset, + endNodeSelector, + endOffset, + resultType, + sentenceExtent, + sentence, + hasImposter + } = 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)); + + startOffset = parseInt(startOffset, 10); + endOffset = parseInt(endOffset, 10); + sentenceExtent = parseInt(sentenceExtent, 10); + + 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(imposter ? imposter : startNode, startOffset); + range.setEnd(imposter ? imposter : startNode, endOffset); + + // Override getClientRects to return a rect guaranteed to contain (x, y) + range.getClientRects = () => [new DOMRect(x - 1, y - 1, 2, 2)]; + return range; + }; + + // Test docRangeFromPoint + const source = docRangeFromPoint(0, 0, false); + 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; } + + // Test docSentenceExtract + const sentenceActual = docSentenceExtract(source, sentenceExtent).text; + assert.strictEqual(sentenceActual, sentence); + + // Clean + source.cleanup(); + } +} + +async function testTextSourceRangeSeekFunctions(dom, {TextSourceRange}) { + const document = dom.window.document; + + for (const testElement of document.querySelectorAll('.test[data-test-type=text-source-range-seek]')) { + // Get test parameters + let { + seekNodeSelector, + seekNodeIsText, + seekOffset, + seekLength, + seekDirection, + expectedResultNodeSelector, + expectedResultNodeIsText, + expectedResultOffset, + expectedResultContent + } = testElement.dataset; + + seekOffset = parseInt(seekOffset, 10); + seekLength = parseInt(seekLength, 10); + expectedResultOffset = parseInt(expectedResultOffset, 10); + + let seekNode = testElement.querySelector(seekNodeSelector); + if (seekNodeIsText === 'true') { + seekNode = seekNode.firstChild; + } + + let expectedResultNode = testElement.querySelector(expectedResultNodeSelector); + if (expectedResultNodeIsText === 'true') { + expectedResultNode = expectedResultNode.firstChild; + } + + const {node, offset, content} = ( + seekDirection === 'forward' ? + TextSourceRange.seekForward(seekNode, seekOffset, seekLength) : + TextSourceRange.seekBackward(seekNode, seekOffset, seekLength) + ); + + assert.strictEqual(node, expectedResultNode); + assert.strictEqual(offset, expectedResultOffset); + assert.strictEqual(content, expectedResultContent); + } +} + + +async function main() { + await testDocument1(); +} + + +if (require.main === module) { main(); } diff --git a/test/test-stylesheet.css b/test/test-stylesheet.css new file mode 100644 index 00000000..ab25732e --- /dev/null +++ b/test/test-stylesheet.css @@ -0,0 +1,32 @@ +body { + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + max-width: 680px; + padding: 0 1em; + box-sizing: border-box; + margin: 0 auto; + background-color: #f8f8f8; + counter-reset: test-id; +} + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +.test { + background-color: #ffffff; + margin: 1em 0; + padding: 0.5em; + box-shadow: rgba(64, 64, 64, 0.3) 0px 1px 2px 0px, rgba(64, 64, 64, 0.15) 0px 1px 3px 1px; + border-radius: 4px; +} + +.test:before { + content: "Test " counter(test-id); + display: block; + counter-increment: test-id; + margin-bottom: 0.5em; + border-bottom: 1px solid #d8d8d8; + font-weight: bold; +} |