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.
This commit is contained in:
pj committed 2026-08-22 21:20:40 +05:30
1 parent 22296c5316
commit 5a0439d394
4 files changed
+70 -120

No files matched your search

+17 -25
View File
@@ -1,14 +1,14 @@
package verifier package verifier
import ( import (
"os"
"path/filepath"
"strings" "strings"
"testing" "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: // legacyRuntimeEntry stands in for the @sanderling/spec 0.0.3 runtime entry:
// it installs the picker and declares nothing, and its authored Scroll carries // 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 // 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) { func TestLoad_RefusesABundleThatDeclaresNoActionEncoding(t *testing.T) {
verifier := newVerifier(t) verifier := newVerifier(t)
err := verifier.Load(bundleWithRuntimeEntry(t, legacyRuntimeEntry)) err := verifier.Load(bundleSpec(t, bundleOptions{
SpecSource: propertiesOnlySpec,
RuntimeSource: legacyRuntimeEntry,
}))
if err == nil { if err == nil {
t.Fatal("Load accepted a bundle that declares no action encoding; a spec " + t.Fatal("Load accepted a bundle that declares no action encoding; a spec " +
"bundled by a package older than this binary dispatches every scroll " + "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<string, unknown>; `const target = globalThis as Record<string, unknown>;
target.__sanderlingActionEncoding__ = "action-wire/1";`, 1) 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 { if err == nil {
t.Fatal("Load accepted a bundle built against a different action encoding") 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) { func TestLoad_AcceptsABundleThatInstallsNoPicker(t *testing.T) {
verifier := newVerifier(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) t.Fatalf("Load refused a bundle that generates no actions: %v", err)
} }
} }
+1 -36
View File
@@ -3,54 +3,19 @@ package verifier
import ( import (
"errors" "errors"
"maps" "maps"
"os"
"path/filepath"
"slices" "slices"
"testing" "testing"
"github.com/priyanshujain/sanderling/internal/bundler"
"github.com/priyanshujain/sanderling/internal/hierarchy" "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 { func loadBundled(t *testing.T, source, treeJSON string) *Verifier {
t.Helper() t.Helper()
v, err := New() v, err := New()
if err != nil { if err != nil {
t.Fatal(err) 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) t.Fatalf("load: %v", err)
} }
tree, err := hierarchy.Parse(treeJSON) tree, err := hierarchy.Parse(treeJSON)
+6 -39
View File
@@ -2,48 +2,15 @@ package verifier
import ( import (
"encoding/json" "encoding/json"
"path/filepath"
"testing" "testing"
"github.com/priyanshujain/sanderling/internal/bundler"
"github.com/priyanshujain/sanderling/internal/hierarchy" "github.com/priyanshujain/sanderling/internal/hierarchy"
"github.com/priyanshujain/sanderling/internal/ltl" "github.com/priyanshujain/sanderling/internal/ltl"
) )
// bundleIntegrationSpec bundles testdata/integration_spec.ts via the real // The integration spec carries no reference to any specific example app, so
// @sanderling/spec API so the integration test exercises the same path the CLI // bundling it exercises the CLI's path over a neutral subject.
// uses, with no reference to any specific example app. var integrationSpec = bundleOptions{SpecFile: "testdata/integration_spec.ts"}
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)
}
func listSnapshots() Snapshots { func listSnapshots() Snapshots {
return Snapshots{ return Snapshots{
@@ -66,7 +33,7 @@ func formSnapshots() Snapshots {
// primary button when both are present in the hierarchy. // primary button when both are present in the hierarchy.
func TestIntegrationSpecFiresInputActions(t *testing.T) { func TestIntegrationSpecFiresInputActions(t *testing.T) {
v := newVerifier(t) v := newVerifier(t)
if err := v.Load(bundleIntegrationSpec(t)); err != nil { if err := v.Load(bundleSpec(t, integrationSpec)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -106,7 +73,7 @@ func TestIntegrationSpecFiresInputActions(t *testing.T) {
// for liveness properties that haven't had time to resolve yet. // for liveness properties that haven't had time to resolve yet.
func TestIntegrationSpecPropertiesEvaluate(t *testing.T) { func TestIntegrationSpecPropertiesEvaluate(t *testing.T) {
v := newVerifier(t) v := newVerifier(t)
if err := v.Load(bundleIntegrationSpec(t)); err != nil { if err := v.Load(bundleSpec(t, integrationSpec)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@@ -163,7 +130,7 @@ func TestIntegrationSpecActionsFireOnEachRoute(t *testing.T) {
for _, tc := range cases { for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) { t.Run(tc.name, func(t *testing.T) {
v := newVerifier(t) v := newVerifier(t)
if err := v.Load(bundleIntegrationSpec(t)); err != nil { if err := v.Load(bundleSpec(t, integrationSpec)); err != nil {
t.Fatal(err) t.Fatal(err)
} }
tree, err := hierarchy.Parse(tc.hierarchy) tree, err := hierarchy.Parse(tc.hierarchy)
+46 -20
View File
@@ -34,41 +34,67 @@ func mustLoad(t *testing.T, verifier *Verifier, source string) {
} }
} }
// bundleActionSpec bundles an inline TS spec authored against @sanderling/spec // bundleOptions names the entry of a test bundle: SpecSource is written to a
// together with the goja runtime entry, so loading it installs // temporary file, SpecFile is a path already on disk, and setting both is a
// __sanderlingNextAction__ (the shared picker). Action targets must be resolved // mistake. RuntimeSource stands in for the shipped goja runtime entry so a
// ax elements (carrying x/y) or builtins; raw selector strings no longer // test can bundle against a picker this binary does not ship.
// resolve to coordinates in the unified contract. type bundleOptions struct {
func bundleActionSpec(t *testing.T, specSource string) string { 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() t.Helper()
dir := t.TempDir() directory := t.TempDir()
specPath := filepath.Join(dir, "spec.ts") absolute := func(relative string) string {
if err := os.WriteFile(specPath, []byte(specSource), 0o600); err != nil { path, err := filepath.Abs(relative)
t.Fatal(err) if err != nil {
t.Fatal(err)
}
return path
} }
apiPath, err := filepath.Abs("../../pkg/spec/src/index.ts") write := func(name, contents string) string {
if err != nil { path := filepath.Join(directory, name)
t.Fatal(err) 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 { specPath := write("spec.ts", options.SpecSource)
t.Fatal(err) 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{ bundle, err := bundler.Bundle(bundler.Options{
EntryFile: specPath, EntryFile: specPath,
RuntimeFile: runtimePath, 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 { if err != nil {
t.Fatal(err) t.Fatalf("bundle: %v", err)
} }
return string(bundle.JavaScript) 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) { func loadActionSpec(t *testing.T, verifier *Verifier, specSource string) {
t.Helper() t.Helper()
mustLoad(t, verifier, bundleActionSpec(t, specSource)) mustLoad(t, verifier, bundleSpec(t, bundleOptions{SpecSource: specSource}))
} }
const helloSpec = ` const helloSpec = `