package web import ( "context" "strings" "testing" "git.d-ma.be/mathias/tapir/internal/adapters/store" ) func renderVideoCard(t *testing.T, r store.SummaryRow) string { t.Helper() var sb strings.Builder if err := VideoCard(r).Render(context.Background(), &sb); err != nil { t.Fatalf("render VideoCard: %v", err) } return sb.String() } // noActionButton asserts the rendered HTML contains no form POST (no action URL // of the form /v/.../summarize or /v/.../retry-now) and no btn-quiet button. func noActionButton(t *testing.T, html, state string) { t.Helper() if strings.Contains(html, "/summarize") { t.Errorf("state %q: expected no summarize URL, got:\n%s", state, html) } if strings.Contains(html, "/retry-now") { t.Errorf("state %q: expected no retry-now URL, got:\n%s", state, html) } if strings.Contains(html, "btn-quiet") { t.Errorf("state %q: expected no action button, got:\n%s", state, html) } } // TestVideoCard_State1_Summarized — chip + no nudge button. func TestVideoCard_State1_Summarized(t *testing.T) { html := renderVideoCard(t, store.SummaryRow{ VideoID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", Title: "Done Video", Summarized: true, Summary: "A great talk about Go.", AIProvider: "local", }) if !strings.Contains(html, "local") { t.Errorf("state 1: expected AI provider chip, got:\n%s", html) } noActionButton(t, html, "summarized") if strings.Contains(html, "Summarize") { t.Errorf("state 1: no nudge button on a summarized card, got:\n%s", html) } } // TestVideoCard_State2_NoTranscript — terminal; muted status, NO button, NO POST URL. func TestVideoCard_State2_NoTranscript(t *testing.T) { html := renderVideoCard(t, store.SummaryRow{ VideoID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", Title: "Silent Video", Summarized: false, TranscriptStatus: "none", }) if !strings.Contains(html, "No transcript available") { t.Errorf("state 2: expected 'No transcript available' text, got:\n%s", html) } noActionButton(t, html, "none-transcript") if strings.Contains(html, "Summarize") { t.Errorf("state 2: no nudge button when there are no captions, got:\n%s", html) } } // TestVideoCard_State3_Queued — "Queued" chip, no button. func TestVideoCard_State3_Queued(t *testing.T) { html := renderVideoCard(t, store.SummaryRow{ VideoID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", Title: "Queued Video", Summarized: false, SummarizeRequested: true, }) if !strings.Contains(html, "Queued") { t.Errorf("state 3: expected 'Queued' chip, got:\n%s", html) } noActionButton(t, html, "queued") } // TestVideoCard_State4_RateLimited — quiet status + "Summarize" → retry-now URL. func TestVideoCard_State4_RateLimited(t *testing.T) { html := renderVideoCard(t, store.SummaryRow{ VideoID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", Title: "Throttled Video", Summarized: false, TranscriptStatus: "rate_limited", }) if !strings.Contains(html, "In queue") { t.Errorf("state 4: expected 'In queue' status text, got:\n%s", html) } if !strings.Contains(html, "Summarize") { t.Errorf("state 4: expected 'Summarize' button, got:\n%s", html) } if !strings.Contains(html, "retry-now") { t.Errorf("state 4: expected retry-now URL in form action, got:\n%s", html) } if strings.Contains(html, "/summarize\"") { t.Errorf("state 4: rate-limited card must not POST to /summarize, got:\n%s", html) } if strings.Contains(html, "Try now") { t.Errorf("state 4: 'Try now' verb must not appear, got:\n%s", html) } } // TestVideoCard_State5_Pending — quiet status + "Summarize" → summarize URL. func TestVideoCard_State5_Pending(t *testing.T) { html := renderVideoCard(t, store.SummaryRow{ VideoID: "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa", Title: "Fresh Video", Summarized: false, }) if !strings.Contains(html, "Not summarized") { t.Errorf("state 5: expected 'Not summarized' status text, got:\n%s", html) } if !strings.Contains(html, "Summarize") { t.Errorf("state 5: expected 'Summarize' button, got:\n%s", html) } if !strings.Contains(html, "/summarize") { t.Errorf("state 5: expected summarize URL in form action, got:\n%s", html) } if strings.Contains(html, "retry-now") { t.Errorf("state 5: pending card must not POST to /retry-now, got:\n%s", html) } if strings.Contains(html, "Try now") { t.Errorf("state 5: 'Try now' verb must not appear, got:\n%s", html) } } // TestVideoCard_ForbiddenCopyAbsent — the over-promising / jargon phrases the // honesty pass removed must not reappear in any rendered card state (UX review // A3/A4/A6): "Try now", "Summarize now", "Fetching soon", "the next run". func TestVideoCard_ForbiddenCopyAbsent(t *testing.T) { forbidden := []string{"Try now", "Summarize now", "Fetching soon", "the next run"} cases := []store.SummaryRow{ {VideoID: "a", Summarized: true, Summary: "s", AIProvider: "local"}, {VideoID: "b", TranscriptStatus: "none"}, {VideoID: "c", SummarizeRequested: true}, {VideoID: "d", TranscriptStatus: "rate_limited"}, {VideoID: "e"}, } for _, r := range cases { html := renderVideoCard(t, r) for _, phrase := range forbidden { if strings.Contains(html, phrase) { t.Errorf("forbidden copy %q reappeared in card %q:\n%s", phrase, r.VideoID, html) } } } }