package web_test import ( "io" "net/http" "net/http/httptest" "strings" "testing" ) // TestThemeDefaultIsDark proves the layout boots into dark mode when no // projax_theme cookie is present — that is Phase 4b's stated default. func TestThemeDefaultIsDark(t *testing.T) { srv, pool := mustServer(t) defer pool.Close() h := srv.Routes() code, body := get(t, h, "/") if code != 200 { t.Fatalf("GET / → %d", code) } for _, want := range []string{ ``, ``, `id="theme-toggle"`, } { if !strings.Contains(body, want) { t.Errorf("default-theme page missing %q", want) } } // The toggle's icon for dark = sun glyph (☀) so the click implies "switch // to light". Cross-check that the inverse glyph (moon ☾) is absent in // dark mode so we don't accidentally render both. if !strings.Contains(body, "☀") { t.Errorf("dark mode should show ☀ glyph (action = flip to light), got body lacking it") } } // TestThemeCookieRoundTrips: setting projax_theme=light makes the next // render flip and the apple theme-color meta to // the light palette colour. func TestThemeCookieRoundTrips(t *testing.T) { srv, pool := mustServer(t) defer pool.Close() h := srv.Routes() req := httptest.NewRequest(http.MethodGet, "/", nil) req.AddCookie(&http.Cookie{Name: "projax_theme", Value: "light"}) w := httptest.NewRecorder() h.ServeHTTP(w, req) body, _ := io.ReadAll(w.Result().Body) bodyStr := string(body) if !strings.Contains(bodyStr, `data-theme="light"`) { t.Errorf("expected data-theme=light when cookie set, got body:\n%s", bodyStr[:400]) } if !strings.Contains(bodyStr, ``) { t.Errorf("light mode should set apple theme-color to light palette bg-alt") } // Sanity: the dark colour is NOT present anywhere in the resulting meta. if strings.Contains(bodyStr, ``) { t.Errorf("light render leaked the dark theme-color meta") } } // TestThemeCookieUnknownFallsBackToDark proves we don't break when a stale // or hand-typed cookie value sneaks in. func TestThemeCookieUnknownFallsBackToDark(t *testing.T) { srv, pool := mustServer(t) defer pool.Close() h := srv.Routes() req := httptest.NewRequest(http.MethodGet, "/", nil) req.AddCookie(&http.Cookie{Name: "projax_theme", Value: "neon-puke"}) w := httptest.NewRecorder() h.ServeHTTP(w, req) body, _ := io.ReadAll(w.Result().Body) if !strings.Contains(string(body), `data-theme="dark"`) { t.Errorf("unknown cookie value should fall back to dark") } } // TestThemeTogglePagesShareSameTheme: every chrome-bearing page renders the // same . Without that guarantee m would see the theme // flicker as he navigated between pages. func TestThemeTogglePagesShareSameTheme(t *testing.T) { srv, pool := mustServer(t) defer pool.Close() h := srv.Routes() probe := func(path, cookieValue string) string { t.Helper() req := httptest.NewRequest(http.MethodGet, path, nil) if cookieValue != "" { req.AddCookie(&http.Cookie{Name: "projax_theme", Value: cookieValue}) } w := httptest.NewRecorder() h.ServeHTTP(w, req) body, _ := io.ReadAll(w.Result().Body) return string(body) } for _, path := range []string{"/", "/dashboard", "/timeline", "/graph", "/admin", "/admin/bulk", "/admin/classify"} { dark := probe(path, "") light := probe(path, "light") if !strings.Contains(dark, `data-theme="dark"`) { t.Errorf("GET %s default should render data-theme=dark", path) } if !strings.Contains(light, `data-theme="light"`) { t.Errorf("GET %s with cookie=light should render data-theme=light", path) } } } // TestThemeToggleScriptPresent: the inline JS that writes the cookie must // ship with every layout-bearing page so the toggle button is functional. func TestThemeToggleScriptPresent(t *testing.T) { srv, pool := mustServer(t) defer pool.Close() h := srv.Routes() _, body := get(t, h, "/dashboard") for _, want := range []string{ "document.cookie = 'projax_theme=", `getElementById('theme-toggle')`, "SameSite=Lax", } { if !strings.Contains(body, want) { t.Errorf("dashboard page missing theme-toggle script piece %q", want) } } } // TestThemeColorMetaHelper covers the small server-side helper directly. // Belongs in theme_test.go even though it's a unit test — it lives next to // the integration tests so a future hand can flip the palette in one file. func TestThemeColorMetaHelper(t *testing.T) { srv, pool := mustServer(t) defer pool.Close() // Indirect: render a fragment with a Theme override to confirm injection // does not double-write the meta when caller already populates it. req := httptest.NewRequest(http.MethodGet, "/dashboard", nil) req.AddCookie(&http.Cookie{Name: "projax_theme", Value: "light"}) w := httptest.NewRecorder() srv.Routes().ServeHTTP(w, req) body, _ := io.ReadAll(w.Result().Body) // Count actual tags, not the JS selector reference in the inline script. if c := strings.Count(string(body), ` tag, got %d", c) } }