299 lines
9.3 KiB
Go
299 lines
9.3 KiB
Go
package html
|
|
|
|
import (
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
func TestResponsive_SingleVariant(t *testing.T) {
|
|
ctx := NewContext()
|
|
r := NewResponsive().
|
|
Variant("desktop", NewLayout("HLCRF").
|
|
H(Raw("header")).L(Raw("nav")).C(Raw("main")).R(Raw("aside")).F(Raw("footer")))
|
|
got := r.Render(ctx)
|
|
|
|
if !strings.Contains(got, `data-variant="desktop"`) {
|
|
t.Errorf("responsive should contain data-variant, got:\n%s", got)
|
|
}
|
|
if !strings.Contains(got, `data-block="H-0"`) {
|
|
t.Errorf("responsive should contain layout content, got:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestResponsive_MultiVariant(t *testing.T) {
|
|
ctx := NewContext()
|
|
r := NewResponsive().
|
|
Variant("desktop", NewLayout("HLCRF").H(Raw("h")).L(Raw("l")).C(Raw("c")).R(Raw("r")).F(Raw("f"))).
|
|
Variant("tablet", NewLayout("HCF").H(Raw("h")).C(Raw("c")).F(Raw("f"))).
|
|
Variant("mobile", NewLayout("C").C(Raw("c")))
|
|
|
|
got := r.Render(ctx)
|
|
|
|
for _, v := range []string{"desktop", "tablet", "mobile"} {
|
|
if !strings.Contains(got, `data-variant="`+v+`"`) {
|
|
t.Errorf("responsive missing variant %q in:\n%s", v, got)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestResponsive_VariantOrder(t *testing.T) {
|
|
ctx := NewContext()
|
|
r := NewResponsive().
|
|
Variant("desktop", NewLayout("HLCRF").C(Raw("d"))).
|
|
Variant("mobile", NewLayout("C").C(Raw("m")))
|
|
|
|
got := r.Render(ctx)
|
|
|
|
di := strings.Index(got, `data-variant="desktop"`)
|
|
mi := strings.Index(got, `data-variant="mobile"`)
|
|
if di < 0 || mi < 0 {
|
|
t.Fatalf("missing variants in:\n%s", got)
|
|
}
|
|
if di >= mi {
|
|
t.Errorf("desktop should appear before mobile (insertion order), desktop=%d mobile=%d", di, mi)
|
|
}
|
|
}
|
|
|
|
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_VariantClonesLayoutInput(t *testing.T) {
|
|
layout := NewLayout("C").C(Raw("original"))
|
|
responsive := NewResponsive().Variant("desktop", layout)
|
|
|
|
layout.C(Raw("mutated"))
|
|
|
|
got := responsive.Render(NewContext())
|
|
|
|
if !strings.Contains(got, "original") {
|
|
t.Fatalf("Variant should snapshot the layout at insertion time, got:\n%s", got)
|
|
}
|
|
if strings.Contains(got, "mutated") {
|
|
t.Fatalf("Variant should not share later layout mutations, got:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestResponsive_NestedPaths(t *testing.T) {
|
|
ctx := NewContext()
|
|
inner := NewLayout("HCF").H(Raw("ih")).C(Raw("ic")).F(Raw("if"))
|
|
r := NewResponsive().
|
|
Variant("desktop", NewLayout("HLCRF").C(inner))
|
|
|
|
got := r.Render(ctx)
|
|
|
|
if !strings.Contains(got, `data-block="C-0-H-0"`) {
|
|
t.Errorf("nested layout in responsive variant missing C-0-H-0 in:\n%s", got)
|
|
}
|
|
if !strings.Contains(got, `data-block="C-0-C-0"`) {
|
|
t.Errorf("nested layout in responsive variant missing C-0-C-0 in:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestResponsive_VariantsIndependent(t *testing.T) {
|
|
ctx := NewContext()
|
|
r := NewResponsive().
|
|
Variant("a", NewLayout("HLCRF").C(Raw("content-a"))).
|
|
Variant("b", NewLayout("HCF").C(Raw("content-b")))
|
|
|
|
got := r.Render(ctx)
|
|
|
|
count := strings.Count(got, `data-block="C-0"`)
|
|
if count != 2 {
|
|
t.Errorf("expected 2 independent C-0 blocks, got %d in:\n%s", count, got)
|
|
}
|
|
}
|
|
|
|
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 TestResponsive_BuilderNilReceiver(t *testing.T) {
|
|
var r *Responsive
|
|
if got := r.Variant("desktop", NewLayout("C")); got != nil {
|
|
t.Fatalf("nil Responsive.Variant() should return nil, got %v", got)
|
|
}
|
|
}
|
|
|
|
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().
|
|
Variant("desktop", nil).
|
|
Variant("mobile", NewLayout("C").C(Raw("m")))
|
|
|
|
got := r.Render(ctx)
|
|
|
|
if !strings.Contains(got, `data-variant="desktop"`) {
|
|
t.Fatalf("nil layout variant should still render its wrapper, got:\n%s", got)
|
|
}
|
|
if strings.Contains(got, "<nil>") {
|
|
t.Fatalf("nil layout variant should not render placeholder text, got:\n%s", got)
|
|
}
|
|
if !strings.Contains(got, `data-variant="mobile"`) {
|
|
t.Fatalf("responsive should still render subsequent variants, got:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestResponsive_Attributes(t *testing.T) {
|
|
ctx := NewContext()
|
|
r := Attr(NewResponsive().
|
|
Variant("desktop", NewLayout("C").C(Raw("main"))).
|
|
Variant("mobile", NewLayout("C").C(Raw("main"))),
|
|
"aria-label", "Responsive content",
|
|
)
|
|
r = Attr(r, "class", "responsive-shell")
|
|
|
|
got := r.Render(ctx)
|
|
|
|
if count := strings.Count(got, `aria-label="Responsive content"`); count != 2 {
|
|
t.Fatalf("responsive attrs should apply to each wrapper, got %d in:\n%s", count, got)
|
|
}
|
|
if count := strings.Count(got, `class="responsive-shell"`); count != 2 {
|
|
t.Fatalf("responsive class should apply to each wrapper, got %d in:\n%s", count, got)
|
|
}
|
|
if !strings.Contains(got, `aria-label="Responsive content" class="responsive-shell" data-variant="desktop"`) {
|
|
t.Fatalf("responsive wrapper attrs should be sorted and preserved, got:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestResponsive_ReservedVariantAttributeIsIgnored(t *testing.T) {
|
|
ctx := NewContext()
|
|
r := Attr(NewResponsive().
|
|
Variant("desktop", NewLayout("C").C(Raw("main"))),
|
|
"data-variant", "override",
|
|
)
|
|
|
|
got := r.Render(ctx)
|
|
|
|
if count := strings.Count(got, `data-variant=`); count != 1 {
|
|
t.Fatalf("responsive wrapper should emit exactly one data-variant attribute, got %d in:\n%s", count, got)
|
|
}
|
|
if !strings.Contains(got, `data-variant="desktop"`) {
|
|
t.Fatalf("responsive wrapper should preserve its own variant name, got:\n%s", got)
|
|
}
|
|
if strings.Contains(got, `data-variant="override"`) {
|
|
t.Fatalf("responsive wrapper should ignore reserved data-variant attrs, got:\n%s", got)
|
|
}
|
|
}
|
|
|
|
func TestVariantSelector(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
variant string
|
|
want string
|
|
}{
|
|
{name: "plain", variant: "desktop", want: `[data-variant="desktop"]`},
|
|
{name: "escaped", variant: `desk"top\` + "\n" + `line`, want: `[data-variant="desk\"top\\\a line"]`},
|
|
{name: "control char", variant: "tab\tname", want: `[data-variant="tab\9 name"]`},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := VariantSelector(tt.variant)
|
|
if got != tt.want {
|
|
t.Fatalf("VariantSelector(%q) = %q, want %q", tt.variant, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScopeVariant(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
variant string
|
|
selector string
|
|
want string
|
|
}{
|
|
{name: "scope", variant: "desktop", selector: ".nav", want: `[data-variant="desktop"] .nav`},
|
|
{name: "empty selector", variant: "mobile", selector: "", want: `[data-variant="mobile"]`},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
t.Run(tt.name, func(t *testing.T) {
|
|
got := ScopeVariant(tt.variant, tt.selector)
|
|
if got != tt.want {
|
|
t.Fatalf("ScopeVariant(%q, %q) = %q, want %q", tt.variant, tt.selector, got, tt.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestScopeVariant_MultipleSelectors(t *testing.T) {
|
|
got := ScopeVariant("desktop", ".nav, .sidebar")
|
|
want := `[data-variant="desktop"] .nav, [data-variant="desktop"] .sidebar`
|
|
if got != want {
|
|
t.Fatalf("ScopeVariant with selector list = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestScopeVariant_IgnoresEmptySelectorSegments(t *testing.T) {
|
|
got := ScopeVariant("desktop", ".nav, , .sidebar,")
|
|
want := `[data-variant="desktop"] .nav, [data-variant="desktop"] .sidebar`
|
|
if got != want {
|
|
t.Fatalf("ScopeVariant should skip empty selector segments = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestScopeVariant_PreservesNestedCommas(t *testing.T) {
|
|
got := ScopeVariant("desktop", `:is(.nav, .sidebar), .footer`)
|
|
want := `[data-variant="desktop"] :is(.nav, .sidebar), [data-variant="desktop"] .footer`
|
|
if got != want {
|
|
t.Fatalf("ScopeVariant should preserve nested commas = %q, want %q", got, want)
|
|
}
|
|
}
|
|
|
|
func TestScopeVariant_PreservesEscapedSelectorCharacters(t *testing.T) {
|
|
got := ScopeVariant("desktop", `.nav\,primary, [data-state="open\,expanded"]`)
|
|
want := `[data-variant="desktop"] .nav\,primary, [data-variant="desktop"] [data-state="open\,expanded"]`
|
|
if got != want {
|
|
t.Fatalf("ScopeVariant should preserve escaped selector characters = %q, want %q", got, want)
|
|
}
|
|
}
|