WIP! A BB-style forum, on the ATmosphere!
We're still working... we'll be back soon when we have something to show off!
node
typescript
hono
htmx
atproto
1import { describe, it, expect } from "vitest";
2import { Hono } from "hono";
3import { ErrorDisplay } from "../error-display.js";
4import { EmptyState } from "../empty-state.js";
5import { LoadingState } from "../loading-state.js";
6
7describe("ErrorDisplay", () => {
8 it("renders message with error-display class", async () => {
9 const app = new Hono().get("/", (c) =>
10 c.html(<ErrorDisplay message="Something went wrong" />)
11 );
12 const res = await app.request("/");
13 const html = await res.text();
14 expect(html).toContain('class="error-display"');
15 expect(html).toContain("Something went wrong");
16 });
17
18 it("renders detail when provided", async () => {
19 const app = new Hono().get("/", (c) =>
20 c.html(<ErrorDisplay message="Error" detail="Extra context" />)
21 );
22 const res = await app.request("/");
23 const html = await res.text();
24 expect(html).toContain("Extra context");
25 });
26
27 it("omits detail element when not provided", async () => {
28 const app = new Hono().get("/", (c) =>
29 c.html(<ErrorDisplay message="Error" />)
30 );
31 const res = await app.request("/");
32 const html = await res.text();
33 expect(html).not.toMatch(/<p[^>]*class="error-display__detail"[^>]*>/);
34 });
35});
36
37describe("EmptyState", () => {
38 it("renders message with empty-state class", async () => {
39 const app = new Hono().get("/", (c) =>
40 c.html(<EmptyState message="No topics yet" />)
41 );
42 const res = await app.request("/");
43 const html = await res.text();
44 expect(html).toContain('class="empty-state"');
45 expect(html).toContain("No topics yet");
46 });
47
48 it("renders action when provided", async () => {
49 const app = new Hono().get("/", (c) =>
50 c.html(
51 <EmptyState message="Empty" action={<a href="/new">Create one</a>} />
52 )
53 );
54 const res = await app.request("/");
55 const html = await res.text();
56 expect(html).toContain('class="empty-state__action"');
57 expect(html).toContain("Create one");
58 });
59
60 it("omits action element when not provided", async () => {
61 const app = new Hono().get("/", (c) =>
62 c.html(<EmptyState message="Empty" />)
63 );
64 const res = await app.request("/");
65 const html = await res.text();
66 expect(html).not.toContain('class="empty-state__action"');
67 });
68});
69
70describe("LoadingState", () => {
71 it("renders with htmx-indicator class", async () => {
72 const app = new Hono().get("/", (c) => c.html(<LoadingState />));
73 const res = await app.request("/");
74 const html = await res.text();
75 expect(html).toContain("htmx-indicator");
76 });
77
78 it("renders default loading message", async () => {
79 const app = new Hono().get("/", (c) => c.html(<LoadingState />));
80 const res = await app.request("/");
81 const html = await res.text();
82 expect(html).toContain("Loading");
83 });
84
85 it("renders custom message when provided", async () => {
86 const app = new Hono().get("/", (c) =>
87 c.html(<LoadingState message="Fetching posts…" />)
88 );
89 const res = await app.request("/");
90 const html = await res.text();
91 expect(html).toContain("Fetching posts");
92 });
93});