fix(html): normalise nil render context
Co-Authored-By: Virgil <virgil@lethean.io>
This commit is contained in:
parent
aa00f27db4
commit
ae286563fd
8 changed files with 56 additions and 18 deletions
|
|
@ -33,6 +33,15 @@ func applyLocaleToService(svc Translator, locale string) {
|
|||
}
|
||||
}
|
||||
|
||||
// normaliseContext ensures render paths always have a usable context.
|
||||
// A nil input is replaced with a fresh default context.
|
||||
func normaliseContext(ctx *Context) *Context {
|
||||
if ctx != nil {
|
||||
return ctx
|
||||
}
|
||||
return NewContext()
|
||||
}
|
||||
|
||||
// context.go: NewContext creates a new rendering context with sensible defaults.
|
||||
// Example: ctx := NewContext("en-GB").
|
||||
// An optional locale may be provided as the first argument.
|
||||
|
|
|
|||
|
|
@ -133,6 +133,7 @@ func (l *Layout) Render(ctx *Context) string {
|
|||
if l == nil {
|
||||
return ""
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
|
||||
var b strings.Builder
|
||||
|
||||
|
|
|
|||
|
|
@ -226,3 +226,15 @@ func TestLayout_RenderNilReceiver(t *testing.T) {
|
|||
t.Fatalf("nil Layout should render empty string, got %q", 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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
25
node.go
25
node.go
|
|
@ -34,7 +34,7 @@ func renderNode(n Node, ctx *Context) string {
|
|||
if n == nil {
|
||||
return ""
|
||||
}
|
||||
return n.Render(ctx)
|
||||
return n.Render(normaliseContext(ctx))
|
||||
}
|
||||
|
||||
// renderNodeWithPath renders a node while preserving layout path prefixes for
|
||||
|
|
@ -43,6 +43,7 @@ func renderNodeWithPath(n Node, ctx *Context, path string) string {
|
|||
if n == nil {
|
||||
return ""
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
|
||||
switch t := n.(type) {
|
||||
case *Layout:
|
||||
|
|
@ -246,7 +247,7 @@ func (n *elNode) Render(ctx *Context) string {
|
|||
return ""
|
||||
}
|
||||
|
||||
return n.renderWithPath(ctx, "")
|
||||
return n.renderWithPath(normaliseContext(ctx), "")
|
||||
}
|
||||
|
||||
func (n *elNode) renderWithPath(ctx *Context, path string) string {
|
||||
|
|
@ -312,9 +313,10 @@ func (n *textNode) Render(ctx *Context) string {
|
|||
if n == nil {
|
||||
return ""
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
|
||||
var text string
|
||||
if ctx != nil && ctx.service != nil {
|
||||
if ctx.service != nil {
|
||||
text = ctx.service.T(n.key, n.args...)
|
||||
} else {
|
||||
text = i18n.T(n.key, n.args...)
|
||||
|
|
@ -339,9 +341,10 @@ func (n *ifNode) Render(ctx *Context) string {
|
|||
if n == nil || n.cond == nil || n.node == nil {
|
||||
return ""
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
|
||||
if n.cond(ctx) {
|
||||
return n.node.Render(ctx)
|
||||
return renderNodeWithPath(n.node, ctx, "")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -363,9 +366,10 @@ func (n *unlessNode) Render(ctx *Context) string {
|
|||
if n == nil || n.cond == nil || n.node == nil {
|
||||
return ""
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
|
||||
if !n.cond(ctx) {
|
||||
return n.node.Render(ctx)
|
||||
return renderNodeWithPath(n.node, ctx, "")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -389,11 +393,12 @@ func (n *entitledNode) Render(ctx *Context) string {
|
|||
if n == nil || n.node == nil {
|
||||
return ""
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
|
||||
if ctx == nil || ctx.Entitlements == nil || !ctx.Entitlements(n.feature) {
|
||||
if ctx.Entitlements == nil || !ctx.Entitlements(n.feature) {
|
||||
return ""
|
||||
}
|
||||
return n.node.Render(ctx)
|
||||
return renderNodeWithPath(n.node, ctx, "")
|
||||
}
|
||||
|
||||
// --- switchNode ---
|
||||
|
|
@ -413,10 +418,11 @@ func (n *switchNode) Render(ctx *Context) string {
|
|||
if n == nil || n.selector == nil || n.cases == nil {
|
||||
return ""
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
|
||||
key := n.selector(ctx)
|
||||
if node, ok := n.cases[key]; ok {
|
||||
return renderNode(node, ctx)
|
||||
return renderNodeWithPath(node, ctx, "")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
@ -444,10 +450,11 @@ func (n *eachNode[T]) Render(ctx *Context) string {
|
|||
if n == nil || n.items == nil || n.fn == nil {
|
||||
return ""
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
|
||||
var b strings.Builder
|
||||
for item := range n.items {
|
||||
b.WriteString(renderNode(n.fn(item), ctx))
|
||||
b.WriteString(renderNodeWithPath(n.fn(item), ctx, ""))
|
||||
}
|
||||
return b.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,9 +48,7 @@ func StripTags(html string) string {
|
|||
// and returns a GrammarImprint — the full render-reverse pipeline.
|
||||
// Example: Imprint(NewLayout("C").C(Text("page.body")), NewContext()).
|
||||
func Imprint(node Node, ctx *Context) reversal.GrammarImprint {
|
||||
if ctx == nil {
|
||||
ctx = NewContext()
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
rendered := Render(node, ctx)
|
||||
text := StripTags(rendered)
|
||||
tok := reversal.NewTokeniser()
|
||||
|
|
@ -62,9 +60,7 @@ func Imprint(node Node, ctx *Context) reversal.GrammarImprint {
|
|||
// and returns pairwise similarity scores. Key format: "name1:name2".
|
||||
// Example: CompareVariants(NewResponsive().Variant("desktop", NewLayout("C")), NewContext()).
|
||||
func CompareVariants(r *Responsive, ctx *Context) map[string]float64 {
|
||||
if ctx == nil {
|
||||
ctx = NewContext()
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
if r == nil {
|
||||
return map[string]float64{}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,9 +3,7 @@ package html
|
|||
// render.go: Render is a convenience function that renders a node tree to HTML.
|
||||
// Example: Render(NewLayout("C").C(Raw("body")), NewContext()).
|
||||
func Render(node Node, ctx *Context) string {
|
||||
if ctx == nil {
|
||||
ctx = NewContext()
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
if node == nil {
|
||||
return ""
|
||||
}
|
||||
|
|
|
|||
|
|
@ -106,6 +106,7 @@ func (r *Responsive) Render(ctx *Context) string {
|
|||
if r == nil {
|
||||
return ""
|
||||
}
|
||||
ctx = normaliseContext(ctx)
|
||||
|
||||
var b strings.Builder
|
||||
for _, v := range r.variants {
|
||||
|
|
|
|||
|
|
@ -96,6 +96,20 @@ func TestResponsive_RenderNilReceiver(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestResponsive_RenderNilContext(t *testing.T) {
|
||||
r := NewResponsive().
|
||||
Variant("desktop", NewLayout("C").C(Raw("main")))
|
||||
|
||||
got := r.Render(nil)
|
||||
|
||||
if !strings.Contains(got, `data-variant="desktop"`) {
|
||||
t.Fatalf("Responsive.Render(nil) should still render the variant wrapper, got:\n%s", got)
|
||||
}
|
||||
if !strings.Contains(got, `data-block="C-0"`) {
|
||||
t.Fatalf("Responsive.Render(nil) should still render the layout block, got:\n%s", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestResponsive_NilLayoutVariant(t *testing.T) {
|
||||
ctx := NewContext()
|
||||
r := NewResponsive().
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue