using Explorer.Application; using Explorer.Domain; namespace Explorer.Application.Tests; public class GitCommitPlannerTests { private static readonly HashSet None = new(StringComparer.OrdinalIgnoreCase); [Fact] public void Rejects_an_empty_message() { var plan = GitCommitPlanner.Create( " ", [Change("src/App.cs")], None, None, operationInProgress: false); Assert.False(plan.CanCommit); Assert.Equal("Enter a commit message.", plan.Error); } [Fact] public void Rejects_merge_or_rebase_in_progress() { var plan = GitCommitPlanner.Create( "wip", [Change("src/App.cs")], None, None, operationInProgress: true); Assert.False(plan.CanCommit); Assert.Contains("merge or rebase", plan.Error, StringComparison.OrdinalIgnoreCase); } [Fact] public void Skips_unmerged_hydration_and_directories() { var plan = GitCommitPlanner.Create( "save", [ Change("conflict.txt", GitChangeState.Unmerged, 'U', 'U'), Change("cloud.bin"), Change("src"), ], new HashSet(StringComparer.OrdinalIgnoreCase) { "cloud.bin" }, new HashSet(StringComparer.OrdinalIgnoreCase) { "src" }, operationInProgress: false); Assert.False(plan.CanCommit); Assert.Equal(["conflict.txt"], plan.SkippedUnmerged); Assert.Equal(["cloud.bin"], plan.SkippedHydration); Assert.Equal(["src"], plan.SkippedDirectories); Assert.Contains("online-only", plan.Error, StringComparison.OrdinalIgnoreCase); } [Fact] public void Dedupes_staged_and_unstaged_of_the_same_path() { var plan = GitCommitPlanner.Create( "both", [ Change("src/Both.cs", GitChangeState.Staged, 'M', '.'), Change("src/Both.cs", GitChangeState.Unstaged, '.', 'M'), ], None, None, operationInProgress: false); Assert.True(plan.CanCommit); Assert.Equal(["src/Both.cs"], plan.Paths); } [Fact] public void Commits_files_and_deleted_paths_while_skipping_the_rest() { var plan = GitCommitPlanner.Create( "mixed", [ Change("keep.cs"), Change("gone.cs", GitChangeState.Unstaged, '.', 'D'), Change("conflict.txt", GitChangeState.Unmerged, 'U', 'U'), Change("cloud.bin"), Change("src"), ], new HashSet(StringComparer.OrdinalIgnoreCase) { "cloud.bin" }, new HashSet(StringComparer.OrdinalIgnoreCase) { "src", "gone.cs" }, operationInProgress: false); Assert.True(plan.CanCommit); Assert.Equal(["keep.cs", "gone.cs"], plan.Paths); Assert.Equal(["conflict.txt"], plan.SkippedUnmerged); Assert.Equal(["cloud.bin"], plan.SkippedHydration); Assert.Equal(["src"], plan.SkippedDirectories); } [Fact] public void Requires_at_least_one_file() { var plan = GitCommitPlanner.Create("msg", [], None, None, operationInProgress: false); Assert.False(plan.CanCommit); Assert.Equal("Select at least one file to commit.", plan.Error); } private static GitChange Change( string path, GitChangeState state = GitChangeState.Unstaged, char index = '.', char workTree = 'M') => new() { Path = path, State = state, Index = index, WorkTree = workTree }; }