Files
site-builder/craft/src/editor/CanvasTokenStyle.test.tsx
T
shadowdaoandClaude Opus 5.5 f8ac1fbe42 feat(site-builder): token module, canvas token variables, palette swatch row
Make var(--wsb-*) a first-class style value in the editor:

- utils/tokens.ts: TokenKey, TOKEN_VARS (moved from html-export.ts, same
  order/content as the backend), COLOR_TOKENS palette order, tokenRef,
  isTokenRef, tokenKeyOf, resolveTokenValue and buildCanvasTokenCss, which
  defines the 16 vars + token base CSS scoped to .canvas-device-frame
  (never :root) with every value through cssValue().
- editor/CanvasTokenStyle.tsx: <style data-wsb-canvas-tokens> driven by
  useSiteDesign(), mounted in App inside SiteDesignProvider, so design
  edits recolour token-backed values on the canvas instantly.
- panels/right/styles/TokenSwatchRow.tsx: palette-first swatch row that
  emits token references; .token-swatch styles appended to editor.css.
- html-export.ts imports TOKEN_VARS; export output pinned byte-identical
  to the pre-move baseline by hash in html-export.test.ts.

Refs #44

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-07 11:16:29 -07:00

80 lines
2.8 KiB
TypeScript

import React from 'react';
import { describe, test, expect, afterEach } from 'vitest';
import { createRoot } from 'react-dom/client';
import { act } from 'react-dom/test-utils';
import { renderEditorHarness, EditorHarness, ensureJsdomShims } from '../test-utils/editorHarness';
import { App } from '../App';
import { SiteDesignProvider, useSiteDesign, SiteDesignContextValue } from '../state/SiteDesignContext';
import { CanvasTokenStyle } from './CanvasTokenStyle';
let h: EditorHarness | null = null;
afterEach(() => {
h?.unmount();
h = null;
});
/** Grabs the live SiteDesign context so the test can drive updateDesign(). */
const DesignProbe: React.FC<{ out: { current: SiteDesignContextValue | null } }> = ({ out }) => {
out.current = useSiteDesign();
return null;
};
const styleText = () => {
const els = document.querySelectorAll('style[data-wsb-canvas-tokens]');
expect(els.length).toBe(1);
return els[0].textContent ?? '';
};
describe('CanvasTokenStyle (real editor harness)', () => {
test('defines the token vars in the document and follows updateDesign()', () => {
h = renderEditorHarness();
const ctx: { current: SiteDesignContextValue | null } = { current: null };
h.mountChild(
<SiteDesignProvider>
<CanvasTokenStyle />
<DesignProbe out={ctx} />
</SiteDesignProvider>,
);
const before = styleText();
expect(before).toContain('.canvas-device-frame{--wsb-primary:#3b82f6;');
expect(before).toContain('--wsb-heading-font:Inter, sans-serif');
expect(before).not.toContain(':root');
h.act(() => {
ctx.current!.updateDesign({ primaryColor: '#ff0000', linkColor: '#00ff00' });
});
const after = styleText();
expect(after).toContain('--wsb-primary:#ff0000');
expect(after).toContain('--wsb-link:#00ff00');
expect(after).not.toContain('--wsb-primary:#3b82f6');
h.act(() => {
ctx.current!.resetToDefaults();
});
expect(styleText()).toContain('--wsb-primary:#3b82f6');
});
});
describe('CanvasTokenStyle is mounted by <App> (standalone mode)', () => {
test('the real App renders exactly one canvas token <style> alongside the device frame', { timeout: 30000 }, async () => {
ensureJsdomShims();
const container = document.createElement('div');
document.body.appendChild(container);
const root = createRoot(container);
try {
await act(async () => {
root.render(<App whpConfig={null} />);
});
expect(container.querySelector('.canvas-device-frame')).not.toBeNull();
const styles = container.querySelectorAll('style[data-wsb-canvas-tokens]');
expect(styles.length).toBe(1);
expect(styles[0].textContent).toContain('.canvas-device-frame{--wsb-primary:#3b82f6;');
} finally {
act(() => root.unmount());
container.remove();
}
});
});