happy-dom 9.10.2 → 9.10.4
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.
Potentially problematic release.
This version of happy-dom might be problematic. Click here for more details.
- package/lib/nodes/element/Element.d.ts +12 -0
- package/lib/nodes/element/Element.d.ts.map +1 -1
- package/lib/nodes/element/Element.js +16 -0
- package/lib/nodes/element/Element.js.map +1 -1
- package/lib/nodes/element/IElement.d.ts +1 -0
- package/lib/nodes/element/IElement.d.ts.map +1 -1
- package/lib/nodes/html-input-element/HTMLInputElement.d.ts +6 -0
- package/lib/nodes/html-input-element/HTMLInputElement.d.ts.map +1 -1
- package/lib/nodes/html-input-element/HTMLInputElement.js +19 -11
- package/lib/nodes/html-input-element/HTMLInputElement.js.map +1 -1
- package/lib/query-selector/ISelectorPseudo.d.ts +5 -0
- package/lib/query-selector/ISelectorPseudo.d.ts.map +1 -0
- package/lib/query-selector/ISelectorPseudo.js +3 -0
- package/lib/query-selector/ISelectorPseudo.js.map +1 -0
- package/lib/query-selector/QuerySelector.d.ts.map +1 -1
- package/lib/query-selector/QuerySelector.js +5 -0
- package/lib/query-selector/QuerySelector.js.map +1 -1
- package/lib/query-selector/SelectorItem.d.ts +4 -9
- package/lib/query-selector/SelectorItem.d.ts.map +1 -1
- package/lib/query-selector/SelectorItem.js +84 -78
- package/lib/query-selector/SelectorItem.js.map +1 -1
- package/lib/query-selector/SelectorParser.d.ts.map +1 -1
- package/lib/query-selector/SelectorParser.js +37 -14
- package/lib/query-selector/SelectorParser.js.map +1 -1
- package/package.json +1 -1
- package/src/nodes/element/Element.ts +18 -0
- package/src/nodes/element/IElement.ts +1 -0
- package/src/nodes/html-input-element/HTMLInputElement.ts +22 -13
- package/src/query-selector/ISelectorPseudo.ts +4 -0
- package/src/query-selector/QuerySelector.ts +6 -0
- package/src/query-selector/SelectorItem.ts +99 -92
- package/src/query-selector/SelectorParser.ts +37 -14
@@ -6,18 +6,17 @@ import SelectorCombinatorEnum from './SelectorCombinatorEnum';
|
|
6
6
|
import ISelectorAttribute from './ISelectorAttribute';
|
7
7
|
import SelectorParser from './SelectorParser';
|
8
8
|
import ISelectorMatch from './ISelectorMatch';
|
9
|
+
import ISelectorPseudo from './ISelectorPseudo';
|
9
10
|
|
10
11
|
/**
|
11
12
|
* Selector item.
|
12
13
|
*/
|
13
14
|
export default class SelectorItem {
|
14
|
-
public all: string | null;
|
15
15
|
public tagName: string | null;
|
16
16
|
public id: string | null;
|
17
17
|
public classNames: string[] | null;
|
18
18
|
public attributes: ISelectorAttribute[] | null;
|
19
|
-
public
|
20
|
-
public pseudoArguments: string | null;
|
19
|
+
public pseudos: ISelectorPseudo[] | null;
|
21
20
|
public combinator: SelectorCombinatorEnum;
|
22
21
|
|
23
22
|
/**
|
@@ -25,31 +24,25 @@ export default class SelectorItem {
|
|
25
24
|
*
|
26
25
|
* @param [options] Options.
|
27
26
|
* @param [options.combinator] Combinator.
|
28
|
-
* @param [options.all] All.
|
29
27
|
* @param [options.tagName] Tag name.
|
30
28
|
* @param [options.id] ID.
|
31
29
|
* @param [options.classNames] Class names.
|
32
30
|
* @param [options.attributes] Attributes.
|
33
|
-
* @param [options.
|
34
|
-
* @param [options.pseudoArguments] Pseudo arguments.
|
31
|
+
* @param [options.pseudos] Pseudos.
|
35
32
|
*/
|
36
33
|
constructor(options?: {
|
37
|
-
all?: string;
|
38
34
|
tagName?: string;
|
39
35
|
id?: string;
|
40
36
|
classNames?: string[];
|
41
37
|
attributes?: ISelectorAttribute[];
|
42
|
-
|
43
|
-
pseudoArguments?: string;
|
38
|
+
pseudos?: ISelectorPseudo[];
|
44
39
|
combinator?: SelectorCombinatorEnum;
|
45
40
|
}) {
|
46
|
-
this.all = options?.all || null;
|
47
41
|
this.tagName = options?.tagName || null;
|
48
42
|
this.id = options?.id || null;
|
49
43
|
this.classNames = options?.classNames || null;
|
50
44
|
this.attributes = options?.attributes || null;
|
51
|
-
this.
|
52
|
-
this.pseudoArguments = options?.pseudoArguments || null;
|
45
|
+
this.pseudos = options?.pseudos || null;
|
53
46
|
this.combinator = options?.combinator || SelectorCombinatorEnum.descendant;
|
54
47
|
}
|
55
48
|
|
@@ -64,7 +57,7 @@ export default class SelectorItem {
|
|
64
57
|
|
65
58
|
// Tag name match
|
66
59
|
if (this.tagName) {
|
67
|
-
if (this.tagName !== element.tagName) {
|
60
|
+
if (this.tagName !== '*' && this.tagName !== element.tagName) {
|
68
61
|
return null;
|
69
62
|
}
|
70
63
|
priorityWeight += 1;
|
@@ -97,7 +90,7 @@ export default class SelectorItem {
|
|
97
90
|
}
|
98
91
|
|
99
92
|
// Pseudo match
|
100
|
-
if (this.
|
93
|
+
if (this.pseudos && !this.matchPsuedo(element)) {
|
101
94
|
return null;
|
102
95
|
}
|
103
96
|
|
@@ -113,97 +106,105 @@ export default class SelectorItem {
|
|
113
106
|
private matchPsuedo(element: IElement): boolean {
|
114
107
|
const parent = <IElement>element.parentNode;
|
115
108
|
|
116
|
-
|
117
|
-
|
118
|
-
case 'not':
|
119
|
-
case 'nth-child':
|
120
|
-
case 'nth-of-type':
|
121
|
-
case 'nth-last-child':
|
122
|
-
case 'nth-last-of-type':
|
123
|
-
if (!this.pseudoArguments) {
|
124
|
-
throw new DOMException(`The selector "${this.getSelectorString()}" is not valid.`);
|
125
|
-
}
|
126
|
-
break;
|
109
|
+
if (!this.pseudos) {
|
110
|
+
return true;
|
127
111
|
}
|
128
112
|
|
129
|
-
|
130
|
-
|
131
|
-
switch (
|
113
|
+
for (const psuedo of this.pseudos) {
|
114
|
+
// Validation
|
115
|
+
switch (psuedo.name) {
|
116
|
+
case 'not':
|
117
|
+
case 'nth-child':
|
118
|
+
case 'nth-of-type':
|
119
|
+
case 'nth-last-child':
|
120
|
+
case 'nth-last-of-type':
|
121
|
+
if (!psuedo.arguments) {
|
122
|
+
throw new DOMException(`The selector "${this.getSelectorString()}" is not valid.`);
|
123
|
+
}
|
124
|
+
break;
|
125
|
+
}
|
126
|
+
|
127
|
+
// Check if parent exists
|
128
|
+
if (!parent) {
|
129
|
+
switch (psuedo.name) {
|
130
|
+
case 'first-child':
|
131
|
+
case 'last-child':
|
132
|
+
case 'only-child':
|
133
|
+
case 'first-of-type':
|
134
|
+
case 'last-of-type':
|
135
|
+
case 'only-of-type':
|
136
|
+
case 'nth-child':
|
137
|
+
case 'nth-of-type':
|
138
|
+
case 'nth-last-child':
|
139
|
+
case 'nth-last-of-type':
|
140
|
+
return false;
|
141
|
+
}
|
142
|
+
}
|
143
|
+
|
144
|
+
switch (psuedo.name) {
|
132
145
|
case 'first-child':
|
146
|
+
return parent.children[0] === element;
|
133
147
|
case 'last-child':
|
148
|
+
return parent.children.length && parent.children[parent.children.length - 1] === element;
|
134
149
|
case 'only-child':
|
150
|
+
return parent.children.length === 1 && parent.children[0] === element;
|
135
151
|
case 'first-of-type':
|
152
|
+
for (const child of parent.children) {
|
153
|
+
if (child.tagName === element.tagName) {
|
154
|
+
return child === element;
|
155
|
+
}
|
156
|
+
}
|
157
|
+
return false;
|
136
158
|
case 'last-of-type':
|
159
|
+
for (let i = parent.children.length - 1; i >= 0; i--) {
|
160
|
+
const child = parent.children[i];
|
161
|
+
if (child.tagName === element.tagName) {
|
162
|
+
return child === element;
|
163
|
+
}
|
164
|
+
}
|
165
|
+
return false;
|
137
166
|
case 'only-of-type':
|
167
|
+
let isFound = false;
|
168
|
+
for (const child of parent.children) {
|
169
|
+
if (child.tagName === element.tagName) {
|
170
|
+
if (isFound || child !== element) {
|
171
|
+
return false;
|
172
|
+
}
|
173
|
+
isFound = true;
|
174
|
+
}
|
175
|
+
}
|
176
|
+
return isFound;
|
177
|
+
case 'checked':
|
178
|
+
return element.tagName === 'INPUT' && (<IHTMLInputElement>element).checked;
|
179
|
+
case 'empty':
|
180
|
+
return !element.children.length;
|
181
|
+
case 'root':
|
182
|
+
return element.tagName === 'HTML';
|
183
|
+
case 'not':
|
184
|
+
return !SelectorParser.getSelectorItem(psuedo.arguments).match(element);
|
138
185
|
case 'nth-child':
|
186
|
+
return this.matchNthChild(element, parent.children, psuedo.arguments);
|
139
187
|
case 'nth-of-type':
|
188
|
+
if (!element.parentNode) {
|
189
|
+
return false;
|
190
|
+
}
|
191
|
+
return this.matchNthChild(
|
192
|
+
element,
|
193
|
+
parent.children.filter((child) => child.tagName === element.tagName),
|
194
|
+
psuedo.arguments
|
195
|
+
);
|
140
196
|
case 'nth-last-child':
|
197
|
+
return this.matchNthChild(element, parent.children.reverse(), psuedo.arguments);
|
141
198
|
case 'nth-last-of-type':
|
142
|
-
return
|
199
|
+
return this.matchNthChild(
|
200
|
+
element,
|
201
|
+
parent.children.filter((child) => child.tagName === element.tagName).reverse(),
|
202
|
+
psuedo.arguments
|
203
|
+
);
|
143
204
|
}
|
144
205
|
}
|
145
206
|
|
146
|
-
|
147
|
-
case 'first-child':
|
148
|
-
return parent.children[0] === element;
|
149
|
-
case 'last-child':
|
150
|
-
return parent.children.length && parent.children[parent.children.length - 1] === element;
|
151
|
-
case 'only-child':
|
152
|
-
return parent.children.length === 1 && parent.children[0] === element;
|
153
|
-
case 'first-of-type':
|
154
|
-
for (const child of parent.children) {
|
155
|
-
if (child.tagName === element.tagName) {
|
156
|
-
return child === element;
|
157
|
-
}
|
158
|
-
}
|
159
|
-
return false;
|
160
|
-
case 'last-of-type':
|
161
|
-
for (let i = parent.children.length - 1; i >= 0; i--) {
|
162
|
-
const child = parent.children[i];
|
163
|
-
if (child.tagName === element.tagName) {
|
164
|
-
return child === element;
|
165
|
-
}
|
166
|
-
}
|
167
|
-
return false;
|
168
|
-
case 'only-of-type':
|
169
|
-
let isFound = false;
|
170
|
-
for (const child of parent.children) {
|
171
|
-
if (child.tagName === element.tagName) {
|
172
|
-
if (isFound || child !== element) {
|
173
|
-
return false;
|
174
|
-
}
|
175
|
-
isFound = true;
|
176
|
-
}
|
177
|
-
}
|
178
|
-
return isFound;
|
179
|
-
case 'checked':
|
180
|
-
return element.tagName === 'INPUT' && (<IHTMLInputElement>element).checked;
|
181
|
-
case 'empty':
|
182
|
-
return !element.children.length;
|
183
|
-
case 'root':
|
184
|
-
return element.tagName === 'HTML';
|
185
|
-
case 'not':
|
186
|
-
return !SelectorParser.getSelectorItem(this.pseudoArguments).match(element);
|
187
|
-
case 'nth-child':
|
188
|
-
return this.matchNthChild(element, parent.children, this.pseudoArguments);
|
189
|
-
case 'nth-of-type':
|
190
|
-
if (!element.parentNode) {
|
191
|
-
return false;
|
192
|
-
}
|
193
|
-
return this.matchNthChild(
|
194
|
-
element,
|
195
|
-
parent.children.filter((child) => child.tagName === element.tagName),
|
196
|
-
this.pseudoArguments
|
197
|
-
);
|
198
|
-
case 'nth-last-child':
|
199
|
-
return this.matchNthChild(element, parent.children.reverse(), this.pseudoArguments);
|
200
|
-
case 'nth-last-of-type':
|
201
|
-
return this.matchNthChild(
|
202
|
-
element,
|
203
|
-
parent.children.filter((child) => child.tagName === element.tagName).reverse(),
|
204
|
-
this.pseudoArguments
|
205
|
-
);
|
206
|
-
}
|
207
|
+
return true;
|
207
208
|
}
|
208
209
|
|
209
210
|
/**
|
@@ -349,7 +350,7 @@ export default class SelectorItem {
|
|
349
350
|
* @returns Selector string.
|
350
351
|
*/
|
351
352
|
private getSelectorString(): string {
|
352
|
-
return `${this.
|
353
|
+
return `${this.tagName || ''}${this.id ? `#${this.id}` : ''}${
|
353
354
|
this.classNames ? `.${this.classNames.join('.')}` : ''
|
354
355
|
}${
|
355
356
|
this.attributes
|
@@ -362,6 +363,12 @@ export default class SelectorItem {
|
|
362
363
|
)
|
363
364
|
.join('')
|
364
365
|
: ''
|
365
|
-
}${
|
366
|
+
}${
|
367
|
+
this.pseudos
|
368
|
+
? this.pseudos
|
369
|
+
.map((pseudo) => `:${pseudo.name}${pseudo.arguments ? `(${pseudo.arguments})` : ''}`)
|
370
|
+
.join('')
|
371
|
+
: ''
|
372
|
+
}`;
|
366
373
|
}
|
367
374
|
}
|
@@ -10,15 +10,19 @@ import DOMException from '../exception/DOMException';
|
|
10
10
|
* Group 3: ID (e.g. "#id")
|
11
11
|
* Group 4: Class (e.g. ".class")
|
12
12
|
* Group 5: Attribute name when no value (e.g. "attr1")
|
13
|
-
* Group 6: Attribute name when there is a value (e.g. "attr1")
|
14
|
-
* Group 7: Attribute operator (e.g. "~")
|
15
|
-
* Group 8: Attribute value (e.g. "value1")
|
16
|
-
* Group 9:
|
17
|
-
* Group 10:
|
18
|
-
* Group 11:
|
13
|
+
* Group 6: Attribute name when there is a value using apostrophe (e.g. "attr1")
|
14
|
+
* Group 7: Attribute operator when using apostrophe (e.g. "~")
|
15
|
+
* Group 8: Attribute value when using apostrophe (e.g. "value1")
|
16
|
+
* Group 9: Attribute name when threre is a value not using apostrophe (e.g. "attr1")
|
17
|
+
* Group 10: Attribute operator when not using apostrophe (e.g. "~")
|
18
|
+
* Group 11: Attribute value when notusing apostrophe (e.g. "value1")
|
19
|
+
* Group 12: Pseudo name when arguments (e.g. "nth-child")
|
20
|
+
* Group 13: Arguments of pseudo (e.g. "2n + 1")
|
21
|
+
* Group 14: Pseudo name when no arguments (e.g. "empty")
|
22
|
+
* Group 15: Combinator.
|
19
23
|
*/
|
20
24
|
const SELECTOR_REGEXP =
|
21
|
-
/(\*)|([a-zA-Z0-9-]+)|#((?:[a-zA-Z0-9-_]|\\.)+)|\.((?:[a-zA-Z0-9-_]|\\.)+)|\[([a-zA-Z0-9-_]+)\]|\[([a-zA-Z0-9-_]+)([~|^$*]{0,1}) *= *["']{
|
25
|
+
/(\*)|([a-zA-Z0-9-]+)|#((?:[a-zA-Z0-9-_]|\\.)+)|\.((?:[a-zA-Z0-9-_]|\\.)+)|\[([a-zA-Z0-9-_]+)\]|\[([a-zA-Z0-9-_]+)([~|^$*]{0,1}) *= *["']{1}([^"']*)["']{1}\]|\[([a-zA-Z0-9-_]+)([~|^$*]{0,1}) *= *([^\]]*)\]|:([a-zA-Z-]+) *\(([^)]+)\)|:([a-zA-Z-]+)|([ ,+>]*)/g;
|
22
26
|
|
23
27
|
/**
|
24
28
|
* Escaped Character RegExp.
|
@@ -55,6 +59,10 @@ export default class SelectorParser {
|
|
55
59
|
* @returns Selector groups.
|
56
60
|
*/
|
57
61
|
public static getSelectorGroups(selector: string): Array<Array<SelectorItem>> {
|
62
|
+
if (selector === '*') {
|
63
|
+
return [[new SelectorItem({ tagName: '*' })]];
|
64
|
+
}
|
65
|
+
|
58
66
|
const simpleMatch = selector.match(SIMPLE_SELECTOR_REGEXP);
|
59
67
|
|
60
68
|
if (simpleMatch) {
|
@@ -81,7 +89,7 @@ export default class SelectorParser {
|
|
81
89
|
isValid = true;
|
82
90
|
|
83
91
|
if (match[1]) {
|
84
|
-
currentSelectorItem.
|
92
|
+
currentSelectorItem.tagName = '*';
|
85
93
|
} else if (match[2]) {
|
86
94
|
currentSelectorItem.tagName = match[2].toUpperCase();
|
87
95
|
} else if (match[3]) {
|
@@ -103,12 +111,27 @@ export default class SelectorParser {
|
|
103
111
|
operator: match[7] || null,
|
104
112
|
value: match[8]
|
105
113
|
});
|
106
|
-
} else if (match[9]) {
|
107
|
-
currentSelectorItem.
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
114
|
+
} else if (match[9] && match[11] !== undefined) {
|
115
|
+
currentSelectorItem.attributes = currentSelectorItem.attributes || [];
|
116
|
+
currentSelectorItem.attributes.push({
|
117
|
+
name: match[9].toLowerCase(),
|
118
|
+
operator: match[10] || null,
|
119
|
+
value: match[11]
|
120
|
+
});
|
121
|
+
} else if (match[12] && match[13]) {
|
122
|
+
currentSelectorItem.pseudos = currentSelectorItem.pseudos || [];
|
123
|
+
currentSelectorItem.pseudos.push({
|
124
|
+
name: match[12].toLowerCase(),
|
125
|
+
arguments: match[13]
|
126
|
+
});
|
127
|
+
} else if (match[14]) {
|
128
|
+
currentSelectorItem.pseudos = currentSelectorItem.pseudos || [];
|
129
|
+
currentSelectorItem.pseudos.push({
|
130
|
+
name: match[14].toLowerCase(),
|
131
|
+
arguments: null
|
132
|
+
});
|
133
|
+
} else if (match[15]) {
|
134
|
+
switch (match[15].trim()) {
|
112
135
|
case ',':
|
113
136
|
currentSelectorItem = new SelectorItem({
|
114
137
|
combinator: SelectorCombinatorEnum.descendant
|