package web
import (
"regexp"
"strings"
"testing"
"craftdesign.group/quarry/internal/oltp"
)
// The tab comment that held the strip at two set its own acceptance
// test: "a tab that opens onto an empty page teaches a reader the forge
// is broken rather than that the feature is unbuilt." So the Teams tab
// appears only when the forge has one.
func TestTheTeamsTabAppearsOnlyOnceATeamExists(t *testing.T) {
var none, some strings.Builder
if err := peoplePage.ExecuteTemplate(&none, "content",
pageData{Explore: "people", FleetTotal: 20, PeopleTotal: 3}); err != nil {
t.Fatal(err)
}
if strings.Contains(none.String(), `href="/teams"`) {
t.Error("FAIL: a forge with no teams draws the Teams tab")
}
if err := peoplePage.ExecuteTemplate(&some, "content",
pageData{Explore: "people", FleetTotal: 20, PeopleTotal: 3, TeamsTotal: 2}); err != nil {
t.Fatal(err)
}
if !strings.Contains(some.String(), `href="/teams"`) {
t.Error("FAIL: a forge with teams does not draw the Teams tab")
}
// And the other two are still there, in order.
body := some.String()
repos, people, teams := strings.Index(body, `href="/?view=explore"`),
strings.Index(body, `href="/people"`), strings.Index(body, `href="/teams"`)
if !(repos < people && people < teams) {
t.Errorf("FAIL: tab order is repos %d, people %d, teams %d", repos, people, teams)
}
}
// A team is shown by its title where it has one, and never by both —
// "Craft Design Group (craft-design-group)" is the same fact twice and
// the handle is already in the URL.
func TestATeamIsLabelledOnceNotTwice(t *testing.T) {
named := teamRow{Name: "craft-design-group", Title: "Craft Design Group"}
if got := named.Label(); got != "Craft Design Group" {
t.Errorf("FAIL: label = %q", got)
}
if !named.Named() {
t.Error("FAIL: a titled team does not read as named")
}
bare := teamRow{Name: "core"}
if got := bare.Label(); got != "core" {
t.Errorf("FAIL: an untitled team labels as %q", got)
}
if bare.Named() {
t.Error("FAIL: an untitled team would print its handle twice")
}
// A title that merely repeats the handle is not a second fact.
if (teamRow{Name: "core", Title: "core"}).Named() {
t.Error("FAIL: a title equal to the handle reads as named")
}
}
// The directory says where a team's substance is, and a team with no
// repositories is a real row rather than one that reads as broken.
func TestTheDirectoryNamesAnEmptyTeamHonestly(t *testing.T) {
var buf strings.Builder
if err := teamsPage.ExecuteTemplate(&buf, "content", pageData{
Explore: "teams", TeamsTotal: 2,
Teams: []teamRow{
{Name: "craft-design-group", Title: "Craft Design Group",
Descr: "quarry and zit", Members: 1, Repos: 0},
{Name: "firestorm-dataworks", Title: "FireStorm DataWorks", Members: 4, Repos: 3},
},
}); err != nil {
t.Fatal(err)
}
body := buf.String()
for _, want := range []string{
`href="/t/craft-design-group"`, "Craft Design Group", "quarry and zit",
"no repositories yet", "3 repositories", "1 member", "4 members",
} {
if !strings.Contains(body, want) {
t.Errorf("FAIL: the directory is missing %q", want)
}
}
// A count of zero must not render as a bare "0 repositories".
if strings.Contains(body, "0 repositor") {
t.Error("FAIL: an empty team reads as a zero rather than as a sentence")
}
}
// An empty forge says what a team is for, rather than showing an empty
// list — the same rule the tab comment states.
func TestAnEmptyDirectorySaysWhatATeamIs(t *testing.T) {
var buf strings.Builder
if err := teamsPage.ExecuteTemplate(&buf, "content", pageData{Explore: "teams"}); err != nil {
t.Fatal(err)
}
if !strings.Contains(buf.String(), "No teams yet") {
t.Error("FAIL: an empty directory renders an empty list")
}
}
// A member's standing is named in the words the People editor uses: the
// preset where the string matches one, the letters where it does not.
func TestAMembersRoleReadsAsARoleWhereItIsOne(t *testing.T) {
for _, tc := range []struct {
caps, want string
}{
{"", "by membership"}, // RFC 0018's third route, named
{oltp.Maintain.Caps(), "Maintainer"},
{"a", "Admin"},
{"2i", "2i"}, // no preset covers it; the letters stand
} {
if got := (teamMember{Caps: tc.caps}).Role(); got != tc.want {
t.Errorf("FAIL: Role(%q) = %q, want %q", tc.caps, got, tc.want)
}
}
}
// One team's page: the repositories under it, the members, and the
// owner marked. An unlisted member reaching a manager is marked as
// such, so the manager can see why the public page differs.
func TestTheTeamPageShowsReposMembersAndWhoOwnsIt(t *testing.T) {
var buf strings.Builder
td := teamDetail{
Team: teamRow{Name: "craft-design-group", Title: "Craft Design Group",
Descr: "quarry and zit", Members: 2, Repos: 2},
Repos: []teamRepo{
{Repo: "quarry", Added: "2026-09-12T00:00:00Z"},
{Repo: "zit", Added: "2026-09-12T00:00:00Z"},
},
Members: []teamMember{
{Account: "dmestas", Owner: true, Listed: true},
{Account: "lou", Caps: oltp.Maintain.Caps(), Listed: false},
},
CanManage: true,
}
if err := teamPage.ExecuteTemplate(&buf, "content",
pageData{Title: "Craft Design Group", OwnH1: true, Team: &td}); err != nil {
t.Fatal(err)
}
body := buf.String()
for _, want := range []string{
"Craft Design Group", "quarry and zit",
`href="/r/quarry"`, `href="/r/zit"`,
`href="/people/dmestas"`, "owner", "Maintainer", "unlisted",
} {
if !strings.Contains(body, want) {
t.Errorf("FAIL: the team page is missing %q", want)
}
}
}
// A team holding nothing says so on its own page too, in a sentence
// that tells a reader how a repository gets there.
func TestAnEmptyTeamPageExplainsItself(t *testing.T) {
var buf strings.Builder
td := teamDetail{Team: teamRow{Name: "core", Members: 1}}
if err := teamPage.ExecuteTemplate(&buf, "content", pageData{Team: &td}); err != nil {
t.Fatal(err)
}
body := buf.String()
if !strings.Contains(body, "somebody who administers it") {
t.Error("FAIL: an empty team does not say how a repository joins")
}
if regexp.MustCompile(`0 repositor`).MatchString(body) {
t.Error("FAIL: renders a zero instead of a sentence")
}
}