ai-chatbot/tests/routes/canvas-entry-performance.test.ts
2026-07-10 18:52:41 +08:00

174 lines
6.6 KiB
TypeScript

import { expect, test } from "@playwright/test";
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
function readSource(path: string) {
return readFileSync(resolve(process.cwd(), path), "utf8");
}
function readFunctionBlock(source: string, start: string, end: string) {
const startIndex = source.indexOf(start);
const endIndex = source.indexOf(end, startIndex + start.length);
expect(startIndex, `Missing ${start}`).toBeGreaterThanOrEqual(0);
expect(endIndex, `Missing ${end}`).toBeGreaterThan(startIndex);
return source.slice(startIndex, endIndex);
}
test.describe("home to canvas entry performance", () => {
test("navigates after project creation without waiting for a list refresh", () => {
const source = readSource("components/home/lovart-home.tsx");
const composerFlow = readFunctionBlock(
source,
"const createCanvasFromComposer = async () =>",
"const openNewCanvas = async () =>"
);
const blankCanvasFlow = readFunctionBlock(
source,
"const openNewCanvas = async () =>",
"const prepareCanvasProject = useCallback"
);
expect(composerFlow).toContain("prepareCanvasNavigation(canvasId)");
expect(composerFlow).toMatch(/router\.push\(`\/canvas\/\$\{canvasId\}`\)/);
expect(composerFlow).not.toContain("await mutateCanvas()");
expect(blankCanvasFlow).toContain('fetch("/api/canvas"');
expect(blankCanvasFlow).toMatch(/router\.push\(`\/canvas\/\$\{canvasId\}`\)/);
expect(blankCanvasFlow).not.toContain('router.push("/canvas?new=1")');
});
test("starts editor and chat downloads before the user opens the panel", () => {
const homeSource = readSource("components/home/lovart-home.tsx");
const editorSource = readSource(
"app/canvas/[id]/CanvasEditorLegacyPageClient.tsx"
);
const workspacePreloadSource = readSource(
"lib/client/canvas-workspace-preload.ts"
);
expect(homeSource).toContain("requestIdleCallback(preloadCanvasWorkspace");
expect(homeSource).toContain("timeout: 1200");
expect(homeSource).toContain(
"window.setTimeout(preloadCanvasWorkspace, 450)"
);
expect(workspacePreloadSource).toContain("preloadCanvasEditor()");
expect(workspacePreloadSource).toContain("preloadCanvasChatPanel()");
expect(editorSource).toContain("preloadCanvasChatPanel();");
expect(editorSource).toContain("preloadCanvasChatData(id);");
const entryWarmup = readFunctionBlock(
editorSource,
"useEffect(() => {\n preloadCanvasChatPanel();",
"const hydrateCanvasProject"
);
expect(entryWarmup).toContain(
'scheduleCanvasIdleTask(() => {\n void import("@/components/canvas/CanvasFileListPanel");'
);
expect(entryWarmup).not.toContain(
'import("@/components/canvas/CanvasChatPanel")'
);
});
test("reuses in-flight editor and chat module downloads", () => {
const editorLoader = readSource(
"app/canvas/[id]/canvas-editor-loader.ts"
);
const chatLoader = readSource(
"components/canvas/canvas-chat-panel-loader.ts"
);
expect(editorLoader).toContain(
'canvasEditorModulePromise ??= import("./CanvasEditorLegacyPageClient")'
);
expect(chatLoader).toContain(
'canvasChatPanelModulePromise ??= import("./CanvasChatPanel")'
);
});
test("keeps heavy 3d renderers out of the normal canvas workspace chunk", () => {
const workspaceSource = readSource("components/canvas/CanvasWorkspace.tsx");
expect(workspaceSource).not.toContain(
'import { Model3DNode } from "./nodes/Model3DNode"'
);
expect(workspaceSource).not.toContain(
'import { World3DNode } from "./nodes/World3DNode"'
);
expect(workspaceSource).toContain(
'model3DNodeModulePromise ??= import("./nodes/Model3DNode")'
);
expect(workspaceSource).toContain(
'world3DNodeModulePromise ??= import("./nodes/World3DNode")'
);
expect(workspaceSource).toContain("window.setTimeout(() => {");
expect(workspaceSource).toContain('document.visibilityState !== "visible"');
expect(workspaceSource).toContain("timeout: 15000");
expect(workspaceSource).toContain("}, 12000)");
});
test("keeps image editing tools out of the home and chat first-open path", () => {
const homeSource = readSource("components/home/lovart-home.tsx");
const imagePreviewSource = readSource("components/image-preview.tsx");
const imageAnnotatorSource = readSource("components/image-annotator.tsx");
const workspaceSource = readSource("components/canvas/CanvasWorkspace.tsx");
expect(homeSource).not.toContain(
'requestIdleCallback(preloadImageAnnotator'
);
expect(homeSource).not.toContain(
'window.setTimeout(preloadImageAnnotator'
);
expect(homeSource).toContain(
'import("@/components/image-annotator").then((module) => module.ImageAnnotator)'
);
expect(imagePreviewSource).not.toContain(
'import { ImageAnnotator } from "./image-annotator"'
);
expect(imagePreviewSource).not.toContain(
'import { AngleEditDialog } from "./angle-edit-dialog"'
);
expect(imagePreviewSource).toContain(
'import("./image-annotator").then((module) => module.ImageAnnotator)'
);
expect(imagePreviewSource).toContain(
'import("./angle-edit-dialog").then((module) => module.AngleEditDialog)'
);
expect(imageAnnotatorSource).not.toContain(
'import { AngleEditDialog } from "./angle-edit-dialog"'
);
expect(imageAnnotatorSource).not.toContain(
'import { SceneFusionView } from "./scene-fusion-view"'
);
expect(imageAnnotatorSource).toContain(
'import("./angle-edit-dialog").then((module) => module.AngleEditDialog)'
);
expect(imageAnnotatorSource).toContain(
'import("./scene-fusion-view").then((module) => module.SceneFusionView)'
);
expect(workspaceSource).not.toContain(
'import { ImageNode } from "./nodes/ImageNode"'
);
expect(workspaceSource).toContain(
'imageNodeModulePromise ??= import("./nodes/ImageNode")'
);
expect(workspaceSource).toContain("preloadCanvasImageNodeRenderer");
});
test("loads markdown response rendering only when message content needs it", () => {
const chatPanelSource = readSource("components/canvas/CanvasChatPanel.tsx");
expect(chatPanelSource).not.toContain(
'import { Response } from "@/components/elements/response"'
);
expect(chatPanelSource).toContain(
'await import("@/components/elements/response")'
);
expect(chatPanelSource).toContain("function MarkdownResponseFallback");
expect(chatPanelSource).toContain("<MarkdownResponse {...props} />");
});
});