Files

381 lines
9.9 KiB
Go

package host
import (
"bytes"
"context"
"image"
"testing"
"aego/art"
)
func document(t *testing.T, h *Documents, name string) string {
t.Helper()
d, err := art.New(8, 8, art.RGBA, art.Limits{})
if err != nil {
t.Fatal(err)
}
key, err := h.Open(d, name, false)
if err != nil {
t.Fatal(err)
}
return key
}
func session(t *testing.T, h *Documents, key string) *art.Session {
t.Helper()
s, err := h.Session(key)
if err != nil {
t.Fatal(err)
}
return s
}
func draw(t *testing.T, s *art.Session, x int) {
t.Helper()
_, err := s.Execute(context.Background(), art.Operation{Kind: "stroke", Points: []image.Point{{X: x, Y: 1}}, Brush: art.Brush{Size: 1, Paint: art.Paint{Color: art.Color{R: 220, A: 255}}}})
if err != nil {
t.Fatal(err)
}
}
func export(t *testing.T, h *Documents, key string) Export {
t.Helper()
out, err := h.Export(key, "aeart", 1)
if err != nil {
t.Fatal(err)
}
return out
}
func TestTabsRetainIndependentHistoryAndPixels(t *testing.T) {
h := New()
a := document(t, h, "first.aeart")
sa := session(t, h, a)
draw(t, sa, 1)
before, err := sa.Document.PNG(sa.Frame, art.Albedo)
if err != nil {
t.Fatal(err)
}
b := document(t, h, "second.aeart")
sb := session(t, h, b)
draw(t, sb, 4)
if err = h.Select(a); err != nil {
t.Fatal(err)
}
after, _ := sa.Document.PNG(sa.Frame, art.Albedo)
if !bytes.Equal(before, after) || sa.Info().Undo != 1 || sb.Info().Undo != 1 {
t.Fatal("switch lost pixel/history state")
}
if err = sa.Undo(); err != nil {
t.Fatal(err)
}
if sb.Info().Undo != 1 || !sb.Info().Dirty {
t.Fatal("undo crossed document boundary")
}
}
func TestSaveReceiptTargetsOriginalDocumentAndRevision(t *testing.T) {
h := New()
a := document(t, h, "A.aeart")
sa := session(t, h, a)
draw(t, sa, 1)
out := export(t, h, a)
if err := h.BeginWrite(out.Receipt); err != nil {
t.Fatal(err)
}
draw(t, sa, 2)
b := document(t, h, "B.aeart")
sb := session(t, h, b)
draw(t, sb, 4)
if err := h.Saved(out.Receipt); err != nil {
t.Fatal(err)
}
if !sa.Info().Dirty || !sb.Info().Dirty {
t.Fatal("late save cleared newer pixels or another tab")
}
if err := sa.Undo(); err != nil {
t.Fatal(err)
}
if sa.Info().Dirty {
t.Fatal("undo to actually saved snapshot must be clean")
}
if err := h.Saved(out.Receipt); err == nil {
t.Fatal("receipt reused")
}
}
func TestCloseRefusesDirtyAndRevokesPendingSaves(t *testing.T) {
h := New()
a := document(t, h, "A.aeart")
s := session(t, h, a)
draw(t, s, 1)
out := export(t, h, a)
if err := h.Close(a, false); err == nil {
t.Fatal("discarded dirty document")
}
if len(h.Tabs()) != 1 {
t.Fatal("refused close changed tabs")
}
if err := h.Close("", true); err != nil {
t.Fatal(err)
}
if h.Active() != "" || len(h.Tabs()) != 0 {
t.Fatal("closed default tab remains active")
}
if err := h.Saved(out.Receipt); err == nil {
t.Fatal("late receipt published after close")
}
}
func TestCancelledSaveDoesNotClearDirty(t *testing.T) {
h := New()
key := document(t, h, "A")
s := session(t, h, key)
draw(t, s, 1)
out := export(t, h, key)
h.CancelSave(out.Receipt)
if err := h.Saved(out.Receipt); err == nil {
t.Fatal("cancelled save accepted")
}
if !s.Info().Dirty {
t.Fatal("cancelled save lost dirty marker")
}
}
func TestReceiptAndDocumentLimits(t *testing.T) {
h := New()
key := document(t, h, "A")
for n := 0; n < MaxPendingSaves; n++ {
export(t, h, key)
}
if _, err := h.Export(key, "aeart", 1); err == nil {
t.Fatal("unbounded retained snapshots")
}
if out, err := h.Export(key, "recovery", 1); err != nil || out.Receipt != "" {
t.Fatal("recovery consumes save receipts", err)
}
for n := 1; n < MaxDocuments; n++ {
document(t, h, "tab")
}
extra, _ := art.New(8, 8, art.RGBA, art.Limits{})
defer extra.Close()
if _, err := h.Open(extra, "extra", false); err == nil {
t.Fatal("unbounded open docs")
}
}
func TestCopyPasteAcrossOpenDocuments(t *testing.T) {
h := New()
a := document(t, h, "A")
sa := session(t, h, a)
draw(t, sa, 1)
if err := h.Copy(a); err != nil {
t.Fatal(err)
}
b := document(t, h, "B")
sb := session(t, h, b)
if err := h.Paste(b, image.Point{}); err != nil {
t.Fatal(err)
}
ca, _ := sa.Document.Pixel(sa.ActiveCel(), 1, 1)
cb, _ := sb.Document.Pixel(sb.ActiveCel(), 1, 1)
if ca != cb || cb.A != 255 {
t.Fatal("clipboard did not cross documents")
}
if err := sb.Undo(); err != nil {
t.Fatal(err)
}
ca, _ = sa.Document.Pixel(sa.ActiveCel(), 1, 1)
if ca.A != 255 {
t.Fatal("paste undo damaged source")
}
}
func TestCannotSwitchMidGestureOrShareMutableSession(t *testing.T) {
h := New()
a := document(t, h, "A")
b := document(t, h, "B")
s := session(t, h, b)
if err := s.BeginGesture("pencil", image.Point{}, art.Brush{Size: 1}, false); err != nil {
t.Fatal(err)
}
if err := h.Select(a); err == nil {
t.Fatal("switched mid gesture")
}
s.CancelGesture()
if err := h.Select(a); err != nil {
t.Fatal(err)
}
if _, err := h.Open(s.Document, "same pointer", false); err == nil {
t.Fatal("one document acquired two histories")
}
}
func TestNewAndImportedTabsCannotOrphanAnActiveGesture(t *testing.T) {
for _, imported := range []bool{false, true} {
name := "new"
if imported {
name = "imported"
}
t.Run(name, func(t *testing.T) {
h := New()
key := document(t, h, "drawing.aeart")
s := session(t, h, key)
next, err := art.New(8, 8, art.RGBA, art.Limits{})
if err != nil {
t.Fatal(err)
}
if imported {
next.Close()
out, err := h.Export(key, "recovery", 1)
if err != nil {
t.Fatal(err)
}
next, err = art.Decode(out.Bytes, art.Limits{})
if err != nil {
t.Fatal(err)
}
}
defer next.Close()
revision := s.Document.Revision()
brush := art.Brush{Size: 1, Paint: art.Paint{Color: art.Color{R: 220, A: 255}}}
if err := s.BeginGesture("pencil", image.Pt(1, 1), brush, false); err != nil {
t.Fatal(err)
}
if _, err := h.Open(next, name+".aeart", !imported); err == nil {
t.Fatal("opened a tab underneath the active stroke")
}
if h.Active() != key || len(h.Tabs()) != 1 || !s.Info().Gesture || s.Info().Undo != 0 || s.Document.Revision() != revision {
t.Fatal("rejected open changed the active session, gesture or history")
}
if err := s.MoveGesture([]image.Point{image.Pt(3, 1)}); err != nil {
t.Fatal(err)
}
if err := s.EndGesture(); err != nil {
t.Fatal(err)
}
for x := 1; x <= 3; x++ {
pixel, err := s.Document.Pixel(s.ActiveCel(), x, 1)
if err != nil || pixel != brush.Paint.Color {
t.Fatalf("remaining stroke was redirected at %d: %v, %v", x, pixel, err)
}
}
if s.Info().Undo != 1 {
t.Fatal("one stroke did not remain one undo operation")
}
opened, err := h.Open(next, name+".aeart", !imported)
if err != nil || opened == key || h.Active() != opened {
t.Fatal("opening after a completed gesture failed", err)
}
if imported && session(t, h, opened).Document.Info().ID != s.Document.Info().ID {
t.Fatal("imported document identity was unexpectedly rewritten")
}
if err := s.Undo(); err != nil {
t.Fatal(err)
}
pixel, err := s.Document.Pixel(s.ActiveCel(), 1, 1)
if err != nil || pixel.A != 0 || session(t, h, opened).Info().Undo != 0 {
t.Fatal("history crossed the opened document boundary", err)
}
})
}
}
func TestDeferredSaveRefusesStalePickerAndClosedDocument(t *testing.T) {
h := New()
key := document(t, h, "A")
s := session(t, h, key)
draw(t, s, 1)
out := export(t, h, key)
draw(t, s, 2)
if err := h.BeginWrite(out.Receipt); err != art.ErrConflict {
t.Fatalf("stale picker: %v", err)
}
h.CancelSave(out.Receipt)
out = export(t, h, key)
if err := h.Close(key, true); err != nil {
t.Fatal(err)
}
if err := h.BeginWrite(out.Receipt); err == nil {
t.Fatal("write started after close")
}
}
func TestWritePinsOnlyItsDocumentAndPreservesConcurrentEdits(t *testing.T) {
h := New()
a := document(t, h, "A")
sa := session(t, h, a)
draw(t, sa, 1)
out := export(t, h, a)
if err := h.BeginWrite(out.Receipt); err != nil {
t.Fatal(err)
}
if err := h.BeginWrite(out.Receipt); err == nil {
t.Fatal("write started twice")
}
if err := h.Close(a, true); err == nil {
t.Fatal("closed pending writer")
}
b := document(t, h, "B")
if err := h.Close(b, true); err != nil {
t.Fatal("unrelated tab is pinned", err)
}
draw(t, sa, 2)
if err := h.Saved(out.Receipt); err != nil {
t.Fatal(err)
}
if !sa.Info().Dirty {
t.Fatal("concurrent edit marked saved")
}
if err := h.Close(a, true); err != nil {
t.Fatal(err)
}
}
func TestPaletteReadTargetsOriginalTabAndRejectsStaleWork(t *testing.T) {
h := New()
a := document(t, h, "A")
sa := session(t, h, a)
revision := sa.Document.Revision()
b := document(t, h, "B")
sb := session(t, h, b)
palette := []byte("GIMP Palette\n10 20 30 Name\n")
if err := h.ImportPalette(a, revision, palette, "gpl", false); err != nil {
t.Fatal(err)
}
if !sa.Info().Dirty || sb.Info().Dirty || h.Active() != b {
t.Fatal("palette import changed the active tab instead of its owner")
}
if err := h.ImportPalette(a, revision, palette, "gpl", false); err != art.ErrConflict {
t.Fatal("stale palette read was accepted", err)
}
out, err := h.Export(a, "palette", 1)
if err != nil {
t.Fatal(err)
}
p, err := art.DecodePalette(out.Bytes, "json")
if err != nil || len(p) != 2 || p[1].Name != "Name" {
t.Fatal(p, err)
}
if err = h.Close(a, true); err != nil {
t.Fatal(err)
}
if err = h.ImportPalette(a, revision, palette, "gpl", false); err == nil {
t.Fatal("closed palette read published")
}
}
func TestSaveAcknowledgementRequiresStartedWrite(t *testing.T) {
h := New()
a := document(t, h, "A")
s := session(t, h, a)
draw(t, s, 1)
out := export(t, h, a)
if err := h.Saved(out.Receipt); err == nil {
t.Fatal("export alone marked document saved")
}
if !s.Info().Dirty {
t.Fatal("download lost dirty state")
}
if err := h.BeginWrite(out.Receipt); err != nil {
t.Fatal(err)
}
if err := h.Saved(out.Receipt); err != nil {
t.Fatal(err)
}
if s.Info().Dirty {
t.Fatal("successful write was not recorded")
}
}