BlueSky & more on desktop lazurite.stormlightlabs.org/
tauri rust typescript bluesky appview atproto solid
2
fork

Configure Feed

Select the types of activity you want to include in your feed.

at main 693 lines 27 kB view raw
1import { beforeEach, describe, expect, it } from "vitest"; 2import { 3 applyFeedPreferences, 4 buildPublicPostUrl, 5 buildThreadOverlayRoute, 6 decodeThreadRouteUri, 7 getFeedCommand, 8 getQuotedPresentation, 9 getThreadOverlayUri, 10 getUnknownEmbedTelemetryForTests, 11 type NormalizedEmbed, 12 normalizeEmbed, 13 parseFeedResponse, 14 parseThreadResponse, 15 resetUnknownEmbedTelemetryForTests, 16} from "../feeds"; 17import type { FeedViewPost, FeedViewPrefItem, SavedFeedItem } from "../types"; 18 19function createFeedItem(overrides: Partial<FeedViewPost> = {}): FeedViewPost { 20 return { 21 post: { 22 author: { did: "did:plc:alice", handle: "alice.test" }, 23 cid: "cid-1", 24 indexedAt: "2026-03-28T12:00:00.000Z", 25 likeCount: 10, 26 record: { createdAt: "2026-03-28T12:00:00.000Z", text: "hello world" }, 27 uri: "at://did:plc:alice/app.bsky.feed.post/1", 28 }, 29 ...overrides, 30 }; 31} 32 33function createPref(overrides: Partial<FeedViewPrefItem> = {}): FeedViewPrefItem { 34 return { 35 feed: "following", 36 hideQuotePosts: false, 37 hideReplies: false, 38 hideRepliesByLikeCount: null, 39 hideRepliesByUnfollowed: false, 40 hideReposts: false, 41 ...overrides, 42 }; 43} 44 45function walkNormalizedEmbeds(root: NormalizedEmbed, visit: (embed: NormalizedEmbed) => void) { 46 visit(root); 47 if (root.kind === "record") { 48 for (const nested of root.quoted.normalizedEmbeds) { 49 walkNormalizedEmbeds(nested, visit); 50 } 51 } 52 if (root.kind === "recordWithMedia") { 53 if (root.media) { 54 walkNormalizedEmbeds(root.media, visit); 55 } 56 for (const nested of root.quoted.normalizedEmbeds) { 57 walkNormalizedEmbeds(nested, visit); 58 } 59 } 60} 61 62describe("feed helpers", () => { 63 beforeEach(() => { 64 resetUnknownEmbedTelemetryForTests(); 65 }); 66 67 it("filters reposts, replies, quote posts, and low-like replies", () => { 68 const base = createFeedItem(); 69 const repost = createFeedItem({ 70 post: { ...base.post, uri: "at://did:plc:alice/app.bsky.feed.post/2" }, 71 reason: { 72 $type: "app.bsky.feed.defs#reasonRepost", 73 by: { did: "did:plc:bob", handle: "bob.test" }, 74 indexedAt: "2026-03-28T12:10:00.000Z", 75 }, 76 }); 77 const reply = createFeedItem({ 78 post: { ...base.post, likeCount: 2, uri: "at://did:plc:alice/app.bsky.feed.post/3" }, 79 reply: { 80 parent: { $type: "app.bsky.feed.defs#postView", ...base.post }, 81 root: { $type: "app.bsky.feed.defs#postView", ...base.post }, 82 }, 83 }); 84 const quote = createFeedItem({ 85 post: { 86 ...base.post, 87 embed: { $type: "app.bsky.embed.record#view", record: { uri: "at://did:plc:bob/app.bsky.feed.post/9" } }, 88 uri: "at://did:plc:alice/app.bsky.feed.post/4", 89 }, 90 }); 91 92 const filtered = applyFeedPreferences( 93 [base, repost, reply, quote], 94 createPref({ hideQuotePosts: true, hideReplies: true, hideReposts: true }), 95 ); 96 97 expect(filtered).toEqual([base]); 98 expect(applyFeedPreferences([reply], createPref({ hideRepliesByLikeCount: 5 }))).toEqual([]); 99 }); 100 101 it("treats zero as an active reply-like threshold", () => { 102 const reply = createFeedItem({ 103 post: { ...createFeedItem().post, likeCount: 0, uri: "at://did:plc:alice/app.bsky.feed.post/zero" }, 104 reply: { 105 parent: { $type: "app.bsky.feed.defs#postView", ...createFeedItem().post }, 106 root: { $type: "app.bsky.feed.defs#postView", ...createFeedItem().post }, 107 }, 108 }); 109 110 expect(applyFeedPreferences([reply], createPref({ hideRepliesByLikeCount: 0 }))).toEqual([reply]); 111 expect(applyFeedPreferences([reply], createPref({ hideRepliesByLikeCount: 1 }))).toEqual([]); 112 }); 113 114 it("detects replies from the embedded record and respects the unfollowed reply filter", () => { 115 const base = createFeedItem(); 116 const unfollowedReply = createFeedItem({ 117 post: { 118 ...base.post, 119 author: { did: "did:plc:bob", handle: "bob.test", viewer: { following: null } }, 120 record: { 121 createdAt: "2026-03-28T12:00:00.000Z", 122 reply: { parent: { uri: "at://did:plc:alice/app.bsky.feed.post/1" } }, 123 text: "reply from unfollowed author", 124 }, 125 uri: "at://did:plc:bob/app.bsky.feed.post/2", 126 }, 127 }); 128 129 expect(applyFeedPreferences([unfollowedReply], createPref({ hideReplies: true }))).toEqual([]); 130 expect(applyFeedPreferences([unfollowedReply], createPref({ hideRepliesByUnfollowed: true }))).toEqual([]); 131 expect(applyFeedPreferences([unfollowedReply], createPref({ hideRepliesByUnfollowed: false }))).toEqual([ 132 unfollowedReply, 133 ]); 134 }); 135 136 it("builds feed commands per saved feed type", () => { 137 const timeline: SavedFeedItem = { id: "following", pinned: true, type: "timeline", value: "following" }; 138 const feed: SavedFeedItem = { 139 id: "custom", 140 pinned: true, 141 type: "feed", 142 value: "at://did:plc:alice/app.bsky.feed.generator/custom", 143 }; 144 const list: SavedFeedItem = { 145 id: "list", 146 pinned: false, 147 type: "list", 148 value: "at://did:plc:alice/app.bsky.graph.list/list", 149 }; 150 151 expect(getFeedCommand(timeline)).toEqual({ args: expect.any(Function), name: "get_timeline" }); 152 expect(getFeedCommand(feed).name).toBe("get_feed"); 153 expect(getFeedCommand(list).name).toBe("get_list_feed"); 154 expect(getFeedCommand(list).args("cursor-1", 30)).toEqual({ cursor: "cursor-1", limit: 30, uri: list.value }); 155 }); 156 157 it("encodes and decodes thread overlays", () => { 158 const uri = "at://did:plc:alice/app.bsky.feed.post/abc123"; 159 160 expect(buildThreadOverlayRoute("/profile/alice", "", uri)).toBe( 161 "/profile/alice?thread=at%3A%2F%2Fdid%3Aplc%3Aalice%2Fapp.bsky.feed.post%2Fabc123", 162 ); 163 expect(buildThreadOverlayRoute("/profile/alice", "?foo=bar", uri)).toBe( 164 "/profile/alice?foo=bar&thread=at%3A%2F%2Fdid%3Aplc%3Aalice%2Fapp.bsky.feed.post%2Fabc123", 165 ); 166 expect(buildThreadOverlayRoute("/profile/alice", "?foo=bar&thread=old", null)).toBe("/profile/alice?foo=bar"); 167 expect(getThreadOverlayUri("?thread=at%3A%2F%2Fdid%3Aplc%3Aalice%2Fapp.bsky.feed.post%2Fabc123")).toBe(uri); 168 expect(decodeThreadRouteUri("at%3A%2F%2Fdid%3Aplc%3Aalice%2Fapp.bsky.feed.post%2Fabc123")).toBe(uri); 169 expect(decodeThreadRouteUri(uri)).toBe(uri); 170 expect(decodeThreadRouteUri("https%3A%2F%2Fexample.com")).toBeNull(); 171 }); 172 173 it("builds public post urls from handles and post rkeys", () => { 174 expect(buildPublicPostUrl(createFeedItem().post)).toBe("https://bsky.app/profile/alice.test/post/1"); 175 }); 176 177 it("builds feed/list quoted-record presentations without thread URIs", () => { 178 const feedPresentation = getQuotedPresentation({ 179 $type: "app.bsky.embed.record#view", 180 record: { 181 $type: "app.bsky.feed.defs#generatorView", 182 creator: { did: "did:plc:alice", handle: "alice.test" }, 183 displayName: "For You", 184 uri: "at://did:plc:alice/app.bsky.feed.generator/for-you", 185 }, 186 }); 187 const listPresentation = getQuotedPresentation({ 188 $type: "app.bsky.embed.record#view", 189 record: { 190 $type: "app.bsky.graph.defs#listView", 191 creator: { did: "did:plc:alice", handle: "alice.test" }, 192 name: "Reading List", 193 uri: "at://did:plc:alice/app.bsky.graph.list/reading-list", 194 }, 195 }); 196 197 expect(feedPresentation).toMatchObject({ 198 href: "https://bsky.app/profile/alice.test/feed/for-you", 199 kind: "feed", 200 title: "Embedded feed", 201 uri: null, 202 }); 203 expect(listPresentation).toMatchObject({ 204 href: "https://bsky.app/profile/alice.test/lists/reading-list", 205 kind: "list", 206 title: "Embedded list", 207 uri: null, 208 }); 209 }); 210 211 it("keeps post quoted-record presentations thread-openable", () => { 212 const postPresentation = getQuotedPresentation({ 213 $type: "app.bsky.embed.record#view", 214 record: { 215 $type: "app.bsky.embed.record#viewRecord", 216 author: { did: "did:plc:bob", handle: "bob.test" }, 217 uri: "at://did:plc:bob/app.bsky.feed.post/123", 218 value: { text: "quoted body" }, 219 }, 220 }); 221 222 expect(postPresentation).toMatchObject({ 223 href: "https://bsky.app/profile/bob.test/post/123", 224 kind: "post", 225 text: "quoted body", 226 title: "Quoted post", 227 uri: "at://did:plc:bob/app.bsky.feed.post/123", 228 }); 229 }); 230 231 it("extracts text, facets, and embed media from quoted postView records", () => { 232 const presentation = getQuotedPresentation({ 233 $type: "app.bsky.embed.record#view", 234 record: { 235 $type: "app.bsky.feed.defs#postView", 236 author: { did: "did:plc:bob", handle: "bob.test" }, 237 record: { 238 text: "quoted postView body", 239 facets: [{ 240 features: [{ $type: "app.bsky.richtext.facet#link", uri: "https://example.com" }], 241 index: { byteEnd: 20, byteStart: 0 }, 242 }], 243 embed: { 244 $type: "app.bsky.embed.images#view", 245 images: [{ fullsize: "https://cdn.example.com/postview-image.png" }], 246 }, 247 }, 248 uri: "at://did:plc:bob/app.bsky.feed.post/postview", 249 }, 250 }); 251 252 expect(presentation).toMatchObject({ 253 href: "https://bsky.app/profile/bob.test/post/postview", 254 kind: "post", 255 text: "quoted postView body", 256 uri: "at://did:plc:bob/app.bsky.feed.post/postview", 257 }); 258 expect(presentation.facets).toHaveLength(1); 259 expect(presentation.normalizedEmbeds.map((embed) => embed.kind)).toEqual(["images"]); 260 }); 261 262 it("hydrates quoted record image blobs into renderable CDN URLs", () => { 263 const presentation = getQuotedPresentation({ 264 $type: "app.bsky.embed.record#view", 265 record: { 266 $type: "app.bsky.feed.defs#postView", 267 author: { did: "did:plc:bob", handle: "bob.test" }, 268 record: { 269 embed: { 270 $type: "app.bsky.embed.images", 271 images: [{ alt: "Blob image", image: { mimeType: "image/jpeg", ref: { $link: "bafyblobimg" } } }], 272 }, 273 text: "", 274 }, 275 uri: "at://did:plc:bob/app.bsky.feed.post/blob-post", 276 }, 277 }); 278 279 expect(presentation.normalizedEmbeds).toHaveLength(1); 280 expect(presentation.normalizedEmbeds[0]?.kind).toBe("images"); 281 if (presentation.normalizedEmbeds[0]?.kind === "images") { 282 expect(presentation.normalizedEmbeds[0].embed.images[0]).toMatchObject({ 283 fullsize: "https://cdn.bsky.app/img/feed_fullsize/plain/did%3Aplc%3Abob/bafyblobimg@jpeg", 284 thumb: "https://cdn.bsky.app/img/feed_thumbnail/plain/did%3Aplc%3Abob/bafyblobimg@jpeg", 285 }); 286 } 287 }); 288 289 it("extracts quoted post embeds and keeps unknown custom embeds in the unknown list", () => { 290 const presentation = getQuotedPresentation({ 291 $type: "app.bsky.embed.record#view", 292 record: { 293 $type: "app.bsky.embed.record#viewRecord", 294 author: { did: "did:plc:bob", handle: "bob.test" }, 295 embeds: [ 296 { $type: "app.bsky.embed.images#view", images: [{ fullsize: "https://cdn.example.com/quoted-image.png" }] }, 297 { $type: "app.bsky.embed.video#view", playlist: "https://cdn.example.com/quoted-video.m3u8" }, 298 { $type: "app.bsky.embed.external#view", external: { uri: "https://example.com", title: "External card" } }, 299 { $type: "app.bsky.embed.unsupported#view" }, 300 ], 301 uri: "at://did:plc:bob/app.bsky.feed.post/123", 302 value: { text: "quoted body" }, 303 }, 304 }); 305 306 expect(presentation.normalizedEmbeds).toHaveLength(4); 307 expect(presentation.normalizedEmbeds.map((embed) => embed.kind)).toEqual([ 308 "images", 309 "video", 310 "external", 311 "unknown", 312 ]); 313 expect(presentation.unknownEmbeds).toHaveLength(1); 314 }); 315 316 it("normalizes every official top-level embed kind without treating them as unknown", () => { 317 const fixtures = [{ 318 expectedKind: "images", 319 value: { $type: "app.bsky.embed.images#view", images: [{ fullsize: "https://cdn.example.com/top-image.png" }] }, 320 }, { 321 expectedKind: "video", 322 value: { $type: "app.bsky.embed.video#view", playlist: "https://cdn.example.com/top-video.m3u8" }, 323 }, { 324 expectedKind: "external", 325 value: { $type: "app.bsky.embed.external#view", external: { title: "External", uri: "https://example.com" } }, 326 }, { 327 expectedKind: "record", 328 value: { 329 $type: "app.bsky.embed.record#view", 330 record: { 331 $type: "app.bsky.embed.record#viewRecord", 332 author: { did: "did:plc:bob", handle: "bob.test" }, 333 uri: "at://did:plc:bob/app.bsky.feed.post/quoted-a", 334 value: { text: "quoted a" }, 335 }, 336 }, 337 }, { 338 expectedKind: "recordWithMedia", 339 value: { 340 $type: "app.bsky.embed.recordWithMedia#view", 341 media: { 342 $type: "app.bsky.embed.images#view", 343 images: [{ fullsize: "https://cdn.example.com/top-rwm-image.png" }], 344 }, 345 record: { 346 $type: "app.bsky.embed.record#view", 347 record: { 348 $type: "app.bsky.embed.record#viewRecord", 349 author: { did: "did:plc:bob", handle: "bob.test" }, 350 uri: "at://did:plc:bob/app.bsky.feed.post/quoted-b", 351 value: { text: "quoted b" }, 352 }, 353 }, 354 }, 355 }] as const; 356 357 for (const fixture of fixtures) { 358 const normalized = normalizeEmbed(fixture.value, { source: "top" }); 359 expect(normalized.kind).toBe(fixture.expectedKind); 360 expect(normalized.kind).not.toBe("unknown"); 361 if (normalized.kind === "record" || normalized.kind === "recordWithMedia") { 362 expect(normalized.quoted.unknownEmbeds).toHaveLength(0); 363 } 364 } 365 }); 366 367 it("covers official quoted record union variants without emitting unknown embeds", () => { 368 const fixtures = [{ 369 expectedKind: "post", 370 record: { 371 $type: "app.bsky.embed.record#viewRecord", 372 author: { did: "did:plc:bob", handle: "bob.test" }, 373 uri: "at://did:plc:bob/app.bsky.feed.post/1", 374 value: { text: "post record" }, 375 }, 376 }, { 377 expectedKind: "not-found", 378 record: { 379 $type: "app.bsky.embed.record#viewNotFound", 380 notFound: true, 381 uri: "at://did:plc:bob/app.bsky.feed.post/2", 382 }, 383 }, { 384 expectedKind: "blocked", 385 record: { 386 $type: "app.bsky.embed.record#viewBlocked", 387 blocked: true, 388 uri: "at://did:plc:bob/app.bsky.feed.post/3", 389 }, 390 }, { 391 expectedKind: "detached", 392 record: { 393 $type: "app.bsky.embed.record#viewDetached", 394 detached: true, 395 uri: "at://did:plc:bob/app.bsky.feed.post/4", 396 }, 397 }, { 398 expectedKind: "feed", 399 record: { 400 $type: "app.bsky.feed.defs#generatorView", 401 creator: { did: "did:plc:bob", handle: "bob.test" }, 402 uri: "at://did:plc:bob/app.bsky.feed.generator/following", 403 }, 404 }, { 405 expectedKind: "list", 406 record: { 407 $type: "app.bsky.graph.defs#listView", 408 creator: { did: "did:plc:bob", handle: "bob.test" }, 409 uri: "at://did:plc:bob/app.bsky.graph.list/curated", 410 }, 411 }, { 412 expectedKind: "labeler", 413 record: { 414 $type: "app.bsky.labeler.defs#labelerView", 415 creator: { did: "did:plc:bob", handle: "bob.test" }, 416 uri: "at://did:plc:bob/app.bsky.labeler.service/self", 417 }, 418 }, { 419 expectedKind: "starter-pack", 420 record: { 421 $type: "app.bsky.graph.defs#starterPackViewBasic", 422 creator: { did: "did:plc:bob", handle: "bob.test" }, 423 record: { name: "Starter Pack" }, 424 uri: "at://did:plc:bob/app.bsky.graph.starterpack/abc123", 425 }, 426 }] as const; 427 428 for (const fixture of fixtures) { 429 const presentation = getQuotedPresentation({ $type: "app.bsky.embed.record#view", record: fixture.record }); 430 431 expect(presentation.kind).toBe(fixture.expectedKind); 432 expect(presentation.unknownEmbeds).toHaveLength(0); 433 } 434 }); 435 436 it("infers malformed but recognizable embed shapes without adding unknown embeds", () => { 437 const inferredImages = normalizeEmbed({ images: [{ fullsize: "https://cdn.example.com/inferred-image.png" }] }, { 438 source: "quoted", 439 }); 440 const inferredVideo = normalizeEmbed({ playlist: "https://cdn.example.com/inferred-video.m3u8" }, { 441 source: "quoted", 442 }); 443 const inferredExternal = normalizeEmbed({ 444 external: { title: "Inferred external", uri: "https://example.com/inferred" }, 445 }, { source: "quoted" }); 446 const inferredRecord = normalizeEmbed({ record: { uri: "at://did:plc:bob/app.bsky.feed.post/inferred" } }, { 447 source: "quoted", 448 }); 449 const inferredRecordWithMedia = normalizeEmbed({ 450 media: { images: [{ fullsize: "https://cdn.example.com/inferred-rwm.png" }] }, 451 record: { uri: "at://did:plc:bob/app.bsky.feed.post/inferred-rwm" }, 452 }, { source: "quoted" }); 453 454 expect(inferredImages.kind).toBe("images"); 455 expect(inferredVideo.kind).toBe("video"); 456 expect(inferredExternal.kind).toBe("external"); 457 expect(inferredRecord.kind).toBe("record"); 458 expect(inferredRecordWithMedia.kind).toBe("recordWithMedia"); 459 }); 460 461 it("uses quoted embed extraction precedence: embeds > value.embed > value.embeds", () => { 462 const fromEmbeds = getQuotedPresentation({ 463 $type: "app.bsky.embed.record#view", 464 record: { 465 $type: "app.bsky.embed.record#viewRecord", 466 embeds: [{ $type: "app.bsky.embed.images#view", images: [{ fullsize: "https://cdn.example.com/a.png" }] }], 467 uri: "at://did:plc:bob/app.bsky.feed.post/a", 468 value: { 469 embed: { $type: "app.bsky.embed.video#view", playlist: "https://cdn.example.com/a.m3u8" }, 470 embeds: [{ $type: "app.bsky.embed.external#view", external: { title: "A", uri: "https://example.com/a" } }], 471 text: "a", 472 }, 473 }, 474 }); 475 const fromValueEmbed = getQuotedPresentation({ 476 $type: "app.bsky.embed.record#view", 477 record: { 478 $type: "app.bsky.embed.record#viewRecord", 479 uri: "at://did:plc:bob/app.bsky.feed.post/b", 480 value: { 481 embed: { $type: "app.bsky.embed.video#view", playlist: "https://cdn.example.com/b.m3u8" }, 482 embeds: [{ $type: "app.bsky.embed.external#view", external: { title: "B", uri: "https://example.com/b" } }], 483 text: "b", 484 }, 485 }, 486 }); 487 const fromValueEmbeds = getQuotedPresentation({ 488 $type: "app.bsky.embed.record#view", 489 record: { 490 $type: "app.bsky.embed.record#viewRecord", 491 uri: "at://did:plc:bob/app.bsky.feed.post/c", 492 value: { 493 embeds: [{ $type: "app.bsky.embed.external#view", external: { title: "C", uri: "https://example.com/c" } }], 494 text: "c", 495 }, 496 }, 497 }); 498 499 expect(fromEmbeds.normalizedEmbeds).toHaveLength(1); 500 expect(fromEmbeds.normalizedEmbeds[0]?.kind).toBe("images"); 501 expect(fromEmbeds.normalizedEmbeds[0]?.meta.source).toBe("viewRecord.embeds"); 502 503 expect(fromValueEmbed.normalizedEmbeds).toHaveLength(1); 504 expect(fromValueEmbed.normalizedEmbeds[0]?.kind).toBe("video"); 505 expect(fromValueEmbed.normalizedEmbeds[0]?.meta.source).toBe("value.embed"); 506 507 expect(fromValueEmbeds.normalizedEmbeds).toHaveLength(1); 508 expect(fromValueEmbeds.normalizedEmbeds[0]?.kind).toBe("external"); 509 expect(fromValueEmbeds.normalizedEmbeds[0]?.meta.source).toBe("value.embeds"); 510 }); 511 512 it("prefers postView.record.embed before value embed fallbacks", () => { 513 const fromPostViewEmbed = getQuotedPresentation({ 514 $type: "app.bsky.embed.record#view", 515 record: { 516 $type: "app.bsky.feed.defs#postView", 517 author: { did: "did:plc:bob", handle: "bob.test" }, 518 record: { 519 embed: { 520 $type: "app.bsky.embed.images#view", 521 images: [{ fullsize: "https://cdn.example.com/postview.png" }], 522 }, 523 text: "postview", 524 }, 525 uri: "at://did:plc:bob/app.bsky.feed.post/postview-priority", 526 value: { 527 embed: { $type: "app.bsky.embed.video#view", playlist: "https://cdn.example.com/fallback.m3u8" }, 528 embeds: [{ 529 $type: "app.bsky.embed.external#view", 530 external: { title: "fallback", uri: "https://example.com/fallback" }, 531 }], 532 text: "fallback text", 533 }, 534 }, 535 }); 536 537 expect(fromPostViewEmbed.normalizedEmbeds).toHaveLength(1); 538 expect(fromPostViewEmbed.normalizedEmbeds[0]?.kind).toBe("images"); 539 expect(fromPostViewEmbed.normalizedEmbeds[0]?.meta.source).toBe("value.embed"); 540 541 const fromFallbackValueEmbed = getQuotedPresentation({ 542 $type: "app.bsky.embed.record#view", 543 record: { 544 $type: "app.bsky.feed.defs#postView", 545 author: { did: "did:plc:bob", handle: "bob.test" }, 546 record: { text: "postview without hydrated embed" }, 547 uri: "at://did:plc:bob/app.bsky.feed.post/postview-fallback", 548 value: { 549 embed: { $type: "app.bsky.embed.video#view", playlist: "https://cdn.example.com/value-only.m3u8" }, 550 text: "value-only", 551 }, 552 }, 553 }); 554 555 expect(fromFallbackValueEmbed.normalizedEmbeds).toHaveLength(1); 556 expect(fromFallbackValueEmbed.normalizedEmbeds[0]?.kind).toBe("video"); 557 expect(fromFallbackValueEmbed.normalizedEmbeds[0]?.meta.source).toBe("value.embed"); 558 }); 559 560 it("keeps unknown custom embeds visible and aggregates telemetry by fingerprint", () => { 561 const custom = { $type: "dev.example.embed#view", payload: { nested: { key: "value" } } }; 562 const topUnknownA = normalizeEmbed(custom, { source: "top" }); 563 const topUnknownB = normalizeEmbed(custom, { source: "top" }); 564 const quoted = getQuotedPresentation({ 565 $type: "app.bsky.embed.record#view", 566 record: { 567 $type: "app.bsky.embed.record#viewRecord", 568 embeds: [ 569 { $type: "app.bsky.embed.images#view", images: [{ fullsize: "https://cdn.example.com/known.png" }] }, 570 custom, 571 ], 572 uri: "at://did:plc:bob/app.bsky.feed.post/custom", 573 value: { text: "custom quote" }, 574 }, 575 }); 576 577 expect(topUnknownA.kind).toBe("unknown"); 578 expect(topUnknownB.kind).toBe("unknown"); 579 expect(quoted.normalizedEmbeds.map((embed) => embed.kind)).toEqual(["images", "unknown"]); 580 expect(quoted.unknownEmbeds).toHaveLength(1); 581 582 const telemetry = [...getUnknownEmbedTelemetryForTests().values()]; 583 expect(telemetry.length).toBeGreaterThan(0); 584 expect(Math.max(...telemetry)).toBeGreaterThanOrEqual(2); 585 }); 586 587 it("guards against deep nesting and embed cycles", () => { 588 const deep = { 589 $type: "app.bsky.embed.record#view", 590 record: { 591 $type: "app.bsky.embed.record#viewRecord", 592 embeds: [{ 593 $type: "app.bsky.embed.record#view", 594 record: { 595 $type: "app.bsky.embed.record#viewRecord", 596 embeds: [{ 597 $type: "app.bsky.embed.record#view", 598 record: { 599 $type: "app.bsky.embed.record#viewRecord", 600 embeds: [{ 601 $type: "app.bsky.embed.record#view", 602 record: { 603 $type: "app.bsky.embed.record#viewRecord", 604 embeds: [{ 605 $type: "app.bsky.embed.record#view", 606 record: { 607 $type: "app.bsky.embed.record#viewRecord", 608 uri: "at://did:plc:bob/app.bsky.feed.post/deep-leaf", 609 value: { text: "leaf" }, 610 }, 611 }], 612 uri: "at://did:plc:bob/app.bsky.feed.post/deep-4", 613 value: { text: "deep-4" }, 614 }, 615 }], 616 uri: "at://did:plc:bob/app.bsky.feed.post/deep-3", 617 value: { text: "deep-3" }, 618 }, 619 }], 620 uri: "at://did:plc:bob/app.bsky.feed.post/deep-2", 621 value: { text: "deep-2" }, 622 }, 623 }], 624 uri: "at://did:plc:bob/app.bsky.feed.post/deep-root", 625 value: { text: "deep-root" }, 626 }, 627 }; 628 const depthLimited = normalizeEmbed(deep, { maxDepth: 3, source: "top" }); 629 const seen: NormalizedEmbed[] = []; 630 walkNormalizedEmbeds(depthLimited, (embed) => seen.push(embed)); 631 632 expect(seen.some((embed) => embed.meta.depthLimited)).toBe(true); 633 634 const cycleRoot: Record<string, unknown> = { 635 $type: "app.bsky.embed.record#view", 636 record: { 637 $type: "app.bsky.embed.record#viewRecord", 638 uri: "at://did:plc:bob/app.bsky.feed.post/cycle-root", 639 value: { text: "cycle root" }, 640 }, 641 }; 642 const cycleRecord = cycleRoot.record as Record<string, unknown>; 643 cycleRecord.embeds = [cycleRoot]; 644 645 const cycleNormalized = normalizeEmbed(cycleRoot, { source: "top" }); 646 expect(cycleNormalized.kind).toBe("record"); 647 if (cycleNormalized.kind === "record") { 648 expect(cycleNormalized.quoted.normalizedEmbeds).toHaveLength(1); 649 expect(cycleNormalized.quoted.normalizedEmbeds[0]?.kind).toBe("recognized-unrenderable"); 650 expect(cycleNormalized.quoted.normalizedEmbeds[0]?.meta.cycle).toBe(true); 651 } 652 }); 653 654 it("builds starter-pack and labeler external links", () => { 655 const starterPack = getQuotedPresentation({ 656 $type: "app.bsky.embed.record#view", 657 record: { 658 $type: "app.bsky.graph.defs#starterPackViewBasic", 659 creator: { did: "did:plc:alice", handle: "alice.test" }, 660 uri: "at://did:plc:alice/app.bsky.graph.starterpack/3lxyx7z7p2f2u", 661 }, 662 }); 663 const labeler = getQuotedPresentation({ 664 $type: "app.bsky.embed.record#view", 665 record: { 666 $type: "app.bsky.labeler.defs#labelerView", 667 creator: { did: "did:plc:labeler123", handle: "labeler.example" }, 668 uri: "at://did:plc:labeler123/app.bsky.labeler.service/self", 669 }, 670 }); 671 672 expect(starterPack.href).toBe("https://bsky.app/starter-pack/did%3Aplc%3Aalice/3lxyx7z7p2f2u"); 673 expect(labeler.href).toBe("https://bsky.app/profile/labeler.example"); 674 }); 675 676 it("falls back to did-based post urls when handle is missing", () => { 677 const postWithoutHandle = { 678 ...createFeedItem().post, 679 author: { did: "did:plc:alice", handle: undefined as unknown as string }, 680 }; 681 682 expect(buildPublicPostUrl(postWithoutHandle)).toBe("https://bsky.app/profile/did%3Aplc%3Aalice/post/1"); 683 }); 684 685 it("rejects malformed feed payloads", () => { 686 expect(() => parseFeedResponse({ cursor: null, feed: {} })).toThrow("feed response payload is invalid"); 687 expect(() => parseFeedResponse({ cursor: 42, feed: [] })).toThrow("feed response cursor is invalid"); 688 }); 689 690 it("rejects malformed thread payloads", () => { 691 expect(() => parseThreadResponse({ thread: { nope: true } })).toThrow("thread response payload is invalid"); 692 }); 693});