package web
import (
"os"
"path/filepath"
"strings"
"testing"
"craftdesign.group/quarry/internal/oltp"
)
// A LOOKING aid, not a guard: writes the teams directory and one team's
// page with the real production data. Off unless QUARRY_SHOT is set.
func TestWriteTeamShots(t *testing.T) {
out := os.Getenv("QUARRY_SHOT")
if out == "" {
t.Skip("QUARRY_SHOT unset")
}
dir := filepath.Dir(out)
if err := os.WriteFile(filepath.Join(dir, "quarry.css"), []byte(sheet), 0o644); err != nil {
t.Fatal(err)
}
rows := []teamRow{
{Name: "craft-design-group", Title: "Craft Design Group",
Descr: "quarry and zit, and the tools around them", Members: 1, Repos: 0},
{Name: "firestorm-dataworks", Title: "FireStorm DataWorks",
Descr: "Data engineering and the pipelines behind it", Members: 1, Repos: 0},
}
var dirBuf strings.Builder
if err := teamsPage.Execute(&dirBuf, pageData{Title: "Teams", HideH1: true,
Explore: "teams", SheetURL: "quarry.css",
FleetTotal: 20, PeopleTotal: 3100, TeamsTotal: len(rows), Teams: rows}); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(out, []byte(dirBuf.String()), 0o644); err != nil {
t.Fatal(err)
}
// One team, populated the way it will look once repositories and
// members are in it — an empty page shows nothing about layout.
td := teamDetail{
Team: teamRow{Name: "craft-design-group", Title: "Craft Design Group",
Descr: "quarry and zit, and the tools around them", Members: 3, Repos: 2},
Repos: []teamRepo{
{Repo: "quarry", Added: "2026-09-10T09:00:00Z"},
{Repo: "zit", Added: "2026-09-11T14:00:00Z"},
},
Members: []teamMember{
{Account: "dmestas", Owner: true, Listed: true},
{Account: "lou", Caps: oltp.Maintain.Caps(), Listed: true},
{Account: "zeitgeist", Caps: "", Listed: false},
},
CanManage: true, Sect: "",
// What this viewer could add, and which rows they may take out
// — both come from the op, so a shot has to supply them or the
// controls are invisible and the layout is untested.
Donatable: []string{"orchard", "tk"},
MayRemove: []string{"quarry", "zit"},
}
var one strings.Builder
if err := teamPage.Execute(&one, pageData{Title: "Craft Design Group",
OwnH1: true, SheetURL: "quarry.css", Team: &td, TeamBar: td.bar()}); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, "team-one.html"), []byte(one.String()), 0o644); err != nil {
t.Fatal(err)
}
t.Logf("ok: wrote %s and team-one.html", out)
}
// The People screen, with all three routes present — the state that
// shows whether the layout distinguishes them.
func TestWriteTeamPeopleShot(t *testing.T) {
out := os.Getenv("QUARRY_SHOT_PEOPLE")
if out == "" {
t.Skip("QUARRY_SHOT_PEOPLE unset")
}
if err := os.WriteFile(filepath.Join(filepath.Dir(out), "quarry.css"), []byte(sheet), 0o644); err != nil {
t.Fatal(err)
}
tp := teamPeople{
Team: teamRow{Name: "craft-design-group", Title: "Craft Design Group"},
Repos: 2, CanManage: true, Presets: oltp.Presets,
// A second page exists, so the pager renders. Owners comes
// from the op, not from these rows.
Total: 128, Owners: 1, Page: 1, Next: 2,
People: []teamPersonRow{
{Account: "dmestas", Member: true, Owner: true,
Direct: []teamDirectRow{{Repo: "quarry", Caps: "a"}, {Repo: "zit", Caps: "a"}}},
{Account: "lou", Member: true, Caps: oltp.Maintain.Caps()},
{Account: "zeitgeist", Member: true},
{Account: "calionauta", Direct: []teamDirectRow{{Repo: "quarry", Caps: "uv3bBC78"}}},
},
}
var buf strings.Builder
if err := teamPeoplePage.Execute(&buf, pageData{Title: "Craft Design Group · People",
OwnH1: true, SheetURL: "quarry.css", TeamPeople: &tp, TeamBar: tp.bar()}); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(out, []byte(buf.String()), 0o644); err != nil {
t.Fatal(err)
}
t.Logf("ok: wrote %s", out)
}
// The room, with both kinds of line present — one said to the team and
// one from a repository — because the whole design is that a reader can
// tell them apart and reply to the right place.
func TestWriteTeamRoomShot(t *testing.T) {
out := os.Getenv("QUARRY_SHOT_ROOM")
if out == "" {
t.Skip("QUARRY_SHOT_ROOM unset")
}
if err := os.WriteFile(filepath.Join(filepath.Dir(out), "quarry.css"),
[]byte(sheet), 0o644); err != nil {
t.Fatal(err)
}
tr := teamRoom{
Team: teamRow{Name: "craft-design-group", Title: "Craft Design Group"},
Rooms: []string{"quarry", "zit"},
Messages: []roomLine{
{Seq: 91, Kind: "msg", User: "dmestas", Repo: "", Text: "Standup in ten.",
At: "2026-09-13T17:40:00Z"},
{Seq: 88, Kind: "msg", User: "lou", Repo: "zit", Text: "The landing probe is green on trunk now.",
At: "2026-09-13T17:12:00Z"},
{Seq: 74, Kind: "msg", User: "calionauta", Repo: "quarry",
Text: "Anyone else seeing the sessions table squeeze Sign out on a phone?",
At: "2026-09-13T16:30:00Z"},
},
Cursor: "eyJiZWZvcmUiOnsiYSI6NX19",
}
var buf strings.Builder
if err := teamRoomPage.Execute(&buf, pageData{Title: "Craft Design Group · Room",
OwnH1: true, SheetURL: "quarry.css", TeamRoom: &tr, TeamBar: tr.bar(),
ChatOn: "/t/" + tr.Team.Name, ChatName: tr.Team.Name, ChatMsgs: tr.log(),
ChatOlder: tr.older()}); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(out, []byte(buf.String()), 0o644); err != nil {
t.Fatal(err)
}
t.Logf("ok: wrote %s", out)
}
// The board, rendering the REPOSITORY board's own body grouped by
// assignee: same lanes, same columns, the same shared card. One
// repository's tracker defines no assignee, which is the state that
// shows whether the page explains a one-column board rather than
// looking broken.
func TestWriteTeamBoardShot(t *testing.T) {
out := os.Getenv("QUARRY_SHOT_BOARD")
if out == "" {
t.Skip("QUARRY_SHOT_BOARD unset")
}
if err := os.WriteFile(filepath.Join(filepath.Dir(out), "quarry.css"),
[]byte(sheet), 0o644); err != nil {
t.Fatal(err)
}
rows := []ticketSummary{
{Repo: "quarry", ID: "8318bd2819ca7f31aa", Title: "Teams: organisations that own repositories and carry grants",
Status: "Open", Priority: "Low", Assignee: "dmestas", Updated: "2026-09-13T20:10:00Z"},
{Repo: "quarry", ID: "085786dd870e4455bb", Title: "The Reader preset grants wiki and ticket editing",
Status: "Open", Priority: "High", Assignee: "lou", Updated: "2026-09-13T17:05:00Z"},
{Repo: "zit", ID: "b906985a303c1122cc", Title: "ci: supersede and label queries are exact only below the scan",
Status: "Open", Priority: "Medium", Updated: "2026-09-13T18:40:00Z"},
{Repo: "zit", ID: "82eb641adb994411dd", Title: "An orphan tk mirror is still listed on the fleet",
Status: "Open", Updated: "2026-09-12T09:00:00Z"},
}
labels := []string{"dmestas", "lou", "calionauta", unsetLane}
tt := teamTickets{
Team: teamRow{Name: "craft-design-group", Title: "Craft Design Group"},
Repos: []string{"quarry", "zit"},
Assignable: []string{"quarry"},
Open: 14, Closed: 132,
GroupBy: "assignee",
ColumnLabels: labels,
Nav: ticketNav{Base: "/t/craft-design-group/tickets", View: "board",
Open: 14, Closed: 132},
}
tt.Board = buildBoard(rows, "assignee", "",
map[string][]string{"assignee": labels}, cardView{
GroupBy: "assignee", ColumnLabels: labels, ShowRepo: true})
nameAssigneeColumns(tt.Board)
tt.allowCards(true)
var buf strings.Builder
if err := teamTicketsPage.Execute(&buf, pageData{Title: "Craft Design Group · Tickets",
OwnH1: true, SheetURL: "quarry.css", TeamTickets: &tt, TeamBar: tt.bar()}); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(out, []byte(buf.String()), 0o644); err != nil {
t.Fatal(err)
}
t.Logf("ok: wrote %s", out)
}
// The SETTINGS page: where a team is changed, reached by the cog. Off
// unless QUARRY_SHOT_TSET is set.
func TestWriteTeamSettingsShot(t *testing.T) {
out := os.Getenv("QUARRY_SHOT_TSET")
if out == "" {
t.Skip("QUARRY_SHOT_TSET unset")
}
if err := os.WriteFile(filepath.Join(filepath.Dir(out), "quarry.css"),
[]byte(sheet), 0o644); err != nil {
t.Fatal(err)
}
td := teamDetail{
Team: teamRow{Name: "craft-design-group", Title: "Craft Design Group",
Descr: "quarry and zit, and the tools around them", Members: 3, Repos: 2},
Repos: []teamRepo{
{Repo: "quarry", Added: "2026-09-10T09:00:00Z"},
{Repo: "zit", Added: "2026-09-11T14:00:00Z"},
},
CanManage: true, Sect: "settings",
Offered: []teamOfferRow{{Repo: "orchard", Team: "craft-design-group",
OfferedBy: "calionauta", Offered: "2026-09-13T18:00:00Z",
Expires: "2026-09-20T18:00:00Z"}},
Donatable: []string{"orchard", "tk"},
MayRemove: []string{"quarry", "zit"},
Losing: map[string][]string{"quarry": {"lou", "calionauta"}},
}
var buf strings.Builder
if err := teamSettingsPage.Execute(&buf, pageData{
Title: "Craft Design Group · Settings", OwnH1: true,
SheetURL: "quarry.css", Team: &td, TeamBar: td.bar()}); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(out, []byte(buf.String()), 0o644); err != nil {
t.Fatal(err)
}
t.Logf("ok: wrote %s", out)
}