mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 02:02:33 +00:00
Chart theming: selectable chart theme + spec↔config merge/extract
This commit is contained in:
@@ -10,6 +10,8 @@
|
||||
import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest';
|
||||
import { act } from 'react';
|
||||
import { createRoot, type Root } from 'react-dom/client';
|
||||
import { chartConfigForSelection } from '@core/vega-themes';
|
||||
import { useAppStore } from '../stores/AppStore';
|
||||
import { usePreviewStore } from '../stores/PreviewStore';
|
||||
import { useSnippetStore } from '../stores/SnippetStore';
|
||||
import { useDatasetStore } from '../stores/DatasetStore';
|
||||
@@ -24,10 +26,12 @@ const H = vi.hoisted(() => ({
|
||||
calls: 0,
|
||||
pending: [] as Array<() => void>,
|
||||
destroyed: [] as number[],
|
||||
configs: [] as unknown[],
|
||||
}));
|
||||
vi.mock('../services/chart-renderer', () => ({
|
||||
renderSpec: (node: HTMLElement) => {
|
||||
renderSpec: (node: HTMLElement, _spec: unknown, config: unknown) => {
|
||||
const id = ++H.calls;
|
||||
H.configs.push(config);
|
||||
return new Promise((resolve) => {
|
||||
H.pending.push(() => {
|
||||
node.replaceChildren(); // a real embed wipes then rebuilds the host
|
||||
@@ -64,6 +68,7 @@ beforeEach(() => {
|
||||
H.calls = 0;
|
||||
H.pending.length = 0;
|
||||
H.destroyed.length = 0;
|
||||
H.configs.length = 0;
|
||||
usePreviewStore.setState({ error: null, busy: false });
|
||||
useSnippetStore.getState().reset();
|
||||
useDatasetStore.getState().reset();
|
||||
@@ -82,21 +87,27 @@ afterEach(() => {
|
||||
});
|
||||
|
||||
describe('LivePreview busy overlay', () => {
|
||||
// The overlay is the aria-hidden element carrying the "Rendering…" label — a
|
||||
// bare [aria-hidden] query would also match decorative bits of the header
|
||||
// controls (e.g. the chart-theme select's caret).
|
||||
const overlay = () =>
|
||||
[...container.querySelectorAll('[aria-hidden="true"]')].find((el) =>
|
||||
/rendering/i.test(el.textContent ?? ''),
|
||||
) ?? null;
|
||||
|
||||
test('does not render the busy overlay when busy=false', () => {
|
||||
// The overlay element should not be in the DOM at all during normal operation.
|
||||
expect(container.querySelector('[aria-hidden="true"]')).toBeNull();
|
||||
expect(overlay()).toBeNull();
|
||||
});
|
||||
|
||||
test('renders the busy overlay when PreviewStore.busy=true', () => {
|
||||
act(() => usePreviewStore.setState({ busy: true }));
|
||||
const overlay = container.querySelector('[aria-hidden="true"]');
|
||||
expect(overlay).not.toBeNull();
|
||||
expect(overlay()).not.toBeNull();
|
||||
});
|
||||
|
||||
test('overlay carries a visible label for sighted users', () => {
|
||||
act(() => usePreviewStore.setState({ busy: true }));
|
||||
const label = container.querySelector('[aria-hidden="true"]')?.textContent;
|
||||
expect(label).toMatch(/rendering/i);
|
||||
expect(overlay()?.textContent).toMatch(/rendering/i);
|
||||
});
|
||||
|
||||
test('the preview body carries aria-busy=true when busy', () => {
|
||||
@@ -113,9 +124,9 @@ describe('LivePreview busy overlay', () => {
|
||||
|
||||
test('overlay disappears when busy returns to false', () => {
|
||||
act(() => usePreviewStore.setState({ busy: true }));
|
||||
expect(container.querySelector('[aria-hidden="true"]')).not.toBeNull();
|
||||
expect(overlay()).not.toBeNull();
|
||||
act(() => usePreviewStore.setState({ busy: false }));
|
||||
expect(container.querySelector('[aria-hidden="true"]')).toBeNull();
|
||||
expect(overlay()).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -186,3 +197,32 @@ describe('LivePreview render serialization', () => {
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('LivePreview chart theme', () => {
|
||||
const tick = (ms = 6000) => act(async () => void (await vi.advanceTimersByTimeAsync(ms)));
|
||||
|
||||
test('the selected chart theme decides the config passed to renderSpec', async () => {
|
||||
vi.useFakeTimers();
|
||||
try {
|
||||
act(() => {
|
||||
useAppStore.setState({ chartTheme: 'stock', uiTheme: 'dark' });
|
||||
useSnippetStore.setState({ draftText: '{"data":{"values":[]},"mark":"point"}' });
|
||||
});
|
||||
await tick();
|
||||
act(() => H.pending[0]());
|
||||
await tick(0);
|
||||
// Stock = inject nothing; vega-lite's own defaults apply.
|
||||
expect(H.configs[0]).toEqual({});
|
||||
|
||||
// Switching the theme re-renders with the new config (no text change needed).
|
||||
act(() => useAppStore.setState({ chartTheme: 'astrolabe' }));
|
||||
await tick();
|
||||
act(() => H.pending[1]());
|
||||
await tick(0);
|
||||
expect(H.configs[1]).toEqual(chartConfigForSelection('astrolabe', 'dark'));
|
||||
} finally {
|
||||
vi.useRealTimers();
|
||||
act(() => useAppStore.setState({ chartTheme: 'astrolabe', uiTheme: 'light' }));
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user