Skip to content
quarry Artifact [3831e34115]
Login

Artifact [3831e34115]

Artifact 3831e34115a1afe69231b9b484a117762aeb6ea84a47ea40bb6f6d3e7e4bfceb:


package harness

import (
	"io"
	"net/http"
	"net/url"
	"regexp"
	"strings"
	"testing"

	"craftdesign.group/quarry/internal/api"
	"craftdesign.group/quarry/internal/ops"
)

// The ticket board must work with no script at all. Columns are grouped
// on the server and a card is moved with a form that submits — the
// select is the MECHANISM, not a fallback to be hidden once JavaScript
// loads. Any drag layer added later sits on top of this; if hiding the
// select ever makes this test fail, the board has stopped being
// operable by touch and keyboard readers.
func TestBoardWorksWithoutScript(t *testing.T) {
	stores := map[string]string{"alpha": buildStore(t, "boardseed")}
	srv := ingestAndServeAuth(t, stores, t.TempDir(),
		func(repo, user, pass string, _ ops.Auth) api.Decision {
			if repo == "alpha" && user == "dan" && pass == "hunter2" {
				return api.Allowed
			}
			return api.Denied
		})
	client := srv.Client()
	client.CheckRedirect = func(*http.Request, []*http.Request) error { return http.ErrUseLastResponse }

	post := func(path string, form url.Values) *http.Response {
		req, _ := http.NewRequest("POST", srv.URL+path, strings.NewReader(form.Encode()))
		req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
		req.SetBasicAuth("dan", "hunter2")
		resp, err := client.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		resp.Body.Close()
		return resp
	}
	get := func(path string) string {
		req, _ := http.NewRequest("GET", srv.URL+path, nil)
		req.SetBasicAuth("dan", "hunter2")
		resp, err := client.Do(req)
		if err != nil {
			t.Fatal(err)
		}
		defer resp.Body.Close()
		b, _ := io.ReadAll(resp.Body)
		return string(b)
	}

	if resp := post("/r/alpha/ticket/new", url.Values{
		"title": {"a card to move"}, "comment": {"body"},
	}); resp.StatusCode != http.StatusSeeOther {
		t.Fatalf("ticket new: %d", resp.StatusCode)
	}

	board := get("/r/alpha/ticket?view=board")
	if !strings.Contains(board, `class="board"`) {
		t.Fatalf("no board rendered:\n%s", board)
	}
	if !strings.Contains(board, "a card to move") {
		t.Error("the new ticket is not on the board")
	}
	// The move control must be real, submitting markup — not something a
	// script has to activate.
	if !strings.Contains(board, `<form class="move" method="post"`) {
		t.Error("the move control is not a submitting form")
	}
	if strings.Contains(board, "data-on:") && !strings.Contains(board, `class="move"`) {
		t.Error("the board depends on script bindings for its only control")
	}

	// Move it exactly as a reader with no JavaScript would: submit the form.
	id := regexp.MustCompile(`/r/alpha/ticket/([0-9a-f]{10})/edit`).FindStringSubmatch(board)
	if id == nil {
		t.Fatalf("no move form target in the board:\n%s", board)
	}
	if resp := post("/r/alpha/ticket/"+id[1]+"/edit", url.Values{
		"status": {"Closed"},
	}); resp.StatusCode != http.StatusSeeOther {
		t.Fatalf("move: %d", resp.StatusCode)
	}

	// It left the open board, and appears on the one that shows closed.
	if b := get("/r/alpha/ticket?view=board"); strings.Contains(b, "a card to move") {
		t.Error("a closed ticket is still on the open board")
	}
	all := get("/r/alpha/ticket?view=board&closed=1")
	if !strings.Contains(all, "a card to move") {
		t.Errorf("the moved ticket is on neither board:\n%s", all)
	}
	if !strings.Contains(all, "Closed") {
		t.Error("no Closed column after moving a ticket into it")
	}
}