Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
Blame · Line-by-line history

system-states.test.ts

Each line is annotated with the commit that last touched it. Click any SHA to jump to that commit and see the surrounding change.

system-states.test.tsBlame283 lines · 1 contributor
c63b860Claude1/**
2 * BLOCK O2 — Every system state polished.
3 *
4 * Covers the four polished surfaces:
5 * 1. Error pages (404 / 500 / 403)
6 * 2. Empty state component
7 * 3. Skeleton component
8 * 4. Form validation script
9 *
10 * No mock pollution — each test renders the real component to JSX
11 * string output (or hits the real Hono router) and asserts on the
12 * rendered HTML.
13 */
14
15import { describe, it, expect } from "bun:test";
16import app from "../app";
17import {
18 ErrorPage,
19 NotFoundPage,
20 ServerErrorPage,
21 ForbiddenPage,
22 renderStandaloneErrorPage,
23} from "../views/error-page";
24import { EmptyState } from "../views/empty-state";
25import {
26 Skeleton,
27 SkeletonList,
28 SkeletonRepoRow,
29} from "../views/skeleton";
30import {
31 formValidationScript,
32 formValidationCss,
33 FormValidationAssets,
34} from "../views/form-validation-js";
35
36// Render a JSX element to its HTML string via Hono's JSX runtime.
37async function renderJsx(node: any): Promise<string> {
38 if (node && typeof node === "object" && "toString" in node) {
39 const s = await Promise.resolve(node.toString());
40 return typeof s === "string" ? s : String(s);
41 }
42 return String(node);
43}
44
45describe("BLOCK O2 — Error pages", () => {
46 it("GET unknown deep path returns the polished 404 page via app.notFound", async () => {
47 // A path with 4+ segments doesn't match any defined route — falls
48 // through to app.notFound. (`/:owner` would otherwise swallow a
49 // single-segment 404 with a 200 user-profile shell on DB miss.)
50 const res = await app.request("/__o2_unknown__/a/b/c/d");
51 expect(res.status).toBe(404);
52 const body = await res.text();
53 // Big "404" gradient text
54 expect(body).toContain("data-error-code=\"404\"");
55 expect(body).toContain(">404<");
56 expect(body).toContain("gradient-text");
57 // Two CTAs (primary + secondary)
58 expect(body).toMatch(/data-error-cta="primary"/);
59 expect(body).toMatch(/data-error-cta="secondary"/);
60 expect(body).toContain("Go home");
61 expect(body).toContain("Status page");
62 // Subhead/body
63 expect(body).toContain("Not found");
64 });
65
66 it("ServerErrorPage component renders the 500 + request ID line", async () => {
67 const node = ServerErrorPage({
68 user: null,
69 requestId: "req-test-abc-123",
70 } as any);
71 const body = await renderJsx(node);
72 expect(body).toContain(">500<");
73 expect(body).toContain("Server error");
74 expect(body).toContain("Request ID:");
75 expect(body).toContain("req-test-abc-123");
76 // Two CTAs
77 expect(body).toMatch(/data-error-cta="primary"/);
78 expect(body).toMatch(/data-error-cta="secondary"/);
79 });
80
81 it("ForbiddenPage component renders the polished 403", async () => {
82 const node = ForbiddenPage({ user: null } as any);
83 const body = await renderJsx(node);
84 expect(body).toContain(">403<");
85 expect(body).toContain("Forbidden");
86 expect(body).toContain("Admin access required");
87 // Signed-out path → "Sign in" suggestion
88 expect(body).toContain("Sign in");
89 });
90
91 it("ForbiddenPage when signed in offers 'sign in as different user'", async () => {
92 const fakeUser: any = { id: "u1", username: "alice" };
93 const node = ForbiddenPage({ user: fakeUser } as any);
94 const body = await renderJsx(node);
95 expect(body).toContain("Sign in as a different user");
96 expect(body).toContain("/logout");
97 });
98
99 it("renderStandaloneErrorPage returns a complete <!doctype html> document", () => {
100 const html = renderStandaloneErrorPage({
101 code: "403",
102 eyebrow: "Forbidden",
103 title: "Admin access required.",
104 body: "Body copy.",
105 signedIn: true,
106 });
107 expect(html.toLowerCase()).toContain("<!doctype html>");
108 expect(html).toContain("data-error-code=\"403\"");
109 expect(html).toContain("Admin access required.");
110 expect(html).toContain("Body copy.");
111 // Self-contained — has its own <style> + gradient CSS for the code.
112 expect(html).toContain("background-clip:text");
113 // Signed-in CTA
114 expect(html).toContain("Sign in as a different user");
115 });
116
117 it("ErrorPage component honours a custom Request ID + trace", async () => {
118 const node = ErrorPage({
119 code: "500",
120 eyebrow: "Server error",
121 title: "Boom.",
122 body: "Body.",
123 requestId: "req-xyz",
124 trace: "Error: synthetic\n at test",
125 } as any);
126 const body = await renderJsx(node);
127 expect(body).toContain("req-xyz");
128 expect(body).toContain("error-page-trace");
129 expect(body).toContain("Error: synthetic");
130 });
131});
132
133describe("BLOCK O2 — EmptyState component", () => {
134 it("renders title, body and both CTAs", async () => {
135 const node = EmptyState({
136 icon: "🐛",
137 title: "No issues yet",
138 body: "When you or your team file issues, they'll appear here.",
139 primaryCta: { href: "/new", label: "Open the first issue" },
140 secondaryCta: { href: "/closed", label: "View closed" },
141 } as any);
142 const body = await renderJsx(node);
143 expect(body).toContain("No issues yet");
144 expect(body).toContain("When you or your team file issues");
145 expect(body).toContain("Open the first issue");
146 expect(body).toContain("View closed");
147 expect(body).toContain("/new");
148 expect(body).toContain("/closed");
149 // The default icon SVG isn't used here because we passed an emoji.
150 expect(body).toContain("🐛");
151 });
152
153 it("uses the default gradient SVG when icon is omitted", async () => {
154 const node = EmptyState({
155 title: "Nothing here",
156 body: "Body.",
157 } as any);
158 const body = await renderJsx(node);
159 // SVG gradient block is inlined.
160 expect(body).toContain("linearGradient");
161 expect(body).toContain("#8c6dff");
162 expect(body).toContain("#36c5d6");
163 });
164
165 it("renders without CTAs when none are provided", async () => {
166 const node = EmptyState({
167 title: "Quiet",
168 body: "Nothing to do.",
169 } as any);
170 const body = await renderJsx(node);
171 expect(body).toContain("Quiet");
172 // No <a class="btn"> markers
173 expect(body).not.toContain('class="btn btn-primary"');
174 });
175});
176
177describe("BLOCK O2 — Skeleton component", () => {
178 it("renders the requested number of bars", async () => {
179 const node = Skeleton({ count: 5, height: "14px" } as any);
180 const body = await renderJsx(node);
181 // 5 skeleton bars
182 const matches = body.match(/class="skeleton-bar"/g) || [];
183 expect(matches.length).toBe(5);
184 // height inline
185 expect(body).toContain("height:14px");
186 });
187
188 it("defaults to one bar", async () => {
189 const node = Skeleton({} as any);
190 const body = await renderJsx(node);
191 const matches = body.match(/class="skeleton-bar"/g) || [];
192 expect(matches.length).toBe(1);
193 });
194
195 it("SkeletonRepoRow renders an avatar circle and two text bars", async () => {
196 const node = SkeletonRepoRow({} as any);
197 const body = await renderJsx(node);
198 expect(body).toContain("border-radius:50%");
199 const matches = body.match(/class="skeleton-bar"/g) || [];
200 expect(matches.length).toBe(3); // avatar + 2 text bars
201 });
202
203 it("SkeletonList renders N rep rows with aria-busy", async () => {
204 const node = SkeletonList({ count: 3 } as any);
205 const body = await renderJsx(node);
206 const rows = body.match(/class="skeleton-repo-row"/g) || [];
207 expect(rows.length).toBe(3);
208 expect(body).toContain('aria-busy="true"');
209 expect(body).toContain('role="status"');
210 });
211
212 it("emits the @keyframes skeleton-shimmer animation", async () => {
213 const node = Skeleton({} as any);
214 const body = await renderJsx(node);
215 expect(body).toContain("@keyframes skeleton-shimmer");
216 expect(body).toContain("background-position: 200% 0");
217 });
218});
219
220describe("BLOCK O2 — formValidationScript", () => {
221 it("is well-formed JS (parseable by the engine)", () => {
222 // `new Function` throws SyntaxError on malformed JS.
223 expect(() => {
224 new Function(formValidationScript);
225 }).not.toThrow();
226 });
227
228 it("contains the expected event handlers", () => {
229 expect(formValidationScript).toContain('addEventListener("blur"');
230 expect(formValidationScript).toContain('addEventListener("input"');
231 expect(formValidationScript).toContain("__gluecronFormValidationMounted");
232 // Guards against double-mount.
233 expect(formValidationScript).toContain("if (window.__gluecronFormValidationMounted) return");
234 });
235
236 it("validates required / email / pattern / minlength / maxlength", () => {
237 expect(formValidationScript).toContain('hasAttribute("required")');
238 expect(formValidationScript).toContain('el.type === "email"');
239 expect(formValidationScript).toContain('getAttribute("pattern")');
240 expect(formValidationScript).toContain('getAttribute("minlength")');
241 expect(formValidationScript).toContain('getAttribute("maxlength")');
242 });
243
244 it("respects data-validation-message overrides", () => {
245 expect(formValidationScript).toContain('getAttribute("data-validation-message")');
246 expect(formValidationScript).toContain('getAttribute("data-validation-required")');
247 });
248
249 it("formValidationCss exposes the .input-valid / .input-invalid / .field-error styles", () => {
250 expect(formValidationCss).toContain(".input-invalid");
251 expect(formValidationCss).toContain(".input-valid");
252 expect(formValidationCss).toContain(".field-error");
253 // SVG checkmark for green-state confirmation
254 expect(formValidationCss).toContain("data:image/svg+xml");
255 });
256
257 it("FormValidationAssets emits both style and script tags", async () => {
258 const node = FormValidationAssets({} as any);
259 const body = await renderJsx(node);
260 expect(body).toContain("<style");
261 expect(body).toContain("<script");
262 expect(body).toContain("input-invalid");
263 expect(body).toContain("__gluecronFormValidationMounted");
264 });
265});
266
267describe("BLOCK O2 — NotFoundPage component (unit, no HTTP)", () => {
268 it("renders the 404 number, both CTAs, and the suggestion list", async () => {
269 const node = NotFoundPage({ user: null, method: "GET", path: "/foo" } as any);
270 const body = await renderJsx(node);
271 expect(body).toContain(">404<");
272 expect(body).toContain("Not found");
273 expect(body).toContain("We can&#39;t find that page");
274 // Two CTAs
275 expect(body).toMatch(/data-error-cta="primary"/);
276 expect(body).toMatch(/data-error-cta="secondary"/);
277 // Method/path meta
278 expect(body).toContain("GET /foo");
279 // Suggestion list
280 expect(body).toContain("Explore public repositories");
281 expect(body).toContain("Read the quickstart guide");
282 });
283});