View Javadoc
1   /*
2    * Copyright (c) 2002-2025 Gargoyle Software Inc.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    * https://www.apache.org/licenses/LICENSE-2.0
8    *
9    * Unless required by applicable law or agreed to in writing, software
10   * distributed under the License is distributed on an "AS IS" BASIS,
11   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12   * See the License for the specific language governing permissions and
13   * limitations under the License.
14   */
15  package org.htmlunit.javascript.host.css;
16  
17  import org.htmlunit.WebDriverTestCase;
18  import org.htmlunit.junit.annotation.Alerts;
19  import org.junit.jupiter.api.Test;
20  
21  /**
22   * Tests for css pseudo selectors :not(), :is(), :where() and :has().
23   *
24   * @author Ronald Brill
25   *
26   */
27  public class CSSSelector3Test extends WebDriverTestCase {
28  
29      /**
30       * @throws Exception if the test fails
31       */
32      @Test
33      @Alerts({"6", "[object HTMLBodyElement]", "[object HTMLLIElement]", "[object HTMLScriptElement]",
34               "5", "[object HTMLBodyElement]", "[object HTMLScriptElement]",
35               "5", "[object HTMLBodyElement]", "[object HTMLScriptElement]"})
36      public void notElement() throws Exception {
37          final String html = DOCTYPE_HTML
38                  + "<html>\n"
39                  + "<head><title></title></head>\n"
40                  + "<body>\n"
41                  + "<ul>\n"
42                  + "  <li>ul - item 0</li>\n"
43                  + "</ul>\n"
44  
45                  + "<script>\n"
46                  + LOG_TITLE_FUNCTION
47                  + "  try {\n"
48                  + "    let items = document.querySelectorAll(':not(ul)');"
49                  + "    log(items.length);\n"
50                  + "    log(items[3]);\n"
51                  + "    log(items[4]);\n"
52                  + "    log(items[5]);\n"
53  
54                  + "    items = document.querySelectorAll(':not(ul, li)');"
55                  + "    log(items.length);\n"
56                  + "    log(items[3]);\n"
57                  + "    log(items[4]);\n"
58  
59                  + "    items = document.querySelectorAll(':not(ul):not(li)');"
60                  + "    log(items.length);\n"
61                  + "    log(items[3]);\n"
62                  + "    log(items[4]);\n"
63                  + "  } catch (e) { logEx(e); }\n"
64                  + "</script>\n"
65  
66                  + "</body></html>";
67  
68          loadPageVerifyTitle2(html);
69      }
70  
71      /**
72       * @throws Exception if the test fails
73       */
74      @Test
75      @Alerts("0")
76      public void notStar() throws Exception {
77          final String html = DOCTYPE_HTML
78                  + "<html>\n"
79                  + "<head><title></title></head>\n"
80                  + "<body>\n"
81                  + "<ul>\n"
82                  + "  <li>ul - item 0</li>\n"
83                  + "</ul>\n"
84  
85                  + "<script>\n"
86                  + LOG_TITLE_FUNCTION
87                  + "  try {\n"
88                  + "    let items = document.querySelectorAll(':not(*)');"
89                  + "    log(items.length);\n"
90                  + "  } catch (e) { logEx(e); }\n"
91                  + "</script>\n"
92  
93                  + "</body></html>";
94  
95          loadPageVerifyTitle2(html);
96      }
97  
98      /**
99       * @throws Exception if the test fails
100      */
101     @Test
102     @Alerts({"1", "[object HTMLUListElement]"})
103     public void notId() throws Exception {
104         final String html = DOCTYPE_HTML
105                 + "<html>\n"
106                 + "<head><title></title></head>\n"
107                 + "<body>\n"
108                 + "<ul id='foo'>\n"
109                 + "  <li>ul - item 0</li>\n"
110                 + "</ul>\n"
111 
112                 + "<script>\n"
113                 + LOG_TITLE_FUNCTION
114                 + "  try {\n"
115                 + "    let items = document.querySelectorAll('#foo:not(#bar)');"
116                 + "    log(items.length);\n"
117                 + "    log(items[0]);\n"
118                 + "  } catch (e) { logEx(e); }\n"
119                 + "</script>\n"
120 
121                 + "</body></html>";
122 
123         loadPageVerifyTitle2(html);
124     }
125 
126     /**
127      * @throws Exception if the test fails
128      */
129     @Test
130     @Alerts({"1", "<a>my</a>", "0"})
131     public void notTable() throws Exception {
132         final String html = DOCTYPE_HTML
133                 + "<html>\n"
134                 + "<head><title></title></head>\n"
135                 + "<body>\n"
136                 + "<table>\n"
137                 + "  <tr><td><a>my</a></td></tr>\n"
138                 + "</table>\n"
139 
140                 + "<script>\n"
141                 + LOG_TITLE_FUNCTION
142                 + "  try {\n"
143                 + "    let items = document.querySelectorAll('body :not(table) a');"
144                 + "    log(items.length);\n"
145                 + "    log(items[0].outerHTML);\n"
146 
147                 + "    items = document.querySelectorAll('body a:not(table a)');"
148                 + "    log(items.length);\n"
149                 + "  } catch (e) { logEx(e); }\n"
150                 + "</script>\n"
151 
152                 + "</body></html>";
153 
154         loadPageVerifyTitle2(html);
155     }
156 
157     /**
158      * @throws Exception if the test fails
159      */
160     @Test
161     @Alerts("SyntaxError/DOMException")
162     public void notDoubleColon() throws Exception {
163         final String html = DOCTYPE_HTML
164                 + "<html>\n"
165                 + "<head><title></title></head>\n"
166                 + "<body>\n"
167                 + "<ul>\n"
168                 + "  <li>ul - item 0</li>\n"
169                 + "</ul>\n"
170 
171                 + "<script>\n"
172                 + LOG_TITLE_FUNCTION
173                 + "  try {\n"
174                 + "    let items = document.querySelectorAll('::not(ul)');"
175                 + "  } catch (e) { logEx(e); }\n"
176                 + "</script>\n"
177 
178                 + "</body></html>";
179 
180         loadPageVerifyTitle2(html);
181     }
182 
183     /**
184      * @throws Exception if the test fails
185      */
186     @Test
187     @Alerts({"1", "[object HTMLUListElement]",
188              "2", "[object HTMLUListElement]", "[object HTMLOListElement]"})
189     public void isElement() throws Exception {
190         final String html = DOCTYPE_HTML
191                 + "<html>\n"
192                 + "<head></head>\n"
193                 + "<body>\n"
194                 + "<ul>\n"
195                 + "  <li>ul - item 0</li>\n"
196                 + "  <li>ul - item 1</li>\n"
197                 + "</ul>\n"
198                 + "<ol>\n"
199                 + "  <li>ol - item 0</li>\n"
200                 + "  <li>ol - item 1</li>\n"
201                 + "</ol>\n"
202 
203                 + "<script>\n"
204                 + LOG_TITLE_FUNCTION
205                 + "  try {\n"
206                 + "    let items = document.querySelectorAll(':is(ul)');"
207                 + "    log(items.length);\n"
208                 + "    log(items[0]);\n"
209 
210                 + "    items = document.querySelectorAll(':is(ul, ol)');"
211                 + "    log(items.length);\n"
212                 + "    log(items[0]);\n"
213                 + "    log(items[1]);\n"
214                 + "  } catch (e) { logEx(e); }\n"
215                 + "</script>\n"
216 
217                 + "</body></html>";
218 
219         loadPageVerifyTitle2(html);
220     }
221 
222     /**
223      * @throws Exception if the test fails
224      */
225     @Test
226     @Alerts({"2", "ul - item 1", "ol - item 1"})
227     public void isAttribute() throws Exception {
228         final String html = DOCTYPE_HTML
229                 + "<html>\n"
230                 + "<head></head>\n"
231                 + "<body>\n"
232                 + "<ul>\n"
233                 + "  <li name='i0'>ul - item 0</li>\n"
234                 + "  <li name='i1'>ul - item 1</li>\n"
235                 + "</ul>\n"
236                 + "<ol>\n"
237                 + "  <li name='i7'>ol - item 0</li>\n"
238                 + "  <li name='i2'>ol - item 1</li>\n"
239                 + "</ol>\n"
240 
241                 + "<script>\n"
242                 + LOG_TITLE_FUNCTION
243                 + "  try {\n"
244                 + "    let items = document.querySelectorAll(\":is([name='i1'], [name='i2'])\");"
245                 + "    log(items.length);\n"
246                 + "    log(items[0].innerText);\n"
247                 + "    log(items[1].innerText);\n"
248                 + "  } catch (e) { logEx(e); }\n"
249                 + "</script>\n"
250 
251                 + "</body></html>";
252 
253         loadPageVerifyTitle2(html);
254     }
255 
256     /**
257      * @throws Exception if the test fails
258      */
259     @Test
260     @Alerts({"4", "ul - item 0 ul - item 1", "ul - item 1",
261              "ol - item 0 ol - item 1", "ol - item 1"})
262     public void isDuplicates() throws Exception {
263         final String html = DOCTYPE_HTML
264                 + "<html>\n"
265                 + "<head></head>\n"
266                 + "<body>\n"
267                 + "<ul>\n"
268                 + "  <li name='i0'>ul - item 0</li>\n"
269                 + "  <li name='i1'>ul - item 1</li>\n"
270                 + "</ul>\n"
271                 + "<ol name='i1'>\n"
272                 + "  <li name='i7'>ol - item 0</li>\n"
273                 + "  <li name='i2'>ol - item 1</li>\n"
274                 + "</ol>\n"
275 
276                 + "<script>\n"
277                 + LOG_TITLE_FUNCTION
278                 + "  try {\n"
279                 + "    let items = document.querySelectorAll(\":is(ul, [name='i1'], [name='i2'])\");"
280                 + "    log(items.length);\n"
281                 + "    log(items[0].innerText);\n"
282                 + "    log(items[1].innerText);\n"
283                 + "    log(items[2].innerText);\n"
284                 + "    log(items[3].innerText);\n"
285                 + "  } catch (e) { logEx(e); }\n"
286                 + "</script>\n"
287 
288                 + "</body></html>";
289 
290         loadPageVerifyTitle2(html);
291     }
292 
293     /**
294      * @throws Exception if the test fails
295      */
296     @Test
297     @Alerts("SyntaxError/DOMException")
298     public void isDoubleColon() throws Exception {
299         final String html = DOCTYPE_HTML
300                 + "<html>\n"
301                 + "<head><title></title></head>\n"
302                 + "<body>\n"
303                 + "<ul>\n"
304                 + "  <li>ul - item 0</li>\n"
305                 + "</ul>\n"
306 
307                 + "<script>\n"
308                 + LOG_TITLE_FUNCTION
309                 + "  try {\n"
310                 + "    let items = document.querySelectorAll('::is(ul)');"
311                 + "  } catch (e) { logEx(e); }\n"
312                 + "</script>\n"
313 
314                 + "</body></html>";
315 
316         loadPageVerifyTitle2(html);
317     }
318 
319     /**
320      * @throws Exception if the test fails
321      */
322     @Test
323     @Alerts({"1", "[object HTMLUListElement]",
324              "2", "[object HTMLUListElement]", "[object HTMLOListElement]"})
325     public void whereElement() throws Exception {
326         final String html = DOCTYPE_HTML
327                 + "<html>\n"
328                 + "<head></head>\n"
329                 + "<body>\n"
330                 + "<ul>\n"
331                 + "  <li>ul - item 0</li>\n"
332                 + "  <li>ul - item 1</li>\n"
333                 + "</ul>\n"
334                 + "<ol>\n"
335                 + "  <li>ol - item 0</li>\n"
336                 + "  <li>ol - item 1</li>\n"
337                 + "</ol>\n"
338 
339                 + "<script>\n"
340                 + LOG_TITLE_FUNCTION
341                 + "  try {\n"
342                 + "    let items = document.querySelectorAll(':where(ul)');"
343                 + "    log(items.length);\n"
344                 + "    log(items[0]);\n"
345 
346                 + "    items = document.querySelectorAll(':where(ul, ol)');"
347                 + "    log(items.length);\n"
348                 + "    log(items[0]);\n"
349                 + "    log(items[1]);\n"
350                 + "  } catch (e) { logEx(e); }\n"
351                 + "</script>\n"
352 
353                 + "</body></html>";
354 
355         loadPageVerifyTitle2(html);
356     }
357 
358     /**
359      * @throws Exception if the test fails
360      */
361     @Test
362     @Alerts({"2", "ul - item 1", "ol - item 1"})
363     public void whereAttribute() throws Exception {
364         final String html = DOCTYPE_HTML
365                 + "<html>\n"
366                 + "<head></head>\n"
367                 + "<body>\n"
368                 + "<ul>\n"
369                 + "  <li name='i0'>ul - item 0</li>\n"
370                 + "  <li name='i1'>ul - item 1</li>\n"
371                 + "</ul>\n"
372                 + "<ol>\n"
373                 + "  <li name='i7'>ol - item 0</li>\n"
374                 + "  <li name='i2'>ol - item 1</li>\n"
375                 + "</ol>\n"
376 
377                 + "<script>\n"
378                 + LOG_TITLE_FUNCTION
379                 + "  try {\n"
380                 + "    let items = document.querySelectorAll(\":where([name='i1'], [name='i2'])\");"
381                 + "    log(items.length);\n"
382                 + "    log(items[0].innerText);\n"
383                 + "    log(items[1].innerText);\n"
384                 + "  } catch (e) { logEx(e); }\n"
385                 + "</script>\n"
386 
387                 + "</body></html>";
388 
389         loadPageVerifyTitle2(html);
390     }
391 
392     /**
393      * @throws Exception if the test fails
394      */
395     @Test
396     @Alerts({"4", "ul - item 0 ul - item 1", "ul - item 1",
397              "ol - item 0 ol - item 1", "ol - item 1"})
398     public void whereDuplicates() throws Exception {
399         final String html = DOCTYPE_HTML
400                 + "<html>\n"
401                 + "<head></head>\n"
402                 + "<body>\n"
403                 + "<ul>\n"
404                 + "  <li name='i0'>ul - item 0</li>\n"
405                 + "  <li name='i1'>ul - item 1</li>\n"
406                 + "</ul>\n"
407                 + "<ol name='i1'>\n"
408                 + "  <li name='i7'>ol - item 0</li>\n"
409                 + "  <li name='i2'>ol - item 1</li>\n"
410                 + "</ol>\n"
411 
412                 + "<script>\n"
413                 + LOG_TITLE_FUNCTION
414                 + "  try {\n"
415                 + "    let items = document.querySelectorAll(\":where(ul, [name='i1'], [name='i2'])\");"
416                 + "    log(items.length);\n"
417                 + "    log(items[0].innerText);\n"
418                 + "    log(items[1].innerText);\n"
419                 + "    log(items[2].innerText);\n"
420                 + "    log(items[3].innerText);\n"
421                 + "  } catch (e) { logEx(e); }\n"
422                 + "</script>\n"
423 
424                 + "</body></html>";
425 
426         loadPageVerifyTitle2(html);
427     }
428 
429     /**
430      * @throws Exception if the test fails
431      */
432     @Test
433     @Alerts("SyntaxError/DOMException")
434     public void whereDoubleColon() throws Exception {
435         final String html = DOCTYPE_HTML
436                 + "<html>\n"
437                 + "<head><title></title></head>\n"
438                 + "<body>\n"
439                 + "<ul>\n"
440                 + "  <li>ul - item 0</li>\n"
441                 + "</ul>\n"
442 
443                 + "<script>\n"
444                 + LOG_TITLE_FUNCTION
445                 + "  try {\n"
446                 + "    let items = document.querySelectorAll('::where(ul)');"
447                 + "  } catch (e) { logEx(e); }\n"
448                 + "</script>\n"
449 
450                 + "</body></html>";
451 
452         loadPageVerifyTitle2(html);
453     }
454 
455     /**
456      * @throws Exception if the test fails
457      */
458     @Test
459     @Alerts({"1", "SECTION / a0 a1"})
460     public void hasDescandant() throws Exception {
461         final String html = DOCTYPE_HTML
462                 + "<html>\n"
463                 + "<head></head>\n"
464                 + "<body>\n"
465                 + "<section>\n"
466                 + "  <article class='featured'>a0</article>\n"
467                 + "  <article>a1</article>\n"
468                 + "</section>\n"
469                 + "<section>\n"
470                 + "  <article>a2</article>\r\n"
471                 + "</section>\n"
472 
473                 + "<script>\n"
474                 + LOG_TITLE_FUNCTION
475                 + "  try {\n"
476                 + "    let items = document.querySelectorAll(\"section:has(.featured)\");"
477                 + "    log(items.length);\n"
478                 + "    log(items[0].tagName + ' / ' + items[0].innerText);\n"
479                 + "  } catch (e) { logEx(e); }\n"
480                 + "</script>\n"
481 
482                 + "</body></html>";
483 
484         loadPageVerifyTitle2(html);
485     }
486 
487     /**
488      * @throws Exception if the test fails
489      */
490     @Test
491     @Alerts({"1", "SECTION / a0 a1"})
492     public void hasDescandantDeep() throws Exception {
493         final String html = DOCTYPE_HTML
494                 + "<html>\n"
495                 + "<head></head>\n"
496                 + "<body>\n"
497                 + "<section>\n"
498                 + "  <div>\n"
499                 + "    <article class='featured'>a0</article>\n"
500                 + "    <article>a1</article>\n"
501                 + "  </div>\n"
502                 + "</section>\n"
503 
504                 + "<script>\n"
505                 + LOG_TITLE_FUNCTION
506                 + "  try {\n"
507                 + "    let items = document.querySelectorAll(\"section:has(.featured)\");"
508                 + "    log(items.length);\n"
509                 + "    log(items[0].tagName + ' / ' + items[0].innerText);\n"
510                 + "  } catch (e) { logEx(e); }\n"
511                 + "</script>\n"
512 
513                 + "</body></html>";
514 
515         loadPageVerifyTitle2(html);
516     }
517 
518     /**
519      * @throws Exception if the test fails
520      */
521     @Test
522     @Alerts({"1", "SECTION / a0 a1"})
523     public void hasChild() throws Exception {
524         final String html = DOCTYPE_HTML
525                 + "<html>\n"
526                 + "<head></head>\n"
527                 + "<body>\n"
528                 + "<section>\n"
529                 + "  <article class='featured'>a0</article>\n"
530                 + "  <article>a1</article>\n"
531                 + "</section>\n"
532                 + "<section>\n"
533                 + "  <article>a2</article>\r\n"
534                 + "</section>\n"
535 
536                 + "<script>\n"
537                 + LOG_TITLE_FUNCTION
538                 + "  try {\n"
539                 + "    let items = document.querySelectorAll(\"section:has(> .featured)\");"
540                 + "    log(items.length);\n"
541                 + "    log(items[0].tagName + ' / ' + items[0].innerText);\n"
542                 + "  } catch (e) { logEx(e); }\n"
543                 + "</script>\n"
544 
545                 + "</body></html>";
546 
547         loadPageVerifyTitle2(html);
548     }
549 
550     /**
551      * @throws Exception if the test fails
552      */
553     @Test
554     @Alerts("0")
555     public void hasChildDeep() throws Exception {
556         final String html = DOCTYPE_HTML
557                 + "<html>\n"
558                 + "<head></head>\n"
559                 + "<body>\n"
560                 + "<section>\n"
561                 + "  <div>\n"
562                 + "    <article class='featured'>a0</article>\n"
563                 + "    <article>a1</article>\n"
564                 + "  </div>\n"
565                 + "</section>\n"
566 
567                 + "<script>\n"
568                 + LOG_TITLE_FUNCTION
569                 + "  try {\n"
570                 + "    let items = document.querySelectorAll(\"section:has(> .featured)\");"
571                 + "    log(items.length);\n"
572                 + "  } catch (e) { logEx(e); }\n"
573                 + "</script>\n"
574 
575                 + "</body></html>";
576 
577         loadPageVerifyTitle2(html);
578     }
579 
580     /**
581      * @throws Exception if the test fails
582      */
583     @Test
584     @Alerts({"1", "H1 / h1 1"})
585     public void hasNextSiblingCombinator() throws Exception {
586         final String html = DOCTYPE_HTML
587                 + "<html>\n"
588                 + "<head></head>\n"
589                 + "<body>\n"
590                 + "<section>\n"
591                 + "  <article>\n"
592                 + "    <h1>h1 0</h1>\n"
593                 + "    <p>p0</p>\n"
594                 + "  </article>\n"
595                 + "  <article>\n"
596                 + "    <h1>h1 1</h1>\n"
597                 + "    <h2>h2 0</h2>\n"
598                 + "    <p>p1</p>\n"
599                 + "  </article>\n"
600                 + "  <article>\n"
601                 + "    <h1>h1 2</h1>\n"
602                 + "    <p>p2</p>\n"
603                 + "    <h2>h2 1</h2>\n"
604                 + "  </article>\n"
605                 + "</section>\n"
606                 + "<section>\n"
607                 + "  <article>a2</article>\r\n"
608                 + "</section>\n"
609 
610                 + "<script>\n"
611                 + LOG_TITLE_FUNCTION
612                 + "  try {\n"
613                 + "    let items = document.querySelectorAll(\"h1:has(+ h2)\");"
614                 + "    log(items.length);\n"
615                 + "    log(items[0].tagName + ' / ' + items[0].innerText);\n"
616                 + "  } catch (e) { logEx(e); }\n"
617                 + "</script>\n"
618 
619                 + "</body></html>";
620 
621         loadPageVerifyTitle2(html);
622     }
623 
624     /**
625      * @throws Exception if the test fails
626      */
627     @Test
628     @Alerts({"2", "H1 / h1 1", "H1 / h1 2"})
629     public void hasSubsequentSiblingCombinator() throws Exception {
630         final String html = DOCTYPE_HTML
631                 + "<html>\n"
632                 + "<head></head>\n"
633                 + "<body>\n"
634                 + "<section>\n"
635                 + "  <article>\n"
636                 + "    <h1>h1 0</h1>\n"
637                 + "    <p>p0</p>\n"
638                 + "  </article>\n"
639                 + "  <article>\n"
640                 + "    <h1>h1 1</h1>\n"
641                 + "    <h2>h2 0</h2>\n"
642                 + "    <p>p1</p>\n"
643                 + "  </article>\n"
644                 + "  <article>\n"
645                 + "    <h1>h1 2</h1>\n"
646                 + "    <p>p2</p>\n"
647                 + "    <h2>h2 1</h2>\n"
648                 + "  </article>\n"
649                 + "</section>\n"
650                 + "<section>\n"
651                 + "  <article>a2</article>\r\n"
652                 + "</section>\n"
653 
654                 + "<script>\n"
655                 + LOG_TITLE_FUNCTION
656                 + "  try {\n"
657                 + "    let items = document.querySelectorAll(\"h1:has(~ h2)\");"
658                 + "    log(items.length);\n"
659                 + "    log(items[0].tagName + ' / ' + items[0].innerText);\n"
660                 + "    log(items[1].tagName + ' / ' + items[1].innerText);\n"
661                 + "  } catch (e) { logEx(e); }\n"
662                 + "</script>\n"
663 
664                 + "</body></html>";
665 
666         loadPageVerifyTitle2(html);
667     }
668 
669     /**
670      * @throws Exception if the test fails
671      */
672     @Test
673     @Alerts({"2", "H1 / h1 1", "H2 / h2 0",
674              "2", "H1 / h1 1", "H2 / h2 0"})
675     public void hasIs() throws Exception {
676         final String html = DOCTYPE_HTML
677                 + "<html>\n"
678                 + "<head></head>\n"
679                 + "<body>\n"
680                 + "<section>\n"
681                 + "  <article>\n"
682                 + "    <h1>h1 0</h1>\n"
683                 + "    <p>p0</p>\n"
684                 + "  </article>\n"
685                 + "  <article>\n"
686                 + "    <h1>h1 1</h1>\n"
687                 + "    <h2>h2 0</h2>\n"
688                 + "    <h3>h3 0</h3>\n"
689                 + "    <p>p1</p>\n"
690                 + "  </article>\n"
691                 + "</section>\n"
692                 + "<section>\n"
693                 + "  <article>a2</article>\r\n"
694                 + "</section>\n"
695 
696                 + "<script>\n"
697                 + LOG_TITLE_FUNCTION
698                 + "  try {\n"
699                 + "    let items = document.querySelectorAll(\":is(h1, h2, h3):has(+ :is(h2, h3, h4))\");"
700                 + "    log(items.length);\n"
701                 + "    log(items[0].tagName + ' / ' + items[0].innerText);\n"
702                 + "    log(items[1].tagName + ' / ' + items[1].innerText);\n"
703 
704                 + "    items = document.querySelectorAll(\":is(h1, h2, h3):has(+ h2, + h3, + h4)\");"
705                 + "    log(items.length);\n"
706                 + "    log(items[0].tagName + ' / ' + items[0].innerText);\n"
707                 + "    log(items[1].tagName + ' / ' + items[1].innerText);\n"
708                 + "  } catch (e) { logEx(e); }\n"
709                 + "</script>\n"
710 
711                 + "</body></html>";
712 
713         loadPageVerifyTitle2(html);
714     }
715 
716     /**
717      * @throws Exception if the test fails
718      */
719     @Test
720     @Alerts({"2", "ARTICLE / h1 0", "ARTICLE / p0"})
721     public void hasOr() throws Exception {
722         final String html = DOCTYPE_HTML
723                 + "<html>\n"
724                 + "<head></head>\n"
725                 + "<body>\n"
726                 + "<section>\n"
727                 + "  <article>\n"
728                 + "    <h1>h1 0</h1>\n"
729                 + "  </article>\n"
730                 + "  <article>\n"
731                 + "    <p>p0</p>\n"
732                 + "  </article>\n"
733                 + "  <article>\n"
734                 + "    <div>div0</div>\n"
735                 + "  </article>\n"
736                 + "</section>\n"
737 
738                 + "<script>\n"
739                 + LOG_TITLE_FUNCTION
740                 + "  try {\n"
741                 + "    let items = document.querySelectorAll(\"article:has(h1, p)\");"
742                 + "    log(items.length);\n"
743                 + "    log(items[0].tagName + ' / ' + items[0].innerText);\n"
744                 + "    log(items[1].tagName + ' / ' + items[1].innerText);\n"
745                 + "  } catch (e) { logEx(e); }\n"
746                 + "</script>\n"
747 
748                 + "</body></html>";
749 
750         loadPageVerifyTitle2(html);
751     }
752 
753     /**
754      * @throws Exception if the test fails
755      */
756     @Test
757     @Alerts({"1", "ARTICLE / h1 1 p1"})
758     public void hasAnd() throws Exception {
759         final String html = DOCTYPE_HTML
760                 + "<html>\n"
761                 + "<head></head>\n"
762                 + "<body>\n"
763                 + "<section>\n"
764                 + "  <article>\n"
765                 + "    <h1>h1 0</h1>\n"
766                 + "  </article>\n"
767                 + "  <article>\n"
768                 + "    <p>p0</p>\n"
769                 + "  </article>\n"
770                 + "  <article>\n"
771                 + "    <h1>h1 1</h1>\n"
772                 + "    <p>p1</p>\n"
773                 + "  </article>\n"
774                 + "  <article>\n"
775                 + "    <div>div0</div>\n"
776                 + "  </article>\n"
777                 + "</section>\n"
778 
779                 + "<script>\n"
780                 + LOG_TITLE_FUNCTION
781                 + "  try {\n"
782                 + "    let items = document.querySelectorAll(\"article:has(h1):has(p)\");"
783                 + "    log(items.length);\n"
784                 + "    log(items[0].tagName + ' / ' + items[0].innerText);\n"
785                 + "  } catch (e) { logEx(e); }\n"
786                 + "</script>\n"
787 
788                 + "</body></html>";
789 
790         loadPageVerifyTitle2(html);
791     }
792 
793 
794     /**
795      * @throws Exception if the test fails
796      */
797     @Test
798     @Alerts({"0", "SyntaxError/DOMException"})
799     public void hasSizzleJQuery182InvalidContains() throws Exception {
800         final String html = DOCTYPE_HTML
801                 + "<html>\n"
802                 + "<head></head>\n"
803                 + "<body>\n"
804 
805                 + "<script>\n"
806                 + LOG_TITLE_FUNCTION
807                 + "  try {\n"
808                 + "    items = document.querySelectorAll(\"#form select:has(option:first-child)\");"
809                 + "    log(items.length);\n"
810 
811                 + "    items = document.querySelectorAll(\"#form select:has(option:first-child:contains('o'))\");"
812                 + "    log(items.length);\n"
813                 + "  } catch (e) { logEx(e); }\n"
814                 + "</script>\n"
815 
816                 + "</body></html>";
817 
818         loadPageVerifyTitle2(html);
819     }
820 
821     /**
822      * @throws Exception if the test fails
823      */
824     @Test
825     @Alerts("SyntaxError/DOMException")
826     public void hasDoubleColon() throws Exception {
827         final String html = DOCTYPE_HTML
828                 + "<html>\n"
829                 + "<head><title></title></head>\n"
830                 + "<body>\n"
831                 + "<ul>\n"
832                 + "  <li>ul - item 0</li>\n"
833                 + "</ul>\n"
834 
835                 + "<script>\n"
836                 + LOG_TITLE_FUNCTION
837                 + "  try {\n"
838                 + "    let items = document.querySelectorAll('::has(ul)');"
839                 + "  } catch (e) { logEx(e); }\n"
840                 + "</script>\n"
841 
842                 + "</body></html>";
843 
844         loadPageVerifyTitle2(html);
845     }
846 }