Files
sanderling/internal/bundler/web_bundle_test.go
T

165 lines
4.9 KiB
Go

package bundler
import (
"os"
"path/filepath"
"strings"
"testing"
)
const fakeRuntime = `
(globalThis as Record<string, unknown>).__sanderling__ = {
extract: (g: () => unknown) => ({ current: g(), previous: undefined }),
always: () => ({ __sanderlingFormula: true }),
now: () => ({ __sanderlingFormula: true }),
next: () => ({ __sanderlingFormula: true }),
eventually: () => ({ __sanderlingFormula: true }),
actions: (g: () => unknown) => ({ __sanderlingActionGenerator: true, generate: g }),
weighted: () => ({ __sanderlingActionGenerator: true }),
from: () => ({ generate: () => null }),
tap: () => null,
inputText: () => null,
swipe: () => null,
pressKey: () => null,
wait: () => null,
taps: {},
swipes: {},
waitOnce: {},
pressKeys: {},
};
(globalThis as Record<string, unknown>).__sanderlingExtractors__ = () => ({});
(globalThis as Record<string, unknown>).__sanderlingNextAction__ = () => null;
export {};
`
const fakeSpec = `
const handle = (globalThis as { __sanderling__: { extract: (g: () => unknown) => unknown } }).__sanderling__.extract(() => 42);
export const actionsRoot = handle;
export const properties = "WEB_PROPS_MARKER";
`
func TestBundleWeb_RegistersExpectedGlobals(t *testing.T) {
directory := t.TempDir()
runtimePath := filepath.Join(directory, "web-runtime.ts")
specPath := filepath.Join(directory, "spec.ts")
if err := os.WriteFile(runtimePath, []byte(fakeRuntime), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(specPath, []byte(fakeSpec), 0o644); err != nil {
t.Fatal(err)
}
result, err := BundleWeb(WebOptions{
EntryFile: specPath,
WebRuntimeFile: runtimePath,
})
if err != nil {
t.Fatalf("BundleWeb: %v", err)
}
source := string(result.JavaScript)
for _, expected := range []string{
"__sanderlingExtractors__",
"__sanderlingNextAction__",
"__sanderling__",
"WEB_PROPS_MARKER",
"globalThis.actions",
} {
if !strings.Contains(source, expected) {
t.Errorf("bundle missing %q\nsource head:\n%s", expected, head(source, 500))
}
}
if !strings.HasPrefix(strings.TrimSpace(source), "(()") {
t.Errorf("expected IIFE format, head:\n%s", head(source, 200))
}
if result.SHA256 == "" {
t.Errorf("expected non-empty sha256")
}
}
func TestBundleWeb_RejectsMissingEntry(t *testing.T) {
if _, err := BundleWeb(WebOptions{}); err == nil {
t.Error("expected error for empty options")
}
if _, err := BundleWeb(WebOptions{EntryFile: "x.ts"}); err == nil {
t.Error("expected error for missing runtime")
}
}
// TestBundleWeb_IsDeterministic builds the same source repeatedly with multiple
// Defines entries and asserts the SHA matches. This guards against any future
// change that introduces map-iteration ordering into the bundle output.
func TestBundleWeb_IsDeterministic(t *testing.T) {
directory := t.TempDir()
runtimePath := filepath.Join(directory, "web-runtime.ts")
specPath := filepath.Join(directory, "spec.ts")
if err := os.WriteFile(runtimePath, []byte(fakeRuntime), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(specPath, []byte(fakeSpec), 0o644); err != nil {
t.Fatal(err)
}
options := WebOptions{
EntryFile: specPath,
WebRuntimeFile: runtimePath,
Defines: map[string]string{
"API_BASE": "https://example.com",
"FEATURE": "true",
"VERSION": "1.2.3",
"TENANT": "alpha",
},
}
first, err := BundleWeb(options)
if err != nil {
t.Fatalf("first build: %v", err)
}
for attempt := range 10 {
repeat, err := BundleWeb(options)
if err != nil {
t.Fatalf("attempt %d: %v", attempt, err)
}
if repeat.SHA256 != first.SHA256 {
t.Fatalf("attempt %d: SHA changed (%s vs %s)", attempt, repeat.SHA256, first.SHA256)
}
}
}
// TestBundleWeb_InjectsSeedDefine asserts that a SANDERLING_SEED define is
// inlined into the bundle so --seed reaches the web runtime PRNG. esbuild
// replaces process.env.SANDERLING_SEED with the quoted literal at build time.
func TestBundleWeb_InjectsSeedDefine(t *testing.T) {
directory := t.TempDir()
runtimePath := filepath.Join(directory, "web-runtime.ts")
specPath := filepath.Join(directory, "spec.ts")
runtime := fakeRuntime + "\n(globalThis as Record<string, unknown>).__seed = process.env.SANDERLING_SEED;\n"
if err := os.WriteFile(runtimePath, []byte(runtime), 0o644); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(specPath, []byte(fakeSpec), 0o644); err != nil {
t.Fatal(err)
}
result, err := BundleWeb(WebOptions{
EntryFile: specPath,
WebRuntimeFile: runtimePath,
Defines: map[string]string{"SANDERLING_SEED": "8675309"},
})
if err != nil {
t.Fatalf("BundleWeb: %v", err)
}
source := string(result.JavaScript)
if !strings.Contains(source, "8675309") {
t.Errorf("seed literal not inlined into bundle\nsource head:\n%s", head(source, 500))
}
if strings.Contains(source, "process.env.SANDERLING_SEED") {
t.Errorf("define left unsubstituted in bundle")
}
}
func head(text string, max int) string {
if len(text) <= max {
return text
}
return text[:max] + "..."
}