- Add shared ui_*.html parts (pageHeader, sectionHeader, statusBadge, emptyState) parsed into every template set - Add anatomy lint rules with a shrinking allowlist and screen-coverage check - Add make screens capture harness with contact sheets and baseline diff - Compose member and FedWiki regions server-side so pages arrive complete - Rebuild Domains and Integrations on the parts as pilots
115 lines
5.0 KiB
Go
115 lines
5.0 KiB
Go
package server
|
|
|
|
import (
|
|
"bytes"
|
|
"html/template"
|
|
"io/fs"
|
|
"strings"
|
|
"testing"
|
|
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/domains"
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/embeds"
|
|
"git.coopcloud.tech/wiki-cafe/member-console/internal/web"
|
|
)
|
|
|
|
// pilotSet parses the operator partials with the shared parts, the way the
|
|
// operator partials set does.
|
|
func pilotSet(t *testing.T) *template.Template {
|
|
t.Helper()
|
|
partialsSub, err := fs.Sub(embeds.Templates, "templates/partials")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
tmpl := template.New("pilot").Funcs(template.FuncMap{
|
|
"routeURL": web.RouteURL,
|
|
"renderBody": func(string, any) (template.HTML, error) { return "", nil },
|
|
"fieldErr": func(_, _, _, _ string, _, _ any) string { return "" },
|
|
"stripeEntityURL": func(string, string) string { return "" },
|
|
})
|
|
return template.Must(web.ParseUIPartials(template.Must(tmpl.ParseFS(partialsSub, "operator_*.html"))))
|
|
}
|
|
|
|
func renderPilot(t *testing.T, name string, data any) string {
|
|
t.Helper()
|
|
var buf bytes.Buffer
|
|
if err := pilotSet(t).ExecuteTemplate(&buf, name, data); err != nil {
|
|
t.Fatalf("render %s: %v", name, err)
|
|
}
|
|
return buf.String()
|
|
}
|
|
|
|
// TestPilotPagesUseTheParts pins page-anatomy "The pilot pages use the
|
|
// parts": Domains and Integrations render their title, sections, badges,
|
|
// and empty states through ui_*.html and nothing else, at the one title
|
|
// size and table density.
|
|
func TestPilotPagesUseTheParts(t *testing.T) {
|
|
t.Run("integrations", func(t *testing.T) {
|
|
out := renderPilot(t, "operator_integrations.html", IntegrationsData{
|
|
Integrations: []IntegrationRow{{Key: "acme", DisplayName: "Acme", Kind: "provisioning", Status: "active", Operations: []string{"create"}, MissingKeysText: "acme-key"}},
|
|
Orphans: []OrphanOverrideRow{{Key: "k", Value: "v"}},
|
|
})
|
|
for _, want := range []string{
|
|
`<h1 class="h2 mb-0">Integrations</h1>`,
|
|
`>1 integration</span>`,
|
|
`<table class="table table-hover table-sm align-middle">`,
|
|
`<span class="badge text-bg-success">Active</span>`,
|
|
`<span class="badge text-bg-warning" title="Missing: acme-key">Not configured</span>`,
|
|
`<code class="small me-1">create</code>`,
|
|
`<h2 class="h5 mb-0">Unrecognized overrides</h2>`,
|
|
} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("integrations missing %q", want)
|
|
}
|
|
}
|
|
if strings.Contains(out, `<h1 class="mb-1">`) || strings.Contains(out, `text-bg-light border me-1`) || strings.Contains(out, "integration(s) · driven by") {
|
|
t.Error("old header, operation pills, or footer count still render")
|
|
}
|
|
empty := renderPilot(t, "operator_integrations.html", IntegrationsData{})
|
|
if !strings.Contains(empty, `<p class="text-muted mb-2">No integrations registered.</p>`) || strings.Contains(empty, "py-5") {
|
|
t.Error("empty branch must render through emptyState")
|
|
}
|
|
})
|
|
|
|
t.Run("domains", func(t *testing.T) {
|
|
out := renderPilot(t, "operator_domains.html", OperatorDomainsData{
|
|
Claims: []OperatorDomainClaimRow{{ClaimID: "c1", Root: "a.example", Kind: domains.KindExternal, Status: domains.StatusPending, OrgID: "o1", OrgName: "Org", Evidence: true, Releasable: true}},
|
|
Placements: []OperatorPlacementRow{{Name: "a.example", Provider: "fedwiki", ResourceRef: "site-1", Servable: true}},
|
|
TerminalClaims: []OperatorTerminalClaimRow{{Root: "b.example", Kind: domains.KindOperatorRoot, Status: domains.StatusExpired}},
|
|
Policy: OperatorDomainPolicyView{ClaimWindow: "72h"},
|
|
})
|
|
for _, want := range []string{
|
|
`<h1 class="h2 mb-0">Domains</h1>`,
|
|
`>1 live claim</span>`,
|
|
`<summary class="app-section-header mb-2"><h2 class="h5 d-inline mb-0">Effective claim policy</h2></summary>`,
|
|
`Name policy governs carving inside operator roots only`,
|
|
`<span class="badge text-bg-light border">External</span>`,
|
|
`<span class="badge text-bg-warning">Pending</span>`,
|
|
`<span class="badge text-bg-info">Seen</span>`,
|
|
`<h2 class="h5 mb-0">Placements</h2>`,
|
|
`>1 served name</span>`,
|
|
`<span class="badge text-bg-success">Servable</span>`,
|
|
`<summary class="app-section-header mb-2"><h2 class="h5 d-inline mb-0">Claim history (1)</h2></summary>`,
|
|
`<span class="badge text-bg-light border">Operator root</span>`,
|
|
`<span class="badge text-bg-secondary">Expired</span>`,
|
|
} {
|
|
if !strings.Contains(out, want) {
|
|
t.Errorf("domains missing %q", want)
|
|
}
|
|
}
|
|
for _, banned := range []string{`h1 class="h4`, "border rounded", "operator_root<", ">servable<", `class="badge {{`} {
|
|
if strings.Contains(out, banned) {
|
|
t.Errorf("domains still renders %q", banned)
|
|
}
|
|
}
|
|
empty := renderPilot(t, "operator_domains.html", OperatorDomainsData{})
|
|
for _, want := range []string{"No live domain claims.", "No placements. Nothing is currently served.", "No expired, canceled, or released claims yet."} {
|
|
if !strings.Contains(empty, `<p class="text-muted mb-2">`+want+`</p>`) {
|
|
t.Errorf("domains empty state %q must render through emptyState", want)
|
|
}
|
|
}
|
|
if strings.Contains(empty, "border") {
|
|
t.Error("no bordered empty boxes may remain")
|
|
}
|
|
})
|
|
}
|