fix(canvas): 画布删除 - 跳过关联素材预加载与合同校验
This commit is contained in:
@@ -5,6 +5,13 @@ description: 当前项目已实现功能的手工验证清单与未确认边界
|
||||
|
||||
# 待测试
|
||||
|
||||
## 画布删除不再预加载关联素材
|
||||
|
||||
登录后的画布删除直接按 ID 请求服务端,不再先进入编辑加载流程,避免历史素材的 MIME 等字段校验阻塞删除。保留账号会话检查、服务端删除失败上抛及成功后清理本地缓存的顺序。
|
||||
|
||||
- [x] 26 项画布同步与素材合同专项测试通过;新增回归覆盖关联素材为 `image/*`、未缓存画布、重复 ID,以及批量删除部分失败后缓存与模拟服务端状态一致。
|
||||
- [ ] 在真实账号下从画布列表和编辑页删除包含异常 MIME 素材的画布,刷新确认已删除;自动化验证使用模拟 API,未删除实际账号数据。
|
||||
|
||||
## 站点皮肤、控件焦点与外观分类
|
||||
|
||||
主按钮填充改为主题参数统一管理,经典黑白采用黑白基础色与 115° 紫蓝渐变;复制主题后可分别编辑明暗模式的纯色/渐变、角度、常态/悬停/按下双色和文字色。创作页“开始创作”与前后台主按钮使用相同参数,危险、幽灵和禁用按钮不套用渐变。后台隔离用户工作台外壳与浮层样式。输入、选择、日期和搜索控件移除重复焦点阴影,保留键盘焦点与错误状态。站点及外观按品牌、登录页、欢迎页、SEO、页尾备案、皮肤六个 Tab 展示,页面草稿统一保存,欢迎页开关继续独立即时保存。
|
||||
|
||||
@@ -457,9 +457,7 @@ export async function deleteCanvasProjectsWithRemoteSync(ids: string[]) {
|
||||
const epoch = sessionEpoch;
|
||||
const projectIds = [...new Set(ids.map((id) => id.trim()).filter(Boolean))];
|
||||
if (!projectIds.length) return;
|
||||
if (incrementalSession) {
|
||||
for (const id of projectIds) await loadCanvasProjectForEditing(id);
|
||||
}
|
||||
// 删除只依赖画布 ID,跳过编辑加载,避免关联素材的合同校验阻塞删除。
|
||||
await withRemoteUserDataSyncExclusive(async () => {
|
||||
if (epoch !== sessionEpoch) throw new Error("账号已切换,请重新选择要删除的画布");
|
||||
if (activeRemoteUserId) requireRemoteUserDataBaseline();
|
||||
|
||||
@@ -4,7 +4,7 @@ import { apiClient } from "../src/services/api/request";
|
||||
import { canvasContentHash } from "../src/lib/canvas/canvas-content";
|
||||
import { rebaseCanvasProjects, parseCanvasStorageDocument } from "../src/lib/canvas/canvas-storage-revision";
|
||||
import { readCanvasSyncDrafts } from "../src/services/canvas-sync-drafts";
|
||||
import { applyAgentCanvasPatches, refreshCanvasAfterAgent, initializeRemoteUserDataSession, installRemoteUserDataAutoSync, loadCanvasProjectForEditing, resetRemoteUserDataSync, saveRemoteUserDataNow, syncRemoteUserData } from "../src/services/user-data-sync";
|
||||
import { applyAgentCanvasPatches, deleteCanvasProjectsWithRemoteSync, refreshCanvasAfterAgent, initializeRemoteUserDataSession, installRemoteUserDataAutoSync, loadCanvasProjectForEditing, resetRemoteUserDataSync, saveRemoteUserDataNow, syncRemoteUserData } from "../src/services/user-data-sync";
|
||||
import { createAgentCanvasSync } from "../src/services/agent-canvas-sync";
|
||||
import { flushCanvasStorePersistence, useCanvasStore, type CanvasProject } from "../src/stores/canvas/use-canvas-store";
|
||||
import { flushAssetStorePersistence, useAssetStore } from "../src/stores/use-asset-store";
|
||||
@@ -22,6 +22,7 @@ let autoSave: (() => void) | undefined;
|
||||
let remote = new Map<string, CanvasProject>();
|
||||
let requests: Array<{ method: string; id: string; project?: CanvasProject }> = [];
|
||||
let beforePut: (() => Promise<void>) | undefined;
|
||||
let deleteFailureId: string | undefined;
|
||||
|
||||
function canvas(id = "canvas"): CanvasProject {
|
||||
return {
|
||||
@@ -52,6 +53,7 @@ beforeEach(async () => {
|
||||
requests = [];
|
||||
autoSave = undefined;
|
||||
beforePut = undefined;
|
||||
deleteFailureId = undefined;
|
||||
remote = new Map([
|
||||
["canvas", canvas()],
|
||||
["other", canvas("other")],
|
||||
@@ -82,7 +84,19 @@ beforeEach(async () => {
|
||||
let data: unknown;
|
||||
let status = 200;
|
||||
if (id === "snapshot") data = { projects: structuredClone([...remote.values()]), assets: [] };
|
||||
else if (id === "restore" && method === "post") {
|
||||
else if (method === "delete") {
|
||||
if (id === deleteFailureId) status = 403;
|
||||
else {
|
||||
remote.delete(id);
|
||||
data = { id };
|
||||
}
|
||||
} else if (id === "batch" && method === "post") {
|
||||
data = { assets: [{
|
||||
id: "invalid-mime-asset", kind: "image", title: "历史素材", coverUrl: "", tags: [],
|
||||
createdAt: "2026-09-01", updatedAt: "2026-09-01",
|
||||
data: { dataUrl: "https://example.com/image.png", width: 100, height: 100, bytes: 1, mimeType: "image/*" },
|
||||
}] };
|
||||
} else if (id === "restore" && method === "post") {
|
||||
const current = remote.get("canvas")!;
|
||||
if (body.revision !== current.revision) status = 409;
|
||||
else {
|
||||
@@ -119,6 +133,39 @@ afterEach(async () => {
|
||||
else Object.defineProperty(globalThis, "window", { configurable: true, value: originalWindow });
|
||||
});
|
||||
|
||||
test("deletion skips editing and invalid MIME assets, including an uncached canvas", async () => {
|
||||
const project = canvas();
|
||||
project.nodes[0].metadata = { assetId: "invalid-mime-asset" };
|
||||
remote.set(project.id, project);
|
||||
await initializeRemoteUserDataSession(scope);
|
||||
await expect(loadCanvasProjectForEditing(project.id)).rejects.toThrow("具体 MIME");
|
||||
useCanvasStore.getState().deleteProjects(["other"]);
|
||||
await flushCanvasStorePersistence();
|
||||
requests = [];
|
||||
|
||||
await deleteCanvasProjectsWithRemoteSync([" canvas ", "canvas", "other"]);
|
||||
|
||||
expect(requests.map(({ method, id }) => ({ method, id }))).toEqual([
|
||||
{ method: "delete", id: "canvas" }, { method: "delete", id: "other" },
|
||||
]);
|
||||
expect(remote.size).toBe(0);
|
||||
expect(useCanvasStore.getState().projects).toEqual([]);
|
||||
await useCanvasStore.persist.rehydrate();
|
||||
expect(useCanvasStore.getState().projects).toEqual([]);
|
||||
});
|
||||
|
||||
test("batch deletion retains failed canvases and persists successful deletions", async () => {
|
||||
await initializeRemoteUserDataSession(scope);
|
||||
deleteFailureId = "other";
|
||||
|
||||
await expect(deleteCanvasProjectsWithRemoteSync(["canvas", "other"])).rejects.toThrow();
|
||||
|
||||
expect([...remote.keys()]).toEqual(["other"]);
|
||||
expect(useCanvasStore.getState().projects.map((project) => project.id)).toEqual(["other"]);
|
||||
await useCanvasStore.persist.rehydrate();
|
||||
expect(useCanvasStore.getState().projects.map((project) => project.id)).toEqual(["other"]);
|
||||
});
|
||||
|
||||
test("stale viewport, no-op restore and open do not submit old content", async () => {
|
||||
const old = useCanvasStore.getState().openProject("canvas")!;
|
||||
remote.set("canvas", { ...addNode(canvas(), "afternoon-video"), revision: 2 });
|
||||
|
||||
Reference in New Issue
Block a user