Files
astrolabe/src/app/components/Onboarding.test.tsx
T

84 lines
3.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 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 examples 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);
});
});