0b2af34029
Batch 2-4 of the test-strategy rollout. Test-only except one minimal, behaviour-preserving extraction in file.utils.ts. All suites green: server 82 suites/836+1todo, editor-ext 86, mcp 270, client (new files) 86. integrations (server): - file.utils.ts: extract pure `isEntryPathSafe(entryName, targetDir)` from extractZipInternal so the zip-slip/path-traversal guard is unit-testable; call site rerouted, behaviour identical (only a warn-message string merged). - file.utils.zip-safety.spec.ts: traversal/strip/__MACOSX/prefix-confusion cases (mutation-resistant: fails if containment loses the path.sep). - import-formatter / import.utils / table-utils / export utils / import.service extractTitleAndRemoveHeading: pure import/export transforms, Notion/XWiki formatting, table colspan widths (idempotent), slug/link rewriting. client: - safeRedirectPath: open-redirect guard, every reject branch independently. - buildChatMarkdown (fence anti-breakout), label-colors, normalize-label, share tree build, page URL builders, notification time-grouping (fake clock). packages: - editor-ext: deriveFootnoteId golden table, parseHtmlEmbedHeight crafted values, orphan footnote extraction. - mcp: deriveFootnoteId parity (drift guard vs editor-ext), applyTextEdits idempotency + cross-block replaceAll, diffDocs/summarizeChange on reorder. Reviewed (APPROVE): extraction behaviour-preserving, assertions mutation-resistant. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
100 lines
3.3 KiB
TypeScript
100 lines
3.3 KiB
TypeScript
import { describe, it, expect } from "vitest";
|
|
import { buildPageUrl, buildSharedPageUrl } from "@/features/page/page.utils.ts";
|
|
|
|
/**
|
|
* URL builders. A page URL is `${titleSlug}-${slugId}` where the title is
|
|
* slugified (lowercase, dashed) after truncating to the first 70 chars, and an
|
|
* empty title becomes "untitled". `buildPageUrl` prefixes `/p/` when no space
|
|
* name is given and `/s/{space}/p/` otherwise. `buildSharedPageUrl` prefixes
|
|
* `/share/p/` when no shareId and `/share/{shareId}/p/` otherwise. An anchorId
|
|
* is appended as `#...`.
|
|
*/
|
|
describe("buildPageUrl", () => {
|
|
it("uses /p/{slug} when spaceName is undefined", () => {
|
|
expect(buildPageUrl(undefined as unknown as string, "abc123", "Hello World")).toBe(
|
|
"/p/hello-world-abc123",
|
|
);
|
|
});
|
|
|
|
it("uses /s/{space}/p/{slug} when spaceName is provided", () => {
|
|
expect(buildPageUrl("eng", "abc123", "Hello World")).toBe(
|
|
"/s/eng/p/hello-world-abc123",
|
|
);
|
|
});
|
|
|
|
it("slugifies (lowercases + dashes) the title", () => {
|
|
expect(buildPageUrl("eng", "id1", "My Cool PAGE!")).toBe(
|
|
"/s/eng/p/my-cool-page-id1",
|
|
);
|
|
});
|
|
|
|
it("uses 'untitled' for an empty title", () => {
|
|
expect(buildPageUrl("eng", "id1", "")).toBe("/s/eng/p/untitled-id1");
|
|
});
|
|
|
|
it("uses 'untitled' when no title is passed at all", () => {
|
|
expect(buildPageUrl("eng", "id1")).toBe("/s/eng/p/untitled-id1");
|
|
});
|
|
|
|
it("truncates the title to the first 70 chars before slugifying", () => {
|
|
// 80 'a' then a space then "tail". Only the first 70 chars feed slugify, so
|
|
// the slug is 70 a's (the space and "tail" past char 70 are dropped).
|
|
const longTitle = "a".repeat(80) + " tail";
|
|
const url = buildPageUrl("eng", "id1", longTitle);
|
|
expect(url).toBe(`/s/eng/p/${"a".repeat(70)}-id1`);
|
|
expect(url).not.toContain("tail");
|
|
});
|
|
|
|
it("appends the anchorId as a #fragment", () => {
|
|
expect(buildPageUrl("eng", "id1", "Page", "section-2")).toBe(
|
|
"/s/eng/p/page-id1#section-2",
|
|
);
|
|
});
|
|
|
|
it("omits the fragment when no anchorId is given", () => {
|
|
expect(buildPageUrl("eng", "id1", "Page")).not.toContain("#");
|
|
});
|
|
});
|
|
|
|
describe("buildSharedPageUrl", () => {
|
|
it("uses /share/p/{slug} when shareId is absent", () => {
|
|
expect(
|
|
buildSharedPageUrl({ shareId: "", pageSlugId: "id1", pageTitle: "Doc" }),
|
|
).toBe("/share/p/doc-id1");
|
|
});
|
|
|
|
it("uses /share/{shareId}/p/{slug} when shareId is present", () => {
|
|
expect(
|
|
buildSharedPageUrl({ shareId: "s9", pageSlugId: "id1", pageTitle: "Doc" }),
|
|
).toBe("/share/s9/p/doc-id1");
|
|
});
|
|
|
|
it("falls back to 'untitled' for an empty title", () => {
|
|
expect(
|
|
buildSharedPageUrl({ shareId: "s9", pageSlugId: "id1", pageTitle: "" }),
|
|
).toBe("/share/s9/p/untitled-id1");
|
|
});
|
|
|
|
it("appends the anchorId as a #fragment", () => {
|
|
expect(
|
|
buildSharedPageUrl({
|
|
shareId: "s9",
|
|
pageSlugId: "id1",
|
|
pageTitle: "Doc",
|
|
anchorId: "h1",
|
|
}),
|
|
).toBe("/share/s9/p/doc-id1#h1");
|
|
});
|
|
|
|
it("truncates the title to the first 70 chars before slugifying", () => {
|
|
const longTitle = "b".repeat(80) + " tail";
|
|
const url = buildSharedPageUrl({
|
|
shareId: "s9",
|
|
pageSlugId: "id1",
|
|
pageTitle: longTitle,
|
|
});
|
|
expect(url).toBe(`/share/s9/p/${"b".repeat(70)}-id1`);
|
|
expect(url).not.toContain("tail");
|
|
});
|
|
});
|