Download scripts/test-v082.mjs from EHS-Studio/dssl: direct link, hf CLI and curl.
- Browser
- Download file 6.73 kB
-
https://huggingface.co/EHS-Studio/dssl/resolve/main/scripts/test-v082.mjs
- Command line
-
hf download hf://EHS-Studio/dssl/scripts/test-v082.mjs
-
curl -L -o test-v082.mjs https://huggingface.co/EHS-Studio/dssl/resolve/main/scripts/test-v082.mjs
6.73 kB
| /* test-v082.mjs: one assertion per change in 0.8.2. | |
| * Found while rebuilding the site on its own engine: | |
| * 1. "later wins" did not hold BETWEEN stylesheets. Each sheet numbers its | |
| * rules from 0 and the cascade compared those numbers across sheets, so a | |
| * later sheet lost to an earlier one that simply had more rules above the | |
| * rule in question. Fixed by giving every rule one global position. | |
| * 2. a later tokens { } block could not override a token an earlier sheet | |
| * set inside @media or @signal (same cause). | |
| * 3. fluid did not work on padding-top, margin-block and the other longhands. | |
| * The cascade tests use the engine's own sort (layer rank, then order) on rules | |
| * from real compiled sheets, so they check the thing that was wrong. */ | |
| import { assignGlobalOrder } from '../public/dssl/engine.js'; | |
| import { compile } from '../public/dssl/index.js'; | |
| let pass = 0, fail = 0; | |
| const ok = (name, cond, extra) => { | |
| if (cond) { pass++; console.log(` ok ${name}`); } | |
| else { fail++; console.log(`FAIL ${name}${extra ? '; ' + extra : ''}`); } | |
| }; | |
| const LAYER_RANK = { tokens: 0, base: 1, components: 2, utilities: 3, pin: 4 }; | |
| const cascade = (rules) => [...rules].sort((a, b) => (LAYER_RANK[a.layer] ?? 2) - (LAYER_RANK[b.layer] ?? 2) || a.order - b.order); | |
| const bundleOf = (src) => { const { doc } = compile(src, 't.dssl'); return { rules: doc.rules, shadowRules: doc.shadowRules, raws: doc.raws || [] }; }; | |
| const winner = (rules, sel) => cascade(rules.filter((r) => r.sel === sel)).pop(); | |
| const val = (rule, prop) => rule.decls.filter((d) => d.prop === prop).pop()?.value; | |
| /* ---- 1: later sheets win ---- */ | |
| { | |
| let filler = ''; | |
| for (let i = 0; i < 30; i++) filler += `.zz${i} { color: green }\n`; | |
| const first = bundleOf(filler + '.x { color: red }'); | |
| const second = bundleOf('.x { color: blue }'); | |
| const before = winner([...first.rules, ...second.rules], '.x'); | |
| ok('1a. the bug: without a global order the EARLIER sheet wins', val(before, 'color') === 'red', val(before, 'color')); | |
| const a = bundleOf(filler + '.x { color: red }'); | |
| const b = bundleOf('.x { color: blue }'); | |
| assignGlobalOrder([a, b]); | |
| const after = winner([...a.rules, ...b.rules], '.x'); | |
| ok('1b. with a global order the LATER sheet wins', val(after, 'color') === 'blue', val(after, 'color')); | |
| const lastOfFirst = Math.max(...a.rules.map((r) => r.order)); | |
| const firstOfSecond = Math.min(...b.rules.map((r) => r.order)); | |
| ok('1c. every rule of a later sheet sorts after every rule of an earlier one', firstOfSecond > lastOfFirst, `${firstOfSecond} vs ${lastOfFirst}`); | |
| const snapshot = [...a.rules, ...b.rules].map((r) => r.order).join(','); | |
| assignGlobalOrder([a, b]); | |
| assignGlobalOrder([a, b]); | |
| ok('1d. it is idempotent: rebuilding does not make the numbers drift', [...a.rules, ...b.rules].map((r) => r.order).join(',') === snapshot); | |
| // replacing a sheet in place: new rules get numbered from the same slot | |
| const b2 = bundleOf('.x { color: purple }\n.y { color: teal }'); | |
| assignGlobalOrder([a, b2]); | |
| ok('1e. a replaced sheet still sorts after the sheet before it', val(winner([...a.rules, ...b2.rules], '.x'), 'color') === 'purple'); | |
| // three sheets in a row | |
| const c = bundleOf('.x { color: orange }'); | |
| assignGlobalOrder([a, b2, c]); | |
| ok('1f. with three sheets the last one wins', val(winner([...a.rules, ...b2.rules, ...c.rules], '.x'), 'color') === 'orange'); | |
| // within one sheet nothing changed: the later line still wins | |
| const one = bundleOf('.x { color: red }\n.x { color: green }'); | |
| assignGlobalOrder([one]); | |
| ok('1g. inside one sheet the later rule still wins', val(winner(one.rules, '.x'), 'color') === 'green'); | |
| // layers still outrank order: a utilities rule in an EARLY sheet beats a components rule in a late one | |
| const early = bundleOf('@layer utilities { .x { margin-top: 0 } }'); | |
| const late = bundleOf('.x { margin-top: 30 }'); | |
| assignGlobalOrder([early, late]); | |
| ok('1h. layers still outrank load order', val(winner([...early.rules, ...late.rules], '.x'), 'margin-top') === '0px'); | |
| // shadow (pseudo-element) rules and raw blocks are renumbered with their sheet | |
| const sh1 = bundleOf('.a::before { content: "a" }'); | |
| const sh2 = bundleOf('.a::before { content: "b" }'); | |
| assignGlobalOrder([sh1, sh2]); | |
| ok('1i. pseudo-element rules follow the same global order', sh2.shadowRules[0].order > sh1.shadowRules[0].order); | |
| } | |
| /* ---- 2: a later tokens block beats an earlier conditional token ---- */ | |
| { | |
| const palettes = bundleOf(`tokens { color { accent: red } } | |
| @media (prefers-color-scheme: dark) { tokens { color { accent: pink } } }`); | |
| const later = bundleOf('tokens { color { accent: blue } }'); | |
| const tokenRules = (b) => b.rules.filter((r) => r.layer === 'tokens' && r.decls.some((d) => d.prop === '--dssl-color-accent')); | |
| const before = cascade([...tokenRules(palettes), ...tokenRules(later)]).pop(); | |
| ok('2a. the bug: an earlier conditional token out-ranked a later tokens block', before.decls.some((d) => d.value === 'pink')); | |
| const p = bundleOf(`tokens { color { accent: red } } | |
| @media (prefers-color-scheme: dark) { tokens { color { accent: pink } } }`); | |
| const l = bundleOf('tokens { color { accent: blue } }'); | |
| assignGlobalOrder([p, l]); | |
| const after = cascade([...tokenRules(p), ...tokenRules(l)]).pop(); | |
| ok('2b. now the later tokens block wins, even in dark mode', after.decls.some((d) => d.value === 'blue')); | |
| } | |
| /* ---- 3: fluid on the longhands ---- */ | |
| { | |
| const evalClamp = (css, vw) => { | |
| const m = /^clamp\(\s*(-?[\d.]+)px,\s*calc\(\s*(-?[\d.]+)px\s*\+\s*(-?[\d.]+)vw\s*\),\s*(-?[\d.]+)px\s*\)$/.exec(css); | |
| if (!m) return NaN; | |
| return Math.min(+m[4], Math.max(+m[1], +m[2] + (+m[3] * vw) / 100)); | |
| }; | |
| const near = (x, y) => Math.abs(x - y) < 0.01; | |
| for (const prop of ['padding-top', 'padding-bottom', 'padding-left', 'padding-right', 'padding-inline', 'padding-block', 'margin-top', 'margin-bottom', 'margin-left', 'margin-right', 'margin-inline', 'margin-block', 'letter-spacing', 'border-radius']) { | |
| const { doc, ok: good } = compile(`.a { ${prop}: fluid 8 24 }`); | |
| const v = doc.rules[0]?.decls.find((d) => d.prop === prop)?.value; | |
| ok(`3. ${prop}: fluid 8 24 is 8px at 360 and 24px at 1200`, good && near(evalClamp(v, 360), 8) && near(evalClamp(v, 1200), 24), v); | |
| } | |
| const still = compile('.a { padding: fluid 8 16 }'); | |
| ok('3b. the shorthand still works', still.ok && /^clamp\(/.test(still.doc.rules[0].decls[0].value)); | |
| const nope = compile('.a { opacity: fluid 0 1 }'); | |
| ok('3c. fluid on a property that cannot take it is still an error, not silence', !nope.ok); | |
| } | |
| console.log(`\n${pass} passed, ${fail} failed`); | |
| process.exit(fail ? 1 : 0); | |