1use super::*;
4
5pub fn selftest() -> (usize, usize) {
8 let cases: &[(&str, &str, &str, bool)] = &[
9 (
11 "<input id='t' type='text'>",
12 "input[type=text]{zz:1}",
13 "t",
14 true,
15 ),
16 (
17 "<input id='t' type='text'>",
18 "input[type=checkbox]{zz:1}",
19 "t",
20 false,
21 ),
22 (
26 "<input id='t' type='CHECKBOX'>",
27 "input[type=\"checkbox\" i]{zz:1}",
28 "t",
29 true,
30 ),
31 (
33 "<input id='t' type='CHECKBOX'>",
34 "input[type=checkbox i]{zz:1}",
35 "t",
36 true,
37 ),
38 (
40 "<input id='t' type='CHECKBOX'>",
41 "input[type=checkbox]{zz:1}",
42 "t",
43 false,
44 ),
45 (
47 "<input id='t' type='CHECKBOX'>",
48 "input[type=\"checkbox\" s]{zz:1}",
49 "t",
50 false,
51 ),
52 (
53 "<div id='t' data-role='nav main'></div>",
54 "[data-role~=main]{zz:1}",
55 "t",
56 true,
57 ),
58 (
59 "<a id='t' href='https://x'></a>",
60 "[href^='https']{zz:1}",
61 "t",
62 true,
63 ),
64 (
65 "<a id='t' href='/page.html'></a>",
66 "[href$='.html']{zz:1}",
67 "t",
68 true,
69 ),
70 (
71 "<a id='t' href='/img/a.png'></a>",
72 "[href*='img']{zz:1}",
73 "t",
74 true,
75 ),
76 ("<div id='t' class='box'></div>", "[class]{zz:1}", "t", true),
77 ("<div id='t'></div>", "[class]{zz:1}", "t", false),
78 (
80 "<input id='t' type='checkbox' checked>",
81 "input:checked{zz:1}",
82 "t",
83 true,
84 ),
85 (
86 "<input id='t' type='checkbox'>",
87 "input:checked{zz:1}",
88 "t",
89 false,
90 ),
91 (
92 "<button id='t' disabled>x</button>",
93 "button:disabled{zz:1}",
94 "t",
95 true,
96 ),
97 (
98 "<button id='t'>x</button>",
99 "button:disabled{zz:1}",
100 "t",
101 false,
102 ),
103 (
105 "<button id='t' disabled>x</button>",
106 "button:enabled{zz:1}",
107 "t",
108 false,
109 ),
110 (
111 "<button id='t'>x</button>",
112 "button:enabled{zz:1}",
113 "t",
114 true,
115 ),
116 (
121 "<input id='t' type='checkbox' _indeterminate>",
122 "input:indeterminate{zz:1}",
123 "t",
124 true,
125 ),
126 (
132 "<input id='t' type='text' required _user_interacted>",
133 "input:user-invalid{zz:1}",
134 "t",
135 true,
136 ),
137 (
138 "<input id='t' type='text' required value='x' _user_interacted>",
139 "input:user-valid{zz:1}",
140 "t",
141 true,
142 ),
143 (
144 "<input id='t' type='text' required>",
145 "input:user-invalid{zz:1}",
146 "t",
147 false,
148 ),
149 (
153 "<div id='t'></div>",
154 "div:defined{zz:1}",
155 "t",
156 true,
157 ),
158 (
159 "<span id='t'></span>",
160 ":defined{zz:1}",
161 "t",
162 true,
163 ),
164 (
167 "<div id='t' _fullscreen></div>",
168 "div:fullscreen{zz:1}",
169 "t",
170 true,
171 ),
172 (
173 "<div id='t'></div>",
174 "div:fullscreen{zz:1}",
175 "t",
176 false,
177 ),
178 (
181 "<dialog id='t' open _modal></dialog>",
182 "dialog:modal{zz:1}",
183 "t",
184 true,
185 ),
186 (
187 "<dialog id='t' open></dialog>",
188 "dialog:modal{zz:1}",
189 "t",
190 false,
191 ),
192 (
193 "<div id='t' _fullscreen></div>",
194 "div:modal{zz:1}",
195 "t",
196 true,
197 ),
198 (
201 "<div id='t' popover _popover_open></div>",
202 "div:popover-open{zz:1}",
203 "t",
204 true,
205 ),
206 (
207 "<div id='t' popover></div>",
208 "div:popover-open{zz:1}",
209 "t",
210 false,
211 ),
212 (
213 "<input id='t' type='checkbox'>",
214 "input:indeterminate{zz:1}",
215 "t",
216 false,
217 ),
218 (
220 "<ul><li></li><li id='t'></li><li></li></ul>",
221 "li:nth-child(2){zz:1}",
222 "t",
223 true,
224 ),
225 (
226 "<ul><li id='t'></li><li></li></ul>",
227 "li:nth-child(odd){zz:1}",
228 "t",
229 true,
230 ),
231 (
232 "<ul><li></li><li id='t'></li></ul>",
233 "li:nth-child(odd){zz:1}",
234 "t",
235 false,
236 ),
237 (
238 "<ul><li></li><li id='t'></li><li></li><li></li></ul>",
239 "li:nth-child(even){zz:1}",
240 "t",
241 true,
242 ),
243 (
244 "<ul><li></li><li></li><li id='t'></li></ul>",
245 "li:nth-child(3){zz:1}",
246 "t",
247 true,
248 ),
249 (
250 "<ul><li></li><li></li><li id='t'></li></ul>",
251 "li:nth-last-child(1){zz:1}",
252 "t",
253 true,
254 ),
255 (
260 "<ul><li></li><li></li><li id='t'></li><li></li></ul>",
261 "li:nth-child(-n+3){zz:1}",
262 "t",
263 true,
264 ),
265 (
266 "<ul><li></li><li></li><li></li><li id='t'></li></ul>",
267 "li:nth-child(-n+3){zz:1}",
268 "t",
269 false,
270 ),
271 (
272 "<ul><li id='t'></li><li></li><li></li></ul>",
273 "li:nth-child(-n+3){zz:1}",
274 "t",
275 true,
276 ),
277 (
283 "<ul><li id='t'></li><li></li></ul>",
284 "li:nth-child(2n+1 of .foo){zz:1}",
285 "t",
286 false,
287 ),
288 (
290 "<ul><li class='foo'></li><li></li><li class='foo' id='t'></li><li class='foo'></li></ul>",
291 "li:nth-child(2n+1 of .foo){zz:1}",
292 "t",
293 false,
294 ),
295 (
296 "<ul><li class='foo'></li><li></li><li class='foo' id='t'></li><li class='foo'></li></ul>",
297 "li:nth-child(2n of .foo){zz:1}",
298 "t",
299 true,
300 ),
301 (
303 "<ul><li class='foo'></li><li></li><li class='foo' id='t'></li><li class='foo'></li></ul>",
304 "li:nth-last-child(1 of .foo){zz:1}",
305 "t",
306 false,
307 ),
308 (
309 "<ul><li class='foo'></li><li></li><li class='foo'></li><li class='foo' id='t'></li></ul>",
310 "li:nth-last-child(1 of .foo){zz:1}",
311 "t",
312 true,
313 ),
314 (
316 "<div><li></li><p></p><li></li><li id='t'></li></div>",
317 "li:nth-last-of-type(1){zz:1}",
318 "t",
319 true,
320 ),
321 (
322 "<div><li id='t'></li><p></p><li></li><li></li></div>",
323 "li:nth-last-of-type(1){zz:1}",
324 "t",
325 false,
326 ),
327 (
328 "<ul><li id='t'></li><li></li><li></li></ul>",
329 "li:nth-child(2n+1){zz:1}",
330 "t",
331 true,
332 ),
333 ("<p id='t' class='a'></p>", "p:not(.b){zz:1}", "t", true),
335 ("<p id='t' class='b'></p>", "p:not(.b){zz:1}", "t", false),
336 ("<div id='t'></div>", "*{zz:1}", "t", true),
338 (
340 "<ul><li class='x'></li><li id='t' class='x'></li></ul>",
341 ".x:nth-child(2){zz:1}",
342 "t",
343 true,
344 ),
345 (
347 "<div><h1></h1><p id='t'></p></div>",
348 "h1 + p{zz:1}",
349 "t",
350 true,
351 ),
352 (
353 "<div><h1></h1><span></span><p id='t'></p></div>",
354 "h1 + p{zz:1}",
355 "t",
356 false,
357 ),
358 (
359 "<div><h1></h1><span></span><p id='t'></p></div>",
360 "h1 ~ p{zz:1}",
361 "t",
362 true,
363 ),
364 (
365 "<div><p id='t'></p><h1></h1></div>",
366 "h1 ~ p{zz:1}",
367 "t",
368 false,
369 ),
370 (
371 "<ul><li class='a'></li><li id='t' class='b'></li></ul>",
372 ".a + .b{zz:1}",
373 "t",
374 true,
375 ),
376 (
378 "<input id='t' type='checkbox' checked>",
379 "input:checked{zz:1}",
380 "t",
381 true,
382 ),
383 (
384 "<input id='t' type='checkbox'>",
385 "input:checked{zz:1}",
386 "t",
387 false,
388 ),
389 (
390 "<input id='t' disabled>",
391 "input:disabled{zz:1}",
392 "t",
393 true,
394 ),
395 ("<input id='t'>", "input:disabled{zz:1}", "t", false),
396 (
397 "<input id='t' required>",
398 "input:required{zz:1}",
399 "t",
400 true,
401 ),
402 ("<input id='t'>", "input:optional{zz:1}", "t", true),
404 (
405 "<input id='t' required>",
406 "input:optional{zz:1}",
407 "t",
408 false,
409 ),
410 (
412 "<input id='t' required>",
413 "input:invalid{zz:1}",
414 "t",
415 true,
416 ),
417 (
418 "<input id='t' required value='x'>",
419 "input:invalid{zz:1}",
420 "t",
421 false,
422 ),
423 (
424 "<input id='t' required value='x'>",
425 "input:valid{zz:1}",
426 "t",
427 true,
428 ),
429 (
430 "<input id='t' min='1' max='10' value='20'>",
431 "input:invalid{zz:1}",
432 "t",
433 true,
434 ),
435 (
436 "<input id='t' min='1' max='10' value='5'>",
437 "input:valid{zz:1}",
438 "t",
439 true,
440 ),
441 (
446 "<input id='t' type='checkbox' required>",
447 "input:invalid{zz:1}",
448 "t",
449 true,
450 ),
451 (
452 "<input id='t' type='checkbox' required checked>",
453 "input:valid{zz:1}",
454 "t",
455 true,
456 ),
457 (
460 "<div><input id='t' type='radio' name='g' required><input type='radio' name='g' checked></div>",
461 "input:valid{zz:1}",
462 "t",
463 true,
464 ),
465 (
466 "<div><input id='t' type='radio' name='g' required><input type='radio' name='g'></div>",
467 "input:invalid{zz:1}",
468 "t",
469 true,
470 ),
471 (
472 "<input id='t' readonly>",
473 "input:readonly{zz:1}",
474 "t",
475 true,
476 ),
477 (
479 "<input id='t' readonly>",
480 "input:read-write{zz:1}",
481 "t",
482 false,
483 ),
484 (
485 "<input id='t'>",
486 "input:read-write{zz:1}",
487 "t",
488 true,
489 ),
490 (
492 "<input id='t' placeholder='name'>",
493 "input:placeholder-shown{zz:1}",
494 "t",
495 true,
496 ),
497 (
498 "<input id='t' placeholder='name' value='x'>",
499 "input:placeholder-shown{zz:1}",
500 "t",
501 false,
502 ),
503 ("<input id='t'>", "input:placeholder-shown{zz:1}", "t", false),
504 (
506 "<input id='t' type='number' min='1' max='10' value='5'>",
507 "input:in-range{zz:1}",
508 "t",
509 true,
510 ),
511 (
512 "<input id='t' type='number' min='1' max='10' value='50'>",
513 "input:in-range{zz:1}",
514 "t",
515 false,
516 ),
517 (
518 "<input id='t' type='number' min='1' max='10' value='50'>",
519 "input:out-of-range{zz:1}",
520 "t",
521 true,
522 ),
523 (
524 "<input id='t' type='number' min='1' max='10' value='5'>",
525 "input:out-of-range{zz:1}",
526 "t",
527 false,
528 ),
529 (
531 "<input id='t' type='number' value='5'>",
532 "input:in-range{zz:1}",
533 "t",
534 false,
535 ),
536 (
538 "<input id='t' type='checkbox' checked>",
539 "input:default{zz:1}",
540 "t",
541 true,
542 ),
543 (
544 "<input id='t' type='checkbox'>",
545 "input:default{zz:1}",
546 "t",
547 false,
548 ),
549 (
550 "<input id='t' type='text' checked>",
551 "input:default{zz:1}",
552 "t",
553 false,
554 ),
555 (
556 "<option id='t' selected>x</option>",
557 "option:default{zz:1}",
558 "t",
559 true,
560 ),
561 (
562 "<option id='t'>x</option>",
563 "option:default{zz:1}",
564 "t",
565 false,
566 ),
567 ("<a id='t' href='https://example.com'>x</a>", "a:link{zz:1}", "t", true),
570 ("<a id='t'>x</a>", "a:link{zz:1}", "t", false),
571 (
572 "<a id='t' href='https://example.com'>x</a>",
573 "a:visited{zz:1}",
574 "t",
575 false,
576 ),
577 (
580 "<div><p></p><span id='t'></span><span></span></div>",
581 "span:first-of-type{zz:1}",
582 "t",
583 true,
584 ),
585 (
586 "<div><span></span><p></p><span id='t'></span></div>",
587 "span:first-of-type{zz:1}",
588 "t",
589 false,
590 ),
591 (
592 "<div><span></span><span id='t'></span><p></p></div>",
593 "span:last-of-type{zz:1}",
594 "t",
595 true,
596 ),
597 (
598 "<div><p></p><span></span><span id='t'></span><span></span></div>",
599 "span:nth-of-type(2){zz:1}",
600 "t",
601 true,
602 ),
603 (
604 "<div><p id='t'></p><span></span></div>",
605 "p:only-of-type{zz:1}",
606 "t",
607 true,
608 ),
609 (
610 "<div><p id='t'></p><p></p></div>",
611 "p:only-of-type{zz:1}",
612 "t",
613 false,
614 ),
615 ("<div><p id='t'></p></div>", "p:only-child{zz:1}", "t", true),
616 (
617 "<div><p id='t'></p><span></span></div>",
618 "p:only-child{zz:1}",
619 "t",
620 false,
621 ),
622 ("<div id='t'></div>", "div:empty{zz:1}", "t", true),
623 (
624 "<div id='t'><span></span></div>",
625 "div:empty{zz:1}",
626 "t",
627 false,
628 ),
629 (
630 "<div id='t'>text</div>",
631 "div:empty{zz:1}",
632 "t",
633 false,
634 ),
635 (
637 "<p id='t' class='a'></p>",
638 ":is(.a, .b){zz:1}",
639 "t",
640 true,
641 ),
642 (
643 "<p id='t' class='c'></p>",
644 ":is(.a, .b){zz:1}",
645 "t",
646 false,
647 ),
648 (
649 "<div><section><p id='t'></p></section></div>",
650 ":is(section, article) p{zz:1}",
651 "t",
652 true,
653 ),
654 (
655 "<span id='t' class='x'></span>",
656 ":where(.x, .y){zz:1}",
657 "t",
658 true,
659 ),
660 (
662 "<div id='t'><span><img></span></div>",
663 "div:has(img){zz:1}",
664 "t",
665 true,
666 ),
667 (
668 "<div id='t'><span></span></div>",
669 "div:has(img){zz:1}",
670 "t",
671 false,
672 ),
673 (
674 "<div id='t'><p class='note'>x</p></div>",
675 "div:has(.note, .warn){zz:1}",
676 "t",
677 true,
678 ),
679 (
682 "<div id='t'><img></div>",
683 "div:has(> img){zz:1}",
684 "t",
685 true,
686 ),
687 (
688 "<div id='t'><span><img></span></div>",
689 "div:has(> img){zz:1}",
690 "t",
691 false,
692 ),
693 (
695 "<div><p id='t'></p><img></div>",
696 "p:has(+ img){zz:1}",
697 "t",
698 true,
699 ),
700 (
701 "<div><p id='t'></p><span></span><img></div>",
702 "p:has(+ img){zz:1}",
703 "t",
704 false,
705 ),
706 (
708 "<div><p id='t'></p><span></span><img></div>",
709 "p:has(~ img){zz:1}",
710 "t",
711 true,
712 ),
713 (
714 "<div><p id='t'></p><span></span></div>",
715 "p:has(~ img){zz:1}",
716 "t",
717 false,
718 ),
719 (
721 "<div class='card'><span id='t'></span></div>",
722 ".card { & span { zz:1; } }",
723 "t",
724 true,
725 ),
726 (
728 "<div class='card'><span id='t'></span></div>",
729 ".card { span { zz:1; } }",
730 "t",
731 true,
732 ),
733 (
735 "<div id='t' class='card'></div>",
736 ".card { &.active { zz:1; } }",
737 "t",
738 false,
739 ),
740 (
741 "<div id='t' class='card active'></div>",
742 ".card { &.active { zz:1; } }",
743 "t",
744 true,
745 ),
746 (
750 "<div id='t' class='card'></div>",
751 ".card { &:hover{color:blue;} zz:1; }",
752 "t",
753 true,
754 ),
755 (
758 "<div id='t'></div>",
759 "@supports (display: grid) { #t{zz:1} }",
760 "t",
761 true,
762 ),
763 (
764 "<div id='t'></div>",
765 "@supports not (display: grid) { #t{zz:1} }",
766 "t",
767 false,
768 ),
769 (
770 "<div id='t'></div>",
771 "@supports (display: grid) and (gap: 1rem) { #t{zz:1} }",
772 "t",
773 true,
774 ),
775 (
776 "<div id='t'></div>",
777 "@supports not (display: grid) or (gap: 1rem) { #t{zz:1} }",
778 "t",
779 true,
780 ),
781 (
783 "<div id='t'></div>",
784 "@media (prefers-color-scheme: light) { #t{zz:1} }",
785 "t",
786 true,
787 ),
788 (
789 "<div id='t'></div>",
790 "@media (prefers-color-scheme: dark) { #t{zz:1} }",
791 "t",
792 false,
793 ),
794 (
796 "<div id='t'></div>",
797 "@media (prefers-reduced-motion: no-preference) { #t{zz:1} }",
798 "t",
799 true,
800 ),
801 (
802 "<div id='t'></div>",
803 "@media (prefers-reduced-motion: reduce) { #t{zz:1} }",
804 "t",
805 false,
806 ),
807 (
810 "<div id='t'></div>",
811 "@media (prefers-contrast: no-preference) { #t{zz:1} }",
812 "t",
813 true,
814 ),
815 (
816 "<div id='t'></div>",
817 "@media (prefers-contrast: more) { #t{zz:1} }",
818 "t",
819 false,
820 ),
821 (
822 "<div id='t'></div>",
823 "@media (prefers-contrast: less) { #t{zz:1} }",
824 "t",
825 false,
826 ),
827 (
831 "<div id='t'></div>",
832 "@media (update: fast) { #t{zz:1} }",
833 "t",
834 true,
835 ),
836 (
837 "<div id='t'></div>",
838 "@media (update: none) { #t{zz:1} }",
839 "t",
840 false,
841 ),
842 (
843 "<div id='t'></div>",
844 "@media (grid: 0) { #t{zz:1} }",
845 "t",
846 true,
847 ),
848 (
849 "<div id='t'></div>",
850 "@media (grid: 1) { #t{zz:1} }",
851 "t",
852 false,
853 ),
854 (
855 "<div id='t'></div>",
856 "@media (prefers-reduced-transparency: no-preference) { #t{zz:1} }",
857 "t",
858 true,
859 ),
860 (
861 "<div id='t'></div>",
862 "@media (prefers-reduced-transparency: reduce) { #t{zz:1} }",
863 "t",
864 false,
865 ),
866 (
867 "<div id='t'></div>",
868 "@media (prefers-reduced-data: no-preference) { #t{zz:1} }",
869 "t",
870 true,
871 ),
872 (
873 "<div id='t'></div>",
874 "@media (prefers-reduced-data: reduce) { #t{zz:1} }",
875 "t",
876 false,
877 ),
878 (
883 "<div id='t'></div>",
884 "@media (pointer: fine) { #t{zz:1} }",
885 "t",
886 true,
887 ),
888 (
889 "<div id='t'></div>",
890 "@media (pointer: coarse) { #t{zz:1} }",
891 "t",
892 false,
893 ),
894 (
895 "<div id='t'></div>",
896 "@media (hover: hover) { #t{zz:1} }",
897 "t",
898 true,
899 ),
900 (
901 "<div id='t'></div>",
902 "@media (hover: none) { #t{zz:1} }",
903 "t",
904 false,
905 ),
906 (
910 "<div id='t'></div>",
911 "@media (scripting: enabled) { #t{zz:1} }",
912 "t",
913 true,
914 ),
915 (
916 "<div id='t'></div>",
917 "@media (scripting: none) { #t{zz:1} }",
918 "t",
919 false,
920 ),
921 (
922 "<div id='t'></div>",
923 "@media (forced-colors: none) { #t{zz:1} }",
924 "t",
925 true,
926 ),
927 (
928 "<div id='t'></div>",
929 "@media (forced-colors: active) { #t{zz:1} }",
930 "t",
931 false,
932 ),
933 (
938 "<div id='t'></div>",
939 "@media (min-resolution: 2dppx) { #t{zz:1} }",
940 "t",
941 false,
942 ),
943 (
944 "<div id='t'></div>",
945 "@media (min-resolution: 1dppx) { #t{zz:1} }",
946 "t",
947 true,
948 ),
949 (
950 "<div id='t'></div>",
951 "@media (max-resolution: 2dppx) { #t{zz:1} }",
952 "t",
953 true,
954 ),
955 (
956 "<div id='t'></div>",
957 "@media (min-resolution: 96dpi) { #t{zz:1} }",
958 "t",
959 true,
960 ),
961 (
963 "<div id='t'></div>",
964 "@media (monochrome) { #t{zz:1} }",
965 "t",
966 false,
967 ),
968 (
969 "<div id='t'></div>",
970 "@media (monochrome: 0) { #t{zz:1} }",
971 "t",
972 true,
973 ),
974 (
975 "<div id='t'></div>",
976 "@media (min-monochrome: 1) { #t{zz:1} }",
977 "t",
978 false,
979 ),
980 (
984 "<div id='t'></div>",
985 "@media screen and (min-width: 700px) { #t{zz:1} }",
986 "t",
987 true,
988 ),
989 (
990 "<div id='t'></div>",
991 "@media screen and (max-width: 700px) { #t{zz:1} }",
992 "t",
993 false,
994 ),
995 (
997 "<div id='t'></div>",
998 "@media (min-width: 700px) and (max-width: 900px) { #t{zz:1} }",
999 "t",
1000 true,
1001 ),
1002 (
1003 "<div id='t'></div>",
1004 "@media (min-width: 700px) and (max-width: 750px) { #t{zz:1} }",
1005 "t",
1006 false,
1007 ),
1008 (
1012 "<p id='t' lang='en-US'>x</p>",
1013 ":lang(en){zz:1}",
1014 "t",
1015 true,
1016 ),
1017 (
1018 "<p id='t' lang='fr'>x</p>",
1019 ":lang(en){zz:1}",
1020 "t",
1021 false,
1022 ),
1023 (
1024 "<p id='t' lang='EN'>x</p>",
1025 ":lang(en){zz:1}",
1026 "t",
1027 true,
1028 ),
1029 (
1032 "<p id='t' dir='rtl'>x</p>",
1033 ":dir(rtl){zz:1}",
1034 "t",
1035 true,
1036 ),
1037 (
1038 "<p id='t' dir='ltr'>x</p>",
1039 ":dir(rtl){zz:1}",
1040 "t",
1041 false,
1042 ),
1043 (
1044 "<p id='t'>x</p>",
1045 ":dir(ltr){zz:1}",
1046 "t",
1047 true,
1048 ),
1049 (
1051 "<input id='t' indeterminate>",
1052 "input:indeterminate{zz:1}",
1053 "t",
1054 true,
1055 ),
1056 (
1057 "<input id='t' checked>",
1058 "input:indeterminate{zz:1}",
1059 "t",
1060 false,
1061 ),
1062 (
1063 "<progress id='t'></progress>",
1064 "progress:indeterminate{zz:1}",
1065 "t",
1066 true,
1067 ),
1068 (
1069 "<progress id='t' value='50'></progress>",
1070 "progress:indeterminate{zz:1}",
1071 "t",
1072 false,
1073 ),
1074 ];
1075
1076 fn find_marked(node: &StyledNode, id: &str) -> Option<bool> {
1077 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
1078 if nid.as_deref() == Some(id) {
1079 return Some(node.specified_values.contains_key("zz"));
1080 }
1081 }
1082 for c in &node.children {
1083 if let Some(r) = find_marked(c, id) {
1084 return Some(r);
1085 }
1086 }
1087 None
1088 }
1089
1090 let value_cases: &[(&str, &str, &str, &str, &str)] = &[
1092 (
1093 "<p id='t'></p>",
1094 "p{color:red} p{color:blue}",
1095 "t",
1096 "color",
1097 "blue",
1098 ), (
1102 "<p id='t'></p>",
1103 "@layer a { #t{color:red} } @layer b { p{color:blue} }",
1104 "t",
1105 "color",
1106 "blue",
1107 ),
1108 (
1111 "<p id='t'></p>",
1112 "@layer a { #t{color:red} } p{color:blue}",
1113 "t",
1114 "color",
1115 "blue",
1116 ),
1117 (
1119 "<p id='t'></p>",
1120 "@layer a { p{color:red} } @layer a { p{color:green} }",
1121 "t",
1122 "color",
1123 "green",
1124 ),
1125 (
1130 "<p id='t'></p>",
1131 "@layer a { p{color:red} } @layer b { #t{color:blue} } @layer a { p{color:green} }",
1132 "t",
1133 "color",
1134 "blue",
1135 ),
1136 (
1140 "<p id='t' style='color:blue'></p>",
1141 "p{color:red !important}",
1142 "t",
1143 "color",
1144 "red",
1145 ),
1146 (
1148 "<p id='t' style='color:blue !important'></p>",
1149 "p{color:red !important}",
1150 "t",
1151 "color",
1152 "blue",
1153 ),
1154 (
1156 "<p id='t' style='color:blue'></p>",
1157 "p{color:red}",
1158 "t",
1159 "color",
1160 "blue",
1161 ),
1162 (
1166 "<div id='t' class='a'></div>",
1167 ".a{color:green; &:hover{color:blue;}}",
1168 "t",
1169 "color",
1170 "green",
1171 ),
1172 (
1176 "<div style='visibility:hidden'><span id='t'></span></div>",
1177 "",
1178 "t",
1179 "visibility",
1180 "hidden",
1181 ),
1182 (
1184 "<div style='visibility:hidden'><span id='t' style='visibility:visible'></span></div>",
1185 "",
1186 "t",
1187 "visibility",
1188 "visible",
1189 ),
1190 (
1194 "<ul><li id='t'></li></ul>",
1195 "ul{list-style-image:url(bullet.png)}",
1196 "t",
1197 "list-style-image",
1198 "url(bullet.png)",
1199 ),
1200 (
1204 "<ul><li id='t'></li></ul>",
1205 "ul{list-style-position:inside}",
1206 "t",
1207 "list-style-position",
1208 "inside",
1209 ),
1210 (
1213 "<p><span id='t'></span></p>",
1214 "p{text-indent:2em}",
1215 "t",
1216 "text-indent",
1217 "2em",
1218 ),
1219 (
1220 "<p id='t' class='x'></p>",
1221 "p{color:red} .x{color:green}",
1222 "t",
1223 "color",
1224 "green",
1225 ), (
1231 "<div id='t' class='b c'></div>",
1232 ":is(#nomatch, .b){color:red} .c{color:blue}",
1233 "t",
1234 "color",
1235 "red",
1236 ),
1237 (
1240 "<div id='t' class='a'></div>",
1241 ".a{color:blue} :where(.a){color:red}",
1242 "t",
1243 "color",
1244 "blue",
1245 ),
1246 (
1247 "<p id='t' class='x'></p>",
1248 "p{color:red !important} .x{color:green}",
1249 "t",
1250 "color",
1251 "red",
1252 ), (
1254 "<p id='t'></p>",
1255 "p{color:red !important} p{color:blue !important}",
1256 "t",
1257 "color",
1258 "blue",
1259 ), (
1261 "<p id='t' class='x'></p>",
1262 "#nope{color:black} .x{color:purple}",
1263 "t",
1264 "color",
1265 "purple",
1266 ),
1267 (
1269 "<div><p id='t'>x</p></div>",
1270 "div{text-transform:uppercase}",
1271 "t",
1272 "text-transform",
1273 "uppercase",
1274 ),
1275 (
1277 "<div><p id='t'>x</p></div>",
1278 "div{-webkit-text-fill-color:red}",
1279 "t",
1280 "-webkit-text-fill-color",
1281 "red",
1282 ),
1283 (
1285 "<ul><li id='t'>x</li></ul>",
1286 "ul{list-style-type:none}",
1287 "t",
1288 "list-style-type",
1289 "none",
1290 ),
1291 (
1293 "<div id='t' class='f'></div>",
1294 ".f{display:flex; gap:10px}",
1295 "t",
1296 "gap",
1297 "10px",
1298 ),
1299 (
1301 "<div id='t' class='o'></div>",
1302 ".o{overflow:hidden}",
1303 "t",
1304 "overflow",
1305 "hidden",
1306 ),
1307 (
1308 "<div id='t' class='lc'></div>",
1309 ".lc{-webkit-line-clamp: 3}",
1310 "t",
1311 "-webkit-line-clamp",
1312 "3",
1313 ),
1314 (
1315 "<div id='t' class='p'></div>",
1316 ".p{position:absolute; top:5px}",
1317 "t",
1318 "position",
1319 "absolute",
1320 ),
1321 (
1323 "<div id='t' class='q'></div>",
1324 ".q::before{content:\"★\"}",
1325 "t",
1326 "x-before-content",
1327 "★",
1328 ),
1329 (
1330 "<div id='t' class='q'></div>",
1331 ".q::after{content:'!'}",
1332 "t",
1333 "x-after-content",
1334 "!",
1335 ),
1336 (
1338 "<div id='t' class='q'></div>",
1339 ".q::before{content:\"x\";color:#ff0000}",
1340 "t",
1341 "color",
1342 "",
1343 ),
1344 (
1346 "<div id='t' class='q'></div>",
1347 ".q::before{content:none}",
1348 "t",
1349 "x-before-content",
1350 "",
1351 ),
1352 (
1354 "<a id='t' href='/foo'></a>",
1355 "a::after{content:attr(href)}",
1356 "t",
1357 "x-after-content",
1358 "/foo",
1359 ),
1360 (
1365 "<div id='t' class='q'></div>",
1366 ".q::before{content:\"\\f2c9\"}",
1367 "t",
1368 "x-before-content",
1369 "\u{f2c9}",
1370 ),
1371 (
1373 "<div id='t' class='q'></div>",
1374 ".q::before{content:\"\\f2c9 icon\"}",
1375 "t",
1376 "x-before-content",
1377 "\u{f2c9}icon",
1378 ),
1379 (
1383 "<div id='t' class='q'></div>",
1384 ".q::before{content:\"x\";display:none}",
1385 "t",
1386 "x-before-display",
1387 "none",
1388 ),
1389 (
1390 "<div id='t' data-tip='hi'></div>",
1391 "div::after{content:attr(data-tip)}",
1392 "t",
1393 "x-after-content",
1394 "hi",
1395 ),
1396 (
1398 "<a id='t' href='/x'></a>",
1399 "a::after{content:\"(\" attr(href) \")\"}",
1400 "t",
1401 "x-after-content",
1402 "(/x)",
1403 ),
1404 (
1406 "<div id='t'></div>",
1407 "div::after{content:attr(data-tip)}",
1408 "t",
1409 "x-after-content",
1410 "",
1411 ),
1412 (
1416 "<div id='t'></div>",
1417 "div::after{content:attr(data-tip, \"none\")}",
1418 "t",
1419 "x-after-content",
1420 "none",
1421 ),
1422 (
1424 "<div id='t' data-tip='hi'></div>",
1425 "div::after{content:attr(data-tip, \"none\")}",
1426 "t",
1427 "x-after-content",
1428 "hi",
1429 ),
1430 (
1432 "<div id='t' class='q'></div>",
1433 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c)}",
1434 "t",
1435 "x-after-content",
1436 "5",
1437 ),
1438 (
1441 "<div id='t' class='q'></div>",
1442 "@counter-style stars{system:cyclic;symbols:\"★\" \"☆\";suffix:\" \"} \
1443 .q{counter-reset:c 1;counter-increment:c}.q::after{content:counter(c, stars)}",
1444 "t",
1445 "x-after-content",
1446 "☆",
1447 ),
1448 (
1451 "<div id='t' class='q'></div>",
1452 "@counter-style bin{system:numeric;symbols:\"0\" \"1\"} \
1453 .q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c, bin)}",
1454 "t",
1455 "x-after-content",
1456 "101",
1457 ),
1458 (
1461 "<div id='t' class='q'></div>",
1462 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c, upper-roman)}",
1463 "t",
1464 "x-after-content",
1465 "V",
1466 ),
1467 (
1468 "<div id='t' class='q'></div>",
1469 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c, lower-roman)}",
1470 "t",
1471 "x-after-content",
1472 "v",
1473 ),
1474 (
1475 "<div id='t' class='q'></div>",
1476 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c, upper-alpha)}",
1477 "t",
1478 "x-after-content",
1479 "E",
1480 ),
1481 (
1482 "<div id='t' class='q'></div>",
1483 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counter(c, lower-alpha)}",
1484 "t",
1485 "x-after-content",
1486 "e",
1487 ),
1488 (
1490 "<ol><li class='c' id='a'>x</li><li class='c' id='t'>y</li></ol>",
1491 ".c{counter-increment:item}.c::after{content:counter(item)}",
1492 "t",
1493 "x-after-content",
1494 "2",
1495 ),
1496 (
1498 "<div id='t' class='q'></div>",
1499 ".q::after{content:counter(missing)}",
1500 "t",
1501 "x-after-content",
1502 "0",
1503 ),
1504 (
1511 "<ol><li class='c'></li><li class='c'><ol><li class='c'></li><li class='c'></li></ol></li><li class='c' id='t'></li></ol>",
1512 "ol{counter-reset:item}.c{counter-increment:item}.c::after{content:counter(item)}",
1513 "t",
1514 "x-after-content",
1515 "3",
1516 ),
1517 (
1521 "<ol><li class='q'></li><li class='q' id='t'></li></ol>",
1522 ".q::after{content:counter(list-item)}",
1523 "t",
1524 "x-after-content",
1525 "2",
1526 ),
1527 (
1530 "<ol start='5'><li class='q' id='t'></li><li class='q'></li></ol>",
1531 ".q::after{content:counter(list-item)}",
1532 "t",
1533 "x-after-content",
1534 "5",
1535 ),
1536 (
1539 "<ol reversed><li class='q' id='t'></li><li class='q'></li><li class='q'></li></ol>",
1540 ".q::after{content:counter(list-item)}",
1541 "t",
1542 "x-after-content",
1543 "3",
1544 ),
1545 (
1547 "<ol reversed start='10'><li class='q' id='t'></li><li class='q'></li></ol>",
1548 ".q::after{content:counter(list-item)}",
1549 "t",
1550 "x-after-content",
1551 "10",
1552 ),
1553 (
1557 "<ol><li class='q' id='t'></li></ol>",
1558 "ol{counter-reset:list-item 10}.q::after{content:counter(list-item)}",
1559 "t",
1560 "x-after-content",
1561 "11",
1562 ),
1563 (
1567 "<div id='t' class='q'></div>",
1568 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counters(c, \".\")}",
1569 "t",
1570 "x-after-content",
1571 "5",
1572 ),
1573 (
1575 "<div id='t' class='q'></div>",
1576 ".q{counter-reset:c 4;counter-increment:c}.q::after{content:counters(c, \".\", upper-roman)}",
1577 "t",
1578 "x-after-content",
1579 "V",
1580 ),
1581 (
1583 "<q id='t' class='q'></q>",
1584 ".q::before{content:open-quote}",
1585 "t",
1586 "x-before-content",
1587 "\u{201C}",
1588 ),
1589 (
1590 "<q id='t' class='q'></q>",
1591 ".q::after{content:close-quote}",
1592 "t",
1593 "x-after-content",
1594 "\u{201D}",
1595 ),
1596 (
1598 "<q id='t' class='q'></q>",
1599 ".q::before{content:no-open-quote}",
1600 "t",
1601 "x-before-content",
1602 "",
1603 ),
1604 (
1606 "<q id='t' class='q'></q>",
1607 ".q::before{content:open-quote \"cited\" close-quote}",
1608 "t",
1609 "x-before-content",
1610 "\u{201C}cited\u{201D}",
1611 ),
1612 (
1614 "<q id='t' class='q'></q>",
1615 ".q{quotes:\"\u{00AB}\" \"\u{00BB}\"}.q::before{content:open-quote}",
1616 "t",
1617 "x-before-content",
1618 "\u{00AB}",
1619 ),
1620 (
1621 "<q id='t' class='q'></q>",
1622 ".q{quotes:\"\u{00AB}\" \"\u{00BB}\"}.q::after{content:close-quote}",
1623 "t",
1624 "x-after-content",
1625 "\u{00BB}",
1626 ),
1627 (
1629 "<q id='t' class='q'></q>",
1630 ".q{quotes:none}.q::before{content:open-quote}",
1631 "t",
1632 "x-before-content",
1633 "",
1634 ),
1635 (
1639 "<q class='q'><q id='t' class='q'></q></q>",
1640 ".q{quotes:\"\u{00AB}\" \"\u{00BB}\" \"\u{2018}\" \"\u{2019}\"}.q::before{content:open-quote}",
1641 "t",
1642 "x-before-content",
1643 "\u{2018}",
1644 ),
1645 (
1647 "<q class='q'><q class='q'><q id='t' class='q'></q></q></q>",
1648 ".q{quotes:\"\u{00AB}\" \"\u{00BB}\" \"\u{2018}\" \"\u{2019}\"}.q::before{content:open-quote}",
1649 "t",
1650 "x-before-content",
1651 "\u{2018}",
1652 ),
1653 (
1655 "<li id='t' class='q'></li>",
1656 ".q::marker{color:#ff0000}",
1657 "t",
1658 "x-marker-color",
1659 "#ff0000",
1660 ),
1661 (
1663 "<li id='t' class='q'></li>",
1664 ".q::marker{font-size:99px}",
1665 "t",
1666 "x-marker-font-size",
1667 "99px",
1668 ),
1669 (
1673 "<li id='t' class='q'></li>",
1674 ".q::marker{content:\"\u{2192} \"}",
1675 "t",
1676 "x-marker-content",
1677 "\u{2192} ",
1678 ),
1679 (
1681 "<li id='t' class='q'></li>",
1682 ".q::marker{font-weight:bold}",
1683 "t",
1684 "x-marker-color",
1685 "",
1686 ),
1687 (
1689 "<input id='t' class='q'></input>",
1690 ".q::placeholder{color:#00aaff}",
1691 "t",
1692 "x-placeholder-color",
1693 "#00aaff",
1694 ),
1695 (
1697 "<input id='t' class='q'></input>",
1698 ".q::placeholder{font-size:99px}",
1699 "t",
1700 "x-placeholder-color",
1701 "",
1702 ),
1703 (
1705 "<p id='t' class='q'></p>",
1706 ".q::first-line{color:#00ff00}",
1707 "t",
1708 "x-first-line-color",
1709 "#00ff00",
1710 ),
1711 (
1712 "<p id='t' class='q'></p>",
1713 ".q::first-line{font-size:24px}",
1714 "t",
1715 "x-first-line-font-size",
1716 "24px",
1717 ),
1718 (
1720 "<p id='t' class='q'></p>",
1721 ".q::first-line{background-color:#000000}",
1722 "t",
1723 "x-first-line-background-color",
1724 "",
1725 ),
1726 (
1728 "<p id='t' class='q'></p>",
1729 ".q::first-letter{color:#0000ff}",
1730 "t",
1731 "x-first-letter-color",
1732 "#0000ff",
1733 ),
1734 (
1735 "<p id='t' class='q'></p>",
1736 ".q::first-letter{font-size:32px}",
1737 "t",
1738 "x-first-letter-font-size",
1739 "32px",
1740 ),
1741 (
1743 "<div id='t'></div>",
1744 "#t{margin-block-start:10px;margin-block-end:20px}",
1745 "t",
1746 "margin-top",
1747 "10px",
1748 ),
1749 (
1751 "<div id='t'></div>",
1752 "#t{padding-inline-start:5px}",
1753 "t",
1754 "padding-left",
1755 "5px",
1756 ),
1757 (
1761 "<div id='t'></div>",
1762 "#t{margin-block:15px}",
1763 "t",
1764 "margin-top",
1765 "15px",
1766 ),
1767 (
1768 "<div id='t'></div>",
1769 "#t{margin-block:15px}",
1770 "t",
1771 "margin-bottom",
1772 "15px",
1773 ),
1774 (
1776 "<div id='t'></div>",
1777 "#t{padding-inline:5px 9px}",
1778 "t",
1779 "padding-left",
1780 "5px",
1781 ),
1782 (
1783 "<div id='t'></div>",
1784 "#t{padding-inline:5px 9px}",
1785 "t",
1786 "padding-right",
1787 "9px",
1788 ),
1789 (
1791 "<div id='t'></div>",
1792 "#t{margin-inline:1px;margin-inline-start:2px}",
1793 "t",
1794 "margin-left",
1795 "2px",
1796 ),
1797 (
1799 "<div id='t'></div>",
1800 "#t{direction:rtl;margin-inline-start:7px}",
1801 "t",
1802 "margin-right",
1803 "7px",
1804 ),
1805 (
1807 "<div style='direction:rtl'><div id='t'></div></div>",
1808 "#t{padding-inline-end:9px}",
1809 "t",
1810 "padding-left",
1811 "9px",
1812 ),
1813 (
1815 "<div id='t'></div>",
1816 "#t{margin-left:1px;margin-inline-start:2px}",
1817 "t",
1818 "margin-left",
1819 "1px",
1820 ),
1821 (
1824 "<div id='t'></div>",
1825 "#t{border-inline-start-width:3px}",
1826 "t",
1827 "border-left-width",
1828 "3px",
1829 ),
1830 (
1831 "<div id='t'></div>",
1832 "#t{border-inline-start-style:dashed}",
1833 "t",
1834 "border-left-style",
1835 "dashed",
1836 ),
1837 (
1838 "<div id='t'></div>",
1839 "#t{direction:rtl;border-inline-end-color:#ff0000}",
1840 "t",
1841 "border-left-color",
1842 "#ff0000",
1843 ),
1844 (
1845 "<div id='t'></div>",
1846 "#t{border-block-start-width:4px}",
1847 "t",
1848 "border-top-width",
1849 "4px",
1850 ),
1851 (
1853 "<div id='t'></div>",
1854 "#t{grid-template:1fr 2fr / 100px 200px}",
1855 "t",
1856 "grid-template-rows",
1857 "1fr 2fr",
1858 ),
1859 (
1860 "<div id='t'></div>",
1861 "#t{grid-template:1fr 2fr / 100px 200px}",
1862 "t",
1863 "grid-template-columns",
1864 "100px 200px",
1865 ),
1866 (
1868 "<div id='t'></div>",
1869 "#t{text-emphasis:dot red}",
1870 "t",
1871 "text-emphasis-style",
1872 "dot",
1873 ),
1874 (
1875 "<div id='t'></div>",
1876 "#t{text-emphasis:dot red}",
1877 "t",
1878 "text-emphasis-color",
1879 "red",
1880 ),
1881 (
1883 "<div id='t'></div>",
1884 "#t{text-emphasis:filled circle #00ff00}",
1885 "t",
1886 "text-emphasis-style",
1887 "circle",
1888 ),
1889 (
1892 "<div id='t'></div>",
1893 "#t{grid-template:\"a\" 1fr / 1fr}",
1894 "t",
1895 "grid-template-areas",
1896 "\"a\" 1fr",
1897 ),
1898 (
1899 "<div id='t'></div>",
1900 "#t{grid-template:\"a\" 1fr / 1fr}",
1901 "t",
1902 "grid-template-rows",
1903 "1fr",
1904 ),
1905 (
1906 "<div id='t'></div>",
1907 "#t{grid-template:\"h h\" 1fr \"m m\" 2fr / 1fr 1fr}",
1908 "t",
1909 "grid-template-areas",
1910 "\"h h\" 1fr \"m m\" 2fr",
1911 ),
1912 (
1913 "<div id='t'></div>",
1914 "#t{grid-template:\"h h\" 1fr \"m m\" 2fr / 1fr 1fr}",
1915 "t",
1916 "grid-template-rows",
1917 "1fr 2fr",
1918 ),
1919 (
1920 "<div id='t'></div>",
1921 "#t{grid-template:\"h h\" 1fr \"m m\" 2fr / 1fr 1fr}",
1922 "t",
1923 "grid-template-columns",
1924 "1fr 1fr",
1925 ),
1926 (
1928 "<div id='t'></div>",
1929 "#t{grid-template-rows:5fr;grid-template:1fr / 1fr}",
1930 "t",
1931 "grid-template-rows",
1932 "5fr",
1933 ),
1934 (
1937 "<div style='color:red'><p id='t' style='color:inherit'></p></div>",
1938 "",
1939 "t",
1940 "color",
1941 "red",
1942 ),
1943 (
1946 "<div id='t'></div>",
1947 "#t{--b:red;--a:var(--b);color:var(--a)}",
1948 "t",
1949 "color",
1950 "red",
1951 ),
1952 (
1954 "<div id='t'></div>",
1955 "#t{--c:blue;--b:var(--c);--a:var(--b);color:var(--a)}",
1956 "t",
1957 "color",
1958 "blue",
1959 ),
1960 (
1962 "<div id='t'></div>",
1963 "#t{--a:var(--undefined, var(--also-undefined, green));color:var(--a)}",
1964 "t",
1965 "color",
1966 "green",
1967 ),
1968 (
1973 "<div id='t'></div>",
1974 "#t{margin-top:env(safe-area-inset-top)}",
1975 "t",
1976 "margin-top",
1977 "0px",
1978 ),
1979 (
1981 "<div id='t'></div>",
1982 "#t{margin-top:env(safe-area-inset-top, 20px)}",
1983 "t",
1984 "margin-top",
1985 "20px",
1986 ),
1987 (
1989 "<div id='t'></div>",
1990 "#t{font:italic bold 16px/1.5 Arial, sans-serif}",
1991 "t",
1992 "font-style",
1993 "italic",
1994 ),
1995 (
1996 "<div id='t'></div>",
1997 "#t{font:italic bold 16px/1.5 Arial, sans-serif}",
1998 "t",
1999 "font-weight",
2000 "bold",
2001 ),
2002 (
2003 "<div id='t'></div>",
2004 "#t{font:italic bold 16px/1.5 Arial, sans-serif}",
2005 "t",
2006 "font-size",
2007 "16px",
2008 ),
2009 (
2010 "<div id='t'></div>",
2011 "#t{font:italic bold 16px/1.5 Arial, sans-serif}",
2012 "t",
2013 "line-height",
2014 "1.5",
2015 ),
2016 (
2017 "<div id='t'></div>",
2018 "#t{font:italic bold 16px/1.5 Arial, sans-serif}",
2019 "t",
2020 "font-family",
2021 "Arial, sans-serif",
2022 ),
2023 (
2025 "<div id='t'></div>",
2026 "#t{font:700 14px sans-serif}",
2027 "t",
2028 "font-weight",
2029 "700",
2030 ),
2031 (
2032 "<div id='t'></div>",
2033 "#t{font:700 14px sans-serif}",
2034 "t",
2035 "font-size",
2036 "14px",
2037 ),
2038 (
2042 "<div id='t'></div>",
2043 "#t{font:oblique 10deg 12px/1.5 sans-serif}",
2044 "t",
2045 "font-size",
2046 "12px",
2047 ),
2048 (
2049 "<div id='t'></div>",
2050 "#t{font:oblique 10deg 12px/1.5 sans-serif}",
2051 "t",
2052 "line-height",
2053 "1.5",
2054 ),
2055 (
2056 "<div id='t'></div>",
2057 "#t{font:oblique 10deg 12px/1.5 sans-serif}",
2058 "t",
2059 "font-family",
2060 "sans-serif",
2061 ),
2062 (
2063 "<div id='t'></div>",
2064 "#t{font:oblique 10deg 12px/1.5 sans-serif}",
2065 "t",
2066 "font-style",
2067 "oblique",
2068 ),
2069 (
2071 "<div id='t'></div>",
2072 "#t{font-size:9px;font:16px Arial}",
2073 "t",
2074 "font-size",
2075 "9px",
2076 ),
2077 (
2080 "<div id='t'></div>",
2081 "#t{border:2px groove #333333}",
2082 "t",
2083 "border-top-style",
2084 "solid",
2085 ),
2086 (
2087 "<div id='t'></div>",
2088 "#t{border:2px ridge #333333}",
2089 "t",
2090 "border-top-width",
2091 "2px",
2092 ),
2093 (
2094 "<div id='t'></div>",
2095 "#t{border:2px hidden #333333}",
2096 "t",
2097 "border-top-style",
2098 "none",
2099 ),
2100 (
2101 "<div id='t'></div>",
2102 "#t{border:2px outset #333333}",
2103 "t",
2104 "border-top-color",
2105 "#333333",
2106 ),
2107 (
2109 "<div id='t' style='color:#ff0000;border-color:currentColor'></div>",
2110 "",
2111 "t",
2112 "border-color",
2113 "#ff0000",
2114 ),
2115 (
2117 "<div style='color:blue'><p id='t' style='outline-color:currentColor'></p></div>",
2118 "",
2119 "t",
2120 "outline-color",
2121 "blue",
2122 ),
2123 (
2125 "<div id='t' style='color:green;background-color:CURRENTCOLOR'></div>",
2126 "",
2127 "t",
2128 "background-color",
2129 "green",
2130 ),
2131 (
2133 "<div id='t' style='color:currentColor'></div>",
2134 "",
2135 "t",
2136 "color",
2137 "currentColor",
2138 ),
2139 (
2141 "<div id='t' style='width:initial'></div>",
2142 "",
2143 "t",
2144 "width",
2145 "",
2146 ),
2147 (
2149 "<div id='t' style='border-color:unset'></div>",
2150 "",
2151 "t",
2152 "border-color",
2153 "",
2154 ),
2155 (
2157 "<div id='t' style='color:red;all:unset'></div>",
2158 "",
2159 "t",
2160 "color",
2161 "",
2162 ),
2163 (
2166 "<div id='t' style='color:red;all:revert-layer'></div>",
2167 "",
2168 "t",
2169 "color",
2170 "red",
2171 ),
2172 (
2174 "<div id='t' style='place-items:center'></div>",
2175 "",
2176 "t",
2177 "align-items",
2178 "center",
2179 ),
2180 (
2181 "<div id='t' style='place-items:center'></div>",
2182 "",
2183 "t",
2184 "justify-items",
2185 "center",
2186 ),
2187 (
2189 "<div id='t' style='place-content:start end'></div>",
2190 "",
2191 "t",
2192 "align-content",
2193 "start",
2194 ),
2195 (
2196 "<div id='t' style='place-content:start end'></div>",
2197 "",
2198 "t",
2199 "justify-content",
2200 "end",
2201 ),
2202 (
2204 "<div id='t' style='align-self:end;place-self:start'></div>",
2205 "",
2206 "t",
2207 "align-self",
2208 "end",
2209 ),
2210 ];
2211
2212 let tt_cases: &[(&str, &str, &str)] = &[
2214 ("hello world", "uppercase", "HELLO WORLD"),
2215 ("Hello WORLD", "lowercase", "hello world"),
2216 ("hello world", "capitalize", "Hello World"),
2217 ("hello world", "none", "hello world"),
2218 ];
2219
2220 fn find_prop(node: &StyledNode, id: &str, prop: &str) -> Option<String> {
2221 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
2222 if nid.as_deref() == Some(id) {
2223 return Some(node.specified_values.get(prop).cloned().unwrap_or_default());
2224 }
2225 }
2226 for c in &node.children {
2227 if let Some(r) = find_prop(c, id, prop) {
2228 return Some(r);
2229 }
2230 }
2231 None
2232 }
2233
2234 let alpha_roman_cases: &[(&str, u32, bool, &str)] = &[
2236 ("alpha", 1, true, "A"),
2237 ("alpha", 26, true, "Z"),
2238 ("alpha", 27, true, "AA"),
2239 ("alpha", 1, false, "a"),
2240 ("roman", 1994, true, "MCMXCIV"),
2241 ("roman", 4, false, "iv"),
2242 ("roman", 2026, true, "MMXXVI"),
2243 ];
2244
2245 let filter_cases: &[(&str, (u8, u8, u8), (u8, u8, u8))] = &[
2247 ("grayscale(1)", (255, 0, 0), (76, 76, 76)),
2248 ("brightness(0.5)", (200, 200, 200), (100, 100, 100)),
2249 ("invert(1)", (0, 0, 0), (255, 255, 255)),
2250 ("", (10, 20, 30), (10, 20, 30)),
2251 ("contrast(2)", (200, 50, 128), (255, 0, 128)),
2253 ("saturate(0)", (255, 0, 0), (76, 76, 76)),
2255 ];
2256
2257 let blur_radius_cases: &[(&str, i32)] = &[
2259 ("blur(5px)", 5),
2260 ("blur(20px)", 8), ("grayscale(1)", 0),
2262 ("", 0),
2263 ];
2264
2265 let blend_cases: &[(&str, (u8, u8, u8), (u8, u8, u8), (u8, u8, u8))] = &[
2267 ("multiply", (200, 200, 200), (100, 100, 100), (78, 78, 78)),
2269 ("screen", (200, 200, 200), (100, 100, 100), (222, 222, 222)),
2271 ("darken", (200, 50, 128), (100, 150, 128), (100, 50, 128)),
2272 ("lighten", (200, 50, 128), (100, 150, 128), (200, 150, 128)),
2273 ("difference", (200, 50, 128), (100, 150, 128), (100, 100, 0)),
2274 ("normal", (10, 20, 30), (200, 200, 200), (10, 20, 30)),
2276 ("overlay", (200, 200, 200), (100, 100, 100), (156, 156, 156)),
2278 ("hard-light", (200, 200, 200), (100, 100, 100), (189, 189, 189)),
2280 ("exclusion", (200, 200, 200), (100, 100, 100), (144, 144, 144)),
2282 ("color-dodge", (200, 200, 200), (100, 100, 100), (255, 255, 255)),
2284 ("color-burn", (200, 200, 200), (100, 100, 100), (58, 58, 58)),
2286 ("luminosity", (255, 255, 255), (0, 0, 0), (255, 255, 255)),
2289 ("color", (255, 255, 255), (0, 0, 0), (0, 0, 0)),
2291 ("hue", (255, 255, 255), (0, 0, 0), (0, 0, 0)),
2293 ("saturation", (255, 255, 255), (0, 0, 0), (0, 0, 0)),
2295 ("soft-light", (200, 200, 200), (100, 100, 100), (133, 133, 133)),
2297 ];
2298
2299 let bg_repeat_cases: &[(&str, super::super::web_engine::BgRepeatMode, super::super::web_engine::BgRepeatMode)] = &[
2301 ("no-repeat", super::super::web_engine::BgRepeatMode::NoRepeat, super::super::web_engine::BgRepeatMode::NoRepeat),
2302 ("repeat-x", super::super::web_engine::BgRepeatMode::Repeat, super::super::web_engine::BgRepeatMode::NoRepeat),
2303 ("repeat-y", super::super::web_engine::BgRepeatMode::NoRepeat, super::super::web_engine::BgRepeatMode::Repeat),
2304 ("space", super::super::web_engine::BgRepeatMode::Space, super::super::web_engine::BgRepeatMode::Space),
2305 ("round", super::super::web_engine::BgRepeatMode::Round, super::super::web_engine::BgRepeatMode::Round),
2306 ("repeat space", super::super::web_engine::BgRepeatMode::Repeat, super::super::web_engine::BgRepeatMode::Space),
2307 ("round no-repeat", super::super::web_engine::BgRepeatMode::Round, super::super::web_engine::BgRepeatMode::NoRepeat),
2308 ];
2309
2310 let rgb_pct_cases: &[(&str, u32)] = &[
2312 ("rgb(100%, 50%, 0%)", 0xFFFF7F00),
2313 ("rgb(0%, 0%, 0%)", 0xFF000000),
2314 ("rgba(100%, 0%, 0%, 0.5)", 0x7FFF0000),
2315 ("rgb(255, 50%, 0)", 0xFFFF7F00),
2317 ("#f00f", 0xFFFF0000),
2321 ("#f008", 0x88FF0000),
2322 ];
2323
2324 let hsl_hue_wrap_cases: &[(&str, u32)] = &[
2326 ("hsl(390, 100%, 50%)", 0xFFFF7F00),
2328 ("hsl(30, 100%, 50%)", 0xFFFF7F00),
2329 ("hsl(-30, 100%, 50%)", 0xFFFF007F),
2331 ("hsl(330, 100%, 50%)", 0xFFFF007F),
2332 ];
2333
2334 let rgb_space_cases: &[(&str, u32)] = &[
2339 ("rgb(255 0 0)", 0xFFFF0000),
2340 ("rgb(255 0 0 / 50%)", 0x7FFF0000),
2341 ("rgba(0 255 0 / 0.5)", 0x7F00FF00),
2342 ];
2343
2344 let oklab_oklch_cases: &[(&str, u32)] = &[
2347 ("oklch(1 0 0)", 0xFFFFFFFF),
2348 ("oklch(0 0 0)", 0xFF000000),
2349 ("oklab(1 0 0)", 0xFFFFFFFF),
2350 ("oklab(0 0 0)", 0xFF000000),
2351 ];
2352
2353 let color_fn_cases: &[(&str, u32)] = &[
2358 ("color(srgb 1 0 0)", 0xFFFF0000),
2359 ("color(srgb 0 1 0)", 0xFF00FF00),
2360 ("color(srgb 1 1 1 / 0.5)", 0x7FFFFFFF),
2361 ("color(srgb-linear 1 1 1)", 0xFFFFFFFF),
2362 ("color(srgb-linear 0 0 0)", 0xFF000000),
2363 ];
2364
2365 let hwb_cases: &[(&str, u32)] = &[
2367 ("hwb(0, 0%, 0%)", 0xFFFF0000), ("hwb(0, 100%, 0%)", 0xFFFFFFFF), ("hwb(0, 0%, 100%)", 0xFF000000), ("hwb(120, 0%, 0%)", 0xFF00FF00), ("hwb(240, 0%, 0%)", 0xFF0000FF), ("hwb(0 0% 0% / 50%)", 0x7FFF0000), ];
2374
2375 let color_mix_cases: &[(&str, u32)] = &[
2378 ("color-mix(in srgb, red, blue)", 0xFF7F007F), ("color-mix(in srgb, red 100%, blue 0%)", 0xFFFF0000), ("color-mix(in srgb, red 0%, blue 100%)", 0xFF0000FF),
2381 ("color-mix(in srgb, red 75%, blue 25%)", 0xFFBF003F), ("color-mix(in srgb, red 40%, blue)", 0xFF660099), ("color-mix(in srgb, white, black)", 0xFF7F7F7F), ];
2385
2386 let text_decoration_cases: &[(&str, bool, bool, bool, bool, Option<&str>, Option<u32>)] = &[
2389 ("underline", true, false, false, false, None, None),
2390 ("none", false, false, false, true, None, None),
2391 ("underline wavy red", true, false, false, false, Some("wavy"), Some(0xFFFF0000)),
2392 (
2393 "line-through dotted #00ff00",
2394 false,
2395 true,
2396 false,
2397 false,
2398 Some("dotted"),
2399 Some(0xFF00FF00),
2400 ),
2401 ("overline", false, false, true, false, None, None),
2402 ];
2403
2404 let mut passed = 0;
2405 let mut total = cases.len()
2424 + value_cases.len()
2425 + tt_cases.len()
2426 + alpha_roman_cases.len()
2427 + filter_cases.len()
2428 + blur_radius_cases.len()
2429 + blend_cases.len()
2430 + text_decoration_cases.len()
2431 + bg_repeat_cases.len()
2432 + rgb_pct_cases.len()
2433 + rgb_space_cases.len()
2434 + hsl_hue_wrap_cases.len()
2435 + hwb_cases.len()
2436 + oklab_oklch_cases.len()
2437 + color_fn_cases.len()
2438 + color_mix_cases.len()
2439 + 3
2440 + 2
2441 + 3
2442 + 1
2443 + 1
2444 + 2
2445 + 1
2446 + 1
2447 + 3
2448 + 10
2449 + 10
2450 + 7
2451 + 5
2452 + 5
2453 + 5
2454 + 4;
2455 for (input, want_radius) in blur_radius_cases {
2456 let got = super::super::web_engine::draw_helpers::parse_blur_radius(input);
2457 if got == *want_radius {
2458 passed += 1;
2459 } else {
2460 crate::println!(
2461 "CSS_SELFTEST FAIL: parse_blur_radius(`{}`) => {} (want {})",
2462 input, got, want_radius
2463 );
2464 }
2465 }
2466 {
2469 let mut rgba = alloc::vec![0u8; 3 * 3 * 4];
2470 let center_idx = 4 * 4;
2471 rgba[center_idx] = 255;
2472 rgba[center_idx + 3] = 255;
2473 let img = super::super::web_engine::DecodedImage { width: 3, height: 3, rgba };
2474 let got = super::super::web_engine::draw_helpers::sample_box_blur(&img, 1, 1, 1);
2475 if got == (28, 0, 0) {
2476 passed += 1;
2477 } else {
2478 crate::println!("CSS_SELFTEST FAIL: sample_box_blur => {:?} (want (28,0,0))", got);
2479 }
2480 }
2481 {
2487 let calc_cases: &[(&str, i32, i32)] = &[
2488 ("calc(100px - 20px)", 0, 80),
2490 ("calc(50% + 10px)", 200, 110),
2491 ("calc(10px * 2)", 0, 20),
2492 ("calc(100px / 4)", 0, 25),
2494 ("calc(300% / 3)", 100, 100),
2495 ("calc(100% - 20px - 10px)", 200, 170),
2497 ("calc(10px + 20px + 30px)", 0, 60),
2498 ("calc((100px - 20px) / 2)", 0, 40),
2500 ("calc(50% - (10px + 5px))", 200, 85),
2501 ("calc(-10px + 50px)", 0, 40),
2503 ];
2504 for (expr, cw, want) in calc_cases {
2505 let got = super::values::eval_calc(expr, *cw);
2506 if got == Some(*want) {
2507 passed += 1;
2508 } else {
2509 crate::println!(
2510 "CSS_SELFTEST FAIL: eval_calc(`{}`, {}) => {:?} (want Some({}))",
2511 expr, cw, got, want
2512 );
2513 }
2514 }
2515 }
2516 {
2523 let orig_w = crate::os_lib::layout::VIEWPORT_HINT_W.load(core::sync::atomic::Ordering::Relaxed);
2528 let orig_h = crate::os_lib::layout::VIEWPORT_HINT_H.load(core::sync::atomic::Ordering::Relaxed);
2529 crate::os_lib::layout::VIEWPORT_HINT_W.store(1000, core::sync::atomic::Ordering::Relaxed);
2530 crate::os_lib::layout::VIEWPORT_HINT_H.store(500, core::sync::atomic::Ordering::Relaxed);
2531 let px_cases: &[(&str, i32)] = &[
2532 ("50vmin", 250),
2534 ("50vmax", 500),
2535 ("1in", 96),
2537 ("2.54cm", 96),
2538 ("25.4mm", 96),
2539 ("72pt", 96),
2540 ("6pc", 96),
2541 ("thin", 1),
2545 ("medium", 3),
2546 ("thick", 5),
2547 ];
2548 for (input, want) in px_cases {
2549 let got = crate::os_lib::layout::parse_px_like(input);
2550 if got == Some(*want) {
2551 passed += 1;
2552 } else {
2553 crate::println!(
2554 "CSS_SELFTEST FAIL: parse_px_like(`{}`) => {:?} (want Some({}))",
2555 input, got, want
2556 );
2557 }
2558 }
2559 let calc_unit_cases: &[(&str, i32, i32)] = &[
2567 ("calc(50vw + 10px)", 0, 510),
2568 ("calc(50vh - 10px)", 0, 240),
2569 ("calc(50vmin + 50px)", 0, 300),
2570 ("calc(1in + 4px)", 0, 100),
2571 ("min(50vw, 300px)", 0, 300),
2572 ("max(50vw, 300px)", 0, 500),
2573 ("clamp(10px, 50vw, 100px)", 0, 100),
2574 ];
2575 for (expr, cw, want) in calc_unit_cases {
2576 let got = super::values::eval_css_math(expr, *cw);
2577 if got == Some(*want) {
2578 passed += 1;
2579 } else {
2580 crate::println!(
2581 "CSS_SELFTEST FAIL: eval_css_math(`{}`, {}) => {:?} (want Some({}))",
2582 expr, cw, got, want
2583 );
2584 }
2585 }
2586 crate::os_lib::layout::VIEWPORT_HINT_W.store(orig_w, core::sync::atomic::Ordering::Relaxed);
2587 crate::os_lib::layout::VIEWPORT_HINT_H.store(orig_h, core::sync::atomic::Ordering::Relaxed);
2588 }
2589 {
2594 let line_height_cases: &[(&str, u32, i32)] = &[
2595 ("16px", 16, 16),
2596 ("1.5", 16, 24),
2597 ("150%", 16, 24),
2598 ("1.5em", 16, 24),
2599 ("2em", 20, 40),
2600 ];
2601 for (input, font_size, want) in line_height_cases {
2602 let got = super::super::web_engine::parse_line_height(input, *font_size);
2603 if got == *want {
2604 passed += 1;
2605 } else {
2606 crate::println!(
2607 "CSS_SELFTEST FAIL: parse_line_height(`{}`, {}) => {} (want {})",
2608 input, font_size, got, want
2609 );
2610 }
2611 }
2612 }
2613 {
2617 let text_indent_cases: &[(&str, i32, u32, i32)] = &[
2619 ("20px", 400, 16, 20),
2620 ("10", 400, 16, 10),
2621 ("10%", 400, 16, 40),
2622 ("2em", 400, 16, 32),
2623 ("2em", 400, 20, 40),
2624 ];
2625 for (input, cw, font_size, want) in text_indent_cases {
2626 let got = super::super::web_engine::resolve_text_indent(input, *cw, *font_size);
2627 if got == *want {
2628 passed += 1;
2629 } else {
2630 crate::println!(
2631 "CSS_SELFTEST FAIL: resolve_text_indent(`{}`, {}, {}) => {} (want {})",
2632 input, cw, font_size, got, want
2633 );
2634 }
2635 }
2636 }
2637 {
2642 let shadow_len_cases: &[(&str, Option<i32>)] = &[
2643 ("10px", Some(10)),
2644 ("0.5rem", Some(8)),
2645 ("2rem", Some(32)),
2646 ("red", None),
2647 ("inset", None),
2648 ];
2649 for (input, want) in shadow_len_cases {
2650 let got = super::super::web_engine::parse_shadow_len_tok(input);
2651 if got == *want {
2652 passed += 1;
2653 } else {
2654 crate::println!(
2655 "CSS_SELFTEST FAIL: parse_shadow_len_tok(`{}`) => {:?} (want {:?})",
2656 input, got, want
2657 );
2658 }
2659 }
2660 }
2661 {
2665 let border_radius_cases: &[(&str, i32, i32)] = &[
2667 ("10px", 200, 10),
2668 ("50%", 200, 100),
2669 ("1rem", 200, 16),
2670 ("0.5rem", 200, 8),
2671 ];
2672 for (input, cw, want) in border_radius_cases {
2673 let got = super::super::web_engine::resolve_border_radius(input, *cw);
2674 if got == *want {
2675 passed += 1;
2676 } else {
2677 crate::println!(
2678 "CSS_SELFTEST FAIL: resolve_border_radius(`{}`, {}) => {} (want {})",
2679 input, cw, got, want
2680 );
2681 }
2682 }
2683 }
2684 for (input, want_u, want_lt, want_ov, want_none, want_style, want_color) in text_decoration_cases {
2685 let (u, lt, ov, is_none, style, color) =
2686 super::super::web_engine::helpers::parse_text_decoration_shorthand(input);
2687 let style_ok = style.as_deref() == *want_style;
2688 let color_ok = color == *want_color;
2689 if u == *want_u
2690 && lt == *want_lt
2691 && ov == *want_ov
2692 && is_none == *want_none
2693 && style_ok
2694 && color_ok
2695 {
2696 passed += 1;
2697 } else {
2698 crate::println!(
2699 "CSS_SELFTEST FAIL: parse_text_decoration_shorthand(`{}`) => ({},{},{},{},{:?},{:?}) (want ({},{},{},{},{:?},{:?}))",
2700 input, u, lt, ov, is_none, style, color, want_u, want_lt, want_ov, want_none, want_style, want_color
2701 );
2702 }
2703 }
2704 for (input, want_x, want_y) in bg_repeat_cases {
2705 let (x, y) = super::super::web_engine::helpers::parse_bg_repeat(Some(input));
2706 if x == *want_x && y == *want_y {
2707 passed += 1;
2708 } else {
2709 crate::println!(
2710 "CSS_SELFTEST FAIL: parse_bg_repeat(`{}`) => ({:?},{:?}) (want ({:?},{:?}))",
2711 input, x, y, want_x, want_y
2712 );
2713 }
2714 }
2715 for (input, want) in rgb_pct_cases {
2716 let got = parse_color(input);
2717 if got == Some(*want) {
2718 passed += 1;
2719 } else {
2720 crate::println!(
2721 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2722 input, got, want
2723 );
2724 }
2725 }
2726 for (input, want) in rgb_space_cases {
2727 let got = parse_color(input);
2728 if got == Some(*want) {
2729 passed += 1;
2730 } else {
2731 crate::println!(
2732 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2733 input, got, want
2734 );
2735 }
2736 }
2737 for (input, want) in hsl_hue_wrap_cases {
2738 let got = parse_color(input);
2739 if got == Some(*want) {
2740 passed += 1;
2741 } else {
2742 crate::println!(
2743 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2744 input, got, want
2745 );
2746 }
2747 }
2748 for (input, want) in hwb_cases {
2749 let got = parse_color(input);
2750 if got == Some(*want) {
2751 passed += 1;
2752 } else {
2753 crate::println!(
2754 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2755 input, got, want
2756 );
2757 }
2758 }
2759 for (input, want) in oklab_oklch_cases {
2760 let got = parse_color(input);
2761 if got == Some(*want) {
2762 passed += 1;
2763 } else {
2764 crate::println!(
2765 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2766 input, got, want
2767 );
2768 }
2769 }
2770 for (input, want) in color_fn_cases {
2771 let got = parse_color(input);
2772 if got == Some(*want) {
2773 passed += 1;
2774 } else {
2775 crate::println!(
2776 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2777 input, got, want
2778 );
2779 }
2780 }
2781 for (input, want) in color_mix_cases {
2782 let got = parse_color(input);
2783 if got == Some(*want) {
2784 passed += 1;
2785 } else {
2786 crate::println!(
2787 "CSS_SELFTEST FAIL: parse_color(`{}`) => {:?} (want {:#010X})",
2788 input, got, want
2789 );
2790 }
2791 }
2792 {
2795 let saved = PREFERS_DARK.load(AtomicOrd::Relaxed);
2796 PREFERS_DARK.store(false, AtomicOrd::Relaxed);
2797 let got_light = parse_color("light-dark(#ff0000, #0000ff)");
2798 if got_light == Some(0xFFFF0000) {
2799 passed += 1;
2800 } else {
2801 crate::println!(
2802 "CSS_SELFTEST FAIL: light-dark (light mode) => {:?} (want 0xFFFF0000)",
2803 got_light
2804 );
2805 }
2806 PREFERS_DARK.store(true, AtomicOrd::Relaxed);
2807 let got_dark = parse_color("light-dark(#ff0000, #0000ff)");
2808 if got_dark == Some(0xFF0000FF) {
2809 passed += 1;
2810 } else {
2811 crate::println!(
2812 "CSS_SELFTEST FAIL: light-dark (dark mode) => {:?} (want 0xFF0000FF)",
2813 got_dark
2814 );
2815 }
2816 let got_nested = parse_color("light-dark(#ff0000, light-dark(#00ff00, #0000ff))");
2818 if got_nested == Some(0xFF0000FF) {
2819 passed += 1;
2820 } else {
2821 crate::println!(
2822 "CSS_SELFTEST FAIL: light-dark nested => {:?} (want 0xFF0000FF)",
2823 got_nested
2824 );
2825 }
2826 PREFERS_DARK.store(saved, AtomicOrd::Relaxed);
2827 }
2828 {
2830 *super::super::layout::CURRENT_TARGET_ID.lock() = String::from("sec2");
2831 let dom = super::super::dom::parse_html(
2832 "<div id='sec1'></div><div id='sec2'></div><div id='t'></div>",
2833 );
2834 let sheet = parse_css("#sec2:target{zz:1}");
2835 let styled = style_tree(&dom, &sheet);
2836 fn find_marked_target(node: &StyledNode, id: &str) -> Option<bool> {
2837 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
2838 if nid.as_deref() == Some(id) {
2839 return Some(node.specified_values.contains_key("zz"));
2840 }
2841 }
2842 for c in &node.children {
2843 if let Some(r) = find_marked_target(c, id) {
2844 return Some(r);
2845 }
2846 }
2847 None
2848 }
2849 let matched = find_marked_target(&styled, "sec2").unwrap_or(false);
2850 let not_matched = find_marked_target(&styled, "sec1").unwrap_or(true);
2851 if matched && !not_matched {
2852 passed += 1;
2853 } else {
2854 crate::println!(
2855 "CSS_SELFTEST FAIL: :target => sec2={} sec1={} (want true/false)",
2856 matched, not_matched
2857 );
2858 }
2859 *super::super::layout::CURRENT_TARGET_ID.lock() = String::new();
2860 }
2861 {
2865 fn collect_path_to(
2866 node: &super::super::dom::Node,
2867 target_id: &str,
2868 path: &mut alloc::vec::Vec<usize>,
2869 ) -> bool {
2870 path.push(node as *const _ as usize);
2871 if let NodeType::Element { id, .. } = &node.node_type {
2872 if id.as_deref() == Some(target_id) {
2873 return true;
2874 }
2875 }
2876 for c in &node.children {
2877 if collect_path_to(c, target_id, path) {
2878 return true;
2879 }
2880 }
2881 path.pop();
2882 false
2883 }
2884 let dom = super::super::dom::parse_html(
2885 "<div id='outer'><span><input id='inp'></span></div><div id='other'></div>",
2886 );
2887 let mut path = alloc::vec::Vec::new();
2888 collect_path_to(&dom, "inp", &mut path);
2889 {
2890 let mut set = super::super::layout::FOCUS_WITHIN_PTRS.lock();
2891 set.clear();
2892 set.extend(path);
2893 }
2894 let sheet = parse_css("#outer:focus-within{zz:1}");
2895 let styled = style_tree(&dom, &sheet);
2896 fn find_marked_fw(node: &StyledNode, id: &str) -> Option<bool> {
2897 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
2898 if nid.as_deref() == Some(id) {
2899 return Some(node.specified_values.contains_key("zz"));
2900 }
2901 }
2902 for c in &node.children {
2903 if let Some(r) = find_marked_fw(c, id) {
2904 return Some(r);
2905 }
2906 }
2907 None
2908 }
2909 let matched = find_marked_fw(&styled, "outer").unwrap_or(false);
2910 let not_matched = find_marked_fw(&styled, "other").unwrap_or(true);
2911 if matched && !not_matched {
2912 passed += 1;
2913 } else {
2914 crate::println!(
2915 "CSS_SELFTEST FAIL: :focus-within => outer={} other={} (want true/false)",
2916 matched, not_matched
2917 );
2918 }
2919 super::super::layout::FOCUS_WITHIN_PTRS.lock().clear();
2920 }
2921 {
2923 let html = r#"<div id="root"><fieldset disabled="true"><input id="i1" /></fieldset><fieldset><input id="i2" /></fieldset></div>"#;
2924 let dom = super::super::dom::parse_html(html);
2925 let sheet = parse_css("input:disabled{zz:1} input:enabled{yy:1}");
2926 let styled = style_tree(&dom, &sheet);
2927 fn find_marked_styles(node: &StyledNode, id: &str) -> Option<(bool, bool)> {
2928 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
2929 if nid.as_deref() == Some(id) {
2930 return Some((
2931 node.specified_values.contains_key("zz"),
2932 node.specified_values.contains_key("yy")
2933 ));
2934 }
2935 }
2936 for c in &node.children {
2937 if let Some(r) = find_marked_styles(c, id) {
2938 return Some(r);
2939 }
2940 }
2941 None
2942 }
2943 let i1_styles = find_marked_styles(&styled, "i1").unwrap_or((false, false));
2944 let i2_styles = find_marked_styles(&styled, "i2").unwrap_or((false, false));
2945
2946 let ok = i1_styles.0 && !i1_styles.1 && !i2_styles.0 && i2_styles.1;
2949 total += 1;
2950 if ok {
2951 passed += 1;
2952 } else {
2953 crate::println!(
2954 "CSS_SELFTEST FAIL: fieldset[disabled] CSS state propagation => i1={:?} i2={:?} (want (true,false) and (false,true))",
2955 i1_styles, i2_styles
2956 );
2957 }
2958 }
2959 {
2961 let got = super::super::web_engine::helpers::extract_all_url_paths("url(a.png), url('b.png')");
2962 if got == alloc::vec!["a.png", "b.png"] {
2963 passed += 1;
2964 } else {
2965 crate::println!("CSS_SELFTEST FAIL: extract_all_url_paths multi => {:?}", got);
2966 }
2967 let got_single = super::super::web_engine::helpers::extract_all_url_paths("url(only.png)");
2968 if got_single == alloc::vec!["only.png"] {
2969 passed += 1;
2970 } else {
2971 crate::println!(
2972 "CSS_SELFTEST FAIL: extract_all_url_paths single => {:?}",
2973 got_single
2974 );
2975 }
2976 let got_none = super::super::web_engine::helpers::extract_all_url_paths("none");
2977 if got_none.is_empty() {
2978 passed += 1;
2979 } else {
2980 crate::println!("CSS_SELFTEST FAIL: extract_all_url_paths none => {:?}", got_none);
2981 }
2982 }
2983 {
2990 let radius = super::super::web_engine::draw_helpers::parse_blur_radius("blur(8px) brightness(1.2)");
2991 total += 1;
2992 if radius == 8 {
2993 passed += 1;
2994 } else {
2995 crate::println!("CSS_SELFTEST FAIL: parse_blur_radius => {}", radius);
2996 }
2997 }
2998 {
3000 let stops = alloc::vec![
3001 super::super::web_engine::GradientStop { position: 0.0, color: 0x00FF0000 },
3002 super::super::web_engine::GradientStop { position: 1.0, color: 0xFF00FF00 },
3003 ];
3004 let mid_color = super::super::web_engine::draw_helpers::sample_gradient_stops(&stops, 0.5);
3005 let mid_alpha = (mid_color >> 24) & 0xFF;
3006 total += 1;
3007 if (120..=135).contains(&mid_alpha) {
3008 passed += 1;
3009 } else {
3010 crate::println!("CSS_SELFTEST FAIL: sample_gradient_stops alpha => 0x{:08X}", mid_color);
3011 }
3012 }
3013 {
3015 total += 1;
3016 if let Some(shape) = super::super::web_engine::draw_helpers::parse_clip_shape("inset(10px round 20px)", 100, 100) {
3017 let inside_center = super::super::web_engine::draw_helpers::clip_inside(&shape, 50, 50);
3018 let inside_corner = super::super::web_engine::draw_helpers::clip_inside(&shape, 12, 12);
3019 if inside_center && !inside_corner {
3020 passed += 1;
3021 } else {
3022 crate::println!("CSS_SELFTEST FAIL: clip-path inset round => center={}, corner={}", inside_center, inside_corner);
3023 }
3024 } else {
3025 crate::println!("CSS_SELFTEST FAIL: clip-path inset round parse failed");
3026 }
3027 }
3028 {
3030 let sheet = parse_css("h1 { -webkit-text-stroke: 2px #ff0000; }");
3031 total += 1;
3032 if !sheet.rules.is_empty() {
3033 passed += 1;
3034 } else {
3035 crate::println!("CSS_SELFTEST FAIL: text-stroke parse failed");
3036 }
3037 }
3038 {
3046 let dom = super::super::dom::parse_html("<div id='d'></div>");
3047 let sheet = parse_css(
3048 "#d{background:url('x.jpg') center/cover no-repeat}",
3049 );
3050 let styled = style_tree(&dom, &sheet);
3051 fn find_div<'a>(node: &'a StyledNode<'a>) -> Option<&'a StyledNode<'a>> {
3052 if let NodeType::Element { id, .. } = &node.node.node_type {
3053 if id.as_deref() == Some("d") {
3054 return Some(node);
3055 }
3056 }
3057 for c in &node.children {
3058 if let Some(r) = find_div(c) {
3059 return Some(r);
3060 }
3061 }
3062 None
3063 }
3064 total += 1;
3065 if let Some(d) = find_div(&styled) {
3066 let size = d.specified_values.get("background-size").map(|s| s.as_str());
3067 let position = d.specified_values.get("background-position").map(|s| s.as_str());
3068 let repeat = d.specified_values.get("background-repeat").map(|s| s.as_str());
3069 if size == Some("cover") && position == Some("center") && repeat == Some("no-repeat") {
3070 passed += 1;
3071 } else {
3072 crate::println!(
3073 "CSS_SELFTEST FAIL: background shorthand slash expansion => size={:?} position={:?} repeat={:?}",
3074 size, position, repeat
3075 );
3076 }
3077 } else {
3078 crate::println!("CSS_SELFTEST FAIL: background shorthand slash expansion => #d not found");
3079 }
3080 }
3081 {
3090 let sheet = parse_css(
3091 "@font-face{font-family:'Noto Sans JP';src:url(a.woff2) format('woff2'),url(b.ttf) format('truetype')}",
3092 );
3093 total += 1;
3094 if sheet.font_faces.len() == 1
3095 && sheet.font_faces[0].family == "Noto Sans JP"
3096 && sheet.font_faces[0].src_url.as_deref() == Some("a.woff2")
3097 {
3098 passed += 1;
3099 } else {
3100 crate::println!(
3101 "CSS_SELFTEST FAIL: @font-face parse => {:?}",
3102 sheet.font_faces
3103 );
3104 }
3105 }
3106 {
3109 let sheet = parse_css("@font-face{font-family:'X';src:url(only.ttf)}");
3110 total += 1;
3111 if sheet.font_faces.len() == 1 && sheet.font_faces[0].src_url.as_deref() == Some("only.ttf")
3112 {
3113 passed += 1;
3114 } else {
3115 crate::println!(
3116 "CSS_SELFTEST FAIL: @font-face parse (no format hint) => {:?}",
3117 sheet.font_faces
3118 );
3119 }
3120 }
3121 {
3127 let font_bytes = alloc::vec::Vec::from(
3128 &include_bytes!("../../../fonts/ipa/ipag.ttf")[..],
3129 );
3130 total += 1;
3131 let ok = crate::kernel::vector_font::register_custom_font("Test Custom Font", font_bytes);
3132 if ok && crate::kernel::vector_font::has_custom_font("test custom font") {
3133 passed += 1;
3134 } else {
3135 crate::println!("CSS_SELFTEST FAIL: register_custom_font round-trip => registered={}", ok);
3136 }
3137 }
3138 {
3149 total += 1;
3150 let font_a = include_bytes!("../../../fonts/MPLUS2/MPLUS2-Regular.ttf");
3151 let font_b = include_bytes!("../../../fonts/ipa/ipag.ttf");
3152 match crate::kernel::vector_font::VectorFont::new(font_a) {
3153 Ok(mut vf) => {
3154 let had_glyph_before = vf.get_glyph('a', 16).is_some();
3155 let switched = vf.set_primary(font_b).is_ok();
3156 let has_glyph_after = vf.get_glyph('a', 16).is_some();
3157 if had_glyph_before && switched && has_glyph_after {
3158 passed += 1;
3159 } else {
3160 crate::println!(
3161 "CSS_SELFTEST FAIL: VectorFont::set_primary => before={} switched={} after={}",
3162 had_glyph_before, switched, has_glyph_after
3163 );
3164 }
3165 }
3166 Err(_) => {
3167 crate::println!("CSS_SELFTEST FAIL: VectorFont::set_primary => failed to build base font");
3168 }
3169 }
3170 }
3171 {
3181 total += 1;
3182 if let Some(dec) = crate::kernel::vector_font::decompress_brotli(&[6], 0) {
3183 if dec.is_empty() {
3184 passed += 1;
3185 } else {
3186 crate::println!("CSS_SELFTEST FAIL: decompress_brotli empty stream should be empty");
3187 }
3188 } else {
3189 crate::println!("CSS_SELFTEST FAIL: decompress_brotli empty stream failed");
3190 }
3191 }
3192 {
3194 let fake_woff2 = alloc::vec![b'w', b'O', b'F', b'2', 0, 0, 0, 0];
3195 total += 1;
3196 if !crate::kernel::vector_font::register_custom_font("Fake Woff2", fake_woff2) {
3197 passed += 1;
3198 } else {
3199 crate::println!("CSS_SELFTEST FAIL: register_custom_font should reject invalid WOFF2 header");
3200 }
3201 }
3202 {
3204 total += 1;
3205 let html = "<html><body><div id='parent' style='word-break:break-all;overflow-wrap:break-word;'><span id='child'>text</span></div></body></html>";
3206 let doc = super::super::dom::parse_html(html);
3207 let stylesheet = parse_css("");
3208 let styled = style_tree(&doc, &stylesheet);
3209 fn find_by_id<'a>(node: &'a StyledNode<'a>, id: &str) -> Option<&'a StyledNode<'a>> {
3210 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
3211 if nid.as_deref() == Some(id) {
3212 return Some(node);
3213 }
3214 }
3215 for c in &node.children {
3216 if let Some(r) = find_by_id(c, id) {
3217 return Some(r);
3218 }
3219 }
3220 None
3221 }
3222 if let Some(child) = find_by_id(&styled, "child") {
3223 let wb = child.value("word-break").map(|v| v.trim() == "break-all").unwrap_or(false);
3224 let ow = child.value("overflow-wrap").map(|v| v.trim() == "break-word").unwrap_or(false);
3225 if wb && ow {
3226 passed += 1;
3227 } else {
3228 crate::println!("CSS_SELFTEST FAIL: word-break/overflow-wrap inheritance => wb={} ow={}", wb, ow);
3229 }
3230 } else {
3231 crate::println!("CSS_SELFTEST FAIL: child node not found in style_tree");
3232 }
3233 }
3234 for (mode, src, dst, expect) in blend_cases {
3235 let got = super::super::web_engine::draw_helpers::blend_pixel(mode, *src, *dst);
3236 if got == *expect {
3237 passed += 1;
3238 } else {
3239 crate::println!(
3240 "CSS_SELFTEST FAIL: blend_pixel(`{}`, {:?}, {:?}) => {:?} (want {:?})",
3241 mode,
3242 src,
3243 dst,
3244 got,
3245 expect
3246 );
3247 }
3248 }
3249 for (filter, input, expect) in filter_cases {
3250 let got = super::super::web_engine::draw_helpers::apply_css_filters(filter, input.0, input.1, input.2);
3251 if got == *expect {
3252 passed += 1;
3253 } else {
3254 crate::println!(
3255 "CSS_SELFTEST FAIL: apply_css_filters(`{}`, {:?}) => {:?} (want {:?})",
3256 filter,
3257 input,
3258 got,
3259 expect
3260 );
3261 }
3262 }
3263 for (kind, n, upper, expect) in alpha_roman_cases {
3264 let got = if *kind == "alpha" {
3265 super::super::web_engine::number_to_alpha(*n, *upper)
3266 } else {
3267 super::super::web_engine::number_to_roman(*n, *upper)
3268 };
3269 if got == *expect {
3270 passed += 1;
3271 } else {
3272 crate::println!(
3273 "CSS_SELFTEST FAIL: number_to_{}({}, {}) => `{}` (want `{}`)",
3274 kind,
3275 n,
3276 upper,
3277 got,
3278 expect
3279 );
3280 }
3281 }
3282 for (input, mode, expect) in tt_cases {
3283 if apply_text_transform(input, mode) == *expect {
3284 passed += 1;
3285 } else {
3286 crate::println!(
3287 "CSS_SELFTEST FAIL: text-transform({}, {}) => `{}` (want `{}`)",
3288 input,
3289 mode,
3290 apply_text_transform(input, mode),
3291 expect
3292 );
3293 }
3294 }
3295 for (html, css, id, expect) in cases {
3296 let dom = super::super::dom::parse_html(html);
3297 let sheet = parse_css(css);
3298 let styled = style_tree(&dom, &sheet);
3299 let got = find_marked(&styled, id).unwrap_or(false);
3300 if got == *expect {
3301 passed += 1;
3302 } else {
3303 crate::println!(
3304 "CSS_SELFTEST FAIL: `{}` + `{}` on #{} => {} (want {})",
3305 html,
3306 css,
3307 id,
3308 got,
3309 expect
3310 );
3311 }
3312 }
3313 for (html, css, id, prop, expect) in value_cases {
3314 let dom = super::super::dom::parse_html(html);
3315 let sheet = parse_css(css);
3316 let styled = style_tree(&dom, &sheet);
3317 let got = find_prop(&styled, id, prop).unwrap_or_default();
3318 if got == *expect {
3319 passed += 1;
3320 } else {
3321 crate::println!(
3322 "CSS_SELFTEST FAIL: `{}` + `{}` on #{}.{} => `{}` (want `{}`)",
3323 html,
3324 css,
3325 id,
3326 prop,
3327 got,
3328 expect
3329 );
3330 }
3331 }
3332 {
3336 fn find_node<'a>(node: &'a StyledNode, id: &str) -> Option<&'a StyledNode<'a>> {
3337 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
3338 if nid.as_deref() == Some(id) {
3339 return Some(node);
3340 }
3341 }
3342 for c in &node.children {
3343 if let Some(r) = find_node(c, id) {
3344 return Some(r);
3345 }
3346 }
3347 None
3348 }
3349 let dom = super::super::dom::parse_html("<input id='t' type='text'>");
3350 let sheet = parse_css("input:focus{border-color:#ff0000}");
3351 let styled = style_tree(&dom, &sheet);
3352 let focus_border = find_node(&styled, "t")
3353 .and_then(|n| n.focus_values.as_ref())
3354 .and_then(|h| h.get("border-color").cloned())
3355 .unwrap_or_default();
3356 if focus_border == "#ff0000" {
3357 passed += 1;
3358 } else {
3359 crate::println!(
3360 "CSS_SELFTEST FAIL: :focus border-color => `{}` (want `#ff0000`)",
3361 focus_border
3362 );
3363 }
3364 let sheet_fv = parse_css("input:focus-visible{border-color:#00ff00}");
3366 let styled_fv = style_tree(&dom, &sheet_fv);
3367 let focus_visible_border = find_node(&styled_fv, "t")
3368 .and_then(|n| n.focus_values.as_ref())
3369 .and_then(|h| h.get("border-color").cloned())
3370 .unwrap_or_default();
3371 if focus_visible_border == "#00ff00" {
3372 passed += 1;
3373 } else {
3374 crate::println!(
3375 "CSS_SELFTEST FAIL: :focus-visible border-color => `{}` (want `#00ff00`)",
3376 focus_visible_border
3377 );
3378 }
3379 let sheet_active = parse_css("input:active{border-color:#0000ff}");
3382 let styled_active = style_tree(&dom, &sheet_active);
3383 let active_border = find_node(&styled_active, "t")
3384 .and_then(|n| n.active_values.as_ref())
3385 .and_then(|h| h.get("border-color").cloned())
3386 .unwrap_or_default();
3387 if active_border == "#0000ff" {
3388 passed += 1;
3389 } else {
3390 crate::println!(
3391 "CSS_SELFTEST FAIL: :active border-color => `{}` (want `#0000ff`)",
3392 active_border
3393 );
3394 }
3395 }
3396
3397 {
3403 let s1 = parse_selector("div::before").map(|s| selector_specificity(&s));
3404 let s2 = parse_selector("div").map(|s| selector_specificity(&s));
3405 if s1 == Some((0, 0, 2)) && s2 == Some((0, 0, 1)) {
3406 passed += 1;
3407 } else {
3408 crate::println!(
3409 "CSS_SELFTEST FAIL: pseudo-element specificity => div::before={:?} (want Some((0,0,2))), div={:?} (want Some((0,0,1)))",
3410 s1, s2
3411 );
3412 }
3413 }
3414
3415 {
3419 let dom_root = super::super::dom::parse_html("<div id='child'></div>");
3420 let sheet_root = parse_css(":root{zz:1}");
3421 let styled_root = style_tree(&dom_root, &sheet_root);
3422 let root_marker = styled_root
3423 .specified_values
3424 .get("zz")
3425 .cloned()
3426 .unwrap_or_default();
3427 if root_marker == "1" {
3428 passed += 1;
3429 } else {
3430 crate::println!(
3431 "CSS_SELFTEST FAIL: :root zz => `{}` (want `1`)",
3432 root_marker
3433 );
3434 }
3435 fn find_node2<'a>(node: &'a StyledNode, id: &str) -> Option<&'a StyledNode<'a>> {
3437 if let NodeType::Element { id: nid, .. } = &node.node.node_type {
3438 if nid.as_deref() == Some(id) {
3439 return Some(node);
3440 }
3441 }
3442 for c in &node.children {
3443 if let Some(r) = find_node2(c, id) {
3444 return Some(r);
3445 }
3446 }
3447 None
3448 }
3449 let child_matched = find_node2(&styled_root, "child")
3450 .and_then(|n| n.specified_values.get("zz").cloned())
3451 .is_some();
3452 if !child_matched {
3453 passed += 1;
3454 } else {
3455 crate::println!("CSS_SELFTEST FAIL: :root should not match non-root child element");
3456 }
3457 }
3458
3459 {
3464 fn find_tag_text<'a>(node: &'a super::super::dom::Node, tag: &str) -> Option<&'a str> {
3465 if let NodeType::Element { tag_name, .. } = &node.node_type {
3466 if tag_name == tag {
3467 for c in &node.children {
3468 if let NodeType::Text(t) = &c.node_type {
3469 return Some(t.as_str());
3470 }
3471 }
3472 return None;
3473 }
3474 }
3475 for c in &node.children {
3476 if let Some(r) = find_tag_text(c, tag) {
3477 return Some(r);
3478 }
3479 }
3480 None
3481 }
3482 let dom = super::super::dom::parse_html(
3483 "<html><style>a{color:red}</style><script>var x=1;</script></html>",
3484 );
3485 let style_text = find_tag_text(&dom, "style").unwrap_or("");
3486 if style_text.contains("color:red") {
3487 passed += 1;
3488 } else {
3489 crate::println!(
3490 "CSS_SELFTEST FAIL: <style> text child => `{}` (want to contain `color:red`)",
3491 style_text
3492 );
3493 }
3494 let script_text = find_tag_text(&dom, "script").unwrap_or("");
3495 if script_text.contains("var x=1") {
3496 passed += 1;
3497 } else {
3498 crate::println!(
3499 "CSS_SELFTEST FAIL: <script> text child => `{}` (want to contain `var x=1`)",
3500 script_text
3501 );
3502 }
3503 }
3504
3505 let mut anim_total = 0usize;
3508 let mut anim_check = |desc: &str, got: String, want: &str| {
3509 anim_total += 1;
3510 if got == want {
3511 passed += 1;
3512 } else {
3513 crate::println!(
3514 "CSS_SELFTEST FAIL: anim {} => `{}` (want `{}`)",
3515 desc,
3516 got,
3517 want
3518 );
3519 }
3520 };
3521
3522 anim_check(
3524 "linear@0.5",
3525 fmt_num(TimingFunction::Linear.ease(0.5)),
3526 "0.5",
3527 );
3528 anim_check("linear@0", fmt_num(TimingFunction::Linear.ease(0.0)), "0");
3529 anim_check("linear@1", fmt_num(TimingFunction::Linear.ease(1.0)), "1");
3530 {
3532 let e = TimingFunction::parse("ease").ease(0.5);
3533 anim_check("ease@0.5>0.5", (e > 0.55).to_string(), "true");
3534 }
3535 {
3537 let e = TimingFunction::parse("ease-in").ease(0.5);
3538 anim_check("ease-in@0.5<0.5", (e < 0.45).to_string(), "true");
3539 }
3540 anim_check(
3542 "ease@0",
3543 fmt_num(TimingFunction::parse("ease").ease(0.0)),
3544 "0",
3545 );
3546 anim_check(
3547 "ease@1",
3548 fmt_num(TimingFunction::parse("ease").ease(1.0)),
3549 "1",
3550 );
3551 anim_check(
3553 "steps(4,end)@0.5",
3554 fmt_num(TimingFunction::parse("steps(4, end)").ease(0.5)),
3555 "0.5",
3556 );
3557 anim_check(
3559 "steps(4,end)@0.7",
3560 fmt_num(TimingFunction::parse("steps(4, end)").ease(0.7)),
3561 "0.5",
3562 );
3563 anim_check(
3565 "steps(4,start)@0.5",
3566 fmt_num(TimingFunction::parse("steps(4, start)").ease(0.5)),
3567 "0.75",
3568 );
3569 anim_check(
3571 "cubic-linear@0.3",
3572 fmt_num(TimingFunction::parse("cubic-bezier(0,0,1,1)").ease(0.3)),
3573 "0.3",
3574 );
3575
3576 anim_check("px@0.5", interpolate_value("0px", "100px", 0.5), "50px");
3578 anim_check("px@0", interpolate_value("10px", "100px", 0.0), "10px");
3579 anim_check("px@1", interpolate_value("10px", "100px", 1.0), "100px");
3580 anim_check("num@0.25", interpolate_value("0", "1", 0.25), "0.25");
3581 anim_check("pct@0.5", interpolate_value("0%", "50%", 0.5), "25%");
3582 anim_check("em@0.5", interpolate_value("1em", "3em", 0.5), "2em");
3583 anim_check("zero-to-pct@0.5", interpolate_value("0", "100%", 0.5), "50%");
3588 anim_check("zero-to-pct@0", interpolate_value("0", "100%", 0.0), "0%");
3589 anim_check("zero-to-pct@1", interpolate_value("0", "100%", 1.0), "100%");
3590 anim_check("pct-to-zero@0.5", interpolate_value("100%", "0", 0.5), "50%");
3591 anim_check("zero-to-px@0.5", interpolate_value("0", "40px", 0.5), "20px");
3592 anim_check(
3600 "translateY-fadein@0.5",
3601 interpolate_value("translateY(30px)", "translateY(0)", 0.5),
3602 "translateY(15px)",
3603 );
3604 anim_check(
3606 "color@0.5",
3607 interpolate_value("#000000", "#ffffff", 0.5),
3608 "#7f7f7f",
3609 );
3610 anim_check(
3611 "color-name@0",
3612 interpolate_value("red", "blue", 0.0),
3613 "#ff0000",
3614 );
3615 anim_check(
3617 "multi@0.5",
3618 interpolate_value("0px 0px", "10px 20px", 0.5),
3619 "5px 10px",
3620 );
3621 anim_check(
3623 "translateX@0.5",
3624 interpolate_value("translateX(0px)", "translateX(40px)", 0.5),
3625 "translateX(20px)",
3626 );
3627 anim_check(
3630 "translate-xy@0.5",
3631 interpolate_value("translate(10px, 20px)", "translate(30px, 40px)", 0.5),
3632 "translate(20px, 30px)",
3633 );
3634 anim_check(
3636 "matrix@0.5",
3637 interpolate_value("matrix(0, 0, 0, 0, 0, 0)", "matrix(2, 2, 2, 2, 2, 2)", 0.5),
3638 "matrix(1, 1, 1, 1, 1, 1)",
3639 );
3640 anim_check(
3643 "translate-rotate-chain@0.5",
3644 interpolate_value(
3645 "translateX(0px) rotate(0deg)",
3646 "translateX(40px) rotate(90deg)",
3647 0.5,
3648 ),
3649 "translateX(20px) rotate(45deg)",
3650 );
3651 anim_check(
3655 "multi-layer-shadow@0.5",
3656 interpolate_value("0px 0px, 1px 1px", "10px 10px, 21px 21px", 0.5),
3657 "5px 5px, 11px 11px",
3658 );
3659 anim_check(
3661 "discrete@0.3",
3662 interpolate_value("auto", "100px", 0.3),
3663 "auto",
3664 );
3665 anim_check(
3666 "discrete@0.7",
3667 interpolate_value("auto", "100px", 0.7),
3668 "100px",
3669 );
3670
3671 anim_check("time-1s", fmt_num(parse_time_ms("1s")), "1000");
3673 anim_check("time-250ms", fmt_num(parse_time_ms("250ms")), "250");
3674 anim_check("time-0.5s", fmt_num(parse_time_ms("0.5s")), "500");
3675
3676 {
3678 let specs = parse_transition("width 1s ease, background-color 0.5s linear 0.2s");
3679 anim_check("trans-count", specs.len().to_string(), "2");
3680 if specs.len() == 2 {
3681 anim_check("trans0-prop", specs[0].property.clone(), "width");
3682 anim_check("trans0-dur", fmt_num(specs[0].duration_ms), "1000");
3683 anim_check("trans1-prop", specs[1].property.clone(), "background-color");
3684 anim_check("trans1-delay", fmt_num(specs[1].delay_ms), "200");
3685 anim_check(
3686 "trans1-timing",
3687 (specs[1].timing == TimingFunction::Linear).to_string(),
3688 "true",
3689 );
3690 }
3691 }
3692
3693 {
3698 let specs = parse_transition("flex-basis 0.3s ease");
3699 anim_check("trans-s-count-flexbasis", specs.len().to_string(), "1");
3700 if specs.len() == 1 {
3701 anim_check("trans-s-prop-flexbasis", specs[0].property.clone(), "flex-basis");
3702 anim_check("trans-s-dur-flexbasis", fmt_num(specs[0].duration_ms), "300");
3703 }
3704 }
3705 {
3706 let specs = parse_transition("border-radius 0.5s linear");
3707 if specs.len() == 1 {
3708 anim_check("trans-s-prop-borderradius", specs[0].property.clone(), "border-radius");
3709 anim_check("trans-s-dur-borderradius", fmt_num(specs[0].duration_ms), "500");
3710 }
3711 }
3712
3713 {
3715 let specs = parse_animations("appears 1s ease");
3716 anim_check("anim-s-count", specs.len().to_string(), "1");
3717 if specs.len() == 1 {
3718 anim_check("anim-s-name", specs[0].name.clone(), "appears");
3719 anim_check("anim-s-dur", fmt_num(specs[0].duration_ms), "1000");
3720 }
3721 }
3722
3723 {
3728 let specs = parse_animations("2.5s steps(16,end) typing");
3729 anim_check("anim-steps-count", specs.len().to_string(), "1");
3730 if specs.len() == 1 {
3731 anim_check("anim-steps-name", specs[0].name.clone(), "typing");
3732 anim_check("anim-steps-dur", fmt_num(specs[0].duration_ms), "2500");
3733 anim_check("anim-steps-timing", fmt_num(specs[0].timing.ease(0.5)), "0.5");
3735 }
3736 }
3737
3738 {
3740 let spec = TransitionSpec {
3741 property: "width".into(),
3742 duration_ms: 1000.0,
3743 timing: TimingFunction::Linear,
3744 delay_ms: 0.0,
3745 };
3746 let (v, active) = sample_transition(&spec, "0px", "100px", 500.0);
3747 anim_check("samp-trans-mid", v, "50px");
3748 anim_check("samp-trans-active", active.to_string(), "true");
3749 let (v2, active2) = sample_transition(&spec, "0px", "100px", 1200.0);
3750 anim_check("samp-trans-end", v2, "100px");
3751 anim_check("samp-trans-done", active2.to_string(), "false");
3752 let spec_d = TransitionSpec {
3754 property: "width".into(),
3755 duration_ms: 1000.0,
3756 timing: TimingFunction::Linear,
3757 delay_ms: 300.0,
3758 };
3759 let (v3, _) = sample_transition(&spec_d, "0px", "100px", 100.0);
3760 anim_check("samp-trans-delay", v3, "0px");
3761 }
3762
3763 {
3765 let sheet = parse_css(
3766 "@keyframes slide { from { left: 0px; } to { left: 100px; } } \
3767 @keyframes fade { 0% { opacity: 0; } 50% { opacity: 1; } 100% { opacity: 0; } }",
3768 );
3769 anim_check("kf-count", sheet.keyframes.len().to_string(), "2");
3770 let slide = sheet.keyframes.iter().find(|k| k.name == "slide");
3771 anim_check("kf-slide-found", slide.is_some().to_string(), "true");
3772 if let Some(slide) = slide {
3773 anim_check("kf-slide-frames", slide.frames.len().to_string(), "2");
3774 let spec = AnimationSpec {
3775 name: "slide".into(),
3776 duration_ms: 1000.0,
3777 timing: TimingFunction::Linear,
3778 delay_ms: 0.0,
3779 iterations: 1.0,
3780 direction: "normal".into(),
3781 fill: "none".into(),
3782 play_state: "running".into(),
3783 };
3784 let m = sample_animation(&spec, slide, 500.0);
3785 anim_check(
3786 "anim-slide-mid",
3787 m.get("left").cloned().unwrap_or_default(),
3788 "50px",
3789 );
3790 }
3791 let fade = sheet.keyframes.iter().find(|k| k.name == "fade");
3792 if let Some(fade) = fade {
3793 anim_check("kf-fade-frames", fade.frames.len().to_string(), "3");
3794 let spec = AnimationSpec {
3795 name: "fade".into(),
3796 duration_ms: 1000.0,
3797 timing: TimingFunction::Linear,
3798 delay_ms: 0.0,
3799 iterations: 1.0,
3800 direction: "normal".into(),
3801 fill: "none".into(),
3802 play_state: "running".into(),
3803 };
3804 let m = sample_animation(&spec, fade, 250.0);
3806 anim_check(
3807 "anim-fade-q1",
3808 m.get("opacity").cloned().unwrap_or_default(),
3809 "0.5",
3810 );
3811 let m2 = sample_animation(&spec, fade, 500.0);
3813 anim_check(
3814 "anim-fade-mid",
3815 m2.get("opacity").cloned().unwrap_or_default(),
3816 "1",
3817 );
3818 }
3819 }
3820
3821 {
3823 let spec = parse_animation("slide 2s ease-in-out 0.5s infinite alternate both");
3824 anim_check("anim-parse-some", spec.is_some().to_string(), "true");
3825 if let Some(s) = spec {
3826 anim_check("anim-name", s.name.clone(), "slide");
3827 anim_check("anim-dur", fmt_num(s.duration_ms), "2000");
3828 anim_check("anim-delay", fmt_num(s.delay_ms), "500");
3829 anim_check(
3830 "anim-iter-inf",
3831 s.iterations.is_infinite().to_string(),
3832 "true",
3833 );
3834 anim_check("anim-dir", s.direction.clone(), "alternate");
3835 anim_check("anim-fill", s.fill.clone(), "both");
3836 }
3837 }
3838
3839 {
3842 let specs = parse_animations("fade 1s, bounce 2s infinite");
3843 anim_check("anim-multi-count", specs.len().to_string(), "2");
3844 if specs.len() == 2 {
3845 anim_check("anim-multi-name-0", specs[0].name.clone(), "fade");
3846 anim_check("anim-multi-dur-0", fmt_num(specs[0].duration_ms), "1000");
3847 anim_check("anim-multi-name-1", specs[1].name.clone(), "bounce");
3848 anim_check("anim-multi-dur-1", fmt_num(specs[1].duration_ms), "2000");
3849 anim_check(
3850 "anim-multi-iter-1-inf",
3851 specs[1].iterations.is_infinite().to_string(),
3852 "true",
3853 );
3854 }
3855 }
3856
3857 {
3859 let sheet = parse_css("@keyframes mv { from { left: 0px; } to { left: 100px; } }");
3860 let kf = &sheet.keyframes[0];
3861 let spec = AnimationSpec {
3862 name: "mv".into(),
3863 duration_ms: 1000.0,
3864 timing: TimingFunction::Linear,
3865 delay_ms: 0.0,
3866 iterations: 4.0,
3867 direction: "alternate".into(),
3868 fill: "none".into(),
3869 play_state: "running".into(),
3870 };
3871 let m0 = sample_animation(&spec, kf, 500.0);
3873 anim_check(
3874 "anim-alt-iter0",
3875 m0.get("left").cloned().unwrap_or_default(),
3876 "50px",
3877 );
3878 let m1 = sample_animation(&spec, kf, 1100.0);
3881 anim_check(
3882 "anim-alt-iter1",
3883 m1.get("left").cloned().unwrap_or_default(),
3884 "90px",
3885 );
3886 }
3887
3888 {
3894 let sheet = parse_css("@keyframes mv3b { from { left: 0px; } to { left: 100px; } }");
3895 let kf = &sheet.keyframes[0];
3896 let spec = AnimationSpec {
3897 name: "mv3b".into(),
3898 duration_ms: 1000.0,
3899 timing: TimingFunction::Linear,
3900 delay_ms: 0.0,
3901 iterations: 2.25,
3902 direction: "alternate".into(),
3903 fill: "forwards".into(),
3904 play_state: "running".into(),
3905 };
3906 let m = sample_animation(&spec, kf, 2250.0);
3910 anim_check(
3911 "anim-frac-iter-alternate-forwards",
3912 m.get("left").cloned().unwrap_or_default(),
3913 "25px",
3914 );
3915 }
3916
3917 {
3919 let sheet = parse_css("@keyframes mv2 { from { left: 0px; } to { left: 100px; } }");
3920 let kf = &sheet.keyframes[0];
3921 let spec = AnimationSpec {
3922 name: "mv2".into(),
3923 duration_ms: 1000.0,
3924 timing: TimingFunction::Linear,
3925 delay_ms: 0.0,
3926 iterations: 1.0,
3927 direction: "normal".into(),
3928 fill: "forwards".into(),
3929 play_state: "running".into(),
3930 };
3931 let m = sample_animation(&spec, kf, 2000.0);
3933 anim_check(
3934 "anim-fill-fwd",
3935 m.get("left").cloned().unwrap_or_default(),
3936 "100px",
3937 );
3938 }
3939
3940 {
3942 let mut eng = AnimationEngine::new();
3943 let spec = TransitionSpec {
3944 property: "left".into(),
3945 duration_ms: 1000.0,
3946 timing: TimingFunction::Linear,
3947 delay_ms: 0.0,
3948 };
3949 eng.start_transition("box", "left", "0px", "100px", spec, 0.0);
3951 anim_check(
3952 "eng-tr-active",
3953 if eng.is_active() {
3954 "y".into()
3955 } else {
3956 "n".into()
3957 },
3958 "y",
3959 );
3960 let active = eng.tick(500.0);
3962 let v = eng
3963 .computed
3964 .iter()
3965 .find(|(e, p, _)| e == "box" && p == "left")
3966 .map(|(_, _, v)| v.clone())
3967 .unwrap_or_default();
3968 anim_check("eng-tr-mid", v, "50px");
3969 anim_check(
3970 "eng-tr-mid-active",
3971 if active { "y".into() } else { "n".into() },
3972 "y",
3973 );
3974 eng.tick(1000.0);
3976 let v_end = eng
3977 .computed
3978 .iter()
3979 .find(|(e, p, _)| e == "box" && p == "left")
3980 .map(|(_, _, v)| v.clone())
3981 .unwrap_or_default();
3982 anim_check("eng-tr-end", v_end, "100px");
3983 anim_check(
3984 "eng-tr-done",
3985 if eng.is_active() {
3986 "y".into()
3987 } else {
3988 "n".into()
3989 },
3990 "n",
3991 );
3992 }
3993
3994 {
3999 let mut eng = AnimationEngine::new();
4000 let spec = TransitionSpec {
4001 property: "left".into(),
4002 duration_ms: 1000.0,
4003 timing: TimingFunction::Linear,
4004 delay_ms: 0.0,
4005 };
4006 eng.start_transition("box3", "left", "0px", "100px", spec, 0.0);
4007 eng.tick(500.0);
4010 anim_check(
4011 "eng-transitionend-mid-empty",
4012 if eng.lifecycle_events.is_empty() { "y".into() } else { "n".into() },
4013 "y",
4014 );
4015 eng.tick(1000.0);
4017 let got = eng
4018 .lifecycle_events
4019 .iter()
4020 .find(|(e, ty, _)| e == "box3" && *ty == "transitionend")
4021 .map(|(_, _, name)| name.clone())
4022 .unwrap_or_default();
4023 anim_check("eng-transitionend-name", got, "left");
4024 eng.tick(1500.0);
4027 anim_check(
4028 "eng-transitionend-no-duplicate",
4029 if eng.lifecycle_events.is_empty() { "y".into() } else { "n".into() },
4030 "y",
4031 );
4032 }
4033 {
4034 let sheet = parse_css("@keyframes fadein { from { opacity: 0; } to { opacity: 1; } }");
4035 let kf = sheet.keyframes[0].clone();
4036 let spec = AnimationSpec {
4037 name: "fadein".into(),
4038 duration_ms: 1000.0,
4039 timing: TimingFunction::Linear,
4040 delay_ms: 100.0,
4041 iterations: 1.0,
4042 direction: "normal".into(),
4043 fill: "none".into(),
4044 play_state: "running".into(),
4045 };
4046 let mut eng = AnimationEngine::new();
4047 eng.start_animation("box4", spec, kf, 0.0);
4048 eng.tick(50.0);
4050 anim_check(
4051 "eng-animationstart-before-delay-empty",
4052 if eng.lifecycle_events.is_empty() { "y".into() } else { "n".into() },
4053 "y",
4054 );
4055 eng.tick(500.0);
4058 let got_start = eng
4059 .lifecycle_events
4060 .iter()
4061 .find(|(e, ty, _)| e == "box4" && *ty == "animationstart")
4062 .map(|(_, _, name)| name.clone())
4063 .unwrap_or_default();
4064 anim_check("eng-animationstart-name", got_start, "fadein");
4065 anim_check(
4066 "eng-animationend-mid-empty",
4067 if eng.lifecycle_events.iter().any(|(_, ty, _)| *ty == "animationend") {
4068 "n".into()
4069 } else {
4070 "y".into()
4071 },
4072 "y",
4073 );
4074 eng.tick(1100.0);
4077 let got = eng
4078 .lifecycle_events
4079 .iter()
4080 .find(|(e, ty, _)| e == "box4" && *ty == "animationend")
4081 .map(|(_, _, name)| name.clone())
4082 .unwrap_or_default();
4083 anim_check("eng-animationend-name", got, "fadein");
4084 }
4085
4086 {
4090 let mut eng = AnimationEngine::new();
4091 let spec = TransitionSpec {
4092 property: "opacity".into(),
4093 duration_ms: 1000.0,
4094 timing: TimingFunction::Linear,
4095 delay_ms: 0.0,
4096 };
4097 eng.start_transition("box2", "opacity", "1", "0", spec.clone(), 0.0);
4099 eng.start_transition("box2", "opacity", "0", "1", spec, 500.0);
4103 eng.tick(500.0);
4104 let v_retarget_start = eng
4105 .computed
4106 .iter()
4107 .find(|(e, p, _)| e == "box2" && p == "opacity")
4108 .map(|(_, _, v)| v.clone())
4109 .unwrap_or_default();
4110 anim_check("eng-tr-retarget-from-current", v_retarget_start, "0.5");
4111 eng.tick(1000.0);
4113 let v_retarget_mid = eng
4114 .computed
4115 .iter()
4116 .find(|(e, p, _)| e == "box2" && p == "opacity")
4117 .map(|(_, _, v)| v.clone())
4118 .unwrap_or_default();
4119 anim_check("eng-tr-retarget-progress", v_retarget_mid, "0.75");
4120 }
4121
4122 {
4124 let sheet = parse_css("@keyframes slide { from { left: 0px; } to { left: 200px; } }");
4125 let kf = sheet.keyframes[0].clone();
4126 let spec = AnimationSpec {
4127 name: "slide".into(),
4128 duration_ms: 1000.0,
4129 timing: TimingFunction::Linear,
4130 delay_ms: 0.0,
4131 iterations: 1.0,
4132 direction: "normal".into(),
4133 fill: "forwards".into(),
4134 play_state: "running".into(),
4135 };
4136 let mut eng = AnimationEngine::new();
4137 eng.start_animation("a", spec, kf, 0.0);
4138 eng.tick(250.0);
4140 let v = eng
4141 .computed
4142 .iter()
4143 .find(|(e, p, _)| e == "a" && p == "left")
4144 .map(|(_, _, v)| v.clone())
4145 .unwrap_or_default();
4146 anim_check("eng-anim-q1", v, "50px");
4147 eng.tick(2000.0);
4149 let v_end = eng
4150 .computed
4151 .iter()
4152 .find(|(e, p, _)| e == "a" && p == "left")
4153 .map(|(_, _, v)| v.clone())
4154 .unwrap_or_default();
4155 anim_check("eng-anim-fwd-hold", v_end, "200px");
4156 anim_check(
4157 "eng-anim-done",
4158 if eng.is_active() {
4159 "y".into()
4160 } else {
4161 "n".into()
4162 },
4163 "n",
4164 );
4165 }
4166
4167 {
4171 let sheet = parse_css("@keyframes slide2 { from { left: 0px; } to { left: 200px; } }");
4172 let kf = sheet.keyframes[0].clone();
4173 let mut spec = AnimationSpec {
4174 name: "slide2".into(),
4175 duration_ms: 1000.0,
4176 timing: TimingFunction::Linear,
4177 delay_ms: 0.0,
4178 iterations: 1.0,
4179 direction: "normal".into(),
4180 fill: "forwards".into(),
4181 play_state: "running".into(),
4182 };
4183 let mut eng = AnimationEngine::new();
4184 eng.start_animation("b", spec.clone(), kf.clone(), 0.0);
4185 eng.tick(250.0);
4187 spec.play_state = "paused".into();
4189 eng.start_animation("b", spec.clone(), kf.clone(), 250.0);
4190 eng.tick(250.0);
4191 let v_paused = eng
4192 .computed
4193 .iter()
4194 .find(|(e, p, _)| e == "b" && p == "left")
4195 .map(|(_, _, v)| v.clone())
4196 .unwrap_or_default();
4197 anim_check("eng-anim-paused-frozen1", v_paused.clone(), "50px");
4198 eng.tick(1000.0);
4200 let v_still_paused = eng
4201 .computed
4202 .iter()
4203 .find(|(e, p, _)| e == "b" && p == "left")
4204 .map(|(_, _, v)| v.clone())
4205 .unwrap_or_default();
4206 anim_check("eng-anim-paused-frozen2", v_still_paused, "50px");
4207 spec.play_state = "running".into();
4213 eng.start_animation("b", spec, kf, 1000.0);
4214 eng.tick(1000.0);
4215 let v_resume_instant = eng
4216 .computed
4217 .iter()
4218 .find(|(e, p, _)| e == "b" && p == "left")
4219 .map(|(_, _, v)| v.clone())
4220 .unwrap_or_default();
4221 anim_check("eng-anim-resume-instant", v_resume_instant, "50px");
4222 eng.tick(1250.0);
4223 let v_resumed = eng
4224 .computed
4225 .iter()
4226 .find(|(e, p, _)| e == "b" && p == "left")
4227 .map(|(_, _, v)| v.clone())
4228 .unwrap_or_default();
4229 anim_check("eng-anim-resumed", v_resumed, "100px");
4230 }
4231
4232 {
4237 let sheet = parse_css(
4238 "@keyframes fadeA { from { opacity: 0; } to { opacity: 1; } } \
4239 @keyframes fadeB { from { opacity: 1; } to { opacity: 0; } }",
4240 );
4241 let empty_kf = |name: &str| Keyframes { name: name.into(), frames: Vec::new() };
4242 let kf_a = sheet
4243 .keyframes
4244 .iter()
4245 .find(|k| k.name == "fadeA")
4246 .cloned()
4247 .unwrap_or_else(|| empty_kf("fadeA"));
4248 let kf_b = sheet
4249 .keyframes
4250 .iter()
4251 .find(|k| k.name == "fadeB")
4252 .cloned()
4253 .unwrap_or_else(|| empty_kf("fadeB"));
4254 let mut eng = AnimationEngine::new();
4255 let spec_a = AnimationSpec {
4256 name: "fadeA".into(),
4257 duration_ms: 1000.0,
4258 timing: TimingFunction::Linear,
4259 delay_ms: 0.0,
4260 iterations: f32::INFINITY,
4261 direction: "normal".into(),
4262 fill: "none".into(),
4263 play_state: "running".into(),
4264 };
4265 eng.start_animation("c", spec_a, kf_a, 0.0);
4266 eng.retain_animation_names("c", &[alloc::string::String::from("fadeB")]);
4269 let spec_b = AnimationSpec {
4270 name: "fadeB".into(),
4271 duration_ms: 1000.0,
4272 timing: TimingFunction::Linear,
4273 delay_ms: 0.0,
4274 iterations: 1.0,
4275 direction: "normal".into(),
4276 fill: "forwards".into(),
4277 play_state: "running".into(),
4278 };
4279 eng.start_animation("c", spec_b, kf_b, 500.0);
4280 eng.tick(500.0);
4281 let props: alloc::vec::Vec<&alloc::string::String> = eng
4282 .computed
4283 .iter()
4284 .filter(|(e, _, _)| e == "c")
4285 .map(|(_, p, _)| p)
4286 .collect();
4287 anim_check("eng-anim-name-switch-count", props.len().to_string(), "1");
4290 }
4291
4292 {
4297 let sheet_v1 = parse_css("@keyframes mv3 { from { left: 0px; } to { left: 100px; } }");
4298 let kf_v1 = sheet_v1.keyframes[0].clone();
4299 let spec = AnimationSpec {
4300 name: "mv3".into(),
4301 duration_ms: 1000.0,
4302 timing: TimingFunction::Linear,
4303 delay_ms: 0.0,
4304 iterations: f32::INFINITY,
4305 direction: "normal".into(),
4306 fill: "none".into(),
4307 play_state: "running".into(),
4308 };
4309 let mut eng = AnimationEngine::new();
4310 eng.start_animation("d", spec.clone(), kf_v1, 0.0);
4311 eng.tick(500.0);
4312 let v_before = eng
4313 .computed
4314 .iter()
4315 .find(|(e, p, _)| e == "d" && p == "left")
4316 .map(|(_, _, v)| v.clone())
4317 .unwrap_or_default();
4318 anim_check("eng-anim-kf-before-update", v_before, "50px");
4319 let sheet_v2 = parse_css("@keyframes mv3 { from { left: 0px; } to { left: 400px; } }");
4322 let kf_v2 = sheet_v2.keyframes[0].clone();
4323 eng.start_animation("d", spec, kf_v2, 500.0);
4324 eng.tick(500.0);
4325 let v_after = eng
4326 .computed
4327 .iter()
4328 .find(|(e, p, _)| e == "d" && p == "left")
4329 .map(|(_, _, v)| v.clone())
4330 .unwrap_or_default();
4331 anim_check("eng-anim-kf-after-update", v_after, "200px");
4332 }
4333
4334 {
4336 let mut eng = AnimationEngine::new();
4337 let spec = TransitionSpec {
4338 property: "left".into(),
4339 duration_ms: 1000.0,
4340 timing: TimingFunction::Linear,
4341 delay_ms: 0.0,
4342 };
4343 eng.start_transition("b", "left", "0px", "100px", spec.clone(), 0.0);
4344 eng.tick(500.0); eng.start_transition("b", "left", "50px", "0px", spec, 500.0);
4347 eng.tick(1000.0); let v = eng
4350 .computed
4351 .iter()
4352 .find(|(e, p, _)| e == "b" && p == "left")
4353 .map(|(_, _, v)| v.clone())
4354 .unwrap_or_default();
4355 anim_check("eng-retarget", v, "25px");
4356 }
4357
4358 let total = total + anim_total;
4359 (passed, total)
4360}