From b05b5b1e5b8d78ef87fe9b3f6e7367db701ba331 Mon Sep 17 00:00:00 2001 From: Ryoya Tamura Date: Wed, 25 Feb 2026 09:47:10 +0900 Subject: [PATCH] feat: support document edit URL in documentUnfurler Extend documentUnfurler to match /document/{projectKey}/e/{documentId} edit URLs, prefixing the title with "Edit" to indicate editing state. Also add documentUnfurler tests and refactor test helpers. Closes #1 Co-Authored-By: Claude Opus 4.6 --- src/entrypoints/content/unfurlers.test.ts | 76 ++++++++++++++++++----- src/entrypoints/content/unfurlers.ts | 19 +++--- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/src/entrypoints/content/unfurlers.test.ts b/src/entrypoints/content/unfurlers.test.ts index 7015d42..0e5d08a 100644 --- a/src/entrypoints/content/unfurlers.test.ts +++ b/src/entrypoints/content/unfurlers.test.ts @@ -1,29 +1,79 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; -const { mockReplaceUrlInTextNodes } = vi.hoisted(() => ({ +const { mockReplaceUrlInTextNodes, mockClient } = vi.hoisted(() => ({ mockReplaceUrlInTextNodes: vi.fn(), + mockClient: vi + .fn() + .mockResolvedValue({ name: "TestSpace", spaceKey: "test" }), })); vi.mock("./fetch", () => ({ - client: vi.fn().mockResolvedValue({ name: "TestSpace", spaceKey: "test" }), + client: mockClient, })); vi.mock("./replaceUrlInTextNodes", () => ({ replaceUrlInTextNodes: mockReplaceUrlInTextNodes, })); -import { gitCommitUnfurler, gitFileUnfurler } from "./unfurlers"; +import { + documentUnfurler, + gitCommitUnfurler, + gitFileUnfurler, +} from "./unfurlers"; -describe("gitFileUnfurler", () => { +const createAnchor = (href: string) => { + const anchor = document.createElement("a"); + anchor.href = href; + return anchor; +}; + +describe("documentUnfurler", () => { beforeEach(() => { mockReplaceUrlInTextNodes.mockClear(); + mockClient.mockReset(); + mockClient.mockImplementation((_hostname: string, path: string) => { + if (path === "/api/v2/space") { + return Promise.resolve({ name: "TestSpace", spaceKey: "test" }); + } + if (path.startsWith("/api/v2/documents/")) { + return Promise.resolve({ + projectId: 1, + title: "Design Document", + }); + } + return Promise.resolve({}); + }); + }); + + it("should build title for view URL", async () => { + const anchor = createAnchor( + "https://nulab.backlog.jp/document/PROJ/abcdef01234567890abcdef012345678", + ); + await documentUnfurler(anchor); + expect(mockReplaceUrlInTextNodes).toHaveBeenCalledWith( + anchor, + "[TestSpace][PROJ] Design Document | Document", + ); + }); + + it("should build title with Edit prefix for edit URL", async () => { + const anchor = createAnchor( + "https://nulab.backlog.jp/document/PROJ/e/abcdef01234567890abcdef012345678", + ); + await documentUnfurler(anchor); + expect(mockReplaceUrlInTextNodes).toHaveBeenCalledWith( + anchor, + "[TestSpace][PROJ] Edit Design Document | Document", + ); }); +}); - const createAnchor = (href: string) => { - const anchor = document.createElement("a"); - anchor.href = href; - return anchor; - }; +describe("gitFileUnfurler", () => { + beforeEach(() => { + mockReplaceUrlInTextNodes.mockClear(); + mockClient.mockReset(); + mockClient.mockResolvedValue({ name: "TestSpace", spaceKey: "test" }); + }); it("should build title for blob URL with filename", async () => { const anchor = createAnchor( @@ -76,14 +126,10 @@ describe("gitFileUnfurler", () => { describe("gitCommitUnfurler", () => { beforeEach(() => { mockReplaceUrlInTextNodes.mockClear(); + mockClient.mockReset(); + mockClient.mockResolvedValue({ name: "TestSpace", spaceKey: "test" }); }); - const createAnchor = (href: string) => { - const anchor = document.createElement("a"); - anchor.href = href; - return anchor; - }; - it("should build title with truncated commit hash", async () => { const anchor = createAnchor( "https://nulab.backlog.jp/git/NAKAMURA/hackz-nulab-26/commit/d883cf51748ad8d4d864d1143657a10a27f73e17", diff --git a/src/entrypoints/content/unfurlers.ts b/src/entrypoints/content/unfurlers.ts index ae99110..34530ce 100644 --- a/src/entrypoints/content/unfurlers.ts +++ b/src/entrypoints/content/unfurlers.ts @@ -90,17 +90,16 @@ export const wikiWithTitleUnfurler = defineUnfurler({ export const documentUnfurler = defineUnfurler({ parseUrl: (url) => - regex(`^/document/${PROJECT_KEY_REGEX}/${DOCUMENT_ID_REGEX}$`).exec( - url.pathname, - )?.groups, + regex( + `^/document/${PROJECT_KEY_REGEX}/(?e/)?${DOCUMENT_ID_REGEX}$`, + ).exec(url.pathname)?.groups, buildTitle: async (params, url) => { - const [document] = await Promise.all([ - client( - url.hostname, - joinURL("/api/v2/documents", params.documentId), - ), - ]); - return `[${params.projectKey}] ${document.title} | Document`; + const document = await client( + url.hostname, + joinURL("/api/v2/documents", params.documentId), + ); + const prefix = params.edit ? "Edit " : ""; + return `[${params.projectKey}] ${prefix}${document.title} | Document`; }, });