Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions QuickShell.Core.Tests/FakeShortcutRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,15 @@ internal sealed class FakeShortcutRepository : IShortcutRepository
private readonly Dictionary<string, TerminalShortcut> _byId;
private readonly Dictionary<string, TerminalShortcut> _byName;

public FakeShortcutRepository(IEnumerable<TerminalShortcut> shortcuts)
public FakeShortcutRepository(IEnumerable<TerminalShortcut> shortcuts, string? configDirectory = null)
{
var list = shortcuts.ToList();
_byId = list.ToDictionary(shortcut => shortcut.Id, StringComparer.OrdinalIgnoreCase);
_byName = list.ToDictionary(shortcut => shortcut.Name, StringComparer.OrdinalIgnoreCase);
ConfigDirectory = configDirectory ?? string.Empty;
}

public string ConfigDirectory => string.Empty;
public string ConfigDirectory { get; }

public string ConfigPath => string.Empty;

Expand Down Expand Up @@ -70,7 +71,7 @@ public Task<ShortcutTransferResult> ImportMergeAsync(string path, CancellationTo
public Task<ShortcutTransferResult> ImportReplaceAsync(string path, CancellationToken cancellationToken = default) =>
Task.FromResult(new ShortcutTransferResult());

public ShortcutTransferResult ResetAll() => new() { Success = true, Message = "No projects to reset." };
public ShortcutTransferResult ResetAll() => new() { Success = true, Message = "No workspaces to reset." };

public bool CanUndo => false;

Expand Down Expand Up @@ -98,6 +99,8 @@ public void MarkUsed(string shortcutId)

public TerminalShortcut? BuildDuplicate(string name) => null;

public TerminalShortcut BuildDuplicateFrom(TerminalShortcut source) => source;

public IEnumerable<TerminalShortcut> Search(string query) => GetShortcuts();

public IEnumerable<TerminalShortcut> SearchForRootPalette(string query) => [];
Expand Down
171 changes: 171 additions & 0 deletions QuickShell.Core.Tests/ShortcutDraftStoreTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
using QuickShell.Models;
using QuickShell.Services;

namespace QuickShell.Core.Tests;

public sealed class ShortcutDraftStoreTests : IDisposable
{
private readonly string _configDirectory;

public ShortcutDraftStoreTests()
{
_configDirectory = Path.Combine(Path.GetTempPath(), "quickshell-draft-store-" + Guid.NewGuid().ToString("N"));
Directory.CreateDirectory(_configDirectory);
}

[Fact]
public void Clear_removes_pending_and_deletes_draft_file()
{
var shortcut = CreateSavedShortcut();
var repository = new FakeShortcutRepository([shortcut], _configDirectory);
var store = new ShortcutDraftStore(repository);

store.SaveIfDirty(
shortcut.Name,
CreateDirtyDraft(shortcut.Name),
CreateBaseline(shortcut),
nameCustomized: false,
autoFilledName: null);
WaitForDraftFile(store);

store.Clear();

Assert.False(store.HasPending);
Assert.False(File.Exists(store.DraftPath));
}

[Fact]
public void Clear_after_save_prevents_stale_persist_from_recreating_draft_file()
{
var shortcut = CreateSavedShortcut();
var repository = new FakeShortcutRepository([shortcut], _configDirectory);
var store = new ShortcutDraftStore(repository);

store.SaveIfDirty(
shortcut.Name,
CreateDirtyDraft(shortcut.Name),
CreateBaseline(shortcut),
nameCustomized: false,
autoFilledName: null);

store.Clear();
store.Dispose();

Assert.False(File.Exists(store.DraftPath));
}

[Fact]
public void Reload_after_clear_does_not_restore_discarded_draft()
{
var shortcut = CreateSavedShortcut();
var repository = new FakeShortcutRepository([shortcut], _configDirectory);

using (var store = new ShortcutDraftStore(repository))
{
store.SaveIfDirty(
shortcut.Name,
CreateDirtyDraft(shortcut.Name),
CreateBaseline(shortcut),
nameCustomized: false,
autoFilledName: null);
WaitForDraftFile(store);
store.Clear();
}

using var reloaded = new ShortcutDraftStore(repository);
Assert.False(reloaded.HasPending);
Assert.False(reloaded.TryGetForRestore(shortcut.Name, out _));
}

[Fact]
public void Clear_raises_Cleared_with_original_name()
{
var shortcut = CreateSavedShortcut();
var repository = new FakeShortcutRepository([shortcut], _configDirectory);
var store = new ShortcutDraftStore(repository);
string? clearedName = null;
store.Cleared += name => clearedName = name;

store.SaveIfDirty(
shortcut.Name,
CreateDirtyDraft(shortcut.Name),
CreateBaseline(shortcut),
nameCustomized: false,
autoFilledName: null);

store.Clear();

Assert.Equal(shortcut.Name, clearedName);
}

[Fact]
public void Clear_without_pending_does_not_raise_Cleared()
{
var shortcut = CreateSavedShortcut();
var repository = new FakeShortcutRepository([shortcut], _configDirectory);
var store = new ShortcutDraftStore(repository);
var raised = false;
store.Cleared += _ => raised = true;

store.Clear();

Assert.False(raised);
}

public void Dispose()
{
try
{
if (Directory.Exists(_configDirectory))
{
Directory.Delete(_configDirectory, recursive: true);
}
}
catch
{
// Best effort cleanup for temp test data.
}
}

private static TerminalShortcut CreateSavedShortcut() => new()
{
Id = "draft-store-test",
Name = "MyProject",
Directory = @"C:\Projects\MyProject",
Command = "npm start",
Terminal = "pwsh",
};

private static ShortcutFormDraftData CreateBaseline(TerminalShortcut shortcut) => new()
{
OriginalName = shortcut.Name,
Name = shortcut.Name,
Directory = shortcut.Directory,
Command = shortcut.Command ?? string.Empty,
LaunchTarget = TerminalCatalog.EncodeLaunchTargetId(shortcut),
};

private static ShortcutFormDraftData CreateDirtyDraft(string originalName) => new()
{
OriginalName = originalName,
Name = "MyProject",
Directory = @"C:\Projects\Changed",
Command = "npm run dev",
LaunchTarget = "default",
};

private static void WaitForDraftFile(ShortcutDraftStore store)
{
for (var attempt = 0; attempt < 50; attempt++)
{
if (File.Exists(store.DraftPath))
{
return;
}

Thread.Sleep(20);
}

throw new InvalidOperationException("Draft file was not written in time.");
}
}
Loading
Loading