BlueSky & more on desktop
lazurite.stormlightlabs.org/
tauri
rust
typescript
bluesky
appview
atproto
solid
1import { AppTestProviders } from "$/test/providers";
2import { fireEvent, render, screen, waitFor } from "@solidjs/testing-library";
3import { beforeEach, describe, expect, it, vi } from "vitest";
4import { DiagnosticsPanel } from "../DiagnosticsPanel";
5
6const getAccountListsMock = vi.hoisted(() => vi.fn());
7const getAccountLabelsMock = vi.hoisted(() => vi.fn());
8const getAccountBlockedByMock = vi.hoisted(() => vi.fn());
9const getAccountBlockingMock = vi.hoisted(() => vi.fn());
10const getAccountStarterPacksMock = vi.hoisted(() => vi.fn());
11const getRecordBacklinksMock = vi.hoisted(() => vi.fn());
12const moderateContentMock = vi.hoisted(() => vi.fn());
13
14vi.mock(
15 "$/lib/api/diagnostics",
16 () => ({
17 DiagnosticsController: {
18 getAccountBlockedBy: getAccountBlockedByMock,
19 getAccountBlocking: getAccountBlockingMock,
20 getAccountLabels: getAccountLabelsMock,
21 getAccountLists: getAccountListsMock,
22 getAccountStarterPacks: getAccountStarterPacksMock,
23 getRecordBacklinks: getRecordBacklinksMock,
24 },
25 }),
26);
27vi.mock("$/lib/api/moderation", () => ({ ModerationController: { moderateContent: moderateContentMock } }));
28
29function renderPanel(recordUri?: string) {
30 render(() => (
31 <AppTestProviders session={{ activeDid: "did:plc:test", activeHandle: "test.bsky.social" }}>
32 <DiagnosticsPanel did="did:plc:test" onClose={vi.fn()} recordUri={recordUri ?? null} />
33 </AppTestProviders>
34 ));
35}
36
37describe("DiagnosticsPanel", () => {
38 beforeEach(() => {
39 getAccountListsMock.mockReset();
40 getAccountLabelsMock.mockReset();
41 getAccountBlockedByMock.mockReset();
42 getAccountBlockingMock.mockReset();
43 getAccountStarterPacksMock.mockReset();
44 getRecordBacklinksMock.mockReset();
45 moderateContentMock.mockReset();
46 moderateContentMock.mockResolvedValue({
47 alert: false,
48 blur: "none",
49 filter: false,
50 inform: false,
51 noOverride: false,
52 });
53
54 getAccountListsMock.mockResolvedValue({
55 lists: [{
56 description: "Builders and product people.",
57 memberCount: 12,
58 purpose: "app.bsky.graph.defs#curatelist",
59 title: "Builders",
60 creator: { handle: "mira.test" },
61 }, {
62 description: "Moderation boundary set.",
63 listItemCount: 5,
64 purpose: "app.bsky.graph.defs#modlist",
65 title: "Safety",
66 creator: { handle: "safety.test" },
67 }],
68 total: 2,
69 truncated: false,
70 });
71 getAccountLabelsMock.mockResolvedValue({
72 labels: [{ src: "did:plc:labeler", val: "!hide" }],
73 sourceProfiles: { "did:plc:labeler": { displayName: "Safety Service", handle: "safety.service" } },
74 cursor: null,
75 });
76 getAccountBlockedByMock.mockResolvedValue({
77 cursor: null,
78 items: [{ availability: "available", did: "did:plc:blocker", profile: { handle: "blocker.test" } }],
79 total: 1,
80 });
81 getAccountBlockingMock.mockResolvedValue({
82 cursor: null,
83 items: [{ availability: "available", subjectDid: "did:plc:boundary", profile: { handle: "boundary.test" } }],
84 });
85 getAccountStarterPacksMock.mockResolvedValue({
86 starterPacks: [{
87 creator: { handle: "packer.test" },
88 description: "Starter pack desc.",
89 listItemCount: 8,
90 title: "Newcomers",
91 }],
92 total: 1,
93 truncated: false,
94 });
95 getRecordBacklinksMock.mockResolvedValue({
96 likes: { cursor: null, records: [], total: 0 },
97 quotes: { cursor: null, records: [], total: 0 },
98 replies: { cursor: null, records: [], total: 0 },
99 reposts: { cursor: null, records: [], total: 0 },
100 });
101 });
102
103 it("renders the tab shell and switches tabs with keys", async () => {
104 renderPanel();
105
106 expect(await screen.findByText("Social Diagnostics")).toBeInTheDocument();
107 expect(screen.getByRole("button", { name: "Lists" })).toHaveAttribute("aria-pressed", "true");
108
109 fireEvent.keyDown(document, { key: "2" });
110 expect(screen.getByRole("button", { name: "Labels" })).toBeInTheDocument();
111 fireEvent.keyDown(document, { key: "Escape" });
112 });
113
114 it("groups lists and shows neutral labels", async () => {
115 renderPanel();
116
117 fireEvent.click(await screen.findByRole("button", { name: "Lists" }));
118 expect(screen.getAllByText("Curation").length).toBeGreaterThan(0);
119 expect(screen.getByText("Builders")).toBeInTheDocument();
120 expect(screen.getAllByText("Moderation").length).toBeGreaterThan(0);
121 });
122
123 it("shows blocks and starter packs with progressive disclosure", async () => {
124 renderPanel();
125
126 fireEvent.click(await screen.findByRole("button", { name: "Blocks" }));
127 expect(await screen.findByText("Boundaries around you")).toBeInTheDocument();
128 expect(screen.getByRole("button", { name: /show details/i })).toBeInTheDocument();
129
130 fireEvent.click(screen.getByRole("button", { name: /show details/i }));
131 await waitFor(() => expect(screen.getAllByText("blocker.test").length).toBeGreaterThan(0));
132
133 fireEvent.click(screen.getByRole("button", { name: "Starter Packs" }));
134 expect(await screen.findByText("Newcomers")).toBeInTheDocument();
135 expect(screen.getByText("8 members")).toBeInTheDocument();
136 });
137
138 it("renders unavailable block rows without breaking the section", async () => {
139 getAccountBlockedByMock.mockResolvedValueOnce({
140 cursor: null,
141 items: [{
142 availability: "unavailable",
143 did: "did:plc:missing",
144 unavailableMessage: "This profile is unavailable right now.",
145 }],
146 total: 1,
147 });
148
149 renderPanel();
150
151 fireEvent.click(await screen.findByRole("button", { name: "Blocks" }));
152 fireEvent.click(screen.getByRole("button", { name: /show details/i }));
153
154 const missing = await screen.findAllByText("did:plc:missing");
155 expect(missing.length).toBeGreaterThan(0);
156 expect(screen.getByText("This profile is unavailable right now.")).toBeInTheDocument();
157 });
158
159 it("renders moderation badges for labeled block-list profiles", async () => {
160 getAccountBlockedByMock.mockResolvedValueOnce({
161 cursor: null,
162 items: [{
163 availability: "available",
164 did: "did:plc:blocker",
165 profile: { handle: "blocker.test", labels: [{ src: "did:plc:labeler", val: "sexual" }] },
166 }],
167 total: 1,
168 });
169 moderateContentMock.mockImplementation(async (_labels, context: string) => {
170 if (context === "profileList") {
171 return { alert: true, blur: "none", filter: false, inform: false, noOverride: false };
172 }
173
174 return { alert: false, blur: "none", filter: false, inform: false, noOverride: false };
175 });
176
177 renderPanel();
178
179 fireEvent.click(await screen.findByRole("button", { name: "Blocks" }));
180 fireEvent.click(screen.getByRole("button", { name: /show details/i }));
181
182 expect(await screen.findByText("blocker.test")).toBeInTheDocument();
183 expect(await screen.findByText("Alert")).toBeInTheDocument();
184 });
185
186 it("explains backlinks when no record URI is selected", async () => {
187 renderPanel();
188
189 fireEvent.click(await screen.findByRole("button", { name: "Backlinks" }));
190
191 expect(await screen.findByText(/Backlinks are record-specific engagement context/i)).toBeInTheDocument();
192 expect(screen.getByText(/Open a post or record to inspect the public references pointing at it/i))
193 .toBeInTheDocument();
194 });
195
196 it("renders moderation badges for labeled backlink profiles", async () => {
197 getRecordBacklinksMock.mockResolvedValueOnce({
198 likes: {
199 cursor: null,
200 records: [{
201 collection: "app.bsky.feed.like",
202 did: "did:plc:fan",
203 profile: { handle: "fan.test", labels: [{ src: "did:plc:labeler", val: "sexual" }] },
204 rkey: "1",
205 uri: "at://did:plc:fan/app.bsky.feed.like/1",
206 }],
207 total: 1,
208 },
209 quotes: { cursor: null, records: [], total: 0 },
210 replies: { cursor: null, records: [], total: 0 },
211 reposts: { cursor: null, records: [], total: 0 },
212 });
213 moderateContentMock.mockImplementation(async (_labels, context: string) => {
214 if (context === "profileList") {
215 return { alert: true, blur: "none", filter: false, inform: false, noOverride: false };
216 }
217
218 return { alert: false, blur: "none", filter: false, inform: false, noOverride: false };
219 });
220
221 renderPanel("at://did:plc:test/app.bsky.feed.post/123");
222
223 fireEvent.click(await screen.findByRole("button", { name: "Backlinks" }));
224 fireEvent.click(await screen.findByRole("button", { name: /likes/i }));
225
226 expect(await screen.findByText("fan.test")).toBeInTheDocument();
227 expect(await screen.findByText("Alert")).toBeInTheDocument();
228 });
229});