// @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>); const seen: string[] = []; const Child = memo(function Child() { const app = useApp(); const [local] = useState(() => 'retained'); seen.push(app.search); return {local}:{app.search}; }); let changeSearch: (search: string) => void = () => undefined; function Harness({ active }: { active: boolean }) { changeSearch = useApp().setSearch; return ; } const container = document.createElement('div'); const root = createRoot(container); const render = (active: boolean) => act(async () => root.render()); 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' ? :
Matrix
)); 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 {range.endIndex}; } 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()); } }); });