learn and share notes on atproto (wip) 馃
malfestio.stormlightlabs.org/
readability
solid
axum
atproto
srs
1import type { Note } from "$lib/model";
2import { cleanup, fireEvent, render, screen } from "@solidjs/testing-library";
3import type { JSX } from "solid-js";
4import { afterEach, describe, expect, it, vi } from "vitest";
5import { NotesSidebar } from "../NotesSidebar";
6
7vi.mock(
8 "@solidjs/router",
9 () => ({
10 A: (props: { href: string; class: string; children: JSX.Element; "data-testid"?: string }) => (
11 <a href={props.href} class={props.class} data-testid={props["data-testid"]}>{props.children}</a>
12 ),
13 }),
14);
15
16const mockNotes: Note[] = [{
17 id: "note-1",
18 owner_did: "did:plc:test",
19 title: "First Note",
20 body: "Content with [[Second Note]] link",
21 tags: ["rust", "learning"],
22 visibility: { type: "Private" },
23 created_at: "2026-01-01T00:00:00Z",
24 updated_at: "2026-01-03T00:00:00Z",
25}, {
26 id: "note-2",
27 owner_did: "did:plc:test",
28 title: "Second Note",
29 body: "This links to [[First Note]]",
30 tags: ["rust", "advanced"],
31 visibility: { type: "Private" },
32 created_at: "2026-01-01T00:00:00Z",
33 updated_at: "2026-01-02T00:00:00Z",
34}, {
35 id: "note-3",
36 owner_did: "did:plc:test",
37 title: "Orphan Note",
38 body: "No wikilinks here",
39 tags: ["learning"],
40 visibility: { type: "Private" },
41 created_at: "2026-01-01T00:00:00Z",
42 updated_at: "2026-01-01T00:00:00Z",
43}];
44
45describe("NotesSidebar", () => {
46 afterEach(cleanup);
47
48 it("renders the sidebar", () => {
49 render(() => <NotesSidebar notes={mockNotes} />);
50 expect(screen.getByTestId("notes-sidebar")).toBeInTheDocument();
51 });
52
53 it("displays filter buttons with counts", () => {
54 render(() => <NotesSidebar notes={mockNotes} />);
55 expect(screen.getByTestId("filter-all")).toHaveTextContent("All (3)");
56 expect(screen.getByTestId("filter-linked")).toHaveTextContent("Linked (2)");
57 expect(screen.getByTestId("filter-orphaned")).toHaveTextContent("Orphaned (1)");
58 });
59
60 it("displays unique tags with correct counts", () => {
61 render(() => <NotesSidebar notes={mockNotes} />);
62
63 expect(screen.getByTestId("tag-rust")).toHaveTextContent("#rust");
64 expect(screen.getByTestId("tag-rust")).toHaveTextContent("2");
65
66 expect(screen.getByTestId("tag-learning")).toHaveTextContent("#learning");
67 expect(screen.getByTestId("tag-learning")).toHaveTextContent("2");
68
69 expect(screen.getByTestId("tag-advanced")).toHaveTextContent("#advanced");
70 expect(screen.getByTestId("tag-advanced")).toHaveTextContent("1");
71 });
72
73 it("displays recent notes sorted by update date", () => {
74 render(() => <NotesSidebar notes={mockNotes} />);
75
76 const recentLinks = screen.getAllByTestId(/^recent-/);
77 expect(recentLinks[0]).toHaveAttribute("href", "/notes/note-1");
78 expect(recentLinks[1]).toHaveAttribute("href", "/notes/note-2");
79 expect(recentLinks[2]).toHaveAttribute("href", "/notes/note-3");
80 });
81
82 it("calls onFilterSelect when filter clicked", async () => {
83 const onFilterSelect = vi.fn();
84 render(() => <NotesSidebar notes={mockNotes} onFilterSelect={onFilterSelect} />);
85
86 await fireEvent.click(screen.getByTestId("filter-linked"));
87 expect(onFilterSelect).toHaveBeenCalledWith("linked");
88
89 await fireEvent.click(screen.getByTestId("filter-orphaned"));
90 expect(onFilterSelect).toHaveBeenCalledWith("orphaned");
91 });
92
93 it("calls onTagSelect when tag clicked", async () => {
94 const onTagSelect = vi.fn();
95 render(() => <NotesSidebar notes={mockNotes} onTagSelect={onTagSelect} />);
96
97 await fireEvent.click(screen.getByTestId("tag-rust"));
98 expect(onTagSelect).toHaveBeenCalledWith("rust");
99 });
100
101 it("toggles tag selection off when same tag clicked", async () => {
102 const onTagSelect = vi.fn();
103 render(() => <NotesSidebar notes={mockNotes} selectedTag="rust" onTagSelect={onTagSelect} />);
104
105 await fireEvent.click(screen.getByTestId("tag-rust"));
106 expect(onTagSelect).toHaveBeenCalledWith(undefined);
107 });
108
109 it("handles empty notes array", () => {
110 render(() => <NotesSidebar notes={[]} />);
111 expect(screen.getByTestId("notes-sidebar")).toBeInTheDocument();
112 expect(screen.getByTestId("filter-all")).toHaveTextContent("All (0)");
113 });
114});