552 lines
20 KiB
C#
552 lines
20 KiB
C#
using Explorer.Domain;
|
|
using Explorer.Domain.Abstractions;
|
|
|
|
namespace Explorer.Domain.Tests;
|
|
|
|
public class NameNormalizerTests
|
|
{
|
|
[Fact]
|
|
public void Normalize_folds_case_and_keeps_unicode()
|
|
{
|
|
Assert.Equal("straße", NameNormalizer.Normalize("Straße"));
|
|
Assert.Equal("abc", NameNormalizer.Normalize("ABC"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Extension_skips_leading_dot_names()
|
|
{
|
|
Assert.Equal("mkv", NameNormalizer.Extension("film.mkv"));
|
|
Assert.Null(NameNormalizer.Extension(".gitignore"));
|
|
Assert.Null(NameNormalizer.Extension("Makefile"));
|
|
}
|
|
}
|
|
|
|
public class PathRulesTests
|
|
{
|
|
[Fact]
|
|
public void Extended_roundtrip_local_and_unc()
|
|
{
|
|
Assert.Equal(@"\\?\C:\Temp", PathRules.ToExtended(@"C:\Temp"));
|
|
Assert.Equal(@"\\?\UNC\server\share", PathRules.ToExtended(@"\\server\share"));
|
|
Assert.Equal(@"C:\Temp", PathRules.FromExtended(@"\\?\C:\Temp"));
|
|
Assert.Equal(@"\\server\share", PathRules.FromExtended(@"\\?\UNC\server\share"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Canonical_unc_lowercases_server()
|
|
{
|
|
Assert.Equal(@"\\media\Movies", PathRules.CanonicalUncRoot(@"\\Media\Movies\Action"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Relative_and_parent()
|
|
{
|
|
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"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Join_display_combines_root_and_relative()
|
|
{
|
|
Assert.Equal(@"C:\Users\Docs", PathRules.JoinDisplay(@"C:\Users", "Docs"));
|
|
Assert.Equal(@"C:\", PathRules.JoinDisplay(@"C:\", ""));
|
|
Assert.Equal("Docs", PathRules.JoinDisplay(null, "Docs"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Shorten_keeps_root_and_leaf()
|
|
{
|
|
Assert.Equal(@"C:\Temp", PathRules.ShortenDisplay(@"C:\Temp"));
|
|
Assert.Equal(@"C:\Users\…\Documents\Budget", PathRules.ShortenDisplay(@"C:\Users\Dominique\Documents\Budget", 28));
|
|
Assert.Equal(@"\\media\movies\…\Action\Dune", PathRules.ShortenDisplay(@"\\media\movies\New\Action\Dune", 28));
|
|
}
|
|
}
|
|
|
|
public class WindowsFileNamesTests
|
|
{
|
|
[Fact]
|
|
public void Rejects_reserved_and_illegal_names()
|
|
{
|
|
Assert.False(WindowsFileNames.IsValid("CON", out _));
|
|
Assert.False(WindowsFileNames.IsValid("a<.txt", out _));
|
|
Assert.False(WindowsFileNames.IsValid("ends.", out _));
|
|
Assert.True(WindowsFileNames.IsValid("Vacation.jpg", out _));
|
|
Assert.Equal(("Vacation", "jpg"), WindowsFileNames.Split("Vacation.jpg"));
|
|
Assert.Equal("Hello World", WindowsFileNames.ApplyCase("hello world", RenameCaseMode.Title));
|
|
Assert.Equal("007", WindowsFileNames.FormatCounter(7, 3));
|
|
}
|
|
}
|
|
|
|
public class ArchiveFormatsTests
|
|
{
|
|
[Fact]
|
|
public void Detects_common_archive_extensions()
|
|
{
|
|
Assert.True(ArchiveFormats.IsArchive("pack.zip"));
|
|
Assert.True(ArchiveFormats.IsArchive("film.7z"));
|
|
Assert.True(ArchiveFormats.IsArchive("backup.rar"));
|
|
Assert.True(ArchiveFormats.IsArchive("src.tar.gz"));
|
|
Assert.True(ArchiveFormats.IsZipFamily("photos.cbz"));
|
|
Assert.False(ArchiveFormats.IsArchive("notes.txt"));
|
|
Assert.False(ArchiveFormats.IsArchive("report.docx"));
|
|
Assert.Equal("2019-backup", ArchiveFormats.Stem("2019-backup.7z"));
|
|
Assert.Equal("bundle", ArchiveFormats.Stem("bundle.tar.gz"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Rejects_unsafe_archive_entry_paths()
|
|
{
|
|
Assert.True(ArchiveFormats.TryNormalizeEntryPath("docs/a.txt", out var ok));
|
|
Assert.Equal(@"docs\a.txt", ok);
|
|
Assert.False(ArchiveFormats.TryNormalizeEntryPath(@"..\escape.txt", out _));
|
|
Assert.False(ArchiveFormats.TryNormalizeEntryPath(@"C:\abs.txt", out _));
|
|
Assert.False(ArchiveFormats.TryNormalizeEntryPath("/", out _));
|
|
}
|
|
}
|
|
|
|
public class ConversionFormatsTests
|
|
{
|
|
[Fact]
|
|
public void Matches_video_audio_and_heic()
|
|
{
|
|
Assert.True(ConversionFormats.Matches("clip.MOV", ConversionKind.VideoToMp4));
|
|
Assert.True(ConversionFormats.Matches("talk.wav", ConversionKind.ExtractAudio));
|
|
Assert.True(ConversionFormats.Matches("film.mkv", ConversionKind.ExtractAudio));
|
|
Assert.True(ConversionFormats.Matches("IMG_0001.heic", ConversionKind.HeicToJpeg));
|
|
Assert.False(ConversionFormats.Matches("notes.txt", ConversionKind.VideoToMp4));
|
|
Assert.False(ConversionFormats.IsConvertible("notes.txt"));
|
|
Assert.True(ConversionFormats.IsConvertible("phone.mp4"));
|
|
Assert.Equal("mp4", ConversionFormats.Extension(ConversionKind.VideoToMp4));
|
|
Assert.Equal("m4a", ConversionFormats.Extension(ConversionKind.ExtractAudio));
|
|
Assert.Equal("jpg", ConversionFormats.Extension(ConversionKind.HeicToJpeg));
|
|
Assert.Equal(ConversionKind.VideoToMp4, ConversionFormats.Preferred(["clip.mov", "notes.txt"]));
|
|
Assert.Equal(ConversionKind.HeicToJpeg, ConversionFormats.Preferred(["IMG.HEIC"]));
|
|
Assert.Equal(ConversionKind.VideoToMp4, ConversionFormats.Infer(@"C:\a.mov", @"C:\a.mp4"));
|
|
Assert.Equal(ConversionKind.ExtractAudio, ConversionFormats.Infer(@"C:\a.mov", @"C:\a.m4a"));
|
|
Assert.Equal(ConversionKind.HeicToJpeg, ConversionFormats.Infer(@"C:\a.heic", @"C:\a.jpg"));
|
|
}
|
|
}
|
|
|
|
public class VolumeIdentityTests
|
|
{
|
|
[Fact]
|
|
public void Matches_volume_guid_across_drive_letters()
|
|
{
|
|
var known = new List<Source>
|
|
{
|
|
new()
|
|
{
|
|
Id = 1,
|
|
StableKey = "a",
|
|
DisplayName = "SanDisk Ultra 64 GB",
|
|
VolumeGuid = @"\\?\Volume{abc}\",
|
|
LastRootPath = @"E:\",
|
|
Kind = SourceKind.Removable
|
|
}
|
|
};
|
|
var fp = new VolumeFingerprint
|
|
{
|
|
Kind = SourceKind.Removable,
|
|
VolumeGuid = @"\\?\Volume{abc}\",
|
|
RootPath = @"G:\",
|
|
DisplayName = "SanDisk"
|
|
};
|
|
var match = VolumeIdentityMatcher.Match(fp, known);
|
|
Assert.NotNull(match.Source);
|
|
Assert.Equal(1, match.Source!.Id);
|
|
Assert.Equal(VolumeIdentityMatcher.MatchStrength.Guid, match.Strength);
|
|
Assert.False(match.Ambiguous);
|
|
}
|
|
|
|
[Fact]
|
|
public void Matches_unc_share()
|
|
{
|
|
var known = new List<Source>
|
|
{
|
|
new()
|
|
{
|
|
Id = 7,
|
|
StableKey = "s",
|
|
DisplayName = @"\\media\movies",
|
|
Kind = SourceKind.Smb,
|
|
LastRootPath = @"\\Media\Movies"
|
|
}
|
|
};
|
|
var fp = new VolumeFingerprint { Kind = SourceKind.Smb, RootPath = @"\\media\movies", DisplayName = "x" };
|
|
Assert.Equal(7, VolumeIdentityMatcher.Match(fp, known).Source?.Id);
|
|
}
|
|
}
|
|
|
|
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]
|
|
public void Excludes_path_glob_and_extension()
|
|
{
|
|
var ev = new ExcludeEvaluator(
|
|
[
|
|
new() { Kind = ExcludeKind.PathPrefix, Pattern = @"C:\Windows" },
|
|
new() { Kind = ExcludeKind.Glob, Pattern = "node_modules" },
|
|
new() { Kind = ExcludeKind.Extension, Pattern = "tmp" }
|
|
], skipHidden: false, skipSystem: false);
|
|
|
|
Assert.True(ev.ShouldExclude(@"C:\Windows\System32", "System32", true, 0, null));
|
|
Assert.True(ev.ShouldExclude(@"D:\proj\node_modules", "node_modules", true, 0, null));
|
|
Assert.True(ev.ShouldExclude(@"D:\a.tmp", "a.tmp", false, 0, null));
|
|
Assert.False(ev.ShouldExclude(@"D:\a.txt", "a.txt", false, 0, null));
|
|
}
|
|
|
|
[Fact]
|
|
public void Hidden_policy()
|
|
{
|
|
var ev = new ExcludeEvaluator([], skipHidden: true, skipSystem: true);
|
|
Assert.True(ev.ShouldExclude(@"C:\x", "x", false, AttributeFlags.Hidden, null));
|
|
}
|
|
}
|
|
|
|
public class ReparsePolicyTests
|
|
{
|
|
[Fact]
|
|
public void Does_not_recurse_reparse_directories()
|
|
{
|
|
var item = new FileSystemItem
|
|
{
|
|
FullPath = @"C:\link",
|
|
Name = "link",
|
|
IsDirectory = true,
|
|
Attributes = AttributeFlags.Directory | AttributeFlags.ReparsePoint,
|
|
ReparseTag = ReparsePolicy.IoReparseTagSymlink
|
|
};
|
|
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"));
|
|
}
|
|
}
|
|
|
|
public class DuplicateClassifierTests
|
|
{
|
|
[Fact]
|
|
public void Same_source_and_file_id_is_hardlink_not_duplicate()
|
|
{
|
|
var entries = new[]
|
|
{
|
|
File(1, 10, 100),
|
|
File(2, 10, 100)
|
|
};
|
|
Assert.True(DuplicateClassifier.IsHardlinkOnly(entries));
|
|
Assert.Equal(DuplicateClass.Hardlink, DuplicateClassifier.Classify(entries, []));
|
|
Assert.True(DuplicateClassifier.IsHiddenByDefault(DuplicateClass.Hardlink));
|
|
Assert.Equal(0, DuplicateClassifier.ClassifyGroup(new DuplicateGroup { SizeBytes = 50, Entries = entries }, []).WastedBytes);
|
|
}
|
|
|
|
[Fact]
|
|
public void Same_file_id_on_different_volumes_is_not_a_hardlink()
|
|
{
|
|
var entries = new[]
|
|
{
|
|
File(1, 10, 100, sourceId: 1),
|
|
File(2, 10, 100, sourceId: 2)
|
|
};
|
|
Assert.False(DuplicateClassifier.IsHardlinkOnly(entries));
|
|
Assert.Equal(DuplicateClass.Unknown, DuplicateClassifier.Classify(entries, []));
|
|
Assert.Equal(2, DuplicateClassifier.UniqueFileCount(entries));
|
|
}
|
|
|
|
[Fact]
|
|
public void Marked_intentional_relation_is_hidden_by_default()
|
|
{
|
|
var entries = new[] { File(1, 10, null), File(2, 11, null) };
|
|
var relations = new[]
|
|
{
|
|
new FileRelation
|
|
{
|
|
LeftEntryId = 1,
|
|
RightEntryId = 2,
|
|
Kind = FileRelationKind.IntentionalDuplicate,
|
|
Origin = FileRelationOrigin.User,
|
|
CreatedUtc = DateTimeOffset.UtcNow
|
|
}
|
|
};
|
|
var classified = DuplicateClassifier.Classify(entries, relations);
|
|
Assert.Equal(DuplicateClass.Intentional, classified);
|
|
Assert.True(DuplicateClassifier.IsHiddenByDefault(classified));
|
|
Assert.False(DuplicateClassifier.IsHiddenByDefault(DuplicateClass.Unknown));
|
|
Assert.False(DuplicateClassifier.IsHiddenByDefault(DuplicateClass.Accidental));
|
|
}
|
|
|
|
[Fact]
|
|
public void Sync_and_backup_relations_classify_ahead_of_unknown()
|
|
{
|
|
var entries = new[] { File(1, 10, 1), File(2, 11, 2) };
|
|
Assert.Equal(DuplicateClass.Synchronized, DuplicateClassifier.Classify(entries,
|
|
[
|
|
Rel(1, 2, FileRelationKind.SyncCopy)
|
|
]));
|
|
Assert.Equal(DuplicateClass.Backup, DuplicateClassifier.Classify(entries,
|
|
[
|
|
Rel(1, 2, FileRelationKind.BackupCopy)
|
|
]));
|
|
}
|
|
|
|
private static IndexEntry File(long id, long sourceFileId, long? fileId, long sourceId = 1)
|
|
=> new()
|
|
{
|
|
Id = id,
|
|
SourceId = sourceId,
|
|
Name = id + ".bin",
|
|
NameNorm = id + ".bin",
|
|
PathRel = id + ".bin",
|
|
SizeBytes = 50,
|
|
FileId = fileId,
|
|
LastSeenUtc = DateTimeOffset.UtcNow
|
|
};
|
|
|
|
private static FileRelation Rel(long left, long right, FileRelationKind kind)
|
|
=> new()
|
|
{
|
|
LeftEntryId = left,
|
|
RightEntryId = right,
|
|
Kind = kind,
|
|
Origin = FileRelationOrigin.User,
|
|
CreatedUtc = DateTimeOffset.UtcNow
|
|
};
|
|
}
|
|
|
|
public class OperationProfileTests
|
|
{
|
|
[Fact]
|
|
public void Auto_run_is_copy_only()
|
|
{
|
|
var copy = new OperationProfile { Name = "Copy", DoCopy = true, AutoRun = true };
|
|
Assert.True(copy.CanAutoRun);
|
|
|
|
var compress = new OperationProfile { Name = "Archive", DoCopy = true, DoCompress = true, AutoRun = true };
|
|
Assert.False(compress.CanAutoRun);
|
|
|
|
var convert = new OperationProfile { Name = "Convert", DoCopy = true, DoConvert = true, AutoRun = true };
|
|
Assert.False(convert.CanAutoRun);
|
|
|
|
var rename = new OperationProfile { Name = "Rename", DoCopy = true, DoRename = true, RenamePrefix = "x_", AutoRun = true };
|
|
Assert.False(rename.CanAutoRun);
|
|
}
|
|
}
|
|
|
|
public class FileClassifierTests
|
|
{
|
|
[Fact]
|
|
public void Extension_and_archive_signals()
|
|
{
|
|
Assert.Equal(FileCategory.Photos, Classify("sunset.jpg"));
|
|
Assert.Equal(FileCategory.Video, Classify("clip.mkv"));
|
|
Assert.Equal(FileCategory.Audio, Classify("track.mp3"));
|
|
Assert.Equal(FileCategory.Documents, Classify("notes.pdf"));
|
|
Assert.Equal(FileCategory.Installer, Classify("Git-64-bit.exe"));
|
|
Assert.Equal(FileCategory.Archive, Classify("pack.7z"));
|
|
Assert.Equal(FileCategory.Backup, Classify("db.bak"));
|
|
Assert.Equal(FileCategory.Unknown, Classify("mystery.bin"));
|
|
}
|
|
|
|
[Fact]
|
|
public void Folder_structure_and_git()
|
|
{
|
|
Assert.Equal(FileCategory.CodeRepository, Classify("Explorer", isDirectory: true, isRepoRoot: true));
|
|
Assert.Equal(FileCategory.BuildOutput, Classify("node_modules", isDirectory: true));
|
|
Assert.Equal(FileCategory.BuildOutput, Classify("bin", isDirectory: true));
|
|
Assert.Equal(FileCategory.Photos, Classify("DCIM", isDirectory: true));
|
|
Assert.Equal(FileCategory.SystemData, FileClassifier.Classify(
|
|
"$RECYCLE.BIN", @"C:\$RECYCLE.BIN", true,
|
|
AttributeFlags.Directory | AttributeFlags.Hidden | AttributeFlags.System).Category);
|
|
Assert.True(FileClassifier.ShouldLeave(FileCategory.Unknown));
|
|
Assert.True(FileClassifier.ShouldLeave(FileCategory.BuildOutput));
|
|
Assert.False(FileClassifier.ShouldLeave(FileCategory.Photos));
|
|
}
|
|
|
|
[Fact]
|
|
public void Majority_of_children_classifies_a_folder()
|
|
{
|
|
var mixed = FileClassifier.Classify(
|
|
"Vacation", @"C:\Downloads\Vacation", true, AttributeFlags.Directory,
|
|
childCategories: [FileCategory.Photos, FileCategory.Photos, FileCategory.Photos, FileCategory.Documents]);
|
|
Assert.Equal(FileCategory.Photos, mixed.Category);
|
|
|
|
var split = FileClassifier.Classify(
|
|
"Misc", @"C:\Downloads\Misc", true, AttributeFlags.Directory,
|
|
childCategories: [FileCategory.Photos, FileCategory.Video]);
|
|
Assert.Equal(FileCategory.Unknown, split.Category);
|
|
}
|
|
|
|
private static FileCategory Classify(string name, bool isDirectory = false, bool isRepoRoot = false)
|
|
=> FileClassifier.Classify(name, @"C:\Downloads\" + name, isDirectory, isDirectory ? AttributeFlags.Directory : 0, isRepoRoot).Category;
|
|
}
|
|
|
|
public class GitChangeTests
|
|
{
|
|
[Fact]
|
|
public void Rename_and_delete_labels()
|
|
{
|
|
var renamed = new GitChange
|
|
{
|
|
Path = "new.txt",
|
|
OriginalPath = "old.txt",
|
|
Index = 'R',
|
|
WorkTree = '.',
|
|
State = GitChangeState.Staged
|
|
};
|
|
Assert.Equal("old.txt → new.txt", renamed.DisplayPath);
|
|
Assert.Equal("renamed", renamed.ChangeLabel);
|
|
Assert.Equal(@"C:\src\new.txt", renamed.ToFullPath(@"C:\src"));
|
|
Assert.False(renamed.IsDeleted);
|
|
|
|
var deleted = new GitChange
|
|
{
|
|
Path = "gone.txt",
|
|
Index = '.',
|
|
WorkTree = 'D',
|
|
State = GitChangeState.Unstaged
|
|
};
|
|
Assert.True(deleted.IsDeleted);
|
|
Assert.Equal("deleted", deleted.ChangeLabel);
|
|
}
|
|
}
|
|
|
|
public class BoundedWaitTests
|
|
{
|
|
[Fact]
|
|
public void Try_returns_the_result_when_work_finishes()
|
|
=> Assert.True(BoundedWait.Try(() => true, TimeSpan.FromSeconds(1)));
|
|
|
|
[Fact]
|
|
public void Try_returns_false_when_work_exceeds_the_timeout()
|
|
=> Assert.False(BoundedWait.Try(
|
|
() =>
|
|
{
|
|
Thread.Sleep(250);
|
|
return true;
|
|
},
|
|
TimeSpan.FromMilliseconds(30)));
|
|
}
|
|
|
|
public class GitCommandResultTests
|
|
{
|
|
[Fact]
|
|
public void Display_prefers_summary_then_stderr_on_failure()
|
|
{
|
|
Assert.Equal("Committed.", GitCommandResult.Ok("Committed.").DisplayMessage);
|
|
Assert.Equal(
|
|
"not a fast-forward",
|
|
new GitCommandResult
|
|
{
|
|
Succeeded = false,
|
|
ExitCode = 1,
|
|
StandardError = "not a fast-forward\n"
|
|
}.DisplayMessage);
|
|
Assert.Equal("git failed (exit 128).", new GitCommandResult
|
|
{
|
|
Succeeded = false,
|
|
ExitCode = 128
|
|
}.DisplayMessage);
|
|
}
|
|
}
|
|
|
|
public class GitStatusBadgeTests
|
|
{
|
|
[Fact]
|
|
public void Includes_merging_instead_of_clean()
|
|
{
|
|
var status = new GitStatus
|
|
{
|
|
RepoRoot = @"C:\src",
|
|
Branch = "main",
|
|
Operation = GitOperationKind.Merge
|
|
};
|
|
Assert.Equal("main · merging", status.Badge);
|
|
Assert.Equal("merging", status.OperationLabel);
|
|
}
|
|
}
|