- switch main document flow toward leptos tiptap island host and generated runtime assets - align page aggregate loading, page head single-source updates, and AI tool result recovery - add tests and smoke scripts for title sync, AI route recovery, and editor host cutover
157 lines
5.0 KiB
TypeScript
157 lines
5.0 KiB
TypeScript
import { act } from "react";
|
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
import { createRoot, type Root } from "react-dom/client";
|
|
import { PreferredSidebarSnapshotProvider } from "@/components/sidebar/preferred-sidebar-snapshot-context";
|
|
import type { SidebarInitialData } from "@/components/sidebar/types";
|
|
import { buildSidebarInitialData } from "@/lib/sidebar-data";
|
|
import type { DocumentRecord } from "@/lib/documents";
|
|
import { usePageHeadTitle } from "./use-page-head-title";
|
|
|
|
function buildDocument(overrides: Partial<DocumentRecord> = {}): DocumentRecord {
|
|
return {
|
|
access_scope: "private",
|
|
id: "doc-1",
|
|
workspace_id: "ws-1",
|
|
title: "标题 A",
|
|
parent_id: null,
|
|
sort_order: 0,
|
|
is_starred: false,
|
|
is_template: false,
|
|
created_at: "2026-04-21T00:00:00.000Z",
|
|
updated_at: "2026-04-21T00:00:00.000Z",
|
|
...overrides,
|
|
};
|
|
}
|
|
|
|
function buildSidebarData(documents: DocumentRecord[]): SidebarInitialData {
|
|
return buildSidebarInitialData({
|
|
activeWorkspaceId: "ws-1",
|
|
workspaces: [],
|
|
documents,
|
|
trashedDocuments: [],
|
|
mindmaps: [],
|
|
mediaAssets: [],
|
|
trashedMediaAssets: [],
|
|
tables: [],
|
|
});
|
|
}
|
|
|
|
function HookProbe(props: { documentId: string; fallbackTitle: string }) {
|
|
const state = usePageHeadTitle(props);
|
|
return (
|
|
<>
|
|
<div
|
|
data-testid="page-head-title"
|
|
data-display-title={state.displayTitle}
|
|
data-committed-title={state.committedTitle}
|
|
data-has-draft={state.hasDraft ? "1" : "0"}
|
|
/>
|
|
<button type="button" data-testid="set-draft" onClick={() => state.setDraftTitle(" 新标题 ")}>
|
|
设草稿
|
|
</button>
|
|
<button type="button" data-testid="commit-persisted" onClick={() => state.commitPersistedTitle("新标题")}>
|
|
提交持久化
|
|
</button>
|
|
</>
|
|
);
|
|
}
|
|
|
|
describe("usePageHeadTitle", () => {
|
|
let container: HTMLDivElement;
|
|
let root: Root;
|
|
|
|
beforeEach(() => {
|
|
(globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }).IS_REACT_ACT_ENVIRONMENT =
|
|
true;
|
|
container = document.createElement("div");
|
|
document.body.appendChild(container);
|
|
root = createRoot(container);
|
|
});
|
|
|
|
afterEach(() => {
|
|
act(() => {
|
|
root.unmount();
|
|
});
|
|
container.remove();
|
|
});
|
|
|
|
it("应优先使用 live sidebar snapshot 中的标题作为 committed title", async () => {
|
|
const snapshot = buildSidebarData([
|
|
buildDocument({
|
|
id: "doc-1",
|
|
title: "树标题",
|
|
}),
|
|
]);
|
|
|
|
await act(async () => {
|
|
root.render(
|
|
<PreferredSidebarSnapshotProvider data={snapshot}>
|
|
<HookProbe documentId="doc-1" fallbackTitle="SSR 标题" />
|
|
</PreferredSidebarSnapshotProvider>,
|
|
);
|
|
});
|
|
|
|
const probe = container.querySelector("[data-testid='page-head-title']");
|
|
expect(probe?.getAttribute("data-committed-title")).toBe("树标题");
|
|
expect(probe?.getAttribute("data-display-title")).toBe("树标题");
|
|
expect(probe?.getAttribute("data-has-draft")).toBe("0");
|
|
});
|
|
|
|
it("应只把本地输入保留为短暂 draft,并在 live 标题追平后自动清空", async () => {
|
|
const initialSnapshot = buildSidebarData([
|
|
buildDocument({
|
|
id: "doc-1",
|
|
title: "旧标题",
|
|
}),
|
|
]);
|
|
|
|
await act(async () => {
|
|
root.render(
|
|
<PreferredSidebarSnapshotProvider data={initialSnapshot}>
|
|
<HookProbe documentId="doc-1" fallbackTitle="SSR 标题" />
|
|
</PreferredSidebarSnapshotProvider>,
|
|
);
|
|
});
|
|
|
|
const setDraftButton = container.querySelector<HTMLButtonElement>("[data-testid='set-draft']");
|
|
act(() => {
|
|
setDraftButton?.click();
|
|
});
|
|
|
|
let probe = container.querySelector("[data-testid='page-head-title']");
|
|
expect(probe?.getAttribute("data-display-title")).toBe(" 新标题 ");
|
|
expect(probe?.getAttribute("data-committed-title")).toBe("旧标题");
|
|
expect(probe?.getAttribute("data-has-draft")).toBe("1");
|
|
|
|
const commitPersistedButton = container.querySelector<HTMLButtonElement>("[data-testid='commit-persisted']");
|
|
act(() => {
|
|
commitPersistedButton?.click();
|
|
});
|
|
|
|
probe = container.querySelector("[data-testid='page-head-title']");
|
|
expect(probe?.getAttribute("data-display-title")).toBe("新标题");
|
|
expect(probe?.getAttribute("data-has-draft")).toBe("1");
|
|
|
|
const syncedSnapshot = buildSidebarData([
|
|
buildDocument({
|
|
id: "doc-1",
|
|
title: "新标题",
|
|
updated_at: "2026-04-21T00:00:02.000Z",
|
|
}),
|
|
]);
|
|
|
|
await act(async () => {
|
|
root.render(
|
|
<PreferredSidebarSnapshotProvider data={syncedSnapshot}>
|
|
<HookProbe documentId="doc-1" fallbackTitle="SSR 标题" />
|
|
</PreferredSidebarSnapshotProvider>,
|
|
);
|
|
});
|
|
|
|
probe = container.querySelector("[data-testid='page-head-title']");
|
|
expect(probe?.getAttribute("data-display-title")).toBe("新标题");
|
|
expect(probe?.getAttribute("data-committed-title")).toBe("新标题");
|
|
expect(probe?.getAttribute("data-has-draft")).toBe("0");
|
|
});
|
|
});
|