Files
gitmost/apps/client/src/features/page/page.utils.test.ts
T
claude_code 0b2af34029 test(integrations/client/packages): batch 2-4 unit coverage + zip-slip guard extraction
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>
2026-06-21 18:22:15 +03:00

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");
});
});