perf(canvas): reduce Convex hot-path query load
This commit is contained in:
66
tests/canvas-graph-query-cache.test.ts
Normal file
66
tests/canvas-graph-query-cache.test.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@/convex/_generated/api", () => ({
|
||||
api: {
|
||||
canvasGraph: { get: "canvasGraph.get" },
|
||||
},
|
||||
}));
|
||||
|
||||
import {
|
||||
getCanvasGraphEdgesFromQuery,
|
||||
getCanvasGraphNodesFromQuery,
|
||||
setCanvasGraphEdgesInQuery,
|
||||
setCanvasGraphNodesInQuery,
|
||||
} from "@/components/canvas/canvas-graph-query-cache";
|
||||
|
||||
describe("canvas graph query cache helpers", () => {
|
||||
it("returns cached nodes and edges from the shared graph query", () => {
|
||||
const graph = {
|
||||
nodes: [{ _id: "node_1" }],
|
||||
edges: [{ _id: "edge_1" }],
|
||||
};
|
||||
const localStore = {
|
||||
getQuery: vi.fn((_query, args) =>
|
||||
args.canvasId === "canvas_1" ? graph : undefined,
|
||||
),
|
||||
};
|
||||
|
||||
expect(getCanvasGraphNodesFromQuery(localStore as never, { canvasId: "canvas_1" as never })).toEqual(graph.nodes);
|
||||
expect(getCanvasGraphEdgesFromQuery(localStore as never, { canvasId: "canvas_1" as never })).toEqual(graph.edges);
|
||||
});
|
||||
|
||||
it("preserves the sibling collection when replacing nodes or edges", () => {
|
||||
const graph = {
|
||||
nodes: [{ _id: "node_1" }],
|
||||
edges: [{ _id: "edge_1" }],
|
||||
};
|
||||
const localStore = {
|
||||
getQuery: vi.fn((_query, args) =>
|
||||
Object.keys(args).length === 1 && args.canvasId === "canvas_1"
|
||||
? graph
|
||||
: undefined,
|
||||
),
|
||||
setQuery: vi.fn(),
|
||||
};
|
||||
|
||||
setCanvasGraphNodesInQuery(localStore as never, {
|
||||
canvasId: "canvas_1" as never,
|
||||
nodes: [{ _id: "node_2" }] as never,
|
||||
});
|
||||
setCanvasGraphEdgesInQuery(localStore as never, {
|
||||
canvasId: "canvas_1" as never,
|
||||
edges: [{ _id: "edge_2" }] as never,
|
||||
});
|
||||
|
||||
expect(localStore.getQuery).toHaveBeenNthCalledWith(1, "canvasGraph.get", { canvasId: "canvas_1" });
|
||||
expect(localStore.getQuery).toHaveBeenNthCalledWith(2, "canvasGraph.get", { canvasId: "canvas_1" });
|
||||
expect(localStore.setQuery).toHaveBeenNthCalledWith(1, "canvasGraph.get", { canvasId: "canvas_1" }, {
|
||||
nodes: [{ _id: "node_2" }],
|
||||
edges: [{ _id: "edge_1" }],
|
||||
});
|
||||
expect(localStore.setQuery).toHaveBeenNthCalledWith(2, "canvasGraph.get", { canvasId: "canvas_1" }, {
|
||||
nodes: [{ _id: "node_1" }],
|
||||
edges: [{ _id: "edge_2" }],
|
||||
});
|
||||
});
|
||||
});
|
||||
371
tests/canvas-presets-context.test.ts
Normal file
371
tests/canvas-presets-context.test.ts
Normal file
@@ -0,0 +1,371 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import React, { act, useEffect } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const queryMock = vi.hoisted(() => vi.fn());
|
||||
const saveMutationMock = vi.hoisted(() => vi.fn());
|
||||
const authState = vi.hoisted(() => ({ isAuthenticated: true }));
|
||||
const convexClient = vi.hoisted(() => ({ query: queryMock }));
|
||||
|
||||
vi.mock("@/convex/_generated/api", () => ({
|
||||
api: {
|
||||
presets: {
|
||||
list: "presets.list",
|
||||
save: "presets.save",
|
||||
},
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("convex/react", () => ({
|
||||
useConvex: () => convexClient,
|
||||
useConvexAuth: () => ({ isAuthenticated: authState.isAuthenticated }),
|
||||
useMutation: (key: string) => {
|
||||
if (key === "presets.save") {
|
||||
return saveMutationMock;
|
||||
}
|
||||
return vi.fn();
|
||||
},
|
||||
}));
|
||||
|
||||
import {
|
||||
CanvasPresetsProvider,
|
||||
useCanvasAdjustmentPresets,
|
||||
useSaveCanvasAdjustmentPreset,
|
||||
} from "@/components/canvas/canvas-presets-context";
|
||||
|
||||
type PresetConsumerSnapshot = {
|
||||
curves: string[];
|
||||
colorAdjust: string[];
|
||||
};
|
||||
|
||||
const latestSnapshotRef: { current: PresetConsumerSnapshot | null } = { current: null };
|
||||
const latestSaveRef: {
|
||||
current:
|
||||
| ((args: {
|
||||
name: string;
|
||||
nodeType: "curves" | "color-adjust" | "light-adjust" | "detail-adjust";
|
||||
params: unknown;
|
||||
}) => Promise<unknown>)
|
||||
| null;
|
||||
} = { current: null };
|
||||
|
||||
function Harness() {
|
||||
const curves = useCanvasAdjustmentPresets("curves");
|
||||
const colorAdjust = useCanvasAdjustmentPresets("color-adjust");
|
||||
const savePreset = useSaveCanvasAdjustmentPreset();
|
||||
|
||||
useEffect(() => {
|
||||
latestSnapshotRef.current = {
|
||||
curves: curves.map((preset) => preset.name),
|
||||
colorAdjust: colorAdjust.map((preset) => preset.name),
|
||||
};
|
||||
latestSaveRef.current = savePreset;
|
||||
|
||||
return () => {
|
||||
latestSnapshotRef.current = null;
|
||||
latestSaveRef.current = null;
|
||||
};
|
||||
}, [colorAdjust, curves, savePreset]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
describe("CanvasPresetsProvider", () => {
|
||||
let container: HTMLDivElement | null = null;
|
||||
let root: Root | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
if (root) {
|
||||
await act(async () => {
|
||||
root?.unmount();
|
||||
});
|
||||
}
|
||||
vi.useRealTimers();
|
||||
container?.remove();
|
||||
container = null;
|
||||
root = null;
|
||||
latestSnapshotRef.current = null;
|
||||
latestSaveRef.current = null;
|
||||
authState.isAuthenticated = true;
|
||||
queryMock.mockReset();
|
||||
saveMutationMock.mockReset();
|
||||
});
|
||||
|
||||
it("loads presets with an imperative one-off query and exposes them by node type", async () => {
|
||||
queryMock.mockResolvedValue([
|
||||
{ _id: "preset-1", name: "Film Fade", nodeType: "curves", params: {} },
|
||||
{ _id: "preset-2", name: "Warm Pop", nodeType: "color-adjust", params: {} },
|
||||
{ _id: "preset-3", name: "Crisp Contrast", nodeType: "curves", params: {} },
|
||||
]);
|
||||
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
|
||||
await act(async () => {
|
||||
root?.render(
|
||||
React.createElement(CanvasPresetsProvider, { enabled: true }, React.createElement(Harness)),
|
||||
);
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(latestSnapshotRef.current).toEqual({
|
||||
curves: ["Film Fade", "Crisp Contrast"],
|
||||
colorAdjust: ["Warm Pop"],
|
||||
});
|
||||
});
|
||||
|
||||
expect(queryMock).toHaveBeenCalledTimes(1);
|
||||
expect(queryMock).toHaveBeenCalledWith("presets.list", {});
|
||||
});
|
||||
|
||||
it("refreshes provider state after saving a preset through context", async () => {
|
||||
queryMock
|
||||
.mockResolvedValueOnce([
|
||||
{ _id: "preset-1", name: "Film Fade", nodeType: "curves", params: {} },
|
||||
])
|
||||
.mockResolvedValueOnce([
|
||||
{ _id: "preset-2", name: "Studio Glow", nodeType: "curves", params: {} },
|
||||
{ _id: "preset-1", name: "Film Fade", nodeType: "curves", params: {} },
|
||||
]);
|
||||
saveMutationMock.mockResolvedValue("preset-2");
|
||||
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
|
||||
await act(async () => {
|
||||
root?.render(
|
||||
React.createElement(CanvasPresetsProvider, { enabled: true }, React.createElement(Harness)),
|
||||
);
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(latestSnapshotRef.current?.curves).toEqual(["Film Fade"]);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await latestSaveRef.current?.({
|
||||
name: "Studio Glow",
|
||||
nodeType: "curves",
|
||||
params: { contrast: 10 },
|
||||
});
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(latestSnapshotRef.current?.curves).toEqual(["Studio Glow", "Film Fade"]);
|
||||
});
|
||||
|
||||
expect(saveMutationMock).toHaveBeenCalledWith({
|
||||
name: "Studio Glow",
|
||||
nodeType: "curves",
|
||||
params: { contrast: 10 },
|
||||
});
|
||||
expect(queryMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
|
||||
it("waits for auth before fetching presets", async () => {
|
||||
authState.isAuthenticated = false;
|
||||
queryMock.mockResolvedValue([
|
||||
{ _id: "preset-1", name: "Recovered", nodeType: "curves", params: {} },
|
||||
]);
|
||||
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
|
||||
await act(async () => {
|
||||
root?.render(
|
||||
React.createElement(CanvasPresetsProvider, { enabled: true }, React.createElement(Harness)),
|
||||
);
|
||||
});
|
||||
|
||||
expect(queryMock).not.toHaveBeenCalled();
|
||||
expect(latestSnapshotRef.current).toEqual({ curves: [], colorAdjust: [] });
|
||||
|
||||
authState.isAuthenticated = true;
|
||||
await act(async () => {
|
||||
root?.render(
|
||||
React.createElement(CanvasPresetsProvider, { enabled: true }, React.createElement(Harness)),
|
||||
);
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(latestSnapshotRef.current?.curves).toEqual(["Recovered"]);
|
||||
});
|
||||
|
||||
expect(queryMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it("retries after repeated transient preset fetch failures", async () => {
|
||||
vi.useFakeTimers();
|
||||
queryMock
|
||||
.mockRejectedValueOnce(new Error("temporary-1"))
|
||||
.mockRejectedValueOnce(new Error("temporary-2"))
|
||||
.mockResolvedValueOnce([
|
||||
{ _id: "preset-1", name: "Recovered", nodeType: "curves", params: {} },
|
||||
]);
|
||||
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
|
||||
await act(async () => {
|
||||
root?.render(
|
||||
React.createElement(CanvasPresetsProvider, { enabled: true }, React.createElement(Harness)),
|
||||
);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(queryMock).toHaveBeenCalledTimes(2);
|
||||
expect(latestSnapshotRef.current?.curves).toEqual([]);
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(1000);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(latestSnapshotRef.current?.curves).toEqual(["Recovered"]);
|
||||
});
|
||||
expect(queryMock).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
it("does not fail the save flow when only the refresh step fails", async () => {
|
||||
vi.useFakeTimers();
|
||||
queryMock
|
||||
.mockResolvedValueOnce([
|
||||
{ _id: "preset-1", name: "Film Fade", nodeType: "curves", params: {} },
|
||||
])
|
||||
.mockRejectedValueOnce(new Error("refresh-temporary"))
|
||||
.mockResolvedValueOnce([
|
||||
{ _id: "preset-2", name: "Studio Glow", nodeType: "curves", params: {} },
|
||||
{ _id: "preset-1", name: "Film Fade", nodeType: "curves", params: {} },
|
||||
]);
|
||||
saveMutationMock.mockResolvedValue("preset-2");
|
||||
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
|
||||
await act(async () => {
|
||||
root?.render(
|
||||
React.createElement(CanvasPresetsProvider, { enabled: true }, React.createElement(Harness)),
|
||||
);
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(latestSnapshotRef.current?.curves).toEqual(["Film Fade"]);
|
||||
});
|
||||
|
||||
const savePromise = latestSaveRef.current?.({
|
||||
name: "Studio Glow",
|
||||
nodeType: "curves",
|
||||
params: { contrast: 10 },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await savePromise;
|
||||
});
|
||||
|
||||
expect(saveMutationMock).toHaveBeenCalledTimes(1);
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(latestSnapshotRef.current?.curves).toEqual(["Studio Glow", "Film Fade"]);
|
||||
});
|
||||
expect(queryMock).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
it("ignores stale failed refreshes after a newer refresh succeeds", async () => {
|
||||
vi.useFakeTimers();
|
||||
|
||||
let rejectOlderRefresh: ((error: Error) => void) | null = null;
|
||||
const olderRefreshPromise = new Promise<never>((_, reject) => {
|
||||
rejectOlderRefresh = reject;
|
||||
});
|
||||
|
||||
queryMock
|
||||
.mockResolvedValueOnce([
|
||||
{ _id: "preset-1", name: "Film Fade", nodeType: "curves", params: {} },
|
||||
])
|
||||
.mockImplementationOnce(() => olderRefreshPromise)
|
||||
.mockResolvedValueOnce([
|
||||
{ _id: "preset-3", name: "Cinematic Glow", nodeType: "curves", params: {} },
|
||||
{ _id: "preset-2", name: "Studio Glow", nodeType: "curves", params: {} },
|
||||
{ _id: "preset-1", name: "Film Fade", nodeType: "curves", params: {} },
|
||||
]);
|
||||
saveMutationMock.mockResolvedValue("preset-2");
|
||||
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
|
||||
await act(async () => {
|
||||
root?.render(
|
||||
React.createElement(CanvasPresetsProvider, { enabled: true }, React.createElement(Harness)),
|
||||
);
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(latestSnapshotRef.current?.curves).toEqual(["Film Fade"]);
|
||||
});
|
||||
|
||||
const firstSavePromise = latestSaveRef.current?.({
|
||||
name: "Studio Glow",
|
||||
nodeType: "curves",
|
||||
params: { contrast: 10 },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
const secondSavePromise = latestSaveRef.current?.({
|
||||
name: "Cinematic Glow",
|
||||
nodeType: "curves",
|
||||
params: { contrast: 20 },
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await secondSavePromise;
|
||||
});
|
||||
|
||||
await vi.waitFor(() => {
|
||||
expect(latestSnapshotRef.current?.curves).toEqual([
|
||||
"Cinematic Glow",
|
||||
"Studio Glow",
|
||||
"Film Fade",
|
||||
]);
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
rejectOlderRefresh?.(new Error("older refresh failed"));
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await firstSavePromise;
|
||||
});
|
||||
|
||||
expect(latestSnapshotRef.current?.curves).toEqual([
|
||||
"Cinematic Glow",
|
||||
"Studio Glow",
|
||||
"Film Fade",
|
||||
]);
|
||||
|
||||
await act(async () => {
|
||||
vi.advanceTimersByTime(1000);
|
||||
await Promise.resolve();
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(queryMock).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
|
||||
});
|
||||
63
tests/convex/canvas-graph-query.test.ts
Normal file
63
tests/convex/canvas-graph-query.test.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
|
||||
vi.mock("@/convex/helpers", () => ({
|
||||
requireAuth: vi.fn(),
|
||||
}));
|
||||
|
||||
import type { Id } from "@/convex/_generated/dataModel";
|
||||
import { loadCanvasGraph } from "@/convex/canvasGraph";
|
||||
|
||||
describe("loadCanvasGraph", () => {
|
||||
it("returns nodes and edges for an authorized canvas", async () => {
|
||||
const canvasId = "canvas_1" as Id<"canvases">;
|
||||
const nodes = [{ _id: "node_1", canvasId }];
|
||||
const edges = [{ _id: "edge_1", canvasId }];
|
||||
|
||||
const ctx = {
|
||||
db: {
|
||||
get: vi.fn(async (id: Id<"canvases">) =>
|
||||
id === canvasId ? { _id: canvasId, ownerId: "user_1" } : null,
|
||||
),
|
||||
query: vi.fn((table: "nodes" | "edges") => ({
|
||||
withIndex: vi.fn((_index: string, apply: (q: { eq: (field: string, value: unknown) => unknown }) => unknown) => {
|
||||
const queryBuilder = {
|
||||
eq: vi.fn().mockReturnThis(),
|
||||
};
|
||||
apply(queryBuilder);
|
||||
return {
|
||||
collect: vi.fn(async () => (table === "nodes" ? nodes : edges)),
|
||||
};
|
||||
}),
|
||||
})),
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
loadCanvasGraph(ctx as never, {
|
||||
canvasId,
|
||||
userId: "user_1",
|
||||
}),
|
||||
).resolves.toEqual({
|
||||
canvas: { _id: canvasId, ownerId: "user_1" },
|
||||
nodes,
|
||||
edges,
|
||||
});
|
||||
});
|
||||
|
||||
it("throws when the canvas belongs to another user", async () => {
|
||||
const canvasId = "canvas_1" as Id<"canvases">;
|
||||
const ctx = {
|
||||
db: {
|
||||
get: vi.fn(async () => ({ _id: canvasId, ownerId: "other_user" })),
|
||||
query: vi.fn(),
|
||||
},
|
||||
};
|
||||
|
||||
await expect(
|
||||
loadCanvasGraph(ctx as never, {
|
||||
canvasId,
|
||||
userId: "user_1",
|
||||
}),
|
||||
).rejects.toThrow("Canvas not found");
|
||||
});
|
||||
});
|
||||
@@ -35,6 +35,7 @@ vi.mock("lucide-react", () => ({
|
||||
|
||||
vi.mock("@/components/canvas/canvas-presets-context", () => ({
|
||||
useCanvasAdjustmentPresets: () => [],
|
||||
useSaveCanvasAdjustmentPreset: () => vi.fn(async () => undefined),
|
||||
}));
|
||||
|
||||
vi.mock("@/components/canvas/canvas-sync-context", () => ({
|
||||
|
||||
115
tests/use-canvas-data.test.ts
Normal file
115
tests/use-canvas-data.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
// @vitest-environment jsdom
|
||||
|
||||
import React, { act, useEffect } from "react";
|
||||
import { createRoot, type Root } from "react-dom/client";
|
||||
import { afterEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const useQueryMock = vi.hoisted(() => vi.fn());
|
||||
const resolveStorageUrlsForCanvasMock = vi.hoisted(() => vi.fn());
|
||||
|
||||
vi.mock("@/convex/_generated/api", () => ({
|
||||
api: {
|
||||
canvasGraph: { get: "canvasGraph.get" },
|
||||
canvases: { get: "canvases.get" },
|
||||
storage: { batchGetUrlsForCanvas: "storage.batchGetUrlsForCanvas" },
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock("convex/react", () => ({
|
||||
useConvexAuth: () => ({ isLoading: false, isAuthenticated: true }),
|
||||
useMutation: () => resolveStorageUrlsForCanvasMock,
|
||||
useQuery: useQueryMock,
|
||||
}));
|
||||
|
||||
vi.mock("@/lib/auth-client", () => ({
|
||||
authClient: {
|
||||
useSession: () => ({
|
||||
data: { user: { email: "user@example.com" } },
|
||||
isPending: false,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
import { useCanvasData } from "@/components/canvas/use-canvas-data";
|
||||
|
||||
const latestHookValue: {
|
||||
current: ReturnType<typeof useCanvasData> | null;
|
||||
} = { current: null };
|
||||
|
||||
function HookHarness() {
|
||||
const value = useCanvasData({ canvasId: "canvas_1" as never });
|
||||
|
||||
useEffect(() => {
|
||||
latestHookValue.current = value;
|
||||
return () => {
|
||||
latestHookValue.current = null;
|
||||
};
|
||||
}, [value]);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT = true;
|
||||
|
||||
describe("useCanvasData", () => {
|
||||
let container: HTMLDivElement | null = null;
|
||||
let root: Root | null = null;
|
||||
|
||||
afterEach(async () => {
|
||||
if (root) {
|
||||
await act(async () => {
|
||||
root?.unmount();
|
||||
});
|
||||
}
|
||||
container?.remove();
|
||||
container = null;
|
||||
root = null;
|
||||
latestHookValue.current = null;
|
||||
useQueryMock.mockReset();
|
||||
resolveStorageUrlsForCanvasMock.mockReset();
|
||||
});
|
||||
|
||||
it("subscribes to the shared graph query and derives nodes and edges from it", async () => {
|
||||
const graph = {
|
||||
nodes: [
|
||||
{
|
||||
_id: "node_1",
|
||||
canvasId: "canvas_1",
|
||||
data: { storageId: "storage_1" },
|
||||
},
|
||||
],
|
||||
edges: [{ _id: "edge_1", canvasId: "canvas_1" }],
|
||||
};
|
||||
|
||||
useQueryMock.mockImplementation((query: string) => {
|
||||
if (query === "canvasGraph.get") {
|
||||
return graph;
|
||||
}
|
||||
if (query === "canvases.get") {
|
||||
return { _id: "canvas_1" };
|
||||
}
|
||||
return undefined;
|
||||
});
|
||||
resolveStorageUrlsForCanvasMock.mockResolvedValue({ storage_1: "https://cdn.example.com/1" });
|
||||
|
||||
container = document.createElement("div");
|
||||
document.body.appendChild(container);
|
||||
root = createRoot(container);
|
||||
|
||||
await act(async () => {
|
||||
root?.render(React.createElement(HookHarness));
|
||||
});
|
||||
|
||||
await act(async () => {
|
||||
await Promise.resolve();
|
||||
});
|
||||
|
||||
expect(useQueryMock).toHaveBeenCalledWith("canvasGraph.get", { canvasId: "canvas_1" });
|
||||
expect(latestHookValue.current?.convexNodes).toEqual(graph.nodes);
|
||||
expect(latestHookValue.current?.convexEdges).toEqual(graph.edges);
|
||||
expect(resolveStorageUrlsForCanvasMock).toHaveBeenCalledWith({
|
||||
canvasId: "canvas_1",
|
||||
storageIds: ["storage_1"],
|
||||
});
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user