519 lines
20 KiB
Go
519 lines
20 KiB
Go
package editor
|
|
|
|
import (
|
|
"bytes"
|
|
stdctx "context"
|
|
"errors"
|
|
"image"
|
|
"os"
|
|
"path/filepath"
|
|
"testing"
|
|
|
|
"aego/art"
|
|
"aego/asset/importers/artsource"
|
|
"aego/asset/pipeline"
|
|
"aego/core/ids"
|
|
"aego/editor/core/artedit"
|
|
"aego/editor/core/assetops"
|
|
"aego/editor/core/context"
|
|
"aego/editor/core/document"
|
|
)
|
|
|
|
// Deferred tests; no real project, asset or recovery file is used here.
|
|
func replaceableBlank(t *testing.T) (*context.Editor, *document.AssetDocument, *artedit.Document, ids.AssetID, []byte, []byte) {
|
|
t.Helper()
|
|
_, pl, ed, ws, registry := workspaceBlankFixture(t)
|
|
old, err := art.New(32, 32, art.RGBA, art.DefaultLimits())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
data, err := old.Encode()
|
|
old.Close()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
id, err := assetops.CreateFile(ed, "assets/existing.aeart", data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
meta, err := pl.SourceBytes("assets/existing.aeart.meta")
|
|
if err != nil || meta == nil {
|
|
t.Fatalf("fixture metadata: %v", err)
|
|
}
|
|
doc, model := enterBlankArt(t, ed, ws, registry)
|
|
if _, err := model.Execute(stdctx.Background(), art.Operation{Kind: "stroke", Points: []image.Point{{X: 4, Y: 5}}, Brush: art.Brush{Size: 1, Paint: art.Paint{Color: art.Color{R: 255, A: 255}}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return ed, doc, model, id, data, meta
|
|
}
|
|
|
|
func replacementQuestion(t *testing.T, ed *context.Editor, doc *document.AssetDocument) context.Question {
|
|
t.Helper()
|
|
if err := ed.SaveAsset(doc); !errors.Is(err, document.ErrSavePending) {
|
|
t.Fatalf("save did not open a path prompt: %v", err)
|
|
}
|
|
q := ed.TakeQuestions()
|
|
if len(q) != 1 || q[0].Submit == nil || q[0].DestructiveWhen == nil || q[0].DestructiveLabel == "" {
|
|
t.Fatal("missing dynamic replacement action")
|
|
}
|
|
if q[0].DestructiveWhen([]string{"assets/free.aeart"}) || !q[0].DestructiveWhen([]string{"assets/existing.aeart"}) {
|
|
t.Fatal("primary action does not follow the destination path")
|
|
}
|
|
return q[0]
|
|
}
|
|
|
|
func TestBlankReplaceRequiresConfirmationAndKeepsIdentity(t *testing.T) {
|
|
ed, doc, model, id, before, metaBefore := replaceableBlank(t)
|
|
inspected, err := ed.DocumentForAsset(id)
|
|
if err != nil || inspected.Binding.Model != nil || inspected.Dirty() {
|
|
t.Fatalf("could not prepare clean Inspector metadata: %v", err)
|
|
}
|
|
replacementQuestion(t, ed, doc).Submit([]string{"./assets/existing.aeart"})
|
|
confirm := ed.TakeQuestions()
|
|
if len(confirm) != 1 || confirm[0].Then == nil {
|
|
t.Fatal("existing drawing did not request explicit replacement confirmation")
|
|
}
|
|
if got, err := ed.Pipeline.SourceBytes("assets/existing.aeart"); err != nil || !bytes.Equal(got, before) {
|
|
t.Fatal("target changed before confirmation", err)
|
|
}
|
|
confirm[0].Then(1)
|
|
if got, _ := ed.Pipeline.SourceBytes("assets/existing.aeart"); !bytes.Equal(got, before) || !doc.Dirty() {
|
|
t.Fatal("cancelling replacement changed the source or unsaved drawing")
|
|
}
|
|
replacementQuestion(t, ed, doc).Submit([]string{"assets/existing.aeart"})
|
|
confirm = ed.TakeQuestions()
|
|
if len(confirm) != 1 || confirm[0].Then == nil {
|
|
t.Fatal("missing second confirmation")
|
|
}
|
|
confirm[0].Then(0)
|
|
if notice, ok := ed.LastNotice(); ok && notice.Level == context.LevelError {
|
|
t.Fatal(notice.Text)
|
|
}
|
|
if got, ok := ed.Pipeline.Lookup("assets/existing.aeart"); !ok || got != id {
|
|
t.Fatal("replacement changed the existing AssetID")
|
|
}
|
|
if got, err := ed.Pipeline.SourceBytes("assets/existing.aeart.meta"); err != nil || !bytes.Equal(got, metaBefore) {
|
|
t.Fatal("replacement changed metadata", err)
|
|
}
|
|
saved := ed.Docs.AssetDocument(id)
|
|
if saved == nil || saved != inspected || saved.Binding.Model != model || model.Draft || saved.Dirty() || model.History.Cursor() != 1 {
|
|
t.Fatal("replacement lost the canonical session, history or saved state")
|
|
}
|
|
if err := model.SessionUndo(); err != nil || !saved.Dirty() {
|
|
t.Fatal("the drawing's Undo did not survive replacement", err)
|
|
}
|
|
}
|
|
|
|
func TestBlankReplaceRejectsChangeDuringConfirmation(t *testing.T) {
|
|
ed, doc, model, _, _, metaBefore := replaceableBlank(t)
|
|
replacementQuestion(t, ed, doc).Submit([]string{"assets/existing.aeart"})
|
|
confirm := ed.TakeQuestions()
|
|
if len(confirm) != 1 || confirm[0].Then == nil {
|
|
t.Fatal("missing confirmation")
|
|
}
|
|
foreign := []byte("external change while confirmation was open")
|
|
if err := os.WriteFile(filepath.Join(ed.Project.Root, "assets", "existing.aeart"), foreign, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
confirm[0].Then(0)
|
|
if got, err := ed.Pipeline.SourceBytes("assets/existing.aeart"); err != nil || !bytes.Equal(got, foreign) {
|
|
t.Fatal("replacement overwrote external changes", err)
|
|
}
|
|
if got, _ := ed.Pipeline.SourceBytes("assets/existing.aeart.meta"); !bytes.Equal(got, metaBefore) || !doc.Dirty() || doc.Binding.Model != model {
|
|
t.Fatal("rejected replacement changed metadata or lost unsaved pixels")
|
|
}
|
|
if notice, ok := ed.LastNotice(); !ok || notice.Level != context.LevelError {
|
|
t.Fatal("the changed destination was not reported")
|
|
}
|
|
}
|
|
|
|
func TestBlankReplaceDoesNotDiscardTargetRecovery(t *testing.T) {
|
|
ed, doc, model, id, before, metaBefore := replaceableBlank(t)
|
|
dir := filepath.Join(ed.Project.Root, document.RecoveryDir)
|
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
recoveryPath := filepath.Join(dir, "asset-"+id.String()+".recovery")
|
|
recovery := []byte("earlier unsaved drawing")
|
|
if err := os.WriteFile(recoveryPath, recovery, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
replacementQuestion(t, ed, doc).Submit([]string{"assets/existing.aeart"})
|
|
if q := ed.TakeQuestions(); len(q) != 0 {
|
|
t.Fatal("replacement offered despite pending destination recovery")
|
|
}
|
|
if got, err := os.ReadFile(recoveryPath); err != nil || !bytes.Equal(got, recovery) {
|
|
t.Fatal("destination recovery was changed", err)
|
|
}
|
|
if got, _ := ed.Pipeline.SourceBytes("assets/existing.aeart"); !bytes.Equal(got, before) || !doc.Dirty() || doc.Binding.Model != model {
|
|
t.Fatal("rejected replacement changed the source or draft")
|
|
}
|
|
if got, _ := ed.Pipeline.SourceBytes("assets/existing.aeart.meta"); !bytes.Equal(got, metaBefore) {
|
|
t.Fatal("rejected replacement changed metadata")
|
|
}
|
|
}
|
|
|
|
func TestReplacementRejectsLostFrameOrSurfaceReferences(t *testing.T) {
|
|
old := art.Info{Frames: []art.Frame{{ID: "a"}, {ID: "b"}}, Tags: []art.Tag{{ID: "tag"}}, Cels: []art.Cel{{Channel: art.Normal}}}
|
|
if err := artReplacementKeepsReferences(old, old); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, next := range []art.Info{
|
|
{Frames: old.Frames[:1], Tags: old.Tags, Cels: old.Cels},
|
|
{Frames: old.Frames, Cels: old.Cels},
|
|
{Frames: old.Frames, Tags: old.Tags},
|
|
} {
|
|
if err := artReplacementKeepsReferences(old, next); err == nil {
|
|
t.Fatal("replacement silently dropped a frame, tag or surface identity")
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestPNGFirstSaveCanReplaceCompatibleDrawing(t *testing.T) {
|
|
ed, source, frames, originalPNG := linkedArtEditor(t)
|
|
doc, err := ed.OpenAsset(frames[1])
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
model := doc.Binding.Model.(*artedit.Document)
|
|
data, err := model.Session.Document.Encode()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
id, err := assetops.CreateFile(ed, "assets/existing.aeart", data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
oldMeta, err := ed.Pipeline.SourceBytes("assets/existing.aeart.meta")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := model.Execute(stdctx.Background(), art.Operation{Kind: "stroke", Points: []image.Point{{X: 1, Y: 1}}, Brush: art.Brush{Size: 1, Paint: art.Paint{Color: art.Color{G: 255, A: 255}}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := ed.SaveAsset(doc); !errors.Is(err, document.ErrSavePending) {
|
|
t.Fatal(err)
|
|
}
|
|
q := ed.TakeQuestions()
|
|
if len(q) != 1 || q[0].DestructiveWhen == nil || !q[0].DestructiveWhen([]string{"assets/existing.aeart"}) {
|
|
t.Fatal("PNG save did not identify the existing source")
|
|
}
|
|
q[0].Submit([]string{"assets/existing.aeart"})
|
|
confirm := ed.TakeQuestions()
|
|
if len(confirm) != 1 || confirm[0].Then == nil {
|
|
t.Fatal("PNG replacement did not request confirmation")
|
|
}
|
|
confirm[0].Then(0)
|
|
if notice, ok := ed.LastNotice(); ok && notice.Level == context.LevelError {
|
|
t.Fatal(notice.Text)
|
|
}
|
|
meta, ok := ed.Pipeline.MetaOf(source)
|
|
if !ok {
|
|
t.Fatal("PNG metadata disappeared")
|
|
}
|
|
link, err := artsource.TextureLinkOf(&meta)
|
|
if err != nil || link == nil || link.Source != id || len(link.Frames) != 2 || link.Frames[1].Asset != frames[1] {
|
|
t.Fatalf("replacement lost PNG frame references: %v", err)
|
|
}
|
|
if got, err := ed.Pipeline.SourceBytes("assets/linked.png"); err != nil || !bytes.Equal(got, originalPNG) {
|
|
t.Fatal("PNG replacement changed the original PNG", err)
|
|
}
|
|
if got, err := ed.Pipeline.SourceBytes("assets/existing.aeart.meta"); err != nil || !bytes.Equal(got, oldMeta) {
|
|
t.Fatal("PNG replacement changed the existing source metadata", err)
|
|
}
|
|
if saved := ed.Docs.AssetDocument(id); saved == nil || saved.Binding.Model != model || model.Draft || saved.Dirty() {
|
|
t.Fatal("PNG replacement did not retain the saved drawing session")
|
|
}
|
|
}
|
|
|
|
func TestArtSavePathAndReplaceHintUseTheSameRelativePath(t *testing.T) {
|
|
ed, _, _, _, _, _ := replaceableBlank(t)
|
|
for _, input := range []string{"assets/existing.aeart", "./assets/existing.aeart", " ././assets/existing.aeart "} {
|
|
name, err := artSavePath(input)
|
|
if err != nil || name != "assets/existing.aeart" || !artSaveReplaces(ed, []string{input}) {
|
|
t.Fatalf("relative destination %q: name=%q replace=%v error=%v", input, name, artSaveReplaces(ed, []string{input}), err)
|
|
}
|
|
}
|
|
for _, input := range []string{"../assets/existing.aeart", "assets/../existing.aeart", "/assets/existing.aeart", "assets\\existing.aeart", "assets/a:b.aeart"} {
|
|
if _, err := artSavePath(input); err == nil || artSaveReplaces(ed, []string{input}) {
|
|
t.Fatalf("unsafe destination accepted: %q", input)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestArtSaveAsExistingTargetRequiresConfirmationAndRetainsOwner(t *testing.T) {
|
|
p := artActionFixture(t)
|
|
ed, doc, model := p.ctx, p.doc, p.model
|
|
data, err := model.Session.Document.Encode()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
id, err := assetops.CreateFile(ed, "assets/save-as-target.aeart", data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
meta, err := ed.Pipeline.SourceBytes("assets/save-as-target.aeart.meta")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := model.Execute(stdctx.Background(), art.Operation{Kind: "stroke", Points: []image.Point{{X: 2, Y: 3}}, Brush: art.Brush{Size: 1, Paint: art.Paint{Color: art.Color{B: 255, A: 255}}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
cursor := model.History.Cursor()
|
|
p.askSaveAs()
|
|
questions := ed.TakeQuestions()
|
|
if len(questions) != 1 || questions[0].Submit == nil || questions[0].DestructiveWhen == nil || questions[0].DestructiveLabel == "" {
|
|
t.Fatal("Save As did not create a dynamic replacement prompt")
|
|
}
|
|
if questions[0].DestructiveWhen([]string{"assets/new-copy.aeart"}) || !questions[0].DestructiveWhen([]string{"./assets/save-as-target.aeart"}) {
|
|
t.Fatal("Save As did not identify the existing relative destination")
|
|
}
|
|
questions[0].Submit([]string{"./assets/save-as-target.aeart"})
|
|
confirm := ed.TakeQuestions()
|
|
if len(confirm) != 1 || confirm[0].Then == nil {
|
|
t.Fatal("Save As did not request explicit replacement confirmation")
|
|
}
|
|
if got, _ := ed.Pipeline.SourceBytes("assets/save-as-target.aeart"); !bytes.Equal(got, data) {
|
|
t.Fatal("target changed before confirmation")
|
|
}
|
|
confirm[0].Then(0)
|
|
if p.err != nil {
|
|
t.Fatal(p.err)
|
|
}
|
|
if got, _ := ed.Pipeline.SourceBytes("assets/save-as-target.aeart.meta"); !bytes.Equal(got, meta) {
|
|
t.Fatal("Save As changed the destination identity or metadata")
|
|
}
|
|
if got, ok := ed.Pipeline.Lookup("assets/save-as-target.aeart"); !ok || got != id {
|
|
t.Fatal("Save As changed the destination AssetID")
|
|
}
|
|
if doc.Binding.Model != model || !doc.Dirty() || model.History.Cursor() != cursor {
|
|
t.Fatal("Save As discarded or marked the original drawing clean")
|
|
}
|
|
copyDoc := ed.Docs.AssetDocument(id)
|
|
if copyDoc == nil || copyDoc == doc || copyDoc.Binding.Model == model {
|
|
t.Fatal("confirmed destination did not open as an independent document")
|
|
}
|
|
}
|
|
|
|
func TestArtSaveAsRejectsDestinationAppearingOrChanging(t *testing.T) {
|
|
for _, mutate := range []string{"appears", "source", "metadata"} {
|
|
t.Run(mutate, func(t *testing.T) {
|
|
p := artActionFixture(t)
|
|
ed, doc, model := p.ctx, p.doc, p.model
|
|
name := "assets/save-as-race.aeart"
|
|
if mutate != "appears" {
|
|
data, err := model.Session.Document.Encode()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if _, err := assetops.CreateFile(ed, name, data); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
p.askSaveAs()
|
|
q := ed.TakeQuestions()
|
|
if len(q) != 1 || q[0].DestructiveWhen == nil {
|
|
t.Fatal("missing Save As prompt")
|
|
}
|
|
foreign := []byte("external bytes; preserve exactly")
|
|
changed := name
|
|
if mutate == "metadata" {
|
|
changed += ".meta"
|
|
}
|
|
if mutate == "appears" {
|
|
if q[0].DestructiveWhen([]string{name}) {
|
|
t.Fatal("absent destination appeared as a replacement")
|
|
}
|
|
if err := os.WriteFile(filepath.Join(ed.Project.Root, filepath.FromSlash(changed)), foreign, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !q[0].DestructiveWhen([]string{"./" + name}) {
|
|
t.Fatal("unindexed destination appearing during the prompt was not marked Replace")
|
|
}
|
|
q[0].Submit([]string{name})
|
|
} else {
|
|
q[0].Submit([]string{name})
|
|
confirm := ed.TakeQuestions()
|
|
if len(confirm) != 1 || confirm[0].Then == nil {
|
|
t.Fatal("missing replacement confirmation")
|
|
}
|
|
if err := os.WriteFile(filepath.Join(ed.Project.Root, filepath.FromSlash(changed)), foreign, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
confirm[0].Then(0)
|
|
}
|
|
if got, err := os.ReadFile(filepath.Join(ed.Project.Root, filepath.FromSlash(changed))); err != nil || !bytes.Equal(got, foreign) {
|
|
t.Fatal("Save As overwrote a concurrent destination change", err)
|
|
}
|
|
if p.err == nil || doc.Binding.Model != model || !doc.Dirty() {
|
|
t.Fatal("rejected Save As lost the error or dirty drawing")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArtSaveAsProtectsOpenDestinationAndRecovery(t *testing.T) {
|
|
for _, state := range []string{"open-clean", "open-dirty", "recovery", "metadata-dirty"} {
|
|
t.Run(state, func(t *testing.T) {
|
|
p := artActionFixture(t)
|
|
ed, doc, model := p.ctx, p.doc, p.model
|
|
data, err := model.Session.Document.Encode()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
id, err := assetops.CreateFile(ed, "assets/protected.aeart", data)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
meta, err := ed.Pipeline.SourceBytes("assets/protected.aeart.meta")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
switch state {
|
|
case "open-clean", "open-dirty":
|
|
other, err := ed.OpenAsset(id)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if state == "open-dirty" {
|
|
bound := other.Binding.Model.(*artedit.Document)
|
|
if _, err := bound.Execute(stdctx.Background(), art.Operation{Kind: "stroke", Points: []image.Point{{X: 3, Y: 3}}, Brush: art.Brush{Size: 1, Paint: art.Paint{Color: art.Color{R: 255, A: 255}}}}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
case "metadata-dirty":
|
|
other, err := ed.DocumentForAsset(id)
|
|
if err != nil || other.Metadata == nil {
|
|
t.Fatal("cannot open target metadata", err)
|
|
}
|
|
other.Metadata.Code.ReplaceSnapshot([]byte("unsaved metadata"), false)
|
|
case "recovery":
|
|
dir := filepath.Join(ed.Project.Root, document.RecoveryDir)
|
|
if err := os.MkdirAll(dir, 0700); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(filepath.Join(dir, "asset-"+id.String()+".recovery"), []byte("pending recovery"), 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
p.askSaveAs()
|
|
q := ed.TakeQuestions()
|
|
if len(q) != 1 || q[0].DestructiveWhen == nil || !q[0].DestructiveWhen([]string{"assets/protected.aeart"}) {
|
|
t.Fatal("existing target was not marked Replace")
|
|
}
|
|
q[0].Submit([]string{"assets/protected.aeart"})
|
|
if len(ed.TakeQuestions()) != 0 || p.err == nil {
|
|
t.Fatal("protected target was offered for replacement")
|
|
}
|
|
if got, _ := ed.Pipeline.SourceBytes("assets/protected.aeart"); !bytes.Equal(got, data) {
|
|
t.Fatal("protected destination source changed")
|
|
}
|
|
if got, _ := ed.Pipeline.SourceBytes("assets/protected.aeart.meta"); !bytes.Equal(got, meta) {
|
|
t.Fatal("protected destination metadata changed")
|
|
}
|
|
if doc.Binding.Model != model || !doc.Dirty() {
|
|
t.Fatal("the original dirty drawing was lost")
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestArtSaveAsCurrentSourceRejectsChangedMetadata(t *testing.T) {
|
|
p := artActionFixture(t)
|
|
ed, doc := p.ctx, p.doc
|
|
before, err := ed.Pipeline.SourceBytes(doc.Path)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
p.askSaveAs()
|
|
q := ed.TakeQuestions()
|
|
if len(q) != 1 || q[0].DestructiveWhen == nil || !q[0].DestructiveWhen([]string{doc.Path}) {
|
|
t.Fatal("current source was not marked Replace")
|
|
}
|
|
q[0].Submit([]string{doc.Path})
|
|
confirm := ed.TakeQuestions()
|
|
if len(confirm) != 1 || confirm[0].Then == nil {
|
|
t.Fatal("current source did not require explicit confirmation")
|
|
}
|
|
foreign := []byte("changed metadata from another process")
|
|
if err := os.WriteFile(filepath.Join(ed.Project.Root, filepath.FromSlash(doc.Path+".meta")), foreign, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
confirm[0].Then(0)
|
|
if p.err == nil || !doc.Dirty() {
|
|
t.Fatal("changed metadata did not block saving the current drawing")
|
|
}
|
|
if got, _ := ed.Pipeline.SourceBytes(doc.Path); !bytes.Equal(got, before) {
|
|
t.Fatal("current source changed after metadata conflict")
|
|
}
|
|
if got, _ := ed.Pipeline.SourceBytes(doc.Path + ".meta"); !bytes.Equal(got, foreign) {
|
|
t.Fatal("external metadata was overwritten")
|
|
}
|
|
}
|
|
|
|
func TestArtSaveAsCurrentSourceRejectsEarlierExternalEdit(t *testing.T) {
|
|
p := artActionFixture(t)
|
|
ed, doc, model := p.ctx, p.doc, p.model
|
|
p.askSaveAs()
|
|
q := ed.TakeQuestions()
|
|
if len(q) != 1 || q[0].Submit == nil {
|
|
t.Fatal("missing Save As prompt")
|
|
}
|
|
foreign := []byte("external source edit made before choosing the path")
|
|
if err := os.WriteFile(filepath.Join(ed.Project.Root, filepath.FromSlash(doc.Path)), foreign, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
q[0].Submit([]string{doc.Path})
|
|
confirm := ed.TakeQuestions()
|
|
if len(confirm) != 1 || confirm[0].Then == nil {
|
|
t.Fatal("missing current-source replacement confirmation")
|
|
}
|
|
confirm[0].Then(0)
|
|
if p.err == nil || doc.Binding.Model != model || !doc.Dirty() {
|
|
t.Fatal("the BinarySource generation guard did not preserve the dirty drawing")
|
|
}
|
|
if got, err := ed.Pipeline.SourceBytes(doc.Path); err != nil || !bytes.Equal(got, foreign) {
|
|
t.Fatal("Save As overwrote the earlier external source edit", err)
|
|
}
|
|
}
|
|
|
|
func TestArtSaveAsExistingUnindexedSourceIsNotOverwritten(t *testing.T) {
|
|
p := artActionFixture(t)
|
|
ed, doc, model := p.ctx, p.doc, p.model
|
|
name := "assets/unindexed.aeart"
|
|
data, err := model.Session.Document.Encode()
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
meta, err := ed.Pipeline.NewSourceMeta(name)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
metaBytes := pipeline.WriteMeta(meta)
|
|
for file, contents := range map[string][]byte{name: data, name + ".meta": metaBytes} {
|
|
if err := os.WriteFile(filepath.Join(ed.Project.Root, filepath.FromSlash(file)), contents, 0600); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
if _, ok := ed.Pipeline.Lookup(name); ok {
|
|
t.Fatal("fixture unexpectedly indexed the destination")
|
|
}
|
|
p.askSaveAs()
|
|
q := ed.TakeQuestions()
|
|
if len(q) != 1 || q[0].DestructiveWhen == nil || !q[0].DestructiveWhen([]string{"./" + name}) {
|
|
t.Fatal("unindexed existing file was not shown as Replace")
|
|
}
|
|
q[0].Submit([]string{"./" + name})
|
|
if p.err == nil || len(ed.TakeQuestions()) != 0 {
|
|
t.Fatal("an unindexed identity was accepted for replacement")
|
|
}
|
|
for file, want := range map[string][]byte{name: data, name + ".meta": metaBytes} {
|
|
if got, err := ed.Pipeline.SourceBytes(file); err != nil || !bytes.Equal(got, want) {
|
|
t.Fatalf("unindexed %s changed: %v", file, err)
|
|
}
|
|
}
|
|
if doc.Binding.Model != model || !doc.Dirty() {
|
|
t.Fatal("rejected unindexed replacement lost the source drawing")
|
|
}
|
|
}
|