Bug 1890689 accumulate input in LargerReceiverBlockSizeThanDesiredBuffering GTest...
[gecko.git] / devtools / client / shared / test / browser_outputparser.js
blob7e0c1b7e00c696199943c9a104ef052959257106
1 /* Any copyright is dedicated to the Public Domain.
2    http://creativecommons.org/publicdomain/zero/1.0/ */
4 "use strict";
6 add_task(async function () {
7   await pushPref("layout.css.backdrop-filter.enabled", true);
8   await pushPref("layout.css.individual-transform.enabled", true);
9   await addTab("about:blank");
10   await performTest();
11   gBrowser.removeCurrentTab();
12 });
14 async function performTest() {
15   await SpecialPowers.pushPrefEnv({
16     set: [["security.allow_unsafe_parent_loads", true]],
17   });
19   const OutputParser = require("resource://devtools/client/shared/output-parser.js");
21   const { host, doc } = await createHost(
22     "bottom",
23     "data:text/html," + "<h1>browser_outputParser.js</h1><div></div>"
24   );
26   const cssProperties = getClientCssProperties();
28   const parser = new OutputParser(doc, cssProperties);
29   testParseCssProperty(doc, parser);
30   testParseCssVar(doc, parser);
31   testParseURL(doc, parser);
32   testParseFilter(doc, parser);
33   testParseBackdropFilter(doc, parser);
34   testParseAngle(doc, parser);
35   testParseShape(doc, parser);
36   testParseVariable(doc, parser);
37   testParseColorVariable(doc, parser);
38   testParseFontFamily(doc, parser);
40   host.destroy();
43 // Class name used in color swatch.
44 var COLOR_TEST_CLASS = "test-class";
46 // Create a new CSS color-parsing test.  |name| is the name of the CSS
47 // property.  |value| is the CSS text to use.  |segments| is an array
48 // describing the expected result.  If an element of |segments| is a
49 // string, it is simply appended to the expected string.  Otherwise,
50 // it must be an object with a |name| property, which is the color
51 // name as it appears in the input.
53 // This approach is taken to reduce boilerplate and to make it simpler
54 // to modify the test when the parseCssProperty output changes.
55 function makeColorTest(name, value, segments) {
56   const result = {
57     name,
58     value,
59     expected: "",
60   };
62   for (const segment of segments) {
63     if (typeof segment === "string") {
64       result.expected += segment;
65     } else {
66       const buttonAttributes = {
67         class: COLOR_TEST_CLASS,
68         style: `background-color:${segment.name}`,
69         tabindex: 0,
70         role: "button",
71       };
72       if (segment.colorFunction) {
73         buttonAttributes["data-color-function"] = segment.colorFunction;
74       }
75       const buttonAttrString = Object.entries(buttonAttributes)
76         .map(([attr, v]) => `${attr}="${v}"`)
77         .join(" ");
79       // prettier-ignore
80       result.expected +=
81         `<span data-color="${segment.name}">` +
82           `<span ${buttonAttrString}></span>`+
83           `<span>${segment.name}</span>` +
84         `</span>`;
85     }
86   }
88   result.desc = "Testing " + name + ": " + value;
90   return result;
93 function testParseCssProperty(doc, parser) {
94   const tests = [
95     makeColorTest("border", "1px solid red", ["1px solid ", { name: "red" }]),
97     makeColorTest(
98       "background-image",
99       "linear-gradient(to right, #F60 10%, rgba(0,0,0,1))",
100       [
101         "linear-gradient(to right, ",
102         { name: "#F60", colorFunction: "linear-gradient" },
103         " 10%, ",
104         { name: "rgba(0,0,0,1)", colorFunction: "linear-gradient" },
105         ")",
106       ]
107     ),
109     // In "arial black", "black" is a font, not a color.
110     // (The font-family parser creates a span)
111     makeColorTest("font-family", "arial black", ["<span>arial black</span>"]),
113     makeColorTest("box-shadow", "0 0 1em red", ["0 0 1em ", { name: "red" }]),
115     makeColorTest("box-shadow", "0 0 1em red, 2px 2px 0 0 rgba(0,0,0,.5)", [
116       "0 0 1em ",
117       { name: "red" },
118       ", 2px 2px 0 0 ",
119       { name: "rgba(0,0,0,.5)" },
120     ]),
122     makeColorTest("content", '"red"', ['"red"']),
124     // Invalid property names should not cause exceptions.
125     makeColorTest("hellothere", "'red'", ["'red'"]),
127     makeColorTest(
128       "filter",
129       "blur(1px) drop-shadow(0 0 0 blue) url(red.svg#blue)",
130       [
131         '<span data-filters="blur(1px) drop-shadow(0 0 0 blue) ',
132         'url(red.svg#blue)"><span>',
133         "blur(1px) drop-shadow(0 0 0 ",
134         { name: "blue", colorFunction: "drop-shadow" },
135         ") url(red.svg#blue)</span></span>",
136       ]
137     ),
139     makeColorTest("color", "currentColor", ["currentColor"]),
141     // Test a very long property.
142     makeColorTest(
143       "background-image",
144       "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%)",
145       [
146         "linear-gradient(to left, ",
147         { name: "transparent", colorFunction: "linear-gradient" },
148         " 0, ",
149         { name: "transparent", colorFunction: "linear-gradient" },
150         " 5%,",
151         { name: "#F00", colorFunction: "linear-gradient" },
152         " 0, ",
153         { name: "#F00", colorFunction: "linear-gradient" },
154         " 10%,",
155         { name: "#FF0", colorFunction: "linear-gradient" },
156         " 0, ",
157         { name: "#FF0", colorFunction: "linear-gradient" },
158         " 15%,",
159         { name: "#0F0", colorFunction: "linear-gradient" },
160         " 0, ",
161         { name: "#0F0", colorFunction: "linear-gradient" },
162         " 20%,",
163         { name: "#0FF", colorFunction: "linear-gradient" },
164         " 0, ",
165         { name: "#0FF", colorFunction: "linear-gradient" },
166         " 25%,",
167         { name: "#00F", colorFunction: "linear-gradient" },
168         " 0, ",
169         { name: "#00F", colorFunction: "linear-gradient" },
170         " 30%,",
171         { name: "#800", colorFunction: "linear-gradient" },
172         " 0, ",
173         { name: "#800", colorFunction: "linear-gradient" },
174         " 35%,",
175         { name: "#880", colorFunction: "linear-gradient" },
176         " 0, ",
177         { name: "#880", colorFunction: "linear-gradient" },
178         " 40%,",
179         { name: "#080", colorFunction: "linear-gradient" },
180         " 0, ",
181         { name: "#080", colorFunction: "linear-gradient" },
182         " 45%,",
183         { name: "#088", colorFunction: "linear-gradient" },
184         " 0, ",
185         { name: "#088", colorFunction: "linear-gradient" },
186         " 50%,",
187         { name: "#008", colorFunction: "linear-gradient" },
188         " 0, ",
189         { name: "#008", colorFunction: "linear-gradient" },
190         " 55%,",
191         { name: "#FFF", colorFunction: "linear-gradient" },
192         " 0, ",
193         { name: "#FFF", colorFunction: "linear-gradient" },
194         " 60%,",
195         { name: "#EEE", colorFunction: "linear-gradient" },
196         " 0, ",
197         { name: "#EEE", colorFunction: "linear-gradient" },
198         " 65%,",
199         { name: "#CCC", colorFunction: "linear-gradient" },
200         " 0, ",
201         { name: "#CCC", colorFunction: "linear-gradient" },
202         " 70%,",
203         { name: "#999", colorFunction: "linear-gradient" },
204         " 0, ",
205         { name: "#999", colorFunction: "linear-gradient" },
206         " 75%,",
207         { name: "#666", colorFunction: "linear-gradient" },
208         " 0, ",
209         { name: "#666", colorFunction: "linear-gradient" },
210         " 80%,",
211         { name: "#333", colorFunction: "linear-gradient" },
212         " 0, ",
213         { name: "#333", colorFunction: "linear-gradient" },
214         " 85%,",
215         { name: "#111", colorFunction: "linear-gradient" },
216         " 0, ",
217         { name: "#111", colorFunction: "linear-gradient" },
218         " 90%,",
219         { name: "#000", colorFunction: "linear-gradient" },
220         " 0, ",
221         { name: "#000", colorFunction: "linear-gradient" },
222         " 95%,",
223         { name: "transparent", colorFunction: "linear-gradient" },
224         " 0, ",
225         { name: "transparent", colorFunction: "linear-gradient" },
226         " 100%)",
227       ]
228     ),
230     // Note the lack of a space before the color here.
231     makeColorTest("border", "1px dotted#f06", [
232       "1px dotted ",
233       { name: "#f06" },
234     ]),
236     makeColorTest("color", "color-mix(in srgb, red, blue)", [
237       "color-mix(in srgb, ",
238       { name: "red", colorFunction: "color-mix" },
239       ", ",
240       { name: "blue", colorFunction: "color-mix" },
241       ")",
242     ]),
244     makeColorTest(
245       "background-image",
246       "linear-gradient(to top, color-mix(in srgb, #008000, rgba(255, 255, 0, 0.9)), blue)",
247       [
248         "linear-gradient(to top, ",
249         "color-mix(in srgb, ",
250         { name: "#008000", colorFunction: "color-mix" },
251         ", ",
252         { name: "rgba(255, 255, 0, 0.9)", colorFunction: "color-mix" },
253         "), ",
254         { name: "blue", colorFunction: "linear-gradient" },
255         ")",
256       ]
257     ),
258   ];
260   const target = doc.querySelector("div");
261   ok(target, "captain, we have the div");
263   for (const test of tests) {
264     info(test.desc);
266     const frag = parser.parseCssProperty(test.name, test.value, {
267       colorSwatchClass: COLOR_TEST_CLASS,
268     });
270     target.appendChild(frag);
272     is(
273       target.innerHTML,
274       test.expected,
275       "CSS property correctly parsed for " + test.name + ": " + test.value
276     );
278     target.innerHTML = "";
279   }
282 function testParseCssVar(doc, parser) {
283   const frag = parser.parseCssProperty("color", "var(--some-kind-of-green)", {
284     colorSwatchClass: "test-colorswatch",
285   });
287   const target = doc.querySelector("div");
288   ok(target, "captain, we have the div");
289   target.appendChild(frag);
291   is(
292     target.innerHTML,
293     "var(--some-kind-of-green)",
294     "CSS property correctly parsed"
295   );
297   target.innerHTML = "";
300 function testParseURL(doc, parser) {
301   info("Test that URL parsing preserves quoting style");
303   const tests = [
304     {
305       desc: "simple test without quotes",
306       leader: "url(",
307       trailer: ")",
308     },
309     {
310       desc: "simple test with single quotes",
311       leader: "url('",
312       trailer: "')",
313     },
314     {
315       desc: "simple test with double quotes",
316       leader: 'url("',
317       trailer: '")',
318     },
319     {
320       desc: "test with single quotes and whitespace",
321       leader: "url( \t'",
322       trailer: "'\r\n\f)",
323     },
324     {
325       desc: "simple test with uppercase",
326       leader: "URL(",
327       trailer: ")",
328     },
329     {
330       desc: "bad url, missing paren",
331       leader: "url(",
332       trailer: "",
333       expectedTrailer: ")",
334     },
335     {
336       desc: "bad url, missing paren, with baseURI",
337       baseURI: "data:text/html,<style></style>",
338       leader: "url(",
339       trailer: "",
340       expectedTrailer: ")",
341     },
342     {
343       desc: "bad url, double quote, missing paren",
344       leader: 'url("',
345       trailer: '"',
346       expectedTrailer: '")',
347     },
348     {
349       desc: "bad url, single quote, missing paren and quote",
350       leader: "url('",
351       trailer: "",
352       expectedTrailer: "')",
353     },
354   ];
356   for (const test of tests) {
357     const url = test.leader + "something.jpg" + test.trailer;
358     const frag = parser.parseCssProperty("background", url, {
359       urlClass: "test-urlclass",
360       baseURI: test.baseURI,
361     });
363     const target = doc.querySelector("div");
364     target.appendChild(frag);
366     const expectedTrailer = test.expectedTrailer || test.trailer;
368     const expected =
369       test.leader +
370       '<a target="_blank" class="test-urlclass" ' +
371       'href="something.jpg">something.jpg</a>' +
372       expectedTrailer;
374     is(target.innerHTML, expected, test.desc);
376     target.innerHTML = "";
377   }
380 function testParseFilter(doc, parser) {
381   const frag = parser.parseCssProperty("filter", "something invalid", {
382     filterSwatchClass: "test-filterswatch",
383   });
385   const swatchCount = frag.querySelectorAll(".test-filterswatch").length;
386   is(swatchCount, 1, "filter swatch was created");
389 function testParseBackdropFilter(doc, parser) {
390   const frag = parser.parseCssProperty("backdrop-filter", "something invalid", {
391     filterSwatchClass: "test-filterswatch",
392   });
394   const swatchCount = frag.querySelectorAll(".test-filterswatch").length;
395   is(swatchCount, 1, "filter swatch was created for backdrop-filter");
398 function testParseAngle(doc, parser) {
399   let frag = parser.parseCssProperty("rotate", "90deg", {
400     angleSwatchClass: "test-angleswatch",
401   });
403   let swatchCount = frag.querySelectorAll(".test-angleswatch").length;
404   is(swatchCount, 1, "angle swatch was created");
406   frag = parser.parseCssProperty(
407     "background-image",
408     "linear-gradient(90deg, red, blue",
409     {
410       angleSwatchClass: "test-angleswatch",
411     }
412   );
414   swatchCount = frag.querySelectorAll(".test-angleswatch").length;
415   is(swatchCount, 1, "angle swatch was created");
418 function testParseShape(doc, parser) {
419   info("Test shape parsing");
421   const tests = [
422     {
423       desc: "Polygon shape",
424       definition:
425         "polygon(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n " +
426         "12em var(--variable), 100% 100%) margin-box",
427       spanCount: 18,
428     },
429     {
430       desc: "POLYGON()",
431       definition:
432         "POLYGON(evenodd, 0px 0px, 10%200px,30%30% , calc(250px - 10px) 0 ,\n " +
433         "12em var(--variable), 100% 100%) margin-box",
434       spanCount: 18,
435     },
436     {
437       desc: "Invalid polygon shape",
438       definition: "polygon(0px 0px 100px 20px, 20% 20%)",
439       spanCount: 0,
440     },
441     {
442       desc: "Circle shape with all arguments",
443       definition: "circle(25% at\n 30% 200px) border-box",
444       spanCount: 4,
445     },
446     {
447       desc: "Circle shape with only one center",
448       definition: "circle(25em at 40%)",
449       spanCount: 3,
450     },
451     {
452       desc: "Circle shape with no radius",
453       definition: "circle(at 30% 40%)",
454       spanCount: 3,
455     },
456     {
457       desc: "Circle shape with no center",
458       definition: "circle(12em)",
459       spanCount: 1,
460     },
461     {
462       desc: "Circle shape with no arguments",
463       definition: "circle()",
464       spanCount: 0,
465     },
466     {
467       desc: "Circle shape with no space before at",
468       definition: "circle(25%at 30% 30%)",
469       spanCount: 4,
470     },
471     {
472       desc: "CIRCLE",
473       definition: "CIRCLE(12em)",
474       spanCount: 1,
475     },
476     {
477       desc: "Invalid circle shape",
478       definition: "circle(25%at30%30%)",
479       spanCount: 0,
480     },
481     {
482       desc: "Ellipse shape with all arguments",
483       definition: "ellipse(200px 10em at 25% 120px) content-box",
484       spanCount: 5,
485     },
486     {
487       desc: "Ellipse shape with only one center",
488       definition: "ellipse(200px 10% at 120px)",
489       spanCount: 4,
490     },
491     {
492       desc: "Ellipse shape with no radius",
493       definition: "ellipse(at 25% 120px)",
494       spanCount: 3,
495     },
496     {
497       desc: "Ellipse shape with no center",
498       definition: "ellipse(200px\n10em)",
499       spanCount: 2,
500     },
501     {
502       desc: "Ellipse shape with no arguments",
503       definition: "ellipse()",
504       spanCount: 0,
505     },
506     {
507       desc: "ELLIPSE()",
508       definition: "ELLIPSE(200px 10em)",
509       spanCount: 2,
510     },
511     {
512       desc: "Invalid ellipse shape",
513       definition: "ellipse(200px100px at 30$ 20%)",
514       spanCount: 0,
515     },
516     {
517       desc: "Inset shape with 4 arguments",
518       definition: "inset(200px 100px\n 30%15%)",
519       spanCount: 4,
520     },
521     {
522       desc: "Inset shape with 3 arguments",
523       definition: "inset(200px 100px 15%)",
524       spanCount: 3,
525     },
526     {
527       desc: "Inset shape with 2 arguments",
528       definition: "inset(200px 100px)",
529       spanCount: 2,
530     },
531     {
532       desc: "Inset shape with 1 argument",
533       definition: "inset(200px)",
534       spanCount: 1,
535     },
536     {
537       desc: "Inset shape with 0 arguments",
538       definition: "inset()",
539       spanCount: 0,
540     },
541     {
542       desc: "INSET()",
543       definition: "INSET(200px)",
544       spanCount: 1,
545     },
546     {
547       desc: "offset-path property with inset shape value",
548       property: "offset-path",
549       definition: "inset(200px)",
550       spanCount: 1,
551     },
552   ];
554   for (const { desc, definition, property = "clip-path", spanCount } of tests) {
555     info(desc);
556     const frag = parser.parseCssProperty(property, definition, {
557       shapeClass: "ruleview-shape",
558     });
559     const spans = frag.querySelectorAll(".ruleview-shape-point");
560     is(spans.length, spanCount, desc + " span count");
561     is(frag.textContent, definition, desc + " text content");
562   }
565 function testParseVariable(doc, parser) {
566   const TESTS = [
567     {
568       text: "var(--seen)",
569       variables: { "--seen": "chartreuse" },
570       expected:
571         // prettier-ignore
572         '<span data-color="chartreuse">' +
573           "<span>var(" +
574             '<span data-variable="--seen = chartreuse">--seen</span>)' +
575           "</span>" +
576         "</span>",
577     },
578     {
579       text: "var(--not-seen)",
580       variables: {},
581       expected:
582         // prettier-ignore
583         "<span>var(" +
584           '<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>' +
585         ")</span>",
586     },
587     {
588       text: "var(--seen, seagreen)",
589       variables: { "--seen": "chartreuse" },
590       expected:
591         // prettier-ignore
592         '<span data-color="chartreuse">' +
593           "<span>var(" +
594             '<span data-variable="--seen = chartreuse">--seen</span>,' +
595             '<span class="unmatched-class"> ' +
596               '<span data-color="seagreen">' +
597                 "<span>seagreen</span>" +
598               "</span>" +
599             "</span>)" +
600           "</span>" +
601         "</span>",
602     },
603     {
604       text: "var(--not-seen, var(--seen))",
605       variables: { "--seen": "chartreuse" },
606       expected:
607         // prettier-ignore
608         "<span>var(" +
609           '<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>,' +
610           "<span> " +
611             '<span data-color="chartreuse">' +
612               "<span>var(" +
613                 '<span data-variable="--seen = chartreuse">--seen</span>)' +
614               "</span>" +
615             "</span>" +
616           "</span>)" +
617         "</span>",
618     },
619     {
620       text: "color-mix(in sgrb, var(--x), purple)",
621       variables: { "--x": "yellow" },
622       expected:
623         // prettier-ignore
624         `color-mix(in sgrb, ` +
625         `<span data-color="yellow">` +
626           `<span class="test-class" style="background-color:yellow" tabindex="0" role="button" data-color-function="color-mix">` +
627           `</span>` +
628           `<span>var(<span data-variable="--x = yellow">--x</span>)</span>` +
629         `</span>` +
630         `, ` +
631         `<span data-color="purple">` +
632           `<span class="test-class" style="background-color:purple" tabindex="0" role="button" data-color-function="color-mix">` +
633           `</span>` +
634           `<span>purple</span>` +
635         `</span>` +
636         `)`,
637       parserExtraOptions: {
638         colorSwatchClass: COLOR_TEST_CLASS,
639       },
640     },
641     {
642       text: "1px solid var(--seen, seagreen)",
643       variables: { "--seen": "chartreuse" },
644       expected:
645         // prettier-ignore
646         '1px solid ' +
647         '<span data-color="chartreuse">' +
648           "<span>var(" +
649             '<span data-variable="--seen = chartreuse">--seen</span>,' +
650             '<span class="unmatched-class"> ' +
651               '<span data-color="seagreen">' +
652                 "<span>seagreen</span>" +
653               "</span>" +
654             "</span>)" +
655           "</span>" +
656         "</span>",
657     },
658     {
659       text: "1px solid var(--not-seen, seagreen)",
660       variables: {},
661       expected:
662         // prettier-ignore
663         `1px solid ` +
664         `<span>var(` +
665           `<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>,` +
666           `<span> ` +
667             `<span data-color="seagreen">` +
668               `<span>seagreen</span>` +
669             `</span>` +
670           `</span>)` +
671         `</span>`,
672     },
673     {
674       text: "rgba(var(--r), 0, 0, var(--a))",
675       variables: { "--r": "255", "--a": "0.5" },
676       expected:
677         // prettier-ignore
678         '<span data-color="rgba(255, 0, 0, 0.5)">' +
679           "<span>rgba("+
680             "<span>" +
681               'var(<span data-variable="--r = 255">--r</span>)' +
682             "</span>, 0, 0, " +
683             "<span>" +
684               'var(<span data-variable="--a = 0.5">--a</span>)' +
685             "</span>" +
686           ")</span>" +
687         "</span>",
688     },
689     {
690       text: "rgb(var(--not-seen, 255), 0, 0)",
691       variables: {},
692       expected:
693         // prettier-ignore
694         '<span data-color="rgb( 255, 0, 0)">' +
695           "<span>rgb("+
696             "<span>var(" +
697               `<span class="unmatched-class" data-variable="--not-seen is not set">--not-seen</span>,` +
698               `<span> 255</span>` +
699             ")</span>, 0, 0" +
700           ")</span>" +
701         "</span>",
702     },
703   ];
705   for (const test of TESTS) {
706     const getValue = function (varName) {
707       return test.variables[varName];
708     };
710     const frag = parser.parseCssProperty("color", test.text, {
711       getVariableValue: getValue,
712       unmatchedVariableClass: "unmatched-class",
713       ...(test.parserExtraOptions || {}),
714     });
716     const target = doc.querySelector("div");
717     target.appendChild(frag);
719     is(target.innerHTML, test.expected, test.text);
720     target.innerHTML = "";
721   }
724 function testParseColorVariable(doc, parser) {
725   const testCategories = [
726     {
727       desc: "Test for CSS variable defining color",
728       tests: [
729         makeColorTest("--test-var", "lime", [{ name: "lime" }]),
730         makeColorTest("--test-var", "#000", [{ name: "#000" }]),
731       ],
732     },
733     {
734       desc: "Test for CSS variable not defining color",
735       tests: [
736         makeColorTest("--foo", "something", ["something"]),
737         makeColorTest("--bar", "Arial Black", ["Arial Black"]),
738         makeColorTest("--baz", "10vmin", ["10vmin"]),
739       ],
740     },
741     {
742       desc: "Test for non CSS variable defining color",
743       tests: [
744         makeColorTest("non-css-variable", "lime", ["lime"]),
745         makeColorTest("-non-css-variable", "#000", ["#000"]),
746       ],
747     },
748   ];
750   for (const category of testCategories) {
751     info(category.desc);
753     for (const test of category.tests) {
754       info(test.desc);
755       const target = doc.querySelector("div");
757       const frag = parser.parseCssProperty(test.name, test.value, {
758         colorSwatchClass: COLOR_TEST_CLASS,
759       });
761       target.appendChild(frag);
763       is(
764         target.innerHTML,
765         test.expected,
766         `The parsed result for '${test.name}: ${test.value}' is correct`
767       );
769       target.innerHTML = "";
770     }
771   }
774 function testParseFontFamily(doc, parser) {
775   info("Test font-family parsing");
776   const tests = [
777     {
778       desc: "No fonts",
779       definition: "",
780       families: [],
781     },
782     {
783       desc: "List of fonts",
784       definition: "Arial,Helvetica,sans-serif",
785       families: ["Arial", "Helvetica", "sans-serif"],
786     },
787     {
788       desc: "Fonts with spaces",
789       definition: "Open Sans",
790       families: ["Open Sans"],
791     },
792     {
793       desc: "Quoted fonts",
794       definition: "\"Arial\",'Open Sans'",
795       families: ["Arial", "Open Sans"],
796     },
797     {
798       desc: "Fonts with extra whitespace",
799       definition: " Open  Sans  ",
800       families: ["Open Sans"],
801     },
802   ];
804   const textContentTests = [
805     {
806       desc: "No whitespace between fonts",
807       definition: "Arial,Helvetica,sans-serif",
808       output: "Arial,Helvetica,sans-serif",
809     },
810     {
811       desc: "Whitespace between fonts",
812       definition: "Arial ,  Helvetica,   sans-serif",
813       output: "Arial , Helvetica, sans-serif",
814     },
815     {
816       desc: "Whitespace before first font trimmed",
817       definition: "  Arial,Helvetica,sans-serif",
818       output: "Arial,Helvetica,sans-serif",
819     },
820     {
821       desc: "Whitespace after last font trimmed",
822       definition: "Arial,Helvetica,sans-serif  ",
823       output: "Arial,Helvetica,sans-serif",
824     },
825     {
826       desc: "Whitespace between quoted fonts",
827       definition: "'Arial' ,  \"Helvetica\" ",
828       output: "'Arial' , \"Helvetica\"",
829     },
830     {
831       desc: "Whitespace within font preserved",
832       definition: "'  Ari al '",
833       output: "'  Ari al '",
834     },
835   ];
837   for (const { desc, definition, families } of tests) {
838     info(desc);
839     const frag = parser.parseCssProperty("font-family", definition, {
840       fontFamilyClass: "ruleview-font-family",
841     });
842     const spans = frag.querySelectorAll(".ruleview-font-family");
844     is(spans.length, families.length, desc + " span count");
845     for (let i = 0; i < spans.length; i++) {
846       is(spans[i].textContent, families[i], desc + " span contents");
847     }
848   }
850   info("Test font-family text content");
851   for (const { desc, definition, output } of textContentTests) {
852     info(desc);
853     const frag = parser.parseCssProperty("font-family", definition, {});
854     is(frag.textContent, output, desc + " text content matches");
855   }
857   info("Test font-family with custom properties");
858   const frag = parser.parseCssProperty(
859     "font-family",
860     "var(--family, Georgia, serif)",
861     {
862       getVariableValue: () => {},
863       unmatchedVariableClass: "unmatched-class",
864       fontFamilyClass: "ruleview-font-family",
865     }
866   );
867   const target = doc.createElement("div");
868   target.appendChild(frag);
869   is(
870     target.innerHTML,
871     // prettier-ignore
872     `<span>var(` +
873       `<span class="unmatched-class" data-variable="--family is not set">` +
874         `--family` +
875       `</span>` +
876       `,` +
877       `<span> ` +
878         `<span class="ruleview-font-family">Georgia</span>` +
879         `, ` +
880         `<span class="ruleview-font-family">serif</span>` +
881       `</span>)` +
882     `</span>`,
883     "Got expected output for font-family with custom properties"
884   );