blob: 10f8bb92e46d7e158158cda267c51fe6d467b99a [file] [log] [blame]
/**
* SPDX-FileCopyrightText: 2016-2021 The Apache Software Foundation
* SPDX-License-Identifier: Apache-2.0
* @license
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { assert } from 'chai';
import { ownerDocument } from '../src/owner-document';
// RangeInfo serialises a Range’s start and end containers as XPaths.
export type RangeInfo = {
startContainerXPath: string;
startOffset: number;
endContainerXPath: string;
endOffset: number;
};
export function evaluateXPath(doc: Document, xpath: string): Node {
const result = doc.evaluate(
xpath,
doc,
null,
XPathResult.ORDERED_NODE_SNAPSHOT_TYPE,
);
const nodes = new Array(result.snapshotLength)
.fill(undefined)
.map((_, i) => result.snapshotItem(i));
assert.equal(
nodes.length,
1,
`Test suite contains XPath with ${nodes.length} results instead of 1: '${xpath}'`,
);
return nodes[0] as Node;
}
export function hydrateRange(rangeInfo: RangeInfo, doc: Document): Range {
const range = doc.createRange();
range.setStart(
evaluateXPath(doc, rangeInfo.startContainerXPath),
rangeInfo.startOffset,
);
range.setEnd(
evaluateXPath(doc, rangeInfo.endContainerXPath),
rangeInfo.endOffset,
);
return range;
}
export function assertRangeEquals(match: Range, expected: RangeInfo): void {
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;
}