Artifact 8f421ccbe12d3246834e916c92277b41f17063ed7936c496a89377ea93ad8d4d:
- File
internal/harness/board_test.go
— part of check-in
[bd62e4820f]
at
2026-08-22 16:33:42
on branch ticket-list
— tickets: state axis, counts header, GitHub-shaped rows
Replace the list op's closed bool with a three-way state (open/closed/all) and carry it through the web layer as ?state=, so filtering and the view/sort axes stay independent. Header gains real open/closed/all counts; rows get a state glyph, signal-only chips, author and comment count.
Also fixes a.btn outranking .btn-primary, which rendered every primary anchor button in the forge as a plain one. (user: dmestas, size: 3450) [annotate] [blame] [check-ins using] [more...]
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. The Move menu is a native popover holding // one plain form per destination column (the drag layer is an // enhancement over the same POST). if !strings.Contains(board, `class="mv-btn"`) { t.Error("no Move control on the card") } if !strings.Contains(board, `/move?to=`) { t.Error("the move control is not a submitting form") } // Move it exactly as a reader with no JavaScript would: submit the form. id := regexp.MustCompile(`/r/alpha/ticket/([0-9a-f]{10})/move\?to=`).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]+"/move?to=Closed&field=status", url.Values{}); 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&state=all") 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") } }