From 3616ad3a763746e3b800b105f6c736d832c3b4c9 Mon Sep 17 00:00:00 2001 From: Virgil Date: Thu, 26 Mar 2026 18:12:06 +0000 Subject: [PATCH] chore: polish ax v0.8.0 conventions Co-Authored-By: Virgil --- cmd/codegen/main.go | 32 ++++++++++++++++++------ cmd/codegen/main_test.go | 8 +++--- cmd/wasm/register.go | 5 ++-- cmd/wasm/register_test.go | 4 +-- cmd/wasm/size_test.go | 35 ++++++++++++++++---------- codegen/codegen.go | 6 ++++- codegen/codegen_test.go | 10 ++++---- context.go | 4 +++ edge_test.go | 52 +++++++++++++++++++-------------------- go.mod | 1 + go.sum | 2 ++ integration_test.go | 4 +-- layout.go | 8 ++++++ layout_test.go | 10 ++++---- node.go | 11 +++++++++ node_test.go | 50 ++++++++++++++++++------------------- path.go | 1 + path_test.go | 8 +++--- pipeline.go | 3 +++ pipeline_test.go | 18 +++++++------- render.go | 1 + render_test.go | 6 ++--- responsive.go | 4 +++ responsive_test.go | 12 ++++----- 24 files changed, 181 insertions(+), 114 deletions(-) diff --git a/cmd/codegen/main.go b/cmd/codegen/main.go index 46f67d1..523f781 100644 --- a/cmd/codegen/main.go +++ b/cmd/codegen/main.go @@ -9,11 +9,11 @@ package main import ( - "encoding/json" goio "io" - "os" + core "dappco.re/go/core" "dappco.re/go/core/html/codegen" + coreio "dappco.re/go/core/io" log "dappco.re/go/core/log" ) @@ -24,22 +24,40 @@ func run(r goio.Reader, w goio.Writer) error { } var slots map[string]string - if err := json.Unmarshal(data, &slots); err != nil { + if result := core.JSONUnmarshal(data, &slots); !result.OK { + err, _ := result.Value.(error) return log.E("codegen", "invalid JSON", err) } js, err := codegen.GenerateBundle(slots) if err != nil { - return err + return log.E("codegen", "generate bundle", err) } _, err = goio.WriteString(w, js) - return err + if err != nil { + return log.E("codegen", "writing bundle", err) + } + return nil } func main() { - if err := run(os.Stdin, os.Stdout); err != nil { + stdin, err := coreio.Local.Open("/dev/stdin") + if err != nil { + panic(log.E("codegen.main", "open stdin", err)) + } + + stdout, err := coreio.Local.Create("/dev/stdout") + if err != nil { + panic(log.E("codegen.main", "open stdout", err)) + } + defer func() { + _ = stdin.Close() + _ = stdout.Close() + }() + + if err := run(stdin, stdout); err != nil { log.Error("codegen failed", "err", err) - os.Exit(1) + panic(err) } } diff --git a/cmd/codegen/main_test.go b/cmd/codegen/main_test.go index 046f470..d0046e3 100644 --- a/cmd/codegen/main_test.go +++ b/cmd/codegen/main_test.go @@ -10,7 +10,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestRun_WritesBundle(t *testing.T) { +func TestRun_WritesBundle_Good(t *testing.T) { input := core.NewReader(`{"H":"nav-bar","C":"main-content"}`) output := core.NewBuilder() @@ -24,7 +24,7 @@ func TestRun_WritesBundle(t *testing.T) { assert.Equal(t, 2, countSubstr(js, "extends HTMLElement")) } -func TestRun_InvalidJSON(t *testing.T) { +func TestRun_InvalidJSON_Bad(t *testing.T) { input := core.NewReader(`not json`) output := core.NewBuilder() @@ -33,7 +33,7 @@ func TestRun_InvalidJSON(t *testing.T) { assert.Contains(t, err.Error(), "invalid JSON") } -func TestRun_InvalidTag(t *testing.T) { +func TestRun_InvalidTag_Bad(t *testing.T) { input := core.NewReader(`{"H":"notag"}`) output := core.NewBuilder() @@ -42,7 +42,7 @@ func TestRun_InvalidTag(t *testing.T) { assert.Contains(t, err.Error(), "hyphen") } -func TestRun_EmptySlots(t *testing.T) { +func TestRun_EmptySlots_Good(t *testing.T) { input := core.NewReader(`{}`) output := core.NewBuilder() diff --git a/cmd/wasm/register.go b/cmd/wasm/register.go index d88c866..d463465 100644 --- a/cmd/wasm/register.go +++ b/cmd/wasm/register.go @@ -3,7 +3,7 @@ package main import ( - "encoding/json" + core "dappco.re/go/core" "dappco.re/go/core/html/codegen" log "dappco.re/go/core/log" @@ -15,7 +15,8 @@ import ( // Use cmd/codegen/ CLI instead for build-time generation. func buildComponentJS(slotsJSON string) (string, error) { var slots map[string]string - if err := json.Unmarshal([]byte(slotsJSON), &slots); err != nil { + if result := core.JSONUnmarshalString(slotsJSON, &slots); !result.OK { + err, _ := result.Value.(error) return "", log.E("buildComponentJS", "unmarshal JSON", err) } return codegen.GenerateBundle(slots) diff --git a/cmd/wasm/register_test.go b/cmd/wasm/register_test.go index 255fab8..9365e9e 100644 --- a/cmd/wasm/register_test.go +++ b/cmd/wasm/register_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestBuildComponentJS_ValidJSON(t *testing.T) { +func TestBuildComponentJS_ValidJSON_Good(t *testing.T) { slotsJSON := `{"H":"nav-bar","C":"main-content"}` js, err := buildComponentJS(slotsJSON) require.NoError(t, err) @@ -18,7 +18,7 @@ func TestBuildComponentJS_ValidJSON(t *testing.T) { assert.Contains(t, js, "customElements.define") } -func TestBuildComponentJS_InvalidJSON(t *testing.T) { +func TestBuildComponentJS_InvalidJSON_Bad(t *testing.T) { _, err := buildComponentJS("not json") assert.Error(t, err) } diff --git a/cmd/wasm/size_test.go b/cmd/wasm/size_test.go index 8187bd9..e8b1d96 100644 --- a/cmd/wasm/size_test.go +++ b/cmd/wasm/size_test.go @@ -5,13 +5,12 @@ package main import ( "compress/gzip" - "os" - "os/exec" - "path/filepath" + "context" "testing" core "dappco.re/go/core" coreio "dappco.re/go/core/io" + process "dappco.re/go/core/process" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -21,34 +20,44 @@ const ( wasmRawLimit = 3_670_016 // 3.5 MB raw size limit ) -func TestWASMBinarySize_WithinBudget(t *testing.T) { +func TestCmdWasm_WASMBinarySize_Good(t *testing.T) { if testing.Short() { t.Skip("skipping WASM build test in short mode") } dir := t.TempDir() - out := filepath.Join(dir, "gohtml.wasm") + out := core.Path(dir, "gohtml.wasm") - cmd := exec.Command("go", "build", "-ldflags=-s -w", "-o", out, ".") - cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm") - output, err := cmd.CombinedOutput() + factory := process.NewService(process.Options{}) + serviceValue, err := factory(core.New()) + require.NoError(t, err) + + svc, ok := serviceValue.(*process.Service) + require.True(t, ok, "process service factory returned %T", serviceValue) + + output, err := svc.RunWithOptions(context.Background(), process.RunOptions{ + Command: "go", + Args: []string{"build", "-ldflags=-s -w", "-o", out, "."}, + Dir: ".", + Env: []string{"GOOS=js", "GOARCH=wasm"}, + }) require.NoError(t, err, "WASM build failed: %s", output) rawStr, err := coreio.Local.Read(out) require.NoError(t, err) - raw := []byte(rawStr) + rawBytes := []byte(rawStr) buf := core.NewBuilder() gz, err := gzip.NewWriterLevel(buf, gzip.BestCompression) require.NoError(t, err) - _, err = gz.Write(raw) + _, err = gz.Write(rawBytes) require.NoError(t, err) require.NoError(t, gz.Close()) - t.Logf("WASM size: %d bytes raw, %d bytes gzip", len(raw), buf.Len()) + t.Logf("WASM size: %d bytes raw, %d bytes gzip", len(rawBytes), buf.Len()) assert.Less(t, buf.Len(), wasmGzLimit, "WASM gzip size %d exceeds 1MB limit", buf.Len()) - assert.Less(t, len(raw), wasmRawLimit, - "WASM raw size %d exceeds 3MB limit", len(raw)) + assert.Less(t, len(rawBytes), wasmRawLimit, + "WASM raw size %d exceeds 3MB limit", len(rawBytes)) } diff --git a/codegen/codegen.go b/codegen/codegen.go index 2f895b3..e0183c5 100644 --- a/codegen/codegen.go +++ b/codegen/codegen.go @@ -32,6 +32,7 @@ var wcTemplate = template.Must(template.New("wc").Parse(`class {{.ClassName}} ex }`)) // GenerateClass produces a JS class definition for a custom element. +// Usage example: js, err := GenerateClass("nav-bar", "H") func GenerateClass(tag, slot string) (string, error) { if !core.Contains(tag, "-") { return "", log.E("codegen.GenerateClass", "custom element tag must contain a hyphen: "+tag, nil) @@ -51,11 +52,13 @@ func GenerateClass(tag, slot string) (string, error) { } // GenerateRegistration produces the customElements.define() call. +// Usage example: js := GenerateRegistration("nav-bar", "NavBar") func GenerateRegistration(tag, className string) string { return `customElements.define("` + tag + `", ` + className + `);` } // TagToClassName converts a kebab-case tag to PascalCase class name. +// Usage example: className := TagToClassName("nav-bar") func TagToClassName(tag string) string { b := core.NewBuilder() for _, p := range core.Split(tag, "-") { @@ -69,6 +72,7 @@ func TagToClassName(tag string) string { // GenerateBundle produces all WC class definitions and registrations // for a set of HLCRF slot assignments. +// Usage example: js, err := GenerateBundle(map[string]string{"H": "nav-bar"}) func GenerateBundle(slots map[string]string) (string, error) { seen := make(map[string]bool) b := core.NewBuilder() @@ -81,7 +85,7 @@ func GenerateBundle(slots map[string]string) (string, error) { cls, err := GenerateClass(tag, slot) if err != nil { - return "", err + return "", log.E("codegen.GenerateBundle", "generate class for tag "+tag, err) } b.WriteString(cls) b.WriteByte('\n') diff --git a/codegen/codegen_test.go b/codegen/codegen_test.go index 480eba6..66359b9 100644 --- a/codegen/codegen_test.go +++ b/codegen/codegen_test.go @@ -9,7 +9,7 @@ import ( "github.com/stretchr/testify/require" ) -func TestGenerateClass_ValidTag(t *testing.T) { +func TestGenerateClass_ValidTag_Good(t *testing.T) { js, err := GenerateClass("photo-grid", "C") require.NoError(t, err) assert.Contains(t, js, "class PhotoGrid extends HTMLElement") @@ -18,19 +18,19 @@ func TestGenerateClass_ValidTag(t *testing.T) { assert.Contains(t, js, "photo-grid") } -func TestGenerateClass_InvalidTag(t *testing.T) { +func TestGenerateClass_InvalidTag_Bad(t *testing.T) { _, err := GenerateClass("invalid", "C") assert.Error(t, err, "custom element names must contain a hyphen") } -func TestGenerateRegistration_DefinesCustomElement(t *testing.T) { +func TestGenerateRegistration_DefinesCustomElement_Good(t *testing.T) { js := GenerateRegistration("photo-grid", "PhotoGrid") assert.Contains(t, js, "customElements.define") assert.Contains(t, js, `"photo-grid"`) assert.Contains(t, js, "PhotoGrid") } -func TestTagToClassName_KebabCase(t *testing.T) { +func TestTagToClassName_KebabCase_Good(t *testing.T) { tests := []struct{ tag, want string }{ {"photo-grid", "PhotoGrid"}, {"nav-breadcrumb", "NavBreadcrumb"}, @@ -42,7 +42,7 @@ func TestTagToClassName_KebabCase(t *testing.T) { } } -func TestGenerateBundle_DeduplicatesRegistrations(t *testing.T) { +func TestGenerateBundle_DeduplicatesRegistrations_Good(t *testing.T) { slots := map[string]string{ "H": "nav-bar", "C": "main-content", diff --git a/context.go b/context.go index a220dbc..c879fd6 100644 --- a/context.go +++ b/context.go @@ -1,6 +1,7 @@ package html // Translator provides Text() lookups for a rendering context. +// Usage example: ctx := NewContextWithService(myTranslator) // // The default server build uses go-i18n. Alternate builds, including WASM, // can provide any implementation with the same T() method. @@ -9,6 +10,7 @@ type Translator interface { } // Context carries rendering state through the node tree. +// Usage example: ctx := NewContext() type Context struct { Identity string Locale string @@ -18,6 +20,7 @@ type Context struct { } // NewContext creates a new rendering context with sensible defaults. +// Usage example: html := Render(Text("welcome"), NewContext()) func NewContext() *Context { return &Context{ Data: make(map[string]any), @@ -25,6 +28,7 @@ func NewContext() *Context { } // NewContextWithService creates a rendering context backed by a specific translator. +// Usage example: ctx := NewContextWithService(myTranslator) func NewContextWithService(svc Translator) *Context { return &Context{ Data: make(map[string]any), diff --git a/edge_test.go b/edge_test.go index 1f745a4..a4e8859 100644 --- a/edge_test.go +++ b/edge_test.go @@ -8,7 +8,7 @@ import ( // --- Unicode / RTL edge cases --- -func TestText_Emoji(t *testing.T) { +func TestText_Emoji_Ugly(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() @@ -39,7 +39,7 @@ func TestText_Emoji(t *testing.T) { } } -func TestEl_Emoji(t *testing.T) { +func TestEl_Emoji_Ugly(t *testing.T) { ctx := NewContext() node := El("span", Raw("\U0001F680 Launch")) got := node.Render(ctx) @@ -49,7 +49,7 @@ func TestEl_Emoji(t *testing.T) { } } -func TestText_RTL(t *testing.T) { +func TestText_RTL_Ugly(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() @@ -74,7 +74,7 @@ func TestText_RTL(t *testing.T) { } } -func TestEl_RTL(t *testing.T) { +func TestEl_RTL_Ugly(t *testing.T) { ctx := NewContext() node := Attr(El("div", Raw("\u0645\u0631\u062D\u0628\u0627")), "dir", "rtl") got := node.Render(ctx) @@ -86,7 +86,7 @@ func TestEl_RTL(t *testing.T) { } } -func TestText_ZeroWidth(t *testing.T) { +func TestText_ZeroWidth_Ugly(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() @@ -112,7 +112,7 @@ func TestText_ZeroWidth(t *testing.T) { } } -func TestText_MixedScripts(t *testing.T) { +func TestText_MixedScripts_Ugly(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() @@ -139,7 +139,7 @@ func TestText_MixedScripts(t *testing.T) { } } -func TestStripTags_Unicode(t *testing.T) { +func TestStripTags_Unicode_Ugly(t *testing.T) { tests := []struct { name string input string @@ -161,7 +161,7 @@ func TestStripTags_Unicode(t *testing.T) { } } -func TestAttr_UnicodeValue(t *testing.T) { +func TestAttr_UnicodeValue_Ugly(t *testing.T) { ctx := NewContext() node := Attr(El("div"), "title", "\U0001F680 Rocket Launch") got := node.Render(ctx) @@ -173,7 +173,7 @@ func TestAttr_UnicodeValue(t *testing.T) { // --- Deep nesting stress tests --- -func TestLayout_DeepNesting_10Levels(t *testing.T) { +func TestLayout_DeepNesting_10Levels_Ugly(t *testing.T) { ctx := NewContext() // Build 10 levels of nested layouts @@ -204,7 +204,7 @@ func TestLayout_DeepNesting_10Levels(t *testing.T) { } } -func TestLayout_DeepNesting_20Levels(t *testing.T) { +func TestLayout_DeepNesting_20Levels_Ugly(t *testing.T) { ctx := NewContext() current := NewLayout("C").C(Raw("bottom")) @@ -222,7 +222,7 @@ func TestLayout_DeepNesting_20Levels(t *testing.T) { } } -func TestLayout_DeepNesting_MixedSlots(t *testing.T) { +func TestLayout_DeepNesting_MixedSlots_Ugly(t *testing.T) { ctx := NewContext() // Alternate slot types at each level: C -> L -> C -> L -> ... @@ -241,7 +241,7 @@ func TestLayout_DeepNesting_MixedSlots(t *testing.T) { } } -func TestEach_LargeIteration_1000(t *testing.T) { +func TestEach_LargeIteration_1000_Ugly(t *testing.T) { ctx := NewContext() items := make([]int, 1000) for i := range items { @@ -265,7 +265,7 @@ func TestEach_LargeIteration_1000(t *testing.T) { } } -func TestEach_LargeIteration_5000(t *testing.T) { +func TestEach_LargeIteration_5000_Ugly(t *testing.T) { ctx := NewContext() items := make([]int, 5000) for i := range items { @@ -283,7 +283,7 @@ func TestEach_LargeIteration_5000(t *testing.T) { } } -func TestEach_NestedEach(t *testing.T) { +func TestEach_NestedEach_Ugly(t *testing.T) { ctx := NewContext() rows := []int{0, 1, 2} cols := []string{"a", "b", "c"} @@ -309,7 +309,7 @@ func TestEach_NestedEach(t *testing.T) { // --- Layout variant validation --- -func TestLayout_InvalidVariant_Chars(t *testing.T) { +func TestLayout_InvalidVariant_Chars_Bad(t *testing.T) { ctx := NewContext() tests := []struct { @@ -341,7 +341,7 @@ func TestLayout_InvalidVariant_Chars(t *testing.T) { } } -func TestLayout_InvalidVariant_MixedValidInvalid(t *testing.T) { +func TestLayout_InvalidVariant_MixedValidInvalid_Bad(t *testing.T) { ctx := NewContext() // "HXC" — H and C are valid, X is not. Only H and C should render. @@ -361,7 +361,7 @@ func TestLayout_InvalidVariant_MixedValidInvalid(t *testing.T) { } } -func TestLayout_DuplicateVariantChars(t *testing.T) { +func TestLayout_DuplicateVariantChars_Ugly(t *testing.T) { ctx := NewContext() // "CCC" — C appears three times. Should render C slot content three times. @@ -374,7 +374,7 @@ func TestLayout_DuplicateVariantChars(t *testing.T) { } } -func TestLayout_EmptySlots(t *testing.T) { +func TestLayout_EmptySlots_Ugly(t *testing.T) { ctx := NewContext() // Variant includes all slots but none are populated — should produce empty output. @@ -388,7 +388,7 @@ func TestLayout_EmptySlots(t *testing.T) { // --- Render convenience function edge cases --- -func TestRender_NilContext(t *testing.T) { +func TestRender_NilContext_Ugly(t *testing.T) { node := Raw("test") got := Render(node, nil) if got != "test" { @@ -396,7 +396,7 @@ func TestRender_NilContext(t *testing.T) { } } -func TestImprint_NilContext(t *testing.T) { +func TestImprint_NilContext_Ugly(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) @@ -408,7 +408,7 @@ func TestImprint_NilContext(t *testing.T) { } } -func TestCompareVariants_NilContext(t *testing.T) { +func TestCompareVariants_NilContext_Ugly(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) @@ -422,7 +422,7 @@ func TestCompareVariants_NilContext(t *testing.T) { } } -func TestCompareVariants_SingleVariant(t *testing.T) { +func TestCompareVariants_SingleVariant_Ugly(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) @@ -437,7 +437,7 @@ func TestCompareVariants_SingleVariant(t *testing.T) { // --- escapeHTML / escapeAttr edge cases --- -func TestEscapeAttr_AllSpecialChars(t *testing.T) { +func TestEscapeAttr_AllSpecialChars_Ugly(t *testing.T) { ctx := NewContext() node := Attr(El("div"), "data-val", `&<>"'`) got := node.Render(ctx) @@ -450,7 +450,7 @@ func TestEscapeAttr_AllSpecialChars(t *testing.T) { } } -func TestElNode_EmptyTag(t *testing.T) { +func TestElNode_EmptyTag_Ugly(t *testing.T) { ctx := NewContext() node := El("", Raw("content")) got := node.Render(ctx) @@ -461,7 +461,7 @@ func TestElNode_EmptyTag(t *testing.T) { } } -func TestSwitchNode_NoMatch(t *testing.T) { +func TestSwitchNode_NoMatch_Ugly(t *testing.T) { ctx := NewContext() cases := map[string]Node{ "a": Raw("alpha"), @@ -474,7 +474,7 @@ func TestSwitchNode_NoMatch(t *testing.T) { } } -func TestEntitled_NilContext(t *testing.T) { +func TestEntitled_NilContext_Ugly(t *testing.T) { node := Entitled("premium", Raw("content")) got := node.Render(nil) if got != "" { diff --git a/go.mod b/go.mod index 64c6ba9..9497a5f 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( dappco.re/go/core/i18n v0.2.0 dappco.re/go/core/io v0.2.0 dappco.re/go/core/log v0.1.0 + dappco.re/go/core/process v0.3.0 github.com/stretchr/testify v1.11.1 ) diff --git a/go.sum b/go.sum index bb601bc..7b72efe 100644 --- a/go.sum +++ b/go.sum @@ -6,6 +6,8 @@ dappco.re/go/core/io v0.2.0 h1:zuudgIiTsQQ5ipVt97saWdGLROovbEB/zdVyy9/l+I4= dappco.re/go/core/io v0.2.0/go.mod h1:1QnQV6X9LNgFKfm8SkOtR9LLaj3bDcsOIeJOOyjbL5E= dappco.re/go/core/log v0.1.0 h1:pa71Vq2TD2aoEUQWFKwNcaJ3GBY8HbaNGqtE688Unyc= dappco.re/go/core/log v0.1.0/go.mod h1:Nkqb8gsXhZAO8VLpx7B8i1iAmohhzqA20b9Zr8VUcJs= +dappco.re/go/core/process v0.3.0 h1:BPF9R79+8ZWe34qCIy/sZy+P4HwbaO95js2oPJL7IqM= +dappco.re/go/core/process v0.3.0/go.mod h1:qwx8kt6x+J9gn7fu8lavuess72Ye9jPBODqDZQ9K0as= forge.lthn.ai/core/go-inference v0.1.4 h1:fuAgWbqsEDajHniqAKyvHYbRcBrkGEiGSqR2pfTMRY0= forge.lthn.ai/core/go-inference v0.1.4/go.mod h1:jfWz+IJX55wAH98+ic6FEqqGB6/P31CHlg7VY7pxREw= forge.lthn.ai/core/go-log v0.0.4 h1:KTuCEPgFmuM8KJfnyQ8vPOU1Jg654W74h8IJvfQMfv0= diff --git a/integration_test.go b/integration_test.go index 1a6346c..00e1867 100644 --- a/integration_test.go +++ b/integration_test.go @@ -6,7 +6,7 @@ import ( i18n "dappco.re/go/core/i18n" ) -func TestIntegration_RenderThenReverse(t *testing.T) { +func TestIntegration_RenderThenReverse_Good(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() @@ -26,7 +26,7 @@ func TestIntegration_RenderThenReverse(t *testing.T) { } } -func TestIntegration_ResponsiveImprint(t *testing.T) { +func TestIntegration_ResponsiveImprint_Good(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() diff --git a/layout.go b/layout.go index 873b1d0..c156739 100644 --- a/layout.go +++ b/layout.go @@ -20,6 +20,7 @@ var slotRegistry = map[byte]slotMeta{ // Layout is an HLCRF compositor. Arranges nodes into semantic HTML regions // with deterministic path-based IDs. +// Usage example: page := NewLayout("HCF").H(Text("title")).C(Text("body")) type Layout struct { variant string // "HLCRF", "HCF", "C", etc. path string // "" for root, "L-0-" for nested @@ -27,6 +28,7 @@ type Layout struct { } // NewLayout creates a new Layout with the given variant string. +// Usage example: page := NewLayout("HLCRF") // The variant determines which slots are rendered (e.g., "HLCRF", "HCF", "C"). func NewLayout(variant string) *Layout { return &Layout{ @@ -36,30 +38,35 @@ func NewLayout(variant string) *Layout { } // H appends nodes to the Header slot. +// Usage example: NewLayout("HCF").H(Text("title")) func (l *Layout) H(nodes ...Node) *Layout { l.slots['H'] = append(l.slots['H'], nodes...) return l } // L appends nodes to the Left aside slot. +// Usage example: NewLayout("LC").L(Text("nav")) func (l *Layout) L(nodes ...Node) *Layout { l.slots['L'] = append(l.slots['L'], nodes...) return l } // C appends nodes to the Content (main) slot. +// Usage example: NewLayout("C").C(Text("body")) func (l *Layout) C(nodes ...Node) *Layout { l.slots['C'] = append(l.slots['C'], nodes...) return l } // R appends nodes to the Right aside slot. +// Usage example: NewLayout("CR").R(Text("ads")) func (l *Layout) R(nodes ...Node) *Layout { l.slots['R'] = append(l.slots['R'], nodes...) return l } // F appends nodes to the Footer slot. +// Usage example: NewLayout("CF").F(Text("footer")) func (l *Layout) F(nodes ...Node) *Layout { l.slots['F'] = append(l.slots['F'], nodes...) return l @@ -71,6 +78,7 @@ func (l *Layout) blockID(slot byte) string { } // Render produces the semantic HTML for this layout. +// Usage example: html := NewLayout("C").C(Text("body")).Render(NewContext()) // Only slots present in the variant string are rendered. func (l *Layout) Render(ctx *Context) string { b := newTextBuilder() diff --git a/layout_test.go b/layout_test.go index 0b575a8..c492d4c 100644 --- a/layout_test.go +++ b/layout_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -func TestLayout_HLCRF(t *testing.T) { +func TestLayout_HLCRF_Good(t *testing.T) { ctx := NewContext() layout := NewLayout("HLCRF"). H(Raw("header")).L(Raw("left")).C(Raw("main")).R(Raw("right")).F(Raw("footer")) @@ -39,7 +39,7 @@ func TestLayout_HLCRF(t *testing.T) { } } -func TestLayout_HCF(t *testing.T) { +func TestLayout_HCF_Good(t *testing.T) { ctx := NewContext() layout := NewLayout("HCF"). H(Raw("header")).L(Raw("left")).C(Raw("main")).R(Raw("right")).F(Raw("footer")) @@ -60,7 +60,7 @@ func TestLayout_HCF(t *testing.T) { } } -func TestLayout_ContentOnly(t *testing.T) { +func TestLayout_ContentOnly_Good(t *testing.T) { ctx := NewContext() layout := NewLayout("C"). H(Raw("header")).L(Raw("left")).C(Raw("main")).R(Raw("right")).F(Raw("footer")) @@ -82,7 +82,7 @@ func TestLayout_ContentOnly(t *testing.T) { } } -func TestLayout_FluentAPI(t *testing.T) { +func TestLayout_FluentAPI_Good(t *testing.T) { layout := NewLayout("HLCRF") // Fluent methods should return the same layout for chaining @@ -97,7 +97,7 @@ func TestLayout_FluentAPI(t *testing.T) { } } -func TestLayout_IgnoresInvalidSlots(t *testing.T) { +func TestLayout_IgnoresInvalidSlots_Good(t *testing.T) { ctx := NewContext() // "C" variant: populating L and R should have no effect layout := NewLayout("C").L(Raw("left")).C(Raw("main")).R(Raw("right")) diff --git a/node.go b/node.go index a5bd78e..921c53c 100644 --- a/node.go +++ b/node.go @@ -8,6 +8,7 @@ import ( ) // Node is anything renderable. +// Usage example: var n Node = El("div", Text("welcome")) type Node interface { Render(ctx *Context) string } @@ -53,6 +54,7 @@ type rawNode struct { } // Raw creates a node that renders without escaping (escape hatch for trusted content). +// Usage example: Raw("trusted") func Raw(content string) Node { return &rawNode{content: content} } @@ -70,6 +72,7 @@ type elNode struct { } // El creates an HTML element node with children. +// Usage example: El("section", Text("welcome")) func El(tag string, children ...Node) Node { return &elNode{ tag: tag, @@ -79,6 +82,7 @@ func El(tag string, children ...Node) Node { } // Attr sets an attribute on an El node. Returns the node for chaining. +// Usage example: Attr(El("a", Text("docs")), "href", "/docs") // It recursively traverses through wrappers like If, Unless, and Entitled. func Attr(n Node, key, value string) Node { switch t := n.(type) { @@ -143,6 +147,7 @@ type textNode struct { } // Text creates a node that renders through the go-i18n grammar pipeline. +// Usage example: Text("welcome", "Ada") // Output is HTML-escaped by default. Safe-by-default path. func Text(key string, args ...any) Node { return &textNode{key: key, args: args} @@ -160,6 +165,7 @@ type ifNode struct { } // If renders child only when condition is true. +// Usage example: If(func(ctx *Context) bool { return ctx.Identity != "" }, Text("hi")) func If(cond func(*Context) bool, node Node) Node { return &ifNode{cond: cond, node: node} } @@ -179,6 +185,7 @@ type unlessNode struct { } // Unless renders child only when condition is false. +// Usage example: Unless(func(ctx *Context) bool { return ctx.Identity == "" }, Text("welcome")) func Unless(cond func(*Context) bool, node Node) Node { return &unlessNode{cond: cond, node: node} } @@ -198,6 +205,7 @@ type entitledNode struct { } // Entitled renders child only when entitlement is granted. Absent, not hidden. +// Usage example: Entitled("beta", Text("preview")) // If no entitlement function is set on the context, access is denied by default. func Entitled(feature string, node Node) Node { return &entitledNode{feature: feature, node: node} @@ -218,6 +226,7 @@ type switchNode struct { } // Switch renders based on runtime selector value. +// Usage example: Switch(func(ctx *Context) string { return ctx.Locale }, map[string]Node{"en": Text("hello")}) func Switch(selector func(*Context) string, cases map[string]Node) Node { return &switchNode{selector: selector, cases: cases} } @@ -238,11 +247,13 @@ type eachNode[T any] struct { } // Each iterates items and renders each via fn. +// Usage example: Each([]string{"a", "b"}, func(v string) Node { return Text(v) }) func Each[T any](items []T, fn func(T) Node) Node { return EachSeq(slices.Values(items), fn) } // EachSeq iterates an iter.Seq and renders each via fn. +// Usage example: EachSeq(slices.Values([]string{"a", "b"}), func(v string) Node { return Text(v) }) func EachSeq[T any](items iter.Seq[T], fn func(T) Node) Node { return &eachNode[T]{items: items, fn: fn} } diff --git a/node_test.go b/node_test.go index 6a076bd..9aa1f33 100644 --- a/node_test.go +++ b/node_test.go @@ -6,7 +6,7 @@ import ( i18n "dappco.re/go/core/i18n" ) -func TestRawNode_Render(t *testing.T) { +func TestRawNode_Render_Good(t *testing.T) { ctx := NewContext() node := Raw("hello") got := node.Render(ctx) @@ -15,7 +15,7 @@ func TestRawNode_Render(t *testing.T) { } } -func TestElNode_Render(t *testing.T) { +func TestElNode_Render_Good(t *testing.T) { ctx := NewContext() node := El("div", Raw("content")) got := node.Render(ctx) @@ -25,7 +25,7 @@ func TestElNode_Render(t *testing.T) { } } -func TestElNode_Nested(t *testing.T) { +func TestElNode_Nested_Good(t *testing.T) { ctx := NewContext() node := El("div", El("span", Raw("inner"))) got := node.Render(ctx) @@ -35,7 +35,7 @@ func TestElNode_Nested(t *testing.T) { } } -func TestElNode_MultipleChildren(t *testing.T) { +func TestElNode_MultipleChildren_Good(t *testing.T) { ctx := NewContext() node := El("div", Raw("a"), Raw("b")) got := node.Render(ctx) @@ -45,7 +45,7 @@ func TestElNode_MultipleChildren(t *testing.T) { } } -func TestElNode_VoidElement(t *testing.T) { +func TestElNode_VoidElement_Good(t *testing.T) { ctx := NewContext() node := El("br") got := node.Render(ctx) @@ -55,7 +55,7 @@ func TestElNode_VoidElement(t *testing.T) { } } -func TestTextNode_Render(t *testing.T) { +func TestTextNode_Render_Good(t *testing.T) { ctx := NewContext() node := Text("hello") got := node.Render(ctx) @@ -64,7 +64,7 @@ func TestTextNode_Render(t *testing.T) { } } -func TestTextNode_Escapes(t *testing.T) { +func TestTextNode_Escapes_Good(t *testing.T) { ctx := NewContext() node := Text("") got := node.Render(ctx) @@ -76,7 +76,7 @@ func TestTextNode_Escapes(t *testing.T) { } } -func TestIfNode_True(t *testing.T) { +func TestIfNode_True_Good(t *testing.T) { ctx := NewContext() node := If(func(*Context) bool { return true }, Raw("visible")) got := node.Render(ctx) @@ -85,7 +85,7 @@ func TestIfNode_True(t *testing.T) { } } -func TestIfNode_False(t *testing.T) { +func TestIfNode_False_Good(t *testing.T) { ctx := NewContext() node := If(func(*Context) bool { return false }, Raw("hidden")) got := node.Render(ctx) @@ -94,7 +94,7 @@ func TestIfNode_False(t *testing.T) { } } -func TestUnlessNode(t *testing.T) { +func TestUnlessNode_Good(t *testing.T) { ctx := NewContext() node := Unless(func(*Context) bool { return false }, Raw("visible")) got := node.Render(ctx) @@ -103,7 +103,7 @@ func TestUnlessNode(t *testing.T) { } } -func TestEntitledNode_Granted(t *testing.T) { +func TestEntitledNode_Granted_Good(t *testing.T) { ctx := NewContext() ctx.Entitlements = func(feature string) bool { return feature == "premium" } node := Entitled("premium", Raw("premium content")) @@ -113,7 +113,7 @@ func TestEntitledNode_Granted(t *testing.T) { } } -func TestEntitledNode_Denied(t *testing.T) { +func TestEntitledNode_Denied_Bad(t *testing.T) { ctx := NewContext() ctx.Entitlements = func(feature string) bool { return false } node := Entitled("premium", Raw("premium content")) @@ -123,7 +123,7 @@ func TestEntitledNode_Denied(t *testing.T) { } } -func TestEntitledNode_NoFunc(t *testing.T) { +func TestEntitledNode_NoFunc_Bad(t *testing.T) { ctx := NewContext() node := Entitled("premium", Raw("premium content")) got := node.Render(ctx) @@ -132,7 +132,7 @@ func TestEntitledNode_NoFunc(t *testing.T) { } } -func TestEachNode(t *testing.T) { +func TestEachNode_Good(t *testing.T) { ctx := NewContext() items := []string{"a", "b", "c"} node := Each(items, func(item string) Node { @@ -145,7 +145,7 @@ func TestEachNode(t *testing.T) { } } -func TestEachNode_Empty(t *testing.T) { +func TestEachNode_Empty_Good(t *testing.T) { ctx := NewContext() node := Each([]string{}, func(item string) Node { return El("li", Raw(item)) @@ -156,7 +156,7 @@ func TestEachNode_Empty(t *testing.T) { } } -func TestElNode_Attr(t *testing.T) { +func TestElNode_Attr_Good(t *testing.T) { ctx := NewContext() node := Attr(El("div", Raw("content")), "class", "container") got := node.Render(ctx) @@ -166,7 +166,7 @@ func TestElNode_Attr(t *testing.T) { } } -func TestElNode_AttrEscaping(t *testing.T) { +func TestElNode_AttrEscaping_Good(t *testing.T) { ctx := NewContext() node := Attr(El("img"), "alt", `he said "hello"`) got := node.Render(ctx) @@ -175,7 +175,7 @@ func TestElNode_AttrEscaping(t *testing.T) { } } -func TestElNode_MultipleAttrs(t *testing.T) { +func TestElNode_MultipleAttrs_Good(t *testing.T) { ctx := NewContext() node := Attr(Attr(El("a", Raw("link")), "href", "/home"), "class", "nav") got := node.Render(ctx) @@ -184,7 +184,7 @@ func TestElNode_MultipleAttrs(t *testing.T) { } } -func TestAttr_NonElement(t *testing.T) { +func TestAttr_NonElement_Ugly(t *testing.T) { node := Attr(Raw("text"), "class", "x") got := node.Render(NewContext()) if got != "text" { @@ -192,7 +192,7 @@ func TestAttr_NonElement(t *testing.T) { } } -func TestUnlessNode_True(t *testing.T) { +func TestUnlessNode_True_Good(t *testing.T) { ctx := NewContext() node := Unless(func(*Context) bool { return true }, Raw("hidden")) got := node.Render(ctx) @@ -201,7 +201,7 @@ func TestUnlessNode_True(t *testing.T) { } } -func TestAttr_ThroughIfNode(t *testing.T) { +func TestAttr_ThroughIfNode_Good(t *testing.T) { ctx := NewContext() inner := El("div", Raw("content")) node := If(func(*Context) bool { return true }, inner) @@ -213,7 +213,7 @@ func TestAttr_ThroughIfNode(t *testing.T) { } } -func TestAttr_ThroughUnlessNode(t *testing.T) { +func TestAttr_ThroughUnlessNode_Good(t *testing.T) { ctx := NewContext() inner := El("div", Raw("content")) node := Unless(func(*Context) bool { return false }, inner) @@ -225,7 +225,7 @@ func TestAttr_ThroughUnlessNode(t *testing.T) { } } -func TestAttr_ThroughEntitledNode(t *testing.T) { +func TestAttr_ThroughEntitledNode_Good(t *testing.T) { ctx := NewContext() ctx.Entitlements = func(string) bool { return true } inner := El("div", Raw("content")) @@ -238,7 +238,7 @@ func TestAttr_ThroughEntitledNode(t *testing.T) { } } -func TestTextNode_WithService(t *testing.T) { +func TestTextNode_WithService_Good(t *testing.T) { svc, _ := i18n.New() ctx := NewContextWithService(svc) node := Text("hello") @@ -248,7 +248,7 @@ func TestTextNode_WithService(t *testing.T) { } } -func TestSwitchNode(t *testing.T) { +func TestSwitchNode_Good(t *testing.T) { ctx := NewContext() cases := map[string]Node{ "dark": Raw("dark theme"), diff --git a/path.go b/path.go index 509970c..dda1d7a 100644 --- a/path.go +++ b/path.go @@ -1,6 +1,7 @@ package html // ParseBlockID extracts the slot sequence from a data-block ID. +// Usage example: slots := ParseBlockID("L-0-C-0") // "L-0-C-0" → ['L', 'C'] func ParseBlockID(id string) []byte { if id == "" { diff --git a/path_test.go b/path_test.go index 0ddfa9c..f8b484b 100644 --- a/path_test.go +++ b/path_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -func TestNestedLayout_PathChain(t *testing.T) { +func TestNestedLayout_PathChain_Good(t *testing.T) { inner := NewLayout("HCF").H(Raw("inner h")).C(Raw("inner c")).F(Raw("inner f")) outer := NewLayout("HLCRF"). H(Raw("header")).L(inner).C(Raw("main")).R(Raw("right")).F(Raw("footer")) @@ -25,7 +25,7 @@ func TestNestedLayout_PathChain(t *testing.T) { } } -func TestNestedLayout_DeepNesting(t *testing.T) { +func TestNestedLayout_DeepNesting_Ugly(t *testing.T) { deepest := NewLayout("C").C(Raw("deep")) middle := NewLayout("C").C(deepest) outer := NewLayout("C").C(middle) @@ -38,7 +38,7 @@ func TestNestedLayout_DeepNesting(t *testing.T) { } } -func TestBlockID(t *testing.T) { +func TestBlockID_Good(t *testing.T) { tests := []struct { path string slot byte @@ -59,7 +59,7 @@ func TestBlockID(t *testing.T) { } } -func TestParseBlockID(t *testing.T) { +func TestParseBlockID_Good(t *testing.T) { tests := []struct { id string want []byte diff --git a/pipeline.go b/pipeline.go index 183e6a8..dd33593 100644 --- a/pipeline.go +++ b/pipeline.go @@ -9,6 +9,7 @@ import ( ) // StripTags removes HTML tags from rendered output, returning plain text. +// Usage example: text := StripTags("
Hello world
") // Tag boundaries are collapsed into single spaces; result is trimmed. // Does not handle script/style element content (go-html does not generate these). func StripTags(html string) string { @@ -45,6 +46,7 @@ func StripTags(html string) string { // Imprint renders a node tree to HTML, strips tags, tokenises the text, // and returns a GrammarImprint — the full render-reverse pipeline. +// Usage example: imp := Imprint(Text("welcome"), NewContext()) func Imprint(node Node, ctx *Context) reversal.GrammarImprint { if ctx == nil { ctx = NewContext() @@ -58,6 +60,7 @@ func Imprint(node Node, ctx *Context) reversal.GrammarImprint { // CompareVariants runs the imprint pipeline on each responsive variant independently // and returns pairwise similarity scores. Key format: "name1:name2". +// Usage example: scores := CompareVariants(NewResponsive(), NewContext()) func CompareVariants(r *Responsive, ctx *Context) map[string]float64 { if ctx == nil { ctx = NewContext() diff --git a/pipeline_test.go b/pipeline_test.go index c896859..570a4bf 100644 --- a/pipeline_test.go +++ b/pipeline_test.go @@ -8,7 +8,7 @@ import ( i18n "dappco.re/go/core/i18n" ) -func TestStripTags_Simple(t *testing.T) { +func TestStripTags_Simple_Good(t *testing.T) { got := StripTags(`
hello
`) want := "hello" if got != want { @@ -16,7 +16,7 @@ func TestStripTags_Simple(t *testing.T) { } } -func TestStripTags_Nested(t *testing.T) { +func TestStripTags_Nested_Good(t *testing.T) { got := StripTags(`

Title

`) want := "Title" if got != want { @@ -24,7 +24,7 @@ func TestStripTags_Nested(t *testing.T) { } } -func TestStripTags_MultipleRegions(t *testing.T) { +func TestStripTags_MultipleRegions_Good(t *testing.T) { got := StripTags(`
Head
Body
`) want := "Head Body Foot" if got != want { @@ -32,21 +32,21 @@ func TestStripTags_MultipleRegions(t *testing.T) { } } -func TestStripTags_Empty(t *testing.T) { +func TestStripTags_Empty_Ugly(t *testing.T) { got := StripTags("") if got != "" { t.Errorf("StripTags(\"\") = %q, want empty", got) } } -func TestStripTags_NoTags(t *testing.T) { +func TestStripTags_NoTags_Good(t *testing.T) { got := StripTags("plain text") if got != "plain text" { t.Errorf("StripTags(plain) = %q, want %q", got, "plain text") } } -func TestStripTags_Entities(t *testing.T) { +func TestStripTags_Entities_Good(t *testing.T) { got := StripTags(`<script>`) want := "<script>" if got != want { @@ -54,7 +54,7 @@ func TestStripTags_Entities(t *testing.T) { } } -func TestImprint_FromNode(t *testing.T) { +func TestImprint_FromNode_Good(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() @@ -74,7 +74,7 @@ func TestImprint_FromNode(t *testing.T) { } } -func TestImprint_SimilarPages(t *testing.T) { +func TestImprint_SimilarPages_Good(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() @@ -102,7 +102,7 @@ func TestImprint_SimilarPages(t *testing.T) { } } -func TestCompareVariants(t *testing.T) { +func TestCompareVariants_Good(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() diff --git a/render.go b/render.go index 3d3a7e3..728ca7f 100644 --- a/render.go +++ b/render.go @@ -1,6 +1,7 @@ package html // Render is a convenience function that renders a node tree to HTML. +// Usage example: html := Render(El("main", Text("welcome")), NewContext()) func Render(node Node, ctx *Context) string { if ctx == nil { ctx = NewContext() diff --git a/render_test.go b/render_test.go index d8c8fcc..2b71858 100644 --- a/render_test.go +++ b/render_test.go @@ -6,7 +6,7 @@ import ( i18n "dappco.re/go/core/i18n" ) -func TestRender_FullPage(t *testing.T) { +func TestRender_FullPage_Good(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() @@ -49,7 +49,7 @@ func TestRender_FullPage(t *testing.T) { } } -func TestRender_EntitlementGating(t *testing.T) { +func TestRender_EntitlementGating_Good(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() @@ -77,7 +77,7 @@ func TestRender_EntitlementGating(t *testing.T) { } } -func TestRender_XSSPrevention(t *testing.T) { +func TestRender_XSSPrevention_Good(t *testing.T) { svc, _ := i18n.New() i18n.SetDefault(svc) ctx := NewContext() diff --git a/responsive.go b/responsive.go index 2be5f2e..fa2c479 100644 --- a/responsive.go +++ b/responsive.go @@ -1,6 +1,7 @@ package html // Responsive wraps multiple Layout variants for breakpoint-aware rendering. +// Usage example: r := NewResponsive().Variant("mobile", NewLayout("C")) // Each variant is rendered inside a container with data-variant for CSS targeting. type Responsive struct { variants []responsiveVariant @@ -12,11 +13,13 @@ type responsiveVariant struct { } // NewResponsive creates a new multi-variant responsive compositor. +// Usage example: r := NewResponsive() func NewResponsive() *Responsive { return &Responsive{} } // Variant adds a named layout variant (e.g., "desktop", "tablet", "mobile"). +// Usage example: NewResponsive().Variant("desktop", NewLayout("HLCRF")) // Variants render in insertion order. func (r *Responsive) Variant(name string, layout *Layout) *Responsive { r.variants = append(r.variants, responsiveVariant{name: name, layout: layout}) @@ -24,6 +27,7 @@ func (r *Responsive) Variant(name string, layout *Layout) *Responsive { } // Render produces HTML with each variant in a data-variant container. +// Usage example: html := NewResponsive().Variant("mobile", NewLayout("C")).Render(NewContext()) func (r *Responsive) Render(ctx *Context) string { b := newTextBuilder() for _, v := range r.variants { diff --git a/responsive_test.go b/responsive_test.go index a0821f8..62f3255 100644 --- a/responsive_test.go +++ b/responsive_test.go @@ -4,7 +4,7 @@ import ( "testing" ) -func TestResponsive_SingleVariant(t *testing.T) { +func TestResponsive_SingleVariant_Good(t *testing.T) { ctx := NewContext() r := NewResponsive(). Variant("desktop", NewLayout("HLCRF"). @@ -19,7 +19,7 @@ func TestResponsive_SingleVariant(t *testing.T) { } } -func TestResponsive_MultiVariant(t *testing.T) { +func TestResponsive_MultiVariant_Good(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"))). @@ -35,7 +35,7 @@ func TestResponsive_MultiVariant(t *testing.T) { } } -func TestResponsive_VariantOrder(t *testing.T) { +func TestResponsive_VariantOrder_Good(t *testing.T) { ctx := NewContext() r := NewResponsive(). Variant("desktop", NewLayout("HLCRF").C(Raw("d"))). @@ -53,7 +53,7 @@ func TestResponsive_VariantOrder(t *testing.T) { } } -func TestResponsive_NestedPaths(t *testing.T) { +func TestResponsive_NestedPaths_Good(t *testing.T) { ctx := NewContext() inner := NewLayout("HCF").H(Raw("ih")).C(Raw("ic")).F(Raw("if")) r := NewResponsive(). @@ -69,7 +69,7 @@ func TestResponsive_NestedPaths(t *testing.T) { } } -func TestResponsive_VariantsIndependent(t *testing.T) { +func TestResponsive_VariantsIndependent_Good(t *testing.T) { ctx := NewContext() r := NewResponsive(). Variant("a", NewLayout("HLCRF").C(Raw("content-a"))). @@ -83,6 +83,6 @@ func TestResponsive_VariantsIndependent(t *testing.T) { } } -func TestResponsive_ImplementsNode(t *testing.T) { +func TestResponsive_ImplementsNode_Ugly(t *testing.T) { var _ Node = NewResponsive() }