, got %d", count)
}
if count := countText(got, "| "); count != 9 {
t.Errorf("nested Each: expected 9 | , got %d", count)
}
if !containsText(got, "1-b") {
t.Error("nested Each: missing cell content '1-b'")
}
}
// --- Layout variant validation ---
func TestLayout_InvalidVariantChars_Bad(t *testing.T) {
ctx := NewContext()
tests := []struct {
name string
variant string
}{
{"all invalid", "XYZ"},
{"lowercase valid", "hlcrf"},
{"numbers", "123"},
{"special chars", "!@#"},
{"mixed valid and invalid", "HXC"},
{"empty string", ""},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
layout := NewLayout(tt.variant).
H(Raw("header")).L(Raw("left")).C(Raw("main")).R(Raw("right")).F(Raw("footer"))
got := layout.Render(ctx)
// Invalid variant chars should silently produce no output for those slots
// This documents the current behaviour: no panic, no error.
if tt.variant == "XYZ" || tt.variant == "hlcrf" || tt.variant == "123" ||
tt.variant == "!@#" || tt.variant == "" {
if got != "" {
t.Errorf("NewLayout(%q) with all invalid chars should produce empty output, got %q", tt.variant, got)
}
}
})
}
}
func TestLayout_VariantError_Bad(t *testing.T) {
tests := []struct {
name string
variant string
wantInvalid bool
wantErrString string
build func(*Layout)
wantRender string
}{
{
name: "valid variant",
variant: "HCF",
wantInvalid: false,
build: func(layout *Layout) {
layout.H(Raw("header")).C(Raw("main")).F(Raw("footer"))
},
wantRender: `main`,
},
{
name: "mixed invalid variant",
variant: "HXC",
wantInvalid: true,
wantErrString: "html: invalid layout variant HXC",
build: func(layout *Layout) {
layout.H(Raw("header")).C(Raw("main"))
},
wantRender: `main`,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
layout := NewLayout(tt.variant)
if tt.build != nil {
tt.build(layout)
}
if tt.wantInvalid {
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)
}
} else if layout.VariantError() != nil {
t.Fatalf("VariantError() = %v, want nil", layout.VariantError())
}
got := layout.Render(NewContext())
if got != tt.wantRender {
t.Fatalf("Render() = %q, want %q", got, tt.wantRender)
}
})
}
}
func TestValidateLayoutVariant_Good(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_InvalidVariantMixedValidInvalid_Bad(t *testing.T) {
ctx := NewContext()
// "HXC" — H and C are valid, X is not. Only H and C should render.
layout := NewLayout("HXC").
H(Raw("header")).C(Raw("main"))
got := layout.Render(ctx)
if !containsText(got, "header") {
t.Errorf("HXC variant should render H slot, got:\n%s", got)
}
if !containsText(got, "main") {
t.Errorf("HXC variant should render C slot, got:\n%s", got)
}
// Should only have 2 semantic elements
if count := countText(got, "data-block="); count != 2 {
t.Errorf("HXC variant should produce 2 blocks, got %d in:\n%s", count, got)
}
}
func TestLayout_DuplicateVariantChars_Ugly(t *testing.T) {
ctx := NewContext()
// "CCC" — C appears three times. Should render C slot content three times.
layout := NewLayout("CCC").C(Raw("content"))
got := layout.Render(ctx)
count := countText(got, "content")
if count != 3 {
t.Errorf("CCC variant should render C slot 3 times, got %d occurrences in:\n%s", count, got)
}
}
func TestLayout_EmptySlots_Ugly(t *testing.T) {
ctx := NewContext()
// Variant includes all slots but none are populated — should produce empty output.
layout := NewLayout("HLCRF")
got := layout.Render(ctx)
if got != "" {
t.Errorf("layout with no slot content should produce empty output, got %q", got)
}
}
func TestLayout_NestedThroughIf_Ugly(t *testing.T) {
ctx := NewContext()
inner := NewLayout("C").C(Raw("wrapped"))
outer := NewLayout("C").C(If(func(*Context) bool { return true }, inner))
got := outer.Render(ctx)
if !containsText(got, `data-block="C-0-C-0"`) {
t.Fatalf("nested layout inside If should inherit block path, got:\n%s", got)
}
}
func TestLayout_NestedThroughSwitch_Ugly(t *testing.T) {
ctx := NewContext()
inner := NewLayout("C").C(Raw("wrapped"))
outer := NewLayout("C").C(Switch(func(*Context) string { return "match" }, map[string]Node{
"match": inner,
"miss": Raw("ignored"),
}))
got := outer.Render(ctx)
if !containsText(got, `data-block="C-0-C-0"`) {
t.Fatalf("nested layout inside Switch should inherit block path, got:\n%s", got)
}
}
// --- Render convenience function edge cases ---
func TestRender_NilContext_Ugly(t *testing.T) {
node := Raw("test")
got := Render(node, nil)
if got != "test" {
t.Errorf("Render with nil context = %q, want %q", got, "test")
}
}
func TestImprint_NilContext_Ugly(t *testing.T) {
svc, _ := i18n.New()
i18n.SetDefault(svc)
node := NewLayout("C").C(El("p", Text("Building project")))
imp := Imprint(node, nil)
if imp.TokenCount == 0 {
t.Error("Imprint with nil context should still produce tokens")
}
}
func TestCompareVariants_NilContext_Ugly(t *testing.T) {
svc, _ := i18n.New()
i18n.SetDefault(svc)
r := NewResponsive().
Variant("a", NewLayout("C").C(Text("Building project"))).
Variant("b", NewLayout("C").C(Text("Building project")))
scores := CompareVariants(r, nil)
if _, ok := scores["a:b"]; !ok {
t.Error("CompareVariants with nil context should still produce scores")
}
}
func TestCompareVariants_SingleVariant_Ugly(t *testing.T) {
svc, _ := i18n.New()
i18n.SetDefault(svc)
r := NewResponsive().
Variant("only", NewLayout("C").C(Text("Building project")))
scores := CompareVariants(r, NewContext())
if len(scores) != 0 {
t.Errorf("CompareVariants with single variant should produce no pairs, got %d", len(scores))
}
}
// --- escapeHTML / escapeAttr edge cases ---
func TestEscapeAttr_AllSpecialChars_Ugly(t *testing.T) {
ctx := NewContext()
node := Attr(El("div"), "data-val", `&<>"'`)
got := node.Render(ctx)
if containsText(got, `"&<>"'"`) {
t.Error("attribute value with special chars must be fully escaped")
}
if !containsText(got, "&<>"'") {
t.Errorf("expected all special chars escaped in attribute, got: %s", got)
}
}
func TestElNode_EmptyTag_Ugly(t *testing.T) {
ctx := NewContext()
node := El("", Raw("content"))
got := node.Render(ctx)
// Empty tag is weird but should not panic
if !containsText(got, "content") {
t.Errorf("El with empty tag should still render children, got %q", got)
}
}
func TestSwitchNode_NoMatch_Ugly(t *testing.T) {
ctx := NewContext()
cases := map[string]Node{
"a": Raw("alpha"),
"b": Raw("beta"),
}
node := Switch(func(*Context) string { return "c" }, cases)
got := node.Render(ctx)
if got != "" {
t.Errorf("Switch with no matching case should produce empty string, got %q", got)
}
}
func TestEntitled_NilContext_Ugly(t *testing.T) {
node := Entitled("premium", Raw("content"))
got := node.Render(nil)
if got != "" {
t.Errorf("Entitled with nil context should produce empty string, got %q", got)
}
}
|