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.

Files changed (32) hide show
  1. package/lib/nodes/element/Element.d.ts +12 -0
  2. package/lib/nodes/element/Element.d.ts.map +1 -1
  3. package/lib/nodes/element/Element.js +16 -0
  4. package/lib/nodes/element/Element.js.map +1 -1
  5. package/lib/nodes/element/IElement.d.ts +1 -0
  6. package/lib/nodes/element/IElement.d.ts.map +1 -1
  7. package/lib/nodes/html-input-element/HTMLInputElement.d.ts +6 -0
  8. package/lib/nodes/html-input-element/HTMLInputElement.d.ts.map +1 -1
  9. package/lib/nodes/html-input-element/HTMLInputElement.js +19 -11
  10. package/lib/nodes/html-input-element/HTMLInputElement.js.map +1 -1
  11. package/lib/query-selector/ISelectorPseudo.d.ts +5 -0
  12. package/lib/query-selector/ISelectorPseudo.d.ts.map +1 -0
  13. package/lib/query-selector/ISelectorPseudo.js +3 -0
  14. package/lib/query-selector/ISelectorPseudo.js.map +1 -0
  15. package/lib/query-selector/QuerySelector.d.ts.map +1 -1
  16. package/lib/query-selector/QuerySelector.js +5 -0
  17. package/lib/query-selector/QuerySelector.js.map +1 -1
  18. package/lib/query-selector/SelectorItem.d.ts +4 -9
  19. package/lib/query-selector/SelectorItem.d.ts.map +1 -1
  20. package/lib/query-selector/SelectorItem.js +84 -78
  21. package/lib/query-selector/SelectorItem.js.map +1 -1
  22. package/lib/query-selector/SelectorParser.d.ts.map +1 -1
  23. package/lib/query-selector/SelectorParser.js +37 -14
  24. package/lib/query-selector/SelectorParser.js.map +1 -1
  25. package/package.json +1 -1
  26. package/src/nodes/element/Element.ts +18 -0
  27. package/src/nodes/element/IElement.ts +1 -0
  28. package/src/nodes/html-input-element/HTMLInputElement.ts +22 -13
  29. package/src/query-selector/ISelectorPseudo.ts +4 -0
  30. package/src/query-selector/QuerySelector.ts +6 -0
  31. package/src/query-selector/SelectorItem.ts +99 -92
  32. 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 pseudoClass: string | null;
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.pseudoClass] Pseudo class.
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
- pseudoClass?: string;
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.pseudoClass = options?.pseudoClass || null;
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.pseudoClass && !this.matchPsuedo(element)) {
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
- // Validation
117
- switch (this.pseudoClass) {
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
- // Check if parent exists
130
- if (!parent) {
131
- switch (this.pseudoClass) {
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 false;
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
- switch (this.pseudoClass) {
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.all || ''}${this.tagName || ''}${this.id ? `#${this.id}` : ''}${
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
- }${this.pseudoClass || ''}${this.pseudoArguments ? `(${this.pseudoArguments})` : ''}`;
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: Pseudo (e.g. "nth-child")
17
- * Group 10: Arguments of pseudo (e.g. "2n + 1")
18
- * Group 11: Combinator.
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}) *= *["']{0,1}([^"']*)["']{0,1}\]|:([a-zA-Z-:]+)|\(([^)]+)\)|([ ,+>]*)/g;
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.all = '*';
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.pseudoClass = match[9].toLowerCase();
108
- } else if (match[10]) {
109
- currentSelectorItem.pseudoArguments = match[10];
110
- } else if (match[11]) {
111
- switch (match[11].trim()) {
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