Files
member-console/internal/server/operator_pilot_anatomy_test.go
T
cgalo5758 408fa6f5a6 Add page anatomy parts and UI quality gate
- 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
2026-08-30 04:05:31 -05:00

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")
}
})
}