0534e9c678
- 新增 conversation/directPendingTurns.ts:入队/移除两个纯函数,不判上限、不排期、不排序 - reducer 新增 queue.enqueued / queue.removed 分支,折成 pendingTurns 投影 - chip 文案派生搬到 pendingTurnChipLabel.ts,删除 chatComposerQueue.ts 整套本地队列实现 - controller 退役 queuedTurns / queueSequenceRef / completionPendingRef / busyBaselineTurnCountRef 与看门 counts effect;取消 chip 改调 remove_direct_project_pending_turn 按 typed 结果说话 - 埋点句柄按 clientTurnId 存表,回合终态按本轮开口条目身份结算 - turnBusy 改名 commandInFlight 并收窄成 IPC 在飞;忙态 = 原生在跑 ∨ IPC 在飞 ∨ 待发消息非空 - 提交改成等命令的入队结果:用户自己能改的入队失败保留草稿,其余按已交出处理 - 测试:队列投影单测、入队只发一次 IPC、IPC 在飞挡住第二次提交、满队保留草稿、取消已放行条目 - 文档:实施计划标注第 0–3 步落地并补第 3 步细则,ADR 的埋点与退役面口径改成落地后的写法
109 lines
3.2 KiB
TypeScript
109 lines
3.2 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { 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('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');
|
|
});
|
|
});
|