From 5a0439d3946cf2beca5c38d3d06190d426b1d54f Mon Sep 17 00:00:00 2001 From: PJ Date: Sat, 22 Aug 2026 21:20:40 +0530 Subject: [PATCH] test(verifier): one spec bundler instead of four The four differed only in which rows of the alias map they omitted, and esbuild applies an alias only on an actual import, so the omissions were not a difference. One bundleSpec carries what genuinely varied: an inline spec or a path on disk, and an optional runtime entry to swap for the shipped one. --- internal/verifier/action_encoding_test.go | 42 ++++++-------- internal/verifier/setup_action_test.go | 37 +----------- internal/verifier/spec_integration_test.go | 45 ++------------- internal/verifier/verifier_test.go | 66 +++++++++++++++------- 4 files changed, 70 insertions(+), 120 deletions(-) diff --git a/internal/verifier/action_encoding_test.go b/internal/verifier/action_encoding_test.go index 5a5fbe7..2689b5a 100644 --- a/internal/verifier/action_encoding_test.go +++ b/internal/verifier/action_encoding_test.go @@ -1,14 +1,14 @@ package verifier import ( - "os" - "path/filepath" "strings" "testing" - - "github.com/priyanshujain/sanderling/internal/bundler" ) +// The picker under test comes from the runtime entry, so the spec beside it +// only has to satisfy Load's demand for a properties global. +const propertiesOnlySpec = "globalThis.properties = {};\n" + // legacyRuntimeEntry stands in for the @sanderling/spec 0.0.3 runtime entry: // it installs the picker and declares nothing, and its authored Scroll carries // the container's own point as both endpoints. Paired with a binary that @@ -33,28 +33,13 @@ Object.defineProperty(target, "__sanderlingNextAction__", { }); ` -func bundleWithRuntimeEntry(t *testing.T, runtimeSource string) string { - t.Helper() - dir := t.TempDir() - specPath := filepath.Join(dir, "spec.ts") - if err := os.WriteFile(specPath, []byte("globalThis.properties = {};\n"), 0o600); err != nil { - t.Fatal(err) - } - runtimePath := filepath.Join(dir, "legacy-runtime.ts") - if err := os.WriteFile(runtimePath, []byte(runtimeSource), 0o600); err != nil { - t.Fatal(err) - } - bundle, err := bundler.Bundle(bundler.Options{EntryFile: specPath, RuntimeFile: runtimePath}) - if err != nil { - t.Fatal(err) - } - return string(bundle.JavaScript) -} - func TestLoad_RefusesABundleThatDeclaresNoActionEncoding(t *testing.T) { verifier := newVerifier(t) - err := verifier.Load(bundleWithRuntimeEntry(t, legacyRuntimeEntry)) + err := verifier.Load(bundleSpec(t, bundleOptions{ + SpecSource: propertiesOnlySpec, + RuntimeSource: legacyRuntimeEntry, + })) if err == nil { t.Fatal("Load accepted a bundle that declares no action encoding; a spec " + "bundled by a package older than this binary dispatches every scroll " + @@ -76,7 +61,10 @@ func TestLoad_RefusesABundleThatDeclaresADifferentActionEncoding(t *testing.T) { `const target = globalThis as Record; target.__sanderlingActionEncoding__ = "action-wire/1";`, 1) - err := verifier.Load(bundleWithRuntimeEntry(t, runtime)) + err := verifier.Load(bundleSpec(t, bundleOptions{ + SpecSource: propertiesOnlySpec, + RuntimeSource: runtime, + })) if err == nil { t.Fatal("Load accepted a bundle built against a different action encoding") } @@ -119,7 +107,11 @@ target.__sanderlingBundleCheck__ = true; func TestLoad_AcceptsABundleThatInstallsNoPicker(t *testing.T) { verifier := newVerifier(t) - if err := verifier.Load(bundleWithRuntimeEntry(t, pickerFreeRuntimeEntry)); err != nil { + bundle := bundleSpec(t, bundleOptions{ + SpecSource: propertiesOnlySpec, + RuntimeSource: pickerFreeRuntimeEntry, + }) + if err := verifier.Load(bundle); err != nil { t.Fatalf("Load refused a bundle that generates no actions: %v", err) } } diff --git a/internal/verifier/setup_action_test.go b/internal/verifier/setup_action_test.go index 7d41b08..c7420ed 100644 --- a/internal/verifier/setup_action_test.go +++ b/internal/verifier/setup_action_test.go @@ -3,54 +3,19 @@ package verifier import ( "errors" "maps" - "os" - "path/filepath" "slices" "testing" - "github.com/priyanshujain/sanderling/internal/bundler" "github.com/priyanshujain/sanderling/internal/hierarchy" ) -// bundleInlineSpec bundles an inline spec through the real @sanderling/spec API -// and goja runtime entry, so the bundle installs __sanderlingSetupAction__ the -// way the CLI does. -func bundleInlineSpec(t *testing.T, source string) string { - t.Helper() - dir := t.TempDir() - specPath := filepath.Join(dir, "spec.ts") - if err := os.WriteFile(specPath, []byte(source), 0o600); err != nil { - t.Fatal(err) - } - abs := func(rel string) string { - path, err := filepath.Abs(rel) - if err != nil { - t.Fatal(err) - } - return path - } - bundle, err := bundler.Bundle(bundler.Options{ - EntryFile: specPath, - RuntimeFile: abs("../../pkg/spec/src/goja-runtime.ts"), - Aliases: map[string]string{ - "@sanderling/spec": abs("../../pkg/spec/src/index.ts"), - "@sanderling/spec/defaults": abs("../../pkg/spec/src/defaults/index.ts"), - "@sanderling/spec/defaults/properties": abs("../../pkg/spec/src/defaults/properties.ts"), - }, - }) - if err != nil { - t.Fatalf("bundle: %v", err) - } - return string(bundle.JavaScript) -} - func loadBundled(t *testing.T, source, treeJSON string) *Verifier { t.Helper() v, err := New() if err != nil { t.Fatal(err) } - if err := v.Load(bundleInlineSpec(t, source)); err != nil { + if err := v.Load(bundleSpec(t, bundleOptions{SpecSource: source})); err != nil { t.Fatalf("load: %v", err) } tree, err := hierarchy.Parse(treeJSON) diff --git a/internal/verifier/spec_integration_test.go b/internal/verifier/spec_integration_test.go index 8727e30..ae146a7 100644 --- a/internal/verifier/spec_integration_test.go +++ b/internal/verifier/spec_integration_test.go @@ -2,48 +2,15 @@ package verifier import ( "encoding/json" - "path/filepath" "testing" - "github.com/priyanshujain/sanderling/internal/bundler" "github.com/priyanshujain/sanderling/internal/hierarchy" "github.com/priyanshujain/sanderling/internal/ltl" ) -// bundleIntegrationSpec bundles testdata/integration_spec.ts via the real -// @sanderling/spec API so the integration test exercises the same path the CLI -// uses, with no reference to any specific example app. -func bundleIntegrationSpec(t *testing.T) string { - t.Helper() - specPath, err := filepath.Abs("testdata/integration_spec.ts") - if err != nil { - t.Fatal(err) - } - apiPath, err := filepath.Abs("../../pkg/spec/src/index.ts") - if err != nil { - t.Fatal(err) - } - runtimePath, err := filepath.Abs("../../pkg/spec/src/goja-runtime.ts") - if err != nil { - t.Fatal(err) - } - defaultsPath, err := filepath.Abs("../../pkg/spec/src/defaults/properties.ts") - if err != nil { - t.Fatal(err) - } - bundle, err := bundler.Bundle(bundler.Options{ - EntryFile: specPath, - RuntimeFile: runtimePath, - Aliases: map[string]string{ - "@sanderling/spec": apiPath, - "@sanderling/spec/defaults/properties": defaultsPath, - }, - }) - if err != nil { - t.Fatal(err) - } - return string(bundle.JavaScript) -} +// The integration spec carries no reference to any specific example app, so +// bundling it exercises the CLI's path over a neutral subject. +var integrationSpec = bundleOptions{SpecFile: "testdata/integration_spec.ts"} func listSnapshots() Snapshots { return Snapshots{ @@ -66,7 +33,7 @@ func formSnapshots() Snapshots { // primary button when both are present in the hierarchy. func TestIntegrationSpecFiresInputActions(t *testing.T) { v := newVerifier(t) - if err := v.Load(bundleIntegrationSpec(t)); err != nil { + if err := v.Load(bundleSpec(t, integrationSpec)); err != nil { t.Fatal(err) } @@ -106,7 +73,7 @@ func TestIntegrationSpecFiresInputActions(t *testing.T) { // for liveness properties that haven't had time to resolve yet. func TestIntegrationSpecPropertiesEvaluate(t *testing.T) { v := newVerifier(t) - if err := v.Load(bundleIntegrationSpec(t)); err != nil { + if err := v.Load(bundleSpec(t, integrationSpec)); err != nil { t.Fatal(err) } @@ -163,7 +130,7 @@ func TestIntegrationSpecActionsFireOnEachRoute(t *testing.T) { for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { v := newVerifier(t) - if err := v.Load(bundleIntegrationSpec(t)); err != nil { + if err := v.Load(bundleSpec(t, integrationSpec)); err != nil { t.Fatal(err) } tree, err := hierarchy.Parse(tc.hierarchy) diff --git a/internal/verifier/verifier_test.go b/internal/verifier/verifier_test.go index 1911739..78b0f6d 100644 --- a/internal/verifier/verifier_test.go +++ b/internal/verifier/verifier_test.go @@ -34,41 +34,67 @@ func mustLoad(t *testing.T, verifier *Verifier, source string) { } } -// bundleActionSpec bundles an inline TS spec authored against @sanderling/spec -// together with the goja runtime entry, so loading it installs -// __sanderlingNextAction__ (the shared picker). Action targets must be resolved -// ax elements (carrying x/y) or builtins; raw selector strings no longer -// resolve to coordinates in the unified contract. -func bundleActionSpec(t *testing.T, specSource string) string { +// bundleOptions names the entry of a test bundle: SpecSource is written to a +// temporary file, SpecFile is a path already on disk, and setting both is a +// mistake. RuntimeSource stands in for the shipped goja runtime entry so a +// test can bundle against a picker this binary does not ship. +type bundleOptions struct { + SpecSource string + SpecFile string + RuntimeSource string +} + +// bundleSpec bundles through the real @sanderling/spec API and runtime entry, +// so loading the result installs __sanderlingNextAction__ and +// __sanderlingSetupAction__ the way the CLI does. +func bundleSpec(t *testing.T, options bundleOptions) string { t.Helper() - dir := t.TempDir() - specPath := filepath.Join(dir, "spec.ts") - if err := os.WriteFile(specPath, []byte(specSource), 0o600); err != nil { - t.Fatal(err) + directory := t.TempDir() + absolute := func(relative string) string { + path, err := filepath.Abs(relative) + if err != nil { + t.Fatal(err) + } + return path } - apiPath, err := filepath.Abs("../../pkg/spec/src/index.ts") - if err != nil { - t.Fatal(err) + write := func(name, contents string) string { + path := filepath.Join(directory, name) + if err := os.WriteFile(path, []byte(contents), 0o600); err != nil { + t.Fatal(err) + } + return path } - runtimePath, err := filepath.Abs("../../pkg/spec/src/goja-runtime.ts") - if err != nil { - t.Fatal(err) + + specPath := write("spec.ts", options.SpecSource) + if options.SpecFile != "" { + specPath = absolute(options.SpecFile) } + runtimePath := absolute("../../pkg/spec/src/goja-runtime.ts") + if options.RuntimeSource != "" { + runtimePath = write("runtime-entry.ts", options.RuntimeSource) + } + bundle, err := bundler.Bundle(bundler.Options{ EntryFile: specPath, RuntimeFile: runtimePath, - Aliases: map[string]string{"@sanderling/spec": apiPath}, + Aliases: map[string]string{ + "@sanderling/spec": absolute("../../pkg/spec/src/index.ts"), + "@sanderling/spec/defaults": absolute("../../pkg/spec/src/defaults/index.ts"), + "@sanderling/spec/defaults/properties": absolute("../../pkg/spec/src/defaults/properties.ts"), + }, }) if err != nil { - t.Fatal(err) + t.Fatalf("bundle: %v", err) } return string(bundle.JavaScript) } -// loadActionSpec bundles and loads an inline authored spec into the verifier. +// Action targets in specSource must be resolved ax elements (carrying x/y) or +// builtins; raw selector strings no longer resolve to coordinates in the +// unified contract. func loadActionSpec(t *testing.T, verifier *Verifier, specSource string) { t.Helper() - mustLoad(t, verifier, bundleActionSpec(t, specSource)) + mustLoad(t, verifier, bundleSpec(t, bundleOptions{SpecSource: specSource})) } const helloSpec = `