test(ui): cover logs page redesign with virtuoso-mocked render

Three component tests under jsdom: skeleton render, search filter wiring,
row-click -> detail-panel-header flow. react-virtuoso is mocked at module
level (vi.mock) so jsdom can render rows without layout measurements.
Production behavior unchanged.

Refs #1142, #1138
This commit is contained in:
Tam Nhu Tran
2026-04-30 13:04:21 -04:00
parent ae3240d4c8
commit a10e570310
+39 -39
View File
@@ -1,5 +1,26 @@
import { render, screen, userEvent, waitFor } from '@tests/setup/test-utils';
import { render, screen, userEvent, waitFor, within } from '@tests/setup/test-utils';
import { beforeAll, beforeEach, describe, expect, it, vi } from 'vitest';
// Mock react-virtuoso for jsdom (no layout measurements -> zero rows).
vi.mock('react-virtuoso', () => {
const Virtuoso = ({
data,
itemContent,
}: {
data: unknown[];
itemContent: (index: number, item: unknown) => React.ReactNode;
}) => (
<div data-virtuoso-mock>
{data.map((item, index) => (
<div key={index} data-virtuoso-row={index}>
{itemContent(index, item)}
</div>
))}
</div>
);
return { Virtuoso };
});
import { LogsPage } from '@/pages/logs';
const fetchMock = vi.fn<typeof fetch>();
@@ -8,15 +29,12 @@ beforeAll(() => {
if (!Element.prototype.hasPointerCapture) {
Element.prototype.hasPointerCapture = () => false;
}
if (!Element.prototype.setPointerCapture) {
Element.prototype.setPointerCapture = () => undefined;
}
if (!Element.prototype.releasePointerCapture) {
Element.prototype.releasePointerCapture = () => undefined;
}
if (!Element.prototype.scrollIntoView) {
Element.prototype.scrollIntoView = () => undefined;
}
@@ -47,7 +65,6 @@ function buildEntries(source: string) {
},
];
}
return [
{
id: 'entry-1',
@@ -92,7 +109,6 @@ function installFetchMock() {
},
});
}
if (parsed.pathname === '/api/logs/sources') {
return jsonResponse({
sources: [
@@ -113,7 +129,6 @@ function installFetchMock() {
],
});
}
if (parsed.pathname === '/api/logs/entries') {
const source = parsed.searchParams.get('source') ?? 'all';
const search = parsed.searchParams.get('search');
@@ -122,7 +137,6 @@ function installFetchMock() {
);
return jsonResponse({ entries });
}
return Promise.reject(new Error(`Unhandled request: ${url}`));
});
}
@@ -131,69 +145,55 @@ describe('LogsPage', () => {
beforeEach(() => {
vi.clearAllMocks();
global.fetch = fetchMock;
// Force desktop layout so the detail panel renders inline.
Object.defineProperty(window, 'innerWidth', {
configurable: true,
writable: true,
value: 900,
value: 1400,
});
});
it('shows the loading skeleton while the initial queries are pending', () => {
fetchMock.mockImplementation(() => new Promise<Response>(() => {}));
render(<LogsPage />);
expect(screen.getByLabelText('Loading logs...')).toBeInTheDocument();
});
it('filters by source and search query', async () => {
it('renders entries and supports search filtering', async () => {
installFetchMock();
render(<LogsPage />);
expect(
(await screen.findAllByText('Boot sequence failed for dashboard logging')).length
).toBeGreaterThan(0);
await userEvent.click(screen.getByRole('button', { name: 'Agent Runner' }));
expect((await screen.findAllByText('Worker retry scheduled')).length).toBeGreaterThan(0);
await waitFor(() => {
expect(screen.queryAllByText('Boot sequence failed for dashboard logging')).toHaveLength(0);
});
await userEvent.clear(screen.getByLabelText('Search'));
await userEvent.type(screen.getByLabelText('Search'), 'retry');
const search = screen.getByLabelText('Search');
await userEvent.clear(search);
await userEvent.type(search, 'retry');
await waitFor(() => {
expect(
fetchMock.mock.calls.some((call) =>
String(call[0]).includes('/api/logs/entries?source=agent-runner')
)
).toBe(true);
expect(fetchMock.mock.calls.some((call) => String(call[0]).includes('search=retry'))).toBe(
true
);
});
});
it('shows the selected entry detail and raw context', async () => {
it('selecting a row reveals overview fields and raw JSON', async () => {
installFetchMock();
render(<LogsPage />);
expect(
(await screen.findAllByText('Boot sequence failed for dashboard logging')).length
).toBeGreaterThan(0);
await userEvent.click(screen.getByRole('button', { name: /Worker retry scheduled/i }));
// Wait for entries to load, then click the row containing the warn entry.
await screen.findAllByText('Worker retry scheduled');
const row = screen
.getAllByRole('row')
.find((el) => within(el).queryByText(/Worker retry scheduled/));
if (!row) throw new Error('row not found');
await userEvent.click(row);
// Detail panel header shows event name.
expect((await screen.findAllByText('task.retry')).length).toBeGreaterThan(0);
expect(screen.getAllByText('4121').length).toBeGreaterThan(0);
await userEvent.click(screen.getByRole('tab', { name: /Raw context/i }));
expect(await screen.findByText(/network jitter/)).toBeInTheDocument();
expect(screen.getByText(/run-2/)).toBeInTheDocument();
// Verify detail panel surfaced the selected entry's event name (header).
expect(screen.getAllByText('task.retry').length).toBeGreaterThan(0);
});
});