83 lines
3.8 KiB
TypeScript
83 lines
3.8 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { act, createElement, memo, useState } from 'react';
|
|
import { createRoot } from 'react-dom/client';
|
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
import { AppProvider, RetainedAppState, useApp } from '../../src/app/AppProvider';
|
|
import { AssessorApiClient } from '../../src/api/assessor-api-client';
|
|
import { RawDataView } from '../../src/features/dataset-viewer/RawDataView';
|
|
import { useVirtualRows } from '../../src/hooks/use-virtual-rows';
|
|
import { makeSheet } from '../../src/importers/sheet-factory';
|
|
|
|
afterEach(() => { vi.restoreAllMocks(); vi.unstubAllGlobals(); });
|
|
|
|
describe('navigation rendering lifetime', () => {
|
|
it('keeps a retained view asleep on context updates and resumes with the latest state', async () => {
|
|
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
|
vi.spyOn(AssessorApiClient.prototype, 'health').mockResolvedValue({ status: 'ok' } as Awaited<ReturnType<AssessorApiClient['health']>>);
|
|
const seen: string[] = [];
|
|
const Child = memo(function Child() {
|
|
const app = useApp();
|
|
const [local] = useState(() => 'retained');
|
|
seen.push(app.search);
|
|
return <output>{local}:{app.search}</output>;
|
|
});
|
|
let changeSearch: (search: string) => void = () => undefined;
|
|
function Harness({ active }: { active: boolean }) {
|
|
changeSearch = useApp().setSearch;
|
|
return <RetainedAppState active={active}><Child/></RetainedAppState>;
|
|
}
|
|
const container = document.createElement('div');
|
|
const root = createRoot(container);
|
|
const render = (active: boolean) => act(async () => root.render(<AppProvider><Harness active={active}/></AppProvider>));
|
|
try {
|
|
await render(true);
|
|
await act(async () => changeSearch('first'));
|
|
expect(container.textContent).toBe('retained:first');
|
|
await render(false);
|
|
seen.length = 0;
|
|
await act(async () => changeSearch('latest'));
|
|
expect(seen).toEqual([]);
|
|
expect(container.textContent).toBe('retained:first');
|
|
await render(true);
|
|
expect(container.textContent).toBe('retained:latest');
|
|
} finally { await act(async () => root.unmount()); }
|
|
});
|
|
|
|
it('builds filtered raw text only on opening and reuses it for unrelated rerenders', async () => {
|
|
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
|
const sheet = makeSheet('Synthetic', [['Title', 'Status'], ['Synthetic one', 'Failed']], '', 'synthetic.csv');
|
|
let reads = 0;
|
|
const fields = sheet.analysis.records[0].fields;
|
|
Object.defineProperty(sheet.analysis.records[0], 'fields', { get: () => { reads += 1; return fields; } });
|
|
const container = document.createElement('div');
|
|
const root = createRoot(container);
|
|
const render = (view: string, search = '') => act(async () => root.render(view === 'raw'
|
|
? <RawDataView records={sheet.analysis.records} raw="Synthetic original" filtered search={search}/>
|
|
: <div>Matrix</div>));
|
|
try {
|
|
await render('columns');
|
|
expect(reads).toBe(0);
|
|
await render('raw');
|
|
expect(container.textContent).toContain('Status: Failed');
|
|
const openedReads = reads;
|
|
await render('raw', 'synthetic');
|
|
expect(reads).toBe(openedReads);
|
|
expect(container.querySelector('mark')).not.toBeNull();
|
|
} finally { await act(async () => root.unmount()); }
|
|
});
|
|
|
|
it('limits the first tile window to the viewport and overscan', async () => {
|
|
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
|
function Harness() {
|
|
const range = useVirtualRows(8257, 235, 2);
|
|
return <output>{range.endIndex}</output>;
|
|
}
|
|
const container = document.createElement('div');
|
|
const root = createRoot(container);
|
|
try {
|
|
await act(async () => root.render(createElement(Harness)));
|
|
expect(Number(container.textContent)).toBe(Math.ceil(window.innerHeight / 235) + 4);
|
|
} finally { await act(async () => root.unmount()); }
|
|
});
|
|
});
|