import { beforeEach, describe, expect, it, vi } from 'vitest'; const invokeIpcMock = vi.fn(); vi.mock('@/lib/api-client', () => ({ invokeIpc: (...args: unknown[]) => invokeIpcMock(...args), })); type ChatLikeState = { currentSessionKey: string; sessions: Array<{ key: string; displayName?: string }>; messages: Array<{ role: string; timestamp?: number; content?: unknown }>; sessionLabels: Record; sessionLastActivity: Record; streamingText: string; streamingMessage: unknown | null; streamingTools: unknown[]; activeRunId: string | null; error: string | null; pendingFinal: boolean; lastUserMessageAt: number | null; pendingToolImages: unknown[]; loadHistory: ReturnType; }; function makeHarness(initial?: Partial) { let state: ChatLikeState = { currentSessionKey: 'agent:main:main', sessions: [{ key: 'agent:main:main' }], messages: [], sessionLabels: {}, sessionLastActivity: {}, streamingText: '', streamingMessage: null, streamingTools: [], activeRunId: null, error: null, pendingFinal: false, lastUserMessageAt: null, pendingToolImages: [], loadHistory: vi.fn(), ...initial, }; const set = (partial: Partial | ((s: ChatLikeState) => Partial)) => { const patch = typeof partial === 'function' ? partial(state) : partial; state = { ...state, ...patch }; }; const get = () => state; return { set, get, read: () => state }; } describe('chat session actions', () => { beforeEach(() => { vi.resetAllMocks(); invokeIpcMock.mockResolvedValue({ success: true }); }); it('switchSession removes empty non-main leaving session and loads history', async () => { const { createSessionActions } = await import('@/stores/chat/session-actions'); const h = makeHarness({ currentSessionKey: 'agent:foo:session-a', sessions: [{ key: 'agent:foo:session-a' }, { key: 'agent:foo:main' }], messages: [], sessionLabels: { 'agent:foo:session-a': 'A' }, sessionLastActivity: { 'agent:foo:session-a': 1 }, }); const actions = createSessionActions(h.set as never, h.get as never); actions.switchSession('agent:foo:main'); const next = h.read(); expect(next.currentSessionKey).toBe('agent:foo:main'); expect(next.sessions.find((s) => s.key === 'agent:foo:session-a')).toBeUndefined(); expect(next.sessionLabels['agent:foo:session-a']).toBeUndefined(); expect(next.sessionLastActivity['agent:foo:session-a']).toBeUndefined(); expect(h.read().loadHistory).toHaveBeenCalledTimes(1); }); it('deleteSession updates current session and keeps sidebar consistent', async () => { const { createSessionActions } = await import('@/stores/chat/session-actions'); const h = makeHarness({ currentSessionKey: 'agent:foo:session-a', sessions: [{ key: 'agent:foo:session-a' }, { key: 'agent:foo:main' }], sessionLabels: { 'agent:foo:session-a': 'A' }, sessionLastActivity: { 'agent:foo:session-a': 1 }, messages: [{ role: 'user' }], }); const actions = createSessionActions(h.set as never, h.get as never); await actions.deleteSession('agent:foo:session-a'); const next = h.read(); expect(invokeIpcMock).toHaveBeenCalledWith('session:delete', 'agent:foo:session-a'); expect(next.currentSessionKey).toBe('agent:foo:main'); expect(next.sessions.map((s) => s.key)).toEqual(['agent:foo:main']); expect(next.sessionLabels['agent:foo:session-a']).toBeUndefined(); expect(next.sessionLastActivity['agent:foo:session-a']).toBeUndefined(); expect(h.read().loadHistory).toHaveBeenCalledTimes(1); }); it('newSession creates a canonical session key and clears transient state', async () => { const nowSpy = vi.spyOn(Date, 'now').mockReturnValue(1711111111111); const { createSessionActions } = await import('@/stores/chat/session-actions'); const h = makeHarness({ currentSessionKey: 'agent:foo:main', sessions: [{ key: 'agent:foo:main' }], messages: [{ role: 'assistant' }], streamingText: 'streaming', activeRunId: 'r1', pendingFinal: true, }); const actions = createSessionActions(h.set as never, h.get as never); actions.newSession(); const next = h.read(); expect(next.currentSessionKey).toBe('agent:foo:session-1711111111111'); expect(next.sessions.some((s) => s.key === 'agent:foo:session-1711111111111')).toBe(true); expect(next.messages).toEqual([]); expect(next.streamingText).toBe(''); expect(next.activeRunId).toBeNull(); expect(next.pendingFinal).toBe(false); nowSpy.mockRestore(); }); });