Files
aego/tests/dataedit/dataedit_test.go
sanspie cffa66514b
ci-platforms / build (amd64, darwin, macos-15-intel) (push) Skipped
ci-platforms / build (amd64, windows, windows-2022) (push) Skipped
ci-platforms / build (arm64, darwin, macos-15) (push) Skipped
ci / checks (push) Successful in 21m2s
ci / build (amd64, linux, ubuntu-24.04) (push) Successful in 22m8s
Stage 9.6c2: prefab apply and data editing without the editor (D298)
2026-10-20 13:05:10 +03:00

216 lines
5.6 KiB
Go

package dataedit_test
import (
"testing"
"aego/app/assembly"
"aego/app/assembly/builtin"
"aego/core/ids"
"aego/data"
"aego/editor/core/dataedit"
"aego/engine/module"
"aego/format/aetext"
"aego/scene"
"aego/scene/mutate"
"aego/schema"
"aego/script/bind"
)
type stats struct {
Speed float32 `aego:"speed,min=0"`
Damage int64 `aego:"damage"`
Title string `aego:"title"`
}
type mod struct{}
func (mod) Info() module.Info {
return module.Info{ID: "dataeditcase"}
}
func (mod) Register(r *module.Registry) error {
return module.DataType[stats](r, schema.Name("test.Stats")).Err()
}
type store struct {
docs map[ids.AssetID][]byte
tables map[ids.AssetID][]byte
}
func (s *store) Data(id ids.AssetID) ([]byte, bool) {
b, ok := s.docs[id]
return b, ok
}
func (s *store) Table(id ids.AssetID) ([]byte, bool) {
b, ok := s.tables[id]
return b, ok
}
func envOf(t *testing.T) (mutate.Env, *schema.Registry) {
t.Helper()
ty := types(t)
return mutate.Env{Types: ty}, ty.Schema()
}
func types(t *testing.T) *scene.Types {
t.Helper()
types, err := assembly.NewTypes()
if err != nil {
t.Fatal(err)
}
if _, err := builtin.Headless().Load(types, bind.New(), assembly.Options{}, mod{}); err != nil {
t.Fatal(err)
}
if err := types.Schema().Freeze(); err != nil {
t.Fatal(err)
}
return types
}
func field(t *testing.T, reg *schema.Registry, name string) *schema.FieldDescriptor {
t.Helper()
td, ok := reg.ByName("test.Stats")
if !ok {
t.Fatal("test.Stats is not registered")
}
fd, ok := td.Field(name)
if !ok {
t.Fatalf("test.Stats has no field %s", name)
}
return fd
}
var (
baseID = ids.AssetID{Hi: 1, Lo: 1}
childID = ids.AssetID{Hi: 1, Lo: 2}
)
func baseStore() *store {
return &store{docs: map[ids.AssetID][]byte{
baseID: data.WriteDoc(&data.Doc{Type: "test.Stats", Fields: []aetext.Field{
{Name: "speed", Value: aetext.Float32(2)},
{Name: "damage", Value: aetext.Int(1)},
}}),
}}
}
func TestDocOverridesBase(t *testing.T) {
env, reg := envOf(t)
lib := data.NewLibrary(reg, baseStore())
d, err := dataedit.NewDoc(env, lib, childID, "assets/fast.aedata", data.WriteDoc(&data.Doc{Type: "test.Stats", Base: baseID}))
if err != nil {
t.Fatal(err)
}
speed := field(t, reg, "speed")
if got := d.Value(speed).AsFloat(); got != 2 {
t.Fatalf("inherited speed = %v, want 2", got)
}
if d.Overridden("speed") || d.Dirty() {
t.Fatal("a fresh child must not override or be dirty")
}
if err := d.Set(speed, schema.Float32(5), true); err != nil {
t.Fatal(err)
}
if !d.Overridden("speed") || !d.Dirty() {
t.Fatal("setting a field must override it")
}
if err := d.Set(speed, schema.Float32(2), true); err != nil {
t.Fatal(err)
}
if d.Overridden("speed") {
t.Fatal("a value equal to the base is not an override")
}
if err := d.Undo(); err != nil {
t.Fatal(err)
}
if got := d.Value(speed).AsFloat(); got != 5 {
t.Fatalf("undo: speed = %v, want 5", got)
}
if err := d.Revert("speed"); err != nil {
t.Fatal(err)
}
if got := d.Value(speed).AsFloat(); got != 2 || d.Overridden("speed") {
t.Fatalf("revert: speed = %v, overridden = %v", got, d.Overridden("speed"))
}
if err := d.Undo(); err != nil {
t.Fatal(err)
}
out, err := data.ParseDoc(d.Bytes(), "fast")
if err != nil {
t.Fatal(err)
}
if out.Base != baseID || len(out.Fields) != 1 || out.Fields[0].Name != "speed" || out.Fields[0].Value.AsFloat() != 5 {
t.Fatalf("saved document = %+v", out)
}
}
func TestDocBaseCycle(t *testing.T) {
env, reg := envOf(t)
src := baseStore()
src.docs[childID] = data.WriteDoc(&data.Doc{Type: "test.Stats", Base: baseID})
lib := data.NewLibrary(reg, src)
d, err := dataedit.NewDoc(env, lib, baseID, "assets/base.aedata", data.WriteDoc(&data.Doc{Type: "test.Stats"}))
if err != nil {
t.Fatal(err)
}
if err := d.SetBase(baseID); err == nil {
t.Fatal("an asset must not become its own base")
}
if err := d.SetBase(childID); err == nil {
t.Fatal("a base chain that comes back must be rejected")
}
if !d.Base().IsZero() || d.Dirty() {
t.Fatal("a rejected base must leave the document unchanged")
}
}
func TestTableMassEditAndCSV(t *testing.T) {
env, reg := envOf(t)
row := func(id string, speed float32) data.TableRow {
return data.TableRow{ID: id, Fields: []aetext.Field{{Name: "speed", Value: aetext.Float32(speed)}}}
}
doc := &data.TableDoc{Type: "test.Stats", Rows: []data.TableRow{row("goblin", 2), row("bat", 4), row("troll", 1)}}
tab, err := dataedit.NewTable(env, ids.AssetID{Hi: 2, Lo: 1}, "assets/enemies.aetable", data.WriteTable(doc))
if err != nil {
t.Fatal(err)
}
speed := field(t, reg, "speed")
if err := tab.Set([]int{0, 2}, speed, schema.Float32(7), true); err != nil {
t.Fatal(err)
}
for i, want := range []float64{7, 4, 7} {
if got := tab.Value(i, speed).AsFloat(); got != want {
t.Fatalf("row %d speed = %v, want %v", i, got, want)
}
}
if err := tab.Rename(1, "goblin"); err == nil {
t.Fatal("a duplicate row id must be rejected")
}
csv, err := tab.ExportCSV()
if err != nil {
t.Fatal(err)
}
if err := tab.Set([]int{1}, speed, schema.Float32(9), true); err != nil {
t.Fatal(err)
}
if err := tab.ImportCSV(csv); err != nil {
t.Fatal(err)
}
if got := tab.Value(1, speed).AsFloat(); got != 4 {
t.Fatalf("after CSV import bat speed = %v, want 4", got)
}
tab.Undo()
if got := tab.Value(1, speed).AsFloat(); got != 9 {
t.Fatalf("undo import: bat speed = %v, want 9", got)
}
tab.Undo()
if got := tab.Value(1, speed).AsFloat(); got != 4 {
t.Fatalf("undo set: bat speed = %v, want 4", got)
}
back, err := data.ParseTable(tab.Bytes(), "enemies")
if err != nil || len(back.Rows) != 3 {
t.Fatalf("round trip: %v, %d rows", err, len(back.Rows))
}
}