package web
import (
"strings"
"testing"
"craftdesign.group/quarry/internal/index"
)
// Every kind the index can return must produce a link. forum and
// technote fell through hitOf's switch with no URL, so their titles
// rendered as <a href="">, which a browser resolves to the current
// page: clicking a forum result reloaded the search. The index found
// them, the facet chips offered them, and the one step that turns a hit
// into somewhere to go was missing.
//
// Driven by index.SearchKinds rather than a hand-written list, so a
// kind added to the index without a link here fails instead of
// shipping a dead result.
func TestEverySearchKindLinksSomewhere(t *testing.T) {
// Refs by kind, in the shape searchDoc actually files them: a
// check-in by hash, a wiki page by title, a ticket by id, a forum
// post by its THREAD ROOT, a technote by its EVENT id.
ref := map[string]string{
"checkin": strings.Repeat("a", 64),
"wiki": "Design Notes",
"ticket": strings.Repeat("b", 40),
"forum": strings.Repeat("c", 64),
"technote": strings.Repeat("d", 40),
"doc": "b8c7665e121b25c3/www/quickstart.wiki",
"file": "b8c7665e121b25c3/src/main.c",
}
want := map[string]string{
"checkin": "/r/demo/checkin/",
"wiki": "/r/demo/wiki/",
"ticket": "/r/demo/ticket/",
"forum": "/r/demo/forum/",
"technote": "/r/demo/technote/",
"doc": "/r/demo/doc/",
"file": "/r/demo/file/",
}
for _, kind := range index.SearchKinds {
// "docs" is a scope over doc and wiki, never a hit's own kind.
if kind == "docs" {
continue
}
r, ok := ref[kind]
if !ok {
t.Fatalf("index.SearchKinds gained %q and this test does not know its ref shape", kind)
}
t.Run(kind, func(t *testing.T) {
h := hitOf("demo", kind, r, "A Label", "a ⟦match⟧ here")
if h.URL == "" {
t.Fatalf("%s hit has no URL — the title renders as href=\"\" "+
"and clicking it reloads the search", kind)
}
if !strings.HasPrefix(h.URL, want[kind]) {
t.Errorf("%s URL = %q, want it under %q", kind, h.URL, want[kind])
}
// A hit with no label must still be clickable and must not
// render as an empty link.
bare := hitOf("demo", kind, r, "", "")
if bare.URL == "" || strings.TrimSpace(bare.Label) == "" {
t.Errorf("%s with no label: url=%q label=%q — both must be filled",
kind, bare.URL, bare.Label)
}
})
}
}
// The snippet's match markers become <mark> only AFTER escaping, so a
// hit whose content carries markup stays inert.
func TestSearchSnippetStaysInert(t *testing.T) {
h := hitOf("demo", "checkin", strings.Repeat("a", 64), "L",
"before ⟦<script>alert(1)</script>⟧ after")
got := string(h.Snippet)
if strings.Contains(got, "<script>") {
t.Errorf("snippet passed markup through: %q", got)
}
if !strings.Contains(got, "<mark>") || !strings.Contains(got, "<script>") {
t.Errorf("snippet = %q, want escaped content inside <mark>", got)
}
}