Files
aego/app/docsview/control_bridge_test.go

228 lines
7.2 KiB
Go

//go:build aego_editor
package docsview
import (
"encoding/json"
"testing"
"aego/app/playground"
"aego/platform/input"
)
func TestTryBridgeControlledFramesPreserveExistingRequests(t *testing.T) {
a := newHeadless(t)
if r := a.Call("scriptEnabled", []byte(`{"script":"missing"}`)); r.Error == "" {
t.Fatal("scriptEnabled accepted a missing boolean")
}
if r := a.Call("control", []byte(`{}`)); r.Error == "" {
t.Fatal("control accepted a missing boolean")
}
if r := a.Call("control", []byte(`{"enabled":true}`)); r.Error != "" {
t.Fatalf("could not enable controlled frames: %s", r.Error)
}
if r := a.Call("advance", []byte(`{"frames":1}`)); r.Error == "" {
t.Fatal("control advanced before loading a running Try")
}
p, err := playground.New()
if err != nil {
t.Fatal(err)
}
e, ok := p.Docs().Get("engine.SpriteRenderer")
if !ok {
t.Fatal("missing SpriteRenderer Try")
}
code := ""
if e.TryScript != "" {
code, err = p.TryCode(e.TryScript)
if err != nil {
t.Fatal(err)
}
}
raw, err := json.Marshal(map[string]any{"scene": e.Try, "select": e.TrySelect, "code": code, "prefabs": e.TryPrefabs, "artifacts": e.TryArtifacts})
if err != nil {
t.Fatal(err)
}
if r := a.Call("load", raw); r.Error != "" {
t.Fatal(r.Error)
}
before := a.session.State().Frames
if err := a.Step(3); err != nil || a.session.State().Frames != before {
t.Fatalf("presentation advanced the controlled world: %v", err)
}
for _, request := range []string{`{"frames":0}`, `{"frames":601}`, `{"frames":2,"keys":["F13"]}`, `{"frames":2,"keys":["D","D"]}`} {
if r := a.Call("advance", []byte(request)); r.Error == "" {
t.Fatalf("control accepted invalid request %s", request)
}
}
if r := a.Call("advance", []byte(`{"frames":4,"keys":["D"]}`)); r.Error != "" {
t.Fatal(r.Error)
}
if r := a.Call("control", []byte(`{"enabled":false}`)); r.Error == "" {
t.Fatal("control disabled while frames were still queued")
}
if err := a.Step(4); err != nil {
t.Fatal(err)
}
if got := a.session.State().Frames; got != before+4 {
t.Fatalf("controlled world advanced %d frames, want 4", got-before)
}
if err := a.Step(3); err != nil || a.session.State().Frames != before+4 {
t.Fatalf("world did not hold after controlled frames: %v", err)
}
if r := a.Call("control", []byte(`{"enabled":false}`)); r.Error != "" {
t.Fatal(r.Error)
}
if r := a.Call("advance", []byte(`{"frames":1}`)); r.Error == "" {
t.Fatal("advance worked after control was disabled")
}
if r := a.Call("scriptEnabled", []byte(`{"script":"missing"}`)); r.Error == "" {
t.Fatal("control broke the existing scriptEnabled validation")
}
}
func TestTryBridgeReportsOnlyPresentedViewport(t *testing.T) {
a := newHeadless(t)
if r := a.Call("viewport", nil); r.Error == "" {
t.Fatal("unpresented viewport was accepted")
}
if err := a.Step(1); err != nil {
t.Fatal(err)
}
var reply struct {
Error string `json:"error"`
Viewport *struct {
X, Y, Width, Height float32
CanvasWidth, CanvasHeight int
} `json:"viewport"`
}
if err := json.Unmarshal([]byte(a.Reply("viewport", "")), &reply); err != nil {
t.Fatal(err)
}
if reply.Error != "" || reply.Viewport == nil || reply.Viewport.Width <= 0 || reply.Viewport.Height <= 0 || reply.Viewport.Y <= 0 || reply.Viewport.CanvasWidth <= 0 || reply.Viewport.CanvasHeight <= 0 {
t.Fatalf("invalid presented Game View rectangle: %+v", reply)
}
}
func TestControlledTryMovesRealGuidePlayerAndResets(t *testing.T) {
p, err := playground.New()
if err != nil {
t.Fatal(err)
}
e, ok := p.Docs().Get("gameplay/motion")
if !ok {
t.Fatal("Motion guide is missing")
}
code, err := p.TryCode(e.TryScript)
if err != nil {
t.Fatal(err)
}
a := newHeadless(t)
if r := a.Call("control", []byte(`{"enabled":true}`)); r.Error != "" {
t.Fatal(r.Error)
}
req, err := json.Marshal(map[string]any{"scene": e.Try, "select": e.TrySelect, "code": code, "prefabs": e.TryPrefabs, "artifacts": e.TryArtifacts})
if err != nil {
t.Fatal(err)
}
if r := a.Call("load", req); r.Error != "" {
t.Fatal(r.Error)
}
advance := func(payload string, frames int) {
t.Helper()
if r := a.Call("advance", []byte(payload)); r.Error != "" {
t.Fatal(r.Error)
}
if err := a.Step(frames); err != nil || a.control.Remaining != 0 {
t.Fatalf("controlled frames did not finish: %v", err)
}
}
advance(`{"frames":30}`, 30)
sc := a.session.Controller().Scene()
player := sc.Find(sc.Root(), "Player")
if player.IsZero() || !sc.IsAlive(player) {
t.Fatal("guide Player is missing")
}
origin := sc.WorldPosition(player)
advance(`{"frames":30,"keys":["D"]}`, 30)
moved := sc.WorldPosition(player)
if moved.X <= origin.X+0.1 {
t.Fatalf("controlled D input did not move the real player: %v -> %v", origin, moved)
}
if err := a.Step(5); err != nil || sc.WorldPosition(player) != moved {
t.Fatalf("unrequested presentation frames moved the player: %v", err)
}
if r := a.Call("advance", []byte(`{"frames":3,"keys":["D"]}`)); r.Error != "" {
t.Fatal(r.Error)
}
if err := a.Step(1); err != nil || !a.e.Input().KeyDown(input.KeyD) {
t.Fatalf("controlled key did not become active: %v", err)
}
if r := a.Call("reset", nil); r.Error != "" {
t.Fatal(r.Error)
}
if a.control.Remaining != 0 || a.e.Input().KeyDown(input.KeyD) {
t.Fatal("Reset retained queued frames or a synthetic pressed key")
}
advance(`{"frames":30}`, 30)
sc = a.session.Controller().Scene()
player = sc.Find(sc.Root(), "Player")
if got := sc.WorldPosition(player); got != origin {
t.Fatalf("Reset did not restore the guide position: %v != %v", got, origin)
}
if r := a.Call("advance", []byte(`{"frames":3,"keys":["D"]}`)); r.Error != "" {
t.Fatal(r.Error)
}
if err := a.Step(1); err != nil {
t.Fatal(err)
}
if r := a.Call("set", []byte(`{"component":"invalid","field":"bad","value":1}`)); r.Error == "" {
t.Fatal("an invalid field edit was accepted")
}
if a.control.Remaining != 2 || !a.e.Input().KeyDown(input.KeyD) {
t.Fatal("a rejected edit unexpectedly cancelled the active input")
}
if r := a.Call("play", nil); r.Error != "" || !a.session.State().Paused {
t.Fatalf("could not pause controlled Try: %s", r.Error)
}
if a.control.Remaining != 0 || a.e.Input().KeyDown(input.KeyD) {
t.Fatal("Pause retained queued frames or a synthetic pressed key")
}
if errors := errorsOf(a); len(errors) != 0 {
t.Fatalf("guide runtime errors: %v", errors)
}
}
func TestTryViewportExcludesInspectorAtDifferentDPR(t *testing.T) {
for _, width := range []int{320, 390, 520, 692, 960} {
a, err := New(Options{Headless: true, FrameTime: 1.0 / 60, Width: width, Height: 560})
if err != nil {
t.Fatal(err)
}
if err := a.Step(1); err != nil {
a.Close()
t.Fatal(err)
}
v := a.Call("viewport", nil).Viewport
if v == nil {
a.Close()
t.Fatal("missing viewport")
}
r, err := v.pixelRect(width*2, 560*2)
if err != nil {
a.Close()
t.Fatal(err)
}
if width == 692 && (v.Width != 292 || r.Max.X != 584) {
t.Errorf("split viewport includes Inspector: %+v -> %v", v, r)
}
if width <= 520 && (r.Max.X != width*2 || r.Max.Y >= 560*2) {
t.Errorf("stacked viewport includes Inspector: %+v -> %v", v, r)
}
if r.Min.Y <= 0 || v.CanvasWidth != width || v.CanvasHeight != 560 {
t.Errorf("viewport includes toolbar or has incorrect canvas dimensions: %+v -> %v", v, r)
}
a.Close()
}
}