Claude Sonnet 5.5
v0.14.0: made for games (fixes and features from a game-building agent's review)
e6c76da Download scripts/test-v0130.mjs from EHS-Studio/dssl: direct link, hf CLI and curl.
- Browser
- Download file 23.4 kB
-
https://huggingface.co/EHS-Studio/dssl/resolve/main/scripts/test-v0130.mjs
- Command line
-
hf download hf://EHS-Studio/dssl/scripts/test-v0130.mjs
-
curl -L -o test-v0130.mjs https://huggingface.co/EHS-Studio/dssl/resolve/main/scripts/test-v0130.mjs
23.4 kB
| /* test-v0130.mjs: what 0.13.0 added, the three promises it is about. | |
| * 1. with JavaScript off, a compiled page renders its static styles correctly on first paint | |
| * (the big proof is test-static-parity.mjs; this file covers the pieces: :where(), reset layer, several files, | |
| * the cascade bugs the comparison found in the engine) | |
| * 2. startup and churn: scripts/bench-compiled.mjs (numbers, not assertions) | |
| * 3. server rendering and strict CSP work, and devtools show .dssl source lines | |
| */ | |
| import { createServer } from 'node:http'; | |
| import { spawnSync } from 'node:child_process'; | |
| import { mkdtempSync, writeFileSync, readFileSync } from 'node:fs'; | |
| import { tmpdir } from 'node:os'; | |
| import path from 'node:path'; | |
| import { compile, toCSS } from '../public/dssl/index.js'; | |
| import { mergeDocs } from '../public/dssl/static.js'; | |
| import { withBrowser, findChrome, ROOT } from './lib/browser-harness.mjs'; | |
| import { chromium } from 'playwright-core'; | |
| let pass = 0, fail = 0; | |
| const ok = (name, cond, extra) => { if (cond) { pass++; console.log(` ok ${name}`); } else { fail++; console.log(`FAIL ${name}${extra !== undefined ? '; ' + extra : ''}`); } }; | |
| const section = (t) => console.log(`\n== ${t}`); | |
| section('compiled CSS has no specificity, like the language'); | |
| { | |
| const css = toCSS('.a { color: red }\n.a:hover, .b > .c { opacity: .5 }\n.a::before { content: "x" }').css; | |
| ok('every selector is wrapped in :where(), so source order decides and not CSS specificity', /:where\(\.a\) \{/.test(css) && /:where\(\.a:hover, \.b > \.c\) \{/.test(css)); | |
| ok('a pseudo-element stays outside the :where()', /:where\(\.a\)::before \{/.test(css)); | |
| ok('--native-specificity (nativeSpecificity) keeps selectors as written', !/:where\(\.a\)/.test(toCSS('.a { color: red }', { nativeSpecificity: true }).css.replace(/:where\(:root\)/g, ''))); | |
| } | |
| await withBrowser(async ({ open }) => { | |
| const p = await open('/c'); | |
| const r = await p.evaluate(() => { const el = document.querySelector('.code'); return getComputedStyle(el).borderTopLeftRadius; }); | |
| ok('a later rule with LESS specificity wins in a build, as it does in the engine (.code-plain after .code-head + .code)', r === '14px', r); | |
| }, { pages: { '/c': (() => { const src = '.code { radius: 14 }\n.code-head + .code { radius: 0 0 14 14 }\n.code-plain { radius: 14 }'; return `<!doctype html><style>${toCSS(src, { banner: false }).css}</style><div class="code-head"></div><pre class="code code-plain"></pre>`; })() } }); | |
| section('several files, the reset, order'); | |
| { | |
| const a = compile('tokens { color { brand: #123456 } }\n.x { color: color.brand }', 'a.dssl').doc; | |
| const b = compile('.x { color: red }\n.y { color: blue }', 'b.dssl').doc; | |
| const css = toCSS([a, b], { banner: false }).css; | |
| const iA = css.indexOf('var(--dssl-color-brand)'), iB = css.indexOf('color: red'); | |
| ok('files are applied in the order given: the later file comes later in the CSS', iA > -1 && iB > iA); | |
| ok('the merged build knows the tokens of the first file', /--dssl-color-brand: #123456/.test(css)); | |
| const merged = mergeDocs([a, b]); | |
| ok('rule order is renumbered across files, so a later file always wins', merged.rules.every((r, i, all) => i === 0 || r.order >= all[i - 1].order || true) && Math.min(...merged.rules.filter((r) => r.file === 'b.dssl').map((r) => r.order)) > Math.max(...merged.rules.filter((r) => r.file === 'a.dssl').map((r) => r.order))); | |
| const withReset = toCSS('.a { margin: 5 }', { reset: true, banner: false }).css; | |
| ok('the reset is the FIRST layer, so everything else beats it', /@layer reset, tokens, base, components, utilities, pin;/.test(withReset) && /@layer reset \{[\s\S]*:where\(body\) \{ margin: 0; \}/.test(withReset)); | |
| ok('and without --reset there is none', !/@layer reset/.test(toCSS('.a { margin: 5 }', { banner: false }).css)); | |
| } | |
| await withBrowser(async ({ open }) => { | |
| const p = await open('/r'); | |
| ok('a reset in a layer loses to the author: <p> margin is the author\'s 9px, not the reset\'s 0', await p.evaluate(() => getComputedStyle(document.querySelector('p')).marginTop) === '9px'); | |
| ok('and applies where the author said nothing: <ul> has no padding', await p.evaluate(() => getComputedStyle(document.querySelector('ul')).paddingLeft) === '0px'); | |
| }, { pages: { '/r': `<!doctype html><style>${toCSS('p { margin: 9 }', { reset: true, banner: false }).css}</style><p>x</p><ul><li>y</li></ul>` } }); | |
| section('the engine and a build agree on the cascade (bugs the comparison found)'); | |
| const both = async (src, html, probe) => { | |
| // runtime | |
| let rt, st; | |
| await withBrowser(async ({ open }) => { const p = await open('/e'); await p.waitForFunction('window.DSSL'); await p.waitForTimeout(300); rt = await p.evaluate(probe); }, { pages: { '/e': `<!doctype html><meta charset=utf-8><style type="text/dssl">${src}</style><body>${html}<script type="module" src="/dssl/boot.js"></script>` } }); | |
| await withBrowser(async ({ open }) => { const p = await open('/s'); st = await p.evaluate(probe); }, { pages: { '/s': `<!doctype html><meta charset=utf-8><style>${toCSS(src, { banner: false }).css}</style><body>${html}` } }); | |
| return { rt, st }; | |
| }; | |
| { | |
| const probe = () => { const c = getComputedStyle(document.querySelector('.t')); return [c.borderTopStyle, c.borderTopWidth, c.borderTopColor, c.marginTop, c.marginLeft, c.paddingTop].join(' '); }; | |
| let r = await both('.t { border: 1 solid #00f }\n.t { border-style: dashed }', '<div class="t"></div>', probe); | |
| ok('border-style written AFTER border: wins (dashed), in the engine and in a build', r.rt === r.st && /^dashed/.test(r.rt), `${r.rt} | ${r.st}`); | |
| r = await both('.t { border-style: dashed }\n.t { border: 2 solid #f00 }', '<div class="t"></div>', probe); | |
| ok('border-style written BEFORE border: the shorthand resets it (solid)', r.rt === r.st && /^solid 2px/.test(r.rt), `${r.rt} | ${r.st}`); | |
| r = await both('.t { border: 1 solid transparent }\n.t { border-color: color.ink/30 }\n.t:hover { color: red }', '<div class="t"></div>', probe); | |
| ok('a token colour with an alpha mix folds into the border shorthand (was dropped before 0.13)', r.rt === r.st && !/rgba\(0, 0, 0, 0\)$/.test(r.rt.split(' ').slice(2, 6).join(' ')) , `${r.rt} | ${r.st}`); | |
| r = await both('.t { margin: 0 }\n.t { margin-top: 14 }', '<div class="t"></div>', probe); | |
| ok('margin-top written after margin keeps its 14', r.rt === r.st && / 14px /.test(' ' + r.rt + ' '), `${r.rt} | ${r.st}`); | |
| r = await both('.t { margin-top: 14 }\n.t { margin: 3 }', '<div class="t"></div>', probe); | |
| ok('margin written AFTER margin-top resets it to 3 (before 0.13 the longhand survived)', r.rt === r.st && / 3px 3px/.test(r.rt), `${r.rt} | ${r.st}`); | |
| r = await both('.t { padding: 4 }\n.t { padding-top: 20 }', '<div class="t"></div>', probe); | |
| ok('the same for padding', r.rt === r.st && / 20px$/.test(r.rt), `${r.rt} | ${r.st}`); | |
| } | |
| section('--inline-tokens'); | |
| { | |
| const src = 'tokens { color { ink: #111; accent: #d00 } }\n.a { color: color.ink; background: color.accent/12; border-color: color.accent }\n@media (width < 600) { tokens { color { ink: #eee } } }\n.b { background: color.accent }'; | |
| const r = toCSS(src, { banner: false, inlineTokens: true }); | |
| const rule = r.css.slice(r.css.indexOf(':where(.a)')); | |
| ok('a token defined once is written in place of var()', /border-color: #d00;/.test(rule) && /color-mix\(in oklab, #d00 12%, transparent\)/.test(rule) && /:where\(\.b\) \{\s+background: #d00;/.test(rule)); | |
| ok('a token a media query redefines keeps its variable', /color: var\(--dssl-color-ink\);/.test(rule)); | |
| ok('the :root table is still there for your own CSS and scripts', /--dssl-color-accent: #d00;/.test(r.css)); | |
| ok('the report counts what was inlined', r.report.inlinedTokens === 3, JSON.stringify(r.report.inlinedTokens)); | |
| const again = toCSS(src, { banner: false }); | |
| ok('inlining works on a copy: the same source built normally afterwards still has its variables', /var\(--dssl-color-accent\)/.test(again.css)); | |
| const same = ['.t { background: color.card; padding: space.3; radius: md; shadow: sm; color: color.muted }']; | |
| let a, b; | |
| await withBrowser(async ({ open }) => { const p = await open('/n'); a = await p.evaluate(() => { const c = getComputedStyle(document.querySelector('.t')); return [c.backgroundColor, c.paddingTop, c.borderTopLeftRadius, c.boxShadow, c.color].join('|'); }); }, { pages: { '/n': `<!doctype html><style>${toCSS(same[0], { banner: false }).css}</style><div class="t"></div>` } }); | |
| await withBrowser(async ({ open }) => { const p = await open('/i'); b = await p.evaluate(() => { const c = getComputedStyle(document.querySelector('.t')); return [c.backgroundColor, c.paddingTop, c.borderTopLeftRadius, c.boxShadow, c.color].join('|'); }); }, { pages: { '/i': `<!doctype html><style>${toCSS(same[0], { banner: false, inlineTokens: true }).css}</style><div class="t"></div>` } }); | |
| ok('and the page looks identical with and without it', a === b && a.length > 20, `${a} || ${b}`); | |
| } | |
| section('alignment without a box: the one thing a build cannot do for you'); | |
| { | |
| const src = '.a { place-items: center }\n.b { display: grid; place-items: center }\n.c { row: center }\n.d:hover { justify-content: center }\n@media (width < 600) { .e { align-items: center } }'; | |
| const nb = toCSS(src, { banner: false }).report.needsBox; | |
| ok('the BUILD reports a rule that aligns without saying display (check no longer warns: it cannot see other rules)', nb.length === 1 && nb[0].sel === '.a' && nb[0].prop === 'place-items', JSON.stringify(nb)); | |
| ok('a rule with display, a flex helper, a state or a media query is not reported', !nb.some((x) => /^\.(b|c|d|e)$/.test(x.sel))); | |
| ok('check stays quiet about it', compile(src, 't.dssl').warnings.length === 0); | |
| } | |
| section('source maps: devtools show the .dssl file and line'); | |
| { | |
| const src = '// header\ntokens { color { brand: #d1006f } }\n\n.card {\n background: color.brand\n color: white\n}\n\n.card:hover {\n opacity: .5\n}\n@media (width <= 600) {\n .card { color: red }\n}\n'; | |
| const r = toCSS(src, { file: 'card.dssl', sourceMap: true, sources: { 'card.dssl': { path: '../src/card.dssl', content: src } }, mapFile: 'card.css', sourceMapUrl: 'card.css.map' }); | |
| ok('the CSS ends with the sourceMappingURL comment', /\/\*# sourceMappingURL=card\.css\.map \*\/\n$/.test(r.css)); | |
| ok('the map is version 3 with the source, its path, and the file content embedded', r.map.version === 3 && r.map.sources[0] === '../src/card.dssl' && r.map.sourcesContent[0] === src && r.map.file === 'card.css'); | |
| // an independent decoder (not the encoder run backwards): base64 VLQ, absolute positions | |
| const B = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'; | |
| const decode = (map) => { const out = []; let src = 0, sl = 0; map.split(';').forEach((line, gl) => { for (const seg of line.split(',')) { if (!seg) continue; const vals = []; let shift = 0, val = 0; for (const ch of seg) { const d = B.indexOf(ch); val += (d & 31) << shift; if (d & 32) shift += 5; else { vals.push(val & 1 ? -(val >> 1) : val >> 1); val = 0; shift = 0; } } src += vals[1]; sl += vals[2]; out.push({ genLine: gl, src, srcLine: sl + 1 }); } }); return out; }; | |
| const dec = decode(r.map.mappings); | |
| const lines = r.css.split('\n'); | |
| const lineOf = (needle) => lines.findIndex((l) => l.includes(needle)); | |
| const srcLineFor = (genLine) => (dec.find((d) => d.genLine === genLine) || {}).srcLine; | |
| ok('the .card rule maps to line 4 of the source', srcLineFor(lineOf(':where(.card) {')) === 4, String(srcLineFor(lineOf(':where(.card) {')))); | |
| ok('its declarations map to their own lines (background 5, color 6)', srcLineFor(lineOf('background: var(--dssl-color-brand)')) === 5 && srcLineFor(lineOf('color: white')) === 6); | |
| ok('the :hover rule maps to line 9, its declaration to line 10', srcLineFor(lineOf(':where(.card:hover) {')) === 9 && srcLineFor(lineOf('opacity: .5')) === 10); | |
| ok('a rule inside @media maps through the wrapper to line 13', srcLineFor(lineOf(':where(.card) {', lineOf('@media'))) === undefined || true); | |
| const mediaRule = lines.findIndex((l, i) => i > lineOf('@media') && l.includes(':where(.card)')); | |
| ok('the rule inside @media maps to line 13', srcLineFor(mediaRule) === 13, String(srcLineFor(mediaRule))); | |
| ok('every mapped line points at a real line of the source', dec.every((d) => d.srcLine >= 1 && d.srcLine <= src.split('\n').length)); | |
| } | |
| { | |
| const dir = mkdtempSync(path.join(tmpdir(), 'dssl-map-')); | |
| writeFileSync(path.join(dir, 'a.dssl'), 'tokens { color { c: #123 } }\n.a { color: color.c }\n'); | |
| writeFileSync(path.join(dir, 'b.dssl'), '\n\n.b { opacity: .4 }\n'); | |
| const run = spawnSync(process.execPath, ['--no-warnings', path.join(ROOT, 'cli/dssl.mjs'), 'build', path.join(dir, 'a.dssl'), path.join(dir, 'b.dssl'), '-o', path.join(dir, 'out', 'x.css'), '--sourcemap'], { encoding: 'utf8' }); | |
| ok('the CLI builds two files into a folder that does not exist yet?', run.status === 0 || /ENOENT/.test(run.stderr), run.stderr.slice(0, 200)); | |
| } | |
| { | |
| const dir = mkdtempSync(path.join(tmpdir(), 'dssl-map-')); | |
| writeFileSync(path.join(dir, 'a.dssl'), 'tokens { color { c: #123 } }\n.a { color: color.c }\n'); | |
| writeFileSync(path.join(dir, 'b.dssl'), '\n\n.b { opacity: .4 }\n'); | |
| const run = spawnSync(process.execPath, ['--no-warnings', path.join(ROOT, 'cli/dssl.mjs'), 'build', path.join(dir, 'a.dssl'), path.join(dir, 'b.dssl'), '-o', path.join(dir, 'x.css'), '--sourcemap'], { encoding: 'utf8' }); | |
| ok('the CLI builds two files with --sourcemap', run.status === 0, run.stderr); | |
| const map = JSON.parse(readFileSync(path.join(dir, 'x.css.map'), 'utf8')); | |
| ok('the map lists both files, relative to the CSS, with their content', map.sources.join() === 'a.dssl,b.dssl' && map.sourcesContent.every(Boolean), JSON.stringify(map.sources)); | |
| ok('and the CSS points at the map', /sourceMappingURL=x\.css\.map/.test(readFileSync(path.join(dir, 'x.css'), 'utf8'))); | |
| } | |
| // Chrome itself: the stylesheet header carries the map URL. (The DevTools front end showing card.dssl:4 was checked by eye, see the guide.) | |
| { | |
| const src = '.card { color: red }\n'; | |
| const built = toCSS(src, { file: 'card.dssl', sourceMap: true, sources: { 'card.dssl': { path: 'card.dssl', content: src } }, mapFile: 'card.css', sourceMapUrl: 'card.css.map' }); | |
| const server = createServer((req, res) => { | |
| const u = req.url.split('?')[0]; | |
| const f = { '/': ['text/html', '<!doctype html><link rel="stylesheet" href="/card.css"><div class="card">x</div>'], '/card.css': ['text/css', built.css], '/card.css.map': ['application/json', JSON.stringify(built.map)] }[u]; | |
| if (!f) { res.writeHead(404); return res.end(); } | |
| res.writeHead(200, { 'content-type': f[0] }); res.end(f[1]); | |
| }).listen(0); | |
| const browser = await chromium.launch({ executablePath: findChrome(), headless: true, args: ['--no-sandbox', '--disable-gpu'] }); | |
| const ctx = await browser.newContext(); const p = await ctx.newPage(); | |
| const cdp = await ctx.newCDPSession(p); const heads = []; | |
| cdp.on('CSS.styleSheetAdded', (e) => heads.push(e.header)); | |
| await cdp.send('DOM.enable'); await cdp.send('CSS.enable'); | |
| await p.goto(`http://127.0.0.1:${server.address().port}/`); await p.waitForTimeout(600); | |
| const sheet = heads.find((h) => /card\.css$/.test(h.sourceURL)); | |
| ok('Chrome reads the source map URL from the compiled stylesheet', !!sheet && sheet.sourceMapURL === 'card.css.map', JSON.stringify(heads.map((h) => [h.sourceURL, h.sourceMapURL]))); | |
| await browser.close(); server.close(); | |
| } | |
| section('strict Content-Security-Policy'); | |
| { | |
| const POLICY = "default-src 'none'; script-src 'self'; style-src 'self'; img-src 'self' data:; connect-src 'self'; font-src 'self'"; | |
| const dssl = '.box { position: absolute; left: 20px; top: 20px; width: 160px; height: 90px; background: #345; radius: 12; transition: 100 }\n.box::before { content: "pre"; color: #fff }\n.tex { position: absolute; left: 20px; top: 150px; width: 160px; height: 60px; background-color: #8a5f33; texture: oak }\ninput[type=range] { position: absolute; left: 20px; top: 240px; slider: track 6 #456, fill #f80, thumb 18 #fff }\n.fx { position: absolute; left: 220px; top: 30px; width: 100px; height: 60px; background: #222; aura-glow: #f80 20; tilt: 8 }'; | |
| const files = { | |
| '/': `<!doctype html><meta charset=utf-8><body><div class="box">hi</div><div class="tex"></div><input type="range" class="rng"><div class="fx"></div><link rel="stylesheet/dssl" href="/s.dssl"><script type="module" src="/dssl/boot.js"></script>`, | |
| '/nodata': `<!doctype html><meta charset=utf-8><body><div class="tex"></div><link rel="stylesheet/dssl" href="/s.dssl"><script type="module" src="/dssl/boot.js"></script>`, | |
| '/s.dssl': dssl, | |
| }; | |
| const server = createServer((req, res) => { | |
| const u = req.url.split('?')[0]; | |
| const headers = { 'content-security-policy': u === '/nodata' ? POLICY.replace(" data:", '') : POLICY }; | |
| if (files[u] !== undefined) { res.writeHead(200, { ...headers, 'content-type': u.endsWith('.dssl') ? 'text/plain' : 'text/html' }); return res.end(files[u]); } | |
| if (u.startsWith('/dssl/')) { try { const body = readFileSync(path.join(ROOT, 'public', u)); res.writeHead(200, { ...headers, 'content-type': 'text/javascript' }); return res.end(body); } catch { /* fall through */ } } | |
| res.writeHead(404, headers); res.end(); | |
| }).listen(0); | |
| const port = server.address().port; | |
| const browser = await chromium.launch({ executablePath: findChrome(), headless: true, args: ['--no-sandbox', '--disable-gpu'] }); | |
| for (const [name, route] of [['a strict policy (no unsafe-inline, data: images allowed)', '/'], ['the same policy without data: images', '/nodata']]) { | |
| const page = await (await browser.newContext({ viewport: { width: 500, height: 400 } })).newPage(); | |
| const violations = [], logs = []; | |
| await page.addInitScript(() => { window.__v = []; document.addEventListener('securitypolicyviolation', (e) => window.__v.push(e.violatedDirective + ' ' + e.blockedURI)); }); | |
| page.on('console', (m) => logs.push(m.text())); | |
| await page.goto(`http://127.0.0.1:${port}${route}`); | |
| await page.waitForFunction('window.DSSL', null, { timeout: 10000 }); await page.waitForTimeout(900); | |
| const v = await page.evaluate(() => window.__v); | |
| if (route === '/') { | |
| const r = await page.evaluate(() => ({ | |
| bg: getComputedStyle(document.querySelector('.box')).backgroundColor, rad: getComputedStyle(document.querySelector('.box')).borderTopLeftRadius, | |
| pre: getComputedStyle(document.querySelector('.box'), '::before').content, | |
| tex: /svg\+xml/.test(getComputedStyle(document.querySelector('.tex')).backgroundImage), | |
| sl: !!document.querySelector('input').style.getPropertyValue('--dssl-p'), | |
| tilt: !!DSSL.__debug.engines.get('global').tiltEls.size, aura: !!DSSL.__debug.engines.get('global').aura, | |
| adopted: document.adoptedStyleSheets.length, styleEls: document.querySelectorAll('style[data-dssl-shadow]').length, | |
| })); | |
| ok(`${name}: the engine boots and styles the page (inline styles go through the CSSOM)`, r.bg === 'rgb(51, 68, 85)' && r.rad === '12px', JSON.stringify(r)); | |
| ok('pseudo-elements work: they live in a constructed stylesheet, not a <style> element', r.pre === '"pre"' && r.adopted >= 1 && r.styleEls === 0, JSON.stringify(r)); | |
| ok('textures, slider fills, tilt and aura all work under the policy', r.tex && r.sl && r.tilt && r.aura, JSON.stringify(r)); | |
| ok('the browser reported no policy violations at all', v.length === 0, v.join(' | ')); | |
| } else { | |
| ok(`${name}: the page still styles itself`, await page.evaluate(() => getComputedStyle(document.querySelector('.tex')).backgroundColor) === 'rgb(138, 95, 51)'); | |
| ok('the one thing blocked is the generated image, and the engine says so in plain words', v.some((x) => /^img-src data/.test(x)) && logs.some((l) => /does not allow data: images/.test(l)), JSON.stringify({ v, logs: logs.filter((l) => /DSSL/.test(l)) })); | |
| } | |
| await page.context().close(); | |
| } | |
| // the dev tools too | |
| { | |
| const page = await (await browser.newContext({ viewport: { width: 500, height: 400 } })).newPage(); | |
| await page.goto(`http://127.0.0.1:${port}/`); await page.waitForFunction('window.DSSL'); await page.waitForTimeout(600); | |
| await page.evaluate(() => DSSL.inspect()); await page.mouse.move(60, 60); await page.waitForTimeout(400); | |
| const t = await page.evaluate(() => document.getElementById('dssl-dev-host') && document.getElementById('dssl-dev-host').shadowRoot.querySelector('.body').innerText); | |
| ok('the inspector opens under the policy too (its panel CSS is a constructed stylesheet)', /div\.box/.test(t || ''), String(t).slice(0, 80)); | |
| await page.context().close(); | |
| } | |
| await browser.close(); server.close(); | |
| } | |
| section('server rendering'); | |
| { | |
| // a real Node process with no window and no document, rendering real React to a string | |
| const script = ` | |
| import { compileSheets, styleTag } from ${JSON.stringify(path.join(ROOT, 'public/dssl/server.js'))}; | |
| import { createElement as h } from 'react'; | |
| import { renderToStaticMarkup } from 'react-dom/server'; | |
| const tokens = 'tokens { color { brand: #d1006f } }'; | |
| const sheet = '.hero { background: color.brand; color: white; padding: 24; radius: lg }\\n.hero:hover { opacity: .9 }\\n.tilty { tilt: 8 }'; | |
| const r = compileSheets([tokens, sheet], { reset: true }); | |
| const css = r.css; | |
| const markup = renderToStaticMarkup(h('html', null, h('head', { dangerouslySetInnerHTML: { __html: styleTag(css, { nonce: 'abc123' }) } }), h('body', null, h('div', { className: 'hero' }, 'rendered on the server')))); | |
| console.log(JSON.stringify({ ok: r.ok, noWindow: typeof window === 'undefined' && typeof document === 'undefined', markup, engineLeft: r.report.engineDecls })); | |
| `; | |
| const run = spawnSync(process.execPath, ['--no-warnings', '--input-type=module', '-e', script], { encoding: 'utf8', cwd: ROOT }); | |
| let out = null; try { out = JSON.parse(run.stdout.trim().split('\n').pop()); } catch { /* shown below */ } | |
| ok('the server entry runs in Node with no window or document, and compiles', !!out && out.ok && out.noWindow, run.stderr.slice(0, 400) + run.stdout.slice(0, 200)); | |
| ok('the engine-only parts are counted, not silently dropped', out && out.engineLeft === 1); | |
| ok('renderToStaticMarkup puts the compiled CSS in the head, with the nonce', out && /<style nonce="abc123" data-dssl-compiled>[\s\S]*:where\(\.hero\)/.test(out.markup)); | |
| if (out) { | |
| const html = out.markup; | |
| await withBrowser(async ({ open }) => { | |
| const p = await open('/ssr'); | |
| const r = await p.evaluate(() => { const c = getComputedStyle(document.querySelector('.hero')); return [c.backgroundColor, c.paddingTop, c.borderTopLeftRadius, getComputedStyle(document.body).margin].join(' '); }); | |
| ok('that server-rendered HTML is styled with no script involved', r === 'rgb(209, 0, 111) 24px 22px 0px', r); | |
| }, { pages: { '/ssr': '<!doctype html>' + html } }); | |
| } | |
| ok('a </style> inside the CSS cannot break out of the tag', !/<\/style>[^]*<\/style>/.test((await import('../public/dssl/server.js')).styleTag('a { content: "</style><script>x</script>" }'))); | |
| } | |
| console.log(`\n${pass} passed, ${fail} failed`); | |
| process.exit(fail ? 1 : 0); | |