Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
113 changes: 113 additions & 0 deletions frontend/apps/web/app/comment-permalink-route.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
import {beforeEach, describe, expect, it, vi} from 'vitest'

const mocks = vi.hoisted(() => ({
getConfig: vi.fn(),
loadSiteResource: vi.fn(),
}))

vi.mock('@/cache-policy', () => ({
useFullRender: () => true,
}))

vi.mock('@/client-lazy', () => ({
WebCommenting: () => null,
}))

vi.mock('@/instrumentation.server', () => ({
createInstrumentationContext: () => ({enabled: false}),
instrument: (_ctx: unknown, _name: string, fn: () => unknown) => fn(),
printInstrumentationSummary: vi.fn(),
setRequestInstrumentationContext: vi.fn(),
}))

vi.mock('@/hypermedia-metadata', () => ({
createResourceMetadata: vi.fn(),
metadataToPageMeta: () => [],
}))

vi.mock('@/loaders', () => ({
loadSiteResource: mocks.loadSiteResource,
}))

vi.mock('@/meta', () => ({
defaultPageMeta: () => () => [],
}))

vi.mock('@/not-registered', () => ({
NoSitePage: () => null,
NotRegisteredPage: () => null,
}))

vi.mock('@/providers', () => ({
WebSiteProvider: ({children}: {children: unknown}) => children,
}))

vi.mock('@/site-config.server', () => ({
getConfig: mocks.getConfig,
}))

vi.mock('@/wrapping', () => ({
unwrap: <T>(value: T) => value,
}))

vi.mock('@/web-feed-page', () => ({
WebFeedPage: () => null,
}))

vi.mock('@/web-resource-page', () => ({
WebInspectorPage: () => null,
WebResourcePage: () => null,
}))

vi.mock('@/wrapping.server', () => ({
wrapJSON: (data: unknown, init?: unknown) => ({data, init}),
}))

vi.mock('@shm/shared/utils/navigation', () => ({
useNavigationState: () => null,
}))

vi.mock('@shm/ui/inspect-ipfs-page', () => ({
InspectIpfsPage: () => null,
}))

vi.mock('@shm/shared/translation', () => ({
useTx: () => (key: string, fallback?: string) => fallback || key,
}))

vi.mock('@shm/ui/text', () => ({
SizableText: ({children}: {children: unknown}) => children,
}))

import {loader} from './routes/$'

describe('comment permalink route loading', () => {
beforeEach(() => {
mocks.getConfig.mockReset()
mocks.loadSiteResource.mockReset()
mocks.getConfig.mockResolvedValue({registeredAccountUid: 'site-account'})
mocks.loadSiteResource.mockResolvedValue({ok: true})
})

it('treats ?v on a comment permalink as the comment version, not the document version', async () => {
await loader({
params: {'*': 'hm/doc-account/docs/:comments/comment-id'},
request: new Request('https://seed.example/hm/doc-account/docs/:comments/comment-id?v=comment-version-cid'),
})

expect(mocks.loadSiteResource).toHaveBeenCalledTimes(1)
const [, documentId, extraData] = mocks.loadSiteResource.mock.calls[0]

expect(documentId).toMatchObject({
uid: 'doc-account',
path: ['docs'],
version: null,
latest: true,
})
expect(extraData).toMatchObject({
viewTerm: 'comments',
openComment: 'comment-id',
commentVersion: 'comment-version-cid',
})
})
})
5 changes: 4 additions & 1 deletion frontend/apps/web/app/loaders.ts
Original file line number Diff line number Diff line change
Expand Up @@ -586,9 +586,12 @@ export async function loadSiteResource<T extends Record<string, unknown> = Recor
let comment = resourceContent.comment
let commentAuthorTitle: string | undefined
const openCommentId = (extraData as any)?.openComment as string | undefined
const commentVersion = (extraData as any)?.commentVersion as string | undefined
if (!comment && openCommentId) {
try {
comment = (await getComment(openCommentId)) ?? undefined
// Comment permalink routes use ?v for the comment version CID. The
// comments API accepts either the stable comment id or a version CID.
comment = (await getComment(commentVersion || openCommentId)) ?? undefined
if (comment?.author) {
try {
const authorResource = await resolveResource(hmId(comment.author))
Expand Down
16 changes: 12 additions & 4 deletions frontend/apps/web/app/routes/$.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ type ExtendedSitePayload = SiteDocumentPayload & {
viewTerm?: ViewRouteKey | null
panelParam?: string | null // Supports extended format like "comments/BLOCKID" or "comments/COMMENT_ID"
openComment?: string | null
commentVersion?: string | null
accountUid?: string | null
inspectTab?: InspectTab | null
}
Expand Down Expand Up @@ -294,6 +295,10 @@ export const loader = async ({params, request}: {params: Params; request: Reques
let documentId
let isInspect = false
let viewTerm: ViewRouteKey | null = null
// On comment permalink routes, ?v refers to the comment version CID, not
// the target document version. Keep it off the document lookup or the
// target document resolves as not found.
let isCommentPermalink = false
// Merge activity filter slug from path into panelParam for createDocumentNavRoute
let effectivePanelParam = panelParam
let openComment: string | null = null
Expand All @@ -313,12 +318,13 @@ export const loader = async ({params, request}: {params: Params; request: Reques
}
if (extracted.commentId) {
openComment = extracted.commentId
isCommentPermalink = true
}
accountUid = extracted.accountUid || null
documentId = hmId(docUid, {
path: extracted.path,
version,
latest,
version: isCommentPermalink ? null : version,
latest: isCommentPermalink ? true : latest,
})
} else {
// Site document (regular path) or inspector document (/inspect/path...)
Expand All @@ -332,12 +338,13 @@ export const loader = async ({params, request}: {params: Params; request: Reques
}
if (extracted.commentId) {
openComment = extracted.commentId
isCommentPermalink = true
}
accountUid = extracted.accountUid || null
documentId = hmId(registeredAccountUid, {
path: extracted.path,
version,
latest,
version: isCommentPermalink ? null : version,
latest: isCommentPermalink ? true : latest,
})
}

Expand All @@ -347,6 +354,7 @@ export const loader = async ({params, request}: {params: Params; request: Reques
viewTerm,
panelParam: effectivePanelParam,
openComment,
commentVersion: isCommentPermalink ? version : null,
accountUid,
isInspect,
inspectTab: isInspect && inspectTab ? (inspectTab as ExtendedSitePayload['inspectTab']) : null,
Expand Down