package web import ( "bytes" "net/http" "net/http/httptest" "strings" "testing" ) func TestRoutes(t *testing.T) { srv, err := NewServer() if err != nil { t.Fatalf("NewServer: %v", err) } t.Run("index", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } if !strings.Contains(rr.Body.String(), "UI Design Code") { t.Fatalf("unexpected body: %s", rr.Body.String()) } }) t.Run("healthz", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/healthz", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } }) t.Run("static", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/static/css/app.css", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } }) t.Run("table pattern page", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/table", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "Canonical List Page") { t.Fatalf("missing table demo content in body") } if !strings.Contains(body, "Showing 1–10 of 36") { t.Fatalf("missing summary: %s", body) } }) t.Run("controls pattern page", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/controls?segment=warning", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "Selection Table") { t.Fatalf("missing controls page content") } }) t.Run("controls selection toggle persists in query links", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/controls?segment=all&selection_action=toggle&id=2", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "1 selected") { t.Fatalf("expected selected counter, got body") } if !strings.Contains(body, "sel=2") { t.Fatalf("expected selected id in follow-up links") } }) t.Run("controls select visible", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/controls?segment=warning&selection_action=select_visible", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "4 selected") { t.Fatalf("expected all visible selected") } }) t.Run("modal pattern page", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/modals?open=edit&stage=confirm&sel=1", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "Confirmation Summary") || !strings.Contains(body, "Single-modal workflow progression") { t.Fatalf("missing modal page markers") } }) t.Run("io pattern page", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/io?import_mode=confirm&export_ready=1", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "Import / Export Pattern") || !strings.Contains(body, "Download CSV") { t.Fatalf("missing io page markers") } }) t.Run("forms pattern page", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/forms?mode=register&step=review", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "Forms") || !strings.Contains(body, "Validation Pattern") || !strings.Contains(body, "Review Summary") { t.Fatalf("missing forms page markers") } if !strings.Contains(body, "Server serial is required") { t.Fatalf("expected validation message") } }) t.Run("style playground pattern page", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/style-playground?style=slate", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "Style Playground") || !strings.Contains(body, "Theme Presets") { t.Fatalf("missing style playground markers") } if !strings.Contains(body, "theme-slate") { t.Fatalf("expected theme class in body") } }) t.Run("style playground extra presets", func(t *testing.T) { for _, tc := range []struct { name string style string expectMark string }{ {name: "y2k silver", style: "y2k-silver", expectMark: "theme-y2k-silver"}, {name: "vaporwave alias", style: "vaporwave", expectMark: "theme-vaporwave"}, {name: "vaporwave soft", style: "vaporwave-soft", expectMark: "theme-vaporwave"}, {name: "vaporwave night", style: "vaporwave-night", expectMark: "theme-vaporwave-night"}, {name: "aqua", style: "aqua", expectMark: "theme-aqua"}, {name: "win9x", style: "win9x", expectMark: "theme-win9x"}, } { t.Run(tc.name, func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/style-playground?style="+tc.style, nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, tc.expectMark) { t.Fatalf("expected theme marker %q", tc.expectMark) } if !strings.Contains(body, "Y2K Silver") || !strings.Contains(body, "Vapor Soft") || !strings.Contains(body, "Vapor Night") || !strings.Contains(body, "Aqua") || !strings.Contains(body, "Win9x") { t.Fatalf("expected preset tabs in switcher") } }) } }) t.Run("operator tools pattern page", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/operator-tools?scope=components&queue=failed", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "Operator Tools Pattern") || !strings.Contains(body, "Operations Queue") { t.Fatalf("missing operator tools page markers") } }) t.Run("timeline pattern page", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/timeline?open=c1", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "Timeline Cards Pattern") || !strings.Contains(body, "Open details") { t.Fatalf("missing timeline page markers") } if !strings.Contains(body, "Event Detail") { t.Fatalf("expected drilldown panel when card is open") } }) t.Run("io export csv endpoint", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/io/export.csv?scope=selected", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } if got := rr.Header().Get("Content-Type"); !strings.Contains(got, "text/csv") { t.Fatalf("content-type=%q", got) } if got := rr.Header().Get("Content-Disposition"); !strings.Contains(got, "items.csv") { t.Fatalf("content-disposition=%q", got) } body := rr.Body.Bytes() if len(body) < 3 || !bytes.Equal(body[:3], []byte{0xEF, 0xBB, 0xBF}) { t.Fatalf("missing UTF-8 BOM") } if !strings.Contains(string(body), "Code;Name;Category;Status;Qty") { t.Fatalf("unexpected csv header") } }) t.Run("table pattern filters before pagination", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/table?q=Rack&page=99", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "Showing 1–3 of 3") { t.Fatalf("expected filtered summary, got: %s", body) } if !strings.Contains(body, "Rack Controller Alpha") { t.Fatalf("expected filtered rows") } if strings.Contains(body, "Component Spec 36") { t.Fatalf("unexpected unrelated row present") } }) t.Run("table pattern keeps filter params in pager links", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/table?status=ready", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "status=ready") { t.Fatalf("expected status filter in pagination links") } }) t.Run("table pattern empty result", func(t *testing.T) { rr := httptest.NewRecorder() req := httptest.NewRequest(http.MethodGet, "/patterns/table?q=does-not-exist", nil) srv.Handler().ServeHTTP(rr, req) if rr.Code != http.StatusOK { t.Fatalf("status=%d", rr.Code) } body := rr.Body.String() if !strings.Contains(body, "No rows match current filters.") { t.Fatalf("expected empty-state message") } if !strings.Contains(body, "Showing 0 of 0") { t.Fatalf("expected zero summary") } }) }