BlueSky & more on desktop
lazurite.stormlightlabs.org/
tauri
rust
typescript
bluesky
appview
atproto
solid
1import { describe, expect, it } from "vitest";
2import { filterProfileFeed, parseActorList, parseProfileResult } from "../profile";
3import type { FeedViewPost } from "../types";
4
5function createFeedItem(overrides: Partial<FeedViewPost> = {}): FeedViewPost {
6 return {
7 post: {
8 author: { did: "did:plc:alice", handle: "alice.test" },
9 cid: "cid-1",
10 indexedAt: "2026-03-28T12:00:00.000Z",
11 record: { createdAt: "2026-03-28T12:00:00.000Z", text: "hello world" },
12 uri: "at://did:plc:alice/app.bsky.feed.post/1",
13 },
14 ...overrides,
15 };
16}
17
18describe("profile helpers", () => {
19 it("parses actor lists with bios and follow state", () => {
20 const response = parseActorList({
21 cursor: "cursor-1",
22 followers: [{
23 avatar: "https://example.com/avatar.png",
24 description: "Writes about protocol design.",
25 did: "did:plc:bob",
26 displayName: "Bob",
27 handle: "bob.test",
28 viewer: { following: "at://did:plc:alice/app.bsky.graph.follow/1" },
29 }],
30 }, "followers");
31
32 expect(response).toEqual({
33 cursor: "cursor-1",
34 actors: [{
35 avatar: "https://example.com/avatar.png",
36 description: "Writes about protocol design.",
37 did: "did:plc:bob",
38 displayName: "Bob",
39 handle: "bob.test",
40 labels: [],
41 viewer: { following: "at://did:plc:alice/app.bsky.graph.follow/1" },
42 }],
43 });
44 });
45
46 it("filters profile feeds by tab semantics", () => {
47 const base = createFeedItem();
48 const reply = createFeedItem({
49 post: { ...base.post, uri: "at://did:plc:alice/app.bsky.feed.post/2" },
50 reply: {
51 parent: { $type: "app.bsky.feed.defs#postView", ...base.post },
52 root: { $type: "app.bsky.feed.defs#postView", ...base.post },
53 },
54 });
55 const media = createFeedItem({
56 post: {
57 ...base.post,
58 embed: { $type: "app.bsky.embed.images#view", images: [{ thumb: "https://example.com/thumb.png" }] },
59 uri: "at://did:plc:alice/app.bsky.feed.post/3",
60 },
61 });
62
63 expect(filterProfileFeed([base, reply, media], "posts")).toEqual([base, media]);
64 expect(filterProfileFeed([base, reply, media], "replies")).toEqual([reply]);
65 expect(filterProfileFeed([base, reply, media], "media")).toEqual([media]);
66 expect(filterProfileFeed([base, reply, media], "likes")).toEqual([base, reply, media]);
67 });
68
69 it("parses available and unavailable profile results", () => {
70 expect(
71 parseProfileResult({
72 status: "available",
73 profile: { did: "did:plc:bob", handle: "bob.test", displayName: "Bob" },
74 }),
75 ).toEqual({
76 status: "available",
77 profile: {
78 avatar: null,
79 banner: null,
80 createdAt: null,
81 description: null,
82 descriptionFacets: null,
83 did: "did:plc:bob",
84 displayName: "Bob",
85 followersCount: null,
86 followsCount: null,
87 handle: "bob.test",
88 indexedAt: null,
89 labels: [],
90 pinnedPost: null,
91 postsCount: null,
92 pronouns: null,
93 viewer: null,
94 website: null,
95 },
96 });
97
98 expect(
99 parseProfileResult({
100 status: "unavailable",
101 requestedActor: "missing.test",
102 handle: "missing.test",
103 reason: "notFound",
104 message: "This profile could not be found.",
105 }),
106 ).toEqual({
107 status: "unavailable",
108 requestedActor: "missing.test",
109 did: null,
110 handle: "missing.test",
111 reason: "notFound",
112 message: "This profile could not be found.",
113 });
114 });
115});