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()
{