From f4d4ab35aa39152b09a87cabb02c9482f2f1534e Mon Sep 17 00:00:00 2001 From: zorkow Date: Thu, 30 Mar 2023 11:26:23 +0200 Subject: [PATCH 1/5] Adds compareDocumentPosition method from level 3 spec. --- lib/dom.js | 87 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 87 insertions(+) diff --git a/lib/dom.js b/lib/dom.js index 766db65c1..9a64991b4 100644 --- a/lib/dom.js +++ b/lib/dom.js @@ -130,6 +130,70 @@ var INVALID_MODIFICATION_ERR = (ExceptionCode.INVALID_MODIFICATION_ERR = ((Excep var NAMESPACE_ERR = (ExceptionCode.NAMESPACE_ERR = ((ExceptionMessage[14] = 'Invalid namespace'), 14)); var INVALID_ACCESS_ERR = (ExceptionCode.INVALID_ACCESS_ERR = ((ExceptionMessage[15] = 'Invalid access'), 15)); +// compareDocumentPosition bitmask results +var DOCUMENT_POSITION_DISCONNECTED = 1; +var DOCUMENT_POSITION_PRECEDING = 2; +var DOCUMENT_POSITION_FOLLOWING = 4; +var DOCUMENT_POSITION_CONTAINS = 8; +var DOCUMENT_POSITION_CONTAINED_BY = 16; +var DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32; + +//helper functions for compareDocumentPosition +/** + * Construct a parent chain for a node. + * @param {Node} node The start node. + * @return {Node[]} The parent chain. + */ +//TODO: this can be members +function parentChain(node) { + var chain = []; + while (node.parentNode || node.ownerElement) { + node = node.parentNode || node.ownerElement; + chain.unshift(node); + } + return chain; +} + +/** + * Find the common ancestor in two parent chains. + * @param {Node[]} a A parent chain. + * @param {Node[]} b A parent chain. + * @return {Node} The common ancestor if it exists. + */ +function commonAncestor(a, b) { + if (b.length < a.length) return commonAncestor(b, a); + var c = null; + for (var n in a) { + if (a[n] !== b[n]) return c; + c = a[n]; + } + return c; +} + +/** + * Computes a new guid. + * @return {string} A new guid. + */ +function guid() { + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = (Math.random() * 16) | 0; + var v = c === 'x' ? r : (r & 0x3) | 0x8; + return v.toString(16); + }); +} + +/** + * Comparing unrelated nodes must be consistent, so we assign a guid to the + * compared docs for further reference. + * @param {Document} doc The document. + * @return {string} The document's guid. + */ +function docGUID(doc) { + if (!doc.guid) doc.guid = guid(); + return doc.guid; +} +//-- end of helper functions + /** * DOM Level 2 * Object DOMException @@ -603,6 +667,29 @@ Node.prototype = { var prefix = this.lookupPrefix(namespaceURI); return prefix == null; }, + // Introduced in DOM Level 3: + compareDocumentPosition: function (o) { + if (this === o) return 0; + if (this.ownerDocument !== o.ownerDocument) + return ( + DOCUMENT_POSITION_DISCONNECTED | + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | + (docGUID(this.ownerDocument) > docGUID(o.ownerDocument) ? DOCUMENT_POSITION_FOLLOWING : DOCUMENT_POSITION_PRECEDING) + ); + var aa = parentChain(this); + var ab = parentChain(o); + if (aa.indexOf(o) >= 0) return DOCUMENT_POSITION_CONTAINS; + if (ab.indexOf(this) >= 0) return DOCUMENT_POSITION_CONTAINED_BY; + var ca = commonAncestor(aa, ab); + for (var n in ca.childNodes) { + var child = ca.childNodes[n]; + if (child === this) return DOCUMENT_POSITION_FOLLOWING; + if (child === o) return DOCUMENT_POSITION_PRECEDING; + if (aa.indexOf(child) >= 0) return DOCUMENT_POSITION_FOLLOWING; + if (ab.indexOf(child) >= 0) return DOCUMENT_POSITION_PRECEDING; + } + return 0; + }, }; function _xmlEncoder(c) { From 7a5e17eeda919384b16217c1f877fdfdb9044702 Mon Sep 17 00:00:00 2001 From: zorkow Date: Mon, 15 May 2023 17:02:25 +0200 Subject: [PATCH 2/5] Implements review suggestions. --- lib/dom.js | 76 +++++++++++++++++++++++++++++++++--------------------- 1 file changed, 46 insertions(+), 30 deletions(-) diff --git a/lib/dom.js b/lib/dom.js index 9a64991b4..188fbcba4 100644 --- a/lib/dom.js +++ b/lib/dom.js @@ -1,6 +1,7 @@ 'use strict'; var conventions = require('./conventions'); +var crypto = require('crypto'); var find = conventions.find; var isHTMLRawTextElement = conventions.isHTMLRawTextElement; var isHTMLVoidElement = conventions.isHTMLVoidElement; @@ -170,18 +171,6 @@ function commonAncestor(a, b) { return c; } -/** - * Computes a new guid. - * @return {string} A new guid. - */ -function guid() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { - var r = (Math.random() * 16) | 0; - var v = c === 'x' ? r : (r & 0x3) | 0x8; - return v.toString(16); - }); -} - /** * Comparing unrelated nodes must be consistent, so we assign a guid to the * compared docs for further reference. @@ -189,7 +178,7 @@ function guid() { * @return {string} The document's guid. */ function docGUID(doc) { - if (!doc.guid) doc.guid = guid(); + if (!doc.guid) doc.guid = crypto.randomUUID(); return doc.guid; } //-- end of helper functions @@ -668,25 +657,52 @@ Node.prototype = { return prefix == null; }, // Introduced in DOM Level 3: - compareDocumentPosition: function (o) { - if (this === o) return 0; - if (this.ownerDocument !== o.ownerDocument) - return ( - DOCUMENT_POSITION_DISCONNECTED | - DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC | - (docGUID(this.ownerDocument) > docGUID(o.ownerDocument) ? DOCUMENT_POSITION_FOLLOWING : DOCUMENT_POSITION_PRECEDING) - ); - var aa = parentChain(this); - var ab = parentChain(o); - if (aa.indexOf(o) >= 0) return DOCUMENT_POSITION_CONTAINS; - if (ab.indexOf(this) >= 0) return DOCUMENT_POSITION_CONTAINED_BY; - var ca = commonAncestor(aa, ab); + /** + * Compares the reference node with a node with regard to their position + * in the document and according to the document order. + * + * @param {Node} other The node to compare the reference node to. + * @return {number} Returns how the node is positioned relatively to the + * reference node according to the bitmask. 0 if reference node and + * given node are the same. + * @see https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-compareDocumentPosition + */ + compareDocumentPosition: function (other) { + if (this === other) return 0; + var node1 = other; + var node2 = this; + var attr1 = null; + var attr2 = null; + if (node1 instanceof Attr) { + attr1 = node1; + node1 = attr1.ownerElement; + } + if (node2 instanceof Attr) { + attr2 = node2; + node2 = attr2.ownerElement; + if (attr1 && node1 && node2 === node1) { + for (var i = 0, attr; attr = node2.attributes[i]; i++) { + if (attr === attr1) return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DOCUMENT_POSITION_PRECEDING; + if (attr === attr2) return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DOCUMENT_POSITION_FOLLOWING; + } + } + } + if (!node1 || !node2 || // Orphan attribute case. + node2.ownerDocument !== node1.ownerDocument) + return DOCUMENT_POSITION_DISCONNECTED + + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + + (docGUID(node2.ownerDocument) > docGUID(node1.ownerDocument) ? DOCUMENT_POSITION_FOLLOWING : DOCUMENT_POSITION_PRECEDING); + const chain1 = parentChain(node1); + const chain2 = parentChain(node2); + if (chain2.indexOf(node1) >= 0) return DOCUMENT_POSITION_CONTAINS; + if (chain1.indexOf(node2) >= 0) return DOCUMENT_POSITION_CONTAINED_BY; + var ca = commonAncestor(chain2, chain1); for (var n in ca.childNodes) { var child = ca.childNodes[n]; - if (child === this) return DOCUMENT_POSITION_FOLLOWING; - if (child === o) return DOCUMENT_POSITION_PRECEDING; - if (aa.indexOf(child) >= 0) return DOCUMENT_POSITION_FOLLOWING; - if (ab.indexOf(child) >= 0) return DOCUMENT_POSITION_PRECEDING; + if (child === node2) return DOCUMENT_POSITION_FOLLOWING; + if (child === node1) return DOCUMENT_POSITION_PRECEDING; + if (chain2.indexOf(child) >= 0) return DOCUMENT_POSITION_FOLLOWING; + if (chain1.indexOf(child) >= 0) return DOCUMENT_POSITION_PRECEDING; } return 0; }, From 0aee9b26ea0b19400c5ea97922a3da650b4185a2 Mon Sep 17 00:00:00 2001 From: zorkow Date: Tue, 16 May 2023 01:27:46 +0200 Subject: [PATCH 3/5] Implements full spec of compareDocumentPosition and adds tests. --- lib/dom.js | 19 ++++---- test/dom/dom-comparison.test.js | 81 +++++++++++++++++++++++++++++++++ 2 files changed, 92 insertions(+), 8 deletions(-) create mode 100644 test/dom/dom-comparison.test.js diff --git a/lib/dom.js b/lib/dom.js index 188fbcba4..0b2a827ad 100644 --- a/lib/dom.js +++ b/lib/dom.js @@ -1,7 +1,6 @@ 'use strict'; var conventions = require('./conventions'); -var crypto = require('crypto'); var find = conventions.find; var isHTMLRawTextElement = conventions.isHTMLRawTextElement; var isHTMLVoidElement = conventions.isHTMLVoidElement; @@ -178,7 +177,7 @@ function commonAncestor(a, b) { * @return {string} The document's guid. */ function docGUID(doc) { - if (!doc.guid) doc.guid = crypto.randomUUID(); + if (!doc.guid) doc.guid = Math.random(); return doc.guid; } //-- end of helper functions @@ -687,15 +686,19 @@ Node.prototype = { } } } - if (!node1 || !node2 || // Orphan attribute case. - node2.ownerDocument !== node1.ownerDocument) + if (!node1 || !node2 || node2.ownerDocument !== node1.ownerDocument) { return DOCUMENT_POSITION_DISCONNECTED + - DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + - (docGUID(node2.ownerDocument) > docGUID(node1.ownerDocument) ? DOCUMENT_POSITION_FOLLOWING : DOCUMENT_POSITION_PRECEDING); + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + + (docGUID(node2.ownerDocument) > docGUID(node1.ownerDocument) ? DOCUMENT_POSITION_FOLLOWING : DOCUMENT_POSITION_PRECEDING); + } const chain1 = parentChain(node1); const chain2 = parentChain(node2); - if (chain2.indexOf(node1) >= 0) return DOCUMENT_POSITION_CONTAINS; - if (chain1.indexOf(node2) >= 0) return DOCUMENT_POSITION_CONTAINED_BY; + if ((!attr1 && chain2.indexOf(node1) >= 0) || (attr2 && node1 === node2)) { + return DOCUMENT_POSITION_CONTAINS + DOCUMENT_POSITION_PRECEDING; + } + if ((!attr2 && chain1.indexOf(node2) >= 0) || (attr1 && node1 === node2)) { + return DOCUMENT_POSITION_CONTAINED_BY + DOCUMENT_POSITION_FOLLOWING; + } var ca = commonAncestor(chain2, chain1); for (var n in ca.childNodes) { var child = ca.childNodes[n]; diff --git a/test/dom/dom-comparison.test.js b/test/dom/dom-comparison.test.js new file mode 100644 index 000000000..a214accd6 --- /dev/null +++ b/test/dom/dom-comparison.test.js @@ -0,0 +1,81 @@ +'use strict'; +const { DOMParser } = require('../../lib'); +const { DOMException } = require('../../lib/dom'); + +// Tests following for steps in the specification +// https://dom.spec.whatwg.org/#dom-node-comparedocumentposition +describe('DOM position comparison', () => { + const dp = new DOMParser(); + const x0 = dp.parseFromString( + '' + + '' + + 'c' + + 'd' + + '' + + '' + + 'e' + + '', 'text/xml').documentElement; + const x1 = x0.childNodes[0]; + const y1 = x0.childNodes[1]; + const x2 = x1.childNodes[0]; + const y2 = x1.childNodes[1]; + const z2 = y1.childNodes[0]; + const foo = x0.attributes[0]; + const bar = x0.attributes[1]; + let text = x2.childNodes[0]; + it('Step 1', () => { + expect(x0.compareDocumentPosition(x0)).toBe(0); + expect(x1.compareDocumentPosition(x1)).toBe(0); + expect(foo.compareDocumentPosition(foo)).toBe(0); + expect(text.compareDocumentPosition(text)); + }); + it('Step 5 2 1 1', async () => { + expect(bar.compareDocumentPosition(foo)).toBe(34); + }); + it('Step 5 2 1 2', async () => { + expect(foo.compareDocumentPosition(bar)).toBe(36); + }); + it('Step 6', () => { + const root = dp.parseFromString('', 'text/xml').documentElement; + const baz = root.childNodes[0]; + const abaz = baz.attributes[0]; + // This ensures the comparison is stable. + let comp = x0.compareDocumentPosition(root) === 35; + expect(x0.compareDocumentPosition(root)).toBe(comp ? 35 : 37); + expect(root.compareDocumentPosition(x0)).toBe(comp ? 37 : 35); + expect(x1.compareDocumentPosition(baz)).toBe(comp ? 35 : 37); + expect(baz.compareDocumentPosition(x1)).toBe(comp ? 37 : 35); + expect(foo.compareDocumentPosition(abaz)).toBe(comp ? 35 : 37); + expect(abaz.compareDocumentPosition(foo)).toBe(comp ? 37 : 35); + }); + it('Step 7', () => { + expect(x1.compareDocumentPosition(x0)).toBe(10); + expect(x2.compareDocumentPosition(x0)).toBe(10); + expect(x2.compareDocumentPosition(x1)).toBe(10); + expect(foo.compareDocumentPosition(x0)).toBe(10); + }); + it('Step 8', () => { + expect(x0.compareDocumentPosition(x1)).toBe(20); + expect(x0.compareDocumentPosition(x2)).toBe(20); + expect(x1.compareDocumentPosition(x2)).toBe(20); + expect(x0.compareDocumentPosition(foo)).toBe(20); + }); + it('Step 9', () => { + expect(y1.compareDocumentPosition(x1)).toBe(2); + expect(y1.compareDocumentPosition(x2)).toBe(2); + expect(z2.compareDocumentPosition(x2)).toBe(2); + expect(z2.compareDocumentPosition(x1)).toBe(2); + expect(y1.compareDocumentPosition(x1.attributes[0])).toBe(2); + expect(x1.attributes[0].compareDocumentPosition(foo)).toBe(2); + expect(y1.attributes[0].compareDocumentPosition(foo)).toBe(2); + }); + it('Step 10', () => { + expect(x1.compareDocumentPosition(y1)).toBe(4); + expect(x2.compareDocumentPosition(y1)).toBe(4); + expect(x2.compareDocumentPosition(z2)).toBe(4); + expect(x1.compareDocumentPosition(z2)).toBe(4); + expect(x1.attributes[0].compareDocumentPosition(y1)).toBe(4); + expect(foo.compareDocumentPosition(x1.attributes[0])).toBe(4); + expect(foo.compareDocumentPosition(y1.attributes[0])).toBe(4); + }); +}); From 80d9c326229b35053762d0d1a45be2d58e842cef Mon Sep 17 00:00:00 2001 From: zorkow Date: Tue, 16 May 2023 01:58:58 +0200 Subject: [PATCH 4/5] Adds document position comparison bitmask to node prototype. --- lib/dom.js | 16 +++++---- test/dom/dom-comparison.test.js | 63 +++++++++++++++++++-------------- 2 files changed, 45 insertions(+), 34 deletions(-) diff --git a/lib/dom.js b/lib/dom.js index 0b2a827ad..10512c7f9 100644 --- a/lib/dom.js +++ b/lib/dom.js @@ -131,12 +131,13 @@ var NAMESPACE_ERR = (ExceptionCode.NAMESPACE_ERR = ((ExceptionMessage[14] = 'Inv var INVALID_ACCESS_ERR = (ExceptionCode.INVALID_ACCESS_ERR = ((ExceptionMessage[15] = 'Invalid access'), 15)); // compareDocumentPosition bitmask results -var DOCUMENT_POSITION_DISCONNECTED = 1; -var DOCUMENT_POSITION_PRECEDING = 2; -var DOCUMENT_POSITION_FOLLOWING = 4; -var DOCUMENT_POSITION_CONTAINS = 8; -var DOCUMENT_POSITION_CONTAINED_BY = 16; -var DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32; +var DocumentPosition = {}; +var DOCUMENT_POSITION_DISCONNECTED = (DocumentPosition.DOCUMENT_POSITION_DISCONNECTED = 1); +var DOCUMENT_POSITION_PRECEDING = (DocumentPosition.DOCUMENT_POSITION_PRECEDING = 2); +var DOCUMENT_POSITION_FOLLOWING = (DocumentPosition.DOCUMENT_POSITION_FOLLOWING = 4); +var DOCUMENT_POSITION_CONTAINS = (DocumentPosition.DOCUMENT_POSITION_CONTAINS = 8); +var DOCUMENT_POSITION_CONTAINED_BY = (DocumentPosition.DOCUMENT_POSITION_CONTAINED_BY = 16); +var DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = (DocumentPosition.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32); //helper functions for compareDocumentPosition /** @@ -144,7 +145,6 @@ var DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC = 32; * @param {Node} node The start node. * @return {Node[]} The parent chain. */ -//TODO: this can be members function parentChain(node) { var chain = []; while (node.parentNode || node.ownerElement) { @@ -719,6 +719,8 @@ function _xmlEncoder(c) { copy(NodeType, Node); copy(NodeType, Node.prototype); +copy(DocumentPosition, Node); +copy(DocumentPosition, Node.prototype); /** * @param callback return true for continue,false for break diff --git a/test/dom/dom-comparison.test.js b/test/dom/dom-comparison.test.js index a214accd6..dea78f7bc 100644 --- a/test/dom/dom-comparison.test.js +++ b/test/dom/dom-comparison.test.js @@ -30,52 +30,61 @@ describe('DOM position comparison', () => { expect(text.compareDocumentPosition(text)); }); it('Step 5 2 1 1', async () => { - expect(bar.compareDocumentPosition(foo)).toBe(34); + let result = x0.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + x0.DOCUMENT_POSITION_PRECEDING; + expect(bar.compareDocumentPosition(foo)).toBe(result); }); it('Step 5 2 1 2', async () => { - expect(foo.compareDocumentPosition(bar)).toBe(36); + let result = x0.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + x0.DOCUMENT_POSITION_FOLLOWING; + expect(foo.compareDocumentPosition(bar)).toBe(result); }); it('Step 6', () => { + let result = x0.DOCUMENT_POSITION_DISCONNECTED + x0.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; + let resPre = result + x0.DOCUMENT_POSITION_PRECEDING; + let resFol = result + x0.DOCUMENT_POSITION_FOLLOWING; const root = dp.parseFromString('', 'text/xml').documentElement; const baz = root.childNodes[0]; const abaz = baz.attributes[0]; // This ensures the comparison is stable. - let comp = x0.compareDocumentPosition(root) === 35; - expect(x0.compareDocumentPosition(root)).toBe(comp ? 35 : 37); - expect(root.compareDocumentPosition(x0)).toBe(comp ? 37 : 35); - expect(x1.compareDocumentPosition(baz)).toBe(comp ? 35 : 37); - expect(baz.compareDocumentPosition(x1)).toBe(comp ? 37 : 35); - expect(foo.compareDocumentPosition(abaz)).toBe(comp ? 35 : 37); - expect(abaz.compareDocumentPosition(foo)).toBe(comp ? 37 : 35); + let comp = x0.compareDocumentPosition(root) === resPre; + expect(x0.compareDocumentPosition(root)).toBe(comp ? resPre : resFol); + expect(root.compareDocumentPosition(x0)).toBe(comp ? resFol : resPre); + expect(x1.compareDocumentPosition(baz)).toBe(comp ? resPre : resFol); + expect(baz.compareDocumentPosition(x1)).toBe(comp ? resFol : resPre); + expect(foo.compareDocumentPosition(abaz)).toBe(comp ? resPre : resFol); + expect(abaz.compareDocumentPosition(foo)).toBe(comp ? resFol : resPre); }); it('Step 7', () => { - expect(x1.compareDocumentPosition(x0)).toBe(10); - expect(x2.compareDocumentPosition(x0)).toBe(10); - expect(x2.compareDocumentPosition(x1)).toBe(10); - expect(foo.compareDocumentPosition(x0)).toBe(10); + let result = x0.DOCUMENT_POSITION_CONTAINS + x0.DOCUMENT_POSITION_PRECEDING; + expect(x1.compareDocumentPosition(x0)).toBe(result); + expect(x2.compareDocumentPosition(x0)).toBe(result); + expect(x2.compareDocumentPosition(x1)).toBe(result); + expect(foo.compareDocumentPosition(x0)).toBe(result); }); it('Step 8', () => { + let result = x0.DOCUMENT_POSITION_CONTAINED_BY + x0.DOCUMENT_POSITION_FOLLOWING; expect(x0.compareDocumentPosition(x1)).toBe(20); expect(x0.compareDocumentPosition(x2)).toBe(20); expect(x1.compareDocumentPosition(x2)).toBe(20); expect(x0.compareDocumentPosition(foo)).toBe(20); }); it('Step 9', () => { - expect(y1.compareDocumentPosition(x1)).toBe(2); - expect(y1.compareDocumentPosition(x2)).toBe(2); - expect(z2.compareDocumentPosition(x2)).toBe(2); - expect(z2.compareDocumentPosition(x1)).toBe(2); - expect(y1.compareDocumentPosition(x1.attributes[0])).toBe(2); - expect(x1.attributes[0].compareDocumentPosition(foo)).toBe(2); - expect(y1.attributes[0].compareDocumentPosition(foo)).toBe(2); + let result = x0.DOCUMENT_POSITION_PRECEDING; + expect(y1.compareDocumentPosition(x1)).toBe(result); + expect(y1.compareDocumentPosition(x2)).toBe(result); + expect(z2.compareDocumentPosition(x2)).toBe(result); + expect(z2.compareDocumentPosition(x1)).toBe(result); + expect(y1.compareDocumentPosition(x1.attributes[0])).toBe(result); + expect(x1.attributes[0].compareDocumentPosition(foo)).toBe(result); + expect(y1.attributes[0].compareDocumentPosition(foo)).toBe(result); }); it('Step 10', () => { - expect(x1.compareDocumentPosition(y1)).toBe(4); - expect(x2.compareDocumentPosition(y1)).toBe(4); - expect(x2.compareDocumentPosition(z2)).toBe(4); - expect(x1.compareDocumentPosition(z2)).toBe(4); - expect(x1.attributes[0].compareDocumentPosition(y1)).toBe(4); - expect(foo.compareDocumentPosition(x1.attributes[0])).toBe(4); - expect(foo.compareDocumentPosition(y1.attributes[0])).toBe(4); + let result = x0.DOCUMENT_POSITION_FOLLOWING; + expect(x1.compareDocumentPosition(y1)).toBe(result); + expect(x2.compareDocumentPosition(y1)).toBe(result); + expect(x2.compareDocumentPosition(z2)).toBe(result); + expect(x1.compareDocumentPosition(z2)).toBe(result); + expect(x1.attributes[0].compareDocumentPosition(y1)).toBe(result); + expect(foo.compareDocumentPosition(x1.attributes[0])).toBe(result); + expect(foo.compareDocumentPosition(y1.attributes[0])).toBe(result); }); }); From d1255f8f96894782c3796565488cf82ea1bd3043 Mon Sep 17 00:00:00 2001 From: zorkow Date: Tue, 16 May 2023 02:09:45 +0200 Subject: [PATCH 5/5] Lints `dom` and `dom-comparison.test`. --- lib/dom.js | 80 ++++++++-------- test/dom/dom-comparison.test.js | 160 ++++++++++++++++---------------- 2 files changed, 122 insertions(+), 118 deletions(-) diff --git a/lib/dom.js b/lib/dom.js index 10512c7f9..c28c6e46e 100644 --- a/lib/dom.js +++ b/lib/dom.js @@ -656,49 +656,51 @@ Node.prototype = { return prefix == null; }, // Introduced in DOM Level 3: - /** - * Compares the reference node with a node with regard to their position - * in the document and according to the document order. - * - * @param {Node} other The node to compare the reference node to. - * @return {number} Returns how the node is positioned relatively to the - * reference node according to the bitmask. 0 if reference node and - * given node are the same. - * @see https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-compareDocumentPosition - */ + /** + * Compares the reference node with a node with regard to their position + * in the document and according to the document order. + * + * @param {Node} other The node to compare the reference node to. + * @return {number} Returns how the node is positioned relatively to the + * reference node according to the bitmask. 0 if reference node and + * given node are the same. + * @see https://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#Node3-compareDocumentPosition + */ compareDocumentPosition: function (other) { if (this === other) return 0; - var node1 = other; - var node2 = this; - var attr1 = null; - var attr2 = null; - if (node1 instanceof Attr) { - attr1 = node1; - node1 = attr1.ownerElement; - } - if (node2 instanceof Attr) { - attr2 = node2; - node2 = attr2.ownerElement; - if (attr1 && node1 && node2 === node1) { - for (var i = 0, attr; attr = node2.attributes[i]; i++) { - if (attr === attr1) return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DOCUMENT_POSITION_PRECEDING; - if (attr === attr2) return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DOCUMENT_POSITION_FOLLOWING; - } - } - } + var node1 = other; + var node2 = this; + var attr1 = null; + var attr2 = null; + if (node1 instanceof Attr) { + attr1 = node1; + node1 = attr1.ownerElement; + } + if (node2 instanceof Attr) { + attr2 = node2; + node2 = attr2.ownerElement; + if (attr1 && node1 && node2 === node1) { + for (var i = 0, attr; (attr = node2.attributes[i]); i++) { + if (attr === attr1) return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DOCUMENT_POSITION_PRECEDING; + if (attr === attr2) return DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + DOCUMENT_POSITION_FOLLOWING; + } + } + } if (!node1 || !node2 || node2.ownerDocument !== node1.ownerDocument) { - return DOCUMENT_POSITION_DISCONNECTED + - DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + - (docGUID(node2.ownerDocument) > docGUID(node1.ownerDocument) ? DOCUMENT_POSITION_FOLLOWING : DOCUMENT_POSITION_PRECEDING); - } - const chain1 = parentChain(node1); - const chain2 = parentChain(node2); - if ((!attr1 && chain2.indexOf(node1) >= 0) || (attr2 && node1 === node2)) { - return DOCUMENT_POSITION_CONTAINS + DOCUMENT_POSITION_PRECEDING; - } + return ( + DOCUMENT_POSITION_DISCONNECTED + + DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + + (docGUID(node2.ownerDocument) > docGUID(node1.ownerDocument) ? DOCUMENT_POSITION_FOLLOWING : DOCUMENT_POSITION_PRECEDING) + ); + } + var chain1 = parentChain(node1); + var chain2 = parentChain(node2); + if ((!attr1 && chain2.indexOf(node1) >= 0) || (attr2 && node1 === node2)) { + return DOCUMENT_POSITION_CONTAINS + DOCUMENT_POSITION_PRECEDING; + } if ((!attr2 && chain1.indexOf(node2) >= 0) || (attr1 && node1 === node2)) { - return DOCUMENT_POSITION_CONTAINED_BY + DOCUMENT_POSITION_FOLLOWING; - } + return DOCUMENT_POSITION_CONTAINED_BY + DOCUMENT_POSITION_FOLLOWING; + } var ca = commonAncestor(chain2, chain1); for (var n in ca.childNodes) { var child = ca.childNodes[n]; diff --git a/test/dom/dom-comparison.test.js b/test/dom/dom-comparison.test.js index dea78f7bc..cad9e4475 100644 --- a/test/dom/dom-comparison.test.js +++ b/test/dom/dom-comparison.test.js @@ -5,86 +5,88 @@ const { DOMException } = require('../../lib/dom'); // Tests following for steps in the specification // https://dom.spec.whatwg.org/#dom-node-comparedocumentposition describe('DOM position comparison', () => { - const dp = new DOMParser(); - const x0 = dp.parseFromString( - '' + - '' + - 'c' + - 'd' + - '' + - '' + - 'e' + - '', 'text/xml').documentElement; - const x1 = x0.childNodes[0]; - const y1 = x0.childNodes[1]; - const x2 = x1.childNodes[0]; - const y2 = x1.childNodes[1]; - const z2 = y1.childNodes[0]; - const foo = x0.attributes[0]; - const bar = x0.attributes[1]; - let text = x2.childNodes[0]; - it('Step 1', () => { - expect(x0.compareDocumentPosition(x0)).toBe(0); - expect(x1.compareDocumentPosition(x1)).toBe(0); - expect(foo.compareDocumentPosition(foo)).toBe(0); - expect(text.compareDocumentPosition(text)); - }); + const dp = new DOMParser(); + const x0 = dp.parseFromString( + '' + + '' + + 'c' + + 'd' + + '' + + '' + + 'e' + + '', + 'text/xml' + ).documentElement; + const x1 = x0.childNodes[0]; + const y1 = x0.childNodes[1]; + const x2 = x1.childNodes[0]; + const y2 = x1.childNodes[1]; + const z2 = y1.childNodes[0]; + const foo = x0.attributes[0]; + const bar = x0.attributes[1]; + let text = x2.childNodes[0]; + it('Step 1', () => { + expect(x0.compareDocumentPosition(x0)).toBe(0); + expect(x1.compareDocumentPosition(x1)).toBe(0); + expect(foo.compareDocumentPosition(foo)).toBe(0); + expect(text.compareDocumentPosition(text)); + }); it('Step 5 2 1 1', async () => { - let result = x0.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + x0.DOCUMENT_POSITION_PRECEDING; - expect(bar.compareDocumentPosition(foo)).toBe(result); - }); + let result = x0.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + x0.DOCUMENT_POSITION_PRECEDING; + expect(bar.compareDocumentPosition(foo)).toBe(result); + }); it('Step 5 2 1 2', async () => { - let result = x0.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + x0.DOCUMENT_POSITION_FOLLOWING; - expect(foo.compareDocumentPosition(bar)).toBe(result); - }); + let result = x0.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + x0.DOCUMENT_POSITION_FOLLOWING; + expect(foo.compareDocumentPosition(bar)).toBe(result); + }); it('Step 6', () => { - let result = x0.DOCUMENT_POSITION_DISCONNECTED + x0.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; - let resPre = result + x0.DOCUMENT_POSITION_PRECEDING; - let resFol = result + x0.DOCUMENT_POSITION_FOLLOWING; - const root = dp.parseFromString('', 'text/xml').documentElement; - const baz = root.childNodes[0]; - const abaz = baz.attributes[0]; - // This ensures the comparison is stable. - let comp = x0.compareDocumentPosition(root) === resPre; - expect(x0.compareDocumentPosition(root)).toBe(comp ? resPre : resFol); - expect(root.compareDocumentPosition(x0)).toBe(comp ? resFol : resPre); - expect(x1.compareDocumentPosition(baz)).toBe(comp ? resPre : resFol); - expect(baz.compareDocumentPosition(x1)).toBe(comp ? resFol : resPre); - expect(foo.compareDocumentPosition(abaz)).toBe(comp ? resPre : resFol); - expect(abaz.compareDocumentPosition(foo)).toBe(comp ? resFol : resPre); - }); - it('Step 7', () => { - let result = x0.DOCUMENT_POSITION_CONTAINS + x0.DOCUMENT_POSITION_PRECEDING; - expect(x1.compareDocumentPosition(x0)).toBe(result); - expect(x2.compareDocumentPosition(x0)).toBe(result); - expect(x2.compareDocumentPosition(x1)).toBe(result); - expect(foo.compareDocumentPosition(x0)).toBe(result); - }); - it('Step 8', () => { - let result = x0.DOCUMENT_POSITION_CONTAINED_BY + x0.DOCUMENT_POSITION_FOLLOWING; - expect(x0.compareDocumentPosition(x1)).toBe(20); - expect(x0.compareDocumentPosition(x2)).toBe(20); - expect(x1.compareDocumentPosition(x2)).toBe(20); - expect(x0.compareDocumentPosition(foo)).toBe(20); - }); - it('Step 9', () => { - let result = x0.DOCUMENT_POSITION_PRECEDING; - expect(y1.compareDocumentPosition(x1)).toBe(result); - expect(y1.compareDocumentPosition(x2)).toBe(result); - expect(z2.compareDocumentPosition(x2)).toBe(result); - expect(z2.compareDocumentPosition(x1)).toBe(result); - expect(y1.compareDocumentPosition(x1.attributes[0])).toBe(result); - expect(x1.attributes[0].compareDocumentPosition(foo)).toBe(result); - expect(y1.attributes[0].compareDocumentPosition(foo)).toBe(result); - }); - it('Step 10', () => { - let result = x0.DOCUMENT_POSITION_FOLLOWING; - expect(x1.compareDocumentPosition(y1)).toBe(result); - expect(x2.compareDocumentPosition(y1)).toBe(result); - expect(x2.compareDocumentPosition(z2)).toBe(result); - expect(x1.compareDocumentPosition(z2)).toBe(result); - expect(x1.attributes[0].compareDocumentPosition(y1)).toBe(result); - expect(foo.compareDocumentPosition(x1.attributes[0])).toBe(result); - expect(foo.compareDocumentPosition(y1.attributes[0])).toBe(result); - }); + let result = x0.DOCUMENT_POSITION_DISCONNECTED + x0.DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC; + let resPre = result + x0.DOCUMENT_POSITION_PRECEDING; + let resFol = result + x0.DOCUMENT_POSITION_FOLLOWING; + const root = dp.parseFromString('', 'text/xml').documentElement; + const baz = root.childNodes[0]; + const abaz = baz.attributes[0]; + // This ensures the comparison is stable. + let comp = x0.compareDocumentPosition(root) === resPre; + expect(x0.compareDocumentPosition(root)).toBe(comp ? resPre : resFol); + expect(root.compareDocumentPosition(x0)).toBe(comp ? resFol : resPre); + expect(x1.compareDocumentPosition(baz)).toBe(comp ? resPre : resFol); + expect(baz.compareDocumentPosition(x1)).toBe(comp ? resFol : resPre); + expect(foo.compareDocumentPosition(abaz)).toBe(comp ? resPre : resFol); + expect(abaz.compareDocumentPosition(foo)).toBe(comp ? resFol : resPre); + }); + it('Step 7', () => { + let result = x0.DOCUMENT_POSITION_CONTAINS + x0.DOCUMENT_POSITION_PRECEDING; + expect(x1.compareDocumentPosition(x0)).toBe(result); + expect(x2.compareDocumentPosition(x0)).toBe(result); + expect(x2.compareDocumentPosition(x1)).toBe(result); + expect(foo.compareDocumentPosition(x0)).toBe(result); + }); + it('Step 8', () => { + let result = x0.DOCUMENT_POSITION_CONTAINED_BY + x0.DOCUMENT_POSITION_FOLLOWING; + expect(x0.compareDocumentPosition(x1)).toBe(20); + expect(x0.compareDocumentPosition(x2)).toBe(20); + expect(x1.compareDocumentPosition(x2)).toBe(20); + expect(x0.compareDocumentPosition(foo)).toBe(20); + }); + it('Step 9', () => { + let result = x0.DOCUMENT_POSITION_PRECEDING; + expect(y1.compareDocumentPosition(x1)).toBe(result); + expect(y1.compareDocumentPosition(x2)).toBe(result); + expect(z2.compareDocumentPosition(x2)).toBe(result); + expect(z2.compareDocumentPosition(x1)).toBe(result); + expect(y1.compareDocumentPosition(x1.attributes[0])).toBe(result); + expect(x1.attributes[0].compareDocumentPosition(foo)).toBe(result); + expect(y1.attributes[0].compareDocumentPosition(foo)).toBe(result); + }); + it('Step 10', () => { + let result = x0.DOCUMENT_POSITION_FOLLOWING; + expect(x1.compareDocumentPosition(y1)).toBe(result); + expect(x2.compareDocumentPosition(y1)).toBe(result); + expect(x2.compareDocumentPosition(z2)).toBe(result); + expect(x1.compareDocumentPosition(z2)).toBe(result); + expect(x1.attributes[0].compareDocumentPosition(y1)).toBe(result); + expect(foo.compareDocumentPosition(x1.attributes[0])).toBe(result); + expect(foo.compareDocumentPosition(y1.attributes[0])).toBe(result); + }); });