mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 04:18:05 +00:00
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:
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user