Skip to content

Commit f1d41cb

Browse files
committed
test(css): hold the printers to real framework stylesheets
The equivalence check ran over `configCases` and wpt, both written to exercise a rule rather than ship. The minifier comparisons already install twenty framework stylesheets, so they are read back as a corpus. It caught a defect in the comparison itself: a pixel ends in a channel, so a token written after a color read as part of it.
1 parent 9ab5580 commit f1d41cb

2 files changed

Lines changed: 129 additions & 3 deletions

File tree

‎test/helpers/syntaxEquivalence.js‎

Lines changed: 95 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,88 @@ const buildCorpus = (dir, extension, minify) => {
6161
});
6262
};
6363

64+
// What the minifier comparisons install, read back as a corpus: real framework
65+
// stylesheets and documents, which `configCases` and wpt between them do not
66+
// have — those are spec fixtures, written to exercise a rule rather than to
67+
// ship. Absent until `yarn benchmark:css-minifiers` / `:html-minifiers` has run
68+
// once, so a caller that finds nothing says so rather than reporting green.
69+
const CSS_BENCHMARK_CACHE = path.join(
70+
__dirname,
71+
"../../node_modules/.cache/css-minifier-comparison"
72+
);
73+
const HTML_BENCHMARK_CACHE = path.join(
74+
__dirname,
75+
"../../node_modules/.cache/html-minifier-comparison"
76+
);
77+
78+
// Named rather than walked: the caches also hold each tool's own `node_modules`,
79+
// and a package's test fixtures are not what any of this is about.
80+
/** @type {[string, string][]} */
81+
const BENCHMARK_STYLESHEETS = [
82+
["Animate.css 4", "node_modules/animate.css/animate.css"],
83+
["Bootstrap 5", "node_modules/bootstrap/dist/css/bootstrap.css"],
84+
["Bootstrap 5 grid", "node_modules/bootstrap/dist/css/bootstrap-grid.css"],
85+
["Bulma 1", "node_modules/bulma/css/bulma.css"],
86+
["Font Awesome 6", "node_modules/@fortawesome/fontawesome-free/css/all.css"],
87+
["Foundation 6", "node_modules/foundation-sites/dist/css/foundation.css"],
88+
["Materialize 1", "node_modules/materialize-css/dist/css/materialize.css"],
89+
["Milligram 1", "node_modules/milligram/dist/milligram.css"],
90+
["normalize.css 8", "node_modules/normalize.css/normalize.css"],
91+
["Pico 2", "node_modules/@picocss/pico/css/pico.css"],
92+
["Primer 21", "node_modules/@primer/css/dist/primer.css"],
93+
["Pure 3", "node_modules/purecss/build/pure.css"],
94+
["sanitize.css 13", "node_modules/sanitize.css/sanitize.css"],
95+
["Semantic UI 2", "node_modules/semantic-ui-css/semantic.css"],
96+
["Tachyons 4", "node_modules/tachyons/css/tachyons.css"],
97+
["UIkit 3", "node_modules/uikit/dist/css/uikit.css"],
98+
["Water.css 2", "node_modules/water.css/out/water.css"],
99+
["Tailwind 4 app", "tailwind-app.css"],
100+
["Tailwind 4 wide", "tailwind-wide.css"],
101+
["Tailwind 4 + daisyUI 5", "tailwind-daisyui.css"]
102+
];
103+
104+
/** @type {[string, string][]} */
105+
const BENCHMARK_DOCUMENTS = [
106+
["HTML5 Boilerplate 9", "node_modules/html5-boilerplate/dist/index.html"],
107+
["Swagger UI 5", "node_modules/swagger-ui-dist/index.html"]
108+
];
109+
110+
/**
111+
* Read a benchmark cache as a corpus. A file the cache does not hold is left out
112+
* rather than failing: each cache is built by the comparison that installs it.
113+
* @param {string} cache the cache directory
114+
* @param {[string, string][]} entries `[label, path within the cache]`
115+
* @param {(source: string) => string} minify the printer to run
116+
* @returns {Fixture[]} the corpus, empty when the cache is not there
117+
*/
118+
const readBenchmarkCache = (cache, entries, minify) => {
119+
/** @type {Fixture[]} */
120+
const out = [];
121+
for (const [label, within] of entries) {
122+
const file = path.join(cache, within);
123+
if (!fs.existsSync(file)) continue;
124+
const raw = fs.readFileSync(file, "utf8");
125+
out.push({ name: label, raw, min: minify(raw) });
126+
}
127+
return out;
128+
};
129+
130+
/**
131+
* The framework stylesheets `yarn benchmark:css-minifiers` installs.
132+
* @param {(source: string) => string} minify the printer to run
133+
* @returns {Fixture[]} the corpus, empty until that has been run once
134+
*/
135+
const benchmarkStylesheets = (minify) =>
136+
readBenchmarkCache(CSS_BENCHMARK_CACHE, BENCHMARK_STYLESHEETS, minify);
137+
138+
/**
139+
* The documents `yarn benchmark:html-minifiers` installs.
140+
* @param {(source: string) => string} minify the printer to run
141+
* @returns {Fixture[]} the corpus, empty until that has been run once
142+
*/
143+
const benchmarkDocuments = (minify) =>
144+
readBenchmarkCache(HTML_BENCHMARK_CACHE, BENCHMARK_DOCUMENTS, minify);
145+
64146
/**
65147
* @typedef {{ kind: string, condition: string }} Condition
66148
* @typedef {{ chain: Condition[], text: string, label?: string, list?: string[], block?: string }} Rule
@@ -211,7 +293,17 @@ const installHelpers = () => {
211293
: word;
212294
word = "";
213295
};
214-
for (const ch of run.replace(COLOR_TOKEN_RE, (color) => painted(color))) {
296+
// A pixel ends in a channel, so a token written straight after the color
297+
// would read as part of it — `rgba(…)0` is a color and a zero, not one
298+
// channel more. The printer writes that form, since the `)` parts them.
299+
const painting = run.replace(
300+
COLOR_TOKEN_RE,
301+
(color, at, whole) =>
302+
`${painted(color)}${
303+
WORD_RE.test(whole[at + color.length] || "") ? " " : ""
304+
}`
305+
);
306+
for (const ch of painting) {
215307
if (WORD_RE.test(ch)) {
216308
word += ch;
217309
continue;
@@ -1694,6 +1786,8 @@ const compareRules = (before, after, signatures) => {
16941786
};
16951787

16961788
module.exports = {
1789+
benchmarkDocuments,
1790+
benchmarkStylesheets,
16971791
buildCorpus,
16981792
collectFixtures,
16991793
compareRules,

‎test/syntaxEquivalence.spectest.js‎

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const {
3030
const expectNoDeprecations = require("./helpers/expectNoDeprecations");
3131
const launchChrome = require("./helpers/launchChrome");
3232
const {
33+
benchmarkDocuments,
34+
benchmarkStylesheets,
3335
buildCorpus,
3436
compareRules,
3537
conditionSignatures,
@@ -74,6 +76,19 @@ const FILED_CONFIG_CSS_DEFECTS = new Map();
7476

7577
const FILED_CONFIG_HTML_DEFECTS = new Map();
7678

79+
const FILED_BENCHMARK_CSS_DEFECTS = new Map([
80+
[
81+
"Semantic UI 2",
82+
"not a printer defect: `.ui.table tr.negative, td.negative` and the same pair for `.error` print the same block, so joining each pair leaves four one-selector entries where the source had eight. The four carry one block between them, which makes their order unobservable — an element carrying both classes computes the same style either way, checked in Chrome. The comparison sorts a run of selectors reaching one block, and these land in two runs rather than one."
83+
],
84+
[
85+
"Tailwind 4 + daisyUI 5",
86+
"not a printer defect: the same shape as Semantic UI, with a `@media` between the two runs. `.collapse-arrow` and `.collapse-plus` `::after` come back reordered and four entries shorter; an element carrying both classes computes the same style at either side of the query, checked in Chrome."
87+
]
88+
]);
89+
90+
const FILED_BENCHMARK_HTML_DEFECTS = new Map();
91+
7792
const FILED_WPT_HTML_DEFECTS = new Map([
7893
[
7994
"test/wpt/html/syntax/parsing/misnested-form-in-template.html",
@@ -237,6 +252,22 @@ const buildCorpora = () => {
237252
filedCss: FILED_CONFIG_CSS_DEFECTS
238253
}
239254
];
255+
// The framework sheets and documents the minifier comparisons install: real
256+
// projects, where `configCases` and wpt are both spec fixtures. Left out
257+
// entirely when neither comparison has been run, and reported below.
258+
const benchHtml = benchmarkDocuments((source) => source);
259+
const benchCss = benchmarkStylesheets(minifyCss);
260+
if (benchHtml.length > 0 || benchCss.length > 0) {
261+
built.push({
262+
label: "benchmark corpus",
263+
html: variant(benchHtml, {}),
264+
htmlAllImpliedTags: variant(benchHtml, { removeImpliedTags: true }),
265+
htmlSmartTags: variant(benchHtml, { removeImpliedTags: "smart" }),
266+
css: benchCss,
267+
filedHtml: FILED_BENCHMARK_HTML_DEFECTS,
268+
filedCss: FILED_BENCHMARK_CSS_DEFECTS
269+
});
270+
}
240271
if (!hasCorpus()) return built;
241272
/** @type {Fixture[]} */
242273
const wptHtml = [];
@@ -656,8 +687,9 @@ describe("printer output in real Chrome", () => {
656687
});
657688
};
658689

659-
describeCorpus(0, "configCases");
660-
describeCorpus(1, "wpt");
690+
// By position: which corpora were built depends on what is checked out and
691+
// which comparisons have been run, so each names itself.
692+
for (const [at, one] of corpora.entries()) describeCorpus(at, one.label);
661693

662694
// One test per declaration, not per file: the value is what a defect is filed
663695
// against, so the run names it without anything having to narrow it down.

0 commit comments

Comments
 (0)