import { describe, expect, it } from 'vitest'; import { directHistoryAnchorGateToWaitFor, openDirectHistoryAnchorGate, reuseOrOpenDirectHistoryAnchorGate, } from '../src/view/project-development/chat/history/directHistoryAnchorGate'; const projectA = '/tmp/项目A'; const projectB = '/tmp/项目B'; /** 订阅侧的回执:一次订阅只 settle 一次。 */ function subscribeReceipt(gate: { settle: (anchor: string | null) => void }) { gate.settle('turn-2-user'); } describe('openDirectHistoryAnchorGate', () => { it('默认不解析:回执到达前首屏必须等它', async () => { const gate = openDirectHistoryAnchorGate(projectA); const settled = { value: 'pending' as string | null | 'pending' }; void gate.anchor.then((anchor) => { settled.value = anchor; }); // 让微任务跑一轮:没有回执就不该有结果。 await Promise.resolve(); await Promise.resolve(); expect(settled.value).toBe('pending'); expect(gate.projectPath).toBe(projectA); expect(gate.consumed).toBe(false); }); it('settle 之后 anchor 解析成回执里的 lastCompletedItemId', async () => { const gate = openDirectHistoryAnchorGate(projectA); subscribeReceipt(gate); await expect(gate.anchor).resolves.toBe('turn-2-user'); }); it('订阅不可用时 settle(null):首屏退化成取文件尾', async () => { const gate = openDirectHistoryAnchorGate(projectA); gate.settle(null); await expect(gate.anchor).resolves.toBeNull(); }); }); describe('reuseOrOpenDirectHistoryAnchorGate', () => { it('同一个项目复用同一道闸门:订阅侧要 settle 首屏已经开好的那道', () => { const opened = openDirectHistoryAnchorGate(projectA); expect(reuseOrOpenDirectHistoryAnchorGate(opened, projectA)).toBe(opened); }); it('换项目开一道新闸门,不复用旧项目的回执', () => { const opened = openDirectHistoryAnchorGate(projectA); const next = reuseOrOpenDirectHistoryAnchorGate(opened, projectB); expect(next).not.toBe(opened); expect(next.projectPath).toBe(projectB); expect(next.consumed).toBe(false); }); it('还没有闸门时新开一道', () => { expect(reuseOrOpenDirectHistoryAnchorGate(null, projectA).projectPath).toBe( projectA, ); }); }); describe('directHistoryAnchorGateToWaitFor', () => { it('没有闸门(首屏比订阅 effect 先跑)时开一道给调用方登记', async () => { const gate = directHistoryAnchorGateToWaitFor(null, projectA); expect(gate).not.toBeNull(); expect(gate?.projectPath).toBe(projectA); subscribeReceipt(gate!); await expect(gate!.anchor).resolves.toBe('turn-2-user'); }); it('同项目未消费的闸门直接复用:首屏等到订阅回执', async () => { const opened = openDirectHistoryAnchorGate(projectA); const toWaitFor = directHistoryAnchorGateToWaitFor(opened, projectA); expect(toWaitFor).toBe(opened); subscribeReceipt(opened); await expect(toWaitFor!.anchor).resolves.toBe('turn-2-user'); }); it('同项目已消费返回 null:重开项目没有新回执可等,退回取文件尾', () => { const opened = openDirectHistoryAnchorGate(projectA); opened.consumed = true; expect(directHistoryAnchorGateToWaitFor(opened, projectA)).toBeNull(); }); it('换项目不复用旧闸门:新项目的首屏要等新订阅的回执', () => { const opened = openDirectHistoryAnchorGate(projectA); subscribeReceipt(opened); opened.consumed = true; const toWaitFor = directHistoryAnchorGateToWaitFor(opened, projectB); expect(toWaitFor).not.toBeNull(); expect(toWaitFor).not.toBe(opened); expect(toWaitFor?.projectPath).toBe(projectB); }); });