fix: add nil-safe rendering paths
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
3d2fdf4e22
commit
ba384aeb12
9 changed files with 75 additions and 0 deletions
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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
31
node.go
|
|
@ -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))
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,5 +5,8 @@ func Render(node Node, ctx *Context) string {
|
|||
if ctx == nil {
|
||||
ctx = NewContext()
|
||||
}
|
||||
if node == nil {
|
||||
return ""
|
||||
}
|
||||
return node.Render(ctx)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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="`)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue