Skip to content
quarry Artifact [ae342506e0]
Login

Artifact [ae342506e0]

Artifact ae342506e0cdf6e134a8c36a971ab8d609f3a0478938c2789bd905fef342eacf:


package web

import (
	"html/template"
	"net/http/httptest"
	"strings"
	"testing"
	"time"
)

// teamFakeLive serves one merged room: whatever it is given, in order.
type teamFakeLive struct{ msgs []ChatMessage }

func (f teamFakeLive) Tail(string, uint64) (<-chan ChatMessage, func()) {
	ch := make(chan ChatMessage)
	close(ch)
	return ch, func() {}
}
func (f teamFakeLive) Join(string, string) func() { return func() {} }
func (f teamFakeLive) Present(string) []string    { return nil }
func (f teamFakeLive) PresenceChanged(string) (<-chan struct{}, func()) {
	return make(chan struct{}), func() {}
}
func (f teamFakeLive) Activity() (<-chan struct{}, func()) {
	return make(chan struct{}), func() {}
}
func (f teamFakeLive) TailTeam(string) (<-chan ChatMessage, func()) {
	ch := make(chan ChatMessage, len(f.msgs))
	for _, m := range f.msgs {
		ch <- m
	}
	close(ch)
	return ch, func() {}
}

// A line arriving live is the same markup as the same line after a
// reload — including the origin chip, which comes off the MESSAGE and
// not off the page. The room used to look like a live channel and not
// be one; this is what made it one.
func TestTheTeamRoomAppendsArrivingLinesThroughThePageRenderer(t *testing.T) {
	live := teamFakeLive{msgs: []ChatMessage{
		// The SAME author, the SAME minute, two different channels —
		// the only shape that can tell "runs group by author" apart
		// from "runs never span two channels".
		{Seq: 4, Kind: "msg", User: "lou", Repo: "zit", Text: "in zit", At: "2026-09-14T17:00:00Z"},
		{Seq: 9, Kind: "msg", User: "lou", ToTeam: true, Text: "to everyone", At: "2026-09-14T17:00:00Z"},
	}}
	req := httptest.NewRequest("GET", "/t/core/chat.sse", nil)
	w := httptest.NewRecorder()
	done := make(chan struct{})
	go func() { defer close(done); liveTeamRoom(live, "core", nil, w, req) }()
	select {
	case <-done:
	case <-time.After(3 * time.Second):
		t.Fatal("the stream did not finish when the channel closed")
	}
	body := w.Body.String()
	if ct := w.Header().Get("Content-Type"); ct != "text/event-stream" {
		t.Errorf("FAIL: content type = %q", ct)
	}
	for _, want := range []string{
		"event: datastar-patch-elements", "data: mode append",
		"data: selector #chatlog",
		// the shared row, and each line's own origin
		`class="chatmsg"`,
		`data-hook="chat-origin" href="/r/zit"`,
		`data-hook="chat-origin" data-to-team`,
	} {
		if !strings.Contains(body, want) {
			t.Errorf("FAIL: the stream is missing %q", want)
		}
	}
	// A run never spans two channels, so the second line is NOT drawn
	// as a continuation of the first even though it is a minute apart.
	if strings.Contains(body, "data-grouped") {
		t.Errorf("FAIL: two channels were grouped into one run")
	}
}

// Tombstones are skipped: each channel numbers its own sequences, so a
// merged view cannot address the message an edit or delete refers to.
// Reloading shows the folded truth; appending the tombstone would not.
func TestTheTeamRoomDoesNotAppendTombstones(t *testing.T) {
	live := teamFakeLive{msgs: []ChatMessage{
		{Seq: 5, Kind: "del", Ref: 4, User: "lou", Repo: "zit"},
		{Seq: 6, Kind: "edit", Ref: 4, User: "lou", Repo: "zit", Text: "fixed"},
	}}
	req := httptest.NewRequest("GET", "/t/core/chat.sse", nil)
	w := httptest.NewRecorder()
	done := make(chan struct{})
	go func() { defer close(done); liveTeamRoom(live, "core", nil, w, req) }()
	select {
	case <-done:
	case <-time.After(3 * time.Second):
		t.Fatal("the stream did not finish")
	}
	if strings.Contains(w.Body.String(), "datastar-patch-elements") {
		t.Errorf("FAIL: a tombstone was appended as a row")
	}
}

// A line that arrives is rendered by the room's own renderer, so it is
// the same markup as the same line after a reload. Without this an
// @mention was a link after a refresh and characters while it arrived.
func TestAnArrivingLineIsRenderedByTheRoomsRenderer(t *testing.T) {
	live := teamFakeLive{msgs: []ChatMessage{
		{Seq: 4, Kind: "msg", User: "lou", ToTeam: true, Text: "hi @mia",
			At: "2026-09-14T17:00:00Z"},
	}}
	var asked []string
	render := func(repo, text string) template.HTML {
		asked = append(asked, repo+"|"+text)
		return template.HTML(`hi <a class="mention" href="/people/mia">@mia</a>`)
	}
	req := httptest.NewRequest("GET", "/t/core/chat.sse", nil)
	w := httptest.NewRecorder()
	done := make(chan struct{})
	go func() { defer close(done); liveTeamRoom(live, "core", render, w, req) }()
	select {
	case <-done:
	case <-time.After(3 * time.Second):
		t.Fatal("the stream did not finish")
	}
	if len(asked) != 1 || asked[0] != "|hi @mia" {
		t.Errorf("FAIL: the renderer was asked %v", asked)
	}
	if !strings.Contains(w.Body.String(), `class="mention" href="/people/mia"`) {
		t.Errorf("FAIL: the arriving line was not the rendered markup")
	}
}