fix: add nil-safe rendering paths
Some checks are pending
Security Scan / security (push) Waiting to run
Test / test (push) Waiting to run

Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
Virgil 2026-04-03 16:55:48 +00:00
parent 3d2fdf4e22
commit ba384aeb12
9 changed files with 75 additions and 0 deletions

View file

@ -111,6 +111,10 @@ func (l *Layout) VariantError() error {
// Render produces the semantic HTML for this layout.
// Only slots present in the variant string are rendered.
func (l *Layout) Render(ctx *Context) string {
if l == nil {
return ""
}
var b strings.Builder
for i := range len(l.variant) {

View file

@ -211,3 +211,11 @@ func TestLayout_VariantError(t *testing.T) {
})
}
}
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)
}
}

31
node.go
View file

@ -96,6 +96,9 @@ func Raw(content string) Node {
}
func (n *rawNode) Render(_ *Context) string {
if n == nil {
return ""
}
return n.content
}
@ -156,6 +159,10 @@ func TabIndex(n Node, index int) Node {
}
func (n *elNode) Render(ctx *Context) string {
if n == nil {
return ""
}
var b strings.Builder
b.WriteByte('<')
@ -210,6 +217,10 @@ func Text(key string, args ...any) Node {
}
func (n *textNode) Render(ctx *Context) string {
if n == nil {
return ""
}
var text string
if ctx != nil && ctx.service != nil {
text = ctx.service.T(n.key, n.args...)
@ -232,6 +243,10 @@ func If(cond func(*Context) bool, node Node) Node {
}
func (n *ifNode) Render(ctx *Context) string {
if n == nil || n.cond == nil || n.node == nil {
return ""
}
if n.cond(ctx) {
return n.node.Render(ctx)
}
@ -251,6 +266,10 @@ func Unless(cond func(*Context) bool, node Node) Node {
}
func (n *unlessNode) Render(ctx *Context) string {
if n == nil || n.cond == nil || n.node == nil {
return ""
}
if !n.cond(ctx) {
return n.node.Render(ctx)
}
@ -271,6 +290,10 @@ func Entitled(feature string, node Node) Node {
}
func (n *entitledNode) Render(ctx *Context) string {
if n == nil || n.node == nil {
return ""
}
if ctx == nil || ctx.Entitlements == nil || !ctx.Entitlements(n.feature) {
return ""
}
@ -290,6 +313,10 @@ func Switch(selector func(*Context) string, cases map[string]Node) Node {
}
func (n *switchNode) Render(ctx *Context) string {
if n == nil || n.selector == nil || n.cases == nil {
return ""
}
key := n.selector(ctx)
if node, ok := n.cases[key]; ok {
return node.Render(ctx)
@ -315,6 +342,10 @@ func EachSeq[T any](items iter.Seq[T], fn func(T) Node) Node {
}
func (n *eachNode[T]) Render(ctx *Context) string {
if n == nil || n.items == nil || n.fn == nil {
return ""
}
var b strings.Builder
for item := range n.items {
b.WriteString(n.fn(item).Render(ctx))

View file

@ -62,6 +62,9 @@ func CompareVariants(r *Responsive, ctx *Context) map[string]float64 {
if ctx == nil {
ctx = NewContext()
}
if r == nil {
return map[string]float64{}
}
type named struct {
name string

View file

@ -128,3 +128,10 @@ func TestCompareVariants(t *testing.T) {
t.Errorf("same content in different variants should score >= 0.8, got %f", sim)
}
}
func TestCompareVariants_NilResponsive(t *testing.T) {
scores := CompareVariants(nil, NewContext())
if len(scores) != 0 {
t.Fatalf("CompareVariants(nil, ctx) = %v, want empty map", scores)
}
}

View file

@ -5,5 +5,8 @@ func Render(node Node, ctx *Context) string {
if ctx == nil {
ctx = NewContext()
}
if node == nil {
return ""
}
return node.Render(ctx)
}

View file

@ -95,3 +95,10 @@ func TestRender_XSSPrevention(t *testing.T) {
t.Errorf("XSS prevention: expected escaped script tag, got:\n%s", got)
}
}
func TestRender_NilNode(t *testing.T) {
got := Render(nil, NewContext())
if got != "" {
t.Fatalf("Render(nil, ctx) = %q, want empty string", got)
}
}

View file

@ -65,6 +65,10 @@ func (r *Responsive) Variant(name string, layout *Layout) *Responsive {
// Render produces HTML with each variant in a data-variant container.
func (r *Responsive) Render(ctx *Context) string {
if r == nil {
return ""
}
var b strings.Builder
for _, v := range r.variants {
b.WriteString(`<div data-variant="`)

View file

@ -88,6 +88,14 @@ func TestResponsive_ImplementsNode(t *testing.T) {
var _ Node = NewResponsive()
}
func TestResponsive_RenderNilReceiver(t *testing.T) {
var r *Responsive
got := r.Render(NewContext())
if got != "" {
t.Fatalf("nil Responsive should render empty string, got %q", got)
}
}
func TestVariantSelector(t *testing.T) {
tests := []struct {
name string