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
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<string, unknown>;
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)
}
}
+1 -36
View File
@@ -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)
+6 -39
View File
@@ -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)
+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
// 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 = `