Skip to content
quarry Artifact [f9bfd48c82]
Login

Artifact [f9bfd48c82]

Artifact f9bfd48c827cc40041facb7c378bf4e5aa161d240bb008ec1df8e5e9019afed4:


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 := noHooks.template(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 := noHooks.template(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)
	}
}