Template
Gate hardening:
- strict + noUncheckedIndexedAccess in tsconfig.app.json; fix resulting errors
- check-tokens.mjs: scan inline style={{ }} in ts/tsx, ban named colors and
rgb()/hsl()/oklch()/lab(), read multi-line values, fail on undefined var(--x)
- vitest (24 tests: overlay, icon-registry, useTimeout, token guard) wired into
npm run check; GitHub Actions CI workflow
Single-source refactors:
- new Overlay primitive owns focus trap/Escape/scroll-lock/focus-restore and
conditional aria-labelledby/aria-describedby; Dialog + Sheet are thin skins
(overlayBehavior.ts exports trapTabKey/useOverlayLifecycle for SearchPalette)
- icon-registry.ts is the one icon map: strict IconName, aliases typed, dev
throws on unknown names instead of silently rendering <Layers/>; IconMatrix
consumes the shared registry (41 curated)
- useTimeout hook replaces fire-and-forget setTimeout in DeploymentModal,
SettingsPanel, IconMatrix, CodeSample (timers die on close/unmount)
Bug fixes:
- SearchPalette: role=dialog/aria-modal, focus trap + restore, combobox/listbox
semantics, labelled input, selection clamped to results
- theme: pre-paint script in index.html (localStorage -> prefers-color-scheme ->
dark), useTheme no longer writes DOM during state init
- StatCard: direction (arrow) decoupled from trend (sentiment); latency drop
renders green "↓ -4.2ms" instead of "↑ -4.2ms"
- sidebar collapse button wired (aria-pressed + collapsed rail CSS); pattern
routing keys on patternId from library.ts, not English labels
- remove 25 dead legacy tokens (--panel*, --line*, --text-soft, --critical,
--pink, --blue, ...); migrate --bg/--text/--positive/--warning usages
- title "Northstar Design System", real README, AGENTS.md structure/theme/
overlay/registry/test docs updated; dev error sink (#dev-errors +
ErrorBoundary) so agents can read runtime errors via inspect
34 lines
1.3 KiB
TypeScript
34 lines
1.3 KiB
TypeScript
import { render } from '@testing-library/react'
|
|
import { describe, expect, it } from 'vitest'
|
|
import { Icon, type IconName } from '../src/components/Icon'
|
|
import { iconMap, iconRegistry } from '../src/components/icon-registry'
|
|
|
|
describe('icon registry (single source of truth)', () => {
|
|
it('has unique names', () => {
|
|
const names = iconRegistry.map(entry => entry.name)
|
|
expect(new Set(names).size).toBe(names.length)
|
|
})
|
|
|
|
it('maps every name (including aliases) to a real registry component', () => {
|
|
const components = new Set(iconRegistry.map(entry => entry.component))
|
|
for (const [name, component] of Object.entries(iconMap)) {
|
|
expect(components.has(component), `icon "${name}" has no canonical twin in the registry`).toBe(true)
|
|
}
|
|
})
|
|
|
|
it('resolves every registry entry through iconMap', () => {
|
|
for (const entry of iconRegistry) {
|
|
expect(iconMap[entry.name]).toBe(entry.component)
|
|
}
|
|
})
|
|
|
|
it('renders a registry icon by name', () => {
|
|
const { container } = render(<Icon name="sparkles" data-testid="sparkles" />)
|
|
expect(container.querySelector('svg')).toBeInTheDocument()
|
|
})
|
|
|
|
it('throws on unknown names in dev instead of silently rendering a wrong glyph', () => {
|
|
expect(() => render(<Icon name={'definitely-not-an-icon' as IconName} />)).toThrow(/Unknown icon name/)
|
|
})
|
|
})
|