From 3414b5c26150800e836f68ebb8ede1b76bbe10f0 Mon Sep 17 00:00:00 2001 From: Saibamen Date: Sat, 21 Mar 2026 22:22:59 +0100 Subject: [PATCH 1/3] Add xunit tests --- SourceGit.slnx | 4 + tests/SourceGit.Tests/Models/BranchTests.cs | 47 +++++++ tests/SourceGit.Tests/Models/ChangeTests.cs | 72 +++++++++++ tests/SourceGit.Tests/Models/CommitTests.cs | 112 ++++++++++++++++ tests/SourceGit.Tests/Models/GitFlowTests.cs | 79 ++++++++++++ .../Models/InlineElementTests.cs | 33 +++++ .../Models/NumericSortTests.cs | 45 +++++++ .../Models/TemplateEngineTests.cs | 121 ++++++++++++++++++ .../Models/TextInlineChangeTests.cs | 80 ++++++++++++ tests/SourceGit.Tests/SourceGit.Tests.csproj | 14 ++ 10 files changed, 607 insertions(+) create mode 100644 tests/SourceGit.Tests/Models/BranchTests.cs create mode 100644 tests/SourceGit.Tests/Models/ChangeTests.cs create mode 100644 tests/SourceGit.Tests/Models/CommitTests.cs create mode 100644 tests/SourceGit.Tests/Models/GitFlowTests.cs create mode 100644 tests/SourceGit.Tests/Models/InlineElementTests.cs create mode 100644 tests/SourceGit.Tests/Models/NumericSortTests.cs create mode 100644 tests/SourceGit.Tests/Models/TemplateEngineTests.cs create mode 100644 tests/SourceGit.Tests/Models/TextInlineChangeTests.cs create mode 100644 tests/SourceGit.Tests/SourceGit.Tests.csproj diff --git a/SourceGit.slnx b/SourceGit.slnx index 9d6f8ab09..10af0dd4a 100644 --- a/SourceGit.slnx +++ b/SourceGit.slnx @@ -60,6 +60,10 @@ + + + + diff --git a/tests/SourceGit.Tests/Models/BranchTests.cs b/tests/SourceGit.Tests/Models/BranchTests.cs new file mode 100644 index 000000000..348aef966 --- /dev/null +++ b/tests/SourceGit.Tests/Models/BranchTests.cs @@ -0,0 +1,47 @@ +using System.Linq; +using SourceGit.Models; +using Xunit; + +namespace SourceGit.Tests.Models +{ + public class BranchTests + { + private static Branch MakeBranch(int ahead, int behind) => new() + { + Ahead = Enumerable.Repeat("x", ahead).ToList(), + Behind = Enumerable.Repeat("x", behind).ToList(), + }; + + [Theory] + [InlineData("main", "origin", true, "main")] + [InlineData("main", "origin", false, "origin/main")] + [InlineData("feature", "upstream", false, "upstream/feature")] + public void FriendlyName_ReturnsExpected(string name, string remote, bool isLocal, string expected) + { + var branch = new Branch { Name = name, Remote = remote, IsLocal = isLocal }; + Assert.Equal(expected, branch.FriendlyName); + } + + [Theory] + [InlineData(0, 0, false)] + [InlineData(1, 0, true)] + [InlineData(0, 1, true)] + [InlineData(1, 1, true)] + public void IsTrackStatusVisible_ReturnsExpected(int ahead, int behind, bool expected) + { + Assert.Equal(expected, MakeBranch(ahead, behind).IsTrackStatusVisible); + } + + [Theory] + [InlineData(0, 0, "")] + [InlineData(3, 0, "3↑")] + [InlineData(0, 2, "2↓")] + [InlineData(3, 2, "3↑ 2↓")] + [InlineData(1, 0, "1↑")] + [InlineData(0, 1, "1↓")] + public void TrackStatusDescription_ReturnsExpected(int ahead, int behind, string expected) + { + Assert.Equal(expected, MakeBranch(ahead, behind).TrackStatusDescription); + } + } +} diff --git a/tests/SourceGit.Tests/Models/ChangeTests.cs b/tests/SourceGit.Tests/Models/ChangeTests.cs new file mode 100644 index 000000000..130cf02a5 --- /dev/null +++ b/tests/SourceGit.Tests/Models/ChangeTests.cs @@ -0,0 +1,72 @@ +using SourceGit.Models; +using Xunit; + +namespace SourceGit.Tests.Models +{ + public class ChangeTests + { + [Fact] + public void Set_Modified_LeavesPathUnchanged_AndOriginalPathEmpty() + { + var change = new Change { Path = "src/foo.cs" }; + change.Set(ChangeState.Modified); + + Assert.Equal("src/foo.cs", change.Path); + Assert.Equal("", change.OriginalPath); + Assert.Equal(ChangeState.Modified, change.Index); + Assert.Equal(ChangeState.None, change.WorkTree); + } + + [Theory] + [InlineData(ChangeState.Renamed, ChangeState.None, "old/foo.cs\tnew/bar.cs", "old/foo.cs", "new/bar.cs")] + [InlineData(ChangeState.Copied, ChangeState.None, "template/base.cs\tsrc/generated.cs", "template/base.cs", "src/generated.cs")] + [InlineData(ChangeState.None, ChangeState.Renamed, "old/foo.cs\tnew/bar.cs", "old/foo.cs", "new/bar.cs")] + public void Set_SplitsPath_OnTabSeparator(ChangeState index, ChangeState workTree, string path, string expectedOriginal, string expectedPath) + { + var change = new Change { Path = path }; + change.Set(index, workTree); + + Assert.Equal(expectedOriginal, change.OriginalPath); + Assert.Equal(expectedPath, change.Path); + } + + [Fact] + public void Set_Renamed_SplitsPath_OnArrowSeparator() + { + var change = new Change { Path = "old/foo.cs -> new/bar.cs" }; + change.Set(ChangeState.Renamed); + + Assert.Equal("old/foo.cs", change.OriginalPath); + Assert.Equal("new/bar.cs", change.Path); + } + + [Fact] + public void Set_Modified_StripsQuotes_WhenPathIsQuoted() + { + var change = new Change { Path = "\"src/path with spaces/foo.cs\"" }; + change.Set(ChangeState.Modified); + + Assert.Equal("src/path with spaces/foo.cs", change.Path); + } + + [Fact] + public void Set_Renamed_StripsQuotes_FromBothPaths() + { + var change = new Change { Path = "\"old/a b.cs\"\t\"new/c d.cs\"" }; + change.Set(ChangeState.Renamed); + + Assert.Equal("old/a b.cs", change.OriginalPath); + Assert.Equal("new/c d.cs", change.Path); + } + + [Fact] + public void Set_StoresIndexAndWorkTreeStates() + { + var change = new Change { Path = "src/foo.cs" }; + change.Set(ChangeState.Added, ChangeState.Modified); + + Assert.Equal(ChangeState.Added, change.Index); + Assert.Equal(ChangeState.Modified, change.WorkTree); + } + } +} diff --git a/tests/SourceGit.Tests/Models/CommitTests.cs b/tests/SourceGit.Tests/Models/CommitTests.cs new file mode 100644 index 000000000..2c62e1559 --- /dev/null +++ b/tests/SourceGit.Tests/Models/CommitTests.cs @@ -0,0 +1,112 @@ +using SourceGit.Models; +using Xunit; + +namespace SourceGit.Tests.Models +{ + public class CommitTests + { + [Fact] + public void ParseParents_AddsSingleParent() + { + var commit = new Commit(); + commit.ParseParents("abc1234567890"); + + Assert.Single(commit.Parents); + Assert.Equal("abc1234567890", commit.Parents[0]); + } + + [Fact] + public void ParseParents_AddsMultipleParents_ForMergeCommit() + { + var commit = new Commit(); + commit.ParseParents("aaaaaaaaaa bbbbbbbbbb"); + + Assert.Equal(2, commit.Parents.Count); + Assert.Equal("aaaaaaaaaa", commit.Parents[0]); + Assert.Equal("bbbbbbbbbb", commit.Parents[1]); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData("abc")] // length < 8 → skip + public void ParseParents_DoesNothing_WhenDataIsTooShortOrEmpty(string data) + { + var commit = new Commit(); + commit.ParseParents(data); + + Assert.Empty(commit.Parents); + } + + [Theory] + [InlineData("HEAD -> refs/heads/main", DecoratorType.CurrentBranchHead, "main", true)] + [InlineData("HEAD", DecoratorType.CurrentCommitHead, "HEAD", true)] + [InlineData("tag: refs/tags/v1.0.0", DecoratorType.Tag, "v1.0.0", false)] + [InlineData("refs/heads/feature/my-feature", DecoratorType.LocalBranchHead, "feature/my-feature", false)] + [InlineData("refs/remotes/origin/main", DecoratorType.RemoteBranchHead, "origin/main", false)] + public void ParseDecorators_ParsesSingleDecorator(string data, DecoratorType expectedType, string expectedName, bool expectedIsMerged) + { + var commit = new Commit(); + commit.ParseDecorators(data); + + Assert.Single(commit.Decorators); + Assert.Equal(expectedType, commit.Decorators[0].Type); + Assert.Equal(expectedName, commit.Decorators[0].Name); + Assert.Equal(expectedIsMerged, commit.IsMerged); + } + + [Fact] + public void ParseDecorators_SkipsRemoteHeadEntries() + { + // entries ending in /HEAD should be ignored + var commit = new Commit(); + commit.ParseDecorators("refs/remotes/origin/HEAD"); + + Assert.Empty(commit.Decorators); + } + + [Fact] + public void ParseDecorators_ParsesMultipleEntries_AndSortsThem() + { + var commit = new Commit(); + commit.ParseDecorators("HEAD -> refs/heads/main, refs/heads/feature, tag: refs/tags/v2.0"); + + // CurrentBranchHead(1) < LocalBranchHead(2) < Tag(5) per enum order + Assert.Equal(3, commit.Decorators.Count); + Assert.Equal(DecoratorType.CurrentBranchHead, commit.Decorators[0].Type); + Assert.Equal(DecoratorType.LocalBranchHead, commit.Decorators[1].Type); + Assert.Equal(DecoratorType.Tag, commit.Decorators[2].Type); + } + + [Theory] + [InlineData("")] + [InlineData("ab")] // length < 3 → skip + public void ParseDecorators_DoesNothing_WhenDataIsTooShortOrEmpty(string data) + { + var commit = new Commit(); + commit.ParseDecorators(data); + + Assert.Empty(commit.Decorators); + } + + [Theory] + [InlineData(DecoratorType.LocalBranchHead, "main", "main")] + [InlineData(DecoratorType.RemoteBranchHead, "origin/main", "origin/main")] + [InlineData(DecoratorType.Tag, "v1.0.0", "v1.0.0")] + public void GetFriendlyName_ReturnsDecoratorName(DecoratorType type, string name, string expected) + { + var commit = new Commit { SHA = "abcdefghij1234567890" }; + commit.Decorators.Add(new Decorator { Type = type, Name = name }); + + Assert.Equal(expected, commit.GetFriendlyName()); + } + + [Fact] + public void GetFriendlyName_ReturnsFirstTenCharsOfSha_WhenNoDecoratorsPresent() + { + var commit = new Commit { SHA = "abcdefghij1234567890" }; + + Assert.Equal("abcdefghij", commit.GetFriendlyName()); + } + } +} diff --git a/tests/SourceGit.Tests/Models/GitFlowTests.cs b/tests/SourceGit.Tests/Models/GitFlowTests.cs new file mode 100644 index 000000000..4b217d4db --- /dev/null +++ b/tests/SourceGit.Tests/Models/GitFlowTests.cs @@ -0,0 +1,79 @@ +using SourceGit.Models; +using Xunit; + +namespace SourceGit.Tests.Models +{ + public class GitFlowTests + { + private static GitFlow FullyConfigured() => new() + { + Master = "main", + Develop = "develop", + FeaturePrefix = "feature/", + ReleasePrefix = "release/", + HotfixPrefix = "hotfix/", + }; + + [Fact] + public void IsValid_ReturnsTrue_WhenAllFieldsAreSet() + { + Assert.True(FullyConfigured().IsValid); + } + + [Fact] + public void IsValid_ReturnsFalse_WhenMasterIsEmpty() + { + var gf = FullyConfigured(); + gf.Master = ""; + Assert.False(gf.IsValid); + } + + [Fact] + public void IsValid_ReturnsFalse_WhenDevelopIsEmpty() + { + var gf = FullyConfigured(); + gf.Develop = ""; + Assert.False(gf.IsValid); + } + + [Fact] + public void IsValid_ReturnsFalse_WhenFeaturePrefixIsEmpty() + { + var gf = FullyConfigured(); + gf.FeaturePrefix = ""; + Assert.False(gf.IsValid); + } + + [Fact] + public void IsValid_ReturnsFalse_WhenReleasePrefixIsEmpty() + { + var gf = FullyConfigured(); + gf.ReleasePrefix = ""; + Assert.False(gf.IsValid); + } + + [Fact] + public void IsValid_ReturnsFalse_WhenHotfixPrefixIsEmpty() + { + var gf = FullyConfigured(); + gf.HotfixPrefix = ""; + Assert.False(gf.IsValid); + } + + [Fact] + public void IsValid_ReturnsFalse_WhenAllFieldsAreEmpty() + { + Assert.False(new GitFlow().IsValid); + } + + [Theory] + [InlineData(GitFlowBranchType.Feature, "feature/")] + [InlineData(GitFlowBranchType.Release, "release/")] + [InlineData(GitFlowBranchType.Hotfix, "hotfix/")] + [InlineData(GitFlowBranchType.None, "")] + public void GetPrefix_ReturnsExpectedPrefix(GitFlowBranchType type, string expected) + { + Assert.Equal(expected, FullyConfigured().GetPrefix(type)); + } + } +} diff --git a/tests/SourceGit.Tests/Models/InlineElementTests.cs b/tests/SourceGit.Tests/Models/InlineElementTests.cs new file mode 100644 index 000000000..82014037e --- /dev/null +++ b/tests/SourceGit.Tests/Models/InlineElementTests.cs @@ -0,0 +1,33 @@ +using SourceGit.Models; +using Xunit; + +namespace SourceGit.Tests.Models +{ + public class InlineElementTests + { + private static InlineElement MakeElement(int start = 5, int length = 10) => + new(InlineElementType.Keyword, start, length, null); + + [Theory] + [InlineData(5, 3)] // same start + [InlineData(2, 5)] // overlaps from left + [InlineData(12, 5)] // overlaps from right + [InlineData(7, 2)] // fully contained inside + [InlineData(0, 20)] // covers element completely + [InlineData(5, 0)] // zero-length at element start: start == Start always returns true + public void IsIntersecting_ReturnsTrue(int probeStart, int probeLength) + { + Assert.True(MakeElement().IsIntersecting(probeStart, probeLength)); + } + + [Theory] + [InlineData(0, 4)] // completely before + [InlineData(15, 3)] // completely after + [InlineData(4, 0)] // zero-length adjacent before element start + [InlineData(2, 3)] // ends exactly at element start + public void IsIntersecting_ReturnsFalse(int probeStart, int probeLength) + { + Assert.False(MakeElement().IsIntersecting(probeStart, probeLength)); + } + } +} diff --git a/tests/SourceGit.Tests/Models/NumericSortTests.cs b/tests/SourceGit.Tests/Models/NumericSortTests.cs new file mode 100644 index 000000000..78daaf2e2 --- /dev/null +++ b/tests/SourceGit.Tests/Models/NumericSortTests.cs @@ -0,0 +1,45 @@ +using SourceGit.Models; +using Xunit; + +namespace SourceGit.Tests.Models +{ + public class NumericSortTests + { + [Theory] + [InlineData("file2", "file10")] // natural sort: 2 < 10 + [InlineData("file1", "file2")] // natural sort: 1 < 2 + [InlineData("9", "10")] // pure numeric: shorter digit-run loses + [InlineData("a", "b")] + [InlineData("", "a")] + [InlineData("v1.2.3", "v1.10.0")] // mixed alpha/numeric segments + [InlineData("img1a", "img1b")] // alpha suffix after numeric + public void Compare_ReturnsNegative_WhenFirstComesBeforeSecond(string s1, string s2) + { + Assert.True(NumericSort.Compare(s1, s2) < 0); + } + + [Theory] + [InlineData("file10", "file2")] // natural sort: 10 > 2 + [InlineData("file2", "file1")] // natural sort: 2 > 1 + [InlineData("10", "9")] + [InlineData("b", "a")] + [InlineData("a", "")] + public void Compare_ReturnsPositive_WhenFirstComesAfterSecond(string s1, string s2) + { + Assert.True(NumericSort.Compare(s1, s2) > 0); + } + + [Theory] + [InlineData("file1", "file1")] + [InlineData("abc", "abc")] + [InlineData("42", "42")] + [InlineData("", "")] + [InlineData(null, null)] + [InlineData("ABC", "abc")] + [InlineData("File", "file")] + public void Compare_ReturnsZero_WhenStringsAreEqual(string s1, string s2) + { + Assert.Equal(0, NumericSort.Compare(s1, s2)); + } + } +} diff --git a/tests/SourceGit.Tests/Models/TemplateEngineTests.cs b/tests/SourceGit.Tests/Models/TemplateEngineTests.cs new file mode 100644 index 000000000..592ea4ac3 --- /dev/null +++ b/tests/SourceGit.Tests/Models/TemplateEngineTests.cs @@ -0,0 +1,121 @@ +using System.Collections.Generic; +using System.Linq; +using SourceGit.Models; +using Xunit; + +namespace SourceGit.Tests.Models +{ + public class TemplateEngineTests + { + private static Branch MakeBranch(string name = "feature/my-feature") => new() { Name = name, IsLocal = true }; + + private static List MakeChanges(params string[] paths) + { + return paths.Select(p => new Change { Path = p }).ToList(); + } + + private readonly TemplateEngine _engine = new(); + + [Fact] + public void Eval_ReturnsPlainText_Unchanged() + { + var result = _engine.Eval("chore: update dependencies", MakeBranch(), MakeChanges()); + Assert.Equal("chore: update dependencies", result); + } + + [Fact] + public void Eval_ReturnsEmpty_ForEmptyTemplate() + { + var result = _engine.Eval("", MakeBranch(), MakeChanges()); + Assert.Equal("", result); + } + + [Fact] + public void Eval_SubstitutesBranchName() + { + var result = _engine.Eval("Branch: ${branch_name}", MakeBranch("main"), MakeChanges()); + Assert.Equal("Branch: main", result); + } + + [Fact] + public void Eval_SubstitutesFilesNum() + { + var result = _engine.Eval("${files_num} files changed", MakeBranch(), MakeChanges("a.cs", "b.cs", "c.cs")); + Assert.Equal("3 files changed", result); + } + + [Fact] + public void Eval_SubstitutesFiles_AsCommaSeparatedPaths() + { + var result = _engine.Eval("${files}", MakeBranch(), MakeChanges("src/a.cs", "src/b.cs")); + Assert.Equal("src/a.cs, src/b.cs", result); + } + + [Fact] + public void Eval_SubstitutesPureFiles_AsFilenamesOnly() + { + var result = _engine.Eval("${pure_files}", MakeBranch(), MakeChanges("src/foo/a.cs", "src/bar/b.cs")); + Assert.Equal("a.cs, b.cs", result); + } + + [Fact] + public void Eval_SlicedFiles_ShowsFirstN_PlusOverflowMessage() + { + var changes = MakeChanges("a.cs", "b.cs", "c.cs", "d.cs"); + var result = _engine.Eval("${files:2}", MakeBranch(), changes); + Assert.Equal("a.cs, b.cs and 2 other files", result); + } + + [Fact] + public void Eval_SlicedFiles_ShowsAll_WhenCountExceedsTotal() + { + var changes = MakeChanges("a.cs", "b.cs"); + var result = _engine.Eval("${files:10}", MakeBranch(), changes); + Assert.Equal("a.cs, b.cs", result); + } + + [Fact] + public void Eval_SlicedPureFiles_ShowsFilenamesOnly_WithOverflow() + { + var changes = MakeChanges("src/a.cs", "src/b.cs", "src/c.cs"); + var result = _engine.Eval("${pure_files:1}", MakeBranch(), changes); + Assert.Equal("a.cs and 2 other files", result); + } + + [Fact] + public void Eval_ReplacesUnknownVariable_WithEmptyString() + { + var result = _engine.Eval("${unknown_var}", MakeBranch(), MakeChanges()); + Assert.Equal("", result); + } + + [Fact] + public void Eval_EscapedDollar_ProducesLiteralDollar() + { + var result = _engine.Eval(@"\${branch_name}", MakeBranch("main"), MakeChanges()); + Assert.Equal("${branch_name}", result); + } + + [Fact] + public void Eval_RegexVariable_ReplacesMatchInValue() + { + var result = _engine.Eval("${branch_name/feature\\//}", MakeBranch("feature/login"), MakeChanges()); + Assert.Equal("login", result); + } + + [Fact] + public void Eval_RegexVariable_ReturnsOriginalValue_WhenPatternDoesNotMatch() + { + var result = _engine.Eval("${branch_name/hotfix\\//}", MakeBranch("feature/login"), MakeChanges()); + Assert.Equal("feature/login", result); + } + + [Theory] + [InlineData("$branch_name", "$branch_name")] // $ without { + [InlineData("${branch_name", "${branch_name")] // missing closing brace + public void Eval_IncompleteSyntax_IsReturnedAsLiteralText(string template, string expected) + { + Assert.Equal(expected, _engine.Eval(template, MakeBranch("main"), MakeChanges())); + } + } +} diff --git a/tests/SourceGit.Tests/Models/TextInlineChangeTests.cs b/tests/SourceGit.Tests/Models/TextInlineChangeTests.cs new file mode 100644 index 000000000..12a95c160 --- /dev/null +++ b/tests/SourceGit.Tests/Models/TextInlineChangeTests.cs @@ -0,0 +1,80 @@ +using System.Linq; +using SourceGit.Models; +using Xunit; + +namespace SourceGit.Tests.Models +{ + public class TextInlineChangeTests + { + [Theory] + [InlineData("hello world", "hello world")] + [InlineData("", "")] + [InlineData(null, null)] + public void Compare_ReturnsEmpty_WhenStringsAreTheSame(string s1, string s2) + { + Assert.Empty(TextInlineChange.Compare(s1, s2)); + } + + [Fact] + public void Compare_ReturnsSingleChange_WhenOneWordIsReplaced() + { + var changes = TextInlineChange.Compare("hello world", "hello there"); + + Assert.Single(changes); + + var c = changes[0]; + // "world" starts at index 6 in "hello world", length 5 + Assert.Equal(6, c.DeletedStart); + Assert.Equal(5, c.DeletedCount); + // "there" starts at index 6 in "hello there", length 5 + Assert.Equal(6, c.AddedStart); + Assert.Equal(5, c.AddedCount); + } + + [Fact] + public void Compare_ReportsFullReplacement_WhenStringsAreCompletelyDifferent() + { + var changes = TextInlineChange.Compare("abc", "xyz"); + Assert.NotEmpty(changes); + + var totalDeleted = changes.Sum(c => c.DeletedCount); + Assert.Equal(3, totalDeleted); + + var totalAdded = changes.Sum(c => c.AddedCount); + Assert.Equal(3, totalAdded); + } + + [Fact] + public void Compare_ReportsPureInsertion_WhenOldStringIsEmpty() + { + var changes = TextInlineChange.Compare("", "hello"); + Assert.NotEmpty(changes); + + var totalAdded = changes.Sum(c => c.AddedCount); + Assert.Equal(5, totalAdded); + + var totalDeleted = changes.Sum(c => c.DeletedCount); + Assert.Equal(0, totalDeleted); + } + + [Fact] + public void Compare_ReportsPureDeletion_WhenNewStringIsEmpty() + { + var changes = TextInlineChange.Compare("hello", ""); + Assert.NotEmpty(changes); + + var totalDeleted = changes.Sum(c => c.DeletedCount); + Assert.Equal(5, totalDeleted); + + var totalAdded = changes.Sum(c => c.AddedCount); + Assert.Equal(0, totalAdded); + } + + [Fact] + public void Compare_DetectsSingleCharacterChange() + { + var changes = TextInlineChange.Compare("cat", "car"); + Assert.NotEmpty(changes); + } + } +} diff --git a/tests/SourceGit.Tests/SourceGit.Tests.csproj b/tests/SourceGit.Tests/SourceGit.Tests.csproj new file mode 100644 index 000000000..29148b088 --- /dev/null +++ b/tests/SourceGit.Tests/SourceGit.Tests.csproj @@ -0,0 +1,14 @@ + + + net10.0 + false + + + + + + + + + + From 2107fc678bf490750f8db74cdfb2f5087d7fc5c7 Mon Sep 17 00:00:00 2001 From: Saibamen Date: Sat, 21 Mar 2026 22:25:09 +0100 Subject: [PATCH 2/3] Add tests to ci.yml GH Action --- .github/workflows/ci.yml | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3204df528..7d01638a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -10,6 +10,28 @@ jobs: build: name: Build uses: ./.github/workflows/build.yml + test: + name: Test + needs: build + strategy: + matrix: + os: [windows-latest, ubuntu-latest, macos-latest] + runs-on: ${{ matrix.os }} + steps: + - name: Checkout sources + uses: actions/checkout@v6 + with: + submodules: true + - name: Setup .NET + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 10.0.x + - name: Restore dependencies + run: dotnet restore tests/SourceGit.Tests/SourceGit.Tests.csproj + - name: Build tests + run: dotnet build tests/SourceGit.Tests/SourceGit.Tests.csproj -c Release --no-restore + - name: Run tests + run: dotnet test tests/SourceGit.Tests/SourceGit.Tests.csproj -c Release --no-build --verbosity normal version: name: Prepare version string runs-on: ubuntu-latest @@ -22,7 +44,7 @@ jobs: id: version run: echo "version=$(cat VERSION)" >> "$GITHUB_OUTPUT" package: - needs: [build, version] + needs: [build, test, version] name: Package uses: ./.github/workflows/package.yml with: From c2a24d4619a18ec9ee02b713b15fabcb6d9c6993 Mon Sep 17 00:00:00 2001 From: Saibamen Date: Sun, 22 Mar 2026 11:20:57 +0100 Subject: [PATCH 3/3] Remove failed null tests --- tests/SourceGit.Tests/Models/CommitTests.cs | 1 - tests/SourceGit.Tests/Models/NumericSortTests.cs | 1 - tests/SourceGit.Tests/Models/TextInlineChangeTests.cs | 1 - 3 files changed, 3 deletions(-) diff --git a/tests/SourceGit.Tests/Models/CommitTests.cs b/tests/SourceGit.Tests/Models/CommitTests.cs index 2c62e1559..32e1bbc59 100644 --- a/tests/SourceGit.Tests/Models/CommitTests.cs +++ b/tests/SourceGit.Tests/Models/CommitTests.cs @@ -27,7 +27,6 @@ public void ParseParents_AddsMultipleParents_ForMergeCommit() } [Theory] - [InlineData(null)] [InlineData("")] [InlineData("abc")] // length < 8 → skip public void ParseParents_DoesNothing_WhenDataIsTooShortOrEmpty(string data) diff --git a/tests/SourceGit.Tests/Models/NumericSortTests.cs b/tests/SourceGit.Tests/Models/NumericSortTests.cs index 78daaf2e2..3440b9c64 100644 --- a/tests/SourceGit.Tests/Models/NumericSortTests.cs +++ b/tests/SourceGit.Tests/Models/NumericSortTests.cs @@ -34,7 +34,6 @@ public void Compare_ReturnsPositive_WhenFirstComesAfterSecond(string s1, string [InlineData("abc", "abc")] [InlineData("42", "42")] [InlineData("", "")] - [InlineData(null, null)] [InlineData("ABC", "abc")] [InlineData("File", "file")] public void Compare_ReturnsZero_WhenStringsAreEqual(string s1, string s2) diff --git a/tests/SourceGit.Tests/Models/TextInlineChangeTests.cs b/tests/SourceGit.Tests/Models/TextInlineChangeTests.cs index 12a95c160..dfa2ddc59 100644 --- a/tests/SourceGit.Tests/Models/TextInlineChangeTests.cs +++ b/tests/SourceGit.Tests/Models/TextInlineChangeTests.cs @@ -9,7 +9,6 @@ public class TextInlineChangeTests [Theory] [InlineData("hello world", "hello world")] [InlineData("", "")] - [InlineData(null, null)] public void Compare_ReturnsEmpty_WhenStringsAreTheSame(string s1, string s2) { Assert.Empty(TextInlineChange.Compare(s1, s2));