diff --git a/web/src/lib/components/profile/pages.ts b/web/src/lib/components/profile/pages.ts index 92c50d34e..3f5f0f44c 100644 --- a/web/src/lib/components/profile/pages.ts +++ b/web/src/lib/components/profile/pages.ts @@ -462,10 +462,10 @@ export interface PinnedOptions { // pinned keys are repoDids or record uris, fetch them directly instead of // paging the owner's whole repo list. bobbin drops records it can no longer // hydrate -export const fetchPinned = async ( +export const fetchPinnedByKey = async ( ctx: BobbinContext, { keys, did, handle, viewerDid }: PinnedOptions -): Promise => { +): Promise> => { const ownerHandleOf = (item: ListItem, data: Sidecar): string => { const owner = repoOwnerOf(data, item as RecordView); const resolved = owner.did === did ? handle : owner.handle; @@ -505,7 +505,12 @@ export const fetchPinned = async ( for (const item of byUri.output.items) { cards.set(item.uri, resolveRepoCard(item, ownerHandleOf(item, byUri.data), byUri.data)); } - return keys + return cards; +}; + +export const fetchPinned = async (ctx: BobbinContext, options: PinnedOptions) => { + const cards = await fetchPinnedByKey(ctx, options); + return options.keys .map((key) => cards.get(key)) .filter((card): card is RepoCardData => card !== undefined); }; diff --git a/web/src/lib/components/profile/pagesPinned.test.ts b/web/src/lib/components/profile/pagesPinned.test.ts new file mode 100644 index 000000000..79cded7ee --- /dev/null +++ b/web/src/lib/components/profile/pagesPinned.test.ts @@ -0,0 +1,46 @@ +import { describe, expect, it, vi } from "vitest"; +import { createBobbinClient } from "#lib/api/client.ts"; +import { fetchPinned, fetchPinnedByKey } from "#lib/components/profile/pages.ts"; + +const manifest = (uri: string, name: string) => ({ + uri, + value: { + declaration: { owner: "did:plc:alice", name }, + createdAt: "2026-08-01T00:00:00Z" + } +}); + +const didKey = "did:plc:repo"; +const legacyA = "at://did:plc:alice/sh.tangled.repo/legacy-a"; +const legacyB = "at://did:plc:alice/sh.tangled.repo/legacy-b"; + +const ctx = () => { + const fetch = vi.fn(async (_input, init) => { + const { xrpc } = JSON.parse(String(init?.body)) as { xrpc: string }; + const items = + xrpc === "org.tangled.repo.getReposByRepoDids" + ? [manifest(`at://${didKey}/org.tangled.repo.manifest/self`, "modern")] + : [manifest(legacyA, "legacy-a"), manifest(legacyB, "legacy-b")]; + return Response.json({ output: { items }, data: {} }); + }); + return createBobbinClient({ serviceUrl: "https://bobbin.test", fetch }); +}; + +const options = { keys: [legacyB, didKey, legacyA], did: "did:plc:alice", handle: "alice.test" }; + +describe("fetchPinnedByKey", () => { + it("files a did pin and legacy uri pins under the key they were asked for", async () => { + const cards = await fetchPinnedByKey(ctx(), options); + expect([...cards.keys()].sort()).toEqual([didKey, legacyA, legacyB].sort()); + expect(cards.get(didKey)?.name).toBe("modern"); + expect(cards.get(legacyA)?.name).toBe("legacy-a"); + expect(cards.get(legacyB)?.name).toBe("legacy-b"); + }); +}); + +describe("fetchPinned", () => { + it("keeps the order of the keys, legacy pins included", async () => { + const cards = await fetchPinned(ctx(), options); + expect(cards.map((card) => card.name)).toEqual(["legacy-b", "modern", "legacy-a"]); + }); +});