import { describe, expect, it } from 'vitest'; import { canStopDirectProjectTurn, deriveDirectProjectTurnStatus, } from '../src/view/project-development/chat/controller/useDirectProjectTurnStatus'; import type { DirectPendingTurn } from '../src/view/project-development/chat/conversation/directPendingTurns'; import type { DirectChatTurn } from '../src/view/project-development/chat/conversation/directTurnPresentation'; const turn = (key: string, state: DirectChatTurn['state']): DirectChatTurn => ({ key, users: [], process: [], finals: [], state, startedAt: 1_800_000_000_000, endedAt: 0, }); /** 待发消息:这里只关心条数,内容与 chip 文案无关。 */ const pending = (clientTurnId: string): DirectPendingTurn => ({ clientTurnId, userItem: { type: 'message', role: 'user', id: `direct-codex:${clientTurnId}:user`, content: [{ type: 'input_text', text: clientTurnId }], }, at: 1, }); describe('DirectProject 回合状态派生', () => { it('displayBusy 是原生真相、IPC 在飞与待发消息条数的并集', () => { expect( deriveDirectProjectTurnStatus({ turnRunning: true, commandInFlight: false, pendingTurns: [], turns: [], }).displayBusy, ).toBe(true); expect( deriveDirectProjectTurnStatus({ turnRunning: false, commandInFlight: true, pendingTurns: [], turns: [], }).displayBusy, ).toBe(true); // 队列非空也是忙:还有一条待发消息在宿主手里,这时不能假装空闲。 expect( deriveDirectProjectTurnStatus({ turnRunning: false, commandInFlight: false, pendingTurns: [pending('client-1')], turns: [], }).displayBusy, ).toBe(true); expect( deriveDirectProjectTurnStatus({ turnRunning: false, commandInFlight: false, pendingTurns: [], turns: [], }).displayBusy, ).toBe(false); }); it('三个来源各自独立暴露,不被并集吃掉', () => { const status = deriveDirectProjectTurnStatus({ turnRunning: true, commandInFlight: true, pendingTurns: [pending('client-1'), pending('client-2')], turns: [], }); expect(status.nativeRunning).toBe(true); expect(status.commandInFlight).toBe(true); expect(status.pendingCount).toBe(2); }); it('canStopTurn 只认"有作用对象"的两种状态:队列非空不算', () => { // 队列非空仍然是 displayBusy(界面不假装空闲),但它不是终止的作用对象——终止钮不该出现, // 否则点了只会报「当前没有正在运行的回合」。 const queuedOnly = deriveDirectProjectTurnStatus({ turnRunning: false, commandInFlight: false, pendingTurns: [pending('client-1')], turns: [], }); expect(queuedOnly.displayBusy).toBe(true); expect(queuedOnly.canStopTurn).toBe(false); // 宿主已经放行 / 命令还在飞:这两段里有东西可终止。 expect( canStopDirectProjectTurn({ turnRunning: true, commandInFlight: false }), ).toBe(true); expect( canStopDirectProjectTurn({ turnRunning: false, commandInFlight: true }), ).toBe(true); // 真空闲:终止钮与发送钮各自归位。 expect( canStopDirectProjectTurn({ turnRunning: false, commandInFlight: false }), ).toBe(false); expect( deriveDirectProjectTurnStatus({ turnRunning: false, commandInFlight: true, pendingTurns: [], turns: [], }).canStopTurn, ).toBe(true); }); it('latestTurnState 取最新一轮的两态;没有回合时为 null', () => { expect( deriveDirectProjectTurnStatus({ turnRunning: false, commandInFlight: false, pendingTurns: [], turns: [turn('u1', 'finished'), turn('u2', 'running')], }).latestTurnState, ).toBe('running'); expect( deriveDirectProjectTurnStatus({ turnRunning: false, commandInFlight: false, pendingTurns: [], turns: [], }).latestTurnState, ).toBeNull(); }); it('IPC 在飞不等于原生在跑', () => { const status = deriveDirectProjectTurnStatus({ turnRunning: false, commandInFlight: true, pendingTurns: [], turns: [turn('u1', 'finished')], }); expect(status.nativeRunning).toBe(false); expect(status.commandInFlight).toBe(true); expect(status.latestTurnState).toBe('finished'); }); });