feat(assets): 素材回收站与画布历史 - 合并 PR #370 并修复删除一致性
This commit is contained in:
@@ -17,16 +17,19 @@ import { CanvasNodeType, type CanvasAssistantSession, type CanvasConnection, typ
|
||||
import { deleteAssetWithRemoteSync, deleteCanvasProjectsWithRemoteSync, installRemoteUserDataAutoSync, resetRemoteUserDataSync, saveRemoteUserDataNow, syncRemoteUserData, withRemoteUserDataSyncExclusive } from "../src/services/user-data-sync";
|
||||
import { apiClient } from "../src/services/api/request";
|
||||
import { useUserStore } from "../src/stores/use-user-store";
|
||||
import { CANVAS_HISTORY_STORE_KEY, useCanvasHistoryStore } from "../src/stores/canvas/use-canvas-history-store";
|
||||
|
||||
test("creation recovery observes streaming text tasks after reload", () => {
|
||||
const conversations = [{
|
||||
id: "conversation-text-recovery",
|
||||
messages: [
|
||||
{ id: "text-streaming", role: "assistant" as const, mode: "text", status: "streaming", taskIds: ["task-text"] },
|
||||
{ id: "text-done", role: "assistant" as const, mode: "text", status: "done", taskIds: ["task-text-done"] },
|
||||
{ id: "image-pending", role: "assistant" as const, mode: "image", status: "pending", taskIds: ["task-image"] },
|
||||
],
|
||||
}];
|
||||
const conversations = [
|
||||
{
|
||||
id: "conversation-text-recovery",
|
||||
messages: [
|
||||
{ id: "text-streaming", role: "assistant" as const, mode: "text", status: "streaming", taskIds: ["task-text"] },
|
||||
{ id: "text-done", role: "assistant" as const, mode: "text", status: "done", taskIds: ["task-text-done"] },
|
||||
{ id: "image-pending", role: "assistant" as const, mode: "image", status: "pending", taskIds: ["task-image"] },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
expect(pendingCreationTaskIds(conversations)).toEqual(["task-text", "task-image"]);
|
||||
expect(pendingCreationTaskKey(conversations)).toContain("conversation-text-recovery:text-streaming:task-text");
|
||||
@@ -788,9 +791,13 @@ test("generation Asset publication preserves an ordinary edit queued behind its
|
||||
const effectKey = "materialize:stale-publication:0";
|
||||
let gateGenerationRead = false;
|
||||
let markGenerationReadStarted!: () => void;
|
||||
const generationReadStarted = new Promise<void>((resolve) => { markGenerationReadStarted = resolve; });
|
||||
const generationReadStarted = new Promise<void>((resolve) => {
|
||||
markGenerationReadStarted = resolve;
|
||||
});
|
||||
let releaseGenerationRead!: () => void;
|
||||
const generationReadGate = new Promise<void>((resolve) => { releaseGenerationRead = resolve; });
|
||||
const generationReadGate = new Promise<void>((resolve) => {
|
||||
releaseGenerationRead = resolve;
|
||||
});
|
||||
localforage.getItem = (async (key: string) => {
|
||||
const snapshot = values.get(key) ?? null;
|
||||
if (key === assetKey && gateGenerationRead) {
|
||||
@@ -4471,3 +4478,57 @@ test("user session stays unhydrated until the remote baseline is durable", async
|
||||
else Object.defineProperty(globalThis, "window", { configurable: true, value: originalWindow });
|
||||
}
|
||||
});
|
||||
|
||||
test("deleted canvas history rehydrates from the active account scope only", async () => {
|
||||
const originalWindow = (globalThis as { window?: unknown }).window;
|
||||
const originalGetItem = localforage.getItem.bind(localforage);
|
||||
const originalSetItem = localforage.setItem.bind(localforage);
|
||||
const previousScope = getActiveUserScope();
|
||||
const previousHistory = useCanvasHistoryStore.getState().deletedProjects;
|
||||
const values = new Map<string, string>();
|
||||
const localStorageValues = new Map<string, string>();
|
||||
Object.defineProperty(globalThis, "window", {
|
||||
configurable: true,
|
||||
value: {
|
||||
localStorage: {
|
||||
getItem: (key: string) => localStorageValues.get(key) ?? null,
|
||||
setItem: (key: string, value: string) => localStorageValues.set(key, value),
|
||||
removeItem: (key: string) => localStorageValues.delete(key),
|
||||
},
|
||||
},
|
||||
});
|
||||
localforage.getItem = (async (key: string) => values.get(key) ?? null) as typeof localforage.getItem;
|
||||
localforage.setItem = (async (key: string, value: string) => {
|
||||
values.set(key, value);
|
||||
return value;
|
||||
}) as typeof localforage.setItem;
|
||||
|
||||
try {
|
||||
setActiveUserScope("history-account-A");
|
||||
useCanvasHistoryStore.setState({ deletedProjects: [] });
|
||||
useCanvasHistoryStore.getState().recordDeletedProjects([storedCanvasProject("deleted-A", "账号 A 的历史")]);
|
||||
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
||||
|
||||
setActiveUserScope("history-account-B");
|
||||
useCanvasHistoryStore.setState({ deletedProjects: [] });
|
||||
useCanvasHistoryStore.getState().recordDeletedProjects([storedCanvasProject("deleted-B", "账号 B 的历史")]);
|
||||
await new Promise<void>((resolve) => queueMicrotask(resolve));
|
||||
|
||||
setActiveUserScope("history-account-A");
|
||||
await useCanvasHistoryStore.persist.rehydrate();
|
||||
expect(useCanvasHistoryStore.getState().deletedProjects.map((item) => item.id)).toEqual(["deleted-A"]);
|
||||
|
||||
setActiveUserScope("history-account-B");
|
||||
await useCanvasHistoryStore.persist.rehydrate();
|
||||
expect(useCanvasHistoryStore.getState().deletedProjects.map((item) => item.id)).toEqual(["deleted-B"]);
|
||||
expect(values.has(`${CANVAS_HISTORY_STORE_KEY}:user:history-account-A`)).toBe(true);
|
||||
expect(values.has(`${CANVAS_HISTORY_STORE_KEY}:user:history-account-B`)).toBe(true);
|
||||
} finally {
|
||||
setActiveUserScope(previousScope);
|
||||
useCanvasHistoryStore.setState({ deletedProjects: previousHistory });
|
||||
localforage.getItem = originalGetItem;
|
||||
localforage.setItem = originalSetItem;
|
||||
if (originalWindow === undefined) delete (globalThis as { window?: unknown }).window;
|
||||
else Object.defineProperty(globalThis, "window", { configurable: true, value: originalWindow });
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user