Files
OpenNest/OpenNest.WinForms.Tests/Forms/UnsavedChangesTests.cs
T
ajandClaude Opus 5.5 d7fe7fe33c feat(ui): mark unsaved nests and ask before closing them
A nest window gave no sign that its nest had changed since the last
save, and closing it (or the app) discarded the edits silently. The
undo history already tracks a save point, so the title now ends in
" *" while the nest is off it (or holds an edit no scope recorded),
and closing asks to save, discard or cancel. Yes saves through the
storage in use; a database save cancels the close and closes the
window once the upload succeeds.

Saves now commit any unrecorded edit first so the save point covers
what was written. The window's cleanup moves from OnClosing to
OnFormClosed: another window can still cancel closing the app, and a
nest window must not lose its plate manager when that happens.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-11-03 12:09:50 -05:00

143 lines
4.4 KiB
C#

using System.Windows.Forms;
using OpenNest.Forms;
namespace OpenNest.WinForms.Tests.Forms;
// The title marks unsaved edits and closing asks before discarding them. Shares the undo
// tests' collection, since it drives the same main-window guards.
[Collection("Fill operation lifetime")]
public class UnsavedChangesTests
{
[Fact]
public void TheTitleIsMarkedWhileTheNestDiffersFromItsSavedState() => UndoTestEnv.Run(env =>
{
var editor = env.Editor;
Assert.False(editor.HasUnsavedChanges);
Assert.Equal("undo", editor.Text);
DeleteFirstPlatesParts(editor);
Assert.True(editor.HasUnsavedChanges);
Assert.Equal("undo *", editor.Text);
Assert.True(env.Host.TryUndo(editor));
Assert.False(editor.HasUnsavedChanges);
Assert.Equal("undo", editor.Text);
Assert.True(env.Host.TryRedo(editor));
Assert.Equal("undo *", editor.Text);
var path = TempNestPath("saved");
try
{
editor.SaveAs(path);
Assert.False(editor.HasUnsavedChanges);
Assert.Equal("saved", editor.Text);
}
finally
{
File.Delete(path);
}
});
[Fact]
public void AnEditNoScopeRecordedStillCountsAsUnsaved() => UndoTestEnv.Run(env =>
{
var editor = env.Editor;
editor.Nest.Plates[0].Parts.RemoveAt(0);
Assert.True(editor.HasUnsavedChanges);
});
[Fact]
public void ClosingAnUnchangedNestDoesNotAsk() => UndoTestEnv.Run(env =>
{
var asked = 0;
WithPrompt(env.Editor, () => { asked++; return DialogResult.Cancel; }, () =>
{
env.Editor.Close();
Assert.Equal(0, asked);
Assert.True(env.Editor.IsDisposed);
});
});
[Fact]
public void CancelKeepsTheWindowOpenAndNoClosesWithoutSaving() => UndoTestEnv.Run(env =>
{
var editor = env.Editor;
DeleteFirstPlatesParts(editor);
var answer = DialogResult.Cancel;
var asked = 0;
WithPrompt(editor, () => { asked++; return answer; }, () =>
{
editor.Close();
Assert.Equal(1, asked);
Assert.False(editor.IsDisposed);
Assert.True(editor.HasUnsavedChanges);
answer = DialogResult.No;
editor.Close();
Assert.Equal(2, asked);
Assert.True(editor.IsDisposed);
});
});
// Without a main window (whose save follows the machine's storage mode) Yes saves to a file.
[Fact]
public void YesSavesToTheNestsFileAndCloses() => StaTestThread.Run(() =>
{
using var editor = new EditNestForm(UndoTestEnv.BuildNest());
editor.PlateView.SetOverlapAutoCheck(null);
editor.Show();
var path = TempNestPath("yes");
try
{
editor.SaveAs(path);
var savedAt = File.GetLastWriteTimeUtc(path);
DeleteFirstPlatesParts(editor);
File.SetLastWriteTimeUtc(path, savedAt.AddMinutes(-1));
WithPrompt(editor, () => DialogResult.Yes, () =>
{
editor.Close();
Assert.True(editor.IsDisposed);
Assert.True(File.GetLastWriteTimeUtc(path) > savedAt.AddMinutes(-1));
});
}
finally
{
File.Delete(path);
}
}, TimeSpan.FromMinutes(1), "The STA test did not complete.");
private static void DeleteFirstPlatesParts(EditNestForm editor)
{
editor.PlateManager.LoadAt(0);
editor.PlateView.SelectAll();
editor.PlateView.Selection.DeleteSelected();
}
private static string TempNestPath(string name)
{
var directory = Path.Combine(Path.GetTempPath(), "OpenNest.WinForms.Tests", Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(directory);
return Path.Combine(directory, name + ".nest");
}
// Answers for this test's window only; other windows closing meanwhile keep the default.
private static void WithPrompt(EditNestForm editor, Func<DialogResult> answer, Action body)
{
var previous = EditNestForm.UnsavedChangesPrompt;
EditNestForm.UnsavedChangesPrompt = form => ReferenceEquals(form, editor) ? answer() : previous(form);
try
{
body();
}
finally
{
EditNestForm.UnsavedChangesPrompt = previous;
}
}
}