~bigbes/sr-ht-dolt

ref: 1dc49e2ecffdcfd6e0503ef183771850451e5eab sr-ht-dolt/web/views_test.go -rw-r--r-- 6.7 KiB
1dc49e2e — Eugene Blikh apk: ship dolt-git-hook as a -hook subpackage 11 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
package web

import (
	"context"
	"net/http"
	"net/url"
	"regexp"
	"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)
	}
	// The bar carries three tabs in order: Overview, Tables, then the view.
	iOverview := strings.Index(body, ">Overview<")
	iTables := strings.Index(body, ">Tables<")
	iView := strings.Index(body, "/~alice/db/view/issues")
	if iOverview < 0 || iTables < 0 {
		t.Fatalf("overview missing Overview/Tables tabs; body=%s", body)
	}
	if !(iOverview < iTables && iTables < iView) {
		t.Fatalf("tab order should be Overview < Tables < view; got %d, %d, %d", iOverview, iTables, iView)
	}
	// The active tab on the overview is Overview, not Tables.
	if !regexp.MustCompile(`nav-link active"[^>]*>Overview<`).MatchString(body) {
		t.Fatalf("overview tab should be active on the overview page; 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
}