fix(board): commit a search only after its query succeeds and report read failures

A failed read on a UI path (sort change, clear, refresh after a write, opening a
card, the tag picker, the tags tab) used to escape as an unhandled exception.
Reads now run before the view is cleared and report Error_Read_* instead; the
active filter changes only once its query has run. DataWritten and TagsChanged
fire only after a successful refresh, so a broken database shows one dialog.
This commit is contained in:
doyaGu committed 2026-10-03 08:58:53 -04:00
1 parent 23f7806e2d
commit f9f0e71453
9 files changed
+216 -33

No files matched your search

@@ -285,6 +285,52 @@ public class BoardViewModelTests
Assert.AreEqual(0, fixture.Repository.GetCards().Count);
}
[TestMethod]
public async Task FailedSearchQueryIsReportedAndKeepsThePreviousResultAndFilter()
{
using var fixture = new Fixture();
ColumnModel column = fixture.Repository.AddColumn("A", "");
fixture.Repository.AddCard(column.Id, "alpha", "", []);
fixture.Repository.AddCard(column.Id, "beta", "", []);
BoardViewModel board = fixture.CreateBoard();
board.SearchText = "alpha";
await board.SearchCommand.ExecuteAsync(null);
CompiledSearch? alphaFilter = board.ActiveFilter;
// Every read fails from now on.
fixture.Repository.Dispose();
board.SearchText = "beta";
await board.SearchCommand.ExecuteAsync(null);
var error = (MessageDialogViewModel)fixture.Dialogs.Shown.Single();
Assert.AreEqual(Resources.Error_Read_Title, error.Title);
Assert.AreSame(alphaFilter, board.ActiveFilter);
CollectionAssert.AreEqual(new[] { "alpha" }, board.Columns.Single().Cards.Select(card => card.Title).ToArray());
}
[TestMethod]
public async Task ReadFailuresOnBoardCommandsAreReportedInsteadOfThrown()
{
using var fixture = new Fixture();
ColumnModel column = fixture.Repository.AddColumn("A", "");
CardModel card = fixture.Repository.AddCard(column.Id, "a", "", []);
BoardViewModel board = fixture.CreateBoard();
var editor = CardEditorDialogViewModel.ForExistingCard(fixture.Repository, fixture.Dialogs, card, confirmDiscard: false);
fixture.Repository.Dispose();
board.SelectedSort = board.SortOptions.Single(option => (CardSortOption)option.Value == CardSortOption.Title);
await board.ClearSearchCommand.ExecuteAsync(null);
await board.Columns.Single().Cards.Single().EditCommand.ExecuteAsync(null);
await board.Columns.Single().Cards.Single().MoveToCommand.ExecuteAsync(column.Id);
await editor.AddTagCommand.ExecuteAsync(null);
// Sort, clear, edit, the failed move with its refresh, and the tag picker.
string read = Resources.Error_Read_Title;
CollectionAssert.AreEqual(
new[] { read, read, read, Resources.Error_Operation_Title, read, read },
fixture.Dialogs.Shown.Select(dialog => ((MessageDialogViewModel)dialog).Title).ToArray());
}
[TestMethod]
public void TagPickerReportsInvalidColorAndDuplicateNameWithoutClosing()
{
@@ -138,7 +138,7 @@ public class TagsAndSearchViewModelTests
board.SearchText = "alpha";
await board.SearchCommand.ExecuteAsync(null);
Assert.IsTrue(board.HasSearchText);
board.ClearSearchCommand.Execute(null);
await board.ClearSearchCommand.ExecuteAsync(null);
Assert.AreEqual("", board.SearchText);
Assert.IsFalse(board.HasSearchText);
@@ -312,6 +312,24 @@ public class TagsAndSearchViewModelTests
Assert.AreEqual(0, fixture.Repository.GetTags().Count);
}
[TestMethod]
public async Task TagReadFailuresAreReportedInsteadOfThrown()
{
using var fixture = new Fixture();
fixture.Repository.AddTag("bug", new RgbColor(1, 2, 3), "");
fixture.Config.Confirmations.DeleteTag = false;
TagsViewModel tags = fixture.CreateTags();
// Every read and write fails from now on.
fixture.Repository.Dispose();
await tags.Tags[0].DeleteCommand.ExecuteAsync(null);
CollectionAssert.AreEqual(
new[] { Resources.Error_Operation_Title, Resources.Error_Read_Title },
fixture.Dialogs.Shown.Select(dialog => ((MessageDialogViewModel)dialog).Title).ToArray());
Assert.AreEqual("bug", tags.Tags.Single().Tag.Name);
}
#endregion
#region Workspace wiring and export
@@ -334,7 +352,7 @@ public class TagsAndSearchViewModelTests
Assert.AreEqual(0, workspace.Board.Columns.Sum(column => column.CardCount));
// Re-tag through the repository, then a board write updates the usage count.
workspace.Board.ClearSearchCommand.Execute(null);
await workspace.Board.ClearSearchCommand.ExecuteAsync(null);
TagModel ui = fixture.Repository.AddTag("ui", new RgbColor(4, 5, 6), "");
fixture.Repository.UpdateCard(card.Id, "a", "", [ui.Id]);
Assert.AreEqual(0, workspace.Tags.Tags.Count);