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

135 lines
4.6 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.
/**
* 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(<StorageMonitor />);
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();
});
});