Dedupe & refactor range comparison code
diff --git a/packages/dom/test/text-position/match.test.ts b/packages/dom/test/text-position/match.test.ts
index f59f490..cbfbfc7 100644
--- a/packages/dom/test/text-position/match.test.ts
+++ b/packages/dom/test/text-position/match.test.ts
@@ -21,7 +21,7 @@
import { assert } from 'chai';
import type { TextPositionSelector } from '@apache-annotator/selector';
import { createTextPositionSelectorMatcher } from '../../src/text-position/match';
-import { evaluateXPath } from '../utils';
+import { evaluateXPath, assertRangeEquals } from '../utils';
import type { RangeInfo } from '../utils';
import { testCases } from './match-cases';
@@ -33,7 +33,7 @@
)) {
it(`works for case: '${name}'`, async () => {
const doc = domParser.parseFromString(html, 'text/html');
- await testMatcher(doc, doc, selector, expected);
+ await testMatcher(doc, selector, expected);
});
}
@@ -46,7 +46,7 @@
// console.log([...textNode.parentNode.childNodes].map(node => node.textContent))
// → [ 'l😃rem ipsum dol', 'or amet yada yada' ]
- await testMatcher(doc, doc, selector, [
+ await testMatcher(doc, selector, [
{
startContainerXPath: '//b/text()[1]',
startOffset: 13,
@@ -72,7 +72,7 @@
// console.log([...textNode.parentNode.childNodes].map(node => node.textContent))
// → [ '', 'l😃rem ipsum ', '', 'dolor', '', ' am', '', 'et yada yada', '' ]
- await testMatcher(doc, doc, selector, [
+ await testMatcher(doc, selector, [
{
startContainerXPath: '//b/text()[4]', // "dolor"
startOffset: 0,
@@ -89,7 +89,7 @@
const scope = doc.createRange();
scope.selectNodeContents(evaluateXPath(doc, '//b/text()'));
- await testMatcher(doc, scope, selector, expected);
+ await testMatcher(scope, selector, expected);
});
it('works when scope starts with an empty text node, matching its first characters', async () => {
@@ -102,7 +102,7 @@
const scope = doc.createRange();
scope.selectNodeContents(evaluateXPath(doc, '//b'));
- await testMatcher(doc, scope, selector, [
+ await testMatcher(scope, selector, [
{
startContainerXPath: '//b/text()[2]',
startOffset: 0,
@@ -128,7 +128,7 @@
end: 14,
};
- await testMatcher(doc, scope, selector, expected);
+ await testMatcher(scope, selector, expected);
});
it('works when scope has both ends inside text nodes', async () => {
@@ -146,7 +146,7 @@
end: 12,
};
- await testMatcher(doc, scope, selector, expected);
+ await testMatcher(scope, selector, expected);
});
it('works when scope has both ends inside an element', async () => {
@@ -161,55 +161,19 @@
start: 6,
end: 14,
};
- await testMatcher(doc, scope, selector, expected);
+ await testMatcher(scope, selector, expected);
});
});
async function testMatcher(
- doc: Document,
scope: Node | Range,
selector: TextPositionSelector,
expected: RangeInfo[],
) {
const matcher = createTextPositionSelectorMatcher(selector);
- const matches = [];
- for await (const value of matcher(scope)) matches.push(value);
- assert.equal(matches.length, expected.length);
- matches.forEach((match, i) => {
- const expectedRange = expected[i];
- const expectedStartContainer = evaluateXPath(
- doc,
- expectedRange.startContainerXPath,
- );
- const expectedEndContainer = evaluateXPath(
- doc,
- expectedRange.endContainerXPath,
- );
- assert(
- match.startContainer === expectedStartContainer,
- `unexpected start container: ${prettyNodeName(match.startContainer)}; ` +
- `expected ${prettyNodeName(expectedStartContainer)}`,
- );
- assert.equal(match.startOffset, expectedRange.startOffset);
- assert(
- match.endContainer ===
- evaluateXPath(doc, expectedRange.endContainerXPath),
- `unexpected end container: ${prettyNodeName(match.endContainer)}; ` +
- `expected ${prettyNodeName(expectedEndContainer)}`,
- );
- assert.equal(match.endOffset, expectedRange.endOffset);
- });
-}
-
-function prettyNodeName(node: Node) {
- switch (node.nodeType) {
- case Node.TEXT_NODE: {
- const text = (node as Text).nodeValue || '';
- return `#text "${text.length > 50 ? text.substring(0, 50) + '…' : text}"`;
- }
- case Node.ELEMENT_NODE:
- return `<${(node as Element).tagName.toLowerCase()}>`;
- default:
- return node.nodeName.toLowerCase();
+ let count = 0;
+ for await (const match of matcher(scope)) {
+ assertRangeEquals(match, expected[count++]);
}
+ assert.equal(count, expected.length, 'Wrong number of matches.');
}
diff --git a/packages/dom/test/text-quote/match.test.ts b/packages/dom/test/text-quote/match.test.ts
index bd54963..c9429a4 100644
--- a/packages/dom/test/text-quote/match.test.ts
+++ b/packages/dom/test/text-quote/match.test.ts
@@ -21,7 +21,7 @@
import { assert } from 'chai';
import type { TextQuoteSelector } from '@apache-annotator/selector';
import { createTextQuoteSelectorMatcher } from '../../src/text-quote/match';
-import { evaluateXPath } from '../utils';
+import { evaluateXPath, assertRangeEquals } from '../utils';
import type { RangeInfo } from '../utils';
import { testCases } from './match-cases';
@@ -194,7 +194,7 @@
const matcher = createTextQuoteSelectorMatcher(selector);
let count = 0;
for await (const match of matcher(scope)) {
- assertMatchIsCorrect(doc, match, expected[count++]);
+ assertRangeEquals(match, expected[count++]);
if (mutateDom) {
const wrapperNode = doc.createElement('mark');
match.surroundContents(wrapperNode);
@@ -202,70 +202,3 @@
}
assert.equal(count, expected.length, 'Wrong number of matches.');
}
-
-function assertMatchIsCorrect(
- doc: Document,
- match: Range,
- expected: RangeInfo,
-) {
- if (expected === undefined) {
- assert.fail(`Unexpected match: ${prettyRange(match)}`);
- }
- const expectedStartContainer = evaluateXPath(
- doc,
- expected.startContainerXPath,
- );
- const expectedEndContainer = evaluateXPath(
- doc,
- expected.endContainerXPath,
- );
- assert(
- match.startContainer === expectedStartContainer,
- `unexpected start container: ${prettyNodeName(match.startContainer)}; ` +
- `expected ${prettyNodeName(expectedStartContainer)}`,
- );
- assert.equal(match.startOffset, expected.startOffset);
- assert(
- match.endContainer ===
- evaluateXPath(doc, expected.endContainerXPath),
- `unexpected end container: ${prettyNodeName(match.endContainer)}; ` +
- `expected ${prettyNodeName(expectedEndContainer)}`,
- );
- assert.equal(match.endOffset, expected.endOffset);
-}
-
-function prettyNodeName(node: Node) {
- switch (node.nodeType) {
- case Node.TEXT_NODE: {
- const text = (node as Text).nodeValue || '';
- return `#text "${text.length > 50 ? text.substring(0, 50) + '…' : text}"`;
- }
- case Node.ELEMENT_NODE:
- return `<${(node as Element).tagName.toLowerCase()}>`;
- default:
- return node.nodeName.toLowerCase();
- }
-}
-
-function prettyRange(range: Range): string {
- let s = 'Range('
- if (
- range.startContainer.nodeType === Node.TEXT_NODE
- && range.startContainer.parentNode
- ) s += prettyNodeName(range.startContainer.parentNode) + ' → ';
- s += prettyNodeName(range.startContainer) + ' : ' + range.startOffset;
- if (range.endContainer !== range.startContainer) {
- s += ' … '
- if (
- range.endContainer.nodeType === Node.TEXT_NODE
- && range.endContainer.parentNode
- && range.endContainer.parentNode !== range.startContainer.parentNode
- ) s += prettyNodeName(range.endContainer.parentNode) + ' → ';
- s += prettyNodeName(range.endContainer) + ' : ';
- } else {
- s += '…';
- }
- s += range.endOffset;
- s += ')';
- return s;
-}
diff --git a/packages/dom/test/utils.ts b/packages/dom/test/utils.ts
index 9a484a9..5a3f183 100644
--- a/packages/dom/test/utils.ts
+++ b/packages/dom/test/utils.ts
@@ -19,6 +19,7 @@
*/
import { assert } from 'chai';
+import { ownerDocument } from '../src/owner-document';
// RangeInfo serialises a Range’s start and end containers as XPaths.
export type RangeInfo = {
@@ -58,3 +59,70 @@
);
return range;
}
+
+export function assertRangeEquals(
+ match: Range,
+ expected: RangeInfo,
+) {
+ const doc = ownerDocument(match);
+ if (expected === undefined) {
+ assert.fail(`Unexpected match: ${prettyRange(match)}`);
+ }
+ const expectedStartContainer = evaluateXPath(
+ doc,
+ expected.startContainerXPath,
+ );
+ const expectedEndContainer = evaluateXPath(
+ doc,
+ expected.endContainerXPath,
+ );
+ assert(
+ match.startContainer === expectedStartContainer,
+ `unexpected start container: ${prettyNodeName(match.startContainer)}; ` +
+ `expected ${prettyNodeName(expectedStartContainer)}`,
+ );
+ assert.equal(match.startOffset, expected.startOffset);
+ assert(
+ match.endContainer ===
+ evaluateXPath(doc, expected.endContainerXPath),
+ `unexpected end container: ${prettyNodeName(match.endContainer)}; ` +
+ `expected ${prettyNodeName(expectedEndContainer)}`,
+ );
+ assert.equal(match.endOffset, expected.endOffset);
+}
+
+function prettyNodeName(node: Node) {
+ switch (node.nodeType) {
+ case Node.TEXT_NODE: {
+ const text = (node as Text).nodeValue || '';
+ return `#text "${text.length > 50 ? text.substring(0, 50) + '…' : text}"`;
+ }
+ case Node.ELEMENT_NODE:
+ return `<${(node as Element).tagName.toLowerCase()}>`;
+ default:
+ return node.nodeName.toLowerCase();
+ }
+}
+
+function prettyRange(range: Range): string {
+ let s = 'Range('
+ if (
+ range.startContainer.nodeType === Node.TEXT_NODE
+ && range.startContainer.parentNode
+ ) s += prettyNodeName(range.startContainer.parentNode) + ' → ';
+ s += prettyNodeName(range.startContainer) + ' : ' + range.startOffset;
+ if (range.endContainer !== range.startContainer) {
+ s += ' … '
+ if (
+ range.endContainer.nodeType === Node.TEXT_NODE
+ && range.endContainer.parentNode
+ && range.endContainer.parentNode !== range.startContainer.parentNode
+ ) s += prettyNodeName(range.endContainer.parentNode) + ' → ';
+ s += prettyNodeName(range.endContainer) + ' : ';
+ } else {
+ s += '…';
+ }
+ s += range.endOffset;
+ s += ')';
+ return s;
+}