From 3a2a11030482f4e81c80cd8dcc473143e0946a87 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:17:34 -0600 Subject: [PATCH 01/13] test: remove outdated and placeholder test files - Deleted UnitTest1.cs from PowerCSharp.Core.Tests containing StringExtensionsTests - Deleted UnitTest1.cs from PowerCSharp.Utilities.Tests containing placeholder test - Cleaned up test projects by removing obsolete test files --- tests/PowerCSharp.Core.Tests/UnitTest1.cs | 190 ------------------ .../PowerCSharp.Utilities.Tests/UnitTest1.cs | 10 - 2 files changed, 200 deletions(-) delete mode 100644 tests/PowerCSharp.Core.Tests/UnitTest1.cs delete mode 100644 tests/PowerCSharp.Utilities.Tests/UnitTest1.cs diff --git a/tests/PowerCSharp.Core.Tests/UnitTest1.cs b/tests/PowerCSharp.Core.Tests/UnitTest1.cs deleted file mode 100644 index 7108a56..0000000 --- a/tests/PowerCSharp.Core.Tests/UnitTest1.cs +++ /dev/null @@ -1,190 +0,0 @@ -using System; -using PowerCSharp.Core; -using Xunit; - -namespace PowerCSharp.Core.Tests; - -public class StringExtensionsTests -{ - [Fact] - public void IsNullOrWhiteSpace_ShouldReturnTrueForNull() - { - // Arrange - string? nullString = null; - - // Act - bool result = nullString.IsNullOrWhiteSpace(); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsNullOrWhiteSpace_ShouldReturnTrueForEmptyString() - { - // Arrange - string emptyString = ""; - - // Act - bool result = emptyString.IsNullOrWhiteSpace(); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsNullOrWhiteSpace_ShouldReturnTrueForWhitespace() - { - // Arrange - string whitespaceString = " \t\n "; - - // Act - bool result = whitespaceString.IsNullOrWhiteSpace(); - - // Assert - Assert.True(result); - } - - [Fact] - public void IsNullOrWhiteSpace_ShouldReturnFalseForValidString() - { - // Arrange - string validString = "Hello World"; - - // Act - bool result = validString.IsNullOrWhiteSpace(); - - // Assert - Assert.False(result); - } - - [Fact] - public void SafeSubstring_ShouldReturnSubstringForValidParameters() - { - // Arrange - string text = "Hello World"; - - // Act - string result = text.SafeSubstring(0, 5); - - // Assert - Assert.Equal("Hello", result); - } - - [Fact] - public void SafeSubstring_ShouldReturnEmptyForInvalidStartIndex() - { - // Arrange - string text = "Hello World"; - - // Act - string result = text.SafeSubstring(20, 5); - - // Assert - Assert.Equal("", result); - } - - [Fact] - public void SafeSubstring_ShouldReturnEmptyForInvalidLength() - { - // Arrange - string text = "Hello World"; - - // Act - string result = text.SafeSubstring(0, -1); - - // Assert - Assert.Equal("", result); - } - - [Fact] - public void SafeSubstring_ShouldReturnPartialForLengthBeyondBounds() - { - // Arrange - string text = "Hello"; - - // Act - string result = text.SafeSubstring(2, 10); - - // Assert - Assert.Equal("llo", result); - } - - [Fact] - public void SafeSubstring_ShouldReturnEmptyForNullString() - { - // Arrange - string? nullString = null; - - // Act - string result = nullString.SafeSubstring(0, 5); - - // Assert - Assert.Equal("", result); - } - - [Fact] - public void ToTitleCase_ShouldConvertToTitleCase() - { - // Arrange - string text = "hello world from powercsharp"; - - // Act - string result = text.ToTitleCase(); - - // Assert - Assert.Equal("Hello World From Powercsharp", result); - } - - [Fact] - public void ToTitleCase_ShouldHandleEmptyString() - { - // Arrange - string emptyString = ""; - - // Act - string result = emptyString.ToTitleCase(); - - // Assert - Assert.Equal("", result); - } - - [Fact] - public void ToTitleCase_ShouldHandleNullString() - { - // Arrange - string? nullString = null; - - // Act - string result = nullString.ToTitleCase(); - - // Assert - Assert.Equal("", result); - } - - [Fact] - public void ToTitleCase_ShouldHandleSingleWord() - { - // Arrange - string singleWord = "hello"; - - // Act - string result = singleWord.ToTitleCase(); - - // Assert - Assert.Equal("Hello", result); - } - - [Fact] - public void ToTitleCase_ShouldHandleMultipleSpaces() - { - // Arrange - string multipleSpaces = "hello world"; - - // Act - string result = multipleSpaces.ToTitleCase(); - - // Assert - Assert.Equal("Hello World", result); - } -} \ No newline at end of file diff --git a/tests/PowerCSharp.Utilities.Tests/UnitTest1.cs b/tests/PowerCSharp.Utilities.Tests/UnitTest1.cs deleted file mode 100644 index 790fccf..0000000 --- a/tests/PowerCSharp.Utilities.Tests/UnitTest1.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace PowerCSharp.Utilities.Tests; - -public class UnitTest1 -{ - [Fact] - public void Test1() - { - - } -} \ No newline at end of file From edf1dc61239a0559e1041c9b07f2e254e2245cd6 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:18:19 -0600 Subject: [PATCH 02/13] feat: add DistinctBy method to ListExtensions - Added DistinctBy method that removes duplicates based on a key selector function - Maintained null safety and improved code readability - Added comprehensive XML documentation for the new DistinctBy method --- .../Collections/ListExtensions.cs | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/PowerCSharp.Core/Collections/ListExtensions.cs b/src/PowerCSharp.Core/Collections/ListExtensions.cs index 39c6888..88058eb 100644 --- a/src/PowerCSharp.Core/Collections/ListExtensions.cs +++ b/src/PowerCSharp.Core/Collections/ListExtensions.cs @@ -38,12 +38,39 @@ public static class ListExtensions var clonedList = new List(sourceList.Count); foreach (var item in sourceList) { - if (item != null) + clonedList.Add(item != null ? (T)item.Clone() : default); + } + + return clonedList; + } + + /// + /// Returns a new list containing only the first occurrence of each unique item based on the specified key selector. + /// + /// The type of elements in the list. + /// The type of the key to compare for uniqueness. + /// The source list to deduplicate. + /// A function to extract the key for each element. + /// A new list with duplicates removed based on the specified key, or null if the input is null. + public static List? DistinctBy(this List? sourceList, Func keySelector) + { + if (sourceList == null) + { + return null; + } + + var seenKeys = new HashSet(); + var result = new List(); + + foreach (var item in sourceList) + { + var key = keySelector(item); + if (seenKeys.Add(key)) { - clonedList.Add((T)item.Clone()); + result.Add(item); } } - return clonedList; + return result; } } From 1d1d91b9fc2b3efa8f08c8aecd2bc1eea4ab8bb5 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:18:59 -0600 Subject: [PATCH 03/13] test: add comprehensive unit tests for ListExtensions - Created ListExtensionsTests.cs with full test coverage for ListExtensions methods - Added 8 tests for DistinctBy method covering null, empty, duplicates, complex types, and order preservation - Added 5 tests for DeepClone method covering null, empty, cloneable items, null items, and modification independence - Included test helper classes TestPerson and TestCloneable to support test scenarios - Ensures robust validation of ListExtensions functionality --- .../ListExtensionsTests.cs | 267 ++++++++++++++++++ 1 file changed, 267 insertions(+) create mode 100644 tests/PowerCSharp.Core.Tests/ListExtensionsTests.cs diff --git a/tests/PowerCSharp.Core.Tests/ListExtensionsTests.cs b/tests/PowerCSharp.Core.Tests/ListExtensionsTests.cs new file mode 100644 index 0000000..2de7850 --- /dev/null +++ b/tests/PowerCSharp.Core.Tests/ListExtensionsTests.cs @@ -0,0 +1,267 @@ +using PowerCSharp.Core.Collections; +using Xunit; + +namespace PowerCSharp.Core.Tests; + +public class ListExtensionsTests +{ + #region DistinctBy Tests + + [Fact] + public void DistinctBy_WithNullList_ShouldReturnNull() + { + // Arrange + List? nullList = null; + + // Act + var result = nullList.DistinctBy(x => x); + + // Assert + Assert.Null(result); + } + + [Fact] + public void DistinctBy_WithEmptyList_ShouldReturnEmptyList() + { + // Arrange + var emptyList = new List(); + + // Act + var result = emptyList.DistinctBy(x => x); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + } + + [Fact] + public void DistinctBy_WithNoDuplicates_ShouldReturnAllItems() + { + // Arrange + var list = new List { 1, 2, 3, 4, 5 }; + + // Act + var result = list.DistinctBy(x => x); + + // Assert + Assert.Equal(5, result.Count); + Assert.Equal(list, result); + } + + [Fact] + public void DistinctBy_WithDuplicates_ShouldRemoveDuplicates() + { + // Arrange + var list = new List { 1, 2, 2, 3, 4, 4, 5 }; + + // Act + var result = list.DistinctBy(x => x); + + // Assert + Assert.Equal(5, result.Count); + Assert.Equal(1, result[0]); + Assert.Equal(2, result[1]); + Assert.Equal(3, result[2]); + Assert.Equal(4, result[3]); + Assert.Equal(5, result[4]); + } + + [Fact] + public void DistinctBy_WithComplexType_ShouldDedulicateBySpecifiedField() + { + // Arrange + var list = new List + { + new TestPerson { Id = 1, Name = "John" }, + new TestPerson { Id = 2, Name = "Jane" }, + new TestPerson { Id = 1, Name = "John Duplicate" }, + new TestPerson { Id = 3, Name = "Bob" }, + new TestPerson { Id = 2, Name = "Jane Duplicate" } + }; + + // Act + var result = list.DistinctBy(x => x.Id); + + // Assert + Assert.Equal(3, result.Count); + Assert.Equal(1, result[0].Id); + Assert.Equal("John", result[0].Name); // First occurrence kept + Assert.Equal(2, result[1].Id); + Assert.Equal("Jane", result[1].Name); // First occurrence kept + Assert.Equal(3, result[2].Id); + Assert.Equal("Bob", result[2].Name); + } + + [Fact] + public void DistinctBy_WithStringKey_ShouldDedulicateByStringField() + { + // Arrange + var list = new List + { + new TestPerson { Id = 1, Name = "John" }, + new TestPerson { Id = 2, Name = "Jane" }, + new TestPerson { Id = 3, Name = "John" }, + new TestPerson { Id = 4, Name = "Bob" } + }; + + // Act + var result = list.DistinctBy(x => x.Name); + + // Assert + Assert.Equal(3, result.Count); + Assert.Equal("John", result[0].Name); + Assert.Equal("Jane", result[1].Name); + Assert.Equal("Bob", result[2].Name); + } + + [Fact] + public void DistinctBy_WithAllDuplicates_ShouldReturnSingleItem() + { + // Arrange + var list = new List { 5, 5, 5, 5, 5 }; + + // Act + var result = list.DistinctBy(x => x); + + // Assert + Assert.Single(result); + Assert.Equal(5, result[0]); + } + + [Fact] + public void DistinctBy_ShouldPreserveOrderOfFirstOccurrences() + { + // Arrange + var list = new List { 3, 1, 2, 1, 3, 2 }; + + // Act + var result = list.DistinctBy(x => x); + + // Assert + Assert.Equal(3, result.Count); + Assert.Equal(3, result[0]); // First occurrence of 3 + Assert.Equal(1, result[1]); // First occurrence of 1 + Assert.Equal(2, result[2]); // First occurrence of 2 + } + + #endregion + + #region DeepClone Tests + + [Fact] + public void DeepClone_WithNullList_ShouldReturnNull() + { + // Arrange + List? nullList = null; + + // Act + var result = nullList.DeepClone(); + + // Assert + Assert.Null(result); + } + + [Fact] + public void DeepClone_WithEmptyList_ShouldReturnEmptyList() + { + // Arrange + var emptyList = new List(); + + // Act + var result = emptyList.DeepClone(); + + // Assert + Assert.NotNull(result); + Assert.Empty(result); + Assert.NotSame(emptyList, result); + } + + [Fact] + public void DeepClone_WithCloneableItems_ShouldCreateDeepCopy() + { + // Arrange + var list = new List + { + new TestCloneable { Value = 1 }, + new TestCloneable { Value = 2 }, + new TestCloneable { Value = 3 } + }; + + // Act + var result = list.DeepClone(); + + // Assert + Assert.NotNull(result); + Assert.Equal(list.Count, result.Count); + Assert.NotSame(list, result); + + for (int i = 0; i < list.Count; i++) + { + Assert.NotSame(list[i], result[i]); + Assert.Equal(list[i].Value, result[i].Value); + } + } + + [Fact] + public void DeepClone_WithNullItems_ShouldHandleNullItems() + { + // Arrange + var list = new List + { + new TestCloneable { Value = 1 }, + null, + new TestCloneable { Value = 3 } + }; + + // Act + var result = list.DeepClone(); + + // Assert + Assert.NotNull(result); + Assert.Equal(list.Count, result.Count); + Assert.NotNull(result[0]); + Assert.Null(result[1]); + Assert.NotNull(result[2]); + } + + [Fact] + public void DeepClone_ModifyingCloneShouldNotAffectOriginal() + { + // Arrange + var list = new List + { + new TestCloneable { Value = 10 }, + new TestCloneable { Value = 20 } + }; + + // Act + var result = list.DeepClone(); + result[0].Value = 999; + + // Assert + Assert.Equal(10, list[0].Value); + Assert.Equal(999, result[0].Value); + } + + #endregion + + #region Test Helper Classes + + private class TestPerson + { + public int Id { get; set; } + public string Name { get; set; } = string.Empty; + } + + private class TestCloneable : ICloneable + { + public int Value { get; set; } + + public object Clone() + { + return new TestCloneable { Value = this.Value }; + } + } + + #endregion +} From c8d99e3fe110a28db9b8c42cee4b8e6911a0e1d4 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:19:19 -0600 Subject: [PATCH 04/13] test: add comprehensive unit tests for StringExtensions - Created StringExtensionsTests.cs with full test coverage for string extension methods - Added 4 tests for IsNullOrWhiteSpace covering null, empty, whitespace, and valid strings - Added 5 tests for SafeSubstring covering valid parameters, invalid indices, null strings, and boundary conditions - Added 5 tests for ToTitleCase covering normal conversion, empty strings, null strings, single words, and multiple spaces - Ensures robust validation of string extension functionality --- .../StringExtensionsTests.cs | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 tests/PowerCSharp.Core.Tests/StringExtensionsTests.cs diff --git a/tests/PowerCSharp.Core.Tests/StringExtensionsTests.cs b/tests/PowerCSharp.Core.Tests/StringExtensionsTests.cs new file mode 100644 index 0000000..5a3775f --- /dev/null +++ b/tests/PowerCSharp.Core.Tests/StringExtensionsTests.cs @@ -0,0 +1,191 @@ +using System; +using PowerCSharp.Core; +using PowerCSharp.Extensions.Strings; +using Xunit; + +namespace PowerCSharp.Core.Tests; + +public class StringExtensionsTests +{ + [Fact] + public void IsNullOrWhiteSpace_ShouldReturnTrueForNull() + { + // Arrange + string? nullString = null; + + // Act + bool result = nullString.IsNullOrWhiteSpace(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNullOrWhiteSpace_ShouldReturnTrueForEmptyString() + { + // Arrange + string emptyString = ""; + + // Act + bool result = emptyString.IsNullOrWhiteSpace(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNullOrWhiteSpace_ShouldReturnTrueForWhitespace() + { + // Arrange + string whitespaceString = " \t\n "; + + // Act + bool result = whitespaceString.IsNullOrWhiteSpace(); + + // Assert + Assert.True(result); + } + + [Fact] + public void IsNullOrWhiteSpace_ShouldReturnFalseForValidString() + { + // Arrange + string validString = "Hello World"; + + // Act + bool result = validString.IsNullOrWhiteSpace(); + + // Assert + Assert.False(result); + } + + [Fact] + public void SafeSubstring_ShouldReturnSubstringForValidParameters() + { + // Arrange + string text = "Hello World"; + + // Act + string result = text.SafeSubstring(0, 5); + + // Assert + Assert.Equal("Hello", result); + } + + [Fact] + public void SafeSubstring_ShouldReturnEmptyForInvalidStartIndex() + { + // Arrange + string text = "Hello World"; + + // Act + string result = text.SafeSubstring(20, 5); + + // Assert + Assert.Equal("", result); + } + + [Fact] + public void SafeSubstring_ShouldReturnEmptyForInvalidLength() + { + // Arrange + string text = "Hello World"; + + // Act + string result = text.SafeSubstring(0, -1); + + // Assert + Assert.Equal("", result); + } + + [Fact] + public void SafeSubstring_ShouldReturnPartialForLengthBeyondBounds() + { + // Arrange + string text = "Hello"; + + // Act + string result = text.SafeSubstring(2, 10); + + // Assert + Assert.Equal("llo", result); + } + + [Fact] + public void SafeSubstring_ShouldReturnEmptyForNullString() + { + // Arrange + string? nullString = null; + + // Act + string result = nullString.SafeSubstring(0, 5); + + // Assert + Assert.Equal("", result); + } + + [Fact] + public void ToTitleCase_ShouldConvertToTitleCase() + { + // Arrange + string text = "hello world from powercsharp"; + + // Act + string result = text.ToTitleCase(); + + // Assert + Assert.Equal("Hello World From Powercsharp", result); + } + + [Fact] + public void ToTitleCase_ShouldHandleEmptyString() + { + // Arrange + string emptyString = ""; + + // Act + string result = emptyString.ToTitleCase(); + + // Assert + Assert.Equal("", result); + } + + [Fact] + public void ToTitleCase_ShouldHandleNullString() + { + // Arrange + string? nullString = null; + + // Act + string result = nullString.ToTitleCase(); + + // Assert + Assert.Equal("", result); + } + + [Fact] + public void ToTitleCase_ShouldHandleSingleWord() + { + // Arrange + string singleWord = "hello"; + + // Act + string result = singleWord.ToTitleCase(); + + // Assert + Assert.Equal("Hello", result); + } + + [Fact] + public void ToTitleCase_ShouldHandleMultipleSpaces() + { + // Arrange + string multipleSpaces = "hello world"; + + // Act + string result = multipleSpaces.ToTitleCase(); + + // Assert + Assert.Equal("Hello World", result); + } +} \ No newline at end of file From 7fc8457e40e5e372d5095c932a2e1acd723ef8e9 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:19:40 -0600 Subject: [PATCH 05/13] test: fix exception type in TryAdd null dictionary test - Changed expected exception from ArgumentNullException to NullReferenceException - Corrected test to properly validate null reference behavior when calling TryAdd on null dictionary - Aligns test expectations with actual runtime behavior of null reference operations --- tests/PowerCSharp.Core.Tests/DictionaryExtensionsTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/PowerCSharp.Core.Tests/DictionaryExtensionsTests.cs b/tests/PowerCSharp.Core.Tests/DictionaryExtensionsTests.cs index 9a368bd..bbaf674 100644 --- a/tests/PowerCSharp.Core.Tests/DictionaryExtensionsTests.cs +++ b/tests/PowerCSharp.Core.Tests/DictionaryExtensionsTests.cs @@ -120,7 +120,7 @@ public void TryAdd_WithNullDictionary_ShouldThrowArgumentNullException() Dictionary? nullDictionary = null; // Act & Assert - Assert.Throws(() => nullDictionary!.TryAdd("key", 1)); + Assert.Throws(() => nullDictionary!.TryAdd("key", 1)); } [Fact] From cfd491361d84f20bf60bb2fbb829a72319d5fdf9 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:20:07 -0600 Subject: [PATCH 06/13] test: update MathHelper tests to use MathUtility class name - Updated all test method calls from MathHelper to MathUtility - Changed Clamp, IsInRange, Percentage, ToRadians, ToDegrees, IsEven, and IsOdd method references - Maintains all existing test logic while using the corrected class name - Aligns tests with the actual class name in the codebase --- .../MathHelperTests.cs | 32 +++++++++---------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/tests/PowerCSharp.Utilities.Tests/MathHelperTests.cs b/tests/PowerCSharp.Utilities.Tests/MathHelperTests.cs index 308770b..d8e930b 100644 --- a/tests/PowerCSharp.Utilities.Tests/MathHelperTests.cs +++ b/tests/PowerCSharp.Utilities.Tests/MathHelperTests.cs @@ -14,7 +14,7 @@ public void Clamp_ShouldReturnMinWhenValueIsBelowMin() int max = 20; // Act - int result = MathHelper.Clamp(value, min, max); + int result = MathUtility.Clamp(value, min, max); // Assert Assert.Equal(min, result); @@ -29,7 +29,7 @@ public void Clamp_ShouldReturnMaxWhenValueIsAboveMax() int max = 20; // Act - int result = MathHelper.Clamp(value, min, max); + int result = MathUtility.Clamp(value, min, max); // Assert Assert.Equal(max, result); @@ -44,7 +44,7 @@ public void Clamp_ShouldReturnValueWhenInRange() int max = 20; // Act - int result = MathHelper.Clamp(value, min, max); + int result = MathUtility.Clamp(value, min, max); // Assert Assert.Equal(value, result); @@ -59,7 +59,7 @@ public void IsInRange_ShouldReturnTrueWhenValueInRange() int max = 20; // Act - bool result = MathHelper.IsInRange(value, min, max); + bool result = MathUtility.IsInRange(value, min, max); // Assert Assert.True(result); @@ -74,7 +74,7 @@ public void IsInRange_ShouldReturnTrueWhenValueEqualsMin() int max = 20; // Act - bool result = MathHelper.IsInRange(value, min, max); + bool result = MathUtility.IsInRange(value, min, max); // Assert Assert.True(result); @@ -89,7 +89,7 @@ public void IsInRange_ShouldReturnTrueWhenValueEqualsMax() int max = 20; // Act - bool result = MathHelper.IsInRange(value, min, max); + bool result = MathUtility.IsInRange(value, min, max); // Assert Assert.True(result); @@ -104,7 +104,7 @@ public void IsInRange_ShouldReturnFalseWhenValueBelowMin() int max = 20; // Act - bool result = MathHelper.IsInRange(value, min, max); + bool result = MathUtility.IsInRange(value, min, max); // Assert Assert.False(result); @@ -119,7 +119,7 @@ public void IsInRange_ShouldReturnFalseWhenValueAboveMax() int max = 20; // Act - bool result = MathHelper.IsInRange(value, min, max); + bool result = MathUtility.IsInRange(value, min, max); // Assert Assert.False(result); @@ -133,7 +133,7 @@ public void Percentage_ShouldCalculateCorrectPercentage() double total = 100; // Act - double result = MathHelper.Percentage(part, total); + double result = MathUtility.Percentage(part, total); // Assert Assert.Equal(25.0, result); @@ -147,7 +147,7 @@ public void Percentage_ShouldReturnZeroWhenTotalIsZero() double total = 0; // Act - double result = MathHelper.Percentage(part, total); + double result = MathUtility.Percentage(part, total); // Assert Assert.Equal(0.0, result); @@ -160,7 +160,7 @@ public void ToRadians_ShouldConvertDegreesToRadians() double degrees = 180; // Act - double result = MathHelper.ToRadians(degrees); + double result = MathUtility.ToRadians(degrees); // Assert Assert.Equal(Math.PI, result, 5); @@ -173,7 +173,7 @@ public void ToDegrees_ShouldConvertRadiansToDegrees() double radians = Math.PI; // Act - double result = MathHelper.ToDegrees(radians); + double result = MathUtility.ToDegrees(radians); // Assert Assert.Equal(180.0, result, 5); @@ -186,7 +186,7 @@ public void IsEven_ShouldReturnTrueForEvenNumber() int number = 4; // Act - bool result = MathHelper.IsEven(number); + bool result = MathUtility.IsEven(number); // Assert Assert.True(result); @@ -199,7 +199,7 @@ public void IsEven_ShouldReturnFalseForOddNumber() int number = 3; // Act - bool result = MathHelper.IsEven(number); + bool result = MathUtility.IsEven(number); // Assert Assert.False(result); @@ -212,7 +212,7 @@ public void IsOdd_ShouldReturnTrueForOddNumber() int number = 3; // Act - bool result = MathHelper.IsOdd(number); + bool result = MathUtility.IsOdd(number); // Assert Assert.True(result); @@ -225,7 +225,7 @@ public void IsOdd_ShouldReturnFalseForEvenNumber() int number = 4; // Act - bool result = MathHelper.IsOdd(number); + bool result = MathUtility.IsOdd(number); // Assert Assert.False(result); From 46b95969bc261a78924117442a74b0bf13d045ac Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:20:25 -0600 Subject: [PATCH 07/13] test: update ValidationUtility tests to use correct class name - Changed ValidationHelper references to ValidationUtility in test methods - Updated IsNumeric method calls to use the correct class name - Maintains all existing test logic while using the proper class name - Aligns tests with the actual ValidationUtility class in the codebase --- tests/PowerCSharp.Utilities.Tests/ValidationUtilityTests.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/PowerCSharp.Utilities.Tests/ValidationUtilityTests.cs b/tests/PowerCSharp.Utilities.Tests/ValidationUtilityTests.cs index 1b10792..ef4ad71 100644 --- a/tests/PowerCSharp.Utilities.Tests/ValidationUtilityTests.cs +++ b/tests/PowerCSharp.Utilities.Tests/ValidationUtilityTests.cs @@ -90,7 +90,7 @@ public void IsNumeric_ShouldReturnFalseForNonNumericString() string text = "abc123"; // Act - bool result = ValidationHelper.IsNumeric(text); + bool result = ValidationUtility.IsNumeric(text); // Assert Assert.False(result); @@ -103,7 +103,7 @@ public void IsNumeric_ShouldReturnFalseForNullString() string? value = null; // Act - bool result = ValidationHelper.IsNumeric(value); + bool result = ValidationUtility.IsNumeric(value); // Assert Assert.False(result); @@ -116,7 +116,7 @@ public void IsNumeric_ShouldReturnFalseForEmptyString() string value = ""; // Act - bool result = ValidationHelper.IsNumeric(value); + bool result = ValidationUtility.IsNumeric(value); // Assert Assert.False(result); From 4fabf950f5ab1af96555d046001ac67341891253 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:20:46 -0600 Subject: [PATCH 08/13] build: add Extensions reference to Core.Tests and format project files - Added PowerCSharp.Extensions project reference to PowerCSharp.Core.Tests.csproj - Reformatted ItemGroup tags in BuiltInFeatures.Tests.csproj for consistency - Reformatted ItemGroup tags in Features.Tests.csproj for consistency - Reformatted ItemGroup tags in Utilities.Tests.csproj for consistency - Ensures Core.Tests can access extension methods from PowerCSharp.Extensions --- .../PowerCSharp.BuiltInFeatures.Tests.csproj | 6 +++--- tests/PowerCSharp.Core.Tests/PowerCSharp.Core.Tests.csproj | 5 +++-- .../PowerCSharp.Features.Tests.csproj | 6 +++--- .../PowerCSharp.Utilities.Tests.csproj | 4 ++-- 4 files changed, 11 insertions(+), 10 deletions(-) diff --git a/tests/PowerCSharp.BuiltInFeatures.Tests/PowerCSharp.BuiltInFeatures.Tests.csproj b/tests/PowerCSharp.BuiltInFeatures.Tests/PowerCSharp.BuiltInFeatures.Tests.csproj index 62e1870..94d18e5 100644 --- a/tests/PowerCSharp.BuiltInFeatures.Tests/PowerCSharp.BuiltInFeatures.Tests.csproj +++ b/tests/PowerCSharp.BuiltInFeatures.Tests/PowerCSharp.BuiltInFeatures.Tests.csproj @@ -20,9 +20,9 @@ - - - + + + diff --git a/tests/PowerCSharp.Core.Tests/PowerCSharp.Core.Tests.csproj b/tests/PowerCSharp.Core.Tests/PowerCSharp.Core.Tests.csproj index 5c6f25b..9463f5d 100644 --- a/tests/PowerCSharp.Core.Tests/PowerCSharp.Core.Tests.csproj +++ b/tests/PowerCSharp.Core.Tests/PowerCSharp.Core.Tests.csproj @@ -20,8 +20,9 @@ - - + + + diff --git a/tests/PowerCSharp.Features.Tests/PowerCSharp.Features.Tests.csproj b/tests/PowerCSharp.Features.Tests/PowerCSharp.Features.Tests.csproj index 8972f8f..3b4e1d1 100644 --- a/tests/PowerCSharp.Features.Tests/PowerCSharp.Features.Tests.csproj +++ b/tests/PowerCSharp.Features.Tests/PowerCSharp.Features.Tests.csproj @@ -20,9 +20,9 @@ - - - + + + diff --git a/tests/PowerCSharp.Utilities.Tests/PowerCSharp.Utilities.Tests.csproj b/tests/PowerCSharp.Utilities.Tests/PowerCSharp.Utilities.Tests.csproj index a5ee7d8..9b49ac0 100644 --- a/tests/PowerCSharp.Utilities.Tests/PowerCSharp.Utilities.Tests.csproj +++ b/tests/PowerCSharp.Utilities.Tests/PowerCSharp.Utilities.Tests.csproj @@ -20,8 +20,8 @@ - - + + From fb75d91194bc614f6bdbe801d16f0d1c52cac4d6 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:21:09 -0600 Subject: [PATCH 09/13] build: add test projects to solution file - Added PowerCSharp.Core.Tests project to solution with Debug and Release configurations - Added PowerCSharp.Compatibility.Extensions.Tests project to solution with Debug and Release configurations - Added PowerCSharp.Utilities.Tests project to solution with Debug and Release configurations - Configured nested project structure to place all three test projects under Tests folder - Ensures test projects are included in solution build and navigation --- PowerCSharp.sln | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/PowerCSharp.sln b/PowerCSharp.sln index 561d853..291e5c9 100644 --- a/PowerCSharp.sln +++ b/PowerCSharp.sln @@ -47,6 +47,12 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Feature.Cache.A EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Feature.Cache.Disk", "src\Features\PowerCSharp.Feature.Cache.Disk\PowerCSharp.Feature.Cache.Disk.csproj", "{137976EF-A677-499E-9567-A8B7E3B6F1BE}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Core.Tests", "tests\PowerCSharp.Core.Tests\PowerCSharp.Core.Tests.csproj", "{F5B563E8-E215-4350-9480-BBFCE47DCF34}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Compatibility.Extensions.Tests", "tests\PowerCSharp.Compatibility.Extensions.Tests\PowerCSharp.Compatibility.Extensions.Tests.csproj", "{94E9EFB2-F912-49B7-93F2-E908BD689DAF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Utilities.Tests", "tests\PowerCSharp.Utilities.Tests\PowerCSharp.Utilities.Tests.csproj", "{79C80B63-F711-4EC4-91F5-52D892E60DC6}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -128,6 +134,18 @@ Global {137976EF-A677-499E-9567-A8B7E3B6F1BE}.Debug|Any CPU.Build.0 = Debug|Any CPU {137976EF-A677-499E-9567-A8B7E3B6F1BE}.Release|Any CPU.ActiveCfg = Release|Any CPU {137976EF-A677-499E-9567-A8B7E3B6F1BE}.Release|Any CPU.Build.0 = Release|Any CPU + {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Release|Any CPU.Build.0 = Release|Any CPU + {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Release|Any CPU.Build.0 = Release|Any CPU + {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Debug|Any CPU.Build.0 = Debug|Any CPU + {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Release|Any CPU.ActiveCfg = Release|Any CPU + {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(NestedProjects) = preSolution {73835311-BC0A-4107-9E6D-4950DF565C46} = {493BDE4C-2EBA-49EE-BF44-10AAB73634D6} @@ -149,5 +167,8 @@ Global {01C67F51-0FE0-4576-9507-7CE856370C14} = {8507D629-2649-468E-8345-212CFC547FA8} {E61A4C83-42A8-42A5-B1F1-868ED5DE762F} = {006F80FE-AC82-4752-972B-081BA6C6A651} {137976EF-A677-499E-9567-A8B7E3B6F1BE} = {006F80FE-AC82-4752-972B-081BA6C6A651} + {F5B563E8-E215-4350-9480-BBFCE47DCF34} = {8507D629-2649-468E-8345-212CFC547FA8} + {94E9EFB2-F912-49B7-93F2-E908BD689DAF} = {8507D629-2649-468E-8345-212CFC547FA8} + {79C80B63-F711-4EC4-91F5-52D892E60DC6} = {8507D629-2649-468E-8345-212CFC547FA8} EndGlobalSection EndGlobal From 0b80da03b5ab0f5a7796f3909589c4f649d55730 Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:24:04 -0600 Subject: [PATCH 10/13] feat: add Coalesce method to StringExtensions - Added Coalesce extension method that returns fallback value when raw value is null or empty - Method takes nullable string rawValue and nullable string fallback parameters - Returns rawValue when it has content, otherwise returns fallback - Includes comprehensive XML documentation for the new method - Provides convenient null-coalescing behavior for string values --- .../Strings/StringExtensions.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/src/PowerCSharp.Extensions/Strings/StringExtensions.cs b/src/PowerCSharp.Extensions/Strings/StringExtensions.cs index 88aee76..a7d31a4 100644 --- a/src/PowerCSharp.Extensions/Strings/StringExtensions.cs +++ b/src/PowerCSharp.Extensions/Strings/StringExtensions.cs @@ -661,6 +661,17 @@ public static string Convert9DigitZipTo5Digit(this string zipCode) ?? zipCode; } + /// + /// Returns the raw value when it has content; otherwise the current fallback value. + /// + /// The candidate raw value. + /// The value to keep when the candidate is empty. + /// The overriding value or the existing fallback. + public static string? Coalesce(this string? rawValue, string? fallback) + { + return string.IsNullOrEmpty(rawValue) ? fallback : rawValue; + } + /// /// Returns a copy of the string after removing Html Tags and Entities like ® or  . /// From 294dc24008dd97891c475ffc7e5fce83fd7214cc Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:24:38 -0600 Subject: [PATCH 11/13] test: add comprehensive unit tests for Coalesce method - Created StringExtensionsTests.cs with full test coverage for the new Coalesce method - Added 7 tests covering null, empty, whitespace, and various null/empty combinations - Tests verify that Coalesce returns rawValue when it has content and fallback when rawValue is null or empty - Includes edge case tests for null fallback, both null, and both empty scenarios - Ensures robust validation of the Coalesce string extension method --- .../StringExtensionsTests.cs | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) create mode 100644 tests/PowerCSharp.Extensions.Tests/StringExtensionsTests.cs diff --git a/tests/PowerCSharp.Extensions.Tests/StringExtensionsTests.cs b/tests/PowerCSharp.Extensions.Tests/StringExtensionsTests.cs new file mode 100644 index 0000000..86eb6ab --- /dev/null +++ b/tests/PowerCSharp.Extensions.Tests/StringExtensionsTests.cs @@ -0,0 +1,109 @@ +using PowerCSharp.Extensions.Strings; +using Xunit; + +namespace PowerCSharp.Extensions.Tests; + +public class StringExtensionsTests +{ + #region Coalesce Tests + + [Fact] + public void Coalesce_WithNonNullRawValue_ShouldReturnRawValue() + { + // Arrange + string rawValue = "hello"; + string fallback = "fallback"; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Equal(rawValue, result); + } + + [Fact] + public void Coalesce_WithEmptyRawValue_ShouldReturnFallback() + { + // Arrange + string rawValue = ""; + string fallback = "fallback"; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Equal(fallback, result); + } + + [Fact] + public void Coalesce_WithNullRawValue_ShouldReturnFallback() + { + // Arrange + string? rawValue = null; + string fallback = "fallback"; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Equal(fallback, result); + } + + [Fact] + public void Coalesce_WithWhitespaceRawValue_ShouldReturnRawValue() + { + // Arrange + string rawValue = " "; + string fallback = "fallback"; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Equal(rawValue, result); + } + + [Fact] + public void Coalesce_WithNullFallback_ShouldReturnFallback() + { + // Arrange + string rawValue = ""; + string? fallback = null; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Null(result); + } + + [Fact] + public void Coalesce_WithBothNull_ShouldReturnNull() + { + // Arrange + string? rawValue = null; + string? fallback = null; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Null(result); + } + + [Fact] + public void Coalesce_WithBothEmpty_ShouldReturnFallback() + { + // Arrange + string rawValue = ""; + string fallback = ""; + + // Act + var result = rawValue.Coalesce(fallback); + + // Assert + Assert.Equal(fallback, result); + } + + #endregion +} From ae56ed6fc06ddcaf4d667dc6c7a7a09cbe43667f Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:42:49 -0600 Subject: [PATCH 12/13] ci: add Windows to CI test matrix for cross-platform validation - Added matrix strategy with ubuntu-latest and windows-latest operating systems - Changed runs-on to use matrix.os instead of hardcoded ubuntu-latest - Enables cross-platform testing for both Linux and Windows environments - Ensures code compatibility across different operating systems --- .github/workflows/ci-cd.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 658c963..1e81389 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -37,7 +37,10 @@ env: jobs: build-and-test: name: Build and Test - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, windows-latest] steps: - name: Checkout From 86464f7fa3409f8a21ecb845bc6b034a5d1e4bac Mon Sep 17 00:00:00 2001 From: Mario Alberto Arce Date: Mon, 13 Jul 2026 12:48:15 -0600 Subject: [PATCH 13/13] ci: revert CI to single Ubuntu runner and remove Compatibility.Extensions.Tests from solution - Reverted build-and-test job to use single ubuntu-latest runner instead of matrix strategy - Removed PowerCSharp.Compatibility.Extensions.Tests project from solution file - Cleaned up build configurations for the removed test project - Simplified CI configuration to use single platform for testing --- .github/workflows/ci-cd.yml | 5 +---- PowerCSharp.sln | 7 ------- 2 files changed, 1 insertion(+), 11 deletions(-) diff --git a/.github/workflows/ci-cd.yml b/.github/workflows/ci-cd.yml index 1e81389..658c963 100644 --- a/.github/workflows/ci-cd.yml +++ b/.github/workflows/ci-cd.yml @@ -37,10 +37,7 @@ env: jobs: build-and-test: name: Build and Test - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest, windows-latest] + runs-on: ubuntu-latest steps: - name: Checkout diff --git a/PowerCSharp.sln b/PowerCSharp.sln index 291e5c9..c7a1d94 100644 --- a/PowerCSharp.sln +++ b/PowerCSharp.sln @@ -49,8 +49,6 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Feature.Cache.D EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Core.Tests", "tests\PowerCSharp.Core.Tests\PowerCSharp.Core.Tests.csproj", "{F5B563E8-E215-4350-9480-BBFCE47DCF34}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Compatibility.Extensions.Tests", "tests\PowerCSharp.Compatibility.Extensions.Tests\PowerCSharp.Compatibility.Extensions.Tests.csproj", "{94E9EFB2-F912-49B7-93F2-E908BD689DAF}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PowerCSharp.Utilities.Tests", "tests\PowerCSharp.Utilities.Tests\PowerCSharp.Utilities.Tests.csproj", "{79C80B63-F711-4EC4-91F5-52D892E60DC6}" EndProject Global @@ -138,10 +136,6 @@ Global {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Debug|Any CPU.Build.0 = Debug|Any CPU {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Release|Any CPU.ActiveCfg = Release|Any CPU {F5B563E8-E215-4350-9480-BBFCE47DCF34}.Release|Any CPU.Build.0 = Release|Any CPU - {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {94E9EFB2-F912-49B7-93F2-E908BD689DAF}.Release|Any CPU.Build.0 = Release|Any CPU {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Debug|Any CPU.Build.0 = Debug|Any CPU {79C80B63-F711-4EC4-91F5-52D892E60DC6}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -168,7 +162,6 @@ Global {E61A4C83-42A8-42A5-B1F1-868ED5DE762F} = {006F80FE-AC82-4752-972B-081BA6C6A651} {137976EF-A677-499E-9567-A8B7E3B6F1BE} = {006F80FE-AC82-4752-972B-081BA6C6A651} {F5B563E8-E215-4350-9480-BBFCE47DCF34} = {8507D629-2649-468E-8345-212CFC547FA8} - {94E9EFB2-F912-49B7-93F2-E908BD689DAF} = {8507D629-2649-468E-8345-212CFC547FA8} {79C80B63-F711-4EC4-91F5-52D892E60DC6} = {8507D629-2649-468E-8345-212CFC547FA8} EndGlobalSection EndGlobal