diff --git a/codegen/codegen.go b/codegen/codegen.go index e0183c5..1c26320 100644 --- a/codegen/codegen.go +++ b/codegen/codegen.go @@ -3,6 +3,7 @@ package codegen import ( + "sort" "text/template" core "dappco.re/go/core" @@ -76,8 +77,14 @@ func TagToClassName(tag string) string { func GenerateBundle(slots map[string]string) (string, error) { seen := make(map[string]bool) b := core.NewBuilder() + keys := make([]string, 0, len(slots)) + for slot := range slots { + keys = append(keys, slot) + } + sort.Strings(keys) - for slot, tag := range slots { + for _, slot := range keys { + tag := slots[slot] if seen[tag] { continue } diff --git a/codegen/codegen_test.go b/codegen/codegen_test.go index 66359b9..98c9e75 100644 --- a/codegen/codegen_test.go +++ b/codegen/codegen_test.go @@ -3,6 +3,7 @@ package codegen import ( + "strings" "testing" "github.com/stretchr/testify/assert" @@ -56,6 +57,29 @@ func TestGenerateBundle_DeduplicatesRegistrations_Good(t *testing.T) { assert.Equal(t, 2, countSubstr(js, "customElements.define")) } +func TestGenerateBundle_DeterministicOrdering_Good(t *testing.T) { + slots := map[string]string{ + "Z": "zed-panel", + "A": "alpha-panel", + "M": "main-content", + } + + js, err := GenerateBundle(slots) + require.NoError(t, err) + + alpha := strings.Index(js, "class AlphaPanel") + main := strings.Index(js, "class MainContent") + zed := strings.Index(js, "class ZedPanel") + + assert.NotEqual(t, -1, alpha) + assert.NotEqual(t, -1, main) + assert.NotEqual(t, -1, zed) + assert.Less(t, alpha, main) + assert.Less(t, main, zed) + assert.Equal(t, 3, countSubstr(js, "extends HTMLElement")) + assert.Equal(t, 3, countSubstr(js, "customElements.define")) +} + func countSubstr(s, substr string) int { if substr == "" { return len(s) + 1 diff --git a/docs/development.md b/docs/development.md index 55af80b..7ee9fec 100644 --- a/docs/development.md +++ b/docs/development.md @@ -293,4 +293,4 @@ func TestGenerateClass_ValidTag(t *testing.T) { - `Responsive.Variant()` accepts only `*Layout`, not arbitrary `Node` values. Arbitrary subtrees must be wrapped in a single-slot layout first. - `Context.service` is unexported. Custom translation injection requires `NewContextWithService()`. There is no way to swap the translator after construction. - The WASM module has no integration test for the JavaScript exports. `size_test.go` tests binary size only; it does not exercise `renderToString` behaviour from JavaScript. -- `codegen.GenerateBundle()` iterates a `map`, so the order of class definitions in the output is non-deterministic. This does not affect correctness but may cause cosmetic diffs between runs. +- `codegen.GenerateBundle()` now renders output classes in sorted slot-key order so generated bundles are stable between runs.