Files
Genarrative/apps/ai-game-creator-shell/tests/directHistoryAnchorGate.test.ts
T
k88936 30c377d0f0
Project CI / AI game creator shell Rust shard 1/4 (push) Successful in 6m21s
Project CI / AI game creator shell Rust smoke (push) Successful in 1m51s
Project CI / AI game creator shell Rust shard 3/4 (push) Successful in 5m3s
Project CI / AI game creator shell Rust shard 2/4 (push) Failing after 5m54s
Project CI / AI game creator shell Rust shard 4/4 (push) Successful in 4m28s
Project CI / AI game creator shell Rust crates (push) Successful in 1m53s
Project CI / Repository checks (push) Successful in 5m33s
Project CI / Frontend tests (push) Successful in 7m20s
Project CI / Native shell tests (push) Successful in 8m29s
Project CI / Backend tests (push) Successful in 8m57s
Project CI / AI game creator shell web tests (push) Successful in 3m16s
保留唯一的thread manager作为direct project的状态来源 (#384)
说明: 在把工作交给段哥前还没有实现direct project聊天页面的迁移, 导致在 #375 里用很复杂的实现又做了一套事件流, 实测还有会话丢失的bug, 我在这里把数据获取的部分迁移到 #367 上

---------

Co-authored-by: 孔令弘 <ink29535@proton.me>
Reviewed-on: https://git.genarrative.world/git/GenarrativeAI/Genarrative/pulls/384
Reviewed-by: 孔令弘 <ink29535@proton.me>
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-18 02:13:50 +08:00

109 lines
3.7 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
directHistoryAnchorGateToWaitFor,
openDirectHistoryAnchorGate,
reuseOrOpenDirectHistoryAnchorGate,
} from '../src/features/project-workspace/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);
});
});