Files
zeekay cb9a592127 Code blocks: a neutral ground on dark, not Dracula's blue-violet
The dark block's ground is the design system's --card (#0f0f0f); the tokens stay the theme's. The gate reads the new ground.

Claude-Session: https://claude.ai/code/session_01SCPMGSQsLNF2YtHFL4bwyp
2026-10-07 16:35:42 -07:00

59 lines
2.8 KiB
TypeScript

import { test, expect } from '@playwright/test'
import { readdirSync, readFileSync } from 'node:fs'
import { join } from 'node:path'
import { OUT, serveExport } from './export'
/**
* Code reads in the code theme, and the highlighter stays at build.
*
* components/code runs shiki in the export and ships coloured spans: @hanzo/ui's
* `codeTheme`, Dracula on the dark theme and GitHub Light on the light one, each
* colour written as `light-dark()` so the page's colour scheme picks it. Three
* kinds of page carry a sample — a connector, a product, a model — and each is
* read in both themes: the block's ground is neutral (the design system's card
* on dark, white on light — never Dracula's blue-violet), and a sample of real
* code is more than three colours.
*
* The second test is the other half of the promise: no shiki in the browser.
* Its grammars and engine are 49 KB gzipped at the least, measured, on pages
* that are under a JS budget.
*/
const ROUTES = ['/integrations/python', '/kms', '/enso']
const GROUND = { dark: 'rgb(15, 15, 15)', light: 'rgb(255, 255, 255)' } as const
for (const theme of ['dark', 'light'] as const)
for (const route of ROUTES)
test(`${route} sets its code in the ${theme} code theme`, async ({ page }) => {
const site = await serveExport()
try {
await page.addInitScript((t) => {
try {
localStorage.setItem('hanzo.appearance', JSON.stringify({ theme: t }))
} catch {}
}, theme)
await page.goto(site.url + route)
await expect.poll(() => page.evaluate(() => document.documentElement.classList.contains('light'))).toBe(theme === 'light')
const blocks = await page.evaluate(() =>
[...document.querySelectorAll('pre')]
.filter((pre) => (pre.getAttribute('style') ?? '').includes('light-dark('))
.map((pre) => ({
ground: getComputedStyle(pre).backgroundColor,
colours: new Set([...pre.querySelectorAll('span span')].map((s) => getComputedStyle(s).color)).size,
})),
)
expect(blocks.length, 'a highlighted block on the page').toBeGreaterThan(0)
for (const b of blocks) expect(b.ground).toBe(GROUND[theme])
expect(Math.max(...blocks.map((b) => b.colours)), 'token colours in the richest block').toBeGreaterThan(3)
} finally {
await site.close()
}
})
test('no highlighter ships to the browser', () => {
const chunks = join(OUT, '_next', 'static', 'chunks')
const walk = (dir: string): string[] =>
readdirSync(dir, { withFileTypes: true }).flatMap((e) => (e.isDirectory() ? walk(join(dir, e.name)) : [join(dir, e.name)]))
const shipped = walk(chunks).filter((f) => f.endsWith('.js') && /createHighlighterCore|oniguruma|shikijs/.test(readFileSync(f, 'utf8')))
expect(shipped, 'client chunks carrying shiki').toEqual([])
})