test(web): scaffold vitest + RTL with Orchestrator seed suite (DEBT-043)
Wire vitest 4 + jsdom + @testing-library/{react,jest-dom,user-event}
+ @vitest/coverage-v8 through vite.config.ts (defineConfig from
vitest/config). src/test/setup.ts registers jest-dom matchers and
RTL cleanup. tsconfig.app.json picks up vitest/globals types.
Seed suite Orchestrator.test.tsx covers the three regressions
called out in DEBT-043: empty-state render, kind-filter toggling
triggers a scoped refetch, mocked stream callback prepends a row.
This commit is contained in:
93
decnet_web/src/components/Orchestrator.test.tsx
Normal file
93
decnet_web/src/components/Orchestrator.test.tsx
Normal file
@@ -0,0 +1,93 @@
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { render, screen, waitFor, act } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { MemoryRouter } from 'react-router-dom';
|
||||
|
||||
import Orchestrator from './Orchestrator';
|
||||
import type {
|
||||
OrchestratorStreamEvent,
|
||||
UseOrchestratorStreamOptions,
|
||||
} from './useOrchestratorStream';
|
||||
|
||||
vi.mock('../utils/api', () => ({
|
||||
default: { get: vi.fn() },
|
||||
}));
|
||||
|
||||
// Capture the live stream callback so tests can drive it manually.
|
||||
let capturedOnEvent:
|
||||
| ((event: OrchestratorStreamEvent) => void)
|
||||
| null = null;
|
||||
vi.mock('./useOrchestratorStream', () => ({
|
||||
useOrchestratorStream: (opts: UseOrchestratorStreamOptions) => {
|
||||
capturedOnEvent = opts.onEvent;
|
||||
},
|
||||
}));
|
||||
|
||||
import api from '../utils/api';
|
||||
const apiGet = api.get as ReturnType<typeof vi.fn>;
|
||||
|
||||
const renderPage = () =>
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/orchestrator']}>
|
||||
<Orchestrator />
|
||||
</MemoryRouter>,
|
||||
);
|
||||
|
||||
describe('Orchestrator', () => {
|
||||
beforeEach(() => {
|
||||
capturedOnEvent = null;
|
||||
apiGet.mockReset();
|
||||
});
|
||||
|
||||
it('renders the empty state when the API returns no events', async () => {
|
||||
apiGet.mockResolvedValueOnce({ data: { data: [], total: 0 } });
|
||||
|
||||
renderPage();
|
||||
|
||||
expect(await screen.findByText(/NO ORCHESTRATOR ACTIVITY YET/i)).toBeInTheDocument();
|
||||
// The kind=all path advertises the orchestrator command, not the emailgen one.
|
||||
expect(screen.getByText(/decnet orchestrate/i)).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('switches the kind filter and refetches scoped to that kind', async () => {
|
||||
apiGet.mockResolvedValue({ data: { data: [], total: 0 } });
|
||||
|
||||
renderPage();
|
||||
await waitFor(() => expect(apiGet).toHaveBeenCalledTimes(1));
|
||||
expect(apiGet.mock.calls[0][0]).toMatch(/^\/orchestrator\/events\?limit=50&offset=0$/);
|
||||
|
||||
await userEvent.click(screen.getByRole('tab', { name: /^email$/ }));
|
||||
|
||||
await waitFor(() =>
|
||||
expect(apiGet.mock.calls.some((c) => /kind=email/.test(c[0]))).toBe(true),
|
||||
);
|
||||
expect(screen.getByRole('tab', { name: /^email$/ })).toHaveAttribute('aria-selected', 'true');
|
||||
});
|
||||
|
||||
it('prepends a row when the live stream pushes a traffic event', async () => {
|
||||
apiGet.mockResolvedValueOnce({ data: { data: [], total: 0 } });
|
||||
|
||||
renderPage();
|
||||
await waitFor(() => expect(capturedOnEvent).not.toBeNull());
|
||||
|
||||
act(() => {
|
||||
capturedOnEvent!({
|
||||
name: 'traffic',
|
||||
ts: new Date().toISOString(),
|
||||
payload: {
|
||||
kind: 'traffic',
|
||||
protocol: 'http',
|
||||
action: 'GET /admin',
|
||||
src_decky_uuid: 'aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee',
|
||||
dst_decky_uuid: 'ffffffff-1111-2222-3333-444444444444',
|
||||
success: true,
|
||||
payload: '{}',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
expect(await screen.findByText('GET /admin')).toBeInTheDocument();
|
||||
// 1 event shown after a single push.
|
||||
expect(screen.getByText(/1 EVENTS SHOWN/i)).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
7
decnet_web/src/test/setup.ts
Normal file
7
decnet_web/src/test/setup.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import '@testing-library/jest-dom/vitest';
|
||||
import { afterEach } from 'vitest';
|
||||
import { cleanup } from '@testing-library/react';
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
Reference in New Issue
Block a user