补齐完美像素第三处静默分支并区分两条结果文案
链路复查发现两处。 遗留:成功路径拿到 result.project 后,本地占位已被删除时直接 return。不套用是 对的(删除意图胜出),但什么都不说。同一形状此前已修两处,这是第三处;既有用例 只断言「不套用」,没断言「要说话」,所以没挡住。 新引入:上次让「对账已收口 + 本地已删」沿用了「只在素材库」的文案。两种情况事实 不同——快照里没有 dialog 时服务端画布确实没有结果;dialog 已收口时服务端画布上 有结果,只是本地未同步,重新加载即可见。混用会让用户以为画布上没有而重做。 两条文案抽成常量按事实分派。 第一次回归验证只有 1 条变红,暴露出「对账已收口 + 本地已删」这条分支根本没有 用例;补上后再破坏,2 条同时变红。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -2610,6 +2610,76 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
expect(screen.getByTestId('dialog').textContent).toBe('-');
|
||||
});
|
||||
|
||||
it('tells the user the reconciled result already landed remotely after they deleted the placeholder', async () => {
|
||||
// 中文注释:对账发现服务端已收口(dialog 保留且写了 generatedLayerId),说明结果**已经在
|
||||
// 服务端画布上**;而本地占位被用户删掉,按删除意图不套用快照。此时不能用「只在素材库」
|
||||
// 那条文案——它会让用户以为画布上没有,再做一遍造出第二份。必须说清重新加载后可见。
|
||||
const applyProjectSnapshot = vi.fn();
|
||||
const reconciledProject = {
|
||||
projectId: 'project-1',
|
||||
title: '未命名画布',
|
||||
viewport: { x: 0, y: 0, scale: 1 },
|
||||
layers: [
|
||||
{
|
||||
itemType: 'generation-dialog',
|
||||
layerId: 'generation-dialog:generation-dialog-1',
|
||||
resourceId: 'generation-dialog:generation-dialog-1',
|
||||
dialog: {
|
||||
id: 'generation-dialog-1',
|
||||
mode: 'quick-edit',
|
||||
prompt: '完美像素',
|
||||
status: 'idle',
|
||||
composerOpen: false,
|
||||
generatedLayerId: 'layer-perfect-pixel-1',
|
||||
},
|
||||
},
|
||||
],
|
||||
resources: [],
|
||||
updatedAt: '2026-08-01T00:00:00.000Z',
|
||||
};
|
||||
let rejectPerfectPixel: ((error: unknown) => void) | undefined;
|
||||
snapImageToPerfectPixelsMock.mockImplementationOnce(
|
||||
() =>
|
||||
new Promise((_resolve, reject) => {
|
||||
rejectPerfectPixel = reject;
|
||||
}),
|
||||
);
|
||||
loadEditorProjectMock.mockResolvedValueOnce(reconciledProject);
|
||||
render(
|
||||
<GenerationWorkflowHarness
|
||||
projectId="project-1"
|
||||
applyProjectSnapshot={applyProjectSnapshot}
|
||||
flushProjectPersistence={vi.fn().mockResolvedValue(undefined)}
|
||||
initialLayers={[
|
||||
createLayer({
|
||||
objectKey: 'generated-images/editor/source.png',
|
||||
src: '/generated-images/editor/source.png',
|
||||
}),
|
||||
]}
|
||||
/>,
|
||||
);
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: '完美像素' }));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('dialog').textContent).toBe(
|
||||
'quick-edit:generating:closed:-:placeholder',
|
||||
);
|
||||
});
|
||||
fireEvent.click(screen.getByRole('button', { name: '删除处理占位' }));
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('generation-dialogs').textContent).toBe('-');
|
||||
});
|
||||
|
||||
rejectPerfectPixel?.(new Error('网络中断'));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByTestId('reference-pick-warning').textContent).toBe(
|
||||
'完美像素结果已生成并保存到素材库。画布占位已被删除,结果未落入当前画布,重新加载后可见。',
|
||||
);
|
||||
});
|
||||
expect(applyProjectSnapshot).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('treats a gateway-synthesized timeout as an unknown outcome and reconciles', async () => {
|
||||
// 中文注释:网关自己合成的 504 只有 code / message,没有 details,所以既不是 transport
|
||||
// 异常也拿不到 resultPersistenceStarted。按「有响应即结果已知」会跳过对账,而此时
|
||||
@@ -2779,6 +2849,11 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
'完美像素结果已保存到素材库,画布占位已不存在。',
|
||||
);
|
||||
});
|
||||
// 中文注释:本例服务端 completion 返回 Ok(None),画布上确实没有结果图层,所以只能用
|
||||
// 「只在素材库」这条;用「重新加载后可见」会是谎报。两条文案对应两种不同的事实。
|
||||
expect(screen.getByTestId('reference-pick-warning').textContent).not.toContain(
|
||||
'重新加载后可见',
|
||||
);
|
||||
expect(applyProjectSnapshot).not.toHaveBeenCalled();
|
||||
expect(screen.getByTestId('generation-dialogs').textContent).toBe('-');
|
||||
});
|
||||
@@ -3099,6 +3174,12 @@ describe('useImageCanvasGenerationWorkflow', () => {
|
||||
});
|
||||
expect(applyProjectSnapshot).not.toHaveBeenCalled();
|
||||
expect(screen.getByTestId('dialog').textContent).toBe('-');
|
||||
// 中文注释:不套用是对的,但不能静默——服务端已经完成并把结果落到画布上,只是本地按
|
||||
// 删除意图没同步。文案必须说清「重新加载后可见」,不能沿用「只在素材库」那条:后者会让
|
||||
// 用户以为画布上没有,再做一遍。
|
||||
expect(screen.getByTestId('reference-pick-warning').textContent).toBe(
|
||||
'完美像素结果已生成并保存到素材库。画布占位已被删除,结果未落入当前画布,重新加载后可见。',
|
||||
);
|
||||
});
|
||||
|
||||
it('opens UI design extraction as a mark selection state before submitting', () => {
|
||||
|
||||
@@ -1752,6 +1752,18 @@ export function useImageCanvasGenerationWorkflow({
|
||||
],
|
||||
);
|
||||
|
||||
// 中文注释:两条文案对应两种**不同的事实**,不能共用。
|
||||
// 「未落入画布」:服务端 completion 返回 Ok(None)——占位在处理期间被删掉,服务端画布上
|
||||
// 没有结果图层,结果只在素材库。
|
||||
// 「未落入当前画布」:服务端已经完成回填,画布上**有**结果图层,只是本地占位已被用户删除、
|
||||
// 按删除意图不套用快照,所以本地看不到;重新加载即可见。
|
||||
// 早先这两种情况共用前一条文案,会让「结果其实已在画布上」的用户以为只进了素材库,
|
||||
// 进而重做一遍——正是本链路要消除的重复创建。
|
||||
const PERFECT_PIXEL_ASSET_ONLY_NOTICE =
|
||||
'完美像素结果已保存到素材库,画布占位已不存在。';
|
||||
const PERFECT_PIXEL_APPLIED_REMOTELY_NOTICE =
|
||||
'完美像素结果已生成并保存到素材库。画布占位已被删除,结果未落入当前画布,重新加载后可见。';
|
||||
|
||||
const snapSelectedLayerToPerfectPixels = useCallback(
|
||||
async (sourceLayer: CanvasLayer) => {
|
||||
const normalizedProjectId = projectId?.trim();
|
||||
@@ -1852,10 +1864,14 @@ export function useImageCanvasGenerationWorkflow({
|
||||
if (hasCanvasGenerationDialogById(perfectPixelDialogId)) {
|
||||
updateCanvasGenerationDialogById(perfectPixelDialogId, () => null);
|
||||
}
|
||||
showGenerationWarning('完美像素结果已保存到素材库,画布占位已不存在。');
|
||||
showGenerationWarning(PERFECT_PIXEL_ASSET_ONLY_NOTICE);
|
||||
return;
|
||||
}
|
||||
if (!hasCanvasGenerationDialogById(perfectPixelDialogId)) {
|
||||
// 中文注释:不套用是对的(删除意图胜出),但不能静默。服务端已经完成并把结果落到了
|
||||
// 画布上,只是本地按用户的删除意图没有同步——必须说清楚,否则用户既看不到结果、
|
||||
// 也不知道它已经存在,会再做一遍。这是本链路同一形状的第三处,前两处已修。
|
||||
showGenerationWarning(PERFECT_PIXEL_APPLIED_REMOTELY_NOTICE);
|
||||
return;
|
||||
}
|
||||
applyProjectSnapshot(result.project, {
|
||||
@@ -1923,18 +1939,14 @@ export function useImageCanvasGenerationWorkflow({
|
||||
() => null,
|
||||
);
|
||||
}
|
||||
showGenerationWarning(
|
||||
'完美像素结果已保存到素材库,画布占位已不存在。',
|
||||
);
|
||||
showGenerationWarning(PERFECT_PIXEL_ASSET_ONLY_NOTICE);
|
||||
return;
|
||||
}
|
||||
if (!isUnresolvedCanvasGenerationDialogRecord(reconciledDialog)) {
|
||||
// 中文注释:服务端已经完成并回填,只是响应没回来。这是真成功。
|
||||
// 但本地占位若已被用户删除,删除意图仍然胜出:不套用快照、不写历史。
|
||||
if (!hasCanvasGenerationDialogById(perfectPixelDialogId)) {
|
||||
showGenerationWarning(
|
||||
'完美像素结果已保存到素材库,画布占位已不存在。',
|
||||
);
|
||||
showGenerationWarning(PERFECT_PIXEL_APPLIED_REMOTELY_NOTICE);
|
||||
return;
|
||||
}
|
||||
applyProjectSnapshot(reconciled, {
|
||||
|
||||
Reference in New Issue
Block a user