feat(html): expose deep clone helpers
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-04 00:53:36 +00:00
parent a4d2341211
commit d12408ffb9
4 changed files with 86 additions and 0 deletions

View file

@ -42,6 +42,20 @@ type Layout struct {
variantErr error
}
// Clone returns a deep copy of the layout tree.
// Example: next := layout.Clone().
func (l *Layout) Clone() *Layout {
if l == nil {
return nil
}
clone, ok := l.cloneNode().(*Layout)
if !ok {
return nil
}
return clone
}
// layout.go: NewLayout creates a new Layout with the given variant string.
// Example: page := NewLayout("HCF").
// The variant determines which slots are rendered (e.g., "HLCRF", "HCF", "C").

View file

@ -99,6 +99,34 @@ func TestLayout_FluentAPI(t *testing.T) {
}
}
func TestLayout_CloneReturnsIndependentCopy(t *testing.T) {
original := NewLayout("HLCRF").
H(Raw("header")).
C(Raw("main")).
F(Raw("footer"))
clone := original.Clone()
if clone == nil {
t.Fatal("Clone should return a layout")
}
if clone == original {
t.Fatal("Clone should return a distinct layout instance")
}
clone.H(Raw("cloned-header"))
clone.C(Raw("cloned-main"))
originalGot := original.Render(NewContext())
cloneGot := clone.Render(NewContext())
if strings.Contains(originalGot, "cloned-header") || strings.Contains(originalGot, "cloned-main") {
t.Fatalf("Clone should not mutate original layout, got:\n%s", originalGot)
}
if !strings.Contains(cloneGot, "cloned-header") || !strings.Contains(cloneGot, "cloned-main") {
t.Fatalf("Clone should preserve mutations on the copy, got:\n%s", cloneGot)
}
}
func TestLayout_IgnoresInvalidSlots(t *testing.T) {
ctx := NewContext()
// "C" variant: populating L and R should have no effect

View file

@ -28,6 +28,20 @@ func NewResponsive() *Responsive {
}
}
// Clone returns a deep copy of the responsive compositor.
// Example: next := responsive.Clone().
func (r *Responsive) Clone() *Responsive {
if r == nil {
return nil
}
clone, ok := r.cloneNode().(*Responsive)
if !ok {
return nil
}
return clone
}
func (r *Responsive) setAttr(key, value string) {
if r == nil {
return

View file

@ -54,6 +54,36 @@ func TestResponsive_VariantOrder(t *testing.T) {
}
}
func TestResponsive_CloneReturnsIndependentCopy(t *testing.T) {
original := NewResponsive().
Variant("desktop", NewLayout("C").C(Raw("desktop"))).
Variant("mobile", NewLayout("C").C(Raw("mobile")))
clone := original.Clone()
if clone == nil {
t.Fatal("Clone should return a responsive compositor")
}
if clone == original {
t.Fatal("Clone should return a distinct responsive instance")
}
clone.Variant("tablet", NewLayout("C").C(Raw("tablet")))
clone.setAttr("class", "cloned-responsive")
originalGot := original.Render(NewContext())
cloneGot := clone.Render(NewContext())
if strings.Contains(originalGot, "tablet") || strings.Contains(originalGot, "cloned-responsive") {
t.Fatalf("Clone should not mutate original responsive compositor, got:\n%s", originalGot)
}
if !strings.Contains(cloneGot, `class="cloned-responsive"`) {
t.Fatalf("Clone should preserve attributes on the copy, got:\n%s", cloneGot)
}
if !strings.Contains(cloneGot, `data-variant="tablet"`) {
t.Fatalf("Clone should preserve new variants on the copy, got:\n%s", cloneGot)
}
}
func TestResponsive_NestedPaths(t *testing.T) {
ctx := NewContext()
inner := NewLayout("HCF").H(Raw("ih")).C(Raw("ic")).F(Raw("if"))