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 = `