learn and share notes on atproto (wip) 馃
malfestio.stormlightlabs.org/
readability
solid
axum
atproto
srs
1import { api } from "$lib/api";
2import { cleanup, render, screen, waitFor } from "@solidjs/testing-library";
3import { JSX } from "solid-js";
4import { afterEach, describe, expect, it, vi } from "vitest";
5import Search from "../Search";
6
7vi.mock("$lib/api", () => ({ api: { search: vi.fn() } }));
8
9const { mockSearchParams } = vi.hoisted(() => ({ mockSearchParams: { q: "" } }));
10
11vi.mock(
12 "@solidjs/router",
13 () => ({
14 useSearchParams: () => [mockSearchParams],
15 A: (props: { href: string; children: JSX.Element }) => <a href={props.href}>{props.children}</a>,
16 useNavigate: () => vi.fn(),
17 }),
18);
19
20describe("Search", () => {
21 afterEach(() => {
22 cleanup();
23 vi.clearAllMocks();
24 });
25
26 const mockSearchResults = [{
27 item_type: "deck",
28 item_id: "deck1",
29 creator_did: "did:test:1",
30 data: {
31 id: "deck1",
32 owner_did: "did:test:1",
33 title: "Test Deck",
34 description: "A test deck",
35 tags: ["test"],
36 visibility: { type: "Public" },
37 },
38 rank: 0.9,
39 }, {
40 item_type: "card",
41 item_id: "card1",
42 creator_did: "did:test:1",
43 data: { id: "card1", deck_id: "deck1", front: "Card Front", back: "Card Back", owner_did: "did:test:1" },
44 rank: 0.8,
45 }, {
46 item_type: "note",
47 item_id: "note1",
48 creator_did: "did:test:1",
49 data: { id: "note1", title: "Test Note", owner_did: "did:test:1" },
50 rank: 0.7,
51 }];
52
53 it("renders search results correctly", async () => {
54 mockSearchParams.q = "test";
55 vi.mocked(api.search).mockResolvedValue(
56 { ok: true, json: () => Promise.resolve(mockSearchResults) } as unknown as Response,
57 );
58
59 render(() => <Search />);
60
61 // Verify loading state or results
62 await waitFor(() => expect(screen.getByText("Search Results")).toBeInTheDocument());
63
64 // Verify Deck result
65 await waitFor(() => expect(screen.getByText("Test Deck")).toBeInTheDocument());
66 expect(screen.getByText("A test deck")).toBeInTheDocument();
67
68 // Verify Card result
69 expect(screen.getByText("Card Front")).toBeInTheDocument();
70 expect(screen.getByText("Card Back")).toBeInTheDocument();
71
72 // Verify Note result
73 expect(screen.getByText("Test Note")).toBeInTheDocument();
74 });
75
76 it("shows empty state when no results", async () => {
77 mockSearchParams.q = "nonexistent";
78 vi.mocked(api.search).mockResolvedValue({ ok: true, json: () => Promise.resolve([]) } as unknown as Response);
79
80 render(() => <Search />);
81
82 await waitFor(() => expect(screen.getByText("No results found for \"nonexistent\"")).toBeInTheDocument());
83 });
84
85 it("handles loading state", async () => {
86 mockSearchParams.q = "loading";
87 vi.mocked(api.search).mockReturnValue(new Promise(() => {}));
88
89 render(() => <Search />);
90
91 expect(api.search).toHaveBeenCalledWith("loading");
92 });
93
94 it("generates correct links for results", async () => {
95 mockSearchParams.q = "test";
96 vi.mocked(api.search).mockResolvedValue(
97 { ok: true, json: () => Promise.resolve(mockSearchResults) } as unknown as Response,
98 );
99
100 render(() => <Search />);
101
102 await waitFor(() => expect(screen.getByText("Test Deck")).toBeInTheDocument());
103
104 const deckLink = screen.getByText("Test Deck").closest("a");
105 expect(deckLink).toHaveAttribute("href", "/decks/deck1");
106
107 const cardLink = screen.getByText("Card in Deck").closest("a");
108 expect(cardLink).toHaveAttribute("href", "/decks/deck1");
109
110 const noteLink = screen.getByText("Test Note").closest("a");
111 expect(noteLink).toHaveAttribute("href", "/notes/note1");
112 });
113
114 it("shows remote indicator for remote results", async () => {
115 mockSearchParams.q = "remote";
116 const remoteResult = {
117 item_type: "deck",
118 item_id: "remote-deck",
119 creator_did: "did:remote:user",
120 data: {
121 id: "remote-deck",
122 owner_did: "did:remote:user",
123 title: "Remote Deck",
124 description: "From afar",
125 tags: [],
126 visibility: { type: "Public" },
127 },
128 rank: 1.0,
129 source: "remote",
130 };
131
132 vi.mocked(api.search).mockResolvedValue(
133 { ok: true, json: () => Promise.resolve([remoteResult]) } as unknown as Response,
134 );
135
136 render(() => <Search />);
137
138 await waitFor(() => expect(screen.getByText("Remote Deck")).toBeInTheDocument());
139 expect(screen.getByText("Remote")).toBeInTheDocument();
140 });
141});