2a2e4c1cea
- 在途拦截补齐生成动画:提交记录的「属于哪张资源」从 draftKey 拆成 resourcePath, `resourceCanvasResourceEditSubmissionBlocksResource` 被快速编辑与生成动画共用; 此前动画这条键对不上、拦不住,重开面板再提交会复用同一 operationId 卡在原生锁上, 或(改了提示词)直接多出一笔付费生成 - 样式缺口守卫扩到三块面板(动画面板 / 快速编辑面板 / 生图面板),判据改为按 「选择器分支 + 声明」逐条比对;两个面板的实测缺口一并照抄补上(快速编辑面板的 `overflow: auto → visible` 与窄屏底栏两条按宿主差异显式登记,不静默漂移) - 窄屏只搬动画面板那一支:网页端 ≤760px 会把快速编辑浮层也变成整宽底栏,AGC 的 快速编辑是「居中贴资源卡」的绝对定位,跟随它会压住栏目工具栏与任务侧栏 - 用例路径不再依赖 cwd:新增 tests/repoPath.ts(按 import.meta.url 反推仓库根), 19 个用例文件的 `resolve(process.cwd(), …)` 统一改用它;顺手修掉 `HOST_STYLE_SHEETS.filter(existsSync)` 把仓库相对路径交给 cwd 解析的假红 - 清掉两处死代码:不可达的 `quickEditPanel.status === 'generating'` 判据与 未被使用的 prune 导出
273 lines
8.4 KiB
TypeScript
273 lines
8.4 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import {
|
|
beginResourceCanvasResourceEditSubmission,
|
|
type PendingLocalProjectResourceEdit,
|
|
pendingResourceEditKindLabel,
|
|
resourceCanvasResourceEditPhaseLabel,
|
|
type ResourceCanvasResourceEditSubmission,
|
|
resourceCanvasResourceEditSubmissionBlocksResource,
|
|
resourceCanvasResourceEditTaskElapsedMillis,
|
|
resourceCanvasResourceEditTaskIsTerminal,
|
|
resourceCanvasResourceEditTasks,
|
|
resourceCanvasResourceEditTaskStatusFromPhase,
|
|
settleResourceCanvasResourceEditSubmission,
|
|
} from '../src/features/resource-canvas/resourceCanvasResourceEditTaskModel';
|
|
|
|
function pendingEdit(
|
|
overrides: Partial<PendingLocalProjectResourceEdit> = {},
|
|
): PendingLocalProjectResourceEdit {
|
|
return {
|
|
operationId: 'op-1',
|
|
editKind: 'image-reference',
|
|
sourceResourceId: 'asset:source-art',
|
|
assetName: 'source-art-编辑版',
|
|
backgroundMode: null,
|
|
screenColor: null,
|
|
phase: 'accepted',
|
|
createdAt: 1_700_000_000_000,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function liveSubmission(
|
|
overrides: Partial<ResourceCanvasResourceEditSubmission> = {},
|
|
): ResourceCanvasResourceEditSubmission {
|
|
return {
|
|
operationId: 'op-1',
|
|
prompt: '把嘴改小一点',
|
|
assetName: 'source-art-编辑版',
|
|
actionLabel: '快速编辑',
|
|
resourcePath: 'assets/source-art.png',
|
|
createdAtMillis: 1_700_000_000_000,
|
|
status: 'running',
|
|
error: null,
|
|
assetId: null,
|
|
finishedAtMillis: null,
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
describe('资源派生任务:阶段映射', () => {
|
|
it('按原生账本阶段给出面板状态', () => {
|
|
expect(resourceCanvasResourceEditTaskStatusFromPhase('prepared')).toBe(
|
|
'queued',
|
|
);
|
|
expect(resourceCanvasResourceEditTaskStatusFromPhase('accepted')).toBe(
|
|
'running',
|
|
);
|
|
expect(
|
|
resourceCanvasResourceEditTaskStatusFromPhase('remote-completed'),
|
|
).toBe('running');
|
|
expect(
|
|
resourceCanvasResourceEditTaskStatusFromPhase('media-downloaded'),
|
|
).toBe('running');
|
|
expect(resourceCanvasResourceEditTaskStatusFromPhase('committed')).toBe(
|
|
'completed',
|
|
);
|
|
expect(resourceCanvasResourceEditTaskStatusFromPhase('archived')).toBe(
|
|
'completed',
|
|
);
|
|
expect(resourceCanvasResourceEditTaskStatusFromPhase('remote-failed')).toBe(
|
|
'failed',
|
|
);
|
|
expect(
|
|
resourceCanvasResourceEditTaskStatusFromPhase('reconciliation-required'),
|
|
).toBe('failed');
|
|
});
|
|
|
|
it('阶段文案与恢复入口同源,未知阶段原样透出', () => {
|
|
expect(resourceCanvasResourceEditPhaseLabel('remote-completed')).toBe(
|
|
'远程已完成',
|
|
);
|
|
expect(resourceCanvasResourceEditPhaseLabel('committed')).toBe(
|
|
'已写入项目',
|
|
);
|
|
// 前端还没跟上的新档:给出阶段名,便于排障时一眼认出。
|
|
expect(resourceCanvasResourceEditPhaseLabel('pending-review')).toBe(
|
|
'pending-review',
|
|
);
|
|
});
|
|
|
|
it('入口文案覆盖派生链路的几种 kind', () => {
|
|
expect(pendingResourceEditKindLabel(pendingEdit())).toBe('快速编辑');
|
|
expect(
|
|
pendingResourceEditKindLabel(
|
|
pendingEdit({ editKind: 'character-animation' }),
|
|
),
|
|
).toBe('生成动画');
|
|
expect(
|
|
pendingResourceEditKindLabel(
|
|
pendingEdit({ editKind: 'background-music' }),
|
|
),
|
|
).toBe('生成背景音乐');
|
|
expect(
|
|
pendingResourceEditKindLabel(
|
|
pendingEdit({
|
|
editKind: 'background-removal',
|
|
backgroundMode: 'flat',
|
|
screenColor: 'auto',
|
|
}),
|
|
),
|
|
).toBe('图片抠图 · 平面背景 · 自动背景色');
|
|
});
|
|
});
|
|
|
|
describe('资源派生任务:本地提交记录', () => {
|
|
it('同一 operationId 只留一条,收口后按结果改写', () => {
|
|
const begun = beginResourceCanvasResourceEditSubmission([], {
|
|
operationId: 'op-1',
|
|
assetName: 'source-art-编辑版',
|
|
actionLabel: '快速编辑',
|
|
prompt: '把嘴改小一点',
|
|
resourcePath: 'assets/source-art.png',
|
|
createdAtMillis: 1_700_000_000_000,
|
|
});
|
|
expect(begun).toHaveLength(1);
|
|
expect(begun[0]?.status).toBe('running');
|
|
|
|
const rebegun = beginResourceCanvasResourceEditSubmission(begun, {
|
|
operationId: 'op-1',
|
|
assetName: 'source-art-编辑版',
|
|
actionLabel: '快速编辑',
|
|
prompt: '把嘴改小一点',
|
|
resourcePath: 'assets/source-art.png',
|
|
createdAtMillis: 1_700_000_001_000,
|
|
});
|
|
expect(rebegun).toHaveLength(1);
|
|
|
|
const settled = settleResourceCanvasResourceEditSubmission(
|
|
rebegun,
|
|
'op-1',
|
|
{
|
|
status: 'completed',
|
|
assetId: 'edit-1',
|
|
finishedAtMillis: 1_700_000_120_000,
|
|
},
|
|
);
|
|
expect(settled[0]).toMatchObject({
|
|
status: 'completed',
|
|
assetId: 'edit-1',
|
|
error: null,
|
|
});
|
|
});
|
|
|
|
it('失败收口带原因,未完成草稿的提交门随之放行', () => {
|
|
const live = beginResourceCanvasResourceEditSubmission([], {
|
|
operationId: 'op-1',
|
|
assetName: 'source-art-编辑版',
|
|
actionLabel: '快速编辑',
|
|
prompt: '把嘴改小一点',
|
|
resourcePath: 'assets/source-art.png',
|
|
createdAtMillis: 1_700_000_000_000,
|
|
});
|
|
expect(
|
|
resourceCanvasResourceEditSubmissionBlocksResource(
|
|
live,
|
|
'assets/source-art.png',
|
|
),
|
|
).toBe(true);
|
|
// 别的素材不受影响,其它入口(没有草稿键)也不受这条判据牵制。
|
|
expect(
|
|
resourceCanvasResourceEditSubmissionBlocksResource(
|
|
live,
|
|
'assets/other.png',
|
|
),
|
|
).toBe(false);
|
|
|
|
const failed = settleResourceCanvasResourceEditSubmission(live, 'op-1', {
|
|
status: 'failed',
|
|
error: '登录已失效,请重新登录',
|
|
finishedAtMillis: 1_700_000_090_000,
|
|
});
|
|
expect(
|
|
resourceCanvasResourceEditSubmissionBlocksResource(
|
|
failed,
|
|
'assets/source-art.png',
|
|
),
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('资源派生任务:两套账本合并', () => {
|
|
it('在途期间以后端阶段为准,收口后以本地结果为准', () => {
|
|
const submissions = [liveSubmission()];
|
|
const inFlight = resourceCanvasResourceEditTasks({
|
|
submissions,
|
|
pendingEdits: [pendingEdit({ phase: 'media-downloaded' })],
|
|
});
|
|
expect(inFlight).toHaveLength(1);
|
|
expect(inFlight[0]).toMatchObject({
|
|
operationId: 'op-1',
|
|
status: 'running',
|
|
phaseDetail: '结果已下载',
|
|
prompt: '把嘴改小一点',
|
|
restored: false,
|
|
});
|
|
|
|
const settled = resourceCanvasResourceEditTasks({
|
|
submissions: settleResourceCanvasResourceEditSubmission(
|
|
submissions,
|
|
'op-1',
|
|
{
|
|
status: 'completed',
|
|
assetId: 'edit-1',
|
|
finishedAtMillis: 1_700_000_120_000,
|
|
},
|
|
),
|
|
pendingEdits: [],
|
|
});
|
|
expect(settled[0]).toMatchObject({
|
|
status: 'completed',
|
|
phaseDetail: '已产出新素材',
|
|
assetId: 'edit-1',
|
|
});
|
|
expect(resourceCanvasResourceEditTaskIsTerminal(settled[0]!)).toBe(true);
|
|
});
|
|
|
|
it('原生有、本地没有的记录恢复成一条待办,但不占用已完成分栏', () => {
|
|
const tasks = resourceCanvasResourceEditTasks({
|
|
submissions: [],
|
|
pendingEdits: [
|
|
pendingEdit({ operationId: 'op-9', phase: 'remote-failed' }),
|
|
],
|
|
});
|
|
expect(tasks).toHaveLength(1);
|
|
expect(tasks[0]).toMatchObject({
|
|
operationId: 'op-9',
|
|
status: 'failed',
|
|
restored: true,
|
|
actionLabel: '快速编辑',
|
|
});
|
|
expect(tasks[0]?.error).toContain('远程明确失败');
|
|
// 恢复记录没有结束时间:不拿创建时间冒充耗时。
|
|
expect(
|
|
resourceCanvasResourceEditTaskElapsedMillis(tasks[0]!, 1_700_000_500_000),
|
|
).toBeNull();
|
|
});
|
|
|
|
it('后端读回非数组时不炸,只按本地提交记录渲染', () => {
|
|
const tasks = resourceCanvasResourceEditTasks({
|
|
submissions: [liveSubmission()],
|
|
pendingEdits: undefined,
|
|
});
|
|
expect(tasks).toHaveLength(1);
|
|
expect(tasks[0]?.phaseDetail).toBe('已提交,等待远端受理');
|
|
});
|
|
|
|
it('新提交的排在前面', () => {
|
|
const tasks = resourceCanvasResourceEditTasks({
|
|
submissions: [
|
|
liveSubmission({
|
|
operationId: 'op-new',
|
|
createdAtMillis: 1_700_000_200_000,
|
|
}),
|
|
],
|
|
pendingEdits: [
|
|
pendingEdit({ operationId: 'op-old', createdAt: 1_700_000_000_000 }),
|
|
],
|
|
});
|
|
expect(tasks.map((task) => task.operationId)).toEqual(['op-new', 'op-old']);
|
|
});
|
|
});
|