2026-02-16 23:39:31 +00:00
|
|
|
package html
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"testing"
|
|
|
|
|
)
|
|
|
|
|
|
2026-03-26 18:12:06 +00:00
|
|
|
func TestLayout_HLCRF_Good(t *testing.T) {
|
2026-02-16 23:39:31 +00:00
|
|
|
ctx := NewContext()
|
|
|
|
|
layout := NewLayout("HLCRF").
|
|
|
|
|
H(Raw("header")).L(Raw("left")).C(Raw("main")).R(Raw("right")).F(Raw("footer"))
|
|
|
|
|
got := layout.Render(ctx)
|
|
|
|
|
|
|
|
|
|
// Must contain semantic elements
|
|
|
|
|
for _, want := range []string{"<header", "<aside", "<main", "<footer"} {
|
2026-03-26 15:24:16 +00:00
|
|
|
if !containsText(got, want) {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("HLCRF layout missing %q in:\n%s", want, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Must contain ARIA roles
|
|
|
|
|
for _, want := range []string{`role="banner"`, `role="complementary"`, `role="main"`, `role="contentinfo"`} {
|
2026-03-26 15:24:16 +00:00
|
|
|
if !containsText(got, want) {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("HLCRF layout missing role %q in:\n%s", want, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Must contain data-block IDs
|
|
|
|
|
for _, want := range []string{`data-block="H-0"`, `data-block="L-0"`, `data-block="C-0"`, `data-block="R-0"`, `data-block="F-0"`} {
|
2026-03-26 15:24:16 +00:00
|
|
|
if !containsText(got, want) {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("HLCRF layout missing %q in:\n%s", want, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Must contain content
|
|
|
|
|
for _, want := range []string{"header", "left", "main", "right", "footer"} {
|
2026-03-26 15:24:16 +00:00
|
|
|
if !containsText(got, want) {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("HLCRF layout missing content %q in:\n%s", want, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 18:12:06 +00:00
|
|
|
func TestLayout_HCF_Good(t *testing.T) {
|
2026-02-16 23:39:31 +00:00
|
|
|
ctx := NewContext()
|
|
|
|
|
layout := NewLayout("HCF").
|
|
|
|
|
H(Raw("header")).L(Raw("left")).C(Raw("main")).R(Raw("right")).F(Raw("footer"))
|
|
|
|
|
got := layout.Render(ctx)
|
|
|
|
|
|
|
|
|
|
// HCF should have header, main, footer
|
|
|
|
|
for _, want := range []string{`data-block="H-0"`, `data-block="C-0"`, `data-block="F-0"`} {
|
2026-03-26 15:24:16 +00:00
|
|
|
if !containsText(got, want) {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("HCF layout missing %q in:\n%s", want, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// HCF must NOT have L or R slots
|
|
|
|
|
for _, unwanted := range []string{`data-block="L-0"`, `data-block="R-0"`} {
|
2026-03-26 15:24:16 +00:00
|
|
|
if containsText(got, unwanted) {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("HCF layout should NOT contain %q in:\n%s", unwanted, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 18:12:06 +00:00
|
|
|
func TestLayout_ContentOnly_Good(t *testing.T) {
|
2026-02-16 23:39:31 +00:00
|
|
|
ctx := NewContext()
|
|
|
|
|
layout := NewLayout("C").
|
|
|
|
|
H(Raw("header")).L(Raw("left")).C(Raw("main")).R(Raw("right")).F(Raw("footer"))
|
|
|
|
|
got := layout.Render(ctx)
|
|
|
|
|
|
|
|
|
|
// Only C slot should render
|
2026-03-26 15:24:16 +00:00
|
|
|
if !containsText(got, `data-block="C-0"`) {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("C layout missing data-block=\"C-0\" in:\n%s", got)
|
|
|
|
|
}
|
2026-03-26 15:24:16 +00:00
|
|
|
if !containsText(got, "<main") {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("C layout missing <main in:\n%s", got)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// No other slots
|
|
|
|
|
for _, unwanted := range []string{`data-block="H-0"`, `data-block="L-0"`, `data-block="R-0"`, `data-block="F-0"`} {
|
2026-03-26 15:24:16 +00:00
|
|
|
if containsText(got, unwanted) {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("C layout should NOT contain %q in:\n%s", unwanted, got)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 18:12:06 +00:00
|
|
|
func TestLayout_FluentAPI_Good(t *testing.T) {
|
2026-02-16 23:39:31 +00:00
|
|
|
layout := NewLayout("HLCRF")
|
|
|
|
|
|
|
|
|
|
// Fluent methods should return the same layout for chaining
|
|
|
|
|
result := layout.H(Raw("h")).L(Raw("l")).C(Raw("c")).R(Raw("r")).F(Raw("f"))
|
|
|
|
|
if result != layout {
|
|
|
|
|
t.Error("fluent methods must return the same *Layout for chaining")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
got := layout.Render(NewContext())
|
|
|
|
|
if got == "" {
|
|
|
|
|
t.Error("fluent chain should produce non-empty output")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-26 18:12:06 +00:00
|
|
|
func TestLayout_IgnoresInvalidSlots_Good(t *testing.T) {
|
2026-02-16 23:39:31 +00:00
|
|
|
ctx := NewContext()
|
|
|
|
|
// "C" variant: populating L and R should have no effect
|
|
|
|
|
layout := NewLayout("C").L(Raw("left")).C(Raw("main")).R(Raw("right"))
|
|
|
|
|
got := layout.Render(ctx)
|
|
|
|
|
|
2026-03-26 15:24:16 +00:00
|
|
|
if !containsText(got, "main") {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("C variant should render main content, got:\n%s", got)
|
|
|
|
|
}
|
2026-03-26 15:24:16 +00:00
|
|
|
if containsText(got, "left") {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("C variant should ignore L slot content, got:\n%s", got)
|
|
|
|
|
}
|
2026-03-26 15:24:16 +00:00
|
|
|
if containsText(got, "right") {
|
2026-02-16 23:39:31 +00:00
|
|
|
t.Errorf("C variant should ignore R slot content, got:\n%s", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-30 00:28:03 +00:00
|
|
|
|
|
|
|
|
func TestLayout_Methods_NilLayout_Ugly(t *testing.T) {
|
|
|
|
|
var layout *Layout
|
|
|
|
|
|
|
|
|
|
if layout.H(Raw("h")) != nil {
|
|
|
|
|
t.Fatal("expected nil layout from H on nil receiver")
|
|
|
|
|
}
|
|
|
|
|
if layout.L(Raw("l")) != nil {
|
|
|
|
|
t.Fatal("expected nil layout from L on nil receiver")
|
|
|
|
|
}
|
|
|
|
|
if layout.C(Raw("c")) != nil {
|
|
|
|
|
t.Fatal("expected nil layout from C on nil receiver")
|
|
|
|
|
}
|
|
|
|
|
if layout.R(Raw("r")) != nil {
|
|
|
|
|
t.Fatal("expected nil layout from R on nil receiver")
|
|
|
|
|
}
|
|
|
|
|
if layout.F(Raw("f")) != nil {
|
|
|
|
|
t.Fatal("expected nil layout from F on nil receiver")
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if got := layout.Render(NewContext()); got != "" {
|
|
|
|
|
t.Fatalf("nil layout render should be empty, got %q", got)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-03-31 19:22:02 +00:00
|
|
|
|
|
|
|
|
func TestLayout_Render_NilContext_Good(t *testing.T) {
|
|
|
|
|
layout := NewLayout("C").C(Raw("content"))
|
|
|
|
|
|
|
|
|
|
got := layout.Render(nil)
|
|
|
|
|
want := `<main role="main" data-block="C-0">content</main>`
|
|
|
|
|
if got != want {
|
|
|
|
|
t.Fatalf("layout.Render(nil) = %q, want %q", got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|