Claude Sonnet 5.5
Site and docs for 0.9.0; drop template leftovers; scripts without hard-coded paths
794b22f Download scripts/test-v060.mjs from EHS-Studio/dssl: direct link, hf CLI and curl.
- Browser
- Download file 14.1 kB
-
https://huggingface.co/EHS-Studio/dssl/resolve/main/scripts/test-v060.mjs
- Command line
-
hf download hf://EHS-Studio/dssl/scripts/test-v060.mjs
-
curl -L -o test-v060.mjs https://huggingface.co/EHS-Studio/dssl/resolve/main/scripts/test-v060.mjs
14.1 kB
| /* v0.6.0 tests - the deform family + gradient tools + pass-through: | |
| * 1-4 bend / bend-y / bend-up / fold / stretch / bend-around parse + loud errors | |
| * 5 composeDeform: 3D bake, edge-pinned pivots, squash & stretch, base transform | |
| * 6 arcLayout: radius math, angle spread, degenerate cases | |
| * 7 motion keyframes carry composed transforms + transformOrigin | |
| * 8-11 gradient / gradient-text / gradient-flow: arrow + comma stops, tokens, alpha, positions | |
| * 12 transition bend/stretch/fold aliases land on transform | |
| * 13 pointer-events: none - the transparent-overlay sibling feature | |
| * 14 deform props inside pseudo blocks stay LOUD (no DOM handle) | |
| * 15 all six site stylesheets chained through the token registry | |
| */ | |
| import { parse } from '../public/dssl/parser.js'; | |
| import { resolve, DEFAULT_TOKENS } from '../public/dssl/resolver.js'; | |
| import { composeDeform, arcLayout } from '../public/dssl/utils.js'; | |
| let pass = 0, fail = 0; | |
| const check = (name, cond, extra = '') => { | |
| if (cond) { pass++; console.log(` ok ${name}`); } | |
| else { fail++; console.log(`FAIL ${name}${extra ? ' - ' + extra : ''}`); } | |
| }; | |
| function compile(src) { | |
| const { ast, errors: parseErrors } = parse(src, 'test.dssl'); | |
| const doc = resolve(ast, 'test.dssl', null); | |
| return { doc, errors: [...parseErrors, ...doc.errors] }; | |
| } | |
| const declOf = (doc, sel, prop) => | |
| doc.rules.find(r => r.sel === sel)?.decls.find(d => d.prop === prop)?.value; | |
| console.log('1. bend / bend-y / bend-up / fold'); | |
| { | |
| const { doc, errors } = compile(` | |
| .a { bend: 20 } | |
| .b { bend-y: -12.5 } | |
| .c { bend-up: 14deg } | |
| .d { fold: 30 } | |
| .e { bend: 0 } | |
| `); | |
| check('1 no errors', errors.length === 0, JSON.stringify(errors)); | |
| check('1 bend = 20', declOf(doc, '.a', '@bend') === 20, declOf(doc, '.a', '@bend')); | |
| check('1 bend-y = -12.5', declOf(doc, '.b', '@bend-y') === -12.5); | |
| check('1 bend-up accepts deg suffix', declOf(doc, '.c', '@bend-up') === 14); | |
| check('1 fold = 30', declOf(doc, '.d', '@fold') === 30); | |
| check('1 bend: 0 emits a resetter', declOf(doc, '.e', '@bend') === 0); | |
| const bad1 = compile('.x { bend: 12px }'); | |
| check('1 12px is a LOUD error', bad1.errors.length === 1 && /bare number of degrees/.test(bad1.errors[0].message), JSON.stringify(bad1.errors)); | |
| const bad2 = compile('.x { bend: swing }'); | |
| check('2 nonsense is a LOUD error', bad2.errors.length === 1 && /bare number of degrees/.test(bad2.errors[0].message)); | |
| check('2 nothing emitted on error', bad2.doc.rules.length === 0); | |
| } | |
| console.log('3. stretch'); | |
| { | |
| const { doc, errors } = compile(` | |
| .a { stretch: 1.2 } | |
| .b { stretch: 1.4 vertical } | |
| .c { stretch: 0.8 y } | |
| `); | |
| check('3 no errors', errors.length === 0, JSON.stringify(errors)); | |
| const a = declOf(doc, '.a', '@stretch'); | |
| check('3 default axis is horizontal', a.factor === 1.2 && a.axis === 'horizontal', JSON.stringify(a)); | |
| check('3 vertical keyword', declOf(doc, '.b', '@stretch').axis === 'vertical'); | |
| check('3 y is an alias', declOf(doc, '.c', '@stretch').axis === 'vertical'); | |
| const z = compile('.x { stretch: 0 }'); | |
| check('3 zero is a LOUD error', z.errors.length === 1 && /flatten/.test(z.errors[0].message), JSON.stringify(z.errors)); | |
| const neg = compile('.x { stretch: -2 }'); | |
| check('3 negative is a LOUD error', neg.errors.length === 1 && /positive factor/.test(neg.errors[0].message)); | |
| const word = compile('.x { stretch: rubber }'); | |
| check('3 words are a LOUD error', word.errors.length === 1 && /positive factor/.test(word.errors[0].message)); | |
| } | |
| console.log('4. bend-around'); | |
| { | |
| const { doc, errors } = compile(` | |
| nav { bend-around: -24 } | |
| .dome { bend-around: 60 } | |
| `); | |
| check('4 no errors', errors.length === 0, JSON.stringify(errors)); | |
| check('4 smile arc passes', declOf(doc, 'nav', '@bend-around') === -24); | |
| check('4 dome arc passes', declOf(doc, '.dome', '@bend-around') === 60); | |
| const cap = compile('.x { bend-around: 200 }'); | |
| check('4 ±170 cap is LOUD', cap.errors.length === 1 && /±170/.test(cap.errors[0].message), JSON.stringify(cap.errors)); | |
| } | |
| console.log('5. composeDeform - one transform to rule them all'); | |
| { | |
| let c = composeDeform({ bend: 20 }, '', { h: 100 }); | |
| check('5 bend = perspective rotateX', c.transform === 'perspective(900px) rotateX(20deg)', c.transform); | |
| c = composeDeform({ fold: 30 }, '', { h: 100 }); | |
| check('5 fold bakes a top pivot', c.transform === 'perspective(900px) translateY(-50px) rotateX(30deg) translateY(50px)', c.transform); | |
| check('5 inline mode sets no origin', c.origin === null); | |
| c = composeDeform({ bendUp: 12 }, '', null); | |
| check('5 keyframe mode emits origin bottom', c.transform === 'perspective(900px) rotateX(12deg)' && c.origin === '50% 100%', JSON.stringify(c)); | |
| c = composeDeform({ stretch: 2 }, '', {}); | |
| check('5 stretch keeps volume', c.transform === 'scaleX(2) scaleY(0.5)', c.transform); | |
| c = composeDeform({ stretch: 2, stretchAxis: 'vertical' }, '', {}); | |
| check('5 vertical stretch flips axes', c.transform === 'scaleY(2) scaleX(0.5)', c.transform); | |
| c = composeDeform({ bend: 10 }, 'rotate(-6deg)', {}); | |
| check('5 user transform runs inside', c.transform === 'perspective(900px) rotateX(10deg) rotate(-6deg)', c.transform); | |
| c = composeDeform({}, 'none', {}); | |
| check('5 empty deform + none stays none', c.transform === 'none', c.transform); | |
| c = composeDeform({ bend: 20, stretch: 1.2, fold: 15 }, '', { h: 80 }); | |
| check('5 members compose in order', c.transform === | |
| 'perspective(900px) rotateX(20deg) translateY(-40px) rotateX(15deg) translateY(40px) scaleX(1.2) scaleY(0.8333)', c.transform); | |
| } | |
| console.log('6. arcLayout - bend-around math'); | |
| { | |
| const a = arcLayout(5, 400, 40); | |
| check('6 radius from chord + angle', Math.abs(a.R - 200 / Math.sin(20 * Math.PI / 180)) < 0.01, a.R); | |
| check('6 angles spread evenly', JSON.stringify(a.angles) === JSON.stringify([-20, -10, 0, 10, 20]), JSON.stringify(a.angles)); | |
| const s = arcLayout(3, 400, -30); | |
| check('6 negative opens a smile', JSON.stringify(s.angles) === JSON.stringify([15, 0, -15]), JSON.stringify(s.angles)); | |
| check('6 one child cannot arc', arcLayout(1, 400, 40) === null); | |
| check('6 zero degrees cannot arc', arcLayout(3, 400, 0) === null); | |
| check('6 zero width cannot arc', arcLayout(3, 0, 40) === null); | |
| } | |
| console.log('7. motions: bend keyframes compose into transform frames'); | |
| { | |
| const { doc, errors } = compile(` | |
| motion wave { | |
| from { bend: 0 } | |
| to { bend: 30; fold: 20 } | |
| } | |
| .x { animate: wave 500 } | |
| `); | |
| check('7 no errors', errors.length === 0, JSON.stringify(errors)); | |
| const spec = declOf(doc, '.x', '@animate'); | |
| const [f0, f1] = spec.frames; | |
| check('7 from frame is neutral', f0.props.transform === 'none', JSON.stringify(f0)); | |
| check('7 to frame carries both rotations', /rotateX\(30deg\)/.test(f1.props.transform) && /rotateX\(20deg\)/.test(f1.props.transform), JSON.stringify(f1)); | |
| check('7 fold pins the pivot via origin', f1.props.transformOrigin === '50% 0%', JSON.stringify(f1.props)); | |
| } | |
| console.log('8. gradient - arrow + comma stops'); | |
| { | |
| const { doc, errors } = compile(` | |
| .a { gradient: linear 135, #ff0055 -> #00d4ff } | |
| .b { gradient: linear to bottom right, red, blue } | |
| .c { gradient: linear 90, #ff0055 20% -> #00d4ff 80% } | |
| .d { gradient: radial circle at center, white -> black } | |
| .e { gradient: conic from 45 at center, red, blue } | |
| .f { gradient: linear, cyan, magenta } | |
| `); | |
| check('8 no errors', errors.length === 0, JSON.stringify(errors)); | |
| check('8 arrow syntax', declOf(doc, '.a', 'background-image') === 'linear-gradient(135deg, #ff0055, #00d4ff)', declOf(doc, '.a', 'background-image')); | |
| check('8 direction words', declOf(doc, '.b', 'background-image') === 'linear-gradient(to bottom right, red, blue)'); | |
| check('8 per-stop positions', declOf(doc, '.c', 'background-image') === 'linear-gradient(90deg, #ff0055 20%, #00d4ff 80%)', declOf(doc, '.c', 'background-image')); | |
| check('8 radial shape', declOf(doc, '.d', 'background-image') === 'radial-gradient(circle at center, white, black)'); | |
| check('8 conic prefix', declOf(doc, '.e', 'background-image') === 'conic-gradient(from 45deg at center, red, blue)'); | |
| check('8 comma-only stops', declOf(doc, '.f', 'background-image') === 'linear-gradient(180deg, cyan, magenta)', declOf(doc, '.f', 'background-image')); | |
| } | |
| console.log('9. gradient - tokens, alpha, loud errors'); | |
| { | |
| const { doc, errors } = compile(` | |
| tokens { color { flame: #ff5533 } } | |
| .a { gradient: linear 135, color.flame -> color.ink/40 -> color.paper } | |
| `); | |
| check('9 no errors', errors.length === 0, JSON.stringify(errors)); | |
| const img = declOf(doc, '.a', 'background-image'); | |
| check('9 token stop resolves to var()', img.includes('var(--dssl-color-flame)'), img); | |
| check('9 alpha stop becomes color-mix', img.includes('color-mix(in oklab, var(--dssl-color-ink) 40%, transparent)'), img); | |
| check('9 three stops survive', img.indexOf('var(--dssl-color-flame)') < img.indexOf('color-mix') && img.indexOf('color-mix') < img.indexOf('var(--dssl-color-paper)'), img); | |
| const nk = compile('.a { gradient: magenta -> cyan }'); | |
| check('9 missing kind is LOUD', nk.errors.length === 1 && /needs a kind/.test(nk.errors[0].message), JSON.stringify(nk.errors)); | |
| const one = compile('.a { gradient: linear 135, #a }'); | |
| check('9 single stop is LOUD', one.errors.length === 1 && /at least two stops/.test(one.errors[0].message), JSON.stringify(one.errors)); | |
| const dir = compile('.a { gradient: linear sideways, red -> blue }'); | |
| check('9 bad direction is LOUD', dir.errors.length === 1 && /not a linear gradient direction/.test(dir.errors[0].message), JSON.stringify(dir.errors)); | |
| const rad = compile('.a { gradient: radial banana, red -> blue }'); | |
| check('9 bad radial atom is LOUD', rad.errors.length === 1 && /radial gradient shape/.test(rad.errors[0].message), JSON.stringify(rad.errors)); | |
| const typo = compile('.a { gradient: linear sideways, red, blue }'); | |
| check('9 comma-mode typo is still LOUD', typo.errors.length >= 1 && /not a color/.test(typo.errors[0].message), JSON.stringify(typo.errors)); | |
| } | |
| console.log('10. gradient-text'); | |
| { | |
| const { doc, errors } = compile('.h { gradient-text: linear 90, #f0f -> #0ff }'); | |
| check('10 no errors', errors.length === 0, JSON.stringify(errors)); | |
| const d = doc.rules[0].decls; | |
| check('10 five declarations', d.length === 5, JSON.stringify(d.map(x => x.prop))); | |
| check('10 clip text (std + webkit)', d.some(x => x.prop === 'background-clip' && x.value === 'text') && d.some(x => x.prop === '-webkit-background-clip')); | |
| check('10 glyphs go transparent', d.some(x => x.prop === 'color' && x.value === 'transparent') && d.some(x => x.prop === '-webkit-text-fill-color')); | |
| } | |
| console.log('11. gradient-flow'); | |
| { | |
| const { doc, errors } = compile(` | |
| .a { gradient-flow: 6s } | |
| .b { gradient-flow: 900 bounce alternate } | |
| .c { gradient-flow: none } | |
| `); | |
| check('11 no errors', errors.length === 0, JSON.stringify(errors)); | |
| const a = declOf(doc, '.a', '@gradient-flow'); | |
| check('11 bare duration is ms, 6s is 6000', a.duration === 6000 && a.easing === 'linear' && a.direction === 'normal', JSON.stringify(a)); | |
| const b = declOf(doc, '.b', '@gradient-flow'); | |
| check('11 easing + alternate', b.duration === 900 && b.easing.includes('cubic-bezier') && b.direction === 'alternate', JSON.stringify(b)); | |
| check('11 none emits a null (cancels)', declOf(doc, '.c', '@gradient-flow') === null); | |
| const bad = compile('.a { gradient-flow: soon }'); | |
| check('11 bad duration is LOUD', bad.errors.length === 1 && /takes a duration/.test(bad.errors[0].message), JSON.stringify(bad.errors)); | |
| const part = compile('.a { gradient-flow: 6s wild }'); | |
| check('11 bad part is LOUD', part.errors.length === 1 && /unknown gradient-flow part/.test(part.errors[0].message)); | |
| } | |
| console.log('12. transition aliases: bend/stretch/fold ride transform'); | |
| { | |
| const { doc, errors } = compile('.btn { transition: 200 snap bend, 150 stretch, 100 fold }'); | |
| check('12 no errors', errors.length === 0, JSON.stringify(errors)); | |
| const t = declOf(doc, '.btn', 'transition'); | |
| check('12 all three became transform', (t.match(/transform \d+ms/g) || []).length === 3, t); | |
| } | |
| console.log('13. pointer-events - transparent overlays stop eating selection'); | |
| { | |
| const { doc, errors } = compile(` | |
| .veil { pointer-events: none } | |
| .hit { pointer-events: auto } | |
| `); | |
| check('13 no errors', errors.length === 0, JSON.stringify(errors)); | |
| check('13 none passes through', declOf(doc, '.veil', 'pointer-events') === 'none'); | |
| check('13 auto passes through', declOf(doc, '.hit', 'pointer-events') === 'auto'); | |
| } | |
| console.log('14. deform props inside pseudo blocks stay LOUD'); | |
| { | |
| const a = compile('.card::before { bend: 20 }'); | |
| check('14 bend-in-pseudo is LOUD', a.errors.length === 1 && /engine-native/.test(a.errors[0].message), JSON.stringify(a.errors)); | |
| check('14 nothing leaked', a.doc.shadowRules.length === 0 && a.doc.rules.length === 0); | |
| const b = compile('.card { ::after { gradient-flow: 3s } }'); | |
| check('14 nested gradient-flow-in-pseudo is LOUD', b.errors.length === 1 && /engine-native/.test(b.errors[0].message)); | |
| } | |
| console.log('15. six site stylesheets chained through the token registry'); | |
| { | |
| const fs = await import('node:fs'); | |
| const files = ['tokens', 'site', 'home', 'docs', 'playground', 'changelog']; | |
| let base = { ...DEFAULT_TOKENS }; | |
| let total = 0, ruleCount = 0; | |
| for (const f of files) { | |
| const src = fs.readFileSync(new URL(`../public/styles/${f}.dssl`, import.meta.url).pathname, 'utf8'); | |
| const { ast, errors } = parse(src, f + '.dssl'); | |
| const doc = resolve(ast, f + '.dssl', base); | |
| total += errors.length + doc.errors.length; | |
| ruleCount += doc.rules.length + doc.shadowRules.length; | |
| base = { ...base, ...doc.tokens }; | |
| } | |
| check('15 zero errors across all stylesheets', total === 0, `${total} errors`); | |
| check('15 rules still compile in bulk', ruleCount > 150, `${ruleCount}`); | |
| } | |
| console.log(`${pass} passed, ${fail} failed`); | |
| process.exit(fail ? 1 : 0); | |