This repository has no description
1import { describe, expect, it, vi } from "vitest";
2import { createBobbinClient } from "$lib/api/client";
3import {
4 fetchPeoplePage,
5 fetchPinned,
6 fetchReposPage,
7 fetchStarredPage,
8 fetchVouchesPage
9} from "./pages";
10
11const enriched = () => Response.json({ output: { items: [], hits: [] }, data: {} });
12
13const requestBody = (fetchMock: ReturnType<typeof vi.fn>, index = 0) => {
14 const init = fetchMock.mock.calls[index][1] as RequestInit;
15 return JSON.parse(String(init.body)) as {
16 xrpc: string;
17 enrich: { source: string; type: string; targets?: string[] }[];
18 };
19};
20
21describe("fetchStarredPage", () => {
22 it("only requests minidocs for string URI subjects", async () => {
23 const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(
24 Response.json({
25 output: { items: [] },
26 data: {}
27 })
28 );
29 const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });
30
31 await fetchStarredPage(ctx, { did: "did:plc:alice" });
32
33 const body = requestBody(fetchMock);
34 expect(body.enrich[0].targets).toEqual(["items[].value.subject.uri"]);
35 });
36
37 it("targets repo stats and owner minidocs independently", async () => {
38 const fetchMock = vi
39 .fn<typeof globalThis.fetch>()
40 .mockResolvedValueOnce(
41 Response.json({
42 output: {
43 items: [
44 {
45 uri: "at://did:plc:alice/sh.tangled.feed.star/one",
46 value: { subject: { did: "did:plc:repo" }, createdAt: "2026-08-01T00:00:00Z" }
47 }
48 ]
49 },
50 data: {}
51 })
52 )
53 .mockResolvedValueOnce(Response.json({ output: { items: [] }, data: {} }));
54 const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });
55
56 await fetchStarredPage(ctx, { did: "did:plc:alice" });
57
58 const body = requestBody(fetchMock, 1);
59 expect(body.enrich).toEqual([
60 {
61 source: "sh.tangled.feed.star:subject",
62 type: "sh.tangled.query.enrichResponse#count",
63 targets: ["items[].value.repoDid"]
64 },
65 {
66 source: "sh.tangled.repo:.repo",
67 type: "com.bad-example.identity.miniDoc",
68 targets: ["items[].uri"]
69 }
70 ]);
71 });
72
73 it.each([
74 [undefined, "items[].value.repoDid"],
75 ["needle", "hits[].value.repoDid"]
76 ])("targets repo stats for list/search results", async (q, expectedTarget) => {
77 const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(enriched());
78 const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });
79
80 await fetchReposPage(ctx, {
81 did: "did:plc:alice",
82 handle: "alice.test",
83 viewerDid: "did:plc:viewer",
84 q
85 });
86
87 const body = requestBody(fetchMock);
88 expect(body.enrich).toHaveLength(2);
89 expect(body.enrich.every((descriptor) => descriptor.targets?.[0] === expectedTarget)).toBe(
90 true
91 );
92 });
93
94 it.each([
95 ["followers" as const, "items[].uri"],
96 ["following" as const, "items[].value.subject"]
97 ])("targets %s identities and stats", async (direction, expectedTarget) => {
98 const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(enriched());
99 const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });
100
101 await fetchPeoplePage(ctx, {
102 did: "did:plc:alice",
103 viewerDid: "did:plc:viewer",
104 direction
105 });
106
107 const body = requestBody(fetchMock);
108 expect(body.enrich).toHaveLength(4);
109 expect(body.enrich.every((descriptor) => descriptor.targets?.[0] === expectedTarget)).toBe(
110 true
111 );
112 });
113
114 it("does not retry missing enriched identities", async () => {
115 const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(
116 Response.json({
117 output: {
118 items: [
119 {
120 uri: "at://did:plc:bob/sh.tangled.graph.follow/one",
121 value: { subject: "did:plc:alice", createdAt: "2026-08-01T00:00:00Z" }
122 }
123 ]
124 },
125 data: {}
126 })
127 );
128 const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });
129
130 const page = await fetchPeoplePage(ctx, {
131 did: "did:plc:alice",
132 direction: "followers"
133 });
134
135 expect(fetchMock).toHaveBeenCalledOnce();
136 expect(page.items[0].handle).toBe("handle.invalid");
137 });
138
139 it("targets incoming vouch authors", async () => {
140 const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(enriched());
141 const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });
142
143 await fetchVouchesPage(ctx, {
144 did: "did:plc:alice",
145 cursors: { outgoing: null }
146 });
147
148 expect(requestBody(fetchMock).enrich[0].targets).toEqual(["items[].uri"]);
149 });
150
151 it("targets repo dids for both pinned repo queries", async () => {
152 const fetchMock = vi.fn<typeof globalThis.fetch>().mockImplementation(async () => enriched());
153 const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });
154
155 await fetchPinned(ctx, {
156 keys: ["did:plc:repo", "at://did:plc:alice/sh.tangled.repo/example"],
157 handle: "alice.test",
158 viewerDid: "did:plc:viewer"
159 });
160
161 const bodies = fetchMock.mock.calls.map((_, index) => requestBody(fetchMock, index));
162 expect(bodies.map((body) => body.xrpc).sort()).toEqual([
163 "sh.tangled.repo.getRepos",
164 "sh.tangled.repo.getReposByRepoDids"
165 ]);
166 expect(
167 bodies.every((body) =>
168 body.enrich.every((descriptor) => descriptor.targets?.[0] === "items[].value.repoDid")
169 )
170 ).toBe(true);
171 });
172
173 it("deduplicates repos returned by fetchReposPage by repoKey", async () => {
174 const fetchMock = vi.fn<typeof globalThis.fetch>().mockResolvedValue(
175 Response.json({
176 output: {
177 items: [
178 {
179 uri: "at://did:plc:alice/sh.tangled.repo/repo-one",
180 value: {
181 repoDid: "did:plc:repo-one",
182 name: "repo-one",
183 createdAt: "2026-08-01T00:00:00Z"
184 }
185 },
186 {
187 uri: "at://did:plc:alice/sh.tangled.repo/repo-one-dup",
188 value: {
189 repoDid: "did:plc:repo-one",
190 name: "repo-one-dup",
191 createdAt: "2026-08-02T00:00:00Z"
192 }
193 },
194 {
195 uri: "at://did:plc:alice/sh.tangled.repo/repo-two",
196 value: {
197 repoDid: "did:plc:repo-two",
198 name: "repo-two",
199 createdAt: "2026-08-03T00:00:00Z"
200 }
201 }
202 ]
203 },
204 data: {}
205 })
206 );
207 const ctx = createBobbinClient({ serviceUrl: "https://bobbin.test", fetch: fetchMock });
208
209 const result = await fetchReposPage(ctx, {
210 did: "did:plc:alice",
211 handle: "alice.test",
212 viewerDid: "did:plc:viewer"
213 });
214
215 expect(result.items).toHaveLength(2);
216 expect(result.items[0].repoDid).toBe("did:plc:repo-one");
217 expect(result.items[0].name).toBe("repo-one-dup");
218 expect(result.items[1].repoDid).toBe("did:plc:repo-two");
219 });
220});