Files
DeskClaw/tests/unit/agents-page.test.tsx

122 lines
3.5 KiB
TypeScript

import React from 'react';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { act, render, waitFor } from '@testing-library/react';
import { Agents } from '../../src/pages/Agents/index';
const hostApiFetchMock = vi.fn();
const subscribeHostEventMock = vi.fn();
const fetchAgentsMock = vi.fn();
const { gatewayState, agentsState } = vi.hoisted(() => ({
gatewayState: {
status: { state: 'running', port: 18789 },
},
agentsState: {
agents: [] as Array<Record<string, unknown>>,
loading: false,
error: null as string | null,
},
}));
vi.mock('@/stores/gateway', () => ({
useGatewayStore: (selector: (state: typeof gatewayState) => unknown) => selector(gatewayState),
}));
vi.mock('@/stores/agents', () => ({
useAgentsStore: (selector?: (state: typeof agentsState & {
fetchAgents: typeof fetchAgentsMock;
createAgent: ReturnType<typeof vi.fn>;
deleteAgent: ReturnType<typeof vi.fn>;
}) => unknown) => {
const state = {
...agentsState,
fetchAgents: fetchAgentsMock,
createAgent: vi.fn(),
deleteAgent: vi.fn(),
};
return typeof selector === 'function' ? selector(state) : state;
},
}));
vi.mock('@/lib/host-api', () => ({
hostApiFetch: (...args: unknown[]) => hostApiFetchMock(...args),
}));
vi.mock('@/lib/host-events', () => ({
subscribeHostEvent: (...args: unknown[]) => subscribeHostEventMock(...args),
}));
vi.mock('react-i18next', () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}));
vi.mock('sonner', () => ({
toast: {
success: vi.fn(),
error: vi.fn(),
warning: vi.fn(),
},
}));
describe('Agents page status refresh', () => {
beforeEach(() => {
vi.clearAllMocks();
gatewayState.status = { state: 'running', port: 18789 };
fetchAgentsMock.mockResolvedValue(undefined);
hostApiFetchMock.mockResolvedValue({
success: true,
channels: [],
});
});
it('refetches channel accounts when gateway channel-status events arrive', async () => {
let channelStatusHandler: (() => void) | undefined;
subscribeHostEventMock.mockImplementation((eventName: string, handler: () => void) => {
if (eventName === 'gateway:channel-status') {
channelStatusHandler = handler;
}
return vi.fn();
});
render(<Agents />);
await waitFor(() => {
expect(fetchAgentsMock).toHaveBeenCalledTimes(1);
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/channels/accounts');
});
expect(subscribeHostEventMock).toHaveBeenCalledWith('gateway:channel-status', expect.any(Function));
await act(async () => {
channelStatusHandler?.();
});
await waitFor(() => {
const channelFetchCalls = hostApiFetchMock.mock.calls.filter(([path]) => path === '/api/channels/accounts');
expect(channelFetchCalls).toHaveLength(2);
});
});
it('refetches channel accounts when the gateway transitions to running after mount', async () => {
gatewayState.status = { state: 'starting', port: 18789 };
const { rerender } = render(<Agents />);
await waitFor(() => {
expect(fetchAgentsMock).toHaveBeenCalledTimes(1);
expect(hostApiFetchMock).toHaveBeenCalledWith('/api/channels/accounts');
});
gatewayState.status = { state: 'running', port: 18789 };
await act(async () => {
rerender(<Agents />);
});
await waitFor(() => {
const channelFetchCalls = hostApiFetchMock.mock.calls.filter(([path]) => path === '/api/channels/accounts');
expect(channelFetchCalls).toHaveLength(2);
});
});
});