package web_test import ( "context" "io" "net/http" "net/http/httptest" "strings" "testing" "time" ) // TestGraphPageRenders proves GET /graph returns an SVG containing every // seeded root + the filter chip strip and the legend. func TestGraphPageRenders(t *testing.T) { srv, pool := mustServer(t) defer pool.Close() h := srv.Routes() code, body := get(t, h, "/views/graph") if code != 200 { t.Fatalf("GET /graph → %d body=%s", code, body) } for _, want := range []string{ `work<") || !strings.Contains(body, ">dev<") { t.Errorf("expected dim-mode to keep every node visible (root nodes missing)") } } // TestGraphIsolateHidesNonMatching: ?isolate=1 + a filter should remove // non-matching nodes from the rendered SVG. func TestGraphIsolateHidesNonMatching(t *testing.T) { srv, pool := mustServer(t) defer pool.Close() h := srv.Routes() ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() // Seed a unique tag on one item so the filter has a known target. stamp := strings.ReplaceAll(time.Now().UTC().Format("150405.000000"), ".", "") slug := "graph-iso-" + stamp tag := "graphiso" + stamp var dev string if err := pool.QueryRow(ctx, `select id from projax.items where slug='dev' and cardinality(parent_ids)=0`).Scan(&dev); err != nil { t.Fatalf("dev: %v", err) } var id string if err := pool.QueryRow(ctx, `insert into projax.items (kind, title, slug, parent_ids, tags) values (array['project']::text[], 'iso', $1, ARRAY[$2]::uuid[], ARRAY[$3]::text[]) returning id`, slug, dev, tag, ).Scan(&id); err != nil { t.Fatalf("seed: %v", err) } defer pool.Exec(context.Background(), `delete from projax.items where id=$1`, id) code, body := get(t, h, "/views/graph?tag="+tag+"&isolate=1") if code != 200 { t.Fatalf("GET /graph?isolate → %d", code) } if !strings.Contains(body, ">"+slug+"<") { t.Errorf("expected isolated slug %q in body", slug) } // A seeded root that does NOT carry this tag must be hidden. if strings.Contains(body, ">finances<") { t.Errorf("isolate=1 should hide non-matching nodes — 'finances' still rendered") } } // TestGraphSVGDownload: ?download=svg returns the raw SVG (no HTML chrome) // with the right Content-Type + attachment header. func TestGraphSVGDownload(t *testing.T) { srv, pool := mustServer(t) defer pool.Close() h := srv.Routes() req := httptest.NewRequest(http.MethodGet, "/views/graph?download=svg", nil) w := httptest.NewRecorder() h.ServeHTTP(w, req) if w.Result().StatusCode != 200 { t.Fatalf("GET /graph?download=svg → %d", w.Result().StatusCode) } if ct := w.Result().Header.Get("Content-Type"); !strings.HasPrefix(ct, "image/svg+xml") { t.Errorf("Content-Type = %q, want image/svg+xml", ct) } if cd := w.Result().Header.Get("Content-Disposition"); !strings.Contains(cd, "attachment") { t.Errorf("Content-Disposition = %q, want attachment", cd) } bodyBytes, _ := io.ReadAll(w.Result().Body) body := string(bodyBytes) if !strings.HasPrefix(strings.TrimSpace(body), "