yomichan/test/test-document-util.js

270 lines
9.1 KiB
JavaScript
Raw Normal View History

2020-03-09 01:31:18 +00:00
/*
* Copyright (C) 2020-2022 Yomichan Authors
2020-03-09 01:31:18 +00:00
*
* 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/>.
*/
2020-02-21 02:18:31 +00:00
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');
2020-02-21 02:18:31 +00:00
// 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 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',
Move mixed/js (#1383) * Move mixed/js/core.js to js/core.js * Move mixed/js/yomichan.js to js/yomichan.js * Move mixed/js/timer.js to js/debug/timer.js * Move mixed/js/hotkey-handler.js to js/input/hotkey-handler.js * Move mixed/js/hotkey-help-controller.js to js/input/hotkey-help-controller.js * Move mixed/js/hotkey-util.js to js/input/hotkey-util.js * Move mixed/js/audio-system.js to js/input/audio-system.js * Move mixed/js/media-loader.js to js/input/media-loader.js * Move mixed/js/text-to-speech-audio.js to js/input/text-to-speech-audio.js * Move mixed/js/comm.js to js/comm/cross-frame-api.js * Move mixed/js/api.js to js/comm/api.js * Move mixed/js/frame-client.js to js/comm/frame-client.js * Move mixed/js/frame-endpoint.js to js/comm/frame-endpoint.js * Move mixed/js/display.js to js/display/display.js * Move mixed/js/display-audio.js to js/display/display-audio.js * Move mixed/js/display-generator.js to js/display/display-generator.js * Move mixed/js/display-history.js to js/display/display-history.js * Move mixed/js/display-notification.js to js/display/display-notification.js * Move mixed/js/display-profile-selection.js to js/display/display-profile-selection.js * Move mixed/js/japanese.js to js/language/japanese-util.js * Move mixed/js/dictionary-data-util.js to js/language/dictionary-data-util.js * Move mixed/js/document-focus-controller.js to js/dom/document-focus-controller.js * Move mixed/js/document-util.js to js/dom/document-util.js * Move mixed/js/dom-data-binder.js to js/dom/dom-data-binder.js * Move mixed/js/html-template-collection.js to js/dom/html-template-collection.js * Move mixed/js/panel-element.js to js/dom/panel-element.js * Move mixed/js/popup-menu.js to js/dom/popup-menu.js * Move mixed/js/selector-observer.js to js/dom/selector-observer.js * Move mixed/js/scroll.js to js/dom/window-scroll.js * Move mixed/js/text-scanner.js to js/language/text-scanner.js * Move mixed/js/cache-map.js to js/general/cache-map.js * Move mixed/js/object-property-accessor.js to js/general/object-property-accessor.js * Move mixed/js/task-accumulator.js to js/general/task-accumulator.js * Move mixed/js/environment.js to js/background/environment.js * Move mixed/js/dynamic-loader.js to js/scripting/dynamic-loader.js * Move mixed/js/dynamic-loader-sentinel.js to js/scripting/dynamic-loader-sentinel.js
2021-02-14 03:52:28 +00:00
'js/dom/document-util.js'
]);
const [DOMTextScanner, TextSourceRange, TextSourceElement, DocumentUtil] = vm.get([
'DOMTextScanner',
'TextSourceRange',
'TextSourceElement',
'DocumentUtil'
]);
2020-02-21 02:18:31 +00:00
try {
await testDocumentTextScanningFunctions(dom, {DocumentUtil, TextSourceRange, TextSourceElement});
await testTextSourceRangeSeekFunctions(dom, {DOMTextScanner});
2020-02-21 02:18:31 +00:00
} finally {
window.close();
}
}
async function testDocumentTextScanningFunctions(dom, {DocumentUtil, TextSourceRange, TextSourceElement}) {
2020-02-21 02:18:31 +00:00
const document = dom.window.document;
2020-02-22 21:32:38 +00:00
for (const testElement of document.querySelectorAll('.test[data-test-type=scan]')) {
2020-02-21 02:18:31 +00:00
// Get test parameters
let {
elementFromPointSelector,
caretRangeFromPointSelector,
startNodeSelector,
startOffset,
endNodeSelector,
endOffset,
resultType,
sentenceScanExtent,
2020-02-21 02:18:31 +00:00
sentence,
hasImposter,
terminateAtNewlines
2020-02-21 02:18:31 +00:00
} = 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);
sentenceScanExtent = parseInt(sentenceScanExtent, 10);
terminateAtNewlines = (terminateAtNewlines !== 'false');
2020-02-21 02:18:31 +00:00
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 = DocumentUtil.getRangeFromPoint(0, 0, {
deepContentScan: false,
normalizeCssZoom: true
});
2020-02-21 02:18:31 +00:00
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]);
}
2020-02-21 02:18:31 +00:00
// Test docSentenceExtract
const sentenceActual = DocumentUtil.extractSentence(
source,
false,
sentenceScanExtent,
terminateAtNewlines,
terminatorMap,
forwardQuoteMap,
backwardQuoteMap
).text;
2020-02-21 02:18:31 +00:00
assert.strictEqual(sentenceActual, sentence);
// Clean
source.cleanup();
}
}
async function testTextSourceRangeSeekFunctions(dom, {DOMTextScanner}) {
2020-02-22 21:32:38 +00:00
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' ?
new DOMTextScanner(seekNode, seekOffset, true, false).seek(seekLength) :
new DOMTextScanner(seekNode, seekOffset, true, false).seek(-seekLength)
2020-02-22 21:32:38 +00:00
);
assert.strictEqual(node, expectedResultNode);
assert.strictEqual(offset, expectedResultOffset);
assert.strictEqual(content, expectedResultContent);
}
}
2020-02-21 02:18:31 +00:00
async function main() {
await testDocument1();
}
if (require.main === module) { testMain(main); }