package web
import (
"os"
"path/filepath"
"strings"
"testing"
)
func bridgeSection(t *testing.T, p *chatBridgePanel) string {
t.Helper()
var b strings.Builder
d := pageData{Repo: "alpha", RepoAdmin: true, ChatBridge: p, Roster: &rosterPanel{Rows: []rosterRow{
{Name: "dan", Caps: "a", Role: "Admin", Chats: true},
{Name: "eve", Caps: "o", Role: "Reader"},
}}}
if err := noHooks.template(settingsPage).ExecuteTemplate(&b, "content", d); err != nil {
t.Fatal(err)
}
s := b.String()
i := strings.Index(s, `data-hook="chat-bridge"`)
if i < 0 {
t.Fatal("FAIL: no chat bridge section")
}
s = s[i:]
return s[:strings.Index(s, "</section>")]
}
// Open to anyone signed in, the section offers the close first, lists
// the category among the readers, and offers no bridge it would refuse.
func TestAnOpenChannelOffersTheCloseNotTheBridge(t *testing.T) {
s := bridgeSection(t, &chatBridgePanel{OpenTo: []string{"anonymous"}})
if !strings.Contains(s, `data-hook="chat-bridge-open"`) || !strings.Contains(s, `data-hook="chat-bridge-close"`) {
t.Error("FAIL: an open channel does not say so and offer to close it")
}
if !strings.Contains(s, `<fieldset disabled>`) {
t.Error("FAIL: the bridge form is usable on an open channel")
}
if !strings.Contains(s, `href="/r/alpha/people/dan"`) || strings.Contains(s, `href="/r/alpha/people/eve"`) {
t.Error("FAIL: the readers are not exactly the people holding chat")
}
}
// Closed to named people, the form is live; bridged, it gives way to
// the bridge's state and Detach; stopped, the section says so.
func TestTheBridgeSectionFollowsTheBridge(t *testing.T) {
ready := bridgeSection(t, &chatBridgePanel{})
if !strings.Contains(ready, `data-hook="chat-bridge-form"`) || strings.Contains(ready, "disabled") ||
strings.Contains(ready, `data-hook="chat-bridge-close"`) {
t.Error("FAIL: a closed channel does not offer a live bridge form, or offers to close it")
}
on := &chatBridgePanel{Bridged: true, State: "connected"}
on.Bridge.URL, on.Bridge.User, on.Bridge.At = "https://fossil.example/r", "bot", "2026-10-26T10:00:00Z"
s := bridgeSection(t, on)
if !strings.Contains(s, `data-hook="chat-bridge-state"`) || !strings.Contains(s, `value="detach"`) ||
strings.Contains(s, `data-hook="chat-bridge-form"`) {
t.Error("FAIL: a bridged channel does not show its state and Detach in place of the form")
}
stop := &chatBridgePanel{Bridged: true, State: "stopped", OpenTo: []string{"anonymous"}}
stop.Bridge.Stopped = "2026-10-26T10:00:00Z"
s = bridgeSection(t, stop)
if !strings.Contains(s, `data-hook="chat-bridge-stopped"`) || !strings.Contains(s, `data-hook="chat-bridge-close"`) {
t.Error("FAIL: a stopped bridge does not say so and offer to close the channel")
}
}
// The section in each state, as whole settings pages for screenshots.
// Off unless QUARRY_SHOT_BRIDGE names a directory.
func TestWriteChatBridgeShots(t *testing.T) {
dir := os.Getenv("QUARRY_SHOT_BRIDGE")
if dir == "" {
t.Skip("QUARRY_SHOT_BRIDGE unset")
}
if err := os.WriteFile(filepath.Join(dir, "quarry.css"), []byte(sheet), 0o644); err != nil {
t.Fatal(err)
}
roster := &rosterPanel{Rows: []rosterRow{
{Name: "dmestas", Caps: "a", Role: "Admin", Chats: true},
{Name: "lou", Caps: "x", Role: "Maintainer", Chats: true},
{Name: "calionauta", Caps: "i", Role: "Developer", Chats: true},
{Name: "mia", Team: "craft-design-group", Chats: true},
}}
on := &chatBridgePanel{Bridged: true, State: "connected"}
on.Bridge.URL, on.Bridge.User, on.Bridge.At = "https://fossil.craftdesign.group/zit", "zeitforge-bridge", "2026-10-26T09:56:00Z"
stop := &chatBridgePanel{Bridged: true, State: "stopped", OpenTo: []string{"anonymous"}}
stop.Bridge.URL, stop.Bridge.User, stop.Bridge.Stopped = on.Bridge.URL, on.Bridge.User, "2026-10-26T09:02:00Z"
for name, p := range map[string]*chatBridgePanel{
"open": {OpenTo: []string{"anonymous"}}, "ready": {}, "connected": on, "stopped": stop,
} {
var b strings.Builder
d := pageData{Title: "Settings", HideH1: true, Repo: "quarry", RepoAdmin: true, SheetURL: "quarry.css",
ChatBridge: p, Roster: roster}
if err := noHooks.template(settingsPage).Execute(&b, d); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(filepath.Join(dir, name+".html"), []byte(b.String()), 0o644); err != nil {
t.Fatal(err)
}
}
}