Add Git overlay, operation tools, and virtualized preview so large folders stay responsive.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
187
tests/Explorer.Application.Tests/RenamePlannerTests.cs
Normal file
187
tests/Explorer.Application.Tests/RenamePlannerTests.cs
Normal file
@@ -0,0 +1,187 @@
|
||||
using Explorer.Application;
|
||||
using Explorer.Domain;
|
||||
|
||||
namespace Explorer.Application.Tests;
|
||||
|
||||
public class RenamePlannerTests
|
||||
{
|
||||
private static RenameSubject File(string path)
|
||||
=> new(path, PathRules.GetFileName(path), false);
|
||||
|
||||
[Fact]
|
||||
public void Search_replace_prefix_suffix_and_counter()
|
||||
{
|
||||
var plan = new RenamePlanner().Build(
|
||||
[File(@"C:\a\Vacation.jpg"), File(@"C:\a\Vacation (2).jpg")],
|
||||
new RenameRuleSet
|
||||
{
|
||||
Search = "Vacation",
|
||||
Replace = "Trip",
|
||||
Prefix = "2024_",
|
||||
Suffix = "_ok",
|
||||
UseCounter = true,
|
||||
CounterStart = 1,
|
||||
CounterPadding = 2
|
||||
});
|
||||
Assert.True(plan.CanEnqueue);
|
||||
Assert.Equal("2024_Trip_ok01.jpg", plan.Preview[0].NewName);
|
||||
Assert.Equal("2024_Trip (2)_ok02.jpg", plan.Preview[1].NewName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Regex_replace_and_case_and_extension()
|
||||
{
|
||||
var plan = new RenamePlanner().Build(
|
||||
[File(@"C:\a\IMG_001.JPG")],
|
||||
new RenameRuleSet
|
||||
{
|
||||
Search = @"IMG_(\d+)",
|
||||
Replace = "Photo_$1",
|
||||
UseRegex = true,
|
||||
CaseMode = RenameCaseMode.Lower,
|
||||
ChangeExtension = true,
|
||||
NewExtension = "jpg"
|
||||
});
|
||||
Assert.Equal("photo_001.jpg", plan.Preview[0].NewName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Counter_placeholder_does_not_need_hydration()
|
||||
{
|
||||
var plan = new RenamePlanner().Build(
|
||||
[File(@"C:\a\clip.mp4")],
|
||||
new RenameRuleSet { Prefix = "Clip_{Counter}_", UseCounter = true, CounterPadding = 3 });
|
||||
Assert.Equal("Clip_001_clip.mp4", plan.Preview[0].NewName);
|
||||
Assert.Contains("{Width}", new RenamePlanner().Build(
|
||||
[File(@"C:\a\clip.mp4")],
|
||||
new RenameRuleSet { Prefix = "{Width}_" }).Preview[0].NewName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Invalid_windows_name_is_an_error()
|
||||
{
|
||||
var plan = new RenamePlanner().Build(
|
||||
[File(@"C:\a\notes.txt")],
|
||||
new RenameRuleSet { Search = "notes", Replace = "CON" });
|
||||
Assert.False(plan.CanEnqueue);
|
||||
Assert.Contains(plan.Issues, i => i.Severity == PlanIssueSeverity.Error);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Collision_when_two_files_map_to_the_same_name()
|
||||
{
|
||||
var plan = new RenamePlanner().Build(
|
||||
[File(@"C:\a\alpha.txt"), File(@"C:\a\beta.txt")],
|
||||
new RenameRuleSet { UseRegex = true, Search = ".+", Replace = "same" });
|
||||
Assert.False(plan.CanEnqueue);
|
||||
Assert.Contains(plan.Issues, i => i.Message.Contains("same name", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Existing_path_outside_the_batch_is_a_collision()
|
||||
{
|
||||
var plan = new RenamePlanner().Build(
|
||||
[File(@"C:\a\one.txt")],
|
||||
new RenameRuleSet { Search = "one", Replace = "held" },
|
||||
path => path.Equals(@"C:\a\held.txt", StringComparison.OrdinalIgnoreCase));
|
||||
Assert.False(plan.CanEnqueue);
|
||||
Assert.Contains(plan.Issues, i => i.Message.Contains("already exists", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Collision_with_an_unchanged_file_in_the_batch()
|
||||
{
|
||||
var plan = new RenamePlanner().Build(
|
||||
[File(@"C:\a\one.txt"), File(@"C:\a\held.txt")],
|
||||
new RenameRuleSet { Search = "one", Replace = "held" });
|
||||
Assert.False(plan.CanEnqueue);
|
||||
Assert.Contains(plan.Issues, i => i.Message.Contains("already exists", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Chain_is_ordered_so_the_destination_moves_first()
|
||||
{
|
||||
var issues = new List<PlanIssue>();
|
||||
var ordered = RenamePlanner.Order(
|
||||
[
|
||||
(@"C:\a\a.txt", @"C:\a\b.txt", "b.txt"),
|
||||
(@"C:\a\b.txt", @"C:\a\c.txt", "c.txt")
|
||||
],
|
||||
issues);
|
||||
Assert.Empty(issues);
|
||||
Assert.Equal(@"C:\a\b.txt", ordered[0].Source);
|
||||
Assert.Equal(@"C:\a\a.txt", ordered[1].Source);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Cycle_is_an_error()
|
||||
{
|
||||
var issues = new List<PlanIssue>();
|
||||
RenamePlanner.Order(
|
||||
[
|
||||
(@"C:\a\x.txt", @"C:\a\y.txt", "y.txt"),
|
||||
(@"C:\a\y.txt", @"C:\a\x.txt", "x.txt")
|
||||
],
|
||||
issues);
|
||||
Assert.Contains(issues, i => i.Message.Contains("cycle", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Undo_restores_names_when_destination_still_matches()
|
||||
{
|
||||
var batch = new RenameBatch
|
||||
{
|
||||
Id = 1,
|
||||
CreatedUtc = DateTimeOffset.UtcNow,
|
||||
Items =
|
||||
[
|
||||
new RenameBatchItem(@"C:\a\old.txt", @"C:\a\new.txt", 0),
|
||||
new RenameBatchItem(@"C:\a\one.txt", @"C:\a\two.txt", 1)
|
||||
]
|
||||
};
|
||||
var exists = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { @"C:\a\new.txt", @"C:\a\two.txt" };
|
||||
var plan = new RenamePlanner().BuildUndo(batch, exists.Contains);
|
||||
Assert.True(plan.CanEnqueue);
|
||||
Assert.Equal(@"C:\a\two.txt", plan.Operations[0].SourcePath);
|
||||
Assert.Equal(@"C:\a\old.txt", plan.Operations[1].DestinationPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Undo_refuses_when_rename_has_not_finished()
|
||||
{
|
||||
var batch = new RenameBatch
|
||||
{
|
||||
Id = 1,
|
||||
CreatedUtc = DateTimeOffset.UtcNow,
|
||||
Items = [new RenameBatchItem(@"C:\a\old.txt", @"C:\a\new.txt", 0)]
|
||||
};
|
||||
var exists = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { @"C:\a\old.txt" };
|
||||
var plan = new RenamePlanner().BuildUndo(batch, exists.Contains);
|
||||
Assert.True(plan.HasErrors);
|
||||
Assert.Contains(plan.Issues, i => i.Message.Contains("not finished", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Undo_fails_when_original_name_is_taken()
|
||||
{
|
||||
var batch = new RenameBatch
|
||||
{
|
||||
Id = 1,
|
||||
CreatedUtc = DateTimeOffset.UtcNow,
|
||||
Items = [new RenameBatchItem(@"C:\a\old.txt", @"C:\a\new.txt", 0)]
|
||||
};
|
||||
var exists = new HashSet<string>(StringComparer.OrdinalIgnoreCase) { @"C:\a\new.txt", @"C:\a\old.txt" };
|
||||
var plan = new RenamePlanner().BuildUndo(batch, exists.Contains);
|
||||
Assert.True(plan.HasErrors);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Invalid_regex_is_an_error()
|
||||
{
|
||||
var plan = new RenamePlanner().Build(
|
||||
[File(@"C:\a\a.txt")],
|
||||
new RenameRuleSet { UseRegex = true, Search = "(" });
|
||||
Assert.False(plan.CanEnqueue);
|
||||
Assert.Contains(plan.Issues, i => i.Message.Contains("regular expression", StringComparison.OrdinalIgnoreCase));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user