Files
tapir/internal/web/videocard_internal_test.go
T
mathiasandClaude Opus 4.8 f29927f50d test(web): guard against the removed over-promise card copy
Extend the card copy guard so "Try now", "Summarize now", "Fetching soon", and
"the next run" can't silently return to any card state (UX review honesty pass).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 14:03:08 +02:00

159 lines
5.3 KiB
Go

package web
import (
"context"
"strings"
"testing"
"gitea.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)
}
}
}
}