import { describe, expect, it } from 'vitest'; import { TOKENS, hexFor, resolveToken } from '../showcase/src/chrome/lib/token-values'; /** * The showcase's tokens page against the file it reads. * * `chrome/lib/token-values.ts` recovers CBAR's published hex by re-reading * `tokens.css` **as text** and pulling the trailing `/* #004976 *\/` comment off * each declaration — comments do not survive into the CSSOM, so there is no * other way to get the number a designer can compare against Figma. That makes * one regex the coupling between a generated file and a page, and the failure * mode is silent: a `figma-sync` that reformats a declaration does not throw, * it just stops matching, and every swatch quietly falls through to a *painted* * approximation (`#014976` for a colour CBAR publishes as `#004976`). * * Nothing else would notice. `test/contrast.test.ts` parses the same file with * its own regex for the oklch triples, and a11y runs in a browser where the page * still renders — wrongly, but it renders. So this suite exists to make the * reformat loud. * * The counts below are floors rather than exact numbers on purpose: adding a * ramp step is normal and must not fail a suite, while a format change takes the * count to zero or near it. */ describe('token-values', () => { it('parses every --ui-* declaration in tokens.css', () => { /* 138 today. A floor, because tokens get added; a *zero* is the regression this catches, and any rewrite that breaks the declaration shape takes the whole table with it rather than a few rows. */ expect(TOKENS.size).toBeGreaterThanOrEqual(120); }); it('keeps the hex comment on the declarations that came from Figma', () => { const withHex = [...TOKENS.values()].filter((entry) => entry.hex); /* 74 today — every colour the extraction read out of CBAR's file. This is the assertion that actually guards the comment format: the declarations still parse without it, they just lose their published value. */ expect(withHex.length).toBeGreaterThanOrEqual(60); }); /* * The assertions below name a token but never its colour. * * They used to pin the literal navy — `oklch(0.392 0.098 244.9)` / `#004976` * — which made the suite fail the moment anyone ran `brand` and made the kit * theirs. A kit whose tests only pass while it is still someone else's is not * a publishable kit. What these tests are actually for is the *parser*: that * a declaration splits into a value and a hex, and that a prose comment never * arrives as one. The brand colour is incidental to all of it, so it is read * from the file rather than restated here. */ const brand500 = TOKENS.get('--ui-color-brand-500'); it('reads a ramp step as its authored value plus its published hex', () => { expect(brand500?.value).toMatch(/^oklch\([\d.]+ [\d.]+ [\d.]+\)$/); expect(brand500?.hex).toMatch(/^#[0-9a-f]{6}$/i); }); it('does not mistake a prose comment for a value', () => { /* Some steps carry an explanation where a hex would be — an interpolated step notes that the source ramp stopped short. Reporting that prose as a colour would be worse than reporting nothing, which is why the comment group in the regex is anchored to a hex literal. Asserted as a property over every entry rather than against one token: which steps are interpolated is a fact about whichever design system the kit currently wears, and naming one is how this test became brittle. */ const malformed = [...TOKENS.entries()].filter( ([, entry]) => entry.hex !== undefined && !/^#[0-9a-f]{6}$/i.test(entry.hex) ); expect(malformed).toEqual([]); }); it('parses the two declarations that wrap onto a second line', () => { /* `[^;]+` spanning newlines is what makes these work; a regex anchored to one line would drop them and, worse, leave a half-value behind. */ expect(TOKENS.get('--ui-font-sans')?.value).toContain('sans-serif'); expect(TOKENS.get('--ui-font-sans')?.value).not.toContain('\n'); expect(TOKENS.get('--ui-shadow-lg')?.value).toBe( '0 4px 6px -4px oklch(0.145 0 0 / 0.1), 0 10px 15px -3px oklch(0.145 0 0 / 0.1)' ); }); it('follows a var() alias to the hex behind it', () => { /* `--ui-color-tertiary-500` is CBAR's yellow ramp; `--ui-color-third-500` is the deprecated spelling kept as an alias for one major. Both must answer with the same published colour, or the tokens page shows the deprecated ramp as a set of blanks. */ const direct = TOKENS.get('--ui-color-tertiary-500'); expect(direct?.hex).toBeTruthy(); const viaAlias = resolveToken('var(--ui-color-third-500)'); expect(viaAlias.hex).toBe(direct?.hex?.toUpperCase()); expect(viaAlias.derived).toBeUndefined(); }); it('prefers a hex sitting on the alias itself', () => { /* `--ui-color-yellow-surface` is a role name the kit gives a ramp step, and the comment on the alias line is the authoritative value — see the note in token-values.ts. Resolving past it to the step would be equally true and equally useless, so the walk stops at the first hex it meets. */ const resolved = resolveToken('var(--ui-color-yellow-surface)'); expect(resolved.hex).toBe('#F3C67F'); expect(resolved.token).toBe('--ui-color-yellow-surface'); }); it('credits a token asked for by name over the value read off the document', () => { /* `hexFor` is what a swatch calls: the live value comes from `getComputedStyle`, but a primitive with its own comment answers from the file. The second argument is deliberately wrong here to prove which side wins. */ const published = brand500?.hex?.toUpperCase(); expect(hexFor('--ui-color-brand-500', brand500!.value).hex).toBe(published); expect(hexFor('--ui-color-brand-500', 'rgb(255 0 0)').hex).toBe(published); }); it('recovers the name behind a flattened value', () => { /* Tailwind v4 resolves `var(--ui-color-brand-500)` before the browser reports it, so a semantic role arrives as the bare literal with the name gone. Matching that literal back is what puts CBAR's hex on a role's swatch instead of a painted approximation. */ const resolved = resolveToken(brand500!.value); expect(resolved.hex).toBe(brand500?.hex?.toUpperCase()); expect(resolved.token).toBe('--ui-color-brand-500'); expect(resolved.derived).toBeUndefined(); }); });