browser_outputparser.js (55542B)
1 /* Any copyright is dedicated to the Public Domain. 2 http://creativecommons.org/publicdomain/zero/1.0/ */ 3 4 "use strict"; 5 6 add_task(async function () { 7 await SpecialPowers.pushPrefEnv({ 8 set: [ 9 ["security.allow_unsafe_parent_loads", true], 10 ["layout.css.backdrop-filter.enabled", true], 11 ["layout.css.relative-color-syntax.enabled", true], 12 ["dom.security.html_serialization_escape_lt_gt", true], 13 ], 14 }); 15 await addTab("about:blank"); 16 await performTest(); 17 gBrowser.removeCurrentTab(); 18 }); 19 20 async function performTest() { 21 const OutputParser = require("resource://devtools/client/shared/output-parser.js"); 22 23 const { host, doc } = await createHost( 24 "bottom", 25 "data:text/html," + "<h1>browser_outputParser.js</h1><div></div>" 26 ); 27 28 const cssProperties = getClientCssProperties(); 29 30 const parser = new OutputParser(doc, cssProperties); 31 testParseCssProperty(doc, parser); 32 testParseCssVar(doc, parser); 33 testParseURL(doc, parser); 34 testParseFilter(doc, parser); 35 testParseBackdropFilter(doc, parser); 36 testParseAngle(doc, parser); 37 testParseShape(doc, parser); 38 testParseVariable(doc, parser); 39 testParseColorVariable(doc, parser); 40 testParseFontFamily(doc, parser); 41 testParseLightDark(doc, parser); 42 43 host.destroy(); 44 } 45 46 // Class name used in color swatch. 47 var COLOR_TEST_CLASS = "test-class"; 48 49 // Create a new CSS color-parsing test. |name| is the name of the CSS 50 // property. |value| is the CSS text to use. |segments| is an array 51 // describing the expected result. If an element of |segments| is a 52 // string, it is simply appended to the expected string. Otherwise, 53 // it must be an object with a |name| property, which is the color 54 // name as it appears in the input. 55 // 56 // This approach is taken to reduce boilerplate and to make it simpler 57 // to modify the test when the parseCssProperty output changes. 58 function makeColorTest(name, value, segments) { 59 const result = { 60 name, 61 value, 62 expected: "", 63 }; 64 65 for (const segment of segments) { 66 if (typeof segment === "string") { 67 result.expected += segment; 68 } else { 69 const buttonAttributes = { 70 class: COLOR_TEST_CLASS, 71 style: `background-color:${segment.name}`, 72 tabindex: 0, 73 role: "button", 74 }; 75 if (segment.colorFunction) { 76 buttonAttributes["data-color-function"] = segment.colorFunction; 77 } 78 const buttonAttrString = Object.entries(buttonAttributes) 79 .map(([attr, v]) => `${attr}="${v}"`) 80 .join(" "); 81 82 // prettier-ignore 83 result.expected += 84 `<span data-color="${segment.name}" class="color-swatch-container">` + 85 `<span ${buttonAttrString}></span>`+ 86 `<span>${segment.name}</span>` + 87 `</span>`; 88 } 89 } 90 91 result.desc = "Testing " + name + ": " + value; 92 93 return result; 94 } 95 96 function testParseCssProperty(doc, parser) { 97 const tests = [ 98 makeColorTest("border", "1px solid red", ["1px solid ", { name: "red" }]), 99 100 makeColorTest( 101 "background-image", 102 "linear-gradient(to right, #F60 10%, rgba(0,0,0,1))", 103 [ 104 "linear-gradient(to right, ", 105 { name: "#F60", colorFunction: "linear-gradient" }, 106 " 10%, ", 107 { name: "rgba(0,0,0,1)", colorFunction: "linear-gradient" }, 108 ")", 109 ] 110 ), 111 112 // In "arial black", "black" is a font, not a color. 113 // (The font-family parser creates a span) 114 makeColorTest("font-family", "arial black", ["<span>arial black</span>"]), 115 116 makeColorTest("box-shadow", "0 0 1em red", ["0 0 1em ", { name: "red" }]), 117 118 makeColorTest("box-shadow", "0 0 1em red, 2px 2px 0 0 rgba(0,0,0,.5)", [ 119 "0 0 1em ", 120 { name: "red" }, 121 ", 2px 2px 0 0 ", 122 { name: "rgba(0,0,0,.5)" }, 123 ]), 124 125 makeColorTest("content", '"red"', ['"red"']), 126 127 // Invalid property names should not cause exceptions. 128 makeColorTest("hellothere", "'red'", ["'red'"]), 129 130 makeColorTest( 131 "filter", 132 "blur(1px) drop-shadow(0 0 0 blue) url(red.svg#blue)", 133 [ 134 '<span data-filters="blur(1px) drop-shadow(0 0 0 blue) ', 135 'url(red.svg#blue)"><span>', 136 "blur(1px) drop-shadow(0 0 0 ", 137 { name: "blue", colorFunction: "drop-shadow" }, 138 ") url(red.svg#blue)</span></span>", 139 ] 140 ), 141 142 makeColorTest("color", "currentColor", ["currentColor"]), 143 144 // Test a very long property. 145 makeColorTest( 146 "background-image", 147 "linear-gradient(to left, transparent 0, transparent 5%,#F00 0, #F00 10%,#FF0 0, #FF0 15%,#0F0 0, #0F0 20%,#0FF 0, #0FF 25%,#00F 0, #00F 30%,#800 0, #800 35%,#880 0, #880 40%,#080 0, #080 45%,#088 0, #088 50%,#008 0, #008 55%,#FFF 0, #FFF 60%,#EEE 0, #EEE 65%,#CCC 0, #CCC 70%,#999 0, #999 75%,#666 0, #666 80%,#333 0, #333 85%,#111 0, #111 90%,#000 0, #000 95%,transparent 0, transparent 100%)", 148 [ 149 "linear-gradient(to left, ", 150 { name: "transparent", colorFunction: "linear-gradient" }, 151 " 0, ", 152 { name: "transparent", colorFunction: "linear-gradient" }, 153 " 5%,", 154 { name: "#F00", colorFunction: "linear-gradient" }, 155 " 0, ", 156 { name: "#F00", colorFunction: "linear-gradient" }, 157 " 10%,", 158 { name: "#FF0", colorFunction: "linear-gradient" }, 159 " 0, ", 160 { name: "#FF0", colorFunction: "linear-gradient" }, 161 " 15%,", 162 { name: "#0F0", colorFunction: "linear-gradient" }, 163 " 0, ", 164 { name: "#0F0", colorFunction: "linear-gradient" }, 165 " 20%,", 166 { name: "#0FF", colorFunction: "linear-gradient" }, 167 " 0, ", 168 { name: "#0FF", colorFunction: "linear-gradient" }, 169 " 25%,", 170 { name: "#00F", colorFunction: "linear-gradient" }, 171 " 0, ", 172 { name: "#00F", colorFunction: "linear-gradient" }, 173 " 30%,", 174 { name: "#800", colorFunction: "linear-gradient" }, 175 " 0, ", 176 { name: "#800", colorFunction: "linear-gradient" }, 177 " 35%,", 178 { name: "#880", colorFunction: "linear-gradient" }, 179 " 0, ", 180 { name: "#880", colorFunction: "linear-gradient" }, 181 " 40%,", 182 { name: "#080", colorFunction: "linear-gradient" }, 183 " 0, ", 184 { name: "#080", colorFunction: "linear-gradient" }, 185 " 45%,", 186 { name: "#088", colorFunction: "linear-gradient" }, 187 " 0, ", 188 { name: "#088", colorFunction: "linear-gradient" }, 189 " 50%,", 190 { name: "#008", colorFunction: "linear-gradient" }, 191 " 0, ", 192 { name: "#008", colorFunction: "linear-gradient" }, 193 " 55%,", 194 { name: "#FFF", colorFunction: "linear-gradient" }, 195 " 0, ", 196 { name: "#FFF", colorFunction: "linear-gradient" }, 197 " 60%,", 198 { name: "#EEE", colorFunction: "linear-gradient" }, 199 " 0, ", 200 { name: "#EEE", colorFunction: "linear-gradient" }, 201 " 65%,", 202 { name: "#CCC", colorFunction: "linear-gradient" }, 203 " 0, ", 204 { name: "#CCC", colorFunction: "linear-gradient" }, 205 " 70%,", 206 { name: "#999", colorFunction: "linear-gradient" }, 207 " 0, ", 208 { name: "#999", colorFunction: "linear-gradient" }, 209 " 75%,", 210 { name: "#666", colorFunction: "linear-gradient" }, 211 " 0, ", 212 { name: "#666", colorFunction: "linear-gradient" }, 213 " 80%,", 214 { name: "#333", colorFunction: "linear-gradient" }, 215 " 0, ", 216 { name: "#333", colorFunction: "linear-gradient" }, 217 " 85%,", 218 { name: "#111", colorFunction: "linear-gradient" }, 219 " 0, ", 220 { name: "#111", colorFunction: "linear-gradient" }, 221 " 90%,", 222 { name: "#000", colorFunction: "linear-gradient" }, 223 " 0, ", 224 { name: "#000", colorFunction: "linear-gradient" }, 225 " 95%,", 226 { name: "transparent", colorFunction: "linear-gradient" }, 227 " 0, ", 228 { name: "transparent", colorFunction: "linear-gradient" }, 229 " 100%)", 230 ] 231 ), 232 233 // Note the lack of a space before the color here. 234 makeColorTest("border", "1px dotted#f06", [ 235 "1px dotted ", 236 { name: "#f06" }, 237 ]), 238 239 makeColorTest("color", "color-mix(in srgb, red, blue)", [ 240 "color-mix(in srgb, ", 241 { name: "red", colorFunction: "color-mix" }, 242 ", ", 243 { name: "blue", colorFunction: "color-mix" }, 244 ")", 245 ]), 246 247 makeColorTest( 248 "background-image", 249 "linear-gradient(to top, color-mix(in srgb, #008000, rgba(255, 255, 0, 0.9)), blue, contrast-color(#abc))", 250 [ 251 "linear-gradient(to top, ", 252 "color-mix(in srgb, ", 253 { name: "#008000", colorFunction: "color-mix" }, 254 ", ", 255 { name: "rgba(255, 255, 0, 0.9)", colorFunction: "color-mix" }, 256 "), ", 257 { name: "blue", colorFunction: "linear-gradient" }, 258 ", ", 259 "contrast-color(", 260 { name: "#abc", colorFunction: "contrast-color" }, 261 ")", 262 ")", 263 ] 264 ), 265 266 makeColorTest("color", "light-dark(red, blue)", [ 267 "light-dark(", 268 { name: "red", colorFunction: "light-dark" }, 269 ", ", 270 { name: "blue", colorFunction: "light-dark" }, 271 ")", 272 ]), 273 274 makeColorTest( 275 "background-image", 276 "linear-gradient(to top, light-dark(#008000, rgba(255, 255, 0, 0.9)), blue)", 277 [ 278 "linear-gradient(to top, ", 279 "light-dark(", 280 { name: "#008000", colorFunction: "light-dark" }, 281 ", ", 282 { name: "rgba(255, 255, 0, 0.9)", colorFunction: "light-dark" }, 283 "), ", 284 { name: "blue", colorFunction: "linear-gradient" }, 285 ")", 286 ] 287 ), 288 289 makeColorTest("color", "rgb(from gold r g b)", [ 290 { name: "rgb(from gold r g b)" }, 291 ]), 292 293 makeColorTest("color", "color(from hsl(0 100% 50%) xyz x y 0.5)", [ 294 { name: "color(from hsl(0 100% 50%) xyz x y 0.5)" }, 295 ]), 296 297 makeColorTest( 298 "color", 299 "oklab(from red calc(l - 1) calc(a * 2) calc(b + 3) / alpha)", 300 [{ name: "oklab(from red calc(l - 1) calc(a * 2) calc(b + 3) / alpha)" }] 301 ), 302 303 makeColorTest( 304 "color", 305 "rgb(from color-mix(in lch, plum 40%, pink) r g b)", 306 [{ name: "rgb(from color-mix(in lch, plum 40%, pink) r g b)" }] 307 ), 308 309 makeColorTest("color", "rgb(from rgb(from gold r g b) r g b)", [ 310 { name: "rgb(from rgb(from gold r g b) r g b)" }, 311 ]), 312 313 makeColorTest( 314 "background-image", 315 "linear-gradient(to right, #F60 10%, rgb(from gold r g b))", 316 [ 317 "linear-gradient(to right, ", 318 { name: "#F60", colorFunction: "linear-gradient" }, 319 " 10%, ", 320 { name: "rgb(from gold r g b)", colorFunction: "linear-gradient" }, 321 ")", 322 ] 323 ), 324 325 { 326 desc: "--a: (min-width:680px)", 327 name: "--a", 328 value: "(min-width:680px)", 329 expected: "(min-width:680px)", 330 }, 331 332 { 333 desc: "Interactive color swatch", 334 name: "color", 335 value: "gold", 336 expected: 337 // prettier-ignore 338 `<span data-color="gold" class="color-swatch-container">` + 339 `<span class="test-class" style="background-color:gold" tabindex="0" role="button"></span>` + 340 `<span>gold</span>` + 341 `</span>`, 342 parserExtraOptions: { 343 colorSwatchReadOnly: false, 344 }, 345 }, 346 347 { 348 desc: "Read-only color swatch", 349 name: "color", 350 value: "gold", 351 expected: 352 // prettier-ignore 353 `<span data-color="gold" class="color-swatch-container">` + 354 `<span class="test-class" style="background-color:gold"></span>` + 355 `<span>gold</span>` + 356 `</span>`, 357 parserExtraOptions: { 358 colorSwatchReadOnly: true, 359 }, 360 }, 361 362 makeColorTest("color", "contrast-color(red)", [ 363 "contrast-color(", 364 { name: "red", colorFunction: "contrast-color" }, 365 ")", 366 ]), 367 368 makeColorTest( 369 "color", 370 "color-mix(in srgb, red, contrast-color(hsl(0 100 200)))", 371 [ 372 "color-mix(in srgb, ", 373 { name: "red", colorFunction: "color-mix" }, 374 ", ", 375 "contrast-color(", 376 { name: "hsl(0 100 200)", colorFunction: "contrast-color" }, 377 ")", 378 ")", 379 ] 380 ), 381 ]; 382 383 const target = doc.querySelector("div"); 384 ok(target, "captain, we have the div"); 385 386 for (const test of tests) { 387 info(test.desc); 388 389 const frag = parser.parseCssProperty(test.name, test.value, { 390 colorSwatchClass: COLOR_TEST_CLASS, 391 ...(test.parserExtraOptions || {}), 392 }); 393 394 target.appendChild(frag); 395 396 is( 397 target.innerHTML, 398 test.expected, 399 "CSS property correctly parsed for " + test.name + ": " + test.value 400 ); 401 402 target.innerHTML = ""; 403 } 404 } 405 406 function testParseCssVar(doc, parser) { 407 const frag = parser.parseCssProperty("color", "var(--some-kind-of-green)", { 408 colorSwatchClass: "test-colorswatch", 409 }); 410 411 const target = doc.querySelector("div"); 412 ok(target, "captain, we have the div"); 413 target.appendChild(frag); 414 415 is( 416 target.innerHTML, 417 "var(--some-kind-of-green)", 418 "CSS property correctly parsed" 419 ); 420 421 target.innerHTML = ""; 422 } 423 424 function testParseURL(doc, parser) { 425 info("Test that URL parsing preserves quoting style"); 426 427 const tests = [ 428 { 429 desc: "simple test without quotes", 430 leader: "url(", 431 trailer: ")", 432 }, 433 { 434 desc: "simple test with single quotes", 435 leader: "url('", 436 trailer: "')", 437 }, 438 { 439 desc: "simple test with double quotes", 440 leader: 'url("', 441 trailer: '")', 442 }, 443 { 444 desc: "test with single quotes and whitespace", 445 leader: "url( \t'", 446 trailer: "'\r\n\f)", 447 }, 448 { 449 desc: "simple test with uppercase", 450 leader: "URL(", 451 trailer: ")", 452 }, 453 { 454 desc: "bad url, missing paren", 455 leader: "url(", 456 trailer: "", 457 expectedTrailer: ")", 458 }, 459 { 460 desc: "bad url, missing paren, with baseURI", 461 baseURI: "data:text/html,<style></style>", 462 leader: "url(", 463 trailer: "", 464 expectedTrailer: ")", 465 }, 466 { 467 desc: "bad url, double quote, missing paren", 468 leader: 'url("', 469 trailer: '"', 470 expectedTrailer: '")', 471 }, 472 { 473 desc: "bad url, single quote, missing paren and quote", 474 leader: "url('", 475 trailer: "", 476 expectedTrailer: "')", 477 }, 478 ]; 479 480 for (const test of tests) { 481 const url = test.leader + "something.jpg" + test.trailer; 482 const frag = parser.parseCssProperty("background", url, { 483 urlClass: "test-urlclass", 484 baseURI: test.baseURI, 485 }); 486 487 const target = doc.querySelector("div"); 488 target.appendChild(frag); 489 490 const expectedTrailer = test.expectedTrailer || test.trailer; 491 492 const expected = 493 test.leader + 494 '<a target="_blank" class="test-urlclass" ' + 495 'href="something.jpg">something.jpg</a>' + 496 expectedTrailer; 497 498 is(target.innerHTML, expected, test.desc); 499 500 target.innerHTML = ""; 501 } 502 } 503 504 function testParseFilter(doc, parser) { 505 const frag = parser.parseCssProperty("filter", "something invalid", { 506 filterSwatchClass: "test-filterswatch", 507 }); 508 509 const swatchCount = frag.querySelectorAll(".test-filterswatch").length; 510 is(swatchCount, 1, "filter swatch was created"); 511 } 512 513 function testParseBackdropFilter(doc, parser) { 514 const frag = parser.parseCssProperty("backdrop-filter", "something invalid", { 515 filterSwatchClass: "test-filterswatch", 516 }); 517 518 const swatchCount = frag.querySelectorAll(".test-filterswatch").length; 519 is(swatchCount, 1, "filter swatch was created for backdrop-filter"); 520 } 521 522 function testParseAngle(doc, parser) { 523 let frag = parser.parseCssProperty("rotate", "90deg", { 524 angleSwatchClass: "test-angleswatch", 525 }); 526 527 let swatchCount = frag.querySelectorAll(".test-angleswatch").length; 528 is(swatchCount, 1, "angle swatch was created"); 529 530 frag = parser.parseCssProperty( 531 "background-image", 532 "linear-gradient(90deg, red, blue", 533 { 534 angleSwatchClass: "test-angleswatch", 535 } 536 ); 537 538 swatchCount = frag.querySelectorAll(".test-angleswatch").length; 539 is(swatchCount, 1, "angle swatch was created"); 540 } 541 542 function testParseShape(doc, parser) { 543 info("Test shape parsing"); 544 545 const tests = [ 546 { 547 desc: "Polygon shape", 548 definition: 549 "polygon(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n " + 550 "12em var(--variable), 100% 100%) margin-box", 551 spanCount: 18, 552 }, 553 { 554 desc: "POLYGON()", 555 definition: 556 "POLYGON(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n " + 557 "12em var(--variable), 100% 100%) margin-box", 558 spanCount: 18, 559 }, 560 { 561 desc: "Invalid polygon shape", 562 definition: "polygon(0px 0px 100px 20px, 20% 20%)", 563 spanCount: 0, 564 }, 565 { 566 desc: "Circle shape with all arguments", 567 definition: "circle(25% at\n 30% 200px) border-box", 568 spanCount: 4, 569 }, 570 { 571 desc: "Circle shape with only one center", 572 definition: "circle(25em at 40%)", 573 spanCount: 3, 574 }, 575 { 576 desc: "Circle shape with no radius", 577 definition: "circle(at 30% 40%)", 578 spanCount: 3, 579 }, 580 { 581 desc: "Circle shape with no center", 582 definition: "circle(12em)", 583 spanCount: 1, 584 }, 585 { 586 desc: "Circle shape with no arguments", 587 definition: "circle()", 588 spanCount: 0, 589 }, 590 { 591 desc: "Circle shape with no space before at", 592 definition: "circle(25%at 30% 30%)", 593 spanCount: 4, 594 }, 595 { 596 desc: "CIRCLE", 597 definition: "CIRCLE(12em)", 598 spanCount: 1, 599 }, 600 { 601 desc: "Invalid circle shape", 602 definition: "circle(25%at30%30%)", 603 spanCount: 0, 604 }, 605 { 606 desc: "Ellipse shape with all arguments", 607 definition: "ellipse(200px 10em at 25% 120px) content-box", 608 spanCount: 5, 609 }, 610 { 611 desc: "Ellipse shape with only one center", 612 definition: "ellipse(200px 10% at 120px)", 613 spanCount: 4, 614 }, 615 { 616 desc: "Ellipse shape with no radius", 617 definition: "ellipse(at 25% 120px)", 618 spanCount: 3, 619 }, 620 { 621 desc: "Ellipse shape with no center", 622 definition: "ellipse(200px\n10em)", 623 spanCount: 2, 624 }, 625 { 626 desc: "Ellipse shape with no arguments", 627 definition: "ellipse()", 628 spanCount: 0, 629 }, 630 { 631 desc: "ELLIPSE()", 632 definition: "ELLIPSE(200px 10em)", 633 spanCount: 2, 634 }, 635 { 636 desc: "Invalid ellipse shape", 637 definition: "ellipse(200px100px at 30$ 20%)", 638 spanCount: 0, 639 }, 640 { 641 desc: "Inset shape with 4 arguments", 642 definition: "inset(200px 100px\n 30%15%)", 643 spanCount: 4, 644 }, 645 { 646 desc: "Inset shape with 3 arguments", 647 definition: "inset(200px 100px 15%)", 648 spanCount: 3, 649 }, 650 { 651 desc: "Inset shape with 2 arguments", 652 definition: "inset(200px 100px)", 653 spanCount: 2, 654 }, 655 { 656 desc: "Inset shape with 1 argument", 657 definition: "inset(200px)", 658 spanCount: 1, 659 }, 660 { 661 desc: "Inset shape with 0 arguments", 662 definition: "inset()", 663 spanCount: 0, 664 }, 665 { 666 desc: "INSET()", 667 definition: "INSET(200px)", 668 spanCount: 1, 669 }, 670 { 671 desc: "offset-path property with inset shape value", 672 property: "offset-path", 673 definition: "inset(200px)", 674 spanCount: 1, 675 }, 676 ]; 677 678 for (const { desc, definition, property = "clip-path", spanCount } of tests) { 679 info(desc); 680 const frag = parser.parseCssProperty(property, definition, { 681 shapeClass: "inspector-shape", 682 }); 683 const spans = frag.querySelectorAll(".inspector-shape-point"); 684 is(spans.length, spanCount, desc + " span count"); 685 is(frag.textContent, definition, desc + " text content"); 686 } 687 } 688 689 function getJumpToVariableButton(varName) { 690 return `<button class="ruleview-variable-link jump-definition" data-variable-name="${varName}" title="Jump to variable definition"></button>`; 691 } 692 693 function testParseVariable(doc, parser) { 694 const TESTS = [ 695 { 696 text: "var(--seen)", 697 variables: { "--seen": "chartreuse" }, 698 expected: 699 // prettier-ignore 700 '<span data-color="chartreuse">' + 701 "<span>var(" + 702 `<span data-variable="chartreuse">--seen${getJumpToVariableButton("--seen")}</span>)` + 703 "</span>" + 704 "</span>", 705 }, 706 { 707 text: "var(--seen)", 708 variables: { 709 "--seen": { value: "var(--base)", computedValue: "1em" }, 710 }, 711 expected: 712 // prettier-ignore 713 "<span>var(" + 714 `<span data-variable="var(--base)" data-variable-computed="1em">--seen${getJumpToVariableButton("--seen")}</span>)` + 715 "</span>", 716 }, 717 { 718 text: "var(--not-seen)", 719 variables: {}, 720 expected: 721 // prettier-ignore 722 "<span>var(" + 723 '<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>' + 724 ")</span>", 725 }, 726 { 727 text: "var(--seen, seagreen)", 728 variables: { "--seen": "chartreuse" }, 729 expected: 730 // prettier-ignore 731 '<span data-color="chartreuse">' + 732 "<span>var(" + 733 `<span data-variable="chartreuse">--seen${getJumpToVariableButton("--seen")}</span>,` + 734 '<span class="unmatched-class"> ' + 735 '<span data-color="seagreen">' + 736 "<span>seagreen</span>" + 737 "</span>" + 738 "</span>)" + 739 "</span>" + 740 "</span>", 741 }, 742 { 743 text: "var(--not-seen, var(--seen))", 744 variables: { "--seen": "chartreuse" }, 745 expected: 746 // prettier-ignore 747 "<span>var(" + 748 '<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>,' + 749 "<span> " + 750 '<span data-color="chartreuse">' + 751 "<span>var(" + 752 `<span data-variable="chartreuse">--seen${getJumpToVariableButton("--seen")}</span>)` + 753 "</span>" + 754 "</span>" + 755 "</span>)" + 756 "</span>", 757 }, 758 { 759 text: "color-mix(in sgrb, var(--x), purple)", 760 variables: { "--x": "yellow" }, 761 expected: 762 // prettier-ignore 763 `color-mix(in sgrb, ` + 764 `<span data-color="yellow" class="color-swatch-container">` + 765 `<span class="test-class" style="background-color:yellow" tabindex="0" role="button" data-color-function="color-mix">` + 766 `</span>` + 767 `<span>var(<span data-variable="yellow">--x${getJumpToVariableButton("--x")}</span>)</span>` + 768 `</span>` + 769 `, ` + 770 `<span data-color="purple" class="color-swatch-container">` + 771 `<span class="test-class" style="background-color:purple" tabindex="0" role="button" data-color-function="color-mix">` + 772 `</span>` + 773 `<span>purple</span>` + 774 `</span>` + 775 `)`, 776 parserExtraOptions: { 777 colorSwatchClass: COLOR_TEST_CLASS, 778 }, 779 }, 780 { 781 text: "light-dark(var(--light), var(--dark))", 782 variables: { "--light": "yellow", "--dark": "gold" }, 783 expected: 784 // prettier-ignore 785 `light-dark(` + 786 `<span data-color="yellow" class="color-swatch-container">` + 787 `<span class="test-class" style="background-color:yellow" tabindex="0" role="button" data-color-function="light-dark">` + 788 `</span>` + 789 `<span>var(<span data-variable="yellow">--light${getJumpToVariableButton("--light")}</span>)</span>` + 790 `</span>` + 791 `, ` + 792 `<span data-color="gold" class="color-swatch-container">` + 793 `<span class="test-class" style="background-color:gold" tabindex="0" role="button" data-color-function="light-dark">` + 794 `</span>` + 795 `<span>var(<span data-variable="gold">--dark${getJumpToVariableButton("--dark")}</span>)</span>` + 796 `</span>` + 797 `)`, 798 parserExtraOptions: { 799 colorSwatchClass: COLOR_TEST_CLASS, 800 }, 801 }, 802 { 803 text: "1px solid var(--seen, seagreen)", 804 // See Bug 1911974 805 skipVariableDeclarationTest: true, 806 variables: { "--seen": "chartreuse" }, 807 expected: 808 // prettier-ignore 809 '1px solid ' + 810 '<span data-color="chartreuse">' + 811 "<span>var(" + 812 `<span data-variable="chartreuse">--seen${getJumpToVariableButton("--seen")}</span>,` + 813 '<span class="unmatched-class"> ' + 814 '<span data-color="seagreen">' + 815 "<span>seagreen</span>" + 816 "</span>" + 817 "</span>)" + 818 "</span>" + 819 "</span>", 820 }, 821 { 822 text: "1px solid var(--not-seen, seagreen)", 823 // See Bug 1911975 824 skipVariableDeclarationTest: true, 825 variables: {}, 826 expected: 827 // prettier-ignore 828 `1px solid ` + 829 `<span>var(` + 830 `<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>,` + 831 `<span> ` + 832 `<span data-color="seagreen">` + 833 `<span>seagreen</span>` + 834 `</span>` + 835 `</span>)` + 836 `</span>`, 837 }, 838 { 839 text: "rgba(var(--r), 0, 0, var(--a))", 840 variables: { "--r": "255", "--a": "0.5" }, 841 expected: 842 // prettier-ignore 843 '<span data-color="rgba(255, 0, 0, 0.5)">' + 844 "<span>rgba("+ 845 "<span>" + 846 `var(<span data-variable="255">--r${getJumpToVariableButton("--r")}</span>)` + 847 "</span>, 0, 0, " + 848 "<span>" + 849 `var(<span data-variable="0.5">--a${getJumpToVariableButton("--a")}</span>)` + 850 "</span>" + 851 ")</span>" + 852 "</span>", 853 }, 854 { 855 text: "rgba(from var(--base) r g 0 / calc(var(--a) * 0.5))", 856 variables: { "--base": "red", "--a": "0.8" }, 857 expected: 858 // prettier-ignore 859 '<span data-color="rgba(from red r g 0 / calc(0.8 * 0.5))">' + 860 "<span>rgba("+ 861 "from " + 862 "<span>" + 863 `var(<span data-variable="red">--base${getJumpToVariableButton("--base")}</span>)` + 864 "</span> r g 0 / " + 865 "calc(" + 866 "<span>" + 867 `var(<span data-variable="0.8">--a${getJumpToVariableButton("--a")}</span>)` + 868 "</span>" + 869 " * 0.5)" + 870 ")</span>" + 871 "</span>", 872 }, 873 { 874 text: "rgb(var(--not-seen, 255), 0, 0)", 875 variables: {}, 876 expected: 877 // prettier-ignore 878 '<span data-color="rgb( 255, 0, 0)">' + 879 "<span>rgb("+ 880 "<span>var(" + 881 `<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>,` + 882 `<span> 255</span>` + 883 ")</span>, 0, 0" + 884 ")</span>" + 885 "</span>", 886 }, 887 { 888 text: "rgb(var(--not-seen), 0, 0)", 889 variables: {}, 890 expected: 891 // prettier-ignore 892 `rgb(` + 893 `<span>` + 894 `var(` + 895 `<span class="unmatched-class" data-variable="--not-seen is not set">` + 896 `--not-seen` + 897 `</span>` + 898 `)` + 899 `</span>` + 900 `, 0, 0` + 901 `)`, 902 }, 903 { 904 text: "var(--registered)", 905 variables: { 906 "--registered": { 907 value: "chartreuse", 908 registeredProperty: { 909 syntax: "<color>", 910 inherits: true, 911 initialValue: "hotpink", 912 }, 913 }, 914 }, 915 expected: 916 // prettier-ignore 917 '<span data-color="chartreuse">' + 918 "<span>var(" + 919 '<span ' + 920 'data-variable="chartreuse" ' + 921 'data-registered-property-initial-value="hotpink" ' + 922 'data-registered-property-syntax="<color>" ' + 923 'data-registered-property-inherits="true"' + 924 `>--registered${getJumpToVariableButton("--registered")}</span>)` + 925 "</span>" + 926 "</span>", 927 }, 928 { 929 text: "var(--registered-universal)", 930 variables: { 931 "--registered-universal": { 932 value: "chartreuse", 933 registeredProperty: { 934 syntax: "*", 935 inherits: false, 936 }, 937 }, 938 }, 939 expected: 940 // prettier-ignore 941 '<span data-color="chartreuse">' + 942 "<span>var(" + 943 '<span ' + 944 'data-variable="chartreuse" ' + 945 'data-registered-property-syntax="*" ' + 946 'data-registered-property-inherits="false"' + 947 `>--registered-universal${getJumpToVariableButton("--registered-universal")}</span>)` + 948 "</span>" + 949 "</span>", 950 }, 951 { 952 text: "var(--x)", 953 variables: { 954 "--x": "light-dark(red, blue)", 955 }, 956 parserExtraOptions: { 957 isDarkColorScheme: false, 958 }, 959 expected: `<span>var(<span data-variable="light-dark(red, blue)">--x${getJumpToVariableButton("--x")}</span>)</span>`, 960 }, 961 { 962 text: "var(--x)", 963 variables: { 964 "--x": "color-mix(in srgb, red 50%, blue)", 965 }, 966 parserExtraOptions: { 967 isDarkColorScheme: false, 968 }, 969 expected: 970 // prettier-ignore 971 '<span data-color="color-mix(in srgb, red 50%, blue)">' + 972 '<span>var(' + 973 `<span data-variable="color-mix(in srgb, red 50%, blue)">--x${getJumpToVariableButton("--x")}</span>` + 974 ')</span>' + 975 '</span>', 976 }, 977 { 978 text: "var(--x)", 979 variables: { 980 "--x": "contrast-color(blue)", 981 }, 982 parserExtraOptions: { 983 isDarkColorScheme: false, 984 }, 985 expected: 986 // prettier-ignore 987 '<span data-color="contrast-color(blue)">' + 988 '<span>var(' + 989 `<span data-variable="contrast-color(blue)">--x${getJumpToVariableButton("--x")}</span>` + 990 ')</span>' + 991 '</span>', 992 }, 993 { 994 text: "var(--refers-empty)", 995 variables: { 996 "--refers-empty": { value: "var(--empty)", computedValue: "" }, 997 }, 998 expected: 999 // prettier-ignore 1000 "<span>var(" + 1001 `<span data-variable="var(--empty)" data-variable-computed="">--refers-empty${getJumpToVariableButton("--refers-empty")}</span>)` + 1002 "</span>", 1003 }, 1004 { 1005 text: "hsl(50, 70%, var(--foo))", 1006 variables: { 1007 "--foo": "40%", 1008 }, 1009 expected: 1010 // prettier-ignore 1011 `<span data-color="hsl(50, 70%, 40%)">` + 1012 `<span>`+ 1013 `hsl(50, 70%, ` + 1014 `<span>` + 1015 `var(` + 1016 `<span data-variable="40%">--foo${getJumpToVariableButton("--foo")}</span>` + 1017 `)` + 1018 `</span>)` + 1019 `</span>` + 1020 `</span>`, 1021 }, 1022 { 1023 text: "var(--bar)", 1024 variables: { 1025 "--foo": "40%", 1026 "--bar": "hsl(50, 70%, var(--foo))", 1027 }, 1028 expected: 1029 // prettier-ignore 1030 `<span data-color="hsl(50, 70%, 40%)">` + 1031 `<span>` + 1032 `var(` + 1033 `<span data-variable="hsl(50, 70%, var(--foo))" data-variable-computed="hsl(50, 70%, 40%)">--bar${getJumpToVariableButton("--bar")}</span>` + 1034 `)` + 1035 `</span>` + 1036 `</span>`, 1037 }, 1038 { 1039 text: "var(--primary)", 1040 variables: { 1041 "--primary": "hsl(10, 100%, var(--fur))", 1042 "--fur": "var(--bar)", 1043 "--bar": "var(--foo)", 1044 "--foo": "50%", 1045 }, 1046 expected: 1047 // prettier-ignore 1048 `<span data-color="hsl(10, 100%, 50%)">` + 1049 `<span>` + 1050 `var(` + 1051 `<span data-variable="hsl(10, 100%, var(--fur))" data-variable-computed="hsl(10, 100%, 50%)">--primary${getJumpToVariableButton("--primary")}</span>` + 1052 `)` + 1053 `</span>` + 1054 `</span>`, 1055 }, 1056 { 1057 text: "oklch(var(--fur) 20 var(--boo))", 1058 variables: { 1059 "--fur": "var(--baz)", 1060 "--baz": "var(--foo)", 1061 "--foo": "10", 1062 "--boo": "30", 1063 }, 1064 expected: 1065 // prettier-ignore 1066 `<span data-color="oklch(10 20 30)">` + 1067 `<span>oklch(` + 1068 `<span>` + 1069 `var(` + 1070 `<span data-variable="var(--baz)" data-variable-computed="10">--fur${getJumpToVariableButton("--fur")}</span>` + 1071 `)` + 1072 `</span>` + 1073 ` 20 ` + 1074 `<span>` + 1075 `var(` + 1076 `<span data-variable="30">--boo${getJumpToVariableButton("--boo")}</span>` + 1077 `)` + 1078 `</span>` + 1079 `)</span>` + 1080 `</span>`, 1081 }, 1082 { 1083 text: "var(--x)", 1084 variables: { 1085 "--x": "10px", 1086 }, 1087 parserExtraOptions: { 1088 showJumpToVariableButton: false, 1089 }, 1090 // This shouldn't have a Jump to variable button 1091 expected: `<span>var(<span data-variable="10px">--x</span>)</span>`, 1092 }, 1093 ]; 1094 1095 const target = doc.querySelector("div"); 1096 1097 const VAR_NAME_TO_DEFINE = "--test-parse-variable"; 1098 for (const test of TESTS) { 1099 // VAR_NAME_TO_DEFINE is used to test parsing the test.text if it's set for a 1100 // variable declaration, so it shouldn't be set in test.variables to avoid 1101 // messing with the test results. 1102 if (VAR_NAME_TO_DEFINE in test.variables) { 1103 throw new Error(`${VAR_NAME_TO_DEFINE} shouldn't be set in variables`); 1104 } 1105 1106 // Also set the variable we're going to define, so its value can be computed as well 1107 const variables = { 1108 ...(test.variables || {}), 1109 [VAR_NAME_TO_DEFINE]: test.text, 1110 }; 1111 // Set the variables to an element so we can get their computed values 1112 for (const [varName, varData] of Object.entries(variables)) { 1113 doc.body.style.setProperty( 1114 varName, 1115 typeof varData === "string" ? varData : varData.value 1116 ); 1117 } 1118 1119 const getVariableData = function (varName) { 1120 if (typeof variables[varName] === "string") { 1121 const value = variables[varName]; 1122 const computedValue = getComputedStyle(doc.body).getPropertyValue( 1123 varName 1124 ); 1125 return { value, computedValue }; 1126 } 1127 1128 return variables[varName] || {}; 1129 }; 1130 1131 const frag = parser.parseCssProperty("color", test.text, { 1132 getVariableData, 1133 unmatchedClass: "unmatched-class", 1134 ...(test.parserExtraOptions || {}), 1135 }); 1136 1137 target.appendChild(frag); 1138 1139 is( 1140 target.innerHTML, 1141 test.expected, 1142 `"color: ${test.text}" is parsed as expected` 1143 ); 1144 1145 target.innerHTML = ""; 1146 1147 if (test.skipVariableDeclarationTest) { 1148 continue; 1149 } 1150 1151 const varFrag = parser.parseCssProperty( 1152 "--test-parse-variable", 1153 test.text, 1154 { 1155 getVariableData, 1156 unmatchedClass: "unmatched-class", 1157 ...(test.parserExtraOptions || {}), 1158 } 1159 ); 1160 1161 target.appendChild(varFrag); 1162 1163 is( 1164 target.innerHTML, 1165 test.expected, 1166 `"--test-parse-variable: ${test.text}" is parsed as expected` 1167 ); 1168 1169 target.innerHTML = ""; 1170 1171 // Remove the variables to an element so we can get their computed values 1172 for (const varName in variables || {}) { 1173 doc.body.style.removeProperty(varName); 1174 } 1175 } 1176 } 1177 1178 function testParseColorVariable(doc, parser) { 1179 const testCategories = [ 1180 { 1181 desc: "Test for CSS variable defining color", 1182 tests: [ 1183 makeColorTest("--test-var", "lime", [{ name: "lime" }]), 1184 makeColorTest("--test-var", "#000", [{ name: "#000" }]), 1185 ], 1186 }, 1187 { 1188 desc: "Test for CSS variable not defining color", 1189 tests: [ 1190 makeColorTest("--foo", "something", ["something"]), 1191 makeColorTest("--bar", "Arial Black", ["Arial Black"]), 1192 makeColorTest("--baz", "10vmin", ["10vmin"]), 1193 ], 1194 }, 1195 { 1196 desc: "Test for non CSS variable defining color", 1197 tests: [ 1198 makeColorTest("non-css-variable", "lime", ["lime"]), 1199 makeColorTest("-non-css-variable", "#000", ["#000"]), 1200 ], 1201 }, 1202 ]; 1203 1204 for (const category of testCategories) { 1205 info(category.desc); 1206 1207 for (const test of category.tests) { 1208 info(test.desc); 1209 const target = doc.querySelector("div"); 1210 1211 const frag = parser.parseCssProperty(test.name, test.value, { 1212 colorSwatchClass: COLOR_TEST_CLASS, 1213 }); 1214 1215 target.appendChild(frag); 1216 1217 is( 1218 target.innerHTML, 1219 test.expected, 1220 `The parsed result for '${test.name}: ${test.value}' is correct` 1221 ); 1222 1223 target.innerHTML = ""; 1224 } 1225 } 1226 } 1227 1228 function testParseFontFamily(doc, parser) { 1229 info("Test font-family parsing"); 1230 const tests = [ 1231 { 1232 desc: "No fonts", 1233 definition: "", 1234 families: [], 1235 }, 1236 { 1237 desc: "List of fonts", 1238 definition: "Arial,Helvetica,sans-serif", 1239 families: ["Arial", "Helvetica", "sans-serif"], 1240 }, 1241 { 1242 desc: "Fonts with spaces", 1243 definition: "Open Sans", 1244 families: ["Open Sans"], 1245 }, 1246 { 1247 desc: "Quoted fonts", 1248 definition: "\"Arial\",'Open Sans'", 1249 families: ["Arial", "Open Sans"], 1250 }, 1251 { 1252 desc: "Fonts with extra whitespace", 1253 definition: " Open Sans ", 1254 families: ["Open Sans"], 1255 }, 1256 ]; 1257 1258 const textContentTests = [ 1259 { 1260 desc: "No whitespace between fonts", 1261 definition: "Arial,Helvetica,sans-serif", 1262 output: "Arial,Helvetica,sans-serif", 1263 }, 1264 { 1265 desc: "Whitespace between fonts", 1266 definition: "Arial , Helvetica, sans-serif", 1267 output: "Arial , Helvetica, sans-serif", 1268 }, 1269 { 1270 desc: "Whitespace before first font trimmed", 1271 definition: " Arial,Helvetica,sans-serif", 1272 output: "Arial,Helvetica,sans-serif", 1273 }, 1274 { 1275 desc: "Whitespace after last font trimmed", 1276 definition: "Arial,Helvetica,sans-serif ", 1277 output: "Arial,Helvetica,sans-serif", 1278 }, 1279 { 1280 desc: "Whitespace between quoted fonts", 1281 definition: "'Arial' , \"Helvetica\" ", 1282 output: "'Arial' , \"Helvetica\"", 1283 }, 1284 { 1285 desc: "Whitespace within font preserved", 1286 definition: "' Ari al '", 1287 output: "' Ari al '", 1288 }, 1289 ]; 1290 1291 for (const { desc, definition, families } of tests) { 1292 info(desc); 1293 const frag = parser.parseCssProperty("font-family", definition, { 1294 fontFamilyClass: "ruleview-font-family", 1295 }); 1296 const spans = frag.querySelectorAll(".ruleview-font-family"); 1297 1298 is(spans.length, families.length, desc + " span count"); 1299 for (let i = 0; i < spans.length; i++) { 1300 is(spans[i].textContent, families[i], desc + " span contents"); 1301 } 1302 } 1303 1304 info("Test font-family text content"); 1305 for (const { desc, definition, output } of textContentTests) { 1306 info(desc); 1307 const frag = parser.parseCssProperty("font-family", definition, {}); 1308 is(frag.textContent, output, desc + " text content matches"); 1309 } 1310 1311 info("Test font-family with custom properties"); 1312 const frag = parser.parseCssProperty( 1313 "font-family", 1314 "var(--family, Georgia, serif)", 1315 { 1316 getVariableData: () => ({}), 1317 unmatchedClass: "unmatched-class", 1318 fontFamilyClass: "ruleview-font-family", 1319 } 1320 ); 1321 const target = doc.createElement("div"); 1322 target.appendChild(frag); 1323 is( 1324 target.innerHTML, 1325 // prettier-ignore 1326 `<span>var(` + 1327 `<span class="unmatched-class" data-variable="--family is not set">` + 1328 `--family` + 1329 `</span>` + 1330 `,` + 1331 `<span> ` + 1332 `<span class="ruleview-font-family">Georgia</span>` + 1333 `, ` + 1334 `<span class="ruleview-font-family">serif</span>` + 1335 `</span>)` + 1336 `</span>`, 1337 "Got expected output for font-family with custom properties" 1338 ); 1339 } 1340 1341 function testParseLightDark(doc, parser) { 1342 const TESTS = [ 1343 { 1344 message: 1345 "Not passing isDarkColorScheme doesn't add unmatched classes to parameters", 1346 propertyName: "color", 1347 propertyValue: "light-dark(red, blue)", 1348 expected: 1349 // prettier-ignore 1350 `light-dark(` + 1351 `<span data-color="red" class="color-swatch-container">` + 1352 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1353 `<span>red</span>` + 1354 `</span>, ` + 1355 `<span data-color="blue" class="color-swatch-container">` + 1356 `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1357 `<span>blue</span>` + 1358 `</span>` + 1359 `)`, 1360 }, 1361 { 1362 message: "in light mode, the second parameter gets the unmatched class", 1363 propertyName: "color", 1364 propertyValue: "light-dark(red, blue)", 1365 isDarkColorScheme: false, 1366 expected: 1367 // prettier-ignore 1368 `light-dark(` + 1369 `<span data-color="red" class="color-swatch-container">` + 1370 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1371 `<span>red</span>` + 1372 `</span>, ` + 1373 `<span data-color="blue" class="color-swatch-container unmatched-class">` + 1374 `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1375 `<span>blue</span>` + 1376 `</span>` + 1377 `)`, 1378 }, 1379 { 1380 message: "in dark mode, the first parameter gets the unmatched class", 1381 propertyName: "color", 1382 propertyValue: "light-dark(red, blue)", 1383 isDarkColorScheme: true, 1384 expected: 1385 // prettier-ignore 1386 `light-dark(` + 1387 `<span data-color="red" class="color-swatch-container unmatched-class">` + 1388 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1389 `<span>red</span>` + 1390 `</span>, ` + 1391 `<span data-color="blue" class="color-swatch-container">` + 1392 `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1393 `<span>blue</span>` + 1394 `</span>` + 1395 `)`, 1396 }, 1397 { 1398 message: "light-dark gets parsed as expected in shorthands in light mode", 1399 propertyName: "border", 1400 propertyValue: "1px solid light-dark(red, blue)", 1401 isDarkColorScheme: false, 1402 expected: 1403 // prettier-ignore 1404 `1px solid light-dark(` + 1405 `<span data-color="red" class="color-swatch-container">` + 1406 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1407 `<span>red</span>` + 1408 `</span>, ` + 1409 `<span data-color="blue" class="color-swatch-container unmatched-class">` + 1410 `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1411 `<span>blue</span>` + 1412 `</span>` + 1413 `)`, 1414 }, 1415 { 1416 message: "light-dark gets parsed as expected in shorthands in dark mode", 1417 propertyName: "border", 1418 propertyValue: "1px solid light-dark(red, blue)", 1419 isDarkColorScheme: true, 1420 expected: 1421 // prettier-ignore 1422 `1px solid light-dark(` + 1423 `<span data-color="red" class="color-swatch-container unmatched-class">` + 1424 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1425 `<span>red</span>` + 1426 `</span>, ` + 1427 `<span data-color="blue" class="color-swatch-container">` + 1428 `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1429 `<span>blue</span>` + 1430 `</span>` + 1431 `)`, 1432 }, 1433 { 1434 message: "Nested light-dark gets parsed as expected in light mode", 1435 propertyName: "background", 1436 propertyValue: 1437 "linear-gradient(45deg, light-dark(red, blue), light-dark(pink, cyan))", 1438 isDarkColorScheme: false, 1439 expected: 1440 // prettier-ignore 1441 `linear-gradient(` + 1442 `<span data-angle="45deg"><span>45deg</span></span>, ` + 1443 `light-dark(` + 1444 `<span data-color="red" class="color-swatch-container">` + 1445 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>`+ 1446 `<span>red</span>`+ 1447 `</span>, `+ 1448 `<span data-color="blue" class="color-swatch-container unmatched-class">` + 1449 `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1450 `<span>blue</span>` + 1451 `</span>` + 1452 `), ` + 1453 `light-dark(` + 1454 `<span data-color="pink" class="color-swatch-container">` + 1455 `<span class="test-class" style="background-color:pink" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1456 `<span>pink</span>` + 1457 `</span>, ` + 1458 `<span data-color="cyan" class="color-swatch-container unmatched-class">` + 1459 `<span class="test-class" style="background-color:cyan" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1460 `<span>cyan</span>` + 1461 `</span>` + 1462 `)` + 1463 `)`, 1464 }, 1465 { 1466 message: "Nested light-dark gets parsed as expected in dark mode", 1467 propertyName: "background", 1468 propertyValue: 1469 "linear-gradient(33deg, light-dark(red, blue), light-dark(pink, cyan))", 1470 isDarkColorScheme: true, 1471 expected: 1472 // prettier-ignore 1473 `linear-gradient(` + 1474 `<span data-angle="33deg"><span>33deg</span></span>, ` + 1475 `light-dark(` + 1476 `<span data-color="red" class="color-swatch-container unmatched-class">` + 1477 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>`+ 1478 `<span>red</span>`+ 1479 `</span>, `+ 1480 `<span data-color="blue" class="color-swatch-container">` + 1481 `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1482 `<span>blue</span>` + 1483 `</span>` + 1484 `), ` + 1485 `light-dark(` + 1486 `<span data-color="pink" class="color-swatch-container unmatched-class">` + 1487 `<span class="test-class" style="background-color:pink" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1488 `<span>pink</span>` + 1489 `</span>, ` + 1490 `<span data-color="cyan" class="color-swatch-container">` + 1491 `<span class="test-class" style="background-color:cyan" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1492 `<span>cyan</span>` + 1493 `</span>` + 1494 `)` + 1495 `)`, 1496 }, 1497 { 1498 message: 1499 "in light mode, the second parameter gets the unmatched class when it's a variable", 1500 propertyName: "color", 1501 propertyValue: "light-dark(var(--x), var(--y))", 1502 isDarkColorScheme: false, 1503 variables: { "--x": "red", "--y": "blue" }, 1504 expected: 1505 // prettier-ignore 1506 `light-dark(` + 1507 `<span data-color="red" class="color-swatch-container">` + 1508 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1509 `<span>var(` + 1510 `<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>` + 1511 `)</span>` + 1512 `</span>, ` + 1513 `<span data-color="blue" class="color-swatch-container unmatched-class">` + 1514 `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1515 `<span>var(` + 1516 `<span data-variable="blue">--y${getJumpToVariableButton("--y")}</span>` + 1517 `)</span>` + 1518 `</span>` + 1519 `)`, 1520 }, 1521 { 1522 message: 1523 "in light mode, the second parameter gets the unmatched class when some param are not parsed", 1524 propertyName: "color", 1525 // Using `notacolor` so we don't get a wrapping Node for it (contrary to colors). 1526 // The value is still valid at parse time since we're using a variable, 1527 // so the OutputParser will actually parse the different parts 1528 propertyValue: "light-dark(var(--x),notacolor)", 1529 isDarkColorScheme: false, 1530 variables: { "--x": "red" }, 1531 expected: 1532 // prettier-ignore 1533 `light-dark(` + 1534 `<span data-color="red" class="color-swatch-container">` + 1535 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1536 `<span>` + 1537 `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` + 1538 `</span>` + 1539 `</span>,` + 1540 `<span class="unmatched-class">notacolor</span>` + 1541 `)`, 1542 }, 1543 { 1544 message: 1545 "in dark mode, the first parameter gets the unmatched class when some param are not parsed", 1546 propertyName: "color", 1547 // Using `notacolor` so we don't get a wrapping Node for it (contrary to colors). 1548 // The value is still valid at parse time since we're using a variable, 1549 // so the OutputParser will actually parse the different parts 1550 propertyValue: "light-dark(notacolor,var(--x))", 1551 isDarkColorScheme: true, 1552 variables: { "--x": "red" }, 1553 expected: 1554 // prettier-ignore 1555 `light-dark(` + 1556 `<span class="unmatched-class">notacolor</span>,` + 1557 `<span data-color="red" class="color-swatch-container">` + 1558 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1559 `<span>` + 1560 `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` + 1561 `</span>` + 1562 `</span>` + 1563 `)`, 1564 }, 1565 { 1566 message: 1567 "in light mode, the second parameter gets the unmatched class, comments are stripped out and whitespace are preserved", 1568 propertyName: "color", 1569 propertyValue: 1570 "light-dark( /* 1st param */ var(--x) /* delim */ , /* 2nd param */ notacolor /* delim */ )", 1571 isDarkColorScheme: false, 1572 variables: { "--x": "red" }, 1573 expected: 1574 // prettier-ignore 1575 `light-dark( ` + 1576 `<span data-color="red" class="color-swatch-container">` + 1577 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1578 `<span>` + 1579 `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` + 1580 `</span>` + 1581 `</span> , ` + 1582 `<span class="unmatched-class">notacolor</span> ` + 1583 `)`, 1584 }, 1585 { 1586 message: 1587 "in dark mode, the first parameter gets the unmatched class, comments are stripped out and whitespace are preserved", 1588 propertyName: "color", 1589 propertyValue: 1590 "light-dark( /* 1st param */ notacolor /* delim */ , /* 2nd param */ var(--x) /* delim */ )", 1591 isDarkColorScheme: true, 1592 variables: { "--x": "red" }, 1593 expected: 1594 // prettier-ignore 1595 `light-dark( ` + 1596 `<span class="unmatched-class">notacolor</span> , ` + 1597 `<span data-color="red" class="color-swatch-container">` + 1598 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1599 `<span>` + 1600 `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` + 1601 `</span>` + 1602 `</span> ` + 1603 `)`, 1604 }, 1605 { 1606 message: 1607 "in light mode with a single parameter, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)", 1608 propertyName: "color", 1609 propertyValue: "light-dark(var(--x))", 1610 isDarkColorScheme: false, 1611 variables: { "--x": "red" }, 1612 expected: 1613 // prettier-ignore 1614 `light-dark(` + 1615 `<span data-color="red" class="color-swatch-container">` + 1616 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1617 `<span>` + 1618 `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` + 1619 `</span>` + 1620 `</span>` + 1621 `)`, 1622 }, 1623 { 1624 message: 1625 "in dark mode with a single parameter, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)", 1626 propertyName: "color", 1627 propertyValue: "light-dark(var(--x))", 1628 isDarkColorScheme: true, 1629 variables: { "--x": "red" }, 1630 expected: 1631 // prettier-ignore 1632 `light-dark(` + 1633 `<span data-color="red" class="color-swatch-container">` + 1634 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1635 `<span>` + 1636 `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` + 1637 `</span>` + 1638 `</span>` + 1639 `)`, 1640 }, 1641 { 1642 message: 1643 "in light mode with 3 parameters, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)", 1644 propertyName: "color", 1645 propertyValue: "light-dark(var(--x),a,b)", 1646 isDarkColorScheme: false, 1647 variables: { "--x": "red" }, 1648 expected: 1649 // prettier-ignore 1650 `light-dark(` + 1651 `<span data-color="red" class="color-swatch-container">` + 1652 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1653 `<span>` + 1654 `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` + 1655 `</span>` + 1656 `</span>,a,b` + 1657 `)`, 1658 }, 1659 { 1660 message: 1661 "in dark mode with 3 parameters, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)", 1662 propertyName: "color", 1663 propertyValue: "light-dark(var(--x),a,b)", 1664 isDarkColorScheme: true, 1665 variables: { "--x": "red" }, 1666 expected: 1667 // prettier-ignore 1668 `light-dark(` + 1669 `<span data-color="red" class="color-swatch-container">` + 1670 `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` + 1671 `<span>` + 1672 `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` + 1673 `</span>` + 1674 `</span>,a,b` + 1675 `)`, 1676 }, 1677 ]; 1678 1679 for (const test of TESTS) { 1680 const frag = parser.parseCssProperty( 1681 test.propertyName, 1682 test.propertyValue, 1683 { 1684 isDarkColorScheme: test.isDarkColorScheme, 1685 unmatchedClass: "unmatched-class", 1686 colorSwatchClass: COLOR_TEST_CLASS, 1687 getVariableData: varName => { 1688 if (typeof test.variables[varName] === "string") { 1689 return { value: test.variables[varName] }; 1690 } 1691 1692 return test.variables[varName] || {}; 1693 }, 1694 } 1695 ); 1696 1697 const target = doc.querySelector("div"); 1698 target.appendChild(frag); 1699 1700 is(target.innerHTML, test.expected, test.message); 1701 target.innerHTML = ""; 1702 } 1703 }