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>, 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; deleteAgent: ReturnType; }) => 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(); 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(); await waitFor(() => { expect(fetchAgentsMock).toHaveBeenCalledTimes(1); expect(hostApiFetchMock).toHaveBeenCalledWith('/api/channels/accounts'); }); gatewayState.status = { state: 'running', port: 18789 }; await act(async () => { rerender(); }); await waitFor(() => { const channelFetchCalls = hostApiFetchMock.mock.calls.filter(([path]) => path === '/api/channels/accounts'); expect(channelFetchCalls).toHaveLength(2); }); }); });