package web
import (
"net/http/httptest"
"net/url"
"strings"
"testing"
)
// The selection column is drawn only for a viewer who holds both
// letters chat.promote needs, and the boxes reach the promote form
// through the form attribute — a form cannot be nested in the composer.
func TestChatSelectionRendersOnlyForPromoters(t *testing.T) {
msgs := []ChatMessage{
{Seq: 4, Kind: "msg", User: "dan", Text: "a", At: "2026-08-29T14:05:00Z"},
{Seq: 5, Kind: "msg", User: "eve", Text: "b", At: "2026-08-29T14:06:00Z"},
}
with := chatContent(t, pageData{Repo: "alpha", ChatMsgs: msgs,
ChatViewer: "dan", ChatPromote: true})
for _, want := range []string{
`class="chat-pick"`,
`name="seq" value="4"`,
`name="seq" value="5"`,
`form="chat-promote"`,
`id="chat-promote"`,
`action="/r/alpha/chat/promote"`,
`class="chat-mark chat-promote"`,
} {
if !strings.Contains(with, want) {
t.Errorf("the promoter's page is missing %q", want)
}
}
without := chatContent(t, pageData{Repo: "alpha", ChatMsgs: msgs, ChatViewer: "dan"})
for _, gone := range []string{`class="chat-pick"`, `id="chat-promote"`} {
if strings.Contains(without, gone) {
t.Errorf("a viewer who cannot promote was offered %q", gone)
}
}
}
// A deleted message and a robot line are not conversation anybody can
// promote, so neither gets a box.
func TestChatSelectionSkipsWhatCannotBePromoted(t *testing.T) {
body := chatContent(t, pageData{Repo: "alpha", ChatPromote: true, ChatViewer: "dan",
ChatMsgs: []ChatMessage{
{Seq: 1, Kind: "msg", User: "dan", Text: "keep", At: "2026-08-29T14:05:00Z"},
{Seq: 2, Kind: "msg", User: "dan", Deleted: true, At: "2026-08-29T14:06:00Z"},
{Seq: 3, Kind: "robot", User: "robot", Text: "check-in abc", At: "2026-08-29T14:07:00Z"},
}})
if !strings.Contains(body, `name="seq" value="1"`) {
t.Error("a live message got no selection box")
}
for _, seq := range []string{"2", "3"} {
if strings.Contains(body, `name="seq" value="`+seq+`"`) {
t.Errorf("message %s was offered for promotion", seq)
}
}
}
// A robot line carries its own mark and takes no author hue: the row
// says "the channel said this", not "a person did".
func TestRobotLineIsMarkedAndUnhued(t *testing.T) {
row := string(chatMsgHTML(ChatMessage{Seq: 9, Kind: "robot", User: "robot",
Text: "promoted #4-#6", At: "2026-08-29T14:05:00Z"}, "alpha", "dan", false))
if !strings.Contains(row, `data-hook="robot-mark"`) {
t.Errorf("robot line has no mark:\n%s", row)
}
if strings.Contains(row, "--uh:") {
t.Errorf("robot line took an author hue:\n%s", row)
}
person := string(chatMsgHTML(ChatMessage{Seq: 10, Kind: "msg", User: "dan",
Text: "hi", At: "2026-08-29T14:05:00Z"}, "alpha", "dan", false))
if strings.Contains(person, `data-hook="robot-mark"`) {
t.Errorf("a person's line was marked as the robot's:\n%s", person)
}
if !strings.Contains(person, "--uh:") {
t.Errorf("a person's line lost its hue:\n%s", person)
}
}
// The checked boxes' outermost pair is the range, whatever order the
// browser sent them in and whatever it sent alongside them.
func TestChatPickedBoundsTheSelection(t *testing.T) {
form := url.Values{"seq": {"12", "4", "9", "", "nonsense", "0"}, "title": {"x"}}
req := httptest.NewRequest("POST", "/r/alpha/chat/promote",
strings.NewReader(form.Encode()))
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
from, to := chatPicked(req)
if from != 4 || to != 12 {
t.Fatalf("chatPicked = %d..%d, want 4..12", from, to)
}
empty := httptest.NewRequest("POST", "/r/alpha/chat/promote", strings.NewReader(""))
empty.Header.Set("Content-Type", "application/x-www-form-urlencoded")
if f, tt := chatPicked(empty); f != 0 || tt != 0 {
t.Fatalf("an empty selection = %d..%d, want 0..0", f, tt)
}
}