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: resolveUserThemePreference unit and resolveTheme integration tests

+132
+132
apps/web/src/lib/__tests__/theme-resolution.test.ts
··· 2 2 import { 3 3 detectColorScheme, 4 4 parseRkeyFromUri, 5 + resolveUserThemePreference, 5 6 FALLBACK_THEME, 6 7 fallbackForScheme, 7 8 resolveTheme, ··· 62 63 63 64 it("returns null for empty string", () => { 64 65 expect(parseRkeyFromUri("")).toBeNull(); 66 + }); 67 + }); 68 + 69 + describe("resolveUserThemePreference", () => { 70 + const availableThemes = [ 71 + { uri: "at://did:plc:forum/space.atbb.forum.theme/3lbllight" }, 72 + { uri: "at://did:plc:forum/space.atbb.forum.theme/3lbldark" }, 73 + ]; 74 + 75 + it("returns null when allowUserChoice is false", () => { 76 + const result = resolveUserThemePreference( 77 + "atbb-light-theme=at://did:plc:forum/space.atbb.forum.theme/3lbllight", 78 + "light", 79 + availableThemes, 80 + false 81 + ); 82 + expect(result).toBeNull(); 83 + }); 84 + 85 + it("returns atbb-light-theme URI when cookie matches and is in availableThemes", () => { 86 + const result = resolveUserThemePreference( 87 + "atbb-light-theme=at://did:plc:forum/space.atbb.forum.theme/3lbllight", 88 + "light", 89 + availableThemes, 90 + true 91 + ); 92 + expect(result).toBe("at://did:plc:forum/space.atbb.forum.theme/3lbllight"); 93 + }); 94 + 95 + it("returns atbb-dark-theme URI when cookie matches and is in availableThemes", () => { 96 + const result = resolveUserThemePreference( 97 + "atbb-dark-theme=at://did:plc:forum/space.atbb.forum.theme/3lbldark", 98 + "dark", 99 + availableThemes, 100 + true 101 + ); 102 + expect(result).toBe("at://did:plc:forum/space.atbb.forum.theme/3lbldark"); 103 + }); 104 + 105 + it("returns null when cookie URI is not in availableThemes (stale/removed)", () => { 106 + const result = resolveUserThemePreference( 107 + "atbb-light-theme=at://did:plc:forum/space.atbb.forum.theme/stale", 108 + "light", 109 + availableThemes, 110 + true 111 + ); 112 + expect(result).toBeNull(); 113 + }); 114 + 115 + it("returns null when cookieHeader is undefined", () => { 116 + const result = resolveUserThemePreference( 117 + undefined, 118 + "light", 119 + availableThemes, 120 + true 121 + ); 122 + expect(result).toBeNull(); 123 + }); 124 + 125 + it("returns null when cookie value is empty string after cookie name", () => { 126 + const result = resolveUserThemePreference( 127 + "atbb-light-theme=", 128 + "light", 129 + availableThemes, 130 + true 131 + ); 132 + expect(result).toBeNull(); 133 + }); 134 + 135 + it("does not match x-atbb-light-theme as a cookie prefix", () => { 136 + const result = resolveUserThemePreference( 137 + "x-atbb-light-theme=at://did:plc:forum/space.atbb.forum.theme/3lbllight", 138 + "light", 139 + availableThemes, 140 + true 141 + ); 142 + expect(result).toBeNull(); 65 143 }); 66 144 }); 67 145 ··· 377 455 expect.stringContaining("CID mismatch"), 378 456 expect.any(Object) 379 457 ); 458 + }); 459 + 460 + it("resolves light preference cookie when URI is in availableThemes", async () => { 461 + mockFetch 462 + .mockResolvedValueOnce(policyResponse()) 463 + .mockResolvedValueOnce(themeResponse("light", "bafylight")); 464 + 465 + const cookieHeader = "atbb-light-theme=at://did:plc:forum/space.atbb.forum.theme/3lbllight"; 466 + const result = await resolveTheme(APPVIEW, cookieHeader, undefined); 467 + 468 + expect(result.tokens["color-bg"]).toBe("#fff"); 469 + expect(result.colorScheme).toBe("light"); 470 + // Verify that the user's theme was fetched (rkey 3lbllight) not the forum default 471 + expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("3lbllight")); 472 + }); 473 + 474 + it("resolves dark preference cookie when URI is in availableThemes", async () => { 475 + mockFetch 476 + .mockResolvedValueOnce(policyResponse()) 477 + .mockResolvedValueOnce(themeResponse("dark", "bafydark")); 478 + 479 + const cookieHeader = "atbb-color-scheme=dark; atbb-dark-theme=at://did:plc:forum/space.atbb.forum.theme/3lbldark"; 480 + const result = await resolveTheme(APPVIEW, cookieHeader, undefined); 481 + 482 + expect(result.tokens["color-bg"]).toBe("#111"); 483 + expect(result.colorScheme).toBe("dark"); 484 + // Verify that the user's theme was fetched (rkey 3lbldark) 485 + expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("3lbldark")); 486 + }); 487 + 488 + it("falls back to forum default when preference cookie URI is not in availableThemes", async () => { 489 + mockFetch 490 + .mockResolvedValueOnce(policyResponse()) 491 + .mockResolvedValueOnce(themeResponse("light", "bafylight")); 492 + 493 + const cookieHeader = "atbb-color-scheme=light; atbb-light-theme=at://did:plc:forum/space.atbb.forum.theme/stale-uri"; 494 + const result = await resolveTheme(APPVIEW, cookieHeader, undefined); 495 + 496 + // Preference URI is stale, so forum default is used 497 + expect(result.tokens["color-bg"]).toBe("#fff"); 498 + expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("3lbllight")); // forum default rkey 499 + }); 500 + 501 + it("ignores preference cookie when policy has allowUserChoice: false", async () => { 502 + mockFetch 503 + .mockResolvedValueOnce(policyResponse({ allowUserChoice: false })) 504 + .mockResolvedValueOnce(themeResponse("light", "bafylight")); 505 + 506 + const cookieHeader = "atbb-color-scheme=light; atbb-light-theme=at://did:plc:forum/space.atbb.forum.theme/stale"; 507 + const result = await resolveTheme(APPVIEW, cookieHeader, undefined); 508 + 509 + // User choice is disabled, so forum default is used even though cookie is set 510 + expect(result.tokens["color-bg"]).toBe("#fff"); 511 + expect(mockFetch).toHaveBeenCalledWith(expect.stringContaining("3lbllight")); // forum default rkey 380 512 }); 381 513 }); 382 514