mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Replace the first-run placeholder seed with an onboarding canvas
This commit is contained in:
@@ -0,0 +1,96 @@
|
||||
/**
|
||||
* 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() {} }),
|
||||
}));
|
||||
|
||||
(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('greets the user and offers the primary create action', () => {
|
||||
expect(container.textContent).toContain('Welcome to Astrolabe');
|
||||
expect(container.textContent).toContain('Create your first snippet');
|
||||
});
|
||||
|
||||
test('renders one card per example, each with an Add control', () => {
|
||||
for (const example of CHART_EXAMPLES) {
|
||||
expect(container.textContent).toContain(example.name);
|
||||
expect(container.textContent).toContain(example.description);
|
||||
expect(container.querySelector(`button[aria-label="Add ${example.name}"]`)).not.toBeNull();
|
||||
}
|
||||
});
|
||||
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user