@remote-dom/polyfill 1.5.0 → 1.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +94 -0
- package/README.md +51 -3
- package/build/cjs/Attr.cjs +33 -4
- package/build/cjs/CharacterData.cjs +10 -0
- package/build/cjs/ChildNode.cjs +79 -13
- package/build/cjs/Comment.cjs +1 -1
- package/build/cjs/CustomElementRegistry.cjs +27 -2
- package/build/cjs/CustomEvent.cjs +2 -2
- package/build/cjs/Document.cjs +56 -32
- package/build/cjs/DocumentFragment.cjs +6 -2
- package/build/cjs/Element.cjs +68 -8
- package/build/cjs/Event.cjs +29 -27
- package/build/cjs/EventTarget.cjs +128 -58
- package/build/cjs/HTMLTemplateElement.cjs +1 -0
- package/build/cjs/MutationObserver.cjs +167 -1
- package/build/cjs/NamedNodeMap.cjs +82 -45
- package/build/cjs/Node.cjs +47 -11
- package/build/cjs/NodeList.cjs +9 -1
- package/build/cjs/ParentNode.cjs +319 -77
- package/build/cjs/SVGElement.cjs +1 -1
- package/build/cjs/Text.cjs +1 -1
- package/build/cjs/Window.cjs +131 -38
- package/build/cjs/attribute-reactions.cjs +20 -0
- package/build/cjs/constants.cjs +48 -24
- package/build/cjs/custom-element-reactions.cjs +52 -0
- package/build/cjs/dom-exception.cjs +15 -0
- package/build/cjs/hook-effects.cjs +51 -0
- package/build/cjs/names.cjs +61 -0
- package/build/cjs/selectors.cjs +213 -122
- package/build/cjs/serialization.cjs +168 -65
- package/build/cjs/shared.cjs +177 -28
- package/build/esm/Attr.mjs +34 -5
- package/build/esm/CharacterData.mjs +10 -0
- package/build/esm/ChildNode.mjs +76 -15
- package/build/esm/Comment.mjs +2 -2
- package/build/esm/CustomElementRegistry.mjs +27 -2
- package/build/esm/CustomEvent.mjs +2 -2
- package/build/esm/Document.mjs +60 -35
- package/build/esm/DocumentFragment.mjs +7 -3
- package/build/esm/Element.mjs +69 -9
- package/build/esm/Event.mjs +27 -27
- package/build/esm/EventTarget.mjs +130 -60
- package/build/esm/HTMLTemplateElement.mjs +2 -1
- package/build/esm/MutationObserver.mjs +166 -2
- package/build/esm/NamedNodeMap.mjs +83 -46
- package/build/esm/Node.mjs +48 -12
- package/build/esm/NodeList.mjs +9 -1
- package/build/esm/ParentNode.mjs +322 -81
- package/build/esm/SVGElement.mjs +2 -2
- package/build/esm/Text.mjs +2 -2
- package/build/esm/Window.mjs +132 -39
- package/build/esm/attribute-reactions.mjs +16 -0
- package/build/esm/constants.mjs +28 -23
- package/build/esm/custom-element-reactions.mjs +47 -0
- package/build/esm/dom-exception.mjs +11 -0
- package/build/esm/hook-effects.mjs +46 -0
- package/build/esm/names.mjs +54 -0
- package/build/esm/selectors.mjs +202 -124
- package/build/esm/serialization.mjs +169 -66
- package/build/esm/shared.mjs +173 -30
- package/build/esnext/Attr.esnext +45 -5
- package/build/esnext/CharacterData.esnext +10 -0
- package/build/esnext/ChildNode.esnext +84 -15
- package/build/esnext/Comment.esnext +2 -2
- package/build/esnext/CustomElementRegistry.esnext +42 -0
- package/build/esnext/CustomEvent.esnext +2 -2
- package/build/esnext/Document.esnext +69 -32
- package/build/esnext/DocumentFragment.esnext +7 -3
- package/build/esnext/Element.esnext +78 -9
- package/build/esnext/Event.esnext +36 -37
- package/build/esnext/EventTarget.esnext +133 -61
- package/build/esnext/HTMLTemplateElement.esnext +3 -1
- package/build/esnext/MutationObserver.esnext +163 -2
- package/build/esnext/NamedNodeMap.esnext +110 -42
- package/build/esnext/Node.esnext +50 -12
- package/build/esnext/NodeList.esnext +1 -1
- package/build/esnext/ParentNode.esnext +352 -79
- package/build/esnext/SVGElement.esnext +2 -2
- package/build/esnext/Text.esnext +2 -2
- package/build/esnext/Window.esnext +120 -16
- package/build/esnext/attribute-reactions.esnext +21 -0
- package/build/esnext/constants.esnext +34 -21
- package/build/esnext/custom-element-reactions.esnext +49 -0
- package/build/esnext/dom-exception.esnext +11 -0
- package/build/esnext/hook-effects.esnext +46 -0
- package/build/esnext/names.esnext +61 -0
- package/build/esnext/selectors.esnext +202 -100
- package/build/esnext/serialization.esnext +171 -48
- package/build/esnext/shared.esnext +181 -34
- package/build/tsconfig.tsbuildinfo +1 -1
- package/build/typescript/Attr.d.ts +6 -4
- package/build/typescript/Attr.d.ts.map +1 -1
- package/build/typescript/CharacterData.d.ts.map +1 -1
- package/build/typescript/ChildNode.d.ts +5 -0
- package/build/typescript/ChildNode.d.ts.map +1 -1
- package/build/typescript/ClipboardEvent.d.ts.map +1 -1
- package/build/typescript/Comment.d.ts +2 -2
- package/build/typescript/Comment.d.ts.map +1 -1
- package/build/typescript/CustomElementRegistry.d.ts +1 -0
- package/build/typescript/CustomElementRegistry.d.ts.map +1 -1
- package/build/typescript/CustomEvent.d.ts.map +1 -1
- package/build/typescript/Document.d.ts +10 -5
- package/build/typescript/Document.d.ts.map +1 -1
- package/build/typescript/DocumentFragment.d.ts +2 -1
- package/build/typescript/DocumentFragment.d.ts.map +1 -1
- package/build/typescript/Element.d.ts +16 -7
- package/build/typescript/Element.d.ts.map +1 -1
- package/build/typescript/ErrorEvent.d.ts.map +1 -1
- package/build/typescript/Event.d.ts +9 -10
- package/build/typescript/Event.d.ts.map +1 -1
- package/build/typescript/EventTarget.d.ts +20 -3
- package/build/typescript/EventTarget.d.ts.map +1 -1
- package/build/typescript/FocusEvent.d.ts.map +1 -1
- package/build/typescript/HTMLTemplateElement.d.ts.map +1 -1
- package/build/typescript/MutationObserver.d.ts +42 -0
- package/build/typescript/MutationObserver.d.ts.map +1 -1
- package/build/typescript/NamedNodeMap.d.ts +5 -3
- package/build/typescript/NamedNodeMap.d.ts.map +1 -1
- package/build/typescript/Node.d.ts +4 -3
- package/build/typescript/Node.d.ts.map +1 -1
- package/build/typescript/NodeList.d.ts +3 -2
- package/build/typescript/NodeList.d.ts.map +1 -1
- package/build/typescript/ParentNode.d.ts +29 -10
- package/build/typescript/ParentNode.d.ts.map +1 -1
- package/build/typescript/PromiseRejectionEvent.d.ts.map +1 -1
- package/build/typescript/SVGElement.d.ts +1 -1
- package/build/typescript/SVGElement.d.ts.map +1 -1
- package/build/typescript/Text.d.ts +1 -1
- package/build/typescript/Text.d.ts.map +1 -1
- package/build/typescript/ToggleEvent.d.ts.map +1 -1
- package/build/typescript/Window.d.ts +5 -1
- package/build/typescript/Window.d.ts.map +1 -1
- package/build/typescript/attribute-reactions.d.ts +4 -0
- package/build/typescript/attribute-reactions.d.ts.map +1 -0
- package/build/typescript/constants.d.ts +27 -18
- package/build/typescript/constants.d.ts.map +1 -1
- package/build/typescript/custom-element-reactions.d.ts +6 -0
- package/build/typescript/custom-element-reactions.d.ts.map +1 -0
- package/build/typescript/dom-exception.d.ts +2 -0
- package/build/typescript/dom-exception.d.ts.map +1 -0
- package/build/typescript/extensions.d.ts +4 -0
- package/build/typescript/extensions.d.ts.map +1 -0
- package/build/typescript/hook-effects.d.ts +6 -0
- package/build/typescript/hook-effects.d.ts.map +1 -0
- package/build/typescript/hooks.d.ts +3 -3
- package/build/typescript/hooks.d.ts.map +1 -1
- package/build/typescript/index.d.ts +1 -0
- package/build/typescript/index.d.ts.map +1 -1
- package/build/typescript/names.d.ts +11 -0
- package/build/typescript/names.d.ts.map +1 -0
- package/build/typescript/selectors.d.ts +21 -22
- package/build/typescript/selectors.d.ts.map +1 -1
- package/build/typescript/serialization.d.ts.map +1 -1
- package/build/typescript/shared.d.ts +10 -0
- package/build/typescript/shared.d.ts.map +1 -1
- package/build/typescript/tests/ChildNode.test.d.ts +2 -0
- package/build/typescript/tests/ChildNode.test.d.ts.map +1 -0
- package/build/typescript/tests/MutationObserver.test.d.ts +2 -0
- package/build/typescript/tests/MutationObserver.test.d.ts.map +1 -0
- package/build/typescript/tests/adopt-inserted-subtrees.test.d.ts +2 -0
- package/build/typescript/tests/adopt-inserted-subtrees.test.d.ts.map +1 -0
- package/build/typescript/tests/attr-mutation.test.d.ts +2 -0
- package/build/typescript/tests/attr-mutation.test.d.ts.map +1 -0
- package/build/typescript/tests/child-node-before-after.test.d.ts +2 -0
- package/build/typescript/tests/child-node-before-after.test.d.ts.map +1 -0
- package/build/typescript/tests/child-node-replace-with.test.d.ts +2 -0
- package/build/typescript/tests/child-node-replace-with.test.d.ts.map +1 -0
- package/build/typescript/tests/clone-import-namespaces.test.d.ts +2 -0
- package/build/typescript/tests/clone-import-namespaces.test.d.ts.map +1 -0
- package/build/typescript/tests/constants.test.d.ts +2 -0
- package/build/typescript/tests/constants.test.d.ts.map +1 -0
- package/build/typescript/tests/create-element-namespaces.test.d.ts +2 -0
- package/build/typescript/tests/create-element-namespaces.test.d.ts.map +1 -0
- package/build/typescript/tests/create-text-node.test.d.ts +2 -0
- package/build/typescript/tests/create-text-node.test.d.ts.map +1 -0
- package/build/typescript/tests/custom-element-reactions.test.d.ts +2 -0
- package/build/typescript/tests/custom-element-reactions.test.d.ts.map +1 -0
- package/build/typescript/tests/custom-element-registry.test.d.ts +2 -0
- package/build/typescript/tests/custom-element-registry.test.d.ts.map +1 -0
- package/build/typescript/tests/custom-event.test.d.ts +2 -0
- package/build/typescript/tests/custom-event.test.d.ts.map +1 -0
- package/build/typescript/tests/document-clone-import-contract.test.d.ts +2 -0
- package/build/typescript/tests/document-clone-import-contract.test.d.ts.map +1 -0
- package/build/typescript/tests/document.test.d.ts +2 -0
- package/build/typescript/tests/document.test.d.ts.map +1 -0
- package/build/typescript/tests/dom-mutation-selector-errors.test.d.ts +2 -0
- package/build/typescript/tests/dom-mutation-selector-errors.test.d.ts.map +1 -0
- package/build/typescript/tests/event-dispatch.test.d.ts +2 -0
- package/build/typescript/tests/event-dispatch.test.d.ts.map +1 -0
- package/build/typescript/tests/event-target.test.d.ts +2 -0
- package/build/typescript/tests/event-target.test.d.ts.map +1 -0
- package/build/typescript/tests/event.test.d.ts +2 -0
- package/build/typescript/tests/event.test.d.ts.map +1 -0
- package/build/typescript/tests/extensions.test.d.ts +2 -0
- package/build/typescript/tests/extensions.test.d.ts.map +1 -0
- package/build/typescript/tests/get-elements-by-tag-name.test.d.ts +2 -0
- package/build/typescript/tests/get-elements-by-tag-name.test.d.ts.map +1 -0
- package/build/typescript/tests/getElementsByClassName.test.d.ts +2 -0
- package/build/typescript/tests/getElementsByClassName.test.d.ts.map +1 -0
- package/build/typescript/tests/hooks.test.d.ts +2 -0
- package/build/typescript/tests/hooks.test.d.ts.map +1 -0
- package/build/typescript/tests/inner-html-parsing.test.d.ts +2 -0
- package/build/typescript/tests/inner-html-parsing.test.d.ts.map +1 -0
- package/build/typescript/tests/is-default-namespace.test.d.ts +2 -0
- package/build/typescript/tests/is-default-namespace.test.d.ts.map +1 -0
- package/build/typescript/tests/name-and-namespace-normalization.test.d.ts +2 -0
- package/build/typescript/tests/name-and-namespace-normalization.test.d.ts.map +1 -0
- package/build/typescript/tests/named-node-map.test.d.ts +2 -0
- package/build/typescript/tests/named-node-map.test.d.ts.map +1 -0
- package/build/typescript/tests/namespaced-attribute-reactions.test.d.ts +2 -0
- package/build/typescript/tests/namespaced-attribute-reactions.test.d.ts.map +1 -0
- package/build/typescript/tests/node.test.d.ts +2 -0
- package/build/typescript/tests/node.test.d.ts.map +1 -0
- package/build/typescript/tests/parent-node-mutation-return-values.test.d.ts +2 -0
- package/build/typescript/tests/parent-node-mutation-return-values.test.d.ts.map +1 -0
- package/build/typescript/tests/parent-node-mutation-validation.test.d.ts +2 -0
- package/build/typescript/tests/parent-node-mutation-validation.test.d.ts.map +1 -0
- package/build/typescript/tests/parent-node.test.d.ts +2 -0
- package/build/typescript/tests/parent-node.test.d.ts.map +1 -0
- package/build/typescript/tests/serialization-template.test.d.ts +2 -0
- package/build/typescript/tests/serialization-template.test.d.ts.map +1 -0
- package/build/typescript/tests/slot-attribute.test.d.ts +2 -0
- package/build/typescript/tests/slot-attribute.test.d.ts.map +1 -0
- package/build/typescript/tests/text-content.test.d.ts +2 -0
- package/build/typescript/tests/text-content.test.d.ts.map +1 -0
- package/build/typescript/tests/toggle-attribute.test.d.ts +2 -0
- package/build/typescript/tests/toggle-attribute.test.d.ts.map +1 -0
- package/build/typescript/tests/wide-tree-traversal.test.d.ts +2 -0
- package/build/typescript/tests/wide-tree-traversal.test.d.ts.map +1 -0
- package/build/typescript/tests/window-set-global.test.d.ts +2 -0
- package/build/typescript/tests/window-set-global.test.d.ts.map +1 -0
- package/package.json +1 -1
- package/source/Attr.ts +60 -8
- package/source/CharacterData.ts +13 -0
- package/source/ChildNode.ts +107 -14
- package/source/Comment.ts +2 -2
- package/source/CustomElementRegistry.ts +56 -0
- package/source/CustomEvent.ts +2 -2
- package/source/Document.ts +104 -38
- package/source/DocumentFragment.ts +15 -3
- package/source/Element.ts +129 -14
- package/source/Event.ts +34 -32
- package/source/EventTarget.ts +203 -79
- package/source/HTMLTemplateElement.ts +2 -1
- package/source/MutationObserver.ts +279 -1
- package/source/NamedNodeMap.ts +141 -60
- package/source/Node.ts +70 -14
- package/source/NodeList.ts +13 -3
- package/source/ParentNode.ts +500 -88
- package/source/SVGElement.ts +2 -2
- package/source/Text.ts +2 -2
- package/source/Window.ts +156 -17
- package/source/attribute-reactions.ts +32 -0
- package/source/constants.ts +40 -17
- package/source/custom-element-reactions.ts +64 -0
- package/source/dom-exception.ts +14 -0
- package/source/extensions.ts +4 -0
- package/source/hook-effects.ts +56 -0
- package/source/hooks.ts +11 -3
- package/source/index.ts +1 -0
- package/source/names.ts +94 -0
- package/source/selectors.ts +301 -138
- package/source/serialization.ts +224 -66
- package/source/shared.ts +269 -40
- package/source/tests/ChildNode.test.ts +178 -0
- package/source/tests/MutationObserver.test.ts +303 -0
- package/source/tests/adopt-inserted-subtrees.test.ts +584 -0
- package/source/tests/attr-mutation.test.ts +112 -0
- package/source/tests/child-node-before-after.test.ts +421 -0
- package/source/tests/child-node-replace-with.test.ts +251 -0
- package/source/tests/clone-import-namespaces.test.ts +365 -0
- package/source/tests/constants.test.ts +46 -0
- package/source/tests/create-element-namespaces.test.ts +142 -0
- package/source/tests/create-text-node.test.ts +38 -0
- package/source/tests/custom-element-reactions.test.ts +600 -0
- package/source/tests/custom-element-registry.test.ts +137 -0
- package/source/tests/custom-event.test.ts +26 -0
- package/source/tests/document-clone-import-contract.test.ts +77 -0
- package/source/tests/document.test.ts +122 -0
- package/source/tests/dom-mutation-selector-errors.test.ts +484 -0
- package/source/tests/event-dispatch.test.ts +270 -0
- package/source/tests/event-target.test.ts +208 -0
- package/source/tests/event.test.ts +98 -0
- package/source/tests/extensions.test.ts +159 -0
- package/source/tests/get-elements-by-tag-name.test.ts +79 -0
- package/source/tests/getElementsByClassName.test.ts +109 -0
- package/source/tests/hooks.test.ts +43 -0
- package/source/tests/inner-html-parsing.test.ts +461 -0
- package/source/tests/is-default-namespace.test.ts +111 -0
- package/source/tests/name-and-namespace-normalization.test.ts +319 -0
- package/source/tests/named-node-map.test.ts +122 -0
- package/source/tests/namespaced-attribute-reactions.test.ts +141 -0
- package/source/tests/node.test.ts +56 -0
- package/source/tests/parent-node-mutation-return-values.test.ts +63 -0
- package/source/tests/parent-node-mutation-validation.test.ts +362 -0
- package/source/tests/parent-node.test.ts +169 -0
- package/source/tests/selectors.test.ts +533 -22
- package/source/tests/serialization-template.test.ts +232 -0
- package/source/tests/slot-attribute.test.ts +133 -0
- package/source/tests/text-content.test.ts +440 -0
- package/source/tests/toggle-attribute.test.ts +113 -0
- package/source/tests/wide-tree-traversal.test.ts +699 -0
- package/source/tests/window-set-global.test.ts +321 -0
- package/tsconfig.json +1 -1
|
@@ -1,7 +1,33 @@
|
|
|
1
1
|
import {Window} from '../index.ts';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
import {
|
|
2
|
+
import {NodeList} from '../NodeList.ts';
|
|
3
|
+
import type {Element as PolyfillElement} from '../Element.ts';
|
|
4
|
+
import {
|
|
5
|
+
MATCHER_CLASS,
|
|
6
|
+
MATCHER_ELEMENT,
|
|
7
|
+
MATCHER_ID,
|
|
8
|
+
MATCHER_UNKNOWN,
|
|
9
|
+
parseSelector,
|
|
10
|
+
querySelector,
|
|
11
|
+
querySelectorAll,
|
|
12
|
+
} from '../selectors.ts';
|
|
13
|
+
import type {ParentNode} from '../ParentNode.ts';
|
|
14
|
+
|
|
15
|
+
const MatcherType = {
|
|
16
|
+
Unknown: MATCHER_UNKNOWN,
|
|
17
|
+
Element: MATCHER_ELEMENT,
|
|
18
|
+
Id: MATCHER_ID,
|
|
19
|
+
Class: MATCHER_CLASS,
|
|
20
|
+
} as const;
|
|
21
|
+
|
|
22
|
+
const ASCII_WHITESPACE = [
|
|
23
|
+
['SPACE', ' '],
|
|
24
|
+
['TAB', '\t'],
|
|
25
|
+
['LINE FEED', '\n'],
|
|
26
|
+
['FORM FEED', '\f'],
|
|
27
|
+
['CARRIAGE RETURN', '\r'],
|
|
28
|
+
] as const;
|
|
29
|
+
|
|
30
|
+
import {describe, it, expect, expectTypeOf, beforeEach} from 'vitest';
|
|
5
31
|
|
|
6
32
|
describe('selector parsing and matching', () => {
|
|
7
33
|
beforeEach(() => {
|
|
@@ -41,6 +67,19 @@ describe('selector parsing and matching', () => {
|
|
|
41
67
|
});
|
|
42
68
|
});
|
|
43
69
|
|
|
70
|
+
it('keeps non-ASCII whitespace in class selector names', () => {
|
|
71
|
+
const parts = parseSelector('.left\u00a0right');
|
|
72
|
+
|
|
73
|
+
expect(parts).toHaveLength(1);
|
|
74
|
+
expect(parts[0]!.matchers).toEqual([
|
|
75
|
+
{
|
|
76
|
+
type: MatcherType.Class,
|
|
77
|
+
name: 'left\u00a0right',
|
|
78
|
+
value: 'left\u00a0right',
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
81
|
+
});
|
|
82
|
+
|
|
44
83
|
it('parses attribute selectors without values', () => {
|
|
45
84
|
const parts = parseSelector('[disabled]');
|
|
46
85
|
expect(parts).toHaveLength(1);
|
|
@@ -61,14 +100,10 @@ describe('selector parsing and matching', () => {
|
|
|
61
100
|
});
|
|
62
101
|
});
|
|
63
102
|
|
|
64
|
-
it('
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
type: 5, // MatcherType.Pseudo
|
|
69
|
-
name: 'hover',
|
|
70
|
-
value: undefined,
|
|
71
|
-
});
|
|
103
|
+
it('rejects unsupported pseudo-class selectors', () => {
|
|
104
|
+
expect(() => parseSelector(':hover')).toThrowError(
|
|
105
|
+
expect.objectContaining({name: 'SyntaxError'}),
|
|
106
|
+
);
|
|
72
107
|
});
|
|
73
108
|
|
|
74
109
|
it('parses function selectors', () => {
|
|
@@ -91,6 +126,36 @@ describe('selector parsing and matching', () => {
|
|
|
91
126
|
});
|
|
92
127
|
});
|
|
93
128
|
|
|
129
|
+
it.each([
|
|
130
|
+
[':not(:has(.missing))', ':has(.missing)'],
|
|
131
|
+
[':has(span:not(.missing))', 'span:not(.missing)'],
|
|
132
|
+
[':has(> .hit)', '> .hit'],
|
|
133
|
+
[':has([data-label=")value("])', '[data-label=")value("]'],
|
|
134
|
+
])('parses the balanced argument in %s', (selector, value) => {
|
|
135
|
+
expect(parseSelector(selector)[0]!.matchers[0]!.value).toBe(value);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it.each([':has(:has(.active))', ':has(:not(:has(.active)))'])(
|
|
139
|
+
'rejects nested :has() in %s',
|
|
140
|
+
(selector) => {
|
|
141
|
+
expect(() => parseSelector(selector)).toThrow();
|
|
142
|
+
},
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
it.each([
|
|
146
|
+
[':HAS(div)', 6, 'has', 'div'],
|
|
147
|
+
[':Not(.Hidden)', 6, 'not', '.Hidden'],
|
|
148
|
+
])(
|
|
149
|
+
'ASCII-lowercases only the pseudo-class name in %s',
|
|
150
|
+
(selector, type, name, value) => {
|
|
151
|
+
expect(parseSelector(selector)[0]!.matchers[0]!).toMatchObject({
|
|
152
|
+
type,
|
|
153
|
+
name,
|
|
154
|
+
value,
|
|
155
|
+
});
|
|
156
|
+
},
|
|
157
|
+
);
|
|
158
|
+
|
|
94
159
|
it('parses compound selectors', () => {
|
|
95
160
|
const parts = parseSelector('div.myclass#myid[type="button"]');
|
|
96
161
|
expect(parts).toHaveLength(1);
|
|
@@ -109,13 +174,16 @@ describe('selector parsing and matching', () => {
|
|
|
109
174
|
expect(parts[1]!.matchers[0]!.name).toBe('span');
|
|
110
175
|
});
|
|
111
176
|
|
|
112
|
-
it(
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
177
|
+
it.each(ASCII_WHITESPACE)(
|
|
178
|
+
'parses %s as a descendant combinator',
|
|
179
|
+
(_name, whitespace) => {
|
|
180
|
+
const parts = parseSelector(`div${whitespace}span`);
|
|
181
|
+
expect(parts).toHaveLength(2);
|
|
182
|
+
expect(parts[0]!.combinator).toBe(0); // Combinator.Descendant
|
|
183
|
+
expect(parts[0]!.matchers[0]!.name).toBe('div');
|
|
184
|
+
expect(parts[1]!.matchers[0]!.name).toBe('span');
|
|
185
|
+
},
|
|
186
|
+
);
|
|
119
187
|
|
|
120
188
|
it('parses adjacent sibling combinator', () => {
|
|
121
189
|
const parts = parseSelector('h1 + p');
|
|
@@ -175,10 +243,29 @@ describe('selector parsing and matching', () => {
|
|
|
175
243
|
</div>
|
|
176
244
|
</footer>
|
|
177
245
|
`;
|
|
246
|
+
container.querySelector('article')!.setAttribute('DATA-STATE', 'Ready');
|
|
247
|
+
container.querySelector('.highlight')!.setAttribute('data-label', 'a)b');
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it('types instance query results as elements', () => {
|
|
251
|
+
const matches = new Window().document
|
|
252
|
+
.createElement('div')
|
|
253
|
+
.querySelectorAll('p');
|
|
254
|
+
|
|
255
|
+
expectTypeOf(matches).toEqualTypeOf<NodeList<PolyfillElement>>();
|
|
256
|
+
expectTypeOf(matches[0]!).toEqualTypeOf<PolyfillElement>();
|
|
257
|
+
expectTypeOf(matches.item(0)).toEqualTypeOf<PolyfillElement | null>();
|
|
178
258
|
});
|
|
179
259
|
|
|
180
|
-
it('
|
|
181
|
-
const
|
|
260
|
+
it('returns the polyfill collection with item() access', () => {
|
|
261
|
+
const matches = container.querySelectorAll('.text');
|
|
262
|
+
|
|
263
|
+
expect(matches).toBeInstanceOf(NodeList);
|
|
264
|
+
expect(matches.item(0)).toBe(matches[0]);
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
it('selects HTML element names case-insensitively', () => {
|
|
268
|
+
const articles = container.querySelectorAll('ARTICLE');
|
|
182
269
|
expect(articles).toHaveLength(1);
|
|
183
270
|
expect(articles[0]!.getAttribute('class')).toBe('post');
|
|
184
271
|
|
|
@@ -186,6 +273,69 @@ describe('selector parsing and matching', () => {
|
|
|
186
273
|
expect(paragraphs).toHaveLength(3);
|
|
187
274
|
});
|
|
188
275
|
|
|
276
|
+
it('returns a static NodeList-compatible collection', () => {
|
|
277
|
+
const matches = container.querySelectorAll('.text');
|
|
278
|
+
const text = [...matches].map((element) => element.textContent?.trim());
|
|
279
|
+
const visited: string[] = [];
|
|
280
|
+
|
|
281
|
+
matches.forEach((element) => visited.push(element.textContent?.trim()!));
|
|
282
|
+
container
|
|
283
|
+
.appendChild(document.createElement('p'))
|
|
284
|
+
.setAttribute('class', 'text');
|
|
285
|
+
|
|
286
|
+
expect(container.querySelectorAll('.text')).toHaveLength(4);
|
|
287
|
+
expect(matches).toBeInstanceOf(NodeList);
|
|
288
|
+
expect(matches).toHaveLength(3);
|
|
289
|
+
expect(matches.item(0)).toBe(matches[0]);
|
|
290
|
+
expect(matches.item(-1)).toBeNull();
|
|
291
|
+
expect(matches.item(matches.length)).toBeNull();
|
|
292
|
+
expect(
|
|
293
|
+
[...matches].map((element) => element.textContent?.trim()),
|
|
294
|
+
).toEqual(text);
|
|
295
|
+
expect(visited).toEqual(text);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('does not fold stored createElementNS HTML names', () => {
|
|
299
|
+
const uppercase = document.createElementNS(
|
|
300
|
+
'http://www.w3.org/1999/xhtml',
|
|
301
|
+
'I',
|
|
302
|
+
);
|
|
303
|
+
container.appendChild(uppercase);
|
|
304
|
+
|
|
305
|
+
expect(container.querySelectorAll('i')).toHaveLength(0);
|
|
306
|
+
expect(container.querySelectorAll('I')).toHaveLength(0);
|
|
307
|
+
|
|
308
|
+
const normalized = document.createElement('I');
|
|
309
|
+
container.appendChild(normalized);
|
|
310
|
+
|
|
311
|
+
expect(container.querySelectorAll('i')).toEqual([normalized]);
|
|
312
|
+
expect(container.querySelectorAll('I')).toEqual([normalized]);
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
it.each(['\u1680', '\ufeff'])(
|
|
316
|
+
'does not treat %j as CSS whitespace',
|
|
317
|
+
(separator) => {
|
|
318
|
+
const ordinary = document.createElement('article');
|
|
319
|
+
container.appendChild(ordinary);
|
|
320
|
+
const selector = `${separator}article`;
|
|
321
|
+
|
|
322
|
+
expect(container.querySelector(selector)).toBeNull();
|
|
323
|
+
|
|
324
|
+
const literal = document.createElement(selector);
|
|
325
|
+
container.appendChild(literal);
|
|
326
|
+
expect(container.querySelector(selector)).toBe(literal);
|
|
327
|
+
expect(container.querySelectorAll(selector)).toEqual([literal]);
|
|
328
|
+
|
|
329
|
+
const classed = document.createElement('div');
|
|
330
|
+
classed.setAttribute('class', `first${separator}second`);
|
|
331
|
+
container.appendChild(classed);
|
|
332
|
+
expect(container.querySelector('.first')).toBeNull();
|
|
333
|
+
expect(container.querySelector(`.first${separator}second`)).toBe(
|
|
334
|
+
classed,
|
|
335
|
+
);
|
|
336
|
+
},
|
|
337
|
+
);
|
|
338
|
+
|
|
189
339
|
it('selects by ID', () => {
|
|
190
340
|
const main = container.querySelector('#main-post');
|
|
191
341
|
expect(main?.tagName.toLowerCase()).toBe('article');
|
|
@@ -199,6 +349,33 @@ describe('selector parsing and matching', () => {
|
|
|
199
349
|
expect(hidden?.textContent?.trim()).toBe('Hidden paragraph');
|
|
200
350
|
});
|
|
201
351
|
|
|
352
|
+
it('keeps non-ASCII whitespace in class names', () => {
|
|
353
|
+
const literal = document.createElement('div');
|
|
354
|
+
literal.setAttribute('class', 'left\u00a0right');
|
|
355
|
+
|
|
356
|
+
const separated = document.createElement('div');
|
|
357
|
+
separated.setAttribute('class', 'left right');
|
|
358
|
+
|
|
359
|
+
container.append(literal, separated);
|
|
360
|
+
|
|
361
|
+
const literalMatches = container.querySelectorAll('.left\u00a0right');
|
|
362
|
+
expect(literalMatches).toHaveLength(1);
|
|
363
|
+
expect(literalMatches[0]).toBe(literal);
|
|
364
|
+
expect(container.querySelector('.left')).toBe(separated);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('selects Unicode and double-hyphen identifiers', () => {
|
|
368
|
+
const article = container.querySelector('article')!;
|
|
369
|
+
const unicode = document.createElement('span');
|
|
370
|
+
unicode.setAttribute('class', 'é');
|
|
371
|
+
unicode.id = '--foo';
|
|
372
|
+
article.appendChild(unicode);
|
|
373
|
+
|
|
374
|
+
expect(container.querySelector('.é')).toBe(unicode);
|
|
375
|
+
expect(container.querySelector('#--foo')).toBe(unicode);
|
|
376
|
+
expect(container.querySelector('article:has(> .é)')).toBe(article);
|
|
377
|
+
});
|
|
378
|
+
|
|
202
379
|
it('selects by attribute', () => {
|
|
203
380
|
const links = container.querySelectorAll('[href]');
|
|
204
381
|
expect(links).toHaveLength(2);
|
|
@@ -206,6 +383,23 @@ describe('selector parsing and matching', () => {
|
|
|
206
383
|
const activeLinks = container.querySelectorAll('[href="#"]');
|
|
207
384
|
expect(activeLinks).toHaveLength(2);
|
|
208
385
|
});
|
|
386
|
+
it('selects by an unquoted exact attribute value', () => {
|
|
387
|
+
expect(container.querySelectorAll('[class=content]')).toHaveLength(2);
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it('rejects a different unquoted exact attribute value', () => {
|
|
391
|
+
expect(container.querySelector('[class=contents]')).toBeNull();
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
it.each([
|
|
395
|
+
['quoted', 'article[data-state="Ready"]', true],
|
|
396
|
+
['unquoted', 'article[data-state=Ready]', true],
|
|
397
|
+
['normalized HTML name', 'article[DATA-STATE=Ready]', true],
|
|
398
|
+
['quoted case-sensitive value', 'article[data-state="ready"]', false],
|
|
399
|
+
['unquoted case-sensitive value', 'article[data-state=ready]', false],
|
|
400
|
+
])('matches %s exact attribute equality', (_name, selector, matches) => {
|
|
401
|
+
expect(container.querySelector(selector) != null).toBe(matches);
|
|
402
|
+
});
|
|
209
403
|
|
|
210
404
|
it('selects by compound selectors', () => {
|
|
211
405
|
const activeLink = container.querySelector('a.link.active');
|
|
@@ -230,6 +424,33 @@ describe('selector parsing and matching', () => {
|
|
|
230
424
|
const directArticleChildren = container.querySelectorAll('article > h1');
|
|
231
425
|
expect(directArticleChildren).toHaveLength(1);
|
|
232
426
|
});
|
|
427
|
+
it('preserves the matched ancestor through chained combinators', () => {
|
|
428
|
+
const activeLink = container.querySelector('article > .sidebar a.active');
|
|
429
|
+
|
|
430
|
+
expect(activeLink?.textContent?.trim()).toBe('Active Link');
|
|
431
|
+
});
|
|
432
|
+
|
|
433
|
+
it('does not restart chained combinators from the leaf', () => {
|
|
434
|
+
expect(container.querySelector('li > .sidebar a.active')).toBeNull();
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
it.each([
|
|
438
|
+
['child', 'article > .sidebar > .nav > li > a.active'],
|
|
439
|
+
['descendant', 'article .sidebar .nav li a.active'],
|
|
440
|
+
['adjacent sibling', 'h1 + .content + .sidebar'],
|
|
441
|
+
['general sibling', 'h1 ~ .content ~ .sidebar'],
|
|
442
|
+
])('preserves state across a 3+ part %s chain', (_name, selector) => {
|
|
443
|
+
expect(container.querySelector(selector)).not.toBeNull();
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
it.each([
|
|
447
|
+
['child', 'article > .sidebar > li > a.active'],
|
|
448
|
+
['descendant', 'footer .sidebar .nav a.active'],
|
|
449
|
+
['adjacent sibling', 'h1 + .sidebar + .content'],
|
|
450
|
+
['general sibling', '.sidebar ~ .content ~ footer'],
|
|
451
|
+
])('rejects an invalid 3+ part %s chain', (_name, selector) => {
|
|
452
|
+
expect(container.querySelector(selector)).toBeNull();
|
|
453
|
+
});
|
|
233
454
|
|
|
234
455
|
it('selects with adjacent sibling combinator', () => {
|
|
235
456
|
const titleSibling = container.querySelector('h1 + div');
|
|
@@ -251,6 +472,103 @@ describe('selector parsing and matching', () => {
|
|
|
251
472
|
const hasActiveLink = container.querySelector(':has(.active)');
|
|
252
473
|
expect(hasActiveLink).toBeTruthy();
|
|
253
474
|
});
|
|
475
|
+
it('matches :has() against descendants', () => {
|
|
476
|
+
expect(container.querySelector('article:has(.active)')?.id).toBe(
|
|
477
|
+
'main-post',
|
|
478
|
+
);
|
|
479
|
+
});
|
|
480
|
+
|
|
481
|
+
it('does not match :has() without a matching descendant', () => {
|
|
482
|
+
expect(container.querySelector('footer:has(.active)')).toBeNull();
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
it.each([
|
|
486
|
+
['direct child', 'article:has(> h1)'],
|
|
487
|
+
['child with descendant', 'article:has(> .content .highlight)'],
|
|
488
|
+
['adjacent sibling', 'article:has(+ footer)'],
|
|
489
|
+
['general sibling', 'article:has(~ footer)'],
|
|
490
|
+
])(
|
|
491
|
+
'matches scoped :has() with a leading %s relation',
|
|
492
|
+
(_name, selector) => {
|
|
493
|
+
document.body.appendChild(container);
|
|
494
|
+
expect(document.body.querySelector(selector)?.id).toBe('main-post');
|
|
495
|
+
},
|
|
496
|
+
);
|
|
497
|
+
|
|
498
|
+
it.each([
|
|
499
|
+
['outside ancestor', 'article:has(body .active)'],
|
|
500
|
+
['scope as explicit ancestor', 'article:has(article .active)'],
|
|
501
|
+
['scope id as explicit ancestor', 'article:has(#main-post .active)'],
|
|
502
|
+
['non-child descendant', 'article:has(> .active)'],
|
|
503
|
+
['wrong adjacent direction', 'footer:has(+ article)'],
|
|
504
|
+
])('rejects :has() with %s', (_name, selector) => {
|
|
505
|
+
document.body.appendChild(container);
|
|
506
|
+
expect(document.body.querySelector(selector)).toBeNull();
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
it.each(['article:has(:has(.active))', 'article:has(:not(:has(.active)))'])(
|
|
510
|
+
'rejects nested :has() before walking candidates in %s',
|
|
511
|
+
(selector) => {
|
|
512
|
+
const empty = document.createElement('div');
|
|
513
|
+
for (const root of [empty, container]) {
|
|
514
|
+
expect(() => root.querySelector(selector)).toThrowError(
|
|
515
|
+
expect.objectContaining({name: 'SyntaxError'}),
|
|
516
|
+
);
|
|
517
|
+
expect(() => root.querySelectorAll(selector)).toThrowError(
|
|
518
|
+
expect.objectContaining({name: 'SyntaxError'}),
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
},
|
|
522
|
+
);
|
|
523
|
+
|
|
524
|
+
it('preserves valid :has() nesting controls', () => {
|
|
525
|
+
expect(() => parseSelector(':not(:has(.missing))')).not.toThrow();
|
|
526
|
+
expect(() => parseSelector(':has(span:not(.missing))')).not.toThrow();
|
|
527
|
+
expect(() => parseSelector(':has(.active):has(h1)')).not.toThrow();
|
|
528
|
+
expect(() =>
|
|
529
|
+
parseSelector(':has([data-label=":has(.active)"])'),
|
|
530
|
+
).not.toThrow();
|
|
531
|
+
});
|
|
532
|
+
|
|
533
|
+
it('matches nested functional pseudo-classes', () => {
|
|
534
|
+
expect(container.querySelector('article:not(:has(.missing))')?.id).toBe(
|
|
535
|
+
'main-post',
|
|
536
|
+
);
|
|
537
|
+
expect(container.querySelector('article:not(:has(.active))')).toBeNull();
|
|
538
|
+
expect(
|
|
539
|
+
container.querySelector('article:has(span:not(.missing))')?.id,
|
|
540
|
+
).toBe('main-post');
|
|
541
|
+
expect(
|
|
542
|
+
container.querySelector('footer:has(span:not(.missing))'),
|
|
543
|
+
).toBeNull();
|
|
544
|
+
});
|
|
545
|
+
|
|
546
|
+
it.each([
|
|
547
|
+
['uppercase simple function', 'article:HAS(.active)'],
|
|
548
|
+
['mixed-case simple function', 'article:Has(.active)'],
|
|
549
|
+
['mixed-case negation', 'article:NOT(.footer)'],
|
|
550
|
+
['nested functions', 'article:NOT(:HAS(.missing))'],
|
|
551
|
+
['nested descendant function', 'article:HAS(span:NoT(.missing))'],
|
|
552
|
+
['relative child function', 'article:HAS(> h1)'],
|
|
553
|
+
['relative sibling function', 'article:hAs(+ footer)'],
|
|
554
|
+
])('matches %s names ASCII-case-insensitively', (_name, selector) => {
|
|
555
|
+
expect(container.querySelector(selector)?.id).toBe('main-post');
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it.each([
|
|
559
|
+
['mixed-case negation result', 'article:NoT(.post)'],
|
|
560
|
+
['class name', 'article:HAS(.ACTIVE)'],
|
|
561
|
+
['ID', '#MAIN-POST'],
|
|
562
|
+
['attribute value', 'article:HAS([data-label="A)B"])'],
|
|
563
|
+
])('does not fold the %s', (_name, selector) => {
|
|
564
|
+
expect(container.querySelector(selector)).toBeNull();
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
it('keeps quoted attribute values balanced inside :has()', () => {
|
|
568
|
+
expect(
|
|
569
|
+
container.querySelector('article:has([data-label="a)b"])')?.id,
|
|
570
|
+
).toBe('main-post');
|
|
571
|
+
});
|
|
254
572
|
|
|
255
573
|
it('handles complex selectors', () => {
|
|
256
574
|
const complexSelector = container.querySelectorAll(
|
|
@@ -269,12 +587,205 @@ describe('selector parsing and matching', () => {
|
|
|
269
587
|
expect(container.querySelector('table')).toBeNull();
|
|
270
588
|
expect(container.querySelector('#nonexistent-id')).toBeNull();
|
|
271
589
|
});
|
|
590
|
+
it('ignores leading selector whitespace', () => {
|
|
591
|
+
expect(container.querySelector(' \n\tarticle')?.id).toBe('main-post');
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
it('does not turn leading whitespace into a match', () => {
|
|
595
|
+
expect(container.querySelector(' \n\tsection')).toBeNull();
|
|
596
|
+
});
|
|
272
597
|
|
|
273
|
-
it(
|
|
274
|
-
|
|
598
|
+
it.each([
|
|
599
|
+
['leading and trailing', ' \n\tarticle ', true],
|
|
600
|
+
['internal child', 'article \n >\t .sidebar ', true],
|
|
601
|
+
['internal descendant', 'article .nav\t a.active ', true],
|
|
602
|
+
['non-match with whitespace', ' \n footer > .sidebar\t ', false],
|
|
603
|
+
])('handles %s whitespace', (_name, selector, matches) => {
|
|
604
|
+
expect(container.querySelector(selector) != null).toBe(matches);
|
|
605
|
+
});
|
|
275
606
|
|
|
607
|
+
it('selects all elements with the universal selector', () => {
|
|
276
608
|
const allElements = container.querySelectorAll('*');
|
|
277
609
|
expect(allElements.length).toBeGreaterThan(0);
|
|
278
610
|
});
|
|
279
611
|
});
|
|
612
|
+
|
|
613
|
+
describe('querySelector and querySelectorAll with Matcher[] argument', () => {
|
|
614
|
+
let container: Element;
|
|
615
|
+
|
|
616
|
+
beforeEach(() => {
|
|
617
|
+
container = document.createElement('div');
|
|
618
|
+
container.innerHTML = `
|
|
619
|
+
<article class="post" id="main-post">
|
|
620
|
+
<h1 class="title">Main Title</h1>
|
|
621
|
+
<div class="content">
|
|
622
|
+
<p class="text">First paragraph</p>
|
|
623
|
+
<p class="text hidden">Hidden paragraph</p>
|
|
624
|
+
<span class="highlight">Highlighted text</span>
|
|
625
|
+
</div>
|
|
626
|
+
</article>
|
|
627
|
+
`;
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
// The standalone querySelector/querySelectorAll functions expect the polyfill's
|
|
631
|
+
// ParentNode, but `container` is typed as the global Element (lib.dom.d.ts).
|
|
632
|
+
// At runtime, Window.setGlobalThis replaces globals with polyfill instances.
|
|
633
|
+
const asPolyfill = (node: Element) => node as unknown as ParentNode;
|
|
634
|
+
|
|
635
|
+
it('selects by ID matcher without parsing', () => {
|
|
636
|
+
const main = querySelector(asPolyfill(container), [
|
|
637
|
+
{type: MatcherType.Id, name: 'main-post'},
|
|
638
|
+
]);
|
|
639
|
+
expect(main?.tagName.toLowerCase()).toBe('article');
|
|
640
|
+
});
|
|
641
|
+
|
|
642
|
+
it('types standalone query results as elements', () => {
|
|
643
|
+
const matches = querySelectorAll(asPolyfill(container), [
|
|
644
|
+
{type: MatcherType.Element, name: 'p'},
|
|
645
|
+
]);
|
|
646
|
+
|
|
647
|
+
expectTypeOf(matches).toEqualTypeOf<NodeList<PolyfillElement>>();
|
|
648
|
+
expectTypeOf(matches[0]!).toEqualTypeOf<PolyfillElement>();
|
|
649
|
+
expectTypeOf(matches.item(0)).toEqualTypeOf<PolyfillElement | null>();
|
|
650
|
+
});
|
|
651
|
+
|
|
652
|
+
it('selects by element matcher without parsing', () => {
|
|
653
|
+
const paragraphs = querySelectorAll(asPolyfill(container), [
|
|
654
|
+
{type: MatcherType.Element, name: 'p'},
|
|
655
|
+
]);
|
|
656
|
+
expect(paragraphs).toHaveLength(2);
|
|
657
|
+
});
|
|
658
|
+
|
|
659
|
+
it('matches ids with special characters literally (no CSS escaping)', () => {
|
|
660
|
+
const element = document.createElement('div');
|
|
661
|
+
element.id = 'a.b:c#d';
|
|
662
|
+
container.appendChild(element);
|
|
663
|
+
|
|
664
|
+
const result = querySelector(asPolyfill(container), [
|
|
665
|
+
{type: MatcherType.Id, name: 'a.b:c#d'},
|
|
666
|
+
]);
|
|
667
|
+
expect(result).toBe(element);
|
|
668
|
+
});
|
|
669
|
+
|
|
670
|
+
it('matches classes with selector punctuation literally', () => {
|
|
671
|
+
const element = document.createElement('div');
|
|
672
|
+
element.setAttribute('class', 'has.dot has:colon has#hash');
|
|
673
|
+
container.appendChild(element);
|
|
674
|
+
|
|
675
|
+
for (const name of ['has.dot', 'has:colon', 'has#hash']) {
|
|
676
|
+
expect(
|
|
677
|
+
querySelector(asPolyfill(container), [
|
|
678
|
+
{type: MatcherType.Class, name},
|
|
679
|
+
]),
|
|
680
|
+
).toBe(element);
|
|
681
|
+
}
|
|
682
|
+
});
|
|
683
|
+
|
|
684
|
+
it.each(ASCII_WHITESPACE)(
|
|
685
|
+
'splits class attributes on %s',
|
|
686
|
+
(_name, whitespace) => {
|
|
687
|
+
const element = document.createElement('div');
|
|
688
|
+
element.setAttribute('class', `left${whitespace}right`);
|
|
689
|
+
container.appendChild(element);
|
|
690
|
+
|
|
691
|
+
expect(
|
|
692
|
+
querySelector(asPolyfill(container), [
|
|
693
|
+
{type: MatcherType.Class, name: 'right'},
|
|
694
|
+
]),
|
|
695
|
+
).toBe(element);
|
|
696
|
+
},
|
|
697
|
+
);
|
|
698
|
+
|
|
699
|
+
it('does not split class attributes on non-ASCII whitespace', () => {
|
|
700
|
+
const literal = document.createElement('div');
|
|
701
|
+
literal.setAttribute('class', 'left\u00a0right');
|
|
702
|
+
|
|
703
|
+
const separated = document.createElement('div');
|
|
704
|
+
separated.setAttribute('class', 'left right');
|
|
705
|
+
|
|
706
|
+
container.append(literal, separated);
|
|
707
|
+
|
|
708
|
+
expect(
|
|
709
|
+
querySelector(asPolyfill(container), [
|
|
710
|
+
{type: MatcherType.Class, name: 'left\u00a0right'},
|
|
711
|
+
]),
|
|
712
|
+
).toBe(literal);
|
|
713
|
+
expect(
|
|
714
|
+
querySelector(asPolyfill(container), [
|
|
715
|
+
{type: MatcherType.Class, name: 'left'},
|
|
716
|
+
]),
|
|
717
|
+
).toBe(separated);
|
|
718
|
+
});
|
|
719
|
+
|
|
720
|
+
it('requires every class matcher to match', () => {
|
|
721
|
+
const complete = document.createElement('div');
|
|
722
|
+
complete.setAttribute('class', 'one two three');
|
|
723
|
+
|
|
724
|
+
const partial = document.createElement('div');
|
|
725
|
+
partial.setAttribute('class', 'one two');
|
|
726
|
+
|
|
727
|
+
container.append(complete, partial);
|
|
728
|
+
|
|
729
|
+
const matches = querySelectorAll(asPolyfill(container), [
|
|
730
|
+
{type: MatcherType.Class, name: 'one'},
|
|
731
|
+
{type: MatcherType.Class, name: 'three'},
|
|
732
|
+
]);
|
|
733
|
+
expect(matches).toHaveLength(1);
|
|
734
|
+
expect(matches[0]).toBe(complete);
|
|
735
|
+
|
|
736
|
+
expect(
|
|
737
|
+
querySelector(asPolyfill(container), [
|
|
738
|
+
{type: MatcherType.Class, name: 'one'},
|
|
739
|
+
{type: MatcherType.Class, name: 'missing'},
|
|
740
|
+
]),
|
|
741
|
+
).toBeNull();
|
|
742
|
+
});
|
|
743
|
+
|
|
744
|
+
it('matches HTML tag names case-insensitively via element matcher', () => {
|
|
745
|
+
const upper = querySelectorAll(asPolyfill(container), [
|
|
746
|
+
{type: MatcherType.Element, name: 'ARTICLE'},
|
|
747
|
+
]);
|
|
748
|
+
expect(upper).toHaveLength(1);
|
|
749
|
+
|
|
750
|
+
const mixed = querySelectorAll(asPolyfill(container), [
|
|
751
|
+
{type: MatcherType.Element, name: 'SpAn'},
|
|
752
|
+
]);
|
|
753
|
+
expect(mixed).toHaveLength(1);
|
|
754
|
+
expect(mixed[0]!.getAttribute('class')).toBe('highlight');
|
|
755
|
+
});
|
|
756
|
+
|
|
757
|
+
it('returns same results as string selectors for compound queries', () => {
|
|
758
|
+
const byString = container.querySelectorAll('p.text.hidden');
|
|
759
|
+
const byObject = querySelectorAll(asPolyfill(container), [
|
|
760
|
+
{type: MatcherType.Element, name: 'p'},
|
|
761
|
+
{type: MatcherType.Class, name: 'text'},
|
|
762
|
+
{type: MatcherType.Class, name: 'hidden'},
|
|
763
|
+
]);
|
|
764
|
+
|
|
765
|
+
expect(byObject).toHaveLength(byString.length);
|
|
766
|
+
expect(byObject[0]?.textContent?.trim()).toBe('Hidden paragraph');
|
|
767
|
+
});
|
|
768
|
+
|
|
769
|
+
it('wildcard matcher returns all elements', () => {
|
|
770
|
+
const all = querySelectorAll(asPolyfill(container), [
|
|
771
|
+
{type: MatcherType.Unknown, name: '*'},
|
|
772
|
+
]);
|
|
773
|
+
expect(all.length).toBeGreaterThan(0);
|
|
774
|
+
// Should include article, h1, div, p, p, span
|
|
775
|
+
expect(all.length).toBe(6);
|
|
776
|
+
});
|
|
777
|
+
|
|
778
|
+
it('returns null/empty for non-matching matchers', () => {
|
|
779
|
+
expect(
|
|
780
|
+
querySelector(asPolyfill(container), [
|
|
781
|
+
{type: MatcherType.Id, name: 'nope'},
|
|
782
|
+
]),
|
|
783
|
+
).toBeNull();
|
|
784
|
+
expect(
|
|
785
|
+
querySelectorAll(asPolyfill(container), [
|
|
786
|
+
{type: MatcherType.Element, name: 'table'},
|
|
787
|
+
]),
|
|
788
|
+
).toHaveLength(0);
|
|
789
|
+
});
|
|
790
|
+
});
|
|
280
791
|
});
|