Files
BountyBoard/internal/http/web_test.go
T
etalon c59aea42ef feat: Y2K dither theme, filter toolbars, chat bubble widget; fix alignment root causes
- theme rework (user request): white paper bg, brown ink, ordered-dither
  halftone textures, hard offset Y2K shadows, dithered masthead bands;
  dark theme matched; token test + DECISIONS updated
- fix: .card + .card stacking margin leaked into grid layouts, shifting
  every card except the first (the 'always the first item' reports)
- fix: CSP style-src 'self' silently dropped every inline style attribute
  (misaligned save button, stat values, editor attach button, bell badge);
  styles now allow inline, scripts remain strict per §12
- fix: [hidden] is now display:none !important so flex containers cannot
  defeat it (chat panel/footers)
- board + metrics filters live in boxed .toolbar rows with baseline-aligned
  controls; metric stat cards use a uniform .stat layout
- new-conversation dialog: fixed 440px width and 180px results list (no
  more resizing while searching), full-width result rows, picked people
  drop out of the list
- floating messages bubble bottom-right on all pages (except /messages):
  unread badge, mini panel with conversation list, thread view, quick
  composer, live WS updates; toasts moved up to clear it

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-12 21:54:27 +02:00

111 lines
3.0 KiB
Go

package httpx
import (
"net/http"
"strings"
"testing"
)
func TestLoginPageRenders(t *testing.T) {
s := newTestServer(t)
rec := get(t, s.Handler(), "/login")
if rec.Code != http.StatusOK {
t.Fatalf("status = %d", rec.Code)
}
body := rec.Body.String()
for _, want := range []string{"login-form", `id="email"`, `id="password"`, "/static/js/login.js"} {
if !strings.Contains(body, want) {
t.Errorf("login page missing %q", want)
}
}
// OIDC is not configured → no SSO button
if strings.Contains(body, "Continue with SSO") {
t.Error("SSO button shown although OIDC is disabled")
}
if csp := rec.Header().Get("Content-Security-Policy"); !strings.Contains(csp, "script-src 'self'") {
t.Errorf("CSP missing or weak: %q", csp)
}
if rec.Header().Get("X-Frame-Options") != "DENY" {
t.Error("X-Frame-Options missing")
}
}
func TestRegisterPageRenders(t *testing.T) {
s := newTestServer(t)
rec := get(t, s.Handler(), "/register")
if rec.Code != http.StatusOK {
t.Fatalf("status = %d", rec.Code)
}
if !strings.Contains(rec.Body.String(), "register-form") {
t.Error("register form missing")
}
}
func TestAnonymousPageRedirectsToLogin(t *testing.T) {
s := newTestServer(t)
for _, path := range []string{"/", "/profile", "/board", "/admin", "/messages"} {
rec := get(t, s.Handler(), path)
if rec.Code != http.StatusFound {
t.Errorf("%s: status = %d, want 302", path, rec.Code)
continue
}
loc := rec.Header().Get("Location")
if !strings.HasPrefix(loc, "/login?next=") {
t.Errorf("%s: redirect to %q", path, loc)
}
}
}
func TestStaticAssetsServed(t *testing.T) {
s := newTestServer(t)
tests := map[string]string{
"/static/css/app.css": "--bg:#ffffff", // Y2K white-paper light theme
"/static/js/theme.js": "data-default-theme",
"/static/js/api.js": "X-CSRF-Token",
"/static/js/login.js": "auth/login",
"/static/js/nav.js": "nav-theme",
}
for path, want := range tests {
rec := get(t, s.Handler(), path)
if rec.Code != http.StatusOK {
t.Errorf("%s: status = %d", path, rec.Code)
continue
}
if !strings.Contains(rec.Body.String(), want) {
t.Errorf("%s: missing %q", path, want)
}
}
}
func TestThemeTokensPresent(t *testing.T) {
s := newTestServer(t)
rec := get(t, s.Handler(), "/static/css/app.css")
css := rec.Body.String()
// spot-check both palettes (Y2K rework, user-requested deviation from
// the §10 beige — see DECISIONS.md) and the square-edge radius
for _, tok := range []string{
"--bg:#ffffff", "--accent:#7a4a14", // light: white paper, brown ink
"--bg:#171209", "--accent:#d9a548", // dark
"--radius:2px",
} {
if !strings.Contains(css, tok) {
t.Errorf("css missing design token %q", tok)
}
}
}
func TestInitialsFunc(t *testing.T) {
tests := []struct{ in, want string }{
{"Jane Doe", "JD"},
{"single", "S"},
{"", "?"},
{" spaced out ", "SO"},
}
f := templateFuncs["initials"].(func(string) string)
for _, tt := range tests {
if got := f(tt.in); got != tt.want {
t.Errorf("initials(%q) = %q, want %q", tt.in, got, tt.want)
}
}
}