summaryrefslogtreecommitdiffstats
path: root/devtools/client/shared/test/browser_outputparser.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/shared/test/browser_outputparser.js')
-rw-r--r--devtools/client/shared/test/browser_outputparser.js856
1 files changed, 856 insertions, 0 deletions
diff --git a/devtools/client/shared/test/browser_outputparser.js b/devtools/client/shared/test/browser_outputparser.js
new file mode 100644
index 0000000000..226e0bb685
--- /dev/null
+++ b/devtools/client/shared/test/browser_outputparser.js
@@ -0,0 +1,856 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(async function () {
+ await pushPref("layout.css.backdrop-filter.enabled", true);
+ await pushPref("layout.css.individual-transform.enabled", true);
+ await pushPref("layout.css.motion-path-basic-shapes.enabled", true);
+ await addTab("about:blank");
+ await performTest();
+ gBrowser.removeCurrentTab();
+});
+
+async function performTest() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["security.allow_unsafe_parent_loads", true]],
+ });
+
+ 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);
+
+ 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 {
+ const buttonAttributes = {
+ class: COLOR_TEST_CLASS,
+ style: `background-color:${segment.name}`,
+ tabindex: 0,
+ role: "button",
+ };
+ if (segment.colorFunction) {
+ buttonAttributes["data-color-function"] = segment.colorFunction;
+ }
+ const buttonAttrString = Object.entries(buttonAttributes)
+ .map(([attr, v]) => `${attr}="${v}"`)
+ .join(" ");
+
+ // prettier-ignore
+ result.expected +=
+ `<span data-color="${segment.name}">` +
+ `<span ${buttonAttrString}></span>`+
+ `<span>${segment.name}</span>` +
+ `</span>`;
+ }
+ }
+
+ result.desc = "Testing " + name + ": " + value;
+
+ return result;
+}
+
+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" },
+ ]),
+
+ makeColorTest("color", "color-mix(in srgb, red, blue)", [
+ "color-mix(in srgb, ",
+ { name: "red", colorFunction: "color-mix" },
+ ", ",
+ { name: "blue", colorFunction: "color-mix" },
+ ")",
+ ]),
+
+ makeColorTest(
+ "background-image",
+ "linear-gradient(to top, color-mix(in srgb, #008000, rgba(255, 255, 0, 0.9)), blue)",
+ [
+ "linear-gradient(to top, ",
+ "color-mix(in srgb, ",
+ { name: "#008000", colorFunction: "color-mix" },
+ ", ",
+ { name: "rgba(255, 255, 0, 0.9)", colorFunction: "color-mix" },
+ "), ",
+ { name: "blue", colorFunction: "linear-gradient" },
+ ")",
+ ]
+ ),
+ ];
+
+ const target = doc.querySelector("div");
+ ok(target, "captain, we have the div");
+
+ for (const test of tests) {
+ info(test.desc);
+
+ const frag = parser.parseCssProperty(test.name, test.value, {
+ colorSwatchClass: COLOR_TEST_CLASS,
+ });
+
+ 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: "')",
+ },
+ ];
+
+ 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,
+ });
+
+ const target = doc.querySelector("div");
+ target.appendChild(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);
+
+ 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: "Polygon shape",
+ definition:
+ "polygon(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n " +
+ "12em var(--variable), 100% 100%) margin-box",
+ spanCount: 18,
+ },
+ {
+ desc: "POLYGON()",
+ definition:
+ "POLYGON(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n " +
+ "12em var(--variable), 100% 100%) margin-box",
+ spanCount: 18,
+ },
+ {
+ desc: "Invalid polygon shape",
+ definition: "polygon(0px 0px 100px 20px, 20% 20%)",
+ spanCount: 0,
+ },
+ {
+ desc: "Circle shape with all arguments",
+ definition: "circle(25% at\n 30% 200px) border-box",
+ spanCount: 4,
+ },
+ {
+ desc: "Circle shape with only one center",
+ definition: "circle(25em at 40%)",
+ spanCount: 3,
+ },
+ {
+ desc: "Circle shape with no radius",
+ definition: "circle(at 30% 40%)",
+ spanCount: 3,
+ },
+ {
+ desc: "Circle shape with no center",
+ definition: "circle(12em)",
+ spanCount: 1,
+ },
+ {
+ desc: "Circle shape with no arguments",
+ definition: "circle()",
+ spanCount: 0,
+ },
+ {
+ desc: "Circle shape with no space before at",
+ definition: "circle(25%at 30% 30%)",
+ spanCount: 4,
+ },
+ {
+ desc: "CIRCLE",
+ definition: "CIRCLE(12em)",
+ spanCount: 1,
+ },
+ {
+ desc: "Invalid circle shape",
+ definition: "circle(25%at30%30%)",
+ spanCount: 0,
+ },
+ {
+ desc: "Ellipse shape with all arguments",
+ definition: "ellipse(200px 10em at 25% 120px) content-box",
+ spanCount: 5,
+ },
+ {
+ desc: "Ellipse shape with only one center",
+ definition: "ellipse(200px 10% at 120px)",
+ spanCount: 4,
+ },
+ {
+ desc: "Ellipse shape with no radius",
+ definition: "ellipse(at 25% 120px)",
+ spanCount: 3,
+ },
+ {
+ desc: "Ellipse shape with no center",
+ definition: "ellipse(200px\n10em)",
+ spanCount: 2,
+ },
+ {
+ desc: "Ellipse shape with no arguments",
+ definition: "ellipse()",
+ spanCount: 0,
+ },
+ {
+ desc: "ELLIPSE()",
+ definition: "ELLIPSE(200px 10em)",
+ spanCount: 2,
+ },
+ {
+ desc: "Invalid ellipse shape",
+ definition: "ellipse(200px100px at 30$ 20%)",
+ spanCount: 0,
+ },
+ {
+ desc: "Inset shape with 4 arguments",
+ definition: "inset(200px 100px\n 30%15%)",
+ spanCount: 4,
+ },
+ {
+ desc: "Inset shape with 3 arguments",
+ definition: "inset(200px 100px 15%)",
+ spanCount: 3,
+ },
+ {
+ desc: "Inset shape with 2 arguments",
+ definition: "inset(200px 100px)",
+ spanCount: 2,
+ },
+ {
+ desc: "Inset shape with 1 argument",
+ definition: "inset(200px)",
+ spanCount: 1,
+ },
+ {
+ desc: "Inset shape with 0 arguments",
+ definition: "inset()",
+ spanCount: 0,
+ },
+ {
+ desc: "INSET()",
+ definition: "INSET(200px)",
+ spanCount: 1,
+ },
+ {
+ desc: "offset-path property with inset shape value",
+ property: "offset-path",
+ definition: "inset(200px)",
+ spanCount: 1,
+ },
+ ];
+
+ for (const { desc, definition, property = "clip-path", spanCount } of tests) {
+ info(desc);
+ const frag = parser.parseCssProperty(property, definition, {
+ shapeClass: "ruleview-shape",
+ });
+ const spans = frag.querySelectorAll(".ruleview-shape-point");
+ is(spans.length, spanCount, desc + " span count");
+ is(frag.textContent, definition, desc + " text content");
+ }
+}
+
+function testParseVariable(doc, parser) {
+ const TESTS = [
+ {
+ text: "var(--seen)",
+ variables: { "--seen": "chartreuse" },
+ expected:
+ // prettier-ignore
+ '<span data-color="chartreuse">' +
+ "<span>var(" +
+ '<span data-variable="--seen = chartreuse">--seen</span>)' +
+ "</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="--seen = chartreuse">--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>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="--seen = chartreuse">--seen</span>)' +
+ "</span>" +
+ "</span>" +
+ "</span>)" +
+ "</span>",
+ },
+ {
+ text: "color-mix(in sgrb, var(--x), purple)",
+ variables: { "--x": "yellow" },
+ expected:
+ // prettier-ignore
+ `color-mix(in sgrb, ` +
+ `<span data-color="yellow">` +
+ `<span class="test-class" style="background-color:yellow" tabindex="0" role="button" data-color-function="color-mix">` +
+ `</span>` +
+ `<span>var(<span data-variable="--x = yellow">--x</span>)</span>` +
+ `</span>` +
+ `, ` +
+ `<span data-color="purple">` +
+ `<span class="test-class" style="background-color:purple" tabindex="0" role="button" data-color-function="color-mix">` +
+ `</span>` +
+ `<span>purple</span>` +
+ `</span>` +
+ `)`,
+ parserExtraOptions: {
+ colorSwatchClass: COLOR_TEST_CLASS,
+ },
+ },
+ {
+ text: "1px solid var(--seen, seagreen)",
+ variables: { "--seen": "chartreuse" },
+ expected:
+ // prettier-ignore
+ '1px solid ' +
+ '<span data-color="chartreuse">' +
+ "<span>var(" +
+ '<span data-variable="--seen = chartreuse">--seen</span>,' +
+ '<span class="unmatched-class"> ' +
+ '<span data-color="seagreen">' +
+ "<span>seagreen</span>" +
+ "</span>" +
+ "</span>)" +
+ "</span>" +
+ "</span>",
+ },
+ {
+ text: "1px solid var(--not-seen, seagreen)",
+ variables: {},
+ expected:
+ // prettier-ignore
+ `1px solid ` +
+ `<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>`,
+ },
+ ];
+
+ for (const test of TESTS) {
+ const getValue = function (varName) {
+ return test.variables[varName];
+ };
+
+ const frag = parser.parseCssProperty("color", test.text, {
+ getVariableValue: getValue,
+ unmatchedVariableClass: "unmatched-class",
+ ...(test.parserExtraOptions || {}),
+ });
+
+ const target = doc.querySelector("div");
+ target.appendChild(frag);
+
+ is(target.innerHTML, test.expected, test.text);
+ target.innerHTML = "";
+ }
+}
+
+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",
+ "var(--family, Georgia, serif)",
+ {
+ getVariableValue: () => {},
+ unmatchedVariableClass: "unmatched-class",
+ fontFamilyClass: "ruleview-font-family",
+ }
+ );
+ const target = doc.createElement("div");
+ target.appendChild(frag);
+ is(
+ target.innerHTML,
+ // prettier-ignore
+ `<span>var(` +
+ `<span class="unmatched-class" data-variable="--family is not set">` +
+ `--family` +
+ `</span>` +
+ `,` +
+ `<span> ` +
+ `<span class="ruleview-font-family">Georgia</span>` +
+ `, ` +
+ `<span class="ruleview-font-family">serif</span>` +
+ `</span>)` +
+ `</span>`,
+ "Got expected output for font-family with custom properties"
+ );
+}