fix(export): write through the storage provider when the target has no local path

The save picker used to return only TryGetLocalPath(), so a target without a
local path looked like a cancel and nothing was written. The dialog service now
hands back an opener for the chosen file. The picker call is inside the error
handler, and the workspace is read before the target is opened, so a failed read
leaves an existing file untouched.
This commit is contained in:
doyaGu committed 2026-10-03 08:59:40 -04:00
1 parent f9f0e71453
commit c9523d0953
5 files changed
+61 -13

No files matched your search

@@ -27,10 +27,12 @@ internal sealed class ScriptedDialogService : IDialogService
return result ?? false;
}
public Task<string?> PickSaveFilePathAsync(string suggestedFileName)
public Task<Func<Task<Stream>>?> PickSaveFileAsync(string suggestedFileName)
{
SuggestedFileNames.Add(suggestedFileName);
return Task.FromResult(SaveFilePath);
string? path = SaveFilePath;
return Task.FromResult<Func<Task<Stream>>?>(
path is null ? null : () => Task.FromResult<Stream>(File.Create(path)));
}
/// <summary>Answers every confirmation dialog with the given choice.</summary>
@@ -414,5 +414,22 @@ public class TagsAndSearchViewModelTests
Assert.IsTrue(message.HasDetails);
}
[TestMethod]
public async Task ExportReadFailureIsReportedAndLeavesTheTargetUntouched()
{
using var fixture = new Fixture();
WorkspaceViewModel workspace = fixture.CreateWorkspace();
string path = Path.Combine(fixture.Directory.FullPath, "out.md");
File.WriteAllText(path, "previous");
fixture.Dialogs.SaveFilePath = path;
fixture.Repository.Dispose();
await workspace.ExportCommand.ExecuteAsync(null);
var message = (MessageDialogViewModel)fixture.Dialogs.Shown.Single();
Assert.AreEqual(Resources.Export_Failed_Title, message.Title);
Assert.AreEqual("previous", File.ReadAllText(path));
}
#endregion
}