Files
aego/app/edit/play_test.go

205 lines
5.4 KiB
Go

package edit
import (
"reflect"
"testing"
"aego/core/ids"
"aego/core/mathx"
"aego/editor/core/document"
"aego/editor/core/history/commands"
"aego/editor/core/templates"
"aego/modules/render2d"
"aego/platform/input"
"aego/scene"
"aego/scene/sceneio"
"aego/schema"
)
type spinFixture struct {
s *Session
doc *document.SceneDocument
spinner ids.NodeID
typ schema.TypeID
speed schema.FieldID
}
func openSpinner(t *testing.T) *spinFixture {
t.Helper()
p := newProject(t)
rel := "scenes/play.aescene"
writeScene(t, p, rel, func(s *scene.Scene) {
cam := s.Create(s.Root(), "Camera")
*scene.Add[render2d.Camera2D](s, cam) = render2d.DefaultCamera2D()
s.Create(s.Root(), "Spinner")
})
s, err := Open(p, headless(), spinModule{})
if err != nil {
t.Fatal(err)
}
s.Step()
ctx := s.App.Context()
d, err := ctx.Docs.Open(rel)
if err != nil {
t.Fatal(err)
}
es := d.Scene()
f := &spinFixture{s: s, doc: d, spinner: es.ID(es.Find(es.Root(), "Spinner"))}
td, ok := ctx.Types.Schema().ByGoType(reflect.TypeFor[Spin]())
if !ok {
t.Fatal("Spin is not registered")
}
fd, _ := td.Field("speed")
f.typ, f.speed = td.ID, fd.ID
add := commands.NewMutation("Add "+td.FullName, commands.EnvFor(es),
commands.AddComp(es, f.spinner, td, []commands.FieldValue{{Field: fd.ID, Value: schema.Float32(3)}}))
if err := d.History.Exec(add); err != nil {
t.Fatal(err)
}
d.History.MarkSaved()
return f
}
func speedIn(t *testing.T, s *scene.Scene, id ids.NodeID) float32 {
t.Helper()
sp, ok := scene.Get[Spin](s, s.Handle(id))
if !ok {
t.Fatal("Spin missing")
}
return sp.Speed
}
func TestPlayEditsGoToPlayScene(t *testing.T) {
f := openSpinner(t)
defer f.s.Close()
ctx := f.s.App.Context()
before := sceneio.Save(f.doc.Scene())
pc := f.s.App.Play()
if err := pc.Start(f.doc); err != nil {
t.Fatal(err)
}
if ctx.Doc() == f.doc || !ctx.Docs.InPlay() {
t.Fatal("the active document must be the play view")
}
if err := ctx.Props().Set(nil, []ids.NodeID{f.spinner}, f.typ, f.speed, schema.Float32(9)); err != nil {
t.Fatal(err)
}
if v := speedIn(t, pc.Scene(), f.spinner); v != 9 {
t.Fatalf("play speed = %v", v)
}
if f.doc.Dirty() {
t.Fatal("play edit dirtied the document")
}
pc.Stop()
if string(sceneio.Save(f.doc.Scene())) != string(before) {
t.Fatal("edit scene changed")
}
if f.doc.Dirty() || ctx.Doc() != f.doc {
t.Fatal("stop must return to a clean edit document")
}
}
func TestPlayCreatedNodesVanishOnStop(t *testing.T) {
f := openSpinner(t)
defer f.s.Close()
ctx := f.s.App.Context()
pc := f.s.App.Play()
pc.Start(f.doc)
empty, _ := f.s.App.Templates().Get("engine.empty")
created, err := templates.Instantiate(ctx, empty, ids.NodeID{}, -1, nil)
if err != nil || len(created) != 1 {
t.Fatalf("create in play: %v %v", created, err)
}
if len(ctx.Selected()) != 1 {
t.Fatal("created node must be selected")
}
pc.Stop()
if !f.doc.Scene().Handle(created[0]).IsZero() {
t.Fatal("play-created node leaked into the edit scene")
}
if len(ctx.Selected()) != 0 {
t.Fatal("selection must drop nodes that no longer exist")
}
}
func TestCopyValuesToEditScene(t *testing.T) {
f := openSpinner(t)
defer f.s.Close()
ctx := f.s.App.Context()
pc := f.s.App.Play()
pc.Start(f.doc)
ctx.Props().Set(nil, []ids.NodeID{f.spinner}, f.typ, f.speed, schema.Float32(7))
if !pc.CanCopyToEdit([]ids.NodeID{f.spinner}, f.typ) {
t.Fatal("copy must be allowed for a node that exists in both scenes")
}
if err := pc.CopyToEdit([]ids.NodeID{f.spinner}, f.typ); err != nil {
t.Fatal(err)
}
if !f.doc.Dirty() || speedIn(t, f.doc.Scene(), f.spinner) != 7 {
t.Fatal("copy did not reach the edit scene")
}
pc.Stop()
if err := f.doc.History.Undo(); err != nil {
t.Fatal(err)
}
if v := speedIn(t, f.doc.Scene(), f.spinner); v != 3 {
t.Fatalf("undo left speed %v", v)
}
}
func TestUndoInsidePlay(t *testing.T) {
f := openSpinner(t)
defer f.s.Close()
ctx := f.s.App.Context()
pc := f.s.App.Play()
pc.Start(f.doc)
ctx.Props().Set(nil, []ids.NodeID{f.spinner}, f.typ, f.speed, schema.Float32(9))
if err := ctx.Undo(); err != nil {
t.Fatal(err)
}
if v := speedIn(t, pc.Scene(), f.spinner); v != 3 {
t.Fatalf("play undo left %v", v)
}
if f.doc.Dirty() {
t.Fatal("play undo touched the document")
}
}
func TestPlayMouseFollowsPanelOrigin(t *testing.T) {
f := openSpinner(t)
defer f.s.Close()
pc := f.s.App.Play()
if err := pc.Start(f.doc); err != nil {
t.Fatal(err)
}
cursor := mathx.Vec2{X: 500, Y: 300}
f.s.Engine.Input().ApplyMouseMove(cursor, mathx.Vec2{})
for _, origin := range []mathx.Vec2{{X: 100, Y: 50}, {X: 320, Y: 200}} {
pc.SetCapture(true, origin, mathx.Vec2{X: 1000, Y: 1000})
pc.Tick(1.0 / 60)
if got, want := pc.World().Input.MousePos(), cursor.Sub(origin); got != want {
t.Fatalf("origin %v: game mouse %v, want %v", origin, got, want)
}
}
}
func TestClickOutsideGameReleasesBeforeGameSeesIt(t *testing.T) {
f := openSpinner(t)
defer f.s.Close()
pc := f.s.App.Play()
if err := pc.Start(f.doc); err != nil {
t.Fatal(err)
}
pc.SetCapture(true, mathx.Vec2{X: 100, Y: 50}, mathx.Vec2{X: 400, Y: 300})
in := f.s.Engine.Input()
in.BeginFrame()
in.ApplyMouseDown(mathx.Vec2{X: 700, Y: 20}, input.MouseLeft)
pc.Tick(1.0 / 60)
if pc.Captured() {
t.Fatal("a click outside the game view must release the capture")
}
if pc.World().Input.ButtonPressed(input.MouseLeft) {
t.Fatal("the releasing click leaked into the game")
}
}