mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
84 lines
3.3 KiB
TypeScript
84 lines
3.3 KiB
TypeScript
/**
|
||
* Onboarding canvas — the empty-workspace actions (spec §02).
|
||
*
|
||
* The live chart rendering is integration (vega-embed) and is mocked away here;
|
||
* what these tests own is the wiring: each affordance creates the right snippets
|
||
* in the store. Card previews are exercised only insofar as they don't crash.
|
||
*/
|
||
|
||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||
import { act } from 'react';
|
||
import { createRoot, type Root } from 'react-dom/client';
|
||
import { CHART_EXAMPLES, exampleSpecText } from '@core/examples';
|
||
import { sampleSpecText } from '@core/snippet';
|
||
import { useSnippetStore } from '../stores/SnippetStore';
|
||
import { Onboarding } from './Onboarding';
|
||
|
||
// The gallery renders one chart per card; stub the shared renderer so the test
|
||
// never touches vega-embed. A resolved no-op handle is enough — Onboarding only
|
||
// finalizes it on unmount.
|
||
vi.mock('../services/chart-renderer', () => ({
|
||
renderSpec: () => Promise.resolve({ destroy() {}, resize() {}, inspectData: () => null }),
|
||
}));
|
||
|
||
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||
|
||
let container: HTMLDivElement;
|
||
let root: Root;
|
||
|
||
beforeEach(() => {
|
||
useSnippetStore.getState().reset();
|
||
container = document.createElement('div');
|
||
document.body.appendChild(container);
|
||
root = createRoot(container);
|
||
act(() => root.render(<Onboarding />));
|
||
});
|
||
|
||
afterEach(() => {
|
||
act(() => root.unmount());
|
||
container.remove();
|
||
useSnippetStore.getState().reset();
|
||
vi.clearAllMocks();
|
||
});
|
||
|
||
/** Click the button whose accessible name (aria-label, else text) matches. */
|
||
function click(name: string) {
|
||
const button = Array.from(container.querySelectorAll('button')).find((b) =>
|
||
(b.getAttribute('aria-label') ?? b.textContent ?? '').includes(name),
|
||
);
|
||
if (!button) throw new Error(`button not found: ${name}`);
|
||
act(() => button.click());
|
||
}
|
||
|
||
describe('Onboarding', () => {
|
||
test('"Create your first snippet" creates one snippet from the sample template', () => {
|
||
click('Create your first snippet');
|
||
const { snippets, activeSnippetId } = useSnippetStore.getState();
|
||
expect(snippets).toHaveLength(1);
|
||
expect(snippets[0].spec).toBe(sampleSpecText());
|
||
expect(activeSnippetId).toBe(snippets[0].id);
|
||
});
|
||
|
||
test('an example’s Add creates that snippet, named and active', () => {
|
||
const scatter = CHART_EXAMPLES.find((e) => e.id === 'scatter')!;
|
||
click(`Add ${scatter.name}`);
|
||
const { snippets, activeSnippetId } = useSnippetStore.getState();
|
||
expect(snippets).toHaveLength(1);
|
||
expect(snippets[0].name).toBe(scatter.name);
|
||
expect(snippets[0].spec).toBe(exampleSpecText(scatter));
|
||
expect(activeSnippetId).toBe(snippets[0].id);
|
||
});
|
||
|
||
test('"Add all" adds every example and makes the bar chart active', () => {
|
||
click('Add all');
|
||
const { snippets, activeSnippetId } = useSnippetStore.getState();
|
||
expect(snippets).toHaveLength(CHART_EXAMPLES.length);
|
||
// Every example name is present.
|
||
const names = new Set(snippets.map((s) => s.name));
|
||
for (const example of CHART_EXAMPLES) expect(names.has(example.name)).toBe(true);
|
||
// The first example (bar) is the active one (staggered newest).
|
||
const active = snippets.find((s) => s.id === activeSnippetId);
|
||
expect(active?.name).toBe(CHART_EXAMPLES[0].name);
|
||
});
|
||
});
|