package web
import (
"encoding/json"
"strings"
"testing"
)
// The handler has three moving parts that all matter, and none of them
// are visible in a rendered page: the address-bar correction reads the
// link's OWN href, it is wrapped so a throw cannot cancel the fetch, and
// the fetch still happens. Pin the shape, not the prose.
func TestSyncURLAttrShape(t *testing.T) {
got := string(syncURLAttr("/r/x/doc.frag?doc=README.md"))
for _, want := range []string{
`data-on:click__prevent="`,
`history.replaceState(null,''`,
`evt.currentTarget.getAttribute('href')`,
`try{`, `}catch(e){}`,
`@get('/r/x/doc.frag?doc=README.md')`,
} {
if !strings.Contains(got, want) {
t.Fatalf("attribute missing %q:\n%s", want, got)
}
}
// replaceState, not pushState: a view toggle must not make the back
// button walk every view the reader tried.
if strings.Contains(got, "pushState") {
t.Fatalf("view toggles must replace, not push:\n%s", got)
}
// The catch has to sit around replaceState and NOT around @get, or a
// cosmetic failure takes the fetch down with it.
if strings.Index(got, "}catch(e){}") > strings.Index(got, "@get(") {
t.Fatalf("@get must sit outside the catch, after it:\n%s", got)
}
}
// The fragment lands inside a JS string inside an HTML attribute with
// escaping switched off. Nothing in it may close either quote.
func TestSyncURLAttrNeutralizesQuotes(t *testing.T) {
got := string(syncURLAttr(`/x?a='+alert(1)+'&b="&c=\`))
if strings.Contains(got, `'+alert(1)+'`) {
t.Fatalf("single quote survived into the JS string:\n%s", got)
}
if strings.Contains(got, `b="`) {
t.Fatalf("double quote survived into the attribute:\n%s", got)
}
for _, want := range []string{"%27", "%22", "%5C"} {
if !strings.Contains(got, want) {
t.Fatalf("expected %s escape in:\n%s", want, got)
}
}
}
// The README/LICENSE tabs are the surface this was built for: their
// href is already the real, cold-loadable route, so the address bar must
// follow it. Render the panel and check the anchor carries both halves.
func TestDocTabsSyncTheURL(t *testing.T) {
// Built the way the handler builds it — from the op's JSON — so the
// test cannot drift from the shape the page actually renders.
var about aboutData
if err := json.Unmarshal([]byte(`{"docPath":"README.md","docs":[
{"path":"README.md","label":"README"},
{"path":"LICENSE","label":"License"}]}`), &about); err != nil {
t.Fatal(err)
}
data := pageData{Repo: "demo", About: &about}
var buf strings.Builder
if err := noHooks.template(treePage).ExecuteTemplate(&buf, "doc-panel", data); err != nil {
t.Fatal(err)
}
got := buf.String()
// The no-script path: a plain load of the same view.
if !strings.Contains(got, `href="/r/demo?doc=LICENSE"`) {
t.Fatalf("license tab lost its real href:\n%s", got)
}
// The scripted path: fragment fetch AND address-bar correction.
if !strings.Contains(got, `@get('/r/demo/doc.frag?doc=LICENSE')`) {
t.Fatalf("license tab lost its fragment fetch:\n%s", got)
}
if !strings.Contains(got, `history.replaceState`) {
t.Fatalf("doc tabs swap content without correcting the URL:\n%s", got)
}
}