d04fe83f10
- App.tsx:`conversation.write` 策略确认后的重跑改为整体复用首轮入参(`directCodexPolicyRetryInput`),不再手写字段,修掉漏传 `references` 导致 @ 引用被静默丢弃的缺陷 - resourceReferences.ts:运行画面引用的判别指纹补上 `resourceIds`(排序集合口径)/`versionId`/`elementRole`/`width`/`height`,避免不同选点撞 key 被 `dedupeChatReferences` 去重丢掉 - ResourceReferenceInput.tsx:程序化重建编辑器内容改为把 chip 内联嵌在文本里的同名 token 位置(不再另起一段堆在末尾),并把 `lastEmittedDraftRef` 记为「编辑器实际读回来的草稿」而不是 props,消除每轮多一段 `@显示名` 的渲染循环 - ResourceReferenceInput.tsx:候选浮层(候选菜单 / 素材选择器)打开时 Enter 不再提交表单,把按键让给 `LexicalTypeaheadMenuPlugin` - ResourceReferenceInput.tsx:`assets` / `currentVersionAssetReferences` 的 memo 依赖改用内容签名;预览请求在换素材 / 卸载时主动作废上一个 scope;空态文案的嵌套三元抽成 `pickerEmptyMessage` - usePromptPolish.ts:`polish()` 补 `catch`,注入式 `requestPolish` 拒绝时给出可见失败提示而不是未处理拒绝;`reset()` 递增请求代次作废在飞请求 - ChatPromptPolishReminder.tsx:章节级 Esc 分支补 `busy` 判据,与关闭按钮 / 遮罩同一口径(在飞不许关面板) - LocalGamePreviewFrame.tsx:资源 id 改用专用净化(允许 `:` 与可打印非 ASCII),修掉 `local-asset:<id>`、`persisted-角色草图.png` 被整条过滤导致引用丢素材关联 - sessionPreview.ts:`get_local_game_preview_status` 读失败不再当作「确认没有在跑」去发停止命令,避免停掉真在跑的活体预览 - useHomeProjectCreation.ts:进项目流程加代次令牌,慢请求后到不再覆盖新项目 - WorkspaceLauncher.tsx:拒收恢复的阶段回写按 `projectId`/`heldRevision`/`snapshotRevision` 关联,不再把恢复结果盖到后续新拒收的提示上 - 用例:`directCodexPolicyRetryInput` 透传、引用指纹区分度、重建不循环、浮层打开时 Enter 不提交、润色拒绝 / reset 作废、预览读失败不停预览
130 lines
3.9 KiB
TypeScript
130 lines
3.9 KiB
TypeScript
// @vitest-environment jsdom
|
|
import { act, cleanup, renderHook } from '@testing-library/react';
|
|
import { afterEach, describe, expect, test, vi } from 'vitest';
|
|
|
|
import { usePromptPolish } from '../src/features/project-workspace/usePromptPolish';
|
|
|
|
afterEach(cleanup);
|
|
|
|
function renderPolish({
|
|
requestPolish,
|
|
applyPrompt = vi.fn(),
|
|
normalizeResult,
|
|
}: {
|
|
requestPolish: (
|
|
prompt: string,
|
|
context: string | null,
|
|
) => Promise<string | null>;
|
|
applyPrompt?: (text: string) => void;
|
|
normalizeResult?: (polished: string) => {
|
|
text: string;
|
|
notice?: string | null;
|
|
};
|
|
}) {
|
|
return renderHook(() =>
|
|
usePromptPolish({
|
|
readPrompt: () => '原始需求',
|
|
applyPrompt,
|
|
requestPolish,
|
|
normalizeResult,
|
|
}),
|
|
);
|
|
}
|
|
|
|
describe('usePromptPolish', () => {
|
|
test('注入的 requestPolish 拒绝时收敛成失败提示,不产生未处理拒绝', async () => {
|
|
const applyPrompt = vi.fn();
|
|
const { result } = renderPolish({
|
|
requestPolish: vi.fn(async () => {
|
|
throw new Error('platform llm unavailable');
|
|
}),
|
|
applyPrompt,
|
|
});
|
|
|
|
let returned: string | null = 'sentinel';
|
|
await act(async () => {
|
|
returned = await result.current.polish();
|
|
});
|
|
|
|
// 默认 `requestChatPromptPolish` 自己吞掉失败;注入实现可以拒绝,此时必须
|
|
// 变成用户可见的失败提示,而不是把 promise 变成未处理拒绝、界面上什么都没有。
|
|
expect(returned).toBeNull();
|
|
expect(result.current.error).toBe('AI 润色失败,可重试');
|
|
expect(result.current.polishing).toBe(false);
|
|
expect(applyPrompt).not.toHaveBeenCalled();
|
|
});
|
|
|
|
test('notice 文案覆盖本次失败,并且回填前的抛错同样被接住', async () => {
|
|
const applyPrompt = vi.fn(() => {
|
|
throw new Error('composer write failed');
|
|
});
|
|
const { result } = renderPolish({
|
|
requestPolish: async () => '润色结果',
|
|
applyPrompt,
|
|
});
|
|
|
|
await act(async () => {
|
|
await result.current.polish({ failureMessage: '润色失败,可直接发送' });
|
|
});
|
|
|
|
expect(result.current.error).toBe('润色失败,可直接发送');
|
|
expect(result.current.polishing).toBe(false);
|
|
});
|
|
|
|
test('reset 之后的在飞润色结果不再回填宿主草稿', async () => {
|
|
let resolvePolish: (value: string) => void = () => {};
|
|
const applyPrompt = vi.fn();
|
|
const { result } = renderPolish({
|
|
requestPolish: vi.fn(
|
|
() =>
|
|
new Promise<string>((resolve) => {
|
|
resolvePolish = resolve;
|
|
}),
|
|
),
|
|
applyPrompt,
|
|
});
|
|
|
|
let pending: Promise<string | null> | null = null;
|
|
await act(async () => {
|
|
pending = result.current.polish();
|
|
});
|
|
// 宿主清空草稿 / 换资源:在飞请求必须被作废,迟到的结果不许写进新草稿。
|
|
await act(async () => {
|
|
result.current.reset();
|
|
});
|
|
await act(async () => {
|
|
resolvePolish('迟到的润色结果');
|
|
await pending;
|
|
});
|
|
|
|
expect(applyPrompt).not.toHaveBeenCalled();
|
|
expect(result.current.originalText).toBeNull();
|
|
expect(result.current.notice).toBeNull();
|
|
expect(result.current.error).toBeNull();
|
|
});
|
|
|
|
test('没有 reset 时在飞结果照旧回填(对照)', async () => {
|
|
let resolvePolish: (value: string) => void = () => {};
|
|
const applyPrompt = vi.fn();
|
|
const { result } = renderPolish({
|
|
requestPolish: () =>
|
|
new Promise<string>((resolve) => {
|
|
resolvePolish = resolve;
|
|
}),
|
|
applyPrompt,
|
|
});
|
|
|
|
let pending: Promise<string | null> | null = null;
|
|
await act(async () => {
|
|
pending = result.current.polish();
|
|
});
|
|
await act(async () => {
|
|
resolvePolish('润色后的需求');
|
|
await pending;
|
|
});
|
|
|
|
expect(applyPrompt).toHaveBeenCalledWith('润色后的需求');
|
|
expect(result.current.originalText).toBe('原始需求');
|
|
});
|
|
});
|