package todotest
import (
"net/http"
"net/url"
"os"
"slices"
"strconv"
"strings"
"testing"
)
func TestFirstVisitSetsListCookie(t *testing.T) {
t.Parallel()
b := newBrowser(t, newApp(t))
rec := b.get("/")
if rec.Code != http.StatusOK {
t.Fatalf("GET /: status %d", rec.Code)
}
cs := rec.Result().Cookies()
if len(cs) != 1 {
t.Fatalf("got %d cookies, want 1", len(cs))
}
c := cs[0]
if !c.HttpOnly || c.SameSite != http.SameSiteLaxMode || c.Path != "/" {
t.Errorf("cookie %+v: want HttpOnly, SameSite=Lax, Path=/", c)
}
if len(c.Value) < 22 { // 128 bits in base64
t.Errorf("cookie value %q is too short to hold 128 random bits", c.Value)
}
// A second visit keeps the same list: no new cookie.
if got := b.get("/").Result().Cookies(); len(got) != 0 {
t.Errorf("second visit set cookies %v", got)
}
}
func TestEmptyListHidesMainAndFooter(t *testing.T) {
t.Parallel()
body := newBrowser(t, newApp(t)).get("/").Body.String()
for _, s := range []string{`class="main"`, `class="footer"`} {
if strings.Contains(body, s) {
t.Errorf("empty list renders %s", s)
}
}
if !strings.Contains(body, `class="new-todo"`) {
t.Error("page has no new-todo field")
}
if !strings.Contains(body, "the CARLOS bakeoff · Go + Rastrillo") {
t.Error("footer doesn't name the setup")
}
}
func TestAddTrimsAndRedirectsToFilter(t *testing.T) {
t.Parallel()
b := newBrowser(t, newApp(t))
for filter, want := range map[string]string{"all": "/", "active": "/active", "completed": "/completed", "": "/", "bogus": "/"} {
rec := b.post("/todos", url.Values{"title": {" x "}, "filter": {filter}})
if rec.Code != http.StatusSeeOther || rec.Header().Get("Location") != want {
t.Errorf("filter %q: %d to %q, want 303 to %q", filter, rec.Code, rec.Header().Get("Location"), want)
}
}
b.add(" ")
got := titles(b.get("/").Body.String())
if want := []string{"x", "x", "x", "x", "x"}; !slices.Equal(got, want) {
t.Errorf("titles %q, want %q", got, want)
}
}
func TestTitlesAreTextAndKeptExactly(t *testing.T) {
t.Parallel()
b := newBrowser(t, newApp(t))
b.add(` & "q"`)
b.add("naïve 🚀 日本語")
long := strings.Repeat("x", 10_000)
b.add(long)
rec := b.get("/")
got := titles(rec.Body.String())
want := []string{"<img src=x onerror=alert(1)> & "q"", "naïve 🚀 日本語", long}
if !slices.Equal(got, want) {
t.Errorf("titles %q, want %q", got, want)
}
if n, _ := strconv.Atoi(rec.Header().Get("Content-Length")); n != rec.Body.Len() {
t.Errorf("Content-Length %d, body %d bytes", n, rec.Body.Len())
}
}
func TestToggleEditDestroy(t *testing.T) {
t.Parallel()
b := newBrowser(t, newApp(t))
b.add("one")
b.add("two")
id := items(b.get("/").Body.String())[0].ID
rec := b.post("/todos/"+id+"/toggle", url.Values{"filter": {"active"}})
if rec.Code != http.StatusSeeOther || rec.Header().Get("Location") != "/active" {
t.Fatalf("toggle: %d to %q", rec.Code, rec.Header().Get("Location"))
}
if got := titles(b.get("/active").Body.String()); !slices.Equal(got, []string{"two"}) {
t.Errorf("active after toggle: %q", got)
}
if got := titles(b.get("/completed").Body.String()); !slices.Equal(got, []string{"one"}) {
t.Errorf("completed after toggle: %q", got)
}
if !strings.Contains(b.get("/").Body.String(), "1 item left") {
t.Error("counter doesn't read 1 item left")
}
b.post("/todos/"+id+"/edit", url.Values{"title": {" uno "}, "filter": {"all"}})
if got := titles(b.get("/").Body.String()); !slices.Equal(got, []string{"uno", "two"}) {
t.Errorf("after edit: %q", got)
}
b.post("/todos/"+id+"/edit", url.Values{"title": {" "}, "filter": {"all"}})
if got := titles(b.get("/").Body.String()); !slices.Equal(got, []string{"two"}) {
t.Errorf("empty edit should delete: %q", got)
}
id2 := items(b.get("/").Body.String())[0].ID
if rec := b.post("/todos/"+id2+"/destroy", url.Values{"filter": {"all"}}); rec.Code != http.StatusSeeOther {
t.Fatalf("destroy: %d", rec.Code)
}
if got := titles(b.get("/").Body.String()); len(got) != 0 {
t.Errorf("after destroy: %q", got)
}
}
func TestEditView(t *testing.T) {
t.Parallel()
b := newBrowser(t, newApp(t))
b.add("one")
id := items(b.get("/").Body.String())[0].ID
body := b.get("/active?edit=" + id).Body.String()
if it := items(body)[0]; it.Class != "editing" {
t.Errorf("li class %q, want editing", it.Class)
}
if !strings.Contains(body, `