Files
projax/web/collapsibles_test.go
mAi 2152c2d68e feat(web): Phase 8 Direction A — read-first detail page (markdown + edit mode)
Implements norman's approved Direction A (docs/plans/phase-8-detail-ux.md): a
projax item is a page you READ, with editing as a deliberate mode.

- READ MODE (default): content_md renders as MARKDOWN via goldmark (pure-Go,
  no cgo; SAFE-by-default — raw HTML omitted, no separate sanitizer needed;
  GFM for tables/strikethrough/autolinks). A compact chip header (status /
  management / tags / pinned / archived) + a single Edit toggle. Kills D1 (no
  read mode) and D7 (title/status rendered twice).
- EDIT MODE (?edit=1): the identity+content form (progressive-enhancement
  baseline — full-page toggle, no JS). Save → POST → back to read; Cancel
  returns to read. Rare settings (Public listing / Timeline behaviour) stay
  nested <details> disclosures inside the form (B's accordion discipline).
- WORK CARDS: Tasks (the Phase 7c unified list, dropped in UNCHANGED), Issues,
  Documents render as always-visible cards below the description, in BOTH modes
  (they were always HTMX-independent of the form). One visible card-title each;
  the in-partial h2 stays visually-hidden (Slice 0); #…-section ids + outerHTML
  swap targets preserved.
- Retired under A (Q6): the top-level proj-section collapse model + its
  localStorage persistence script + 'reset section state' link + the
  aux-divider/'Related' framing. Kept the secondary sub-disclosures (done
  tasks / closed issues) inside the partials.
- CSS: new .detail (760px reading column), .detail-card, .card-title,
  .markdown-body, .btn/.edit-toggle — all within the existing design tokens,
  no foreign system. Phone lands on description→tasks (settings behind Edit).

Tests: TestRenderMarkdown{,Empty,Safe,GFM}; rewrote the detail tests for the
read/edit split (TestDetailReadModeRendersMarkdown, TestDetailEditFieldsRender
InOrder, TestDetailWorkCardsAfterForm, TestDetailReadModeIsNotCollapsibles,
TestDetailEditModeKeepsSettingsDisclosures, updated TestDetailNoDoubleHeader +
public-listing). Only pre-existing failures remain (TestParityListAll,
TestProjectFilter*/TestTimeline* — route-drift on 6436b52).
2026-06-02 12:31:53 +02:00

75 lines
2.7 KiB
Go

package web_test
import (
"strings"
"testing"
)
// TestDetailNoDoubleHeader is the Phase 8 Slice 0 guard for m's literal
// complaint: each aux section showed TWO headers. Under Direction A the work
// sections are always-visible cards whose <h2 class="card-title"> is the single
// VISIBLE header; the partial's own <h2> is visually-hidden (it survives only
// as the a11y label for the standalone HTMX swap fragment). Documents always
// renders, so we assert on it.
func TestDetailNoDoubleHeader(t *testing.T) {
srv, pool := mustServer(t)
defer pool.Close()
h := srv.Routes()
_, body := get(t, h, "/i/dev")
// The visible card header.
if !strings.Contains(body, `class="card-title"`) {
t.Errorf("expected a visible card-title header on the read view")
}
// The in-partial h2 must be hidden (a11y label for the swap fragment).
if !strings.Contains(body, `<h2 class="visually-hidden">Documents</h2>`) {
t.Errorf("expected the Documents partial h2 to be visually-hidden")
}
// A bare, visible <h2>Documents</h2> would be the double-header regression.
if strings.Contains(body, `<h2>Documents</h2>`) {
t.Errorf("found a visible <h2>Documents</h2> — the double header has returned")
}
}
// TestDetailReadModeIsNotCollapsibles proves Direction A retired the top-level
// <details> collapse model + its localStorage persistence script: the read
// view renders work cards, not proj-section accordions, and the per-section
// toggle script is gone.
func TestDetailReadModeIsNotCollapsibles(t *testing.T) {
srv, pool := mustServer(t)
defer pool.Close()
h := srv.Routes()
code, body := get(t, h, "/i/dev")
if code != 200 {
t.Fatalf("GET /i/dev → %d", code)
}
if !strings.Contains(body, `id="documents-card"`) {
t.Errorf("read view missing the Documents work card")
}
// The retired collapse-persistence script must be gone from the read view.
if strings.Contains(body, `projax.section.`) {
t.Errorf("the per-section localStorage collapse script should be retired under Direction A")
}
if strings.Contains(body, `proj-section-reset`) {
t.Errorf("the 'reset section state' link should be retired under Direction A")
}
}
// TestDetailEditModeKeepsSettingsDisclosures proves the rare settings
// (public-listing / timeline-behaviour) remain nested <details> disclosures
// INSIDE the edit form (B's accordion discipline within edit mode).
func TestDetailEditModeKeepsSettingsDisclosures(t *testing.T) {
srv, pool := mustServer(t)
defer pool.Close()
h := srv.Routes()
_, body := get(t, h, "/i/dev?edit=1")
for _, want := range []string{
`data-section="public"`,
`data-section="timeline-behaviour"`,
`class="proj-section-summary"`,
} {
if !strings.Contains(body, want) {
t.Errorf("edit form missing settings disclosure %q", want)
}
}
}