diff --git a/layout.go b/layout.go index 12792b0..25dbc4f 100644 --- a/layout.go +++ b/layout.go @@ -37,38 +37,63 @@ func NewLayout(variant string) *Layout { } } +func (l *Layout) slotsForSlot(slot byte) []Node { + if l == nil { + return nil + } + if l.slots == nil { + l.slots = make(map[byte][]Node) + } + return l.slots[slot] +} + // H appends nodes to the Header slot. // Usage example: NewLayout("HCF").H(Text("title")) func (l *Layout) H(nodes ...Node) *Layout { - l.slots['H'] = append(l.slots['H'], nodes...) + if l == nil { + return nil + } + l.slots['H'] = append(l.slotsForSlot('H'), nodes...) return l } // L appends nodes to the Left aside slot. // Usage example: NewLayout("LC").L(Text("nav")) func (l *Layout) L(nodes ...Node) *Layout { - l.slots['L'] = append(l.slots['L'], nodes...) + if l == nil { + return nil + } + l.slots['L'] = append(l.slotsForSlot('L'), nodes...) return l } // C appends nodes to the Content (main) slot. // Usage example: NewLayout("C").C(Text("body")) func (l *Layout) C(nodes ...Node) *Layout { - l.slots['C'] = append(l.slots['C'], nodes...) + if l == nil { + return nil + } + l.slots['C'] = append(l.slotsForSlot('C'), nodes...) return l } // R appends nodes to the Right aside slot. // Usage example: NewLayout("CR").R(Text("ads")) func (l *Layout) R(nodes ...Node) *Layout { - l.slots['R'] = append(l.slots['R'], nodes...) + if l == nil { + return nil + } + l.slots['R'] = append(l.slotsForSlot('R'), nodes...) return l } // F appends nodes to the Footer slot. // Usage example: NewLayout("CF").F(Text("footer")) func (l *Layout) F(nodes ...Node) *Layout { - l.slots['F'] = append(l.slots['F'], nodes...) + if l == nil { + return nil + } + l.slots['F'] = append(l.slotsForSlot('F'), nodes...) return l } diff --git a/layout_test.go b/layout_test.go index c492d4c..e43c9e8 100644 --- a/layout_test.go +++ b/layout_test.go @@ -113,3 +113,27 @@ func TestLayout_IgnoresInvalidSlots_Good(t *testing.T) { t.Errorf("C variant should ignore R slot content, got:\n%s", got) } } + +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) + } +} diff --git a/responsive.go b/responsive.go index b6fca16..b892e8d 100644 --- a/responsive.go +++ b/responsive.go @@ -25,6 +25,9 @@ func NewResponsive() *Responsive { // Usage example: NewResponsive().Variant("desktop", NewLayout("HLCRF")) // Variants render in insertion order. func (r *Responsive) Variant(name string, layout *Layout) *Responsive { + if r == nil { + r = NewResponsive() + } r.variants = append(r.variants, responsiveVariant{name: name, layout: layout}) return r } diff --git a/responsive_test.go b/responsive_test.go index 62f3255..0d9aa41 100644 --- a/responsive_test.go +++ b/responsive_test.go @@ -86,3 +86,16 @@ func TestResponsive_VariantsIndependent_Good(t *testing.T) { func TestResponsive_ImplementsNode_Ugly(t *testing.T) { var _ Node = NewResponsive() } + +func TestResponsive_Variant_NilResponsive_Ugly(t *testing.T) { + var r *Responsive + + got := r.Variant("mobile", NewLayout("C").C(Raw("content"))) + if got == nil { + t.Fatal("expected non-nil responsive from Variant on nil receiver") + } + + if output := got.Render(NewContext()); output != `
content
` { + t.Fatalf("unexpected output from nil receiver Variant path: %q", output) + } +}