Files
member-console/internal/server/member_nav_test.go
T
cgalo5758 36e58cd821 Flatten operator sidebar to seven entries
Move sub-surfaces into their sections: billing views get a pill row,
org types a header button. Replace inline IdP handoff copy with an SVG
icon and tooltip, add help icons to dense form rows, and delete the
registry-driven sidebar nav plumbing. Update specs and tests.
2026-08-23 18:26:11 -05:00

87 lines
3.9 KiB
Go

package server_test
import (
"net/http/httptest"
"strings"
"testing"
)
// TestMemberNavSeparatesSessionControls pins the chrome-conventions delta
// (ux-ia-naming D8, task 7.3; maintainer 2026-08-23: the handoff marker is a
// subtle inline SVG icon plus a hover tooltip — never inline explanation
// copy that widens the nav, and never a character like U+2197 that takes
// emoji presentation and wraps to its own line): the member shell's
// session/account controls (Identity and Access, Logout) render as their own
// visually separated block after the task entries (Dashboard, Products,
// Billing, Operator when present), using the existing
// border-top-plus-spacing separator idiom rather than a novel style, and the
// Identity and Access control carries the external-link icon glued to the
// label (no whitespace, so no wrap opportunity) and a plain-words title
// tooltip.
func TestMemberNavSeparatesSessionControls(t *testing.T) {
st := rootTemplates(t)
render := func(data dashboardData) string {
t.Helper()
rec := httptest.NewRecorder()
st.Render(rec, "index.html", data)
if rec.Code != 200 {
t.Fatalf("expected 200, got %d: %s", rec.Code, rec.Body.String())
}
return rec.Body.String()
}
t.Run("session controls carry the border-top separator and the IdP handoff copy", func(t *testing.T) {
body := render(dashboardData{KeycloakAccountURL: "https://idp.example.test/account", IsOperator: true})
// The mobile nav list and the sidebar each carry one instance of the
// separator on the Identity and Access entry — two total, none on the
// task entries above it.
if got := strings.Count(body, `mt-2 pt-2 border-top`); got != 2 {
t.Errorf("expected 2 border-top-separated session blocks (mobile nav + sidebar), got %d", got)
}
// Inline SVG is an atomic inline — browsers allow a soft wrap before
// it even with no whitespace — so the icon must share a text-nowrap
// span with the label's last word to never land on its own line.
if got := strings.Count(body, `Identity and <span class="text-nowrap">Access<svg class="external-link-icon"`); got != 2 {
t.Errorf("expected the IdP handoff icon nowrap-bound to the label's last word twice (mobile nav + sidebar), got %d", got)
}
if strings.Contains(body, "↗") {
t.Error("the handoff marker must be the SVG icon, not the ↗ character (emoji presentation in common font stacks)")
}
if got := strings.Count(body, `title="Manage your account and sign-in. Opens in a new tab."`); got != 2 {
t.Errorf("expected the IdP handoff tooltip twice (mobile nav + sidebar), got %d", got)
}
if strings.Contains(body, "opens your identity provider") {
t.Error("inline handoff explanation copy must not render (it widens the nav; the marker is glyph + tooltip)")
}
// The separator sits on Identity and Access itself, immediately after
// Operator — task entries are not split apart by it.
for _, want := range []string{
`<a class="nav-link" href="/operator">Operator</a>`,
`mt-2 pt-2 border-top`,
`Identity and <span class="text-nowrap">Access`,
} {
if !strings.Contains(body, want) {
t.Errorf("body missing %q", want)
}
}
opIdx := strings.Index(body, `href="/operator">Operator</a>`)
sepIdx := strings.Index(body, `mt-2 pt-2 border-top`)
if opIdx == -1 || sepIdx == -1 || sepIdx < opIdx {
t.Error("the session-controls separator must come after the Operator task entry, not before it")
}
})
t.Run("no operator role: separator still isolates session controls from Dashboard/Products/Billing", func(t *testing.T) {
body := render(dashboardData{KeycloakAccountURL: "https://idp.example.test/account"})
if strings.Contains(body, `>Operator<`) {
t.Error("non-operator render must not show the Operator task entry")
}
if got := strings.Count(body, `mt-2 pt-2 border-top`); got != 2 {
t.Errorf("expected 2 border-top-separated session blocks, got %d", got)
}
})
}