Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/devtools/client/shared/test/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 114 kB image not shown  

Quelle  browser_outputparser.js

  Sprache: JAVA
 

/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */


"use strict";

add_task(async function () {
  await SpecialPowers.pushPrefEnv({
    set: [
      ["security.allow_unsafe_parent_loads"true],
      ["layout.css.backdrop-filter.enabled"true],
      ["layout.css.relative-color-syntax.enabled"true],
      ["layout.css.color-mix-multi-color.enabled"true],
      ["dom.security.html_serialization_escape_lt_gt"true],
      ["layout.css.attr.enabled"true],
    ],
  });
  await addTab("about:blank");
  await performTest();
  gBrowser.removeCurrentTab();
});

async function performTest() {
  const OutputParser = require("resource://devtools/client/shared/output-parser.js");

  const { host, doc } = await createHost(
    "bottom",
    "data:text/html," + "<h1>browser_outputParser.js</h1><div></div>"
  );

  const cssProperties = getClientCssProperties();

  const parser = new OutputParser(doc, cssProperties);
  testParseCssProperty(doc, parser);
  testParseCssVar(doc, parser);
  testParseURL(doc, parser);
  testParseFilter(doc, parser);
  testParseBackdropFilter(doc, parser);
  testParseAngle(doc, parser);
  testParseShape(doc, parser);
  testParseVariable(doc, parser);
  testParseColorVariable(doc, parser);
  testParseFontFamily(doc, parser);
  testParseLightDark(doc, parser);
  testParseAttr(doc, parser);
  testParseFunctionsForCssExplainers(doc, parser);

  host.destroy();
}

// Class name used in color swatch.
var COLOR_TEST_CLASS = "test-class";

// Create a new CSS color-parsing test.  |name| is the name of the CSS
// property.  |value| is the CSS text to use.  |segments| is an array
// describing the expected result.  If an element of |segments| is a
// string, it is simply appended to the expected string.  Otherwise,
// it must be an object with a |name| property, which is the color
// name as it appears in the input.
//
// This approach is taken to reduce boilerplate and to make it simpler
// to modify the test when the parseCssProperty output changes.
function makeColorTest(name, value, segments) {
  const result = {
    name,
    value,
    expected: "",
  };

  for (const segment of segments) {
    if (typeof segment === "string") {
      result.expected += segment;
    } else {
      result.expected += getColorMarkup({
        color: segment.name,
        colorFunction: segment.colorFunction,
      });
    }
  }

  return result;
}

function getColorMarkup({ color, colorFunction, content }) {
  const buttonAttributes = {
    class: COLOR_TEST_CLASS,
    style: `background-color:${color}`,
    tabindex: 0,
    role: "button",
  };
  if (colorFunction) {
    buttonAttributes["data-color-function"] = colorFunction;
  }
  const buttonAttrString = Object.entries(buttonAttributes)
    .map(([attr, v]) => `${attr}="${v}"`)
    .join(" ");

  // prettier-ignore
  return (
    `<span data-color="${color}" class="color-swatch-container">` +
      `<span ${buttonAttrString}></span>` +
      `<span>${content ?? color}</span>` +
    `</span>`
  );
}

function testParseCssProperty(doc, parser) {
  const tests = [
    makeColorTest("border""1px solid red", ["1px solid ", { name: "red" }]),

    makeColorTest(
      "background-image",
      "linear-gradient(to right, #F60 10%, rgba(0,0,0,1))",
      [
        "linear-gradient(to right, ",
        { name: "#F60", colorFunction: "linear-gradient" },
        " 10%, ",
        { name: "rgba(0,0,0,1)", colorFunction: "linear-gradient" },
        ")",
      ]
    ),

    // In "arial black", "black" is a font, not a color.
    // (The font-family parser creates a span)
    makeColorTest("font-family""arial black", ["<span>arial black</span>"]),

    makeColorTest("box-shadow""0 0 1em red", ["0 0 1em ", { name: "red" }]),

    makeColorTest("box-shadow""0 0 1em red, 2px 2px 0 0 rgba(0,0,0,.5)", [
      "0 0 1em ",
      { name: "red" },
      ", 2px 2px 0 0 ",
      { name: "rgba(0,0,0,.5)" },
    ]),

    makeColorTest("content"'"red"', ['"red"']),

    // Invalid property names should not cause exceptions.
    makeColorTest("hellothere""'red'", ["'red'"]),

    makeColorTest(
      "filter",
      "blur(1px) drop-shadow(0 0 0 blue) url(red.svg#blue)",
      [
        '<span data-filters="blur(1px) drop-shadow(0 0 0 blue) ',
        'url(red.svg#blue)"><span>',
        "blur(1px) drop-shadow(0 0 0 ",
        { name: "blue", colorFunction: "drop-shadow" },
        ") url(red.svg#blue)</span></span>",
      ]
    ),

    makeColorTest("color""currentColor", ["currentColor"]),

    // Test a very long property.
    makeColorTest(
      "background-image",
      "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%)",
      [
        "linear-gradient(to left, ",
        { name: "transparent", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "transparent", colorFunction: "linear-gradient" },
        " 5%,",
        { name: "#F00", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#F00", colorFunction: "linear-gradient" },
        " 10%,",
        { name: "#FF0", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#FF0", colorFunction: "linear-gradient" },
        " 15%,",
        { name: "#0F0", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#0F0", colorFunction: "linear-gradient" },
        " 20%,",
        { name: "#0FF", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#0FF", colorFunction: "linear-gradient" },
        " 25%,",
        { name: "#00F", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#00F", colorFunction: "linear-gradient" },
        " 30%,",
        { name: "#800", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#800", colorFunction: "linear-gradient" },
        " 35%,",
        { name: "#880", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#880", colorFunction: "linear-gradient" },
        " 40%,",
        { name: "#080", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#080", colorFunction: "linear-gradient" },
        " 45%,",
        { name: "#088", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#088", colorFunction: "linear-gradient" },
        " 50%,",
        { name: "#008", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#008", colorFunction: "linear-gradient" },
        " 55%,",
        { name: "#FFF", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#FFF", colorFunction: "linear-gradient" },
        " 60%,",
        { name: "#EEE", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#EEE", colorFunction: "linear-gradient" },
        " 65%,",
        { name: "#CCC", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#CCC", colorFunction: "linear-gradient" },
        " 70%,",
        { name: "#999", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#999", colorFunction: "linear-gradient" },
        " 75%,",
        { name: "#666", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#666", colorFunction: "linear-gradient" },
        " 80%,",
        { name: "#333", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#333", colorFunction: "linear-gradient" },
        " 85%,",
        { name: "#111", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#111", colorFunction: "linear-gradient" },
        " 90%,",
        { name: "#000", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "#000", colorFunction: "linear-gradient" },
        " 95%,",
        { name: "transparent", colorFunction: "linear-gradient" },
        " 0, ",
        { name: "transparent", colorFunction: "linear-gradient" },
        " 100%)",
      ]
    ),

    // Note the lack of a space before the color here.
    makeColorTest("border""1px dotted#f06", [
      "1px dotted ",
      { name: "#f06" },
    ]),

    {
      name: "color",
      value: "color-mix(in srgb, red, blue)",
      expected: getColorMarkup({
        color: "color-mix(in srgb, red, blue)",
        content:
          `color-mix(in srgb, ` +
          // we have a nested color spans for the color-mix() params, `red` and `blue`
          getColorMarkup({ color: "red", colorFunction: "color-mix" }) +
          `, ` +
          getColorMarkup({ color: "blue", colorFunction: "color-mix" }) +
          `)`,
      }),
    },

    {
      name: "background-image",
      value:
        "linear-gradient(to top, color-mix(in srgb, #008000, rgba(255, 255, 0, 0.9)), blue, contrast-color(#abc))",
      expected:
        `linear-gradient(to top, ` +
        // first we have a nested color span for resulting color of color-mix()
        getColorMarkup({
          color: "color-mix(in srgb, #008000, rgba(255, 255, 0, 0.9))",
          colorFunction: "linear-gradient",
          content:
            `color-mix(in srgb, ` +
            // we have a nested color spans for the color-mix() params, `#008000` and `rgba(255, 255, 0, 0.9)`
            getColorMarkup({ color: "#008000", colorFunction: "color-mix" }) +
            `, ` +
            getColorMarkup({
              color: "rgba(255, 255, 0, 0.9)",
              colorFunction: "color-mix",
            }) +
            // closing the `color-mix()` function
            `)`,
        }) +
        ", " +
        // second param for the gradient, `blue`
        getColorMarkup({
          color: "blue",
          colorFunction: "linear-gradient",
        }) +
        ", " +
        // third param for the gradient, a `contrast-color()`
        getColorMarkup({
          color: "contrast-color(#abc)",
          colorFunction: "linear-gradient",
          content:
            `contrast-color(` +
            getColorMarkup({
              // we have a nested color spans for the contrast-color() param, `#abc`
              color: "#abc",
              colorFunction: "contrast-color",
            }) +
            `)`,
        }) +
        // closing the `linear-gradient()` function
        ")",
    },

    makeColorTest("color""light-dark(red, blue)", [
      "light-dark(",
      { name: "red", colorFunction: "light-dark" },
      ", ",
      { name: "blue", colorFunction: "light-dark" },
      ")",
    ]),

    makeColorTest(
      "background-image",
      "linear-gradient(to top, light-dark(#008000, rgba(255, 255, 0, 0.9)), blue)",
      [
        "linear-gradient(to top, ",
        "light-dark(",
        { name: "#008000", colorFunction: "light-dark" },
        ", ",
        { name: "rgba(255, 255, 0, 0.9)", colorFunction: "light-dark" },
        "), ",
        { name: "blue", colorFunction: "linear-gradient" },
        ")",
      ]
    ),

    {
      name: "color",
      value: "rgb(from gold r g b)",
      expected: getColorMarkup({
        color: "rgb(from gold r g b)",
        // we have a nested color span for the `gold` after `from`
        content: `rgb(from ${getColorMarkup({ color: "gold", colorFunction: "rgb" })} r g b)`,
      }),
    },

    {
      name: "color",
      value: "color(from hsl(0 100% 50%) xyz x y 0.5)",
      expected: getColorMarkup({
        color: "color(from hsl(0 100% 50%) xyz x y 0.5)",
        // we have a nested color span for the inner `hsl()` after `from`
        content:
          `color(from ` +
          getColorMarkup({ color: "hsl(0 100% 50%)", colorFunction: "color" }) +
          ` xyz x y 0.5)`,
      }),
    },

    {
      name: "color",
      value: "oklab(from red calc(l - 1) calc(a * 2) calc(b + 3) / alpha)",
      expected: getColorMarkup({
        color: "oklab(from red calc(l - 1) calc(a * 2) calc(b + 3) / alpha)",
        // we have a nested color span for the inner `red` after `from`
        content:
          `oklab(from ` +
          getColorMarkup({ color: "red", colorFunction: "oklab" }) +
          ` calc(l - 1) calc(a * 2) calc(b + 3) / alpha)`,
      }),
    },

    {
      name: "color",
      value: "rgb(from color-mix(in lch, plum 40%, pink) r g b)",
      expected: getColorMarkup({
        color: "rgb(from color-mix(in lch, plum 40%, pink) r g b)",
        content:
          `rgb(from ` +
          // we have a nested color span for the inner `color-mix()` after `from`
          getColorMarkup({
            color: "color-mix(in lch, plum 40%, pink)",
            colorFunction: "rgb",
            content:
              `color-mix(in lch, ` +
              // and we have nested colors representing the color-mix color params (plum and pink)
              getColorMarkup({ color: "plum", colorFunction: "color-mix" }) +
              ` 40%, ` +
              getColorMarkup({ color: "pink", colorFunction: "color-mix" }) +
              `)`,
          }) +
          ` r g b)`,
      }),
    },

    {
      name: "color",
      value: "rgb(from rgb(from gold r g b) r g b)",
      expected: getColorMarkup({
        color: "rgb(from rgb(from gold r g b) r g b)",
        content:
          `rgb(from ` +
          // we have a nested color span for the inner `rgb()` after `from`
          getColorMarkup({
            color: "rgb(from gold r g b)",
            colorFunction: "rgb",
            content:
              `rgb(from ` +
              // we have a nested color span for `gold` after `from`
              getColorMarkup({ color: "gold", colorFunction: "rgb" }) +
              ` r g b)`,
          }) +
          ` r g b)`,
      }),
    },

    {
      name: "background-image",
      value: `image(rgb(255 0 0 / 0.5)), url("bg-image.png")`,
      expected:
        `image(` +
        getColorMarkup({
          color: "rgb(255 0 0 / 0.5)",
          colorFunction: "image",
        }) +
        `), url("bg-image.png")`,
    },

    {
      name: "background-image",
      value: "linear-gradient(to right, #F60 10%, rgb(from gold r g b))",
      expected:
        `linear-gradient(to right, ` +
        getColorMarkup({ color: "#F60", colorFunction: "linear-gradient" }) +
        " 10%, " +
        getColorMarkup({
          color: "rgb(from gold r g b)",
          colorFunction: "linear-gradient",
          content:
            `rgb(from ` +
            // nested color span for `gold` after `from`
            getColorMarkup({
              color: "gold",
              colorFunction: "rgb",
            }) +
            " r g b)",
        }) +
        // closing linear-gradient()
        ")",
    },

    {
      desc: "--a: (min-width:680px)",
      name: "--a",
      value: "(min-width:680px)",
      expected: "(min-width:680px)",
    },

    {
      desc: "Interactive color swatch",
      name: "color",
      value: "gold",
      expected:
        // prettier-ignore
        `<span data-color="gold" class="color-swatch-container">` +
          `<span class="test-class" style="background-color:gold" tabindex="0" role="button"></span>` +
          `<span>gold</span>` +
        `</span>`,
      parserExtraOptions: {
        colorSwatchReadOnly: false,
      },
    },

    {
      desc: "Read-only color swatch",
      name: "color",
      value: "gold",
      expected:
        // prettier-ignore
        `<span data-color="gold" class="color-swatch-container">` +
          `<span class="test-class" style="background-color:gold"></span>` +
          `<span>gold</span>` +
        `</span>`,
      parserExtraOptions: {
        colorSwatchReadOnly: true,
      },
    },

    {
      name: "color",
      value: "contrast-color(red)",
      expected: getColorMarkup({
        color: "contrast-color(red)",
        content:
          "contrast-color(" +
          // color span for the `color()` param, `red`
          getColorMarkup({ color: "red", colorFunction: "contrast-color" }) +
          ")",
      }),
    },

    {
      name: "color",
      value: "color-mix(in srgb, red, contrast-color(hsl(0 100 200)))",
      expected: getColorMarkup({
        color: "color-mix(in srgb, red, contrast-color(hsl(0 100 200)))",
        content:
          "color-mix(in srgb, " +
          // color span for the `color-mix()` param, `red`
          getColorMarkup({ color: "red", colorFunction: "color-mix" }) +
          ", " +
          // color span for the resulting color of `contrast-color()`
          getColorMarkup({
            color: "contrast-color(hsl(0 100 200))",
            colorFunction: "color-mix",
            content:
              "contrast-color(" +
              // color span for the `color()` param, `hsl(0 100 200)`
              getColorMarkup({
                color: "hsl(0 100 200)",
                colorFunction: "contrast-color",
              }) +
              ")",
          }) +
          // closing `color-mix()`
          ")",
      }),
    },

    {
      name: "color",
      value: "color-mix(in srgb, red, blue, green)",
      expected: getColorMarkup({
        color: "color-mix(in srgb, red, blue, green)",
        content:
          "color-mix(in srgb, " +
          // color span for the first `color-mix()` param, `red`
          getColorMarkup({ color: "red", colorFunction: "color-mix" }) +
          ", " +
          // color span for the second `color-mix()` param, `blue`
          getColorMarkup({ color: "blue", colorFunction: "color-mix" }) +
          ", " +
          // color span for the third `color-mix()` param, `green`
          getColorMarkup({ color: "green", colorFunction: "color-mix" }) +
          // closing `color-mix()
          ")",
      }),
    },

    {
      name: "color",
      value: "color-mix(in srgb, red)",
      expected: getColorMarkup({
        color: "color-mix(in srgb, red)",
        content:
          "color-mix(in srgb, " +
          // color span for the first `color-mix()` param, `red`
          getColorMarkup({ color: "red", colorFunction: "color-mix" }) +
          // closing `color-mix()
          ")",
      }),
    },
  ];

  const target = doc.querySelector("div");
  ok(target, "captain, we have the div");

  for (const test of tests) {
    info(`Testing "${test.name}: ${test.value}"`);

    const frag = parser.parseCssProperty(test.name, test.value, {
      colorSwatchClass: COLOR_TEST_CLASS,
      ...(test.parserExtraOptions || {}),
    });

    target.appendChild(frag);

    is(
      target.innerHTML,
      test.expected,
      `CSS property correctly parsed for "${test.name}: ${test.value}"`
    );

    target.innerHTML = "";
  }
}

function testParseCssVar(doc, parser) {
  const frag = parser.parseCssProperty("color""var(--some-kind-of-green)", {
    colorSwatchClass: "test-colorswatch",
  });

  const target = doc.querySelector("div");
  ok(target, "captain, we have the div");
  target.appendChild(frag);

  is(
    target.innerHTML,
    "var(--some-kind-of-green)",
    "CSS property correctly parsed"
  );

  target.innerHTML = "";
}

function testParseURL(doc, parser) {
  info("Test that URL parsing preserves quoting style");

  const tests = [
    {
      desc: "simple test without quotes",
      leader: "url(",
      trailer: ")",
    },
    {
      desc: "simple test with single quotes",
      leader: "url('",
      trailer: "')",
    },
    {
      desc: "simple test with double quotes",
      leader: 'url("',
      trailer: '")',
    },
    {
      desc: "test with single quotes and whitespace",
      leader: "url( \t'",
      trailer: "'\r\n\f)",
    },
    {
      desc: "simple test with uppercase",
      leader: "URL(",
      trailer: ")",
    },
    {
      desc: "bad url, missing paren",
      leader: "url(",
      trailer: "",
      expectedTrailer: ")",
    },
    {
      desc: "bad url, missing paren, with baseURI",
      baseURI: "data:text/html,<style></style>",
      leader: "url(",
      trailer: "",
      expectedTrailer: ")",
    },
    {
      desc: "bad url, double quote, missing paren",
      leader: 'url("',
      trailer: '"',
      expectedTrailer: '")',
    },
    {
      desc: "bad url, single quote, missing paren and quote",
      leader: "url('",
      trailer: "",
      expectedTrailer: "')",
    },
  ];

  const target = doc.querySelector("div");
  for (const test of tests) {
    const url = test.leader + "something.jpg" + test.trailer;
    const frag = parser.parseCssProperty("background", url, {
      urlClass: "test-urlclass",
      baseURI: test.baseURI,
    });

    target.replaceChildren(frag);

    const expectedTrailer = test.expectedTrailer || test.trailer;

    const expected =
      test.leader +
      '<a target="_blank" class="test-urlclass" ' +
      'href="something.jpg">something.jpg</a>' +
      expectedTrailer;

    is(target.innerHTML, expected, test.desc);
  }

  info("Check that long URLs get the class for visual truncation");
  const LONG_URL = `something.jpg?${"a".repeat(5000)}`;
  target.replaceChildren(
    parser.parseCssProperty("background", `url(${LONG_URL})`, {
      urlClass: "test-urlclass",
    })
  );
  is(
    target.innerHTML,
    // prettier-ignore
    `url(` +
    `<a target="_blank" class="test-urlclass propertyvalue-long-text" href="${LONG_URL}">` +
    LONG_URL +
    `</a>` +
    `)`,
    "long url is wrapped in an element with a specific class"
  );

  target.replaceChildren(
    parser.parseCssProperty("background", `url(${LONG_URL})`, {})
  );
  is(
    target.innerHTML,
    `<span class="propertyvalue-long-text">url(${LONG_URL})</span>`,
    "long url is wrapped in an element with a specific class, even when urlClass option is not set"
  );

  target.innerHTML = "";
}

function testParseFilter(doc, parser) {
  const frag = parser.parseCssProperty("filter""something invalid", {
    filterSwatchClass: "test-filterswatch",
  });

  const swatchCount = frag.querySelectorAll(".test-filterswatch").length;
  is(swatchCount, 1"filter swatch was created");
}

function testParseBackdropFilter(doc, parser) {
  const frag = parser.parseCssProperty("backdrop-filter""something invalid", {
    filterSwatchClass: "test-filterswatch",
  });

  const swatchCount = frag.querySelectorAll(".test-filterswatch").length;
  is(swatchCount, 1"filter swatch was created for backdrop-filter");
}

function testParseAngle(doc, parser) {
  let frag = parser.parseCssProperty("rotate""90deg", {
    angleSwatchClass: "test-angleswatch",
  });

  let swatchCount = frag.querySelectorAll(".test-angleswatch").length;
  is(swatchCount, 1"angle swatch was created");

  frag = parser.parseCssProperty(
    "background-image",
    "linear-gradient(90deg, red, blue",
    {
      angleSwatchClass: "test-angleswatch",
    }
  );

  swatchCount = frag.querySelectorAll(".test-angleswatch").length;
  is(swatchCount, 1"angle swatch was created");
}

function testParseShape(doc, parser) {
  info("Test shape parsing");

  const tests = [
    {
      desc: "simple polygon()",
      definition: "polygon(0px 0px, 10px 10px, 10px 20px)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `polygon(` +
            `<span class="inspector-shape-point" data-point="0">` +
              `<span class="inspector-shape-point" data-point="0" data-pair="x">0px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="0" data-pair="y">0px</span>` +
            `</span>` +
            `, ` +
            `<span class="inspector-shape-point" data-point="1">` +
              `<span class="inspector-shape-point" data-point="1" data-pair="x">10px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="1" data-pair="y">10px</span>` +
            `</span>` +
            `, ` +
            `<span class="inspector-shape-point" data-point="2">` +
              `<span class="inspector-shape-point" data-point="2" data-pair="x">10px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="2" data-pair="y">20px</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "simple polygon() with extra spaces",
      definition: "polygon( 0px 0px , 10px 10px , 10px 20px )",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `polygon( ` +
            `<span class="inspector-shape-point" data-point="0">` +
              `<span class="inspector-shape-point" data-point="0" data-pair="x">0px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="0" data-pair="y">0px</span>` +
            `</span>` +
            ` , ` +
            `<span class="inspector-shape-point" data-point="1">` +
              `<span class="inspector-shape-point" data-point="1" data-pair="x">10px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="1" data-pair="y">10px</span>` +
            `</span>` +
            ` , ` +
            `<span class="inspector-shape-point" data-point="2">` +
              `<span class="inspector-shape-point" data-point="2" data-pair="x">10px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="2" data-pair="y">20px</span>` +
            `</span>` +
            ` )` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "polygon() with fill rule",
      definition: "polygon(nonzero, 0px 0px, 10px 10px, 10px 20px)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `polygon(nonzero, ` +
            `<span class="inspector-shape-point" data-point="0">` +
              `<span class="inspector-shape-point" data-point="0" data-pair="x">0px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="0" data-pair="y">0px</span>` +
            `</span>` +
            `, ` +
            `<span class="inspector-shape-point" data-point="1">` +
              `<span class="inspector-shape-point" data-point="1" data-pair="x">10px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="1" data-pair="y">10px</span>` +
            `</span>` +
            `, ` +
            `<span class="inspector-shape-point" data-point="2">` +
              `<span class="inspector-shape-point" data-point="2" data-pair="x">10px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="2" data-pair="y">20px</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "complex polygon()",
      definition:
        "polygon(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n " +
        "12em var(--variable), 100% 100%) margin-box",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `polygon(` +
            `evenodd, ` +
            `<span class="inspector-shape-point" data-point="0">` +
              `<span class="inspector-shape-point" data-point="0" data-pair="x">0px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="0" data-pair="y">0px</span>` +
            `</span>` +
            `, ` +
            `<span class="inspector-shape-point" data-point="1">` +
              `<span class="inspector-shape-point" data-point="1" data-pair="x">10%</span>` +
              `<span class="inspector-shape-point" data-point="1" data-pair="y">200px</span>` +
            `</span>` +
            `,` +
            `<span class="inspector-shape-point" data-point="2">` +
              `<span class="inspector-shape-point" data-point="2" data-pair="x">30%</span>` +
              `<span class="inspector-shape-point" data-point="2" data-pair="y">30%</span>` +
            `</span>` +
            ` , ` +
            `<span class="inspector-shape-point" data-point="3">` +
              `<span class="inspector-shape-point" data-point="3" data-pair="x">calc(250px - 10px)</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="3" data-pair="y">0</span>` +
            `</span>` +
            ` ,\n ` +
            `<span class="inspector-shape-point" data-point="4">` +
              `<span class="inspector-shape-point" data-point="4" data-pair="x">12em</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="4" data-pair="y">var(--variable)</span>` +
            `</span>` +
            `, ` +
            `<span class="inspector-shape-point" data-point="5">` +
              `<span class="inspector-shape-point" data-point="5" data-pair="x">100%</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="5" data-pair="y">100%</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>` +
        ` margin-box`,
    },
    {
      desc: "complex POLYGON()",
      definition:
        "POLYGON(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n " +
        "12em var(--variable), 100% 100%) margin-box",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `POLYGON(` +
            `evenodd, ` +
            `<span class="inspector-shape-point" data-point="0">` +
              `<span class="inspector-shape-point" data-point="0" data-pair="x">0px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="0" data-pair="y">0px</span>` +
            `</span>` +
            `, ` +
            `<span class="inspector-shape-point" data-point="1">` +
              `<span class="inspector-shape-point" data-point="1" data-pair="x">10%</span>` +
              `<span class="inspector-shape-point" data-point="1" data-pair="y">200px</span>` +
            `</span>` +
            `,` +
            `<span class="inspector-shape-point" data-point="2">` +
              `<span class="inspector-shape-point" data-point="2" data-pair="x">30%</span>` +
              `<span class="inspector-shape-point" data-point="2" data-pair="y">30%</span>` +
            `</span>` +
            ` , ` +
            `<span class="inspector-shape-point" data-point="3">` +
              `<span class="inspector-shape-point" data-point="3" data-pair="x">calc(250px - 10px)</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="3" data-pair="y">0</span>` +
            `</span>` +
            ` ,\n ` +
            `<span class="inspector-shape-point" data-point="4">` +
              `<span class="inspector-shape-point" data-point="4" data-pair="x">12em</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="4" data-pair="y">var(--variable)</span>` +
            `</span>` +
            `, ` +
            `<span class="inspector-shape-point" data-point="5">` +
              `<span class="inspector-shape-point" data-point="5" data-pair="x">100%</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="5" data-pair="y">100%</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>` +
        ` margin-box`,
    },
    {
      desc: "Invalid polygon shape",
      definition: "polygon(0px 0px 100px 20px, 20% 20%)",
      markup: "polygon(0px 0px 100px 20px, 20% 20%)",
    },
    {
      desc: "Circle shape with all arguments",
      definition: "circle(25% at\n 30% 200px) border-box",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `circle(` +
            `<span class="inspector-shape-point" data-point="radius">25%</span>` +
            ` at\n ` +
            `<span class="inspector-shape-point" data-point="center">` +
              `<span class="inspector-shape-point" data-point="center" data-pair="x">30%</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="center" data-pair="y">200px</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>` +
        ` border-box`,
    },
    {
      desc: "Circle shape with only one center",
      definition: "circle(25em at 40%)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `circle(` +
            `<span class="inspector-shape-point" data-point="radius">25em</span>` +
            ` at ` +
            `<span class="inspector-shape-point" data-point="center">` +
              `<span class="inspector-shape-point" data-point="center" data-pair="x">40%</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Circle shape with no radius",
      definition: "circle(at 30% 40%)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `circle(` +
            `at ` +
            `<span class="inspector-shape-point" data-point="center">` +
              `<span class="inspector-shape-point" data-point="center" data-pair="x">30%</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="center" data-pair="y">40%</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Circle shape with no radius and keyword position",
      definition: "circle(at right center)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `circle(` +
            `at ` +
            `<span class="inspector-shape-point" data-point="center">` +
              `<span class="inspector-shape-point" data-point="center" data-pair="x">right</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="center" data-pair="y">center</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Circle shape with no radius and 4 positions",
      definition: "circle(at left 10px top 15px)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `circle(` +
            `at ` +
            `<span class="inspector-shape-point" data-point="center">` +
              `<span class="inspector-shape-point" data-point="center" data-pair="x">left</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="center">10px</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="center" data-pair="y">top</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="center">15px</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Circle shape with no center",
      definition: "circle(12em)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `circle(` +
            `<span class="inspector-shape-point" data-point="radius">12em</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Circle shape with no center and keyword radius size",
      definition: "circle(farthest-side)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `circle(` +
            `<span class="inspector-shape-point" data-point="radius">farthest-side</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Circle shape with no center and computed radius size",
      definition: "circle(calc(10% + 12em))",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `circle(` +
            `<span class="inspector-shape-point" data-point="radius">calc(10% + 12em)</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Circle shape with computed position",
      definition: "circle(25% at calc(30% + 1px) calc(200px - 10%))",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `circle(` +
            `<span class="inspector-shape-point" data-point="radius">25%</span>` +
            ` at ` +
            `<span class="inspector-shape-point" data-point="center">` +
              `<span class="inspector-shape-point" data-point="center" data-pair="x">calc(30% + 1px)</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="center" data-pair="y">calc(200px - 10%)</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Circle shape with no arguments",
      definition: "circle()",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `circle()` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Circle shape with no space before at",
      definition: "circle(25%at 30% 30%)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `circle(` +
            `<span class="inspector-shape-point" data-point="radius">25%</span>` +
            `at ` +
            `<span class="inspector-shape-point" data-point="center">` +
              `<span class="inspector-shape-point" data-point="center" data-pair="x">30%</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="center" data-pair="y">30%</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "CIRCLE",
      definition: "CIRCLE(12em)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `CIRCLE(` +
            `<span class="inspector-shape-point" data-point="radius">12em</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Invalid circle shape",
      definition: "circle(25%at30%30%)",
      markup: "circle(25%at30%30%)",
    },
    {
      desc: "Ellipse shape with all arguments",
      definition: "ellipse(200px 10em at 25% 120px) content-box",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `ellipse(` +
            `<span class="inspector-shape-point" data-point="rx">200px</span>` +
            ` ` +
            `<span class="inspector-shape-point" data-point="ry">10em</span>` +
            ` at ` +
            `<span class="inspector-shape-point" data-point="center">` +
              `<span class="inspector-shape-point" data-point="center" data-pair="x">25%</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="center" data-pair="y">120px</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>` +
        ` content-box`,
    },
    {
      desc: "Ellipse shape with only one center",
      definition: "ellipse(200px 10% at 120px)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `ellipse(` +
            `<span class="inspector-shape-point" data-point="rx">200px</span>` +
            ` ` +
            `<span class="inspector-shape-point" data-point="ry">10%</span>` +
            ` at ` +
            `<span class="inspector-shape-point" data-point="center">` +
              `<span class="inspector-shape-point" data-point="center" data-pair="x">120px</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Ellipse shape with no radius",
      definition: "ellipse(at 25% 120px)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `ellipse(` +
            `at ` +
            `<span class="inspector-shape-point" data-point="center">` +
              `<span class="inspector-shape-point" data-point="center" data-pair="x">25%</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="center" data-pair="y">120px</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Ellipse shape with no center",
      definition: "ellipse(200px\n10em)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `ellipse(` +
            `<span class="inspector-shape-point" data-point="rx">200px</span>` +
            `\n` +
            `<span class="inspector-shape-point" data-point="ry">10em</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Ellipse shape with no center and keyword radii",
      definition: "ellipse(farthest-side closest-side)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `ellipse(` +
            `<span class="inspector-shape-point" data-point="rx">farthest-side</span>` +
            ` ` +
            `<span class="inspector-shape-point" data-point="ry">closest-side</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Ellipse shape with computed position and radiis",
      definition:
        "ellipse(calc(25% + 1px) calc(50% - 2px) at calc(30% + 1px) calc(200px - 10%))",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `ellipse(` +
            `<span class="inspector-shape-point" data-point="rx">calc(25% + 1px)</span>` +
            ` ` +
            `<span class="inspector-shape-point" data-point="ry">calc(50% - 2px)</span>` +
            ` at ` +
            `<span class="inspector-shape-point" data-point="center">` +
              `<span class="inspector-shape-point" data-point="center" data-pair="x">calc(30% + 1px)</span>` +
              ` ` +
              `<span class="inspector-shape-point" data-point="center" data-pair="y">calc(200px - 10%)</span>` +
            `</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Ellipse shape with no arguments",
      definition: "ellipse()",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `ellipse()` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "ELLIPSE()",
      definition: "ELLIPSE(200px 10em)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `ELLIPSE(` +
            `<span class="inspector-shape-point" data-point="rx">200px</span>` +
            ` ` +
            `<span class="inspector-shape-point" data-point="ry">10em</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Invalid ellipse shape",
      definition: "ellipse(200px100px at 30$ 20%)",
      markup: "ellipse(200px100px at 30$ 20%)",
    },
    {
      desc: "Inset shape with 4 arguments",
      definition: "inset(200px 100px\n 30%15%)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `inset(` +
            `<span class="inspector-shape-point top">200px</span>` +
            ` ` +
            `<span class="inspector-shape-point right">100px</span>` +
            `\n ` +
            `<span class="inspector-shape-point bottom">30%</span>` +
            `<span class="inspector-shape-point left">15%</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Inset shape with 3 arguments",
      definition: "inset(200px 100px 15%)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `inset(` +
            `<span class="inspector-shape-point top">200px</span>` +
            ` ` +
            `<span class="inspector-shape-point right left">100px</span>` +
            ` ` +
            `<span class="inspector-shape-point bottom">15%</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Inset shape with 2 arguments",
      definition: "inset(200px 100px)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `inset(` +
            `<span class="inspector-shape-point top bottom">200px</span>` +
            ` ` +
            `<span class="inspector-shape-point right left">100px</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Inset shape with 1 argument",
      definition: "inset(200px)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `inset(` +
            `<span class="inspector-shape-point top right bottom left">200px</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Inset shape with 0 arguments",
      definition: "inset()",
      markup: "inset()",
    },
    {
      desc: "INSET()",
      definition: "INSET(200px)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `INSET(` +
            `<span class="inspector-shape-point top right bottom left">200px</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "offset-path property with inset shape value",
      property: "offset-path",
      definition: "inset(200px)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `inset(` +
            `<span class="inspector-shape-point top right bottom left">200px</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Inset shape with nested function",
      definition: "inset(200px calc(100px + 10%) 15%)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `inset(` +
            `<span class="inspector-shape-point top">200px</span>` +
            ` ` +
            `<span class="inspector-shape-point right left">calc(100px + 10%)</span>` +
            ` ` +
            `<span class="inspector-shape-point bottom">15%</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      desc: "Inset shape with round keyword",
      definition: "inset(4rem round 1rem 2rem 3rem 4rem)",
      markup:
        // prettier-ignore
        `<span>` +
          `<button class="inspector-shape-swatch"></button>` +
          `<span class="inspector-shape">` +
            `inset(` +
            `<span class="inspector-shape-point top right bottom left">4rem</span>` +
            ` round 1rem 2rem 3rem 4rem` +
            `)` +
          `</span>` +
        `</span>`,
    },
  ];

  for (const { desc, definition, property = "clip-path", markup } of tests) {
    info(desc);
    const frag = parser.parseCssProperty(property, definition, {
      shapeClass: "inspector-shape",
      shapeSwatchClass: "inspector-shape-swatch",
    });
    const el = frag.ownerDocument.createElement("span");
    el.append(frag);
    is(el.innerHTML, markup, desc + " markup");
    is(el.textContent, definition, desc + " text content");

    const swatchlessFrag = parser.parseCssProperty(property, definition, {
      shapeClass: "inspector-shape",
    });
    is(
      swatchlessFrag.querySelector("button"),
      null,
      `${desc} does not have a swatch button when shapeSwatchClass option is not passed`
    );
  }
}

function getJumpToVariableButton(varName) {
  return `<button class="ruleview-variable-link jump-definition" data-variable-name="${varName}" title="Jump to variable definition"></button>`;
}

function testParseVariable(doc, parser) {
  const TESTS = [
    {
      text: "var(--seen)",
      variables: { "--seen""chartreuse" },
      expected:
        // prettier-ignore
        '<span data-color="chartreuse">' +
          "<span>var(" +
            `<span data-variable="chartreuse">--seen${getJumpToVariableButton("--seen")}</span>)` +
          "</span>" +
        "</span>",
    },
    {
      text: "var(--seen)",
      variables: {
        "--seen": { value: "var(--base)", computedValue: "1em" },
      },
      expected:
        // prettier-ignore
        "<span>var(" +
          `<span data-variable="var(--base)" data-variable-computed="1em">--seen${getJumpToVariableButton("--seen")}</span>)` +
        "</span>",
    },
    {
      text: "var(--not-seen)",
      variables: {},
      expected:
        // prettier-ignore
        "<span>var(" +
          '<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>' +
        ")</span>",
    },
    {
      text: "var(--seen, seagreen)",
      variables: { "--seen""chartreuse" },
      expected:
        // prettier-ignore
        '<span data-color="chartreuse">' +
          "<span>var(" +
            `<span data-variable="chartreuse">--seen${getJumpToVariableButton("--seen")}</span>` +
            `,` +
            '<span class="unmatched-class"> ' +
              '<span data-color="seagreen">' +
                "<span>seagreen</span>" +
              "</span>" +
            "</span>)" +
          "</span>" +
        "</span>",
    },
    {
      text: "var(--not-seen, var(--seen))",
      variables: { "--seen""chartreuse" },
      expected:
        // prettier-ignore
        `<span data-color=" chartreuse">` +
          "<span>var(" +
            '<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>'+
            ',' +
            "<span> " +
              '<span data-color="chartreuse">' +
                "<span>var(" +
                  `<span data-variable="chartreuse">--seen${getJumpToVariableButton("--seen")}</span>)` +
                "</span>" +
              "</span>" +
            "</span>)" +
          "</span>" +
        "</span>",
    },
    {
      text: "color-mix(in srgb, var(--x), purple)",
      variables: { "--x""yellow" },
      expected:
        // prettier-ignore
        `<span data-color=\"color-mix(in srgb, yellow, purple)\" class=\"color-swatch-container\">` +
          `<span class=\"test-class\" style=\"background-color:color-mix(in srgb, yellow, purple)\" tabindex=\"0\" role=\"button\"></span>` +
          `<span>` +
            `color-mix(in srgb, ` +
            `<span data-color="yellow" class="color-swatch-container">` +
              `<span class="test-class" style="background-color:yellow" tabindex="0" role="button" data-color-function="color-mix">` +
              `</span>` +
              `<span>var(<span data-variable="yellow">--x${getJumpToVariableButton("--x")}</span>)</span>` +
            `</span>` +
            `, ` +
            `<span data-color="purple" class="color-swatch-container">` +
              `<span class="test-class" style="background-color:purple" tabindex="0" role="button" data-color-function="color-mix">` +
              `</span>` +
              `<span>purple</span>` +
            `</span>` +
            // closing `color-mix()`
            `)` +
          `</span>` +
        `</span>`,
      parserExtraOptions: {
        colorSwatchClass: COLOR_TEST_CLASS,
      },
    },
    {
      text: "light-dark(var(--light), var(--dark))",
      variables: { "--light""yellow""--dark""gold" },
      expected:
        // prettier-ignore
        `light-dark(` +
        `<span data-color="yellow" class="color-swatch-container">` +
          `<span class="test-class" style="background-color:yellow" tabindex="0" role="button" data-color-function="light-dark">` +
          `</span>` +
          `<span>var(<span data-variable="yellow">--light${getJumpToVariableButton("--light")}</span>)</span>` +
        `</span>` +
        `, ` +
        `<span data-color="gold" class="color-swatch-container">` +
          `<span class="test-class" style="background-color:gold" tabindex="0" role="button" data-color-function="light-dark">` +
          `</span>` +
          `<span>var(<span data-variable="gold">--dark${getJumpToVariableButton("--dark")}</span>)</span>` +
        `</span>` +
        `)`,
      parserExtraOptions: {
        colorSwatchClass: COLOR_TEST_CLASS,
      },
    },
    {
      text: "1px solid var(--seen, seagreen)",
      // See Bug 1911974
      skipVariableDeclarationTest: true,
      variables: { "--seen""chartreuse" },
      expected:
        // prettier-ignore
        '1px solid ' +
        '<span data-color="chartreuse">' +
          "<span>var(" +
            `<span data-variable="chartreuse">--seen${getJumpToVariableButton("--seen")}</span>` +
            `,` +
            '<span class="unmatched-class"> ' +
              '<span data-color="seagreen">' +
                "<span>seagreen</span>" +
              "</span>" +
            "</span>)" +
          "</span>" +
        "</span>",
    },
    {
      text: "1px solid var(--not-seen, seagreen)",
      // See Bug 1911975
      skipVariableDeclarationTest: true,
      variables: {},
      expected:
        // prettier-ignore
        `1px solid ` +
        `<span data-color=" seagreen">` +
          `<span>var(` +
            `<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>` +
            `,` +
            `<span> ` +
              `<span data-color="seagreen">` +
                `<span>seagreen</span>` +
              `</span>` +
            `</span>)` +
          `</span>` +
        `</span>`,
    },
    {
      text: "rgba(var(--r), 0, 0, var(--a))",
      variables: { "--r""255""--a""0.5" },
      expected:
        // prettier-ignore
        '<span data-color="rgba(255, 0, 0, 0.5)">' +
          "<span>rgba("+
            "<span>" +
              `var(<span data-variable="255">--r${getJumpToVariableButton("--r")}</span>)` +
            "</span>, 0, 0, " +
            "<span>" +
              `var(<span data-variable="0.5">--a${getJumpToVariableButton("--a")}</span>)` +
            "</span>" +
          ")</span>" +
        "</span>",
    },
    {
      text: "rgba(from var(--base) r g 0 / calc(var(--a) * 0.5))",
      variables: { "--base""red""--a""0.8" },
      expected:
        // prettier-ignore
        '<span data-color="rgba(from red r g 0 / calc(0.8 * 0.5))">' +
          "<span>rgba("+
            "from " +
            `<span data-color="red">` +
              "<span>" +
                `var(<span data-variable="red">--base${getJumpToVariableButton("--base")}</span>)` +
              "</span>" +
            "</span>" +
            " r g 0 / " +
            "calc(" +
            "<span>" +
              `var(<span data-variable="0.8">--a${getJumpToVariableButton("--a")}</span>)` +
            "</span>" +
            " * 0.5)" +
          ")</span>" +
        "</span>",
    },
    {
      text: "rgb(var(--not-seen, 255), 0, 0)",
      variables: {},
      expected:
        // prettier-ignore
        '<span data-color="rgb( 255, 0, 0)">' +
          "<span>rgb("+
            "<span>var(" +
              `<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>` +
              `,` +
              `<span> 255</span>` +
            ")</span>, 0, 0" +
          ")</span>" +
        "</span>",
    },
    {
      text: "rgb(var(--not-seen), 0, 0)",
      variables: {},
      expected:
        // prettier-ignore
        `rgb(` +
          `<span>` +
            `var(` +
              `<span class="unmatched-class" data-variable="--not-seen is not set">` +
                `--not-seen` +
              `</span>` +
            `)` +
          `</span>` +
          `, 00` +
        `)`,
    },
    {
      text: "var(--registered)",
      variables: {
        "--registered": {
          value: "chartreuse",
          registeredProperty: {
            syntax: "<color>",
            inherits: true,
            initialValue: "hotpink",
          },
        },
      },
      expected:
        // prettier-ignore
        '<span data-color="chartreuse">' +
          "<span>var(" +
            '<span ' +
              'data-variable="chartreuse" ' +
              'data-registered-property-initial-value="hotpink" ' +
              'data-registered-property-syntax="<color>" ' +
              'data-registered-property-inherits="true"' +
            `>--registered${getJumpToVariableButton("--registered")}</span>)` +
          "</span>" +
        "</span>",
    },
    {
      text: "var(--registered-universal)",
      variables: {
        "--registered-universal": {
          value: "chartreuse",
          registeredProperty: {
            syntax: "*",
            inherits: false,
          },
        },
      },
      expected:
        // prettier-ignore
        '<span data-color="chartreuse">' +
          "<span>var(" +
            '<span ' +
              'data-variable="chartreuse" ' +
              'data-registered-property-syntax="*" ' +
              'data-registered-property-inherits="false"' +
            `>--registered-universal${getJumpToVariableButton("--registered-universal")}</span>)` +
          "</span>" +
        "</span>",
    },
    {
      text: "var(--x)",
      variables: {
        "--x""light-dark(red, blue)",
      },
      parserExtraOptions: {
        isDarkColorScheme: false,
      },
      expected: `<span>var(<span data-variable="light-dark(red, blue)">--x${getJumpToVariableButton("--x")}</span>)</span>`,
    },
    {
      text: "var(--x)",
      variables: {
        "--x""color-mix(in srgb, red 50%, blue)",
      },
      parserExtraOptions: {
        isDarkColorScheme: false,
      },
      expected:
        // prettier-ignore
        '<span data-color="color-mix(in srgb, red 50%, blue)">' +
          '<span>var(' +
            `<span data-variable="color-mix(in srgb, red 50%, blue)">--x${getJumpToVariableButton("--x")}</span>` +
          ')</span>' +
        '</span>',
    },
    {
      text: "var(--x)",
      variables: {
        "--x""contrast-color(blue)",
      },
      parserExtraOptions: {
        isDarkColorScheme: false,
      },
      expected:
        // prettier-ignore
        '<span data-color="contrast-color(blue)">' +
          '<span>var(' +
            `<span data-variable="contrast-color(blue)">--x${getJumpToVariableButton("--x")}</span>` +
          ')</span>' +
        '</span>',
    },
    {
      text: "var(--refers-empty)",
      variables: {
        "--refers-empty": { value: "var(--empty)", computedValue: "" },
      },
      expected:
        // prettier-ignore
        "<span>var(" +
          `<span data-variable="var(--empty)" data-variable-computed="">--refers-empty${getJumpToVariableButton("--refers-empty")}</span>)` +
        "</span>",
    },
    {
      text: "hsl(50, 70%, var(--foo))",
      variables: {
        "--foo""40%",
      },
      expected:
        // prettier-ignore
        `<span data-color="hsl(50, 70%, 40%)">` +
          `<span>`+
            `hsl(5070%, ` +
            `<span>` +
              `var(` +
                `<span data-variable="40%">--foo${getJumpToVariableButton("--foo")}</span>` +
              `)` +
            `</span>)` +
          `</span>` +
        `</span>`,
    },
    {
      text: "var(--bar)",
      variables: {
        "--foo""40%",
        "--bar""hsl(50, 70%, var(--foo))",
      },
      expected:
        // prettier-ignore
        `<span data-color="hsl(50, 70%, 40%)">` +
          `<span>` +
            `var(` +
              `<span data-variable="hsl(50, 70%, var(--foo))" data-variable-computed="hsl(50, 70%, 40%)">--bar${getJumpToVariableButton("--bar")}</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      text: "var(--primary)",
      variables: {
        "--primary""hsl(10, 100%, var(--fur))",
        "--fur""var(--bar)",
        "--bar""var(--foo)",
        "--foo""50%",
      },
      expected:
        // prettier-ignore
        `<span data-color="hsl(10, 100%, 50%)">` +
          `<span>` +
            `var(` +
              `<span data-variable="hsl(10, 100%, var(--fur))" data-variable-computed="hsl(10, 100%, 50%)">--primary${getJumpToVariableButton("--primary")}</span>` +
            `)` +
          `</span>` +
        `</span>`,
    },
    {
      text: "oklch(var(--fur) 20 var(--boo))",
      variables: {
        "--fur""var(--baz)",
        "--baz""var(--foo)",
        "--foo""10",
        "--boo""30",
      },
      expected:
        // prettier-ignore
        `<span data-color="oklch(10 20 30)">` +
          `<span>oklch(` +
            `<span>` +
              `var(` +
                `<span data-variable="var(--baz)" data-variable-computed="10">--fur${getJumpToVariableButton("--fur")}</span>` +
              `)` +
            `</span>` +
            ` 20 ` +
            `<span>` +
              `var(` +
                `<span data-variable="30">--boo${getJumpToVariableButton("--boo")}</span>` +
              `)` +
            `</span>` +
          `)</span>` +
        `</span>`,
    },
    {
      text: "var(--x)",
      variables: {
        "--x""10px",
      },
      parserExtraOptions: {
        showJumpToVariableButton: false,
      },
      // This shouldn't have a Jump to variable button
      expected: `<span>var(<span data-variable="10px">--x</span>)</span>`,
    },
    {
      // var() with spaces between params/parenthesis
      text: "var(  --foo  ,  500px  )",
      variables: { "--foo""1px" },
      expected:
        // prettier-ignore
        `<span>` +
          `var(  ` +
            `<span data-variable="1px">--foo${getJumpToVariableButton("--foo")}</span>` +
            `  ,` +
            `<span class="unmatched-class">  500px</span>` +
          `  )` +
        `</span>`,
    },
    {
      // multiline var()
      text: "var(\n--foo, 500px\n)",
      variables: { "--foo""1px" },
      expected:
        // prettier-ignore
        `<span>` +
          `var(\n` +
            `<span data-variable="1px">--foo${getJumpToVariableButton("--foo")}</span>` +
            `,` +
            `<span class="unmatched-class"500px</span>` +
          `\n)` +
        `</span>`,
    },
  ];

  const target = doc.querySelector("div");

  const VAR_NAME_TO_DEFINE = "--test-parse-variable";
  for (const test of TESTS) {
    // VAR_NAME_TO_DEFINE is used to test parsing the test.text if it's set for a
    // variable declaration, so it shouldn't be set in test.variables to avoid
    // messing with the test results.
    if (VAR_NAME_TO_DEFINE in test.variables) {
      throw new Error(`${VAR_NAME_TO_DEFINE} shouldn't be set in variables`);
    }

    // Also set the variable we're going to define, so its value can be computed as well
    const variables = {
      ...(test.variables || {}),
      [VAR_NAME_TO_DEFINE]: test.text,
    };
    // Set the variables to an element so we can get their computed values
    for (const [varName, varData] of Object.entries(variables)) {
      doc.body.style.setProperty(
        varName,
        typeof varData === "string" ? varData : varData.value
      );
    }

    const getVariableData = function (varName) {
      if (typeof variables[varName] === "string") {
        const value = variables[varName];
        const computedValue = getComputedStyle(doc.body).getPropertyValue(
          varName
        );
        return { value, computedValue };
      }

      return variables[varName] || {};
    };

    const frag = parser.parseCssProperty("color", test.text, {
      getVariableData,
      unmatchedClass: "unmatched-class",
      ...(test.parserExtraOptions || {}),
    });

    target.appendChild(frag);

    is(
      target.innerHTML,
      test.expected,
      `"color: ${test.text}" is parsed as expected`
    );

    target.innerHTML = "";

    if (test.skipVariableDeclarationTest) {
      continue;
    }

    const varFrag = parser.parseCssProperty(
      "--test-parse-variable",
      test.text,
      {
        getVariableData,
        unmatchedClass: "unmatched-class",
        ...(test.parserExtraOptions || {}),
      }
    );

    target.appendChild(varFrag);

    is(
      target.innerHTML,
      test.expected,
      `"--test-parse-variable: ${test.text}" is parsed as expected`
    );

    target.innerHTML = "";

    // Remove the variables to an element so we can get their computed values
    for (const varName in variables || {}) {
      doc.body.style.removeProperty(varName);
    }
  }
}

function testParseColorVariable(doc, parser) {
  const testCategories = [
    {
      desc: "Test for CSS variable defining color",
      tests: [
        makeColorTest("--test-var""lime", [{ name: "lime" }]),
        makeColorTest("--test-var""#000", [{ name: "#000" }]),
      ],
    },
    {
      desc: "Test for CSS variable not defining color",
      tests: [
        makeColorTest("--foo""something", ["something"]),
        makeColorTest("--bar""Arial Black", ["Arial Black"]),
        makeColorTest("--baz""10vmin", ["10vmin"]),
      ],
    },
    {
      desc: "Test for non CSS variable defining color",
      tests: [
        makeColorTest("non-css-variable""lime", ["lime"]),
        makeColorTest("-non-css-variable""#000", ["#000"]),
      ],
    },
  ];

  for (const category of testCategories) {
    info(category.desc);

    for (const test of category.tests) {
      info(test.desc);
      const target = doc.querySelector("div");

      const frag = parser.parseCssProperty(test.name, test.value, {
        colorSwatchClass: COLOR_TEST_CLASS,
      });

      target.appendChild(frag);

      is(
        target.innerHTML,
        test.expected,
        `The parsed result for '${test.name}: ${test.value}' is correct`
      );

      target.innerHTML = "";
    }
  }
}

function testParseFontFamily(doc, parser) {
  info("Test font-family parsing");
  const tests = [
    {
      desc: "No fonts",
      definition: "",
      families: [],
    },
    {
      desc: "List of fonts",
      definition: "Arial,Helvetica,sans-serif",
      families: ["Arial""Helvetica""sans-serif"],
    },
    {
      desc: "Fonts with spaces",
      definition: "Open Sans",
      families: ["Open Sans"],
    },
    {
      desc: "Quoted fonts",
      definition: "\"Arial\",'Open Sans'",
      families: ["Arial""Open Sans"],
    },
    {
      desc: "Fonts with extra whitespace",
      definition: " Open  Sans  ",
      families: ["Open  Sans"],
    },
  ];

  const textContentTests = [
    {
      desc: "No whitespace between fonts",
      definition: "Arial,Helvetica,sans-serif",
      output: "Arial,Helvetica,sans-serif",
    },
    {
      desc: "Whitespace between fonts",
      definition: "Arial ,  Helvetica,   sans-serif",
      output: "Arial ,  Helvetica,   sans-serif",
    },
    {
      desc: "Whitespace before first font trimmed",
      definition: "  Arial,Helvetica,sans-serif",
      output: "Arial,Helvetica,sans-serif",
    },
    {
      desc: "Whitespace after last font trimmed",
      definition: "Arial,Helvetica,sans-serif  ",
      output: "Arial,Helvetica,sans-serif",
    },
    {
      desc: "Whitespace between quoted fonts",
      definition: "'Arial' ,  \"Helvetica\" ",
      output: "'Arial' ,  \"Helvetica\"",
    },
    {
      desc: "Whitespace within font preserved",
      definition: "'  Ari al '",
      output: "'  Ari al '",
    },
  ];

  for (const { desc, definition, families } of tests) {
    info(desc);
    const frag = parser.parseCssProperty("font-family", definition, {
      fontFamilyClass: "ruleview-font-family",
    });
    const spans = frag.querySelectorAll(".ruleview-font-family");

    is(spans.length, families.length, desc + " span count");
    for (let i = 0; i < spans.length; i++) {
      is(spans[i].textContent, families[i], desc + " span contents");
    }
  }

  info("Test font-family text content");
  for (const { desc, definition, output } of textContentTests) {
    info(desc);
    const frag = parser.parseCssProperty("font-family", definition, {});
    is(frag.textContent, output, desc + " text content matches");
  }

  info("Test font-family with custom properties");
  const frag = parser.parseCssProperty(
    "font-family",
    "MonoLisa, var(--family, Georgia black, 'Helvetica Neue', serif), monospace !important",
    {
      getVariableData: () => ({}),
      unmatchedClass: "unmatched-class",
      fontFamilyClass: "ruleview-font-family",
    }
  );
  const target = doc.createElement("div");
  target.appendChild(frag);
  is(
    target.innerHTML,
    // prettier-ignore
    `<span class="ruleview-font-family">MonoLisa</span>` +
    `, ` +
    `<span>var(` +
      `<span class="unmatched-class" data-variable="--family is not set">` +
        `--family` +
      `</span>` +
      `,` +
      `<span> ` +
        `<span class="ruleview-font-family">Georgia black</span>` +
        `, ` +
        `'<span class="ruleview-font-family">Helvetica Neue</span>'` +
        `, ` +
        `<span class="ruleview-font-family">serif</span>` +
      `</span>)` +
    `</span>` +
    `, ` +
    `<span class="ruleview-font-family">monospace</span>` +
    ` !important`,
    "Got expected output for font-family with custom properties"
  );
}

function testParseLightDark(doc, parser) {
  const TESTS = [
    {
      message:
        "Not passing isDarkColorScheme doesn't add unmatched classes to parameters",
      propertyName: "color",
      propertyValue: "light-dark(red, blue)",
      expected:
        // prettier-ignore
        `light-dark(` +
        `<span data-color="red" class="color-swatch-container">` +
          `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
          `<span>red</span>` +
        `</span>, ` +
        `<span data-color="blue" class="color-swatch-container">` +
          `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
          `<span>blue</span>` +
        `</span>` +
      `)`,
    },
    {
      message: "in light mode, the second parameter gets the unmatched class",
      propertyName: "color",
      propertyValue: "light-dark(red, blue)",
      isDarkColorScheme: false,
      expected:
        // prettier-ignore
        `light-dark(` +
        `<span data-color="red" class="color-swatch-container">` +
          `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
          `<span>red</span>` +
        `</span>, ` +
        `<span data-color="blue" class="color-swatch-container unmatched-class">` +
          `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
          `<span>blue</span>` +
        `</span>` +
      `)`,
    },
    {
      message: "in dark mode, the first parameter gets the unmatched class",
      propertyName: "color",
      propertyValue: "light-dark(red, blue)",
      isDarkColorScheme: true,
      expected:
        // prettier-ignore
        `light-dark(` +
        `<span data-color="red" class="color-swatch-container unmatched-class">` +
          `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
          `<span>red</span>` +
        `</span>, ` +
        `<span data-color="blue" class="color-swatch-container">` +
          `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
          `<span>blue</span>` +
        `</span>` +
      `)`,
    },
    {
      message: "light-dark gets parsed as expected in shorthands in light mode",
      propertyName: "border",
      propertyValue: "1px solid light-dark(red, blue)",
      isDarkColorScheme: false,
      expected:
        // prettier-ignore
        `1px solid light-dark(` +
        `<span data-color="red" class="color-swatch-container">` +
          `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
          `<span>red</span>` +
        `</span>, ` +
        `<span data-color="blue" class="color-swatch-container unmatched-class">` +
          `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
          `<span>blue</span>` +
        `</span>` +
      `)`,
    },
    {
      message: "light-dark gets parsed as expected in shorthands in dark mode",
      propertyName: "border",
      propertyValue: "1px solid light-dark(red, blue)",
      isDarkColorScheme: true,
      expected:
        // prettier-ignore
        `1px solid light-dark(` +
        `<span data-color="red" class="color-swatch-container unmatched-class">` +
          `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
          `<span>red</span>` +
        `</span>, ` +
        `<span data-color="blue" class="color-swatch-container">` +
          `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
          `<span>blue</span>` +
        `</span>` +
      `)`,
    },
    {
      message: "Nested light-dark gets parsed as expected in light mode",
      propertyName: "background",
      propertyValue:
        "linear-gradient(45deg, light-dark(red, blue), light-dark(pink, cyan))",
      isDarkColorScheme: false,
      expected:
        // prettier-ignore
        `linear-gradient(` +
          `<span data-angle="45deg"><span>45deg</span></span>, ` +
          `light-dark(` +
            `<span data-color="red" class="color-swatch-container">` +
              `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>`+
              `<span>red</span>`+
            `</span>, `+
            `<span data-color="blue" class="color-swatch-container unmatched-class">` +
              `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
              `<span>blue</span>` +
            `</span>` +
          `), ` +
          `light-dark(` +
            `<span data-color="pink" class="color-swatch-container">` +
              `<span class="test-class" style="background-color:pink" tabindex="0" role="button" data-color-function="light-dark"></span>` +
              `<span>pink</span>` +
            `</span>, ` +
            `<span data-color="cyan" class="color-swatch-container unmatched-class">` +
              `<span class="test-class" style="background-color:cyan" tabindex="0" role="button" data-color-function="light-dark"></span>` +
              `<span>cyan</span>` +
            `</span>` +
          `)` +
        `)`,
    },
    {
      message: "Nested light-dark gets parsed as expected in dark mode",
      propertyName: "background",
      propertyValue:
        "linear-gradient(33deg, light-dark(red, blue), light-dark(pink, cyan))",
      isDarkColorScheme: true,
      expected:
        // prettier-ignore
        `linear-gradient(` +
          `<span data-angle="33deg"><span>33deg</span></span>, ` +
          `light-dark(` +
            `<span data-color="red" class="color-swatch-container unmatched-class">` +
              `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>`+
              `<span>red</span>`+
            `</span>, `+
            `<span data-color="blue" class="color-swatch-container">` +
              `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
              `<span>blue</span>` +
            `</span>` +
          `), ` +
          `light-dark(` +
            `<span data-color="pink" class="color-swatch-container unmatched-class">` +
              `<span class="test-class" style="background-color:pink" tabindex="0" role="button" data-color-function="light-dark"></span>` +
              `<span>pink</span>` +
            `</span>, ` +
            `<span data-color="cyan" class="color-swatch-container">` +
              `<span class="test-class" style="background-color:cyan" tabindex="0" role="button" data-color-function="light-dark"></span>` +
              `<span>cyan</span>` +
            `</span>` +
          `)` +
        `)`,
    },
    {
      message:
        "in light mode, the second parameter gets the unmatched class when it's a variable",
      propertyName: "color",
      propertyValue: "light-dark(var(--x), var(--y))",
      isDarkColorScheme: false,
      variables: { "--x""red""--y""blue" },
      expected:
        // prettier-ignore
        `light-dark(` +
          `<span data-color="red" class="color-swatch-container">` +
            `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
            `<span>var(` +
              `<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>` +
            `)</span>` +
          `</span>, ` +
          `<span data-color="blue" class="color-swatch-container unmatched-class">` +
            `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="light-dark"></span>` +
            `<span>var(` +
              `<span data-variable="blue">--y${getJumpToVariableButton("--y")}</span>` +
            `)</span>` +
          `</span>` +
        `)`,
    },
    {
      message:
        "in light mode, the second parameter gets the unmatched class when some param are not parsed",
      propertyName: "color",
      // Using `notacolor` so we don't get a wrapping Node for it (contrary to colors).
      // The value is still valid at parse time since we're using a variable,
      // so the OutputParser will actually parse the different parts
      propertyValue: "light-dark(var(--x),notacolor)",
      isDarkColorScheme: false,
      variables: { "--x""red" },
      expected:
        // prettier-ignore
        `light-dark(` +
          `<span data-color="red" class="color-swatch-container">` +
            `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
            `<span>` +
              `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` +
            `</span>` +
          `</span>,` +
          `<span class="unmatched-class">notacolor</span>` +
        `)`,
    },
    {
      message:
        "in dark mode, the first parameter gets the unmatched class when some param are not parsed",
      propertyName: "color",
      // Using `notacolor` so we don't get a wrapping Node for it (contrary to colors).
      // The value is still valid at parse time since we're using a variable,
      // so the OutputParser will actually parse the different parts
      propertyValue: "light-dark(notacolor,var(--x))",
      isDarkColorScheme: true,
      variables: { "--x""red" },
      expected:
        // prettier-ignore
        `light-dark(` +
          `<span class="unmatched-class">notacolor</span>,` +
          `<span data-color="red" class="color-swatch-container">` +
            `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
            `<span>` +
              `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` +
            `</span>` +
          `</span>` +
        `)`,
    },
    {
      message:
        "in light mode, the second parameter gets the unmatched class, comments are stripped out and whitespace are preserved",
      propertyName: "color",
      propertyValue:
        "light-dark( /* 1st param */ var(--x) /* delim */ , /*  2nd param */ notacolor /* delim */ )",
      isDarkColorScheme: false,
      variables: { "--x""red" },
      expected:
        // prettier-ignore
        `light-dark(  ` +
          `<span data-color="red" class="color-swatch-container">` +
            `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
            `<span>` +
              `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` +
            `</span>` +
          `</span>  ,  ` +
          `<span class="unmatched-class">notacolor</span>  ` +
        `)`,
    },
    {
      message:
        "in dark mode, the first parameter gets the unmatched class, comments are stripped out and whitespace are preserved",
      propertyName: "color",
      propertyValue:
        "light-dark( /* 1st param */ notacolor /* delim */ , /*  2nd param */ var(--x) /* delim */ )",
      isDarkColorScheme: true,
      variables: { "--x""red" },
      expected:
        // prettier-ignore
        `light-dark(  ` +
          `<span class="unmatched-class">notacolor</span>  ,  ` +
          `<span data-color="red" class="color-swatch-container">` +
            `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
            `<span>` +
              `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` +
            `</span>` +
          `</span>  ` +
        `)`,
    },
    {
      message:
        "in light mode with a single parameter, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)",
      propertyName: "color",
      propertyValue: "light-dark(var(--x))",
      isDarkColorScheme: false,
      variables: { "--x""red" },
      expected:
        // prettier-ignore
        `light-dark(` +
          `<span data-color="red" class="color-swatch-container">` +
            `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
            `<span>` +
              `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` +
            `</span>` +
          `</span>` +
        `)`,
    },
    {
      message:
        "in dark mode with a single parameter, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)",
      propertyName: "color",
      propertyValue: "light-dark(var(--x))",
      isDarkColorScheme: true,
      variables: { "--x""red" },
      expected:
        // prettier-ignore
        `light-dark(` +
          `<span data-color="red" class="color-swatch-container">` +
            `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
            `<span>` +
              `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` +
            `</span>` +
          `</span>` +
        `)`,
    },
    {
      message:
        "in light mode with 3 parameters, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)",
      propertyName: "color",
      propertyValue: "light-dark(var(--x),a,b)",
      isDarkColorScheme: false,
      variables: { "--x""red" },
      expected:
        // prettier-ignore
        `light-dark(` +
          `<span data-color="red" class="color-swatch-container">` +
            `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
            `<span>` +
              `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` +
            `</span>` +
          `</span>,a,b` +
        `)`,
    },
    {
      message:
        "in dark mode with 3 parameters, we don't strike through any parameter (TODO wrap with IACVT - Bug 1910845)",
      propertyName: "color",
      propertyValue: "light-dark(var(--x),a,b)",
      isDarkColorScheme: true,
      variables: { "--x""red" },
      expected:
        // prettier-ignore
        `light-dark(` +
          `<span data-color="red" class="color-swatch-container">` +
            `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="light-dark"></span>` +
            `<span>` +
              `var(<span data-variable="red">--x${getJumpToVariableButton("--x")}</span>)` +
            `</span>` +
          `</span>,a,b` +
        `)`,
    },
    {
      message:
        "in light mode with images, the second parameter gets the unmatched class",
      propertyName: "background-image",
      propertyValue: `light-dark(url("a.png"), url("b.png"))`,
      isDarkColorScheme: false,
      expected: `light-dark(url("a.png"), <span class="unmatched-class">url("b.png")</span>)`,
    },
    {
      message:
        "in dark mode with images, the first parameter gets the unmatched class",
      propertyName: "background-image",
      propertyValue: `light-dark(url("a.png"), url("b.png"))`,
      isDarkColorScheme: true,
      expected: `light-dark(<span class="unmatched-class">url("a.png")</span>, url("b.png"))`,
    },
    {
      message:
        "in light mode with gradients, the second parameter gets the unmatched class",
      propertyName: "background-image",
      propertyValue:
        "light-dark(linear-gradient(white, blue), linear-gradient(black, red))",
      isDarkColorScheme: false,
      expected:
        // prettier-ignore
        `light-dark(` +
          `linear-gradient(` +
            `<span data-color="white" class="color-swatch-container">` +
              `<span class="test-class" style="background-color:white" tabindex="0" role="button" data-color-function="linear-gradient"></span>` +
              `<span>white</span>` +
            `</span>` +
            `, ` +
            `<span data-color="blue" class="color-swatch-container">` +
              `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="linear-gradient"></span>` +
              `<span>blue</span>` +
            `</span>` +
          `)` +
          `, ` +
          `<span class="unmatched-class">` +
            `linear-gradient(` +
              `<span data-color="black" class="color-swatch-container">` +
                `<span class="test-class" style="background-color:black" tabindex="0" role="button" data-color-function="linear-gradient"></span>` +
                `<span>black</span>` +
              `</span>` +
              `, ` +
              `<span data-color="red" class="color-swatch-container">` +
                `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="linear-gradient"></span>` +
                `<span>red</span>` +
              `</span>` +
            `)` +
          `</span>` +
        `)`,
    },
    {
      message:
        "in dark mode with gradients, the first parameter gets the unmatched class",
      propertyName: "background-image",
      propertyValue:
        "light-dark(linear-gradient(white, blue), linear-gradient(black, red))",
      isDarkColorScheme: true,
      expected:
        // prettier-ignore
        `light-dark(` +
          `<span class="unmatched-class">` +
            `linear-gradient(` +
              `<span data-color="white" class="color-swatch-container">` +
                `<span class="test-class" style="background-color:white" tabindex="0" role="button" data-color-function="linear-gradient"></span>` +
                `<span>white</span>` +
              `</span>` +
              `, ` +
              `<span data-color="blue" class="color-swatch-container">` +
                `<span class="test-class" style="background-color:blue" tabindex="0" role="button" data-color-function="linear-gradient"></span>` +
                `<span>blue</span>` +
              `</span>` +
            `)` +
          `</span>` +
          `, ` +
          `linear-gradient(` +
            `<span data-color="black" class="color-swatch-container">` +
              `<span class="test-class" style="background-color:black" tabindex="0" role="button" data-color-function="linear-gradient"></span>` +
              `<span>black</span>` +
            `</span>` +
            `, ` +
            `<span data-color="red" class="color-swatch-container">` +
              `<span class="test-class" style="background-color:red" tabindex="0" role="button" data-color-function="linear-gradient"></span>` +
              `<span>red</span>` +
            `</span>` +
          `)` +
        `)`,
    },
  ];

  for (const test of TESTS) {
    const frag = parser.parseCssProperty(
      test.propertyName,
      test.propertyValue,
      {
        isDarkColorScheme: test.isDarkColorScheme,
        unmatchedClass: "unmatched-class",
        colorSwatchClass: COLOR_TEST_CLASS,
        getVariableData: varName => {
          if (typeof test.variables[varName] === "string") {
            return { value: test.variables[varName] };
          }

          return test.variables[varName] || {};
        },
      }
    );

    const target = doc.querySelector("div");
    target.appendChild(frag);

    is(target.innerHTML, test.expected, test.message);
    target.innerHTML = "";
  }
}

function testParseAttr(doc, parser) {
  const TESTS = [
    {
      message:
        "Not passing getAttributeValue doesn't add unmatched classes to attribute name",
      propertyValue: "attr(unknown)",
      attributes: {},
      getAttributeValue: null,
      expected: `attr(unknown)`,
    },
    {
      message:
        "Passing known attribute doesn't add unmatched classes to attribute name",
      propertyValue: "attr(data-x)",
      attributes: { "data-x""" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute="""">data-x</span>` +
        `</span>` +
        `)`,
    },
    {
      message:
        "Passing unknown attribute adds unmatched classes to attribute name",
      propertyValue: "attr(data-x)",
      attributes: {},
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute data-x is not set">` +
          `<span class="inspector-attr-name">data-x</span>` +
        `</span>` +
        `)`,
    },
    {
      message:
        "Passing unknown attribute adds unmatched classes to attribute name, not to fallback",
      propertyValue: `attr(data-x, "fallback")`,
      attributes: {},
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute data-x is not set">` +
          `<span class="inspector-attr-name">data-x</span>` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback">"fallback"</span>` +
        `)`,
    },
    {
      message: "Passing known attribute adds unmatched classes to fallback",
      propertyValue: `attr(data-x, "fallback")`,
      attributes: { "data-x""" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute="""">data-x</span>` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">"fallback"</span>` +
        `)`,
    },
    {
      message: "Checking attr() + spaces",
      propertyValue: `attr(  data-x  ,  "fallback"  )`,
      attributes: { "data-x""" },
      // prettier-ignore
      expected:
        `attr(` +
          `  ` +
          `<span class="inspector-attr-param">` +
            `<span class="inspector-attr-name" data-attribute="""">data-x</span>` +
          `</span>` +
          `  ,  ` +
          `<span class="inspector-attr-fallback unmatched-class">"fallback"</span>` +
        `  )`,
    },
    {
      message: "Modern attr() with known attribute and simple type",
      propertyValue: "attr(data-x raw-string)",
      attributes: { "data-x""x" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""x"">data-x</span>` +
          ` raw-string` +
        `</span>` +
        `)`,
    },
    {
      message: "Modern attr() with known attribute and type()",
      propertyName: "width",
      propertyValue: "attr(data-x type(<length> | <percentage>))",
      attributes: { "data-x""10px" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""10px"">data-x</span>` +
          ` type(<length> | <percentage>)` +
        `</span>` +
        `)`,
    },
    {
      message: "Modern attr() with unknown attribute and simple type",
      propertyValue: "attr(data-x raw-string)",
      attributes: {},
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute data-x is not set">` +
          `<span class="inspector-attr-name">data-x</span>` +
          ` raw-string` +
        `</span>` +
        `)`,
    },
    {
      message: "Modern attr() with unknown attribute and type()",
      propertyValue: "attr(data-x type(<length> | <percentage>))",
      attributes: {},
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute data-x is not set">` +
          `<span class="inspector-attr-name">data-x</span>` +
          ` type(<length> | <percentage>)` +
        `</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with known attribute, simple type and simple fallback",
      propertyValue: `attr(data-x raw-string, "fallback")`,
      attributes: { "data-x""12" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""12"">data-x</span>` +
          ` raw-string` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">"fallback"</span>` +
        `)`,
    },
    {
      message: "Modern attr() with known attribute, type() and simple fallback",
      propertyName: "width",
      propertyValue: `attr(data-x type(<length> | <percentage>), "fallback")`,
      attributes: { "data-x""10%" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""10%"">data-x</span>` +
          ` type(<length> | <percentage>)` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">"fallback"</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with unknown attribute, simple type and simple fallback",
      propertyValue: `attr(data-x raw-string, "fallback")`,
      attributes: {},
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute data-x is not set">` +
          `<span class="inspector-attr-name">data-x</span>` +
          ` raw-string` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback">"fallback"</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with unknown attribute, type() and simple fallback",
      propertyValue: `attr(data-x type(<length> | <percentage>), "fallback")`,
      attributes: {},
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute data-x is not set">` +
          `<span class="inspector-attr-name">data-x</span>` +
          ` type(<length> | <percentage>)` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback">"fallback"</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with known attribute, simple type and nested attr() fallback",
      propertyValue: `attr(data-x raw-string, attr(data-y, "fallback"))`,
      attributes: { "data-x""x""data-y""y" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""x"">data-x</span>` +
          ` raw-string` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">` +
          `attr(` +
          `<span class="inspector-attr-param">` +
            `<span class="inspector-attr-name" data-attribute=""y"">data-y</span>` +
          `</span>` +
          `, ` +
          `<span class="inspector-attr-fallback unmatched-class">"fallback"</span>` +
          `)` +
        `</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with known attribute, type() and nested attr() fallback",
      propertyValue: `attr(data-x type(<length> | <percentage>), attr(data-y, 300px))`,
      attributes: { "data-x""20em" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""20em"">data-x</span>` +
          ` type(<length> | <percentage>)` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">` +
          `attr(` +
          `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute data-y is not set">` +
            `<span class="inspector-attr-name">data-y</span>` +
          `</span>` +
          `, ` +
          `<span class="inspector-attr-fallback">300px</span>` +
          `)` +
        `</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with unknown attribute, simple type and nested attr() fallback",
      propertyValue: `attr(data-x raw-string, attr(data-y, "fallback"))`,
      attributes: { "data-y""y" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute data-x is not set">` +
          `<span class="inspector-attr-name">data-x</span>` +
          ` raw-string` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback">` +
          `attr(` +
          `<span class="inspector-attr-param">` +
            `<span class="inspector-attr-name" data-attribute=""y"">data-y</span>` +
          `</span>` +
          `, ` +
          `<span class="inspector-attr-fallback unmatched-class">"fallback"</span>` +
          `)` +
        `</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with unknown attribute, type() and nested attr() fallback",
      propertyValue: `attr(data-x type(<length> | <percentage>), attr(data-y, 99%))`,
      attributes: {},
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute data-x is not set">` +
          `<span class="inspector-attr-name">data-x</span>` +
          ` type(<length> | <percentage>)` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback">` +
          `attr(` +
          `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute data-y is not set">` +
            `<span class="inspector-attr-name">data-y</span>` +
          `</span>` +
          `, ` +
          `<span class="inspector-attr-fallback">99%</span>` +
          `)` +
        `</span>` +
        `)`,
    },
    {
      message: "Modern attr() with `number` attr type and value matching type",
      propertyName: "line-height",
      propertyValue: `attr(data-x number, 2)`,
      attributes: { "data-x""3" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""3"">data-x</span>` +
          ` number` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">2</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with `number` attr type and value not matching type",
      propertyName: "line-height",
      propertyValue: `attr(data-x number, 2)`,
      attributes: { "data-x""x" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute value ("x") is not a valid numeric value">` +
          `<span class="inspector-attr-name">data-x</span>` +
          ` number` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback">2</span>` +
        `)`,
    },
    {
      message: "Modern attr() with `deg` attr type and value matching type",
      propertyName: "rotate",
      propertyValue: `attr(data-x deg, 90deg)`,
      attributes: { "data-x""1.5" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""1.5"">data-x</span>` +
          ` deg` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">` +
          `<span data-angle="90deg"><span>90deg</span></span>` +
        `</span>` +
        `)`,
    },
    {
      message: "Modern attr() with `deg` attr type and value not matching type",
      propertyName: "rotate",
      propertyValue: `attr(data-x deg, 90deg)`,
      attributes: { "data-x""x" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute value ("x") is not a valid numeric value">` +
          `<span class="inspector-attr-name">data-x</span>` +
          ` deg` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback">` +
          `<span data-angle="90deg"><span>90deg</span></span>` +
        `</span>` +
        `)`,
    },
    {
      message: "Modern attr() with `%` attr type and value matching type",
      propertyName: "height",
      propertyValue: `attr(data-x %, 50%)`,
      attributes: { "data-x""99" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""99"">data-x</span>` +
          ` %` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">50%</span>` +
        `)`,
    },
    {
      message: "Modern attr() with `%` attr type and value not matching type",
      propertyName: "height",
      propertyValue: `attr(data-x %, 50%)`,
      attributes: { "data-x""20%" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute value ("20%") is not a valid numeric value">` +
          `<span class="inspector-attr-name">data-x</span>` +
          ` %` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback">50%</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with `type(<color>)` attr type and value matching type",
      propertyName: "color",
      propertyValue: `attr(data-x type(<color>), hotpink)`,
      attributes: { "data-x""tomato" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""tomato"">data-x</span>` +
          ` type(<color>)` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">` +
          `<span data-color="hotpink"><span>hotpink</span></span>` +
        `</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with `type(<color>)` attr type and value not matching type",
      propertyName: "color",
      propertyValue: `attr(data-x type(<color>), hotpink)`,
      attributes: { "data-x""uwu" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute value ("uwu") does not match expected "<color>" syntax">` +
          `<span class="inspector-attr-name">data-x</span>` +
          ` type(<color>)` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback">` +
          `<span data-color="hotpink"><span>hotpink</span></span>` +
        `</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with `type(<length> | <percentage>)` attr type and a length value",
      propertyName: "width",
      propertyValue: `attr(data-x type(<length> | <percentage>), 33px)`,
      attributes: { "data-x""42em" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""42em"">data-x</span>` +
          ` type(<length> | <percentage>)` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">33px</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with `type(<length> | <percentage>)` attr type and a percentage value",
      propertyName: "width",
      propertyValue: `attr(data-x type(<length> | <percentage>), 33px)`,
      attributes: { "data-x""42%" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""42%"">data-x</span>` +
          ` type(<length> | <percentage>)` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">33px</span>` +
        `)`,
    },
    {
      message:
        "Modern attr() with `type(<length> | <percentage>)` attr type and value not matching type",
      propertyName: "width",
      propertyValue: `attr(data-x type(<length> | <percentage>), 33px)`,
      attributes: { "data-x""42" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param unmatched-class" data-attribute="Attribute value ("42") does not match expected "<length> | <percentage>" syntax">` +
          `<span class="inspector-attr-name">data-x</span>` +
          ` type(<length> | <percentage>)` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback">33px</span>` +
        `)`,
    },
    {
      message: "Modern attr() with `type(<custom-ident>)` attr type",
      propertyName: "position-anchor",
      propertyValue: `attr(data-x type(<custom-ident>), match-parent)`,
      attributes: { "data-x""--my-anchor" },
      // prettier-ignore
      expected:
        `attr(` +
        `<span class="inspector-attr-param">` +
          `<span class="inspector-attr-name" data-attribute=""--my-anchor"">data-x</span>` +
          ` type(<custom-ident>)` +
        `</span>` +
        `, ` +
        `<span class="inspector-attr-fallback unmatched-class">match-parent</span>` +
        `)`,
    },
  ];

  for (const test of TESTS) {
    const frag = parser.parseCssProperty(
      test.propertyName || "content",
      test.propertyValue,
      {
        unmatchedClass: "unmatched-class",
        getAttributeValue:
          "getAttributeValue" in test
            ? test.getAttributeValue
            : attrName => test.attributes[attrName] ?? null,
      }
    );

    const target = doc.querySelector("div");
    target.appendChild(frag);

    is(target.innerHTML, test.expected, test.message);
    target.innerHTML = "";
  }
}

function testParseFunctionsForCssExplainers(doc, parser) {
  const TESTS = [
    {
      message:
        "No custom elements and attributes when cssExplainersEnabled is false",
      propertyName: "width",
      propertyValue: "calc(10px + 1em)",
      cssExplainersEnabled: false,
      expected: `calc(10px + 1em)`,
    },
    {
      message:
        "No custom elements and attributes when parsing a function that isn't supported",
      propertyName: "content",
      propertyValue: "counter(count, decimal)",
      cssExplainersEnabled: true,
      expected: `counter(count, decimal)`,
    },
    {
      message: "Custom elements and attributes when parsing calc()",
      propertyName: "width",
      propertyValue: "calc(10px + 1em)",
      cssExplainersEnabled: true,
      // prettier-ignore
      expected:
        `<span data-function-expression="calc(10px + 1em)">` +
          `<span class="css-explainers-function-name">calc</span>` +
          `(10px + 1em)` +
        `</span>`,
    },
    {
      message: "Custom elements and attributes when parsing nested functions",
      propertyName: "transform",
      propertyValue: "translateY(calc(10px + round(up, sin(40deg)) * 1px))",
      cssExplainersEnabled: true,
      // prettier-ignore
      expected:
        `translateY(` +
        `<span data-function-expression="calc(10px + round(up, sin(40deg)) * 1px)">` +
          `<span class="css-explainers-function-name">calc</span>` +
          `(` +
            `10px + ` +
            `<span data-function-expression="round(up, sin(40deg))">` +
              `<span class="css-explainers-function-name">round</span>` +
              `(` +
                `up, ` +
                `<span data-function-expression="sin(40deg)">` +
                  `<span class="css-explainers-function-name">sin</span>` +
                  `(` +
                    `<span data-angle="40deg">` +
                      `<span>40deg</span>` +
                    `</span>` +
                  `)` +
                `</span>` +
              `)` +
            `</span>` +
            ` * 1px` +
          `)` +
        `</span>` +
        `)`,
    },
    {
      message: "Custom elements and attributes when using var() and attr()",
      propertyName: "width",
      propertyValue: "calc(var(--x, attr(data-x px, 16px)))",
      cssExplainersEnabled: true,
      attributes: { "data-x""20" },
      // prettier-ignore
      expected:
        `<span data-function-expression="calc(var(--x, attr(data-x px, 16px)))">` +
          `<span class="css-explainers-function-name">calc</span>` +
          `(` +
            `<span data-function-expression="var(--x, attr(data-x px, 16px))">` +
              `<span>` +
                `<span class="css-explainers-function-name">var</span>` +
                `(` +
                `<span data-variable="--x is not set">--x</span>` +
                `,` +
                `<span>` +
                  ` ` +
                  `<span data-function-expression="attr(data-x px, 16px)">` +
                    `<span class="css-explainers-function-name">attr</span>` +
                    `(` +
                      `<span class="inspector-attr-param">` +
                        `<span class="inspector-attr-name" data-attribute=""20"">data-x</span>` +
                        ` px` +
                      `</span>` +
                      `, ` +
                      `<span class="inspector-attr-fallback null">16px</span>` +
                    `)` +
                  `</span>` +
                `</span>` +
                `)` +
              `</span>` +
            `</span>` +
          `)` +
        `</span>`,
    },
    {
      message: "Custom elements and attributes when using parenthesis block",
      propertyName: "opacity",
      propertyValue: "calc(1 - (var(--x) - 0.8rem))",
      cssExplainersEnabled: true,
      variables: { "--x""2em" },
      expected:
        // prettier-ignore
        `<span data-function-expression="calc(1 - (var(--x) - 0.8rem))">` +
          `<span class="css-explainers-function-name">calc</span>` +
          `(` +
          `1 - (` +
          `<span data-function-expression="var(--x)">` +
            `<span>` +
              `<span class="css-explainers-function-name">var</span>` +
              `(` +
              `<span data-variable="2em">` +
                `--x` +
                `<button class="ruleview-variable-link jump-definition" data-variable-name="--x" title="Jump to variable definition"></button>` +
              `</span>` +
              `)` +
            `</span>` +
          `</span>` +
          ` - 0.8rem` +
          `)` +
          `)` +
        `</span>`,
    },
    {
      message:
        "No data-function-expression attribute when a nested function isn't supported",
      propertyName: "width",
      propertyValue:
        "calc(10px + anchor-size(--my-anchor width, calc(50% + 10vw)))",
      cssExplainersEnabled: true,
      // prettier-ignore
      expected:
        `<span class="css-explainers-function-name">calc</span>(` +
          `10px + ` +
          `anchor-size(` +
            `--my-anchor width, ` +
            `<span data-function-expression="calc(50% + 10vw)">` +
              `<span class="css-explainers-function-name">calc</span>` +
              `(50% + 10vw)` +
            `</span>` +
          `)` +
        `)`,
    },
  ];

  const target = doc.querySelector("div");
  for (const test of TESTS) {
    const frag = parser.parseCssProperty(
      test.propertyName,
      test.propertyValue,
      {
        cssExplainersEnabled: test.cssExplainersEnabled,
        getAttributeValue:
          "getAttributeValue" in test
            ? test.getAttributeValue
            : attrName => test.attributes[attrName] ?? null,
        getVariableData: varName => {
          if (typeof test.variables?.[varName] === "string") {
            return { value: test.variables[varName] };
          }

          return test.variables?.[varName] || {};
        },
      }
    );

    target.replaceChildren(frag);

    is(target.innerHTML, test.expected, test.message);
  }
  target.innerHTML = "";
}

Messung V0.5 in Prozent
C=95 H=96 G=95

¤ Diese beiden folgenden Angebotsgruppen bietet das Unternehmen0.78Angebot  ¤

*Eine klare Vorstellung vom Zielzustand






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.