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/README.md | 324 +++++++++++++++++++++ 1 file changed, 324 insertions(+) create mode 100644 vanilla/node_modules/@asamuzakjp/dom-selector/README.md (limited to 'vanilla/node_modules/@asamuzakjp/dom-selector/README.md') diff --git a/vanilla/node_modules/@asamuzakjp/dom-selector/README.md b/vanilla/node_modules/@asamuzakjp/dom-selector/README.md new file mode 100644 index 0000000..f4f2296 --- /dev/null +++ b/vanilla/node_modules/@asamuzakjp/dom-selector/README.md @@ -0,0 +1,324 @@ +# DOM Selector + +[![build](https://github.com/asamuzaK/domSelector/actions/workflows/node.js.yml/badge.svg)](https://github.com/asamuzaK/domSelector/actions/workflows/node.js.yml) +[![CodeQL](https://github.com/asamuzaK/domSelector/actions/workflows/github-code-scanning/codeql/badge.svg)](https://github.com/asamuzaK/domSelector/actions/workflows/github-code-scanning/codeql) +[![npm (scoped)](https://img.shields.io/npm/v/@asamuzakjp/dom-selector)](https://www.npmjs.com/package/@asamuzakjp/dom-selector) + +A CSS selector engine. + +## Install + +```console +npm i @asamuzakjp/dom-selector +``` + +## Usage + +```javascript +import { DOMSelector } from '@asamuzakjp/dom-selector'; +import { JSDOM } from 'jsdom'; + +const { window } = new JSDOM(); +const { + closest, matches, querySelector, querySelectorAll +} = new DOMSelector(window); +``` + + + +### matches(selector, node, opt) + +matches - equivalent to [Element.matches()][64] + +#### Parameters + +- `selector` **[string][59]** CSS selector +- `node` **[object][60]** Element node +- `opt` **[object][60]?** options + - `opt.noexcept` **[boolean][61]?** no exception + - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class + +Returns **[boolean][61]** `true` if matched, `false` otherwise + + +### closest(selector, node, opt) + +closest - equivalent to [Element.closest()][65] + +#### Parameters + +- `selector` **[string][59]** CSS selector +- `node` **[object][60]** Element node +- `opt` **[object][60]?** options + - `opt.noexcept` **[boolean][61]?** no exception + - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class + +Returns **[object][60]?** matched node + + +### querySelector(selector, node, opt) + +querySelector - equivalent to [Document.querySelector()][66], [DocumentFragment.querySelector()][67] and [Element.querySelector()][68] + +#### Parameters + +- `selector` **[string][59]** CSS selector +- `node` **[object][60]** Document, DocumentFragment or Element node +- `opt` **[object][60]?** options + - `opt.noexcept` **[boolean][61]?** no exception + - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class + +Returns **[object][60]?** matched node + + +### querySelectorAll(selector, node, opt) + +querySelectorAll - equivalent to [Document.querySelectorAll()][69], [DocumentFragment.querySelectorAll()][70] and [Element.querySelectorAll()][71] +**NOTE**: returns Array, not NodeList + +#### Parameters + +- `selector` **[string][59]** CSS selector +- `node` **[object][60]** Document, DocumentFragment or Element node +- `opt` **[object][60]?** options + - `opt.noexcept` **[boolean][61]?** no exception + - `opt.warn` **[boolean][61]?** console warn e.g. unsupported pseudo-class + +Returns **[Array][62]<([object][60] \| [undefined][63])>** array of matched nodes + + +## Monkey patch jsdom + +``` javascript +import { DOMSelector } from '@asamuzakjp/dom-selector'; +import { JSDOM } from 'jsdom'; + +const dom = new JSDOM('', { + runScripts: 'dangerously', + url: 'http://localhost/', + beforeParse: window => { + const domSelector = new DOMSelector(window); + + const matches = domSelector.matches.bind(domSelector); + window.Element.prototype.matches = function (...args) { + if (!args.length) { + throw new window.TypeError('1 argument required, but only 0 present.'); + } + const [selector] = args; + return matches(selector, this); + }; + + const closest = domSelector.closest.bind(domSelector); + window.Element.prototype.closest = function (...args) { + if (!args.length) { + throw new window.TypeError('1 argument required, but only 0 present.'); + } + const [selector] = args; + return closest(selector, this); + }; + + const querySelector = domSelector.querySelector.bind(domSelector); + window.Document.prototype.querySelector = function (...args) { + if (!args.length) { + throw new window.TypeError('1 argument required, but only 0 present.'); + } + const [selector] = args; + return querySelector(selector, this); + }; + window.DocumentFragment.prototype.querySelector = function (...args) { + if (!args.length) { + throw new window.TypeError('1 argument required, but only 0 present.'); + } + const [selector] = args; + return querySelector(selector, this); + }; + window.Element.prototype.querySelector = function (...args) { + if (!args.length) { + throw new window.TypeError('1 argument required, but only 0 present.'); + } + const [selector] = args; + return querySelector(selector, this); + }; + + const querySelectorAll = domSelector.querySelectorAll.bind(domSelector); + window.Document.prototype.querySelectorAll = function (...args) { + if (!args.length) { + throw new window.TypeError('1 argument required, but only 0 present.'); + } + const [selector] = args; + return querySelectorAll(selector, this); + }; + window.DocumentFragment.prototype.querySelectorAll = function (...args) { + if (!args.length) { + throw new window.TypeError('1 argument required, but only 0 present.'); + } + const [selector] = args; + return querySelectorAll(selector, this); + }; + window.Element.prototype.querySelectorAll = function (...args) { + if (!args.length) { + throw new window.TypeError('1 argument required, but only 0 present.'); + } + const [selector] = args; + return querySelectorAll(selector, this); + }; + } +}); +``` + + +## Supported CSS selectors + +|Pattern|Supported|Note| +|:--------|:-------:|:--------| +|\*|✓| | +|E|✓| | +|ns\|E|✓| | +|\*\|E|✓| | +|\|E|✓| | +|E F|✓| | +|E > F|✓| | +|E + F|✓| | +|E ~ F|✓| | +|F \|\| E|Unsupported| | +|E.warning|✓| | +|E#myid|✓| | +|E\[foo\]|✓| | +|E\[foo="bar"\]|✓| | +|E\[foo="bar" i\]|✓| | +|E\[foo="bar" s\]|✓| | +|E\[foo~="bar"\]|✓| | +|E\[foo^="bar"\]|✓| | +|E\[foo$="bar"\]|✓| | +|E\[foo*="bar"\]|✓| | +|E\[foo\|="en"\]|✓| | +|E:is(s1, s2, …)|✓| | +|E:not(s1, s2, …)|✓| | +|E:where(s1, s2, …)|✓| | +|E:has(rs1, rs2, …)|✓| | +|E:defined|Partially supported|Matching with MathML is not yet supported.| +|E:dir(ltr)|✓| | +|E:lang(en)|✓| | +|E:any‑link|✓| | +|E:link|✓| | +|E:visited|✓|Returns `false` or `null` to prevent fingerprinting.| +|E:local‑link|✓| | +|E:target|✓| | +|E:target‑within|✓| | +|E:scope|✓| | +|E:hover|✓| | +|E:active|✓| | +|E:focus|✓| | +|E:focus‑visible|✓| | +|E:focus‑within|✓| | +|E:current|Unsupported| | +|E:current(s)|Unsupported| | +|E:past|Unsupported| | +|E:future|Unsupported| | +|E:open
E:closed|Partially supported|Matching with <select>, e.g. `select:open`, is not supported.| +|E:popover-open|✓| | +|E:enabled
E:disabled|✓| | +|E:read‑write
E:read‑only|✓| | +|E:placeholder‑shown|✓| | +|E:default|✓| | +|E:checked|✓| | +|E:indeterminate|✓| | +|E:blank|Unsupported| | +|E:valid
E:invalid|✓| | +|E:in-range
E:out-of-range|✓| | +|E:required
E:optional|✓| | +|E:user‑valid
E:user‑invalid|Unsupported| | +|E:root|✓| | +|E:empty|✓| | +|E:nth‑child(n [of S]?)|✓| | +|E:nth‑last‑child(n [of S]?)|✓| | +|E:first‑child|✓| | +|E:last‑child|✓| | +|E:only‑child|✓| | +|E:nth‑of‑type(n)|✓| | +|E:nth‑last‑of‑type(n)|✓| | +|E:first‑of‑type|✓| | +|E:last‑of‑type|✓| | +|E:only‑of‑type|✓| | +|E:nth‑col(n)|Unsupported| | +|E:nth‑last‑col(n)|Unsupported| | +|CE:state(v)|✓|*1| +|:host|✓| | +|:host(s)|✓| | +|:host(:state(v))|✓|*1| +|:host:has(rs1, rs2, ...)|✓| | +|:host(s):has(rs1, rs2, ...)|✓| | +|:host‑context(s)|✓| | +|:host‑context(s):has(rs1, rs2, ...)|✓| | +|&|✓|Only supports outermost `&`, i.e. equivalent to `:scope`| + +*1: `ElementInternals.states`, i.e. `CustomStateSet`, is not implemented in jsdom, so you need to apply a patch in the custom element constructor. + +``` javascript +class LabeledCheckbox extends window.HTMLElement { + #internals; + constructor() { + super(); + this.#internals = this.attachInternals(); + // patch CustomStateSet + if (!this.#internals.states) { + this.#internals.states = new Set(); + } + this.addEventListener('click', this._onClick.bind(this)); + } + get checked() { + return this.#internals.states.has('checked'); + } + set checked(flag) { + if (flag) { + this.#internals.states.add('checked'); + } else { + this.#internals.states.delete('checked'); + } + } + _onClick(event) { + this.checked = !this.checked; + } +} +``` + + +## Performance + +See [benchmark](https://github.com/asamuzaK/domSelector/actions/workflows/benchmark.yml) for the latest results. + + +## Acknowledgments + +The following resources have been of great help in the development of the DOM Selector. + +- [CSSTree](https://github.com/csstree/csstree) +- [selery](https://github.com/danburzo/selery) +- [jsdom](https://github.com/jsdom/jsdom) +- [nwsapi](https://github.com/dperini/nwsapi) + +--- +Copyright (c) 2023 [asamuzaK (Kazz)](https://github.com/asamuzaK/) + + +[1]: #matches +[2]: #parameters +[3]: #closest +[4]: #parameters-1 +[5]: #queryselector +[6]: #parameters-2 +[7]: #queryselectorall +[8]: #parameters-3 +[59]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String +[60]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object +[61]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean +[62]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array +[63]: https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/undefined +[64]: https://developer.mozilla.org/docs/Web/API/Element/matches +[65]: https://developer.mozilla.org/docs/Web/API/Element/closest +[66]: https://developer.mozilla.org/docs/Web/API/Document/querySelector +[67]: https://developer.mozilla.org/docs/Web/API/DocumentFragment/querySelector +[68]: https://developer.mozilla.org/docs/Web/API/Element/querySelector +[69]: https://developer.mozilla.org/docs/Web/API/Document/querySelectorAll +[70]: https://developer.mozilla.org/docs/Web/API/DocumentFragment/querySelectorAll +[71]: https://developer.mozilla.org/docs/Web/API/Element/querySelectorAll -- cgit v1.2.3