package web
import (
"context"
"net/http"
"net/url"
"strings"
"testing"
"sourcecraft.dev/bigbes/sr-ht-dolt/browse"
"sourcecraft.dev/bigbes/sr-ht-dolt/core"
)
// dummyView is a test-only View that fingerprints repos containing a table
// named "issues". It renders through an EXISTING page template ("404.html") so
// the test needs no newly embedded file. That template references only the
// shared chrome (basePage), so it renders cleanly with the fixed view envelope
// (which does not carry a .Tables field the way tree.html would demand). It
// records whether Build ran.
type dummyView struct {
built bool
}
func (d *dummyView) Name() string { return "issues" }
func (d *dummyView) Label() string { return "Issues" }
func (d *dummyView) Template() string { return "404.html" }
func (d *dummyView) Applies(tables []browse.TableInfo) bool {
for _, t := range tables {
if t.Name == "issues" {
return true
}
}
return false
}
func (d *dummyView) Build(_ context.Context, sess BrowseSession, _ *core.Repo, ref string, _ url.Values) (any, error) {
d.built = true
// Pull data through the BrowseSession surface only, mirroring how a real
// view works, and return the tables so tree.html renders something.
tables, err := sess.Tables(context.Background(), ref)
if err != nil {
return nil, err
}
return tables, nil
}
func issueTables() []browse.TableInfo {
return []browse.TableInfo{
{Name: "issues", Columns: []browse.ColumnInfo{{Name: "id", PrimaryKey: true}}, RowCount: 2},
{Name: "labels", RowCount: 5},
}
}
func TestApplicableViewsFilters(t *testing.T) {
v := &dummyView{}
views := []View{v}
got := applicableViews(views, issueTables())
if len(got) != 1 || got[0] != v {
t.Fatalf("applicableViews should select the matching view; got %v", got)
}
// No "issues" table → no applicable views.
none := applicableViews(views, []browse.TableInfo{{Name: "widgets"}})
if len(none) != 0 {
t.Fatalf("applicableViews should filter out non-matching views; got %v", none)
}
}
func TestApplicableViewsPreservesOrder(t *testing.T) {
a := &dummyView{}
// A second always-applies view to check registration order is preserved.
b := alwaysView{}
got := applicableViews([]View{b, a}, issueTables())
if len(got) != 2 || got[0] != View(b) || got[1] != View(a) {
t.Fatalf("applicableViews should preserve order; got %v", got)
}
}
// alwaysView is a trivial always-applicable view used to check ordering.
type alwaysView struct{}
func (alwaysView) Name() string { return "always" }
func (alwaysView) Label() string { return "Always" }
func (alwaysView) Template() string { return "404.html" }
func (alwaysView) Applies([]browse.TableInfo) bool { return true }
func (alwaysView) Build(context.Context, BrowseSession, *core.Repo, string, url.Values) (any, error) {
return nil, nil
}
func TestHandleViewSelectedAndBuilt(t *testing.T) {
h := newHarness(t)
h.store.add(&core.Repo{Name: "db", OwnerID: 1, OwnerName: "alice", Path: "/d", Visibility: core.VisibilityPublic})
h.browse.sess = &fakeSession{
branches: []browse.Branch{{Name: "main", Head: "abcdef1234567890"}},
tables: issueTables(),
}
dv := &dummyView{}
// Inject the view directly on the app, without polluting the global registry.
setViews(t, h, dv)
rec := h.do("GET", "/~alice/db/view/issues", nil, nil)
// A 200 (rather than the 404 an unknown/non-applicable view returns) proves
// the view was selected and its Template rendered; dv.built proves Build ran.
if rec.Code != http.StatusOK {
t.Fatalf("view page: got %d, want 200; body=%s", rec.Code, rec.Body.String())
}
if !dv.built {
t.Fatalf("view.Build was not called")
}
// The view's Label flows into the page <title> via the envelope's basePage,
// so its presence confirms this view (not a fallback) produced the response.
if !strings.Contains(rec.Body.String(), "Issues") {
t.Fatalf("view page missing view label in chrome; body=%s", rec.Body.String())
}
}
func TestHandleViewUnknownSlugIs404(t *testing.T) {
h := newHarness(t)
h.store.add(&core.Repo{Name: "db", OwnerID: 1, OwnerName: "alice", Path: "/d", Visibility: core.VisibilityPublic})
h.browse.sess = &fakeSession{
branches: []browse.Branch{{Name: "main", Head: "abcdef1234567890"}},
tables: issueTables(),
}
setViews(t, h, &dummyView{})
rec := h.do("GET", "/~alice/db/view/nope", nil, nil)
if rec.Code != http.StatusNotFound {
t.Fatalf("unknown view slug: got %d, want 404", rec.Code)
}
}
func TestHandleViewNotApplicableIs404(t *testing.T) {
h := newHarness(t)
h.store.add(&core.Repo{Name: "db", OwnerID: 1, OwnerName: "alice", Path: "/d", Visibility: core.VisibilityPublic})
// Tables lack "issues", so dummyView.Applies is false even though the slug matches.
h.browse.sess = &fakeSession{
branches: []browse.Branch{{Name: "main", Head: "abcdef1234567890"}},
tables: []browse.TableInfo{{Name: "widgets"}},
}
setViews(t, h, &dummyView{})
rec := h.do("GET", "/~alice/db/view/issues", nil, nil)
if rec.Code != http.StatusNotFound {
t.Fatalf("non-applicable view: got %d, want 404", rec.Code)
}
}
func TestOverviewShowsViewTabs(t *testing.T) {
h := newHarness(t)
h.store.add(&core.Repo{Name: "db", OwnerID: 1, OwnerName: "alice", Path: "/d", Visibility: core.VisibilityPublic})
h.browse.sess = &fakeSession{
branches: []browse.Branch{{Name: "main", Head: "abcdef1234567890"}},
tables: issueTables(),
}
setViews(t, h, &dummyView{})
rec := h.do("GET", "/~alice/db", nil, nil)
if rec.Code != http.StatusOK {
t.Fatalf("overview: got %d, want 200", rec.Code)
}
body := rec.Body.String()
if !strings.Contains(body, "/~alice/db/view/issues") {
t.Fatalf("overview missing view tab link; body=%s", body)
}
if !strings.Contains(body, "nav-tabs") {
t.Fatalf("overview missing tab bar; body=%s", body)
}
}
// setViews overrides the app's view snapshot without touching the global
// registry, so view routing/overview tabs can be exercised with a controlled
// set. The harness retains the *app the router is mounted on.
func setViews(t *testing.T, h *harness, vs ...View) {
t.Helper()
h.app.views = vs
}