/** * StorageMonitor render tests. * * Strategy: seed the snippet + dataset stores (the measured inputs) and mock * readOriginUsage (the async browser adapter) so tests run in happy-dom without a * real Storage Manager, then assert on the rendered DOM — legend text, the * decorative bar, and the absence of a meter/threshold-warning (the redesign). * * CSS Modules are identity-mapped under Vitest, so class tokens come through as-is. */ import { afterEach, beforeEach, describe, expect, test, vi } from 'vitest'; import { act } from 'react'; import { createRoot, type Root } from 'react-dom/client'; import type { Snippet } from '@core/snippet'; import type { Dataset } from '@core/dataset'; import StorageMonitor from './StorageMonitor'; import { useSnippetStore } from '../stores/SnippetStore'; import { useDatasetStore } from '../stores/DatasetStore'; // Mock the browser adapter — tests must not touch navigator.storage. vi.mock('../infrastructure/storage-estimate', () => ({ readOriginUsage: vi.fn(), })); import { readOriginUsage } from '../infrastructure/storage-estimate'; const mockUsage = vi.mocked(readOriginUsage); (globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true; // ---- fixtures --------------------------------------------------------------- /** A snippet is just serialized for its byte size, so a minimal shape suffices. */ function seedSnippets(count: number): void { const snippets = Array.from( { length: count }, (_, i) => ({ id: `s${i}`, name: `snippet ${i}`, draftSpec: '{"x":1}' }) as unknown as Snippet, ); useSnippetStore.setState({ snippets }); } /** Datasets carry their own byte `size`; only that field is read. */ function seedDatasets(...sizes: number[]): void { const datasets = sizes.map( (size, i) => ({ id: i + 1, name: `data ${i}`, size }) as unknown as Dataset, ); useDatasetStore.setState({ datasets }); } // ---- test setup ------------------------------------------------------------- let container: HTMLDivElement; let root: Root; beforeEach(() => { container = document.createElement('div'); document.body.appendChild(container); root = createRoot(container); }); afterEach(() => { act(() => root.unmount()); container.remove(); useSnippetStore.setState({ snippets: [] }); useDatasetStore.setState({ datasets: [] }); vi.clearAllMocks(); }); async function renderMonitor() { await act(async () => { root.render(); await Promise.resolve(); // flush the async readOriginUsage() resolution }); } // ---- tests ------------------------------------------------------------------ const MB = 1024 * 1024; describe('StorageMonitor (composition)', () => { test('shows Snippets, Datasets and App segments once past the user-data floor', async () => { seedSnippets(2); seedDatasets(12 * MB); // 12 MB of datasets — over the 10 MB floor mockUsage.mockResolvedValue(20 * MB); await renderMonitor(); const root_ = container.querySelector('[aria-label="Storage in use"]'); expect(root_).not.toBeNull(); const legend = root_!.textContent ?? ''; expect(legend).toContain('Snippets'); expect(legend).toContain('Datasets'); expect(legend).toContain('App'); // usage − snippets − datasets expect(legend).toContain('in use'); }); test('is not a meter and shows no threshold warning (redesign)', async () => { seedSnippets(1); seedDatasets(11 * MB); mockUsage.mockResolvedValue(20 * MB); await renderMonitor(); expect(container.querySelector('[role="meter"]')).toBeNull(); expect(container.querySelector('[role="status"]')).toBeNull(); // The bar itself is decorative. expect(container.querySelector('[aria-hidden="true"]')).not.toBeNull(); }); test('omits the App segment when the Storage Manager API is unavailable', async () => { seedSnippets(1); seedDatasets(11 * MB); mockUsage.mockResolvedValue(undefined); // API absent await renderMonitor(); const text = container.textContent ?? ''; expect(text).toContain('Snippets'); expect(text).toContain('Datasets'); expect(text).not.toContain('App'); }); test('stays hidden below the user-data floor, even with origin usage', async () => { seedSnippets(3); seedDatasets(2 * MB); // ~2 MB of user data — under the 10 MB floor mockUsage.mockResolvedValue(9 * MB); await renderMonitor(); expect(container.firstElementChild).toBeNull(); }); test('renders nothing for a truly empty workspace', async () => { seedSnippets(0); seedDatasets(); mockUsage.mockResolvedValue(undefined); await renderMonitor(); expect(container.firstElementChild).toBeNull(); }); });