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

test(web): improve admin-themes test quality for GET /admin/themes/:rkey

- Extract MANAGE_THEMES constant to reduce repetition
- Rename setupAuth → setupAuthenticatedSession to match admin.test.tsx pattern
- Remove unnecessary fetch mock from unauthenticated test
- Strengthen CSS overrides assertion to require co-location via regex
- Add colorScheme and second token assertions to happy-path test
- Restore strict "Access Denied" assertion on 403 test
- Add ATB-62 reference to CSS overrides test description

+17 -16
+17 -16
apps/web/src/routes/__tests__/admin-themes.test.tsx
··· 3 3 const mockFetch = vi.fn(); 4 4 5 5 describe("createAdminThemeRoutes — GET /admin/themes/:rkey", () => { 6 + const MANAGE_THEMES = "space.atbb.permission.manageThemes"; 7 + 6 8 beforeEach(() => { 7 9 vi.stubGlobal("fetch", mockFetch); 8 10 vi.stubEnv("APPVIEW_URL", "http://localhost:3000"); ··· 29 31 * Call 1: GET /api/auth/session 30 32 * Call 2: GET /api/admin/members/me 31 33 */ 32 - function setupAuth(permissions: string[]) { 34 + function setupAuthenticatedSession(permissions: string[]) { 33 35 mockFetch.mockResolvedValueOnce( 34 36 mockResponse({ authenticated: true, did: "did:plc:forum", handle: "admin.bsky.social" }) 35 37 ); ··· 56 58 // ── Unauthenticated ────────────────────────────────────────────────────── 57 59 58 60 it("redirects unauthenticated users to /login", async () => { 59 - // No atbb_session cookie → session check returns unauthenticated 60 - mockFetch.mockResolvedValueOnce( 61 - mockResponse({ authenticated: false }) 62 - ); 61 + // No atbb_session cookie → getSession returns early without fetch 63 62 const routes = await loadThemeRoutes(); 64 63 const res = await routes.request("/admin/themes/abc123"); 65 64 expect(res.status).toBe(302); ··· 69 68 // ── No manageThemes permission → 403 ──────────────────────────────────── 70 69 71 70 it("returns 403 for users without manageThemes permission", async () => { 72 - setupAuth([]); 71 + setupAuthenticatedSession([]); 73 72 const routes = await loadThemeRoutes(); 74 73 const res = await routes.request("/admin/themes/abc123", { 75 74 headers: { cookie: "atbb_session=token" }, 76 75 }); 77 76 expect(res.status).toBe(403); 78 77 const html = await res.text(); 79 - expect(html.toLowerCase()).toMatch(/access denied|permission/); 78 + expect(html).toContain("Access Denied"); 80 79 }); 81 80 82 81 // ── Theme not found → 404 ──────────────────────────────────────────────── 83 82 84 83 it("returns 404 when theme not found", async () => { 85 - setupAuth(["space.atbb.permission.manageThemes"]); 84 + setupAuthenticatedSession([MANAGE_THEMES]); 86 85 // Third fetch: AppView returns 404 87 86 mockFetch.mockResolvedValueOnce( 88 87 mockResponse({ error: "Theme not found" }, false, 404) ··· 97 96 // ── Happy path: renders editor with theme tokens ────────────────────────── 98 97 99 98 it("renders editor with theme name and token inputs", async () => { 100 - setupAuth(["space.atbb.permission.manageThemes"]); 99 + setupAuthenticatedSession([MANAGE_THEMES]); 101 100 mockFetch.mockResolvedValueOnce(mockResponse(sampleTheme)); 102 101 const routes = await loadThemeRoutes(); 103 102 const res = await routes.request("/admin/themes/abc123", { ··· 106 105 expect(res.status).toBe(200); 107 106 const html = await res.text(); 108 107 expect(html).toContain("My Theme"); 108 + expect(html).toContain('value="light"'); 109 109 expect(html).toContain('name="color-bg"'); 110 110 expect(html).toContain("#f5f0e8"); 111 + expect(html).toContain("#1a1a1a"); 111 112 }); 112 113 113 114 // ── Preset override ─────────────────────────────────────────────────────── 114 115 115 116 it("uses preset tokens when ?preset=neobrutal-light is present", async () => { 116 - setupAuth(["space.atbb.permission.manageThemes"]); 117 + setupAuthenticatedSession([MANAGE_THEMES]); 117 118 // Theme has empty tokens — preset should fill them in 118 119 mockFetch.mockResolvedValueOnce( 119 120 mockResponse({ ...sampleTheme, tokens: {} }) ··· 131 132 // ── Success banner ──────────────────────────────────────────────────────── 132 133 133 134 it("shows success banner when ?success=1 is present", async () => { 134 - setupAuth(["space.atbb.permission.manageThemes"]); 135 + setupAuthenticatedSession([MANAGE_THEMES]); 135 136 mockFetch.mockResolvedValueOnce(mockResponse(sampleTheme)); 136 137 const routes = await loadThemeRoutes(); 137 138 const res = await routes.request("/admin/themes/abc123?success=1", { ··· 145 146 // ── Error banner ────────────────────────────────────────────────────────── 146 147 147 148 it("shows error banner when ?error= is present", async () => { 148 - setupAuth(["space.atbb.permission.manageThemes"]); 149 + setupAuthenticatedSession([MANAGE_THEMES]); 149 150 mockFetch.mockResolvedValueOnce(mockResponse(sampleTheme)); 150 151 const routes = await loadThemeRoutes(); 151 152 const res = await routes.request( ··· 159 160 160 161 // ── CSS overrides field is disabled ───────────────────────────────────── 161 162 162 - it("renders CSS overrides field as disabled", async () => { 163 - setupAuth(["space.atbb.permission.manageThemes"]); 163 + it("renders CSS overrides field as disabled (awaiting ATB-62)", async () => { 164 + setupAuthenticatedSession([MANAGE_THEMES]); 164 165 mockFetch.mockResolvedValueOnce(mockResponse(sampleTheme)); 165 166 const routes = await loadThemeRoutes(); 166 167 const res = await routes.request("/admin/themes/abc123", { ··· 168 169 }); 169 170 expect(res.status).toBe(200); 170 171 const html = await res.text(); 171 - expect(html).toContain("css-overrides"); 172 - expect(html).toContain("disabled"); 172 + // Both attributes must be on the same element 173 + expect(html).toMatch(/name="css-overrides"[^>]*disabled|disabled[^>]*name="css-overrides"/); 173 174 }); 174 175 });