package html import ( "errors" "strings" "testing" ) func TestLayout_HLCRF(t *testing.T) { 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{"`, `
`, ``} { if !strings.Contains(got, want) { t.Errorf("empty slot should still render %q in:\n%s", want, got) } } } func TestValidateLayoutVariant(t *testing.T) { tests := []struct { name string variant string wantErr bool }{ {name: "valid", variant: "HCF", wantErr: false}, {name: "invalid", variant: "HXC", wantErr: true}, {name: "empty", variant: "", wantErr: false}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { err := ValidateLayoutVariant(tt.variant) if tt.wantErr { if err == nil { t.Fatalf("ValidateLayoutVariant(%q) = nil, want error", tt.variant) } if !errors.Is(err, ErrInvalidLayoutVariant) { t.Fatalf("ValidateLayoutVariant(%q) = %v, want ErrInvalidLayoutVariant", tt.variant, err) } return } if err != nil { t.Fatalf("ValidateLayoutVariant(%q) = %v, want nil", tt.variant, err) } }) } } func TestLayout_VariantError(t *testing.T) { tests := []struct { name string variant string wantErr bool wantErrString string wantRender string }{ { name: "valid variant", variant: "HCF", wantRender: `
header
` + `
main
` + ``, }, { name: "mixed invalid variant", variant: "HXC", wantErr: true, wantErrString: "html: invalid layout variant HXC (invalid slot: 'X' at position 2)", wantRender: `
header
` + `
main
`, }, { name: "multiple invalid slots", variant: "H1X?", wantErr: true, wantErrString: "html: invalid layout variant H1X? (invalid slots: '1' at position 2, 'X' at position 3, '?' at position 4)", wantRender: `
header
`, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { layout := NewLayout(tt.variant) layout.H(Raw("header")).C(Raw("main")).F(Raw("footer")) if tt.wantErr { if layout.VariantError() == nil { t.Fatalf("VariantError() = nil, want sentinel error for %q", tt.variant) } if !errors.Is(layout.VariantError(), ErrInvalidLayoutVariant) { t.Fatalf("VariantError() = %v, want errors.Is(..., ErrInvalidLayoutVariant)", layout.VariantError()) } if got := layout.VariantError().Error(); got != tt.wantErrString { t.Fatalf("VariantError().Error() = %q, want %q", got, tt.wantErrString) } if err, ok := layout.VariantError().(*LayoutVariantError); ok { switch tt.variant { case "HXC": if got := string(err.InvalidSlots()); got != "X" { t.Fatalf("InvalidSlots() = %q, want %q", got, "X") } if got := err.InvalidPositions(); len(got) != 1 || got[0] != 2 { t.Fatalf("InvalidPositions() = %v, want %v", got, []int{2}) } case "H1X?": if got := string(err.InvalidSlots()); got != "1X?" { t.Fatalf("InvalidSlots() = %q, want %q", got, "1X?") } if got := err.InvalidPositions(); len(got) != 3 || got[0] != 2 || got[1] != 3 || got[2] != 4 { t.Fatalf("InvalidPositions() = %v, want %v", got, []int{2, 3, 4}) } } } else { t.Fatalf("VariantError() has unexpected concrete type %T", layout.VariantError()) } } else if layout.VariantError() != nil { t.Fatalf("VariantError() = %v, want nil", layout.VariantError()) } if got := layout.VariantValid(); got != !tt.wantErr { t.Fatalf("VariantValid() = %v, want %v", got, !tt.wantErr) } got := layout.Render(NewContext()) if got != tt.wantRender { t.Fatalf("Render() = %q, want %q", got, tt.wantRender) } }) } } func TestLayout_RenderNilReceiver(t *testing.T) { var layout *Layout got := layout.Render(NewContext()) if got != "" { t.Fatalf("nil Layout should render empty string, got %q", got) } } func TestLayout_BuilderNilReceiver(t *testing.T) { var layout *Layout if got := layout.H(Raw("header")); got != nil { t.Fatalf("nil Layout.H() should return nil, got %v", got) } if got := layout.L(Raw("left")); got != nil { t.Fatalf("nil Layout.L() should return nil, got %v", got) } if got := layout.C(Raw("main")); got != nil { t.Fatalf("nil Layout.C() should return nil, got %v", got) } if got := layout.R(Raw("right")); got != nil { t.Fatalf("nil Layout.R() should return nil, got %v", got) } if got := layout.F(Raw("footer")); got != nil { t.Fatalf("nil Layout.F() should return nil, got %v", got) } } func TestLayout_RenderNilContext(t *testing.T) { layout := NewLayout("C").C(Raw("content")) got := layout.Render(nil) if !strings.Contains(got, `data-block="C-0"`) { t.Fatalf("Layout.Render(nil) should still render the block ID, got:\n%s", got) } if !strings.Contains(got, "content") { t.Fatalf("Layout.Render(nil) should still render content, got:\n%s", got) } }