Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions OnixLabs.Core.UnitTests.Data/Mutable.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright 2020-2025 ONIXLabs
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

namespace OnixLabs.Core.UnitTests.Data;

public sealed class Mutable
{
public required int Value { get; set; }
}
78 changes: 60 additions & 18 deletions OnixLabs.Core.UnitTests/ObjectExtensionTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,34 +20,31 @@ namespace OnixLabs.Core.UnitTests;

public sealed class ObjectExtensionTests
{
[Theory(DisplayName = "IsWithinRangeInclusive should produce the expected result")]
[InlineData(2, 1, 3, true)]
[InlineData(1, 1, 3, true)]
[InlineData(3, 1, 3, true)]
[InlineData(0, 1, 3, false)]
[InlineData(4, 1, 3, false)]
public void IsWithinRangeInclusiveShouldProduceExpectedResult(int value, int min, int max, bool expected)
[Fact(DisplayName = "Apply should produce the expected result (reference type)")]
public void ApplyShouldProduceExpectedResultReferenceType()
{
// Given
Mutable value = new() { Value = 123 };

// When
bool actual = value.IsWithinRangeInclusive(min, max);
Mutable result = value.Apply(it => it.Value = 456);

// Then
Assert.Equal(expected, actual);
Assert.Equal(456, value.Value);
Assert.Same(value, result);
}

[Theory(DisplayName = "IsWithinRangeExclusive should produce the expected result")]
[InlineData(2, 1, 3, true)]
[InlineData(1, 1, 3, false)]
[InlineData(3, 1, 3, false)]
[InlineData(0, 1, 3, false)]
[InlineData(4, 1, 3, false)]
public void IsWithinRangeExclusiveShouldProduceExpectedResult(int value, int min, int max, bool expected)
[Fact(DisplayName = "Apply should produce the expected result (value type)")]
public void ApplyShouldProduceExpectedResultValueType()
{
// Given
int value = 123;

// When
bool actual = value.IsWithinRangeExclusive(min, max);
value = value.Apply(it => it * 2);

// Then
Assert.Equal(expected, actual);
Assert.Equal(246, value);
}

[Fact(DisplayName = "CompareToObject should produce zero if the current IComparable<T> is equal to the specified object.")]
Expand Down Expand Up @@ -112,6 +109,51 @@ public void CompareToObjectShouldThrowArgumentExceptionIfSpecifiedObjectIsOfInco
Assert.Equal("Object must be of type System.Int32 (Parameter 'right')", exception.Message);
}

[Theory(DisplayName = "IsWithinRangeInclusive should produce the expected result")]
[InlineData(2, 1, 3, true)]
[InlineData(1, 1, 3, true)]
[InlineData(3, 1, 3, true)]
[InlineData(0, 1, 3, false)]
[InlineData(4, 1, 3, false)]
public void IsWithinRangeInclusiveShouldProduceExpectedResult(int value, int min, int max, bool expected)
{
// When
bool actual = value.IsWithinRangeInclusive(min, max);

// Then
Assert.Equal(expected, actual);
}

[Theory(DisplayName = "IsWithinRangeExclusive should produce the expected result")]
[InlineData(2, 1, 3, true)]
[InlineData(1, 1, 3, false)]
[InlineData(3, 1, 3, false)]
[InlineData(0, 1, 3, false)]
[InlineData(4, 1, 3, false)]
public void IsWithinRangeExclusiveShouldProduceExpectedResult(int value, int min, int max, bool expected)
{
// When
bool actual = value.IsWithinRangeExclusive(min, max);

// Then
Assert.Equal(expected, actual);
}

[Fact(DisplayName = "Let should produce the expected result")]
public void LetShouldProduceExpectedResult()
{
// Given
const string value = "123";

// When
int result = value
.Let(int.Parse)
.Let(it => it * 2);

// Then
Assert.Equal(246, result);
}

[Fact(DisplayName = "ToRecordString should produce null when the object is null")]
public void ToRecordStringShouldProduceNullWhenObjectIsNull()
{
Expand Down
85 changes: 63 additions & 22 deletions OnixLabs.Core/Extensions.Object.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,32 +41,31 @@ public static class ObjectExtensions
private const string ObjectPropertyAssignment = " = ";

/// <summary>
/// Determines whether the current <see cref="IComparable{T}"/> value falls within range, inclusive of the specified minimum and maximum values.
/// Calls the specified <see cref="Action{T}"/> with the current <paramref name="value"/>.
/// </summary>
/// <param name="value">The value to test.</param>
/// <param name="min">The inclusive minimum value.</param>
/// <param name="max">The inclusive maximum value.</param>
/// <typeparam name="T">The underlying <see cref="IComparable{T}"/> type.</typeparam>
/// <returns>
/// Returns <see langword="true"/> if the current <see cref="IComparable{T}"/> value falls within range,
/// inclusive of the specified minimum and maximum values; otherwise, <see langword="false"/>.
/// </returns>
public static bool IsWithinRangeInclusive<T>(this T value, T min, T max) where T : IComparable<T> =>
value.CompareTo(min) is 0 or 1 && value.CompareTo(max) is 0 or -1;
/// <param name="value">The value to pass to the specified <see cref="Action{T}"/>.</param>
/// <param name="action">The action into which the current <paramref name="value"/> will be passed.</param>
/// <typeparam name="T">The underlying type of the current value.</typeparam>
/// <returns>Returns the current <paramref name="value"/> once the specified <see cref="Action{T}"/> has been executed.</returns>
public static T Apply<T>(this T value, Action<T> action) where T : class
{
RequireNotNull(action, "Action must not be null.", nameof(action));
action(value);
return value;
}

/// <summary>
/// Determines whether the current <see cref="IComparable{T}"/> value falls within range, exclusive of the specified minimum and maximum values.
/// Calls the specified <see cref="Func{T, TResult}"/> with the current <paramref name="value"/>.
/// </summary>
/// <param name="value">The value to test.</param>
/// <param name="min">The exclusive minimum value.</param>
/// <param name="max">The exclusive maximum value.</param>
/// <typeparam name="T">The underlying <see cref="IComparable{T}"/> type.</typeparam>
/// <returns>
/// Returns <see langword="true"/> if the current <see cref="IComparable{T}"/> value falls within range,
/// exclusive of the specified minimum and maximum values; otherwise, <see langword="false"/>.
/// </returns>
public static bool IsWithinRangeExclusive<T>(this T value, T min, T max) where T : IComparable<T> =>
value.CompareTo(min) is 1 && value.CompareTo(max) is -1;
/// <param name="value">The value to pass to the specified <see cref="Func{T, TResult}"/>.</param>
/// <param name="function">The function into which the current <paramref name="value"/> will be passed.</param>
/// <typeparam name="T">The underlying type of the current value.</typeparam>
/// <returns>Returns the result of the specified <see cref="Func{T, TResult}"/>.</returns>
public static T Apply<T>(this T value, Func<T, T> function) where T : struct
{
RequireNotNull(function, "Function must not be null.", nameof(function));
return function(value);
}

/// <summary>
/// Compares the current <typeparamref name="T"/> instance with the specified <typeparamref name="T"/> instance.
Expand Down Expand Up @@ -108,6 +107,48 @@ public static int CompareToNullable<T>(this T left, T? right) where T : struct,
_ => throw new ArgumentException($"Object must be of type {typeof(T).FullName}", nameof(right))
};

/// <summary>
/// Determines whether the current <see cref="IComparable{T}"/> value falls within range, inclusive of the specified minimum and maximum values.
/// </summary>
/// <param name="value">The value to test.</param>
/// <param name="min">The inclusive minimum value.</param>
/// <param name="max">The inclusive maximum value.</param>
/// <typeparam name="T">The underlying <see cref="IComparable{T}"/> type.</typeparam>
/// <returns>
/// Returns <see langword="true"/> if the current <see cref="IComparable{T}"/> value falls within range,
/// inclusive of the specified minimum and maximum values; otherwise, <see langword="false"/>.
/// </returns>
public static bool IsWithinRangeInclusive<T>(this T value, T min, T max) where T : IComparable<T> =>
value.CompareTo(min) is 0 or 1 && value.CompareTo(max) is 0 or -1;

/// <summary>
/// Determines whether the current <see cref="IComparable{T}"/> value falls within range, exclusive of the specified minimum and maximum values.
/// </summary>
/// <param name="value">The value to test.</param>
/// <param name="min">The exclusive minimum value.</param>
/// <param name="max">The exclusive maximum value.</param>
/// <typeparam name="T">The underlying <see cref="IComparable{T}"/> type.</typeparam>
/// <returns>
/// Returns <see langword="true"/> if the current <see cref="IComparable{T}"/> value falls within range,
/// exclusive of the specified minimum and maximum values; otherwise, <see langword="false"/>.
/// </returns>
public static bool IsWithinRangeExclusive<T>(this T value, T min, T max) where T : IComparable<T> =>
value.CompareTo(min) is 1 && value.CompareTo(max) is -1;

/// <summary>
/// Calls the specified <see cref="Func{T, TResult}"/> with the current <paramref name="value"/> as its argument and returns the result.
/// </summary>
/// <param name="value">The value to pass to the specified <see cref="Func{T, TResult}"/>.</param>
/// <param name="function">The function into which the current <paramref name="value"/> will be passed.</param>
/// <typeparam name="TSource">The underlying type of the current value.</typeparam>
/// <typeparam name="TResult">The underlying type of the result value.</typeparam>
/// <returns>Returns the result of the specified <see cref="Func{T, TResult}"/>.</returns>
public static TResult Let<TSource, TResult>(this TSource value, Func<TSource, TResult> function)
{
RequireNotNull(function, "Function must not be null.", nameof(function));
return function(value);
}

/// <summary>
/// Gets a record-like <see cref="String"/> representation of the current <see cref="Object"/> instance.
/// <remarks>This method is designed specifically for record-like objects and may produce undesirable results when applied to primitive-like objects.</remarks>
Expand Down