mirror of
https://github.com/olehomelchenko/astrolabe.git
synced 2026-08-08 10:12:34 +00:00
135 lines
4.6 KiB
TypeScript
135 lines
4.6 KiB
TypeScript
/**
|
||
* 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();
|
||
});
|
||
});
|