From 76cb9c2a39d477a64824a985ade40507e3bbade1 Mon Sep 17 00:00:00 2001 From: Adam Mathes Date: Fri, 13 Feb 2026 21:34:48 -0800 Subject: feat(vanilla): add testing infrastructure and tests (NK-wjnczv) --- .../@asamuzakjp/dom-selector/src/index.js | 353 +++++++++++++++++++++ 1 file changed, 353 insertions(+) create mode 100644 vanilla/node_modules/@asamuzakjp/dom-selector/src/index.js (limited to 'vanilla/node_modules/@asamuzakjp/dom-selector/src/index.js') diff --git a/vanilla/node_modules/@asamuzakjp/dom-selector/src/index.js b/vanilla/node_modules/@asamuzakjp/dom-selector/src/index.js new file mode 100644 index 0000000..8ec7b67 --- /dev/null +++ b/vanilla/node_modules/@asamuzakjp/dom-selector/src/index.js @@ -0,0 +1,353 @@ +/*! + * DOM Selector - A CSS selector engine. + * @license MIT + * @copyright asamuzaK (Kazz) + * @see {@link https://github.com/asamuzaK/domSelector/blob/main/LICENSE} + */ + +/* import */ +import { LRUCache } from 'lru-cache'; +import { Finder } from './js/finder.js'; +import { filterSelector, getType, initNwsapi } from './js/utility.js'; + +/* constants */ +import { + DOCUMENT_NODE, + DOCUMENT_FRAGMENT_NODE, + ELEMENT_NODE, + TARGET_ALL, + TARGET_FIRST, + TARGET_LINEAL, + TARGET_SELF +} from './js/constant.js'; +const MAX_CACHE = 1024; + +/** + * @typedef {object} CheckResult + * @property {boolean} match - The match result. + * @property {string?} pseudoElement - The pseudo-element, if any. + */ + +/* DOMSelector */ +export class DOMSelector { + /* private fields */ + #window; + #document; + #finder; + #idlUtils; + #nwsapi; + #cache; + + /** + * Creates an instance of DOMSelector. + * @param {Window} window - The window object. + * @param {Document} document - The document object. + * @param {object} [opt] - Options. + */ + constructor(window, document, opt = {}) { + const { idlUtils } = opt; + this.#window = window; + this.#document = document ?? window.document; + this.#finder = new Finder(window); + this.#idlUtils = idlUtils; + this.#nwsapi = initNwsapi(window, document); + this.#cache = new LRUCache({ + max: MAX_CACHE + }); + } + + /** + * Clears the internal cache of finder results. + * @returns {void} + */ + clear = () => { + this.#finder.clearResults(true); + }; + + /** + * Checks if an element matches a CSS selector. + * @param {string} selector - The CSS selector to check against. + * @param {Element} node - The element node to check. + * @param {object} [opt] - Optional parameters. + * @returns {CheckResult} An object containing the check result. + */ + check = (selector, node, opt = {}) => { + if (!node?.nodeType) { + const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`); + return this.#finder.onError(e, opt); + } else if (node.nodeType !== ELEMENT_NODE) { + const e = new this.#window.TypeError(`Unexpected node ${node.nodeName}`); + return this.#finder.onError(e, opt); + } + const document = node.ownerDocument; + if ( + document === this.#document && + document.contentType === 'text/html' && + document.documentElement && + node.parentNode + ) { + const cacheKey = `check_${selector}`; + let filterMatches = false; + if (this.#cache.has(cacheKey)) { + filterMatches = this.#cache.get(cacheKey); + } else { + filterMatches = filterSelector(selector, TARGET_SELF); + this.#cache.set(cacheKey, filterMatches); + } + if (filterMatches) { + try { + const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node; + const match = this.#nwsapi.match(selector, n); + return { + match, + pseudoElement: null + }; + } catch (e) { + // fall through + } + } + } + let res; + try { + if (this.#idlUtils) { + node = this.#idlUtils.wrapperForImpl(node); + } + opt.check = true; + opt.noexept = true; + opt.warn = false; + this.#finder.setup(selector, node, opt); + res = this.#finder.find(TARGET_SELF); + } catch (e) { + this.#finder.onError(e, opt); + } + return res; + }; + + /** + * Returns true if the element matches the selector. + * @param {string} selector - The CSS selector to match against. + * @param {Element} node - The element node to test. + * @param {object} [opt] - Optional parameters. + * @returns {boolean} `true` if the element matches, or `false` otherwise. + */ + matches = (selector, node, opt = {}) => { + if (!node?.nodeType) { + const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`); + return this.#finder.onError(e, opt); + } else if (node.nodeType !== ELEMENT_NODE) { + const e = new this.#window.TypeError(`Unexpected node ${node.nodeName}`); + return this.#finder.onError(e, opt); + } + const document = node.ownerDocument; + if ( + document === this.#document && + document.contentType === 'text/html' && + document.documentElement && + node.parentNode + ) { + const cacheKey = `matches_${selector}`; + let filterMatches = false; + if (this.#cache.has(cacheKey)) { + filterMatches = this.#cache.get(cacheKey); + } else { + filterMatches = filterSelector(selector, TARGET_SELF); + this.#cache.set(cacheKey, filterMatches); + } + if (filterMatches) { + try { + const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node; + const res = this.#nwsapi.match(selector, n); + return res; + } catch (e) { + // fall through + } + } + } + let res; + try { + if (this.#idlUtils) { + node = this.#idlUtils.wrapperForImpl(node); + } + this.#finder.setup(selector, node, opt); + const nodes = this.#finder.find(TARGET_SELF); + res = nodes.size; + } catch (e) { + this.#finder.onError(e, opt); + } + return !!res; + }; + + /** + * Traverses up the DOM tree to find the first node that matches the selector. + * @param {string} selector - The CSS selector to match against. + * @param {Element} node - The element from which to start traversing. + * @param {object} [opt] - Optional parameters. + * @returns {?Element} The first matching ancestor element, or `null`. + */ + closest = (selector, node, opt = {}) => { + if (!node?.nodeType) { + const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`); + return this.#finder.onError(e, opt); + } else if (node.nodeType !== ELEMENT_NODE) { + const e = new this.#window.TypeError(`Unexpected node ${node.nodeName}`); + return this.#finder.onError(e, opt); + } + const document = node.ownerDocument; + if ( + document === this.#document && + document.contentType === 'text/html' && + document.documentElement && + node.parentNode + ) { + const cacheKey = `closest_${selector}`; + let filterMatches = false; + if (this.#cache.has(cacheKey)) { + filterMatches = this.#cache.get(cacheKey); + } else { + filterMatches = filterSelector(selector, TARGET_LINEAL); + this.#cache.set(cacheKey, filterMatches); + } + if (filterMatches) { + try { + const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node; + const res = this.#nwsapi.closest(selector, n); + return res; + } catch (e) { + // fall through + } + } + } + let res; + try { + if (this.#idlUtils) { + node = this.#idlUtils.wrapperForImpl(node); + } + this.#finder.setup(selector, node, opt); + const nodes = this.#finder.find(TARGET_LINEAL); + if (nodes.size) { + let refNode = node; + while (refNode) { + if (nodes.has(refNode)) { + res = refNode; + break; + } + refNode = refNode.parentNode; + } + } + } catch (e) { + this.#finder.onError(e, opt); + } + return res ?? null; + }; + + /** + * Returns the first element within the subtree that matches the selector. + * @param {string} selector - The CSS selector to match. + * @param {Document|DocumentFragment|Element} node - The node to find within. + * @param {object} [opt] - Optional parameters. + * @returns {?Element} The first matching element, or `null`. + */ + querySelector = (selector, node, opt = {}) => { + if (!node?.nodeType) { + const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`); + return this.#finder.onError(e, opt); + } + /* + const document = + node.nodeType === DOCUMENT_NODE ? node : node.ownerDocument; + if ( + document === this.#document && + document.contentType === 'text/html' && + document.documentElement && + (node.nodeType !== DOCUMENT_FRAGMENT_NODE || !node.host) + ) { + const cacheKey = `querySelector_${selector}`; + let filterMatches = false; + if (this.#cache.has(cacheKey)) { + filterMatches = this.#cache.get(cacheKey); + } else { + filterMatches = filterSelector(selector, TARGET_FIRST); + this.#cache.set(cacheKey, filterMatches); + } + if (filterMatches) { + try { + const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node; + const res = this.#nwsapi.first(selector, n); + return res; + } catch (e) { + // fall through + } + } + } + */ + let res; + try { + if (this.#idlUtils) { + node = this.#idlUtils.wrapperForImpl(node); + } + this.#finder.setup(selector, node, opt); + const nodes = this.#finder.find(TARGET_FIRST); + if (nodes.size) { + [res] = [...nodes]; + } + } catch (e) { + this.#finder.onError(e, opt); + } + return res ?? null; + }; + + /** + * Returns an array of elements within the subtree that match the selector. + * Note: This method returns an Array, not a NodeList. + * @param {string} selector - The CSS selector to match. + * @param {Document|DocumentFragment|Element} node - The node to find within. + * @param {object} [opt] - Optional parameters. + * @returns {Array} An array of elements, or an empty array. + */ + querySelectorAll = (selector, node, opt = {}) => { + if (!node?.nodeType) { + const e = new this.#window.TypeError(`Unexpected type ${getType(node)}`); + return this.#finder.onError(e, opt); + } + const document = + node.nodeType === DOCUMENT_NODE ? node : node.ownerDocument; + if ( + document === this.#document && + document.contentType === 'text/html' && + document.documentElement && + (node.nodeType !== DOCUMENT_FRAGMENT_NODE || !node.host) + ) { + const cacheKey = `querySelectorAll_${selector}`; + let filterMatches = false; + if (this.#cache.has(cacheKey)) { + filterMatches = this.#cache.get(cacheKey); + } else { + filterMatches = filterSelector(selector, TARGET_ALL); + this.#cache.set(cacheKey, filterMatches); + } + if (filterMatches) { + try { + const n = this.#idlUtils ? this.#idlUtils.wrapperForImpl(node) : node; + const res = this.#nwsapi.select(selector, n); + return res; + } catch (e) { + // fall through + } + } + } + let res; + try { + if (this.#idlUtils) { + node = this.#idlUtils.wrapperForImpl(node); + } + this.#finder.setup(selector, node, opt); + const nodes = this.#finder.find(TARGET_ALL); + if (nodes.size) { + res = [...nodes]; + } + } catch (e) { + this.#finder.onError(e, opt); + } + return res ?? []; + }; +} -- cgit v1.2.3