mirror of
https://github.com/priyanshujain/sanderling.git
synced 2026-10-02 11:07:10 +00:00
Installs globalThis.__uatu__ with extract, always, actions, weighted, tap, inputText, and stub taps/swipes. Load runs the bundled spec, then pulls properties + actions out of globalThis. PushSnapshot rebuilds state.snapshots and refreshes every extractor handle's current/previous in registration order so chained extractors observe up-to-date values. Properties are wired through internal/ltl as Always(Thunk(...)), so verdicts latch to violated as soon as a predicate returns false. NextAction resolves actions/weighted recursively with a seedable rand source for reproducible runs.
85 lines
2.4 KiB
Go
85 lines
2.4 KiB
Go
package verifier
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/dop251/goja"
|
|
)
|
|
|
|
// Snapshots is the per-step extractor output forwarded by the SDK.
|
|
type Snapshots map[string]json.RawMessage
|
|
|
|
// stateObject builds a JS-side `{ snapshots, ax }` matching the State type
|
|
// from pkg/spec-api. ax is currently a stub returning nothing.
|
|
func stateObject(runtime *goja.Runtime, snapshots Snapshots) (*goja.Object, error) {
|
|
state := runtime.NewObject()
|
|
snapshotsObject := runtime.NewObject()
|
|
for key, raw := range snapshots {
|
|
value, err := jsonToJSValue(runtime, raw)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("snapshot %q: %w", key, err)
|
|
}
|
|
if err := snapshotsObject.Set(key, value); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
if err := state.Set("snapshots", snapshotsObject); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
accessibility := runtime.NewObject()
|
|
if err := accessibility.Set("find", runtime.ToValue(func(string) goja.Value { return goja.Undefined() })); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := accessibility.Set("findAll", runtime.ToValue(func(string) []goja.Value { return nil })); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := state.Set("ax", accessibility); err != nil {
|
|
return nil, err
|
|
}
|
|
return state, nil
|
|
}
|
|
|
|
func jsonToJSValue(runtime *goja.Runtime, raw json.RawMessage) (goja.Value, error) {
|
|
if len(raw) == 0 {
|
|
return goja.Undefined(), nil
|
|
}
|
|
var generic any
|
|
if err := json.Unmarshal(raw, &generic); err != nil {
|
|
return nil, fmt.Errorf("decode JSON: %w", err)
|
|
}
|
|
return runtime.ToValue(generic), nil
|
|
}
|
|
|
|
// jsValueToAction converts a JS-side {kind, on?, into?, text?} into a Go Action.
|
|
func jsValueToAction(runtime *goja.Runtime, value goja.Value) (Action, error) {
|
|
if value == nil || goja.IsNull(value) || goja.IsUndefined(value) {
|
|
return Action{}, fmt.Errorf("nil action")
|
|
}
|
|
object := value.ToObject(runtime)
|
|
kindValue := object.Get("kind")
|
|
if kindValue == nil {
|
|
return Action{}, fmt.Errorf("action missing kind")
|
|
}
|
|
kind := kindValue.String()
|
|
switch kind {
|
|
case "Tap":
|
|
on := object.Get("on")
|
|
return Action{Kind: ActionKindTap, On: stringOf(on)}, nil
|
|
case "InputText":
|
|
into := object.Get("into")
|
|
text := object.Get("text")
|
|
return Action{Kind: ActionKindInputText, On: stringOf(into), Text: stringOf(text)}, nil
|
|
default:
|
|
return Action{}, fmt.Errorf("unknown action kind %q", kind)
|
|
}
|
|
}
|
|
|
|
func stringOf(value goja.Value) string {
|
|
if value == nil || goja.IsNull(value) || goja.IsUndefined(value) {
|
|
return ""
|
|
}
|
|
return value.String()
|
|
}
|