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>
159 lines
5.3 KiB
Go
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)
|
|
}
|
|
}
|
|
}
|
|
}
|