diff --git a/src/js/dom-util.js b/src/js/dom-util.js index ae5f2c73..c2852443 100644 --- a/src/js/dom-util.js +++ b/src/js/dom-util.js @@ -67,20 +67,75 @@ export const resolveContent = node => { break; } } - const tree = document.createTreeWalker(root, WALKER_FILTER); + const walker = document.createTreeWalker(root, WALKER_FILTER); return [ document, root, - tree + walker ]; }; +/** + * traverse node tree + * @private + * @param {object} node - node + * @param {object} walker - tree walker + * @returns {?object} - current node + */ +export const traverseNode = (node, walker) => { + if (!node || !node.nodeType) { + // throws + verifyNode(node); + } + let current; + if (walker?.currentNode) { + let refNode = walker.currentNode; + if (refNode === node) { + current = refNode; + } else if (refNode.contains(node)) { + refNode = walker.nextNode(); + while (refNode) { + if (refNode === node) { + current = refNode; + break; + } + refNode = walker.nextNode(); + } + } else { + if (refNode !== walker.root) { + while (refNode) { + if (refNode === walker.root || refNode === node) { + break; + } + refNode = walker.parentNode(); + } + } + if (node.nodeType === ELEMENT_NODE) { + while (refNode) { + if (refNode === node) { + current = refNode; + break; + } + refNode = walker.nextNode(); + } + } else { + current = refNode; + } + } + } + return current ?? null; +}; + /** * is in shadow tree * @param {object} node - node * @returns {boolean} - result; */ -export const isInShadowTree = (node = {}) => { +export const isInShadowTree = node => { + if (!node || !node.type) { + // throws + verifyNode(node); + } let bool; if (node.nodeType === ELEMENT_NODE || node.nodeType === DOCUMENT_FRAGMENT_NODE) { @@ -103,7 +158,11 @@ export const isInShadowTree = (node = {}) => { * @param {object} node - Element node * @returns {?string} - text content */ -export const getSlottedTextContent = (node = {}) => { +export const getSlottedTextContent = node => { + if (!node || !node.nodeType) { + // throws + verifyNode(node); + } let res; if (node.localName === 'slot' && isInShadowTree(node)) { const nodes = node.assignedNodes(); @@ -127,7 +186,11 @@ export const getSlottedTextContent = (node = {}) => { * @param {object} node - Element node * @returns {?string} - 'ltr' / 'rtl' */ -export const getDirectionality = (node = {}) => { +export const getDirectionality = node => { + if (!node || !node.nodeType) { + // throws + verifyNode(node); + } let res; if (node.nodeType === ELEMENT_NODE) { const { dir: nodeDir, localName, parentNode } = node; @@ -248,7 +311,11 @@ export const getDirectionality = (node = {}) => { * @param {object} node - Element node * @returns {boolean} - result */ -export const isContentEditable = (node = {}) => { +export const isContentEditable = node => { + if (!node || !node.nodeType) { + // throws + verifyNode(node); + } let res; if (node.nodeType === ELEMENT_NODE) { if (typeof node.isContentEditable === 'boolean') { @@ -281,8 +348,12 @@ export const isContentEditable = (node = {}) => { * @returns {?string} - namespace URI */ export const getNamespaceURI = (ns, node) => { + if (!node || !node.nodeType) { + // throws + verifyNode(node); + } let res; - if (ns && typeof ns === 'string' && node?.nodeType === ELEMENT_NODE) { + if (ns && typeof ns === 'string' && node.nodeType === ELEMENT_NODE) { const { attributes } = node; for (const attr of attributes) { const { name, namespaceURI, prefix, value } = attr; @@ -329,7 +400,14 @@ export const isNamespaceDeclared = (ns = '', node = {}) => { * @param {object} nodeB - Element node * @returns {boolean} - result */ -export const isPreceding = (nodeA = {}, nodeB = {}) => { +export const isPreceding = (nodeA, nodeB) => { + if (!nodeA || !nodeA.nodeType) { + // throws + verifyNode(nodeA); + } else if (!nodeB || !nodeB.nodeType) { + // throws + verifyNode(nodeB); + } let res; if (nodeA.nodeType === ELEMENT_NODE && nodeB.nodeType === ELEMENT_NODE) { const posBit = nodeB.compareDocumentPosition(nodeA); diff --git a/src/js/finder.js b/src/js/finder.js index c7fc7e81..297befbd 100644 --- a/src/js/finder.js +++ b/src/js/finder.js @@ -6,7 +6,8 @@ import isCustomElementName from 'is-potential-custom-element-name'; import nwsapi from '@asamuzakjp/nwsapi'; import { - isContentEditable, isInShadowTree, resolveContent, sortNodes, verifyNode + isContentEditable, isInShadowTree, resolveContent, sortNodes, traverseNode, + verifyNode } from './dom-util.js'; import { matcher } from './matcher.js'; import { @@ -314,51 +315,6 @@ export class Finder { return this.#qswalker; } - /** - * traverse tree walker - * @private - * @param {object} node - Element node - * @param {object} [walker] - tree walker - * @returns {?object} - current node - */ - _traverse(node, walker = this.#walker) { - let refNode = walker.currentNode; - let current; - if (refNode === node) { - current = refNode; - } else if (refNode.contains(node)) { - refNode = walker.nextNode(); - while (refNode) { - if (refNode === node) { - current = refNode; - break; - } - refNode = walker.nextNode(); - } - } else { - if (refNode !== walker.root) { - while (refNode) { - if (refNode === walker.root || refNode === node) { - break; - } - refNode = walker.parentNode(); - } - } - if (node?.nodeType === ELEMENT_NODE) { - while (refNode) { - if (refNode === node) { - current = refNode; - break; - } - refNode = walker.nextNode(); - } - } else { - current = refNode; - } - } - return current ?? null; - } - /** * collect nth child * @private @@ -387,14 +343,14 @@ export class Finder { } if (parentNode) { const walker = this.#walker; - let refNode = this._traverse(parentNode, walker); + let refNode = traverseNode(parentNode, walker); refNode = walker.firstChild(); let l = 0; while (refNode) { l++; refNode = walker.nextSibling(); } - refNode = this._traverse(parentNode, walker); + refNode = traverseNode(parentNode, walker); const selectorNodes = new Set(); if (selectorBranches) { refNode = walker.firstChild(); @@ -416,7 +372,7 @@ export class Finder { if (a === 0) { if (b > 0 && b <= l) { if (selectorNodes.size) { - refNode = this._traverse(parentNode, walker); + refNode = traverseNode(parentNode, walker); if (reverse) { refNode = walker.lastChild(); } else { @@ -438,7 +394,7 @@ export class Finder { } } } else if (!selector) { - refNode = this._traverse(parentNode, walker); + refNode = traverseNode(parentNode, walker); if (reverse) { refNode = walker.lastChild(); } else { @@ -468,7 +424,7 @@ export class Finder { } } if (nth >= 0 && nth < l) { - refNode = this._traverse(parentNode, walker); + refNode = traverseNode(parentNode, walker); if (reverse) { refNode = walker.lastChild(); } else { @@ -547,7 +503,7 @@ export class Finder { const matched = new Set(); if (parentNode) { const walker = this.#walker; - let refNode = this._traverse(parentNode, walker); + let refNode = traverseNode(parentNode, walker); refNode = walker.firstChild(); let l = 0; while (refNode) { @@ -557,7 +513,7 @@ export class Finder { // :first-of-type, :last-of-type if (a === 0) { if (b > 0 && b <= l) { - refNode = this._traverse(parentNode, walker); + refNode = traverseNode(parentNode, walker); if (reverse) { refNode = walker.lastChild(); } else { @@ -589,7 +545,7 @@ export class Finder { } } if (nth >= 0 && nth < l) { - refNode = this._traverse(parentNode, walker); + refNode = traverseNode(parentNode, walker); if (reverse) { refNode = walker.lastChild(); } else { @@ -1277,7 +1233,7 @@ export class Finder { } if (form) { const walker = this.#walker; - let nextNode = this._traverse(form, walker); + let nextNode = traverseNode(form, walker); nextNode = walker.firstChild(); while (nextNode && form.contains(nextNode)) { const nodeName = nextNode.localName; @@ -1325,7 +1281,7 @@ export class Finder { } else { const defaultOpt = new Set(); const walker = this.#walker; - let refNode = this._traverse(parentNode, walker); + let refNode = traverseNode(parentNode, walker); refNode = walker.firstChild(); while (refNode) { if (refNode.selected || refNode.hasAttribute('selected')) { @@ -1356,7 +1312,7 @@ export class Finder { } } else if (localName === 'fieldset') { const walker = this.#walker; - let refNode = this._traverse(node, walker); + let refNode = traverseNode(node, walker); refNode = walker.firstChild(); let bool; if (!refNode) { @@ -1397,7 +1353,7 @@ export class Finder { } } else if (localName === 'fieldset') { const walker = this.#walker; - let refNode = this._traverse(node, walker); + let refNode = traverseNode(node, walker); refNode = walker.firstChild(); let bool; if (!refNode) { @@ -1989,7 +1945,7 @@ export class Finder { case '~': { if (parentNode) { const walker = this._createTreeWalker(parentNode); - let refNode = this._traverse(node, walker); + let refNode = traverseNode(node, walker); refNode = walker.nextSibling(); while (refNode) { const bool = this._matchLeaves(leaves, refNode, opt); @@ -2003,7 +1959,7 @@ export class Finder { } case '>': { const walker = this._createTreeWalker(node); - let refNode = this._traverse(node, walker); + let refNode = traverseNode(node, walker); refNode = walker.firstChild(); while (refNode) { const bool = this._matchLeaves(leaves, refNode, opt); @@ -2022,7 +1978,7 @@ export class Finder { } if (pending) { const walker = this._createTreeWalker(node); - let refNode = this._traverse(node, walker); + let refNode = traverseNode(node, walker); refNode = walker.nextNode(); while (refNode && node.contains(refNode)) { const bool = this._matchLeaves(leaves, refNode, opt); @@ -2049,7 +2005,7 @@ export class Finder { case '~': { if (parentNode) { const walker = this._createTreeWalker(parentNode); - let refNode = this._traverse(parentNode, walker); + let refNode = traverseNode(parentNode, walker); refNode = walker.firstChild(); while (refNode) { if (refNode === node) { @@ -2104,7 +2060,7 @@ export class Finder { */ _findNode(leaves, opt) { const { node } = opt; - let refNode = this._traverse(node, this.#qswalker); + let refNode = traverseNode(node, this.#qswalker); let matchedNode; if (refNode) { if (refNode.nodeType !== ELEMENT_NODE) { @@ -2545,7 +2501,7 @@ export class Finder { node = this.#root; walker = this.#walker; } - let nextNode = this._traverse(node, walker); + let nextNode = traverseNode(node, walker); while (nextNode) { let bool = false; if (this.#node.nodeType === ELEMENT_NODE) { @@ -2572,7 +2528,7 @@ export class Finder { } } if (nextNode !== walker.currentNode) { - nextNode = this._traverse(nextNode, walker); + nextNode = traverseNode(nextNode, walker); } nextNode = walker.nextNode(); } @@ -2866,10 +2822,11 @@ export class Finder { } const document = node.ownerDocument; if (document === this.#document && document.contentType === 'text/html') { - if (filterSelector(selector, { + const filterOpt = { complex: REG_COMPLEX_A.test(selector), descendant: true - })) { + }; + if (filterSelector(selector, filterOpt)) { return this.#nwsapi.match(selector, node); } } @@ -2898,10 +2855,11 @@ export class Finder { } const document = node.ownerDocument; if (document === this.#document && document.contentType === 'text/html') { - if (filterSelector(selector, { + const filterOpt = { complex: REG_COMPLEX_A.test(selector), descendant: true - })) { + }; + if (filterSelector(selector, filterOpt)) { return this.#nwsapi.closest(selector, node); } } @@ -2941,10 +2899,11 @@ export class Finder { document = node.ownerDocument; } if (document === this.#document && document.contentType === 'text/html') { - if (filterSelector(selector, { + const filterOpt = { complex: REG_COMPLEX_B.test(selector), descendant: false - })) { + }; + if (filterSelector(selector, filterOpt)) { return this.#nwsapi.first(selector, node); } } @@ -2979,10 +2938,11 @@ export class Finder { document = node.ownerDocument; } if (document === this.#document && document.contentType === 'text/html') { - if (filterSelector(selector, { + const filterOpt = { complex: REG_COMPLEX_B.test(selector), descendant: true - })) { + }; + if (filterSelector(selector, filterOpt)) { return this.#nwsapi.select(selector, node); } } diff --git a/test/dom-util.test.js b/test/dom-util.test.js index 2fea1074..35c15c5f 100644 --- a/test/dom-util.test.js +++ b/test/dom-util.test.js @@ -19,6 +19,14 @@ describe('DOM utility functions', () => {
+
+
+ +
`; const domOpt = { @@ -154,12 +162,76 @@ describe('DOM utility functions', () => { }); }); + describe('traverse node tree', () => { + const func = domUtil.traverseNode; + let treeWalker; + beforeEach(() => { + treeWalker = document.createTreeWalker(document, WALKER_FILTER); + }); + afterEach(() => { + treeWalker = null; + }); + + it('should throw', () => { + assert.throws(() => func(), TypeError, 'Unexpected type Undefined'); + }); + + it('should get null', () => { + const res = func(document); + assert.isNull(res, 'result'); + }); + + it('should get matched node', () => { + const res = func(document, treeWalker); + assert.deepEqual(res, document, 'result'); + }); + + it('should get matched node', () => { + const node = document.getElementById('ul1'); + const res = func(node, treeWalker); + assert.deepEqual(res, node, 'result'); + }); + + it('should get matched node', () => { + const node = document.getElementById('ul1'); + func(document.getElementById('li1'), treeWalker); + const res = func(node, treeWalker); + assert.deepEqual(res, node, 'result'); + }); + + it('should not match', () => { + const node = document.createElement('ol'); + const res = func(node, treeWalker); + assert.isNull(res, null, 'result'); + }); + + it('should get matched node', () => { + const parent = document.createElement('ol'); + const node = document.createElement('li'); + parent.appendChild(node); + const walker = document.createTreeWalker(parent, WALKER_FILTER); + const res = func(node, walker); + assert.deepEqual(res, node, 'result'); + }); + + it('should get matched node', () => { + const frag = document.createDocumentFragment(); + const parent = document.createElement('ol'); + const node = document.createElement('li'); + parent.appendChild(node); + frag.appendChild(parent); + const walker = document.createTreeWalker(frag, WALKER_FILTER); + func(node, walker); + const res = func(frag, walker); + assert.deepEqual(res, frag, 'result'); + }); + }); + describe('is in shadow tree', () => { const func = domUtil.isInShadowTree; - it('should not match', () => { - const res = func(); - assert.isFalse(res, 'result'); + it('should throw', () => { + assert.throws(() => func(), TypeError, 'Unexpected type Undefined'); }); it('should not match', () => { @@ -235,9 +307,8 @@ describe('DOM utility functions', () => { describe('get slotted text content', () => { const func = domUtil.getSlottedTextContent; - it('should get null', () => { - const res = func(); - assert.isNull(res, 'result'); + it('should throw', () => { + assert.throws(() => func(), TypeError, 'Unexpected type Undefined'); }); it('should get null', () => { @@ -315,9 +386,8 @@ describe('DOM utility functions', () => { describe('get directionality of node', () => { const func = domUtil.getDirectionality; - it('should get null', () => { - const res = func(); - assert.isNull(res, 'result'); + it('should throw', () => { + assert.throws(() => func(), TypeError, 'Unexpected type Undefined'); }); it('should get value', () => { @@ -765,9 +835,8 @@ describe('DOM utility functions', () => { describe('is content editable', () => { const func = domUtil.isContentEditable; - it('should get result', () => { - const res = func(); - assert.isFalse(res, 'result'); + it('should throw', () => { + assert.throws(() => func(), TypeError, 'Unexpected type Undefined'); }); it('should get result', () => { @@ -851,9 +920,8 @@ describe('DOM utility functions', () => { describe('get namespace URI', () => { const func = domUtil.getNamespaceURI; - it('should get null', () => { - const res = func(); - assert.isNull(res, 'result'); + it('should throw', () => { + assert.throws(() => func(), TypeError, 'Unexpected type Undefined'); }); it('should get null', () => { @@ -947,10 +1015,18 @@ describe('DOM utility functions', () => { describe('is preceding', () => { const func = domUtil.isPreceding; - it('should get result', () => { + it('should throw', () => { + assert.throws(() => func(), TypeError, 'Unexpected type Undefined'); + }); + + it('should throw', () => { const node = document.documentElement; - const res = func(node); - assert.isFalse(res, 'result'); + assert.throws(() => func(node), TypeError, 'Unexpected type Undefined'); + }); + + it('should throw', () => { + const node = document.documentElement; + assert.throws(() => func(null, node), TypeError, 'Unexpected type Null'); }); it('should get result', () => { diff --git a/test/finder.test.js b/test/finder.test.js index 0ee9f8b6..ae58442a 100644 --- a/test/finder.test.js +++ b/test/finder.test.js @@ -444,61 +444,6 @@ describe('Finder', () => { }); }); - describe('traverse tree walker', () => { - it('should get matched node', () => { - const finder = new Finder(window); - finder._setup('*', document); - const res = finder._traverse(document); - assert.deepEqual(res, document, 'result'); - }); - - it('should get matched node', () => { - const node = document.getElementById('ul1'); - const finder = new Finder(window); - finder._setup('*', document); - const res = finder._traverse(node); - assert.deepEqual(res, node, 'result'); - }); - - it('should get matched node', () => { - const node = document.getElementById('ul1'); - const finder = new Finder(window); - finder._setup('*', document); - const walker = finder._createTreeWalker(document); - finder._traverse(document, walker); - const res = finder._traverse(node, walker); - assert.deepEqual(res, node, 'result'); - }); - - it('should get matched node', () => { - const node = document.getElementById('ul1'); - const finder = new Finder(window); - finder._setup('*', document); - const walker = finder._createTreeWalker(document); - finder._traverse(document.getElementById('li1'), walker); - const res = finder._traverse(node, walker); - assert.deepEqual(res, node, 'result'); - }); - - it('should not match', () => { - const node = document.createElement('ol'); - const finder = new Finder(window); - finder._setup('*', document); - finder._createTreeWalker(document); - const res = finder._traverse(node); - assert.isNull(res, null, 'result'); - }); - - it('should get matched node', () => { - const node = document.createElement('ol'); - const finder = new Finder(window); - finder._setup('*', node); - finder._createTreeWalker(node); - const res = finder._traverse(node); - assert.deepEqual(res, node, 'result'); - }); - }); - describe('collect nth child', () => { it('should not match', () => { const anb = {