Make drag-and-drop follow Windows Explorer and refresh the tree after folder operations.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-08-23 16:33:49 +02:00
parent 09a8cfafa3
commit d79605cde9
15 changed files with 879 additions and 58 deletions

View File

@@ -43,6 +43,8 @@ public class PathRulesTests
Assert.Equal(@"Windows\System32", PathRules.MakeRelative(@"C:\", @"C:\Windows\System32"));
Assert.Equal(@"C:\Windows", PathRules.Parent(@"C:\Windows\System32"));
Assert.Equal(@"C:\", PathRules.Parent(@"C:\Windows"));
Assert.True(PathRules.DriveLetterSortKey(@"D:\") < PathRules.DriveLetterSortKey(@"F:\"));
Assert.True(PathRules.DriveLetterSortKey(@"C:\") < PathRules.DriveLetterSortKey(@"\\media\movies"));
Assert.Equal(@"pack.zip\docs", PathRules.RelativeParent(@"pack.zip\docs\a.txt"));
Assert.Equal("pack.zip", PathRules.RelativeParent(@"pack.zip\docs"));
Assert.Equal("", PathRules.RelativeParent("pack.zip"));
@@ -140,6 +142,56 @@ public class VolumeIdentityTests
}
}
public class LocationClassifierTests
{
[Fact]
public void Classifies_known_protected_locations()
{
var svi = LocationClassifier.Classify(
@"C:\System Volume Information", "System Volume Information",
AttributeFlags.Directory | AttributeFlags.Hidden | AttributeFlags.System, true);
Assert.True(svi.IsProtected);
Assert.True(svi.IsHidden);
Assert.True(svi.IsSystem);
Assert.False(svi.IsRecycleBin);
var recycle = LocationClassifier.Classify(
@"D:\$RECYCLE.BIN", "$RECYCLE.BIN",
AttributeFlags.Directory | AttributeFlags.Hidden | AttributeFlags.System, true);
Assert.True(recycle.IsProtected);
Assert.True(recycle.IsRecycleBin);
var pagefile = LocationClassifier.Classify(
@"C:\pagefile.sys", "pagefile.sys", AttributeFlags.Hidden | AttributeFlags.System, false);
Assert.True(pagefile.IsProtected);
Assert.False(pagefile.IsRecycleBin);
}
[Fact]
public void Does_not_treat_normal_hidden_user_files_as_protected()
{
var desktopIni = LocationClassifier.Classify(
@"C:\Users\Ada\Desktop\desktop.ini", "desktop.ini",
AttributeFlags.Hidden | AttributeFlags.System, false);
Assert.True(desktopIni.IsHidden);
Assert.True(desktopIni.IsSystem);
Assert.False(desktopIni.IsProtected);
var movies = LocationClassifier.Classify(
@"C:\Movies", "Movies", AttributeFlags.Directory, true);
Assert.False(movies.IsProtected);
Assert.False(movies.IsHidden);
}
[Fact]
public void Access_denied_marks_protected()
{
var info = LocationClassifier.Classify(@"C:\locked", "locked", AttributeFlags.Directory, true, accessDenied: true);
Assert.True(info.AccessDenied);
Assert.True(info.IsProtected);
}
}
public class ExcludeEvaluatorTests
{
[Fact]
@@ -182,3 +234,36 @@ public class ReparsePolicyTests
Assert.False(ReparsePolicy.ShouldRecurseIntoDirectory(item));
}
}
public class DragDropPolicyTests
{
[Fact]
public void Modifiers_match_windows_explorer()
{
Assert.Equal(DropAction.Move, DragDropPolicy.Resolve(false, false, false, sameVolume: true));
Assert.Equal(DropAction.Copy, DragDropPolicy.Resolve(false, false, false, sameVolume: false));
Assert.Equal(DropAction.Copy, DragDropPolicy.Resolve(control: true, shift: false, alt: false, sameVolume: true));
Assert.Equal(DropAction.Move, DragDropPolicy.Resolve(control: false, shift: true, alt: false, sameVolume: false));
Assert.Equal(DropAction.Link, DragDropPolicy.Resolve(control: false, shift: false, alt: true, sameVolume: true));
Assert.Equal(DropAction.Link, DragDropPolicy.Resolve(control: true, shift: true, alt: false, sameVolume: false));
}
[Fact]
public void Rejects_virtual_and_self_targets()
{
Assert.True(DragDropPolicy.IsInvalidTarget([@"C:\Docs"], LocationRoots.ThisPc));
Assert.True(DragDropPolicy.IsInvalidTarget([@"C:\Docs"], @"C:\Docs"));
Assert.True(DragDropPolicy.IsInvalidTarget([@"C:\Docs"], @"C:\Docs\sub"));
Assert.False(DragDropPolicy.IsInvalidTarget([@"C:\Docs\file.txt"], @"C:\Docs"));
Assert.False(DragDropPolicy.IsInvalidTarget([@"C:\Docs"], @"D:\Other"));
}
[Fact]
public void Same_volume_uses_drive_or_unc_share()
{
Assert.True(PathRules.IsSameVolume(@"C:\a", @"C:\b"));
Assert.False(PathRules.IsSameVolume(@"C:\a", @"D:\b"));
Assert.True(PathRules.IsSameVolume(@"\\media\movies\a", @"\\media\movies\b"));
Assert.False(PathRules.IsSameVolume(@"\\media\movies\a", @"\\media\tv\b"));
}
}

View File

@@ -36,9 +36,12 @@ file sealed class StubEnum : IFileSystemEnumerator
file sealed class StubShell : IShellFileOperations
{
public void Open(string path) { }
public bool DeleteToRecycleBin(IReadOnlyList<string> paths, out string? error) { error = "n/a"; return false; }
public bool DeleteToRecycleBin(IReadOnlyList<string> paths, out string? error) => Delete(paths, true, out error);
public bool Delete(IReadOnlyList<string> paths, bool recycle, out string? error) { error = "n/a"; return false; }
public bool CopyFileWithProgress(string source, string destination, bool overwrite, IProgress<long>? progress, CancellationToken cancellationToken, out string? error)
{ error = "n/a"; return false; }
public bool MoveFileWithProgress(string source, string destination, bool overwrite, IProgress<long>? progress, CancellationToken cancellationToken, out string? error)
{ error = "n/a"; return false; }
public bool CreateShortcut(string targetPath, string shortcutPath, out string? error)
{ error = null; return true; }
}