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>
80 lines
2.8 KiB
TypeScript
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();
|
|
}
|
|
});
|
|
});
|