Artifact 6617f39503b4a7adf7935587c70dea382f1e78da657ac6df4d1b87754e8c682d:
- File
internal/web/refpop_test.go
— part of check-in
[eb4793ee8e]
at
2026-08-31 15:49:44
on branch trunk
— serve the stylesheet instead of inlining it in every page
112,118 bytes of <style> rode on every response — 40% of a 309,584-byte timeline page, byte-identical each time and uncacheable because it had no URL, while the JS assets already carried Cache-Control. Now one hashed asset served immutable: a rule change mints a new URL, so a stale cache is impossible without anyone revalidating. (user: quarry, size: 12206) [annotate] [blame] [check-ins using]
package web import ( "context" "html/template" "net/http" "net/http/httptest" "path/filepath" "regexp" "strings" "testing" "craftdesign.group/quarry/internal/api" "craftdesign.group/quarry/internal/chat" "craftdesign.group/quarry/internal/index" "craftdesign.group/quarry/internal/ops" ) // Principle 3: the plain link is the whole feature. The preview rides // ON the same anchors, armed once for the page, so a reader without // script still navigates. // // The container carries the behaviour; an anchor carries only its // token. Three handlers for the whole page, whatever it holds — a chat // log with two hundred references pays for them once (RFC 0011 §14, // Principle 4). func TestTheContainerCarriesTheHandlersNotTheAnchors(t *testing.T) { for _, ev := range []string{ `data-on:mouseover__debounce.300ms="`, `data-on:focusin="`, `data-on:click="`, } { if n := strings.Count(refDelegation, ev); n != 1 { t.Fatalf("the delegation run declares %s %d times, want 1:\n%s", ev, n, refDelegation) } } if n := strings.Count(refDelegation, "data-on:"); n != 3 { t.Fatalf("the delegation run carries %d handlers, want 3:\n%s", n, refDelegation) } if n := strings.Count(styleSource, refDelegation); n != 1 { t.Fatalf("the layout arms the preview %d times, want 1", n) } // The whole page's preview script, once. Armed per anchor this was // three ~380-byte expressions on every reference on the page. if len(refDelegation) > 2048 { t.Fatalf("the delegation run is %d bytes", len(refDelegation)) } for _, want := range []string{ // The event is resolved to the anchor it reached, not to the // element it landed on. `closest('a[data-ref]')`, // The token is read off the anchor and encoded at event time; // nothing author-written is ever interpolated into the // attribute. `encodeURIComponent(a.dataset.ref)`, `/ref.frag?ref=`, // The repository is the page's own, stated on the container. `document.body.dataset.refRepo`, // Hover and focus are for pointers that hover; the tap path // cancels the navigation only where they do not. `matchMedia('(hover: hover)')`, `evt.preventDefault()`, // The card is parked at its link, not centred by the UA. `anchorName`, `getBoundingClientRect`, `showPopover`, `refpop`, } { if !strings.Contains(refDelegation, want) { t.Fatalf("the delegation run misses %q:\n%s", want, refDelegation) } } // The rows this file draws are references without being prose, so // they carry the one attribute a reference in prose does — and no // handlers of their own. quote := string(chatQuoteHTML(ChatMessage{Ref: 412, RefUser: "dan", RefLine: "a line"}, "demo")) if !strings.Contains(quote, `data-quote-seq="412"`) || !strings.Contains(quote, `data-ref="#412"`) { t.Fatalf("the reply-quote is not armed like a reference:\n%s", quote) } if strings.Contains(quote, "data-on:") { t.Fatalf("the reply-quote arms itself:\n%s", quote) } row := string(chatRefsHTML(chatRefsRow{Count: 2, Seq: 7}, "demo")) if !strings.Contains(row, `data-ref="#7"`) || strings.Contains(row, "data-on:") { t.Fatalf("the backlink row is not armed like a reference:\n%s", row) } } // The shared popover is positioned against the link that opened it, // with the measured fallback only where CSS anchoring is unknown. func TestPopoverIsAnchoredNotCentred(t *testing.T) { rule := styleSource[strings.Index(styleSource, "#refpop { position: fixed"):] rule = rule[:strings.Index(rule, "\n}")] for _, want := range []string{ "#refpop { position: fixed; inset: auto", "position-anchor: " + refAnchorName, "top: anchor(bottom)", } { if !strings.Contains(rule, want) { t.Fatalf("#refpop is not anchored to its link, missing %q:\n%s", want, rule) } } } // One popover element for the whole page: a per-link popover is a // per-link element, and the page grows one for every reference in it. func TestBaseLayoutHasOneSharedPopover(t *testing.T) { if n := strings.Count(styleSource, `id="refpop"`); n != 1 { t.Fatalf("base layout declares %d #refpop elements, want 1", n) } if !strings.Contains(styleSource, `<div id="refpop" popover`) { t.Fatalf("the shared preview is not a native popover:\n%s", styleSource) } } // Each card says what it is, structurally, and offers the page it // previews. The copy is not asserted; the hooks are. func TestRefCardCarriesKindAndOpenLink(t *testing.T) { for _, tt := range []struct{ kind, href string }{ {"chat", "/r/demo/chat/412"}, {"checkin", "/r/demo/checkin/abc"}, {"ticket", "/r/demo/ticket/xy9"}, {"landing", "/r/demo/landing/ab"}, {"forum", "/r/demo/forum/ab"}, {"wiki", "/r/demo/wiki/Home"}, {"person", "/people/alice"}, } { got := string(refCardHTML(refCard{Kind: tt.kind, Href: tt.href, Title: "t"})) if !strings.Contains(got, `data-kind="`+tt.kind+`"`) { t.Fatalf("%s card lost its data-kind:\n%s", tt.kind, got) } if !strings.Contains(got, `href="`+tt.href+`"`) { t.Fatalf("%s card lost its open link:\n%s", tt.kind, got) } } // An aged-out chat reference says so as a state, not as prose the // caller has to parse. got := string(refCardHTML(refCard{Kind: "chat", Href: "/r/demo/chat/1", Expired: true})) if !strings.Contains(got, `data-expired`) { t.Fatalf("expired card carries no state hook:\n%s", got) } // A person card never carries an address (RFC 0004 keeps those // behind `e`), so there is nothing shaped like one to leak. person := string(refCardHTML(refCard{Kind: "person", Href: "/people/alice", Title: "alice"})) if strings.Contains(person, "mailto:") || strings.Contains(person, "@") { t.Fatalf("person card leaked an address:\n%s", person) } } // The reply-quote: a message posted with Ref shows who it answers and // the first line of what they said, linked to the permalink. func TestReplyRendersItsQuote(t *testing.T) { got := string(chatMsgHTML(ChatMessage{ Seq: 9, Kind: "msg", User: "bob", Text: "agreed", Ref: 4, RefUser: "alice", RefLine: "the first line", }, "demo", "", false)) for _, want := range []string{ `class="chat-quote"`, `data-quote-seq="4"`, `href="/r/demo/chat/4"`, } { if !strings.Contains(got, want) { t.Fatalf("reply quote missing %q:\n%s", want, got) } } } // The original says how many answered it, and the count links to the // permalink where they are. func TestOriginalShowsItsReplyCount(t *testing.T) { got := string(chatMsgHTML(ChatMessage{ Seq: 4, Kind: "msg", User: "alice", Text: "hi", Replies: 3, }, "demo", "", false)) if !strings.Contains(got, `data-replies="3"`) { t.Fatalf("reply count hook missing:\n%s", got) } if !strings.Contains(got, `href="/r/demo/chat/4"`) { t.Fatalf("reply count does not link to the permalink:\n%s", got) } } // A message with no replies grows no count element at all: "0 replies" // is noise on every line of a busy channel. func TestNoRepliesRendersNoCount(t *testing.T) { got := string(chatMsgHTML(ChatMessage{Seq: 4, Kind: "msg", User: "alice", Text: "hi"}, "demo", "", false)) if strings.Contains(got, "data-replies") { t.Fatalf("a message with no replies grew a count:\n%s", got) } } // The fragment route: one card, wrapped so the shared popover comes // back still being a popover. func TestRefFragServesTheCard(t *testing.T) { ix, err := index.Open(filepath.Join(t.TempDir(), "index.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { ix.Close() }) r := ops.NewRegistry() fc := &webChatStub{msgs: []chat.Message{ {Seq: 1, Kind: chat.KindMsg, User: "dan", Text: "hello", At: "2026-08-16T12:00:00Z"}, }} if err := api.RegisterAll(r, api.Deps{Version: "t", Repos: api.NewRepos(map[string]*api.Repo{"test": {Index: ix, Code: "c1"}}), Chat: fc, Forum: &webForumReadStub{}, CanRead: func(context.Context, string) bool { return true }}); err != nil { t.Fatal(err) } h := HandlerWith(r, nil, newFakeChatLive(), nil) rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest("GET", "/r/test/ref.frag?ref=%231", nil)) body := rec.Body.String() for _, want := range []string{ "event: datastar-patch-elements", `<div id="refpop" popover>`, `data-kind="chat"`, `href="/r/test/chat/1"`, } { if !strings.Contains(body, want) { t.Fatalf("ref.frag missing %q:\n%s", want, body) } } if rec.Header().Get("ETag") == "" { t.Fatalf("ref.frag served no ETag:\n%v", rec.Header()) } // A repeat hover on an unchanged reference costs a 304, not a redraw. rec2 := httptest.NewRecorder() req := httptest.NewRequest("GET", "/r/test/ref.frag?ref=%231", nil) req.Header.Set("If-None-Match", rec.Header().Get("ETag")) h.ServeHTTP(rec2, req) if rec2.Code != http.StatusNotModified { t.Fatalf("unchanged reference re-rendered: %d", rec2.Code) } } // Principle 3, asserted on a real page: every reference on the chat // page is an anchor with a real href, so navigation never depends on // the Datastar attributes sitting beside it. func TestChatPageReferencesNavigateWithoutScript(t *testing.T) { ix, err := index.Open(filepath.Join(t.TempDir(), "index.db")) if err != nil { t.Fatal(err) } t.Cleanup(func() { ix.Close() }) r := ops.NewRegistry() fc := &webChatStub{msgs: []chat.Message{ {Seq: 1, Kind: chat.KindMsg, User: "dan", Text: "start", At: "2026-08-16T12:00:00Z"}, {Seq: 2, Kind: chat.KindMsg, User: "ann", Text: "see #1", Ref: 1, At: "2026-08-16T12:00:01Z"}, }} if err := api.RegisterAll(r, api.Deps{Version: "t", Repos: api.NewRepos(map[string]*api.Repo{"test": {Index: ix, Code: "c1"}}), Chat: fc, Forum: &webForumReadStub{}, CanRead: func(context.Context, string) bool { return true }}); err != nil { t.Fatal(err) } h := HandlerWith(r, nil, newFakeChatLive(), nil) rec := httptest.NewRecorder() h.ServeHTTP(rec, httptest.NewRequest("GET", "/r/test/chat", nil)) body := rec.Body.String() // The reference minted by the resolver, and the quote block. if !strings.Contains(body, `data-ref="#1"`) { t.Fatalf("chat page did not link a #seq reference:\n%s", body) } if !strings.Contains(body, `data-quote-seq="1"`) { t.Fatalf("reply did not render its quote:\n%s", body) } if !strings.Contains(body, `data-replies="1"`) { t.Fatalf("original did not show its reply count:\n%s", body) } // Every marked reference carries a real destination: Principle 3 // says the link is the feature and the card is the accelerator. for _, tag := range markedAnchors.FindAllString(body, -1) { if !strings.Contains(tag, `href="/`) { t.Fatalf("a reference has no navigable href: %s", tag) } } } // The backlink row on an artifact page: its own row, its own hook, and // a link to the latest message — visibly not one of the durable // mention rows beside it. func TestArtifactPagesShowTheChatRow(t *testing.T) { for _, tt := range []struct { name string tmpl *template.Template data pageData }{ {"ticket", ticketPage, pageData{Repo: "demo", Ticket: ticketDetail{ID: "xy9"}}}, {"checkin", checkinPage, pageData{Repo: "demo", Checkin: checkinDetail{Hash: "abc"}}}, {"forum", forumThreadPage, pageData{Repo: "demo", ForumRoot: "abc"}}, } { tt.data.ChatRefs = chatRefsRow{Count: 2, Seq: 7, At: "2026-08-29T10:00:00Z"} var buf strings.Builder if err := tt.tmpl.Execute(&buf, tt.data); err != nil { t.Fatalf("%s: %v", tt.name, err) } got := buf.String() if !strings.Contains(got, `data-chat-refs data-count="2" data-seq="7"`) { t.Fatalf("%s page has no chat row:\n%s", tt.name, got) } if !strings.Contains(got, `href="/r/demo/chat/7"`) { t.Fatalf("%s chat row does not link the latest message", tt.name) } } } // Nothing named it, nothing shown: an empty row on every artifact page // is noise that teaches readers to skip the place the real one appears. func TestNoChatRowWhenNothingNamedIt(t *testing.T) { var buf strings.Builder if err := ticketPage.Execute(&buf, pageData{Repo: "demo", Ticket: ticketDetail{ID: "xy9"}}); err != nil { t.Fatal(err) } if strings.Contains(buf.String(), "data-chat-refs") { t.Fatalf("an empty chat row was drawn") } } // markedAnchors finds the opening <a> tags markup marked as references. var markedAnchors = regexp.MustCompile(`<a [^>]*\bdata-ref="[^"]*"[^>]*>`)