package web
import (
"net/http/httptest"
"net/url"
"strings"
"testing"
)
// The form offers the tracker's OWN columns. tcl's tracker carries
// fourteen fields including assignee; quarry could read them all and
// write none, which made it a second-class client of its own stores.
func TestTheEditFormOffersTheTrackersOwnColumns(t *testing.T) {
var buf strings.Builder
err := ticketEditPage.ExecuteTemplate(&buf, "content", pageData{
Repo: "quarry",
Ticket: ticketDetail{Fields: []ticketField{
{Name: "title", Value: "t"}, {Name: "assignee", Value: "dan"}}},
ExtraFields: extraFieldRows([]string{"assignee", "closer"}, ticketDetail{
Fields: []ticketField{{Name: "assignee", Value: "dan"}}}),
})
if err != nil {
t.Fatal(err)
}
got := buf.String()
for _, want := range []string{`name="fields.assignee"`, `value="dan"`, `name="fields.closer"`, `data-hook="tkt-extra"`} {
if !strings.Contains(got, want) {
t.Errorf("FAIL: the form is missing %s", want)
}
}
}
// A tracker with nothing beyond fossil's schema draws no row. An empty
// fieldset is a promise of controls that are not there.
func TestATrackerWithNoExtraColumnsDrawsNoRow(t *testing.T) {
var buf strings.Builder
if err := ticketEditPage.ExecuteTemplate(&buf, "content", pageData{
Repo: "quarry", Ticket: ticketDetail{Fields: []ticketField{{Name: "title", Value: "t"}}},
}); err != nil {
t.Fatal(err)
}
if strings.Contains(buf.String(), `data-hook="tkt-extra"`) {
t.Error("FAIL: a tracker with no extra columns drew the generic row")
}
}
// A field with its own input is never repeated in the generic row: one
// value with two controls is a disagreement waiting for its first save.
func TestTheGenericRowNeverRepeatsANamedInput(t *testing.T) {
for _, name := range []string{"status", "title", "foundin", "comment"} {
if !ticketOwnInput[name] {
t.Errorf("FAIL: %q is not recognised as having its own input", name)
}
}
}
func postForm(t *testing.T, form url.Values) *httptest.ResponseRecorder {
t.Helper()
return httptest.NewRecorder()
}
// Only what moved is written. The form posts every input on every save,
// and writing them all would record a change per field per save — the
// timeline would read "changed assignee dan → dan" after every click.
func TestOnlyAChangedExtraFieldIsWritten(t *testing.T) {
was := ticketDetail{Fields: []ticketField{{Name: "assignee", Value: "dan"}}}
req := httptest.NewRequest("POST", "/x", strings.NewReader(
url.Values{"fields.assignee": {"dan"}, "fields.closer": {"lou"}}.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if err := req.ParseForm(); err != nil {
t.Fatal(err)
}
vals := url.Values{"repo": {"quarry"}, "id": {"aa11"}}
carryExtraFields(req, vals, &was)
if vals.Has("fields.assignee") {
t.Error("FAIL: an unchanged field was written")
}
if vals.Get("fields.closer") != "lou" {
t.Errorf("FAIL: a changed field was dropped: %v", vals)
}
}
// On an edit an empty input CLEARS the field — the only way to unset
// one somebody set by mistake. On a new ticket there is nothing to
// clear, so an empty input is nothing to write.
func TestAnEmptyInputClearsOnEditAndIsDroppedOnNew(t *testing.T) {
req := httptest.NewRequest("POST", "/x", strings.NewReader(
url.Values{"fields.assignee": {""}}.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if err := req.ParseForm(); err != nil {
t.Fatal(err)
}
was := ticketDetail{Fields: []ticketField{{Name: "assignee", Value: "dan"}}}
vals := url.Values{}
carryExtraFields(req, vals, &was)
if v, ok := vals["fields.assignee"]; !ok || v[0] != "" {
t.Errorf("FAIL: clearing an assignee produced %v", vals)
}
fresh := url.Values{}
carryExtraFields(req, fresh, nil)
if fresh.Has("fields.assignee") {
t.Errorf("FAIL: a blank input on a new ticket wrote something: %v", fresh)
}
}