Skip to content

Releases: yevhen/sharp.assert

v1.4.0

18 Dec 17:15

Choose a tag to compare

New Features

Collection Quantifiers

A complete suite of collection quantifier expectations for testing element conditions:

using SharpAssert;

// All items must match
Assert(items.Each(x => x > 0));
Assert(items.Each(x => x.IsEquivalentTo(template)));

// At least one must match  
Assert(items.Some(x => x.Matches("error*")));

// No items may match
Assert(items.None(x => x == null));

// Exactly one must match
Assert(items.One(x => x.Id == targetId));

// Count-based quantifiers
Assert(items.Exactly(3, x => x.Score > 90));
Assert(items.AtLeast(2, x => x.IsEquivalentTo(admin)));
Assert(items.AtMost(1, x => x.Matches("*error*")));

All quantifiers support both predicate expressions (x => x > 5) and custom expectations (x => x.IsEquivalentTo(expected)).

Lambda-Based Custom Expectations

New Expectation.From() factory eliminates boilerplate when creating custom expectations:

public static Expectation IsEven(this int value) =>
    Expectation.From(
        () => value % 2 == 0,
        () => [$"Expected even number, got {value}"]
    );

Assert(4.IsEven());

Multiple Assertions with &&

Logical && now evaluates ALL operands and shows all failures - no short-circuit:

Assert(x == 5 && y == 10);
// Shows both failures when both operands are false:
// &&: Both operands were false

Replaces verbose Assert.Multiple() / AssertionScope patterns with native C# syntax.

Full Changelog: v1.3.0...v1.4.0

v1.3.0

16 Dec 15:31

Choose a tag to compare

New Features

Collection Assertions

  • ContainsInOrder(seq) - validates elements appear in order (gaps allowed)
  • ContainsInConsecutiveOrder(seq) - validates elements appear consecutively (no gaps)
  • IsSubsetOf(superset) - validates collection is a subset
  • IsSupersetOf(subset) - validates collection is a superset
  • Intersects(other) - validates collections share at least one element
  • Satisfies(p1, p2, ...) - validates elements satisfy predicates with unique 1:1 matching (bipartite matching)

Proximity Assertions

  • BeCloseTo(target, tolerance) - validates numeric values within tolerance
  • BeApproximately(target, tolerance) - alias for BeCloseTo
  • BeCloseTo(expected, TimeSpan) - validates DateTime/DateTimeOffset within tolerance

IsEquivalentTo Enhancements

  • Full feature parity with FluentAssertions configuration options

Usage

using SharpAssert.Features.Collections;
using SharpAssert.Features.Proximity;

// Collection assertions
Assert(coll.ContainsInOrder(1, 3, 5));
Assert(coll.ContainsInConsecutiveOrder(2, 3, 4));
Assert(coll.IsSubsetOf(superset));
Assert(coll.IsSupersetOf(subset));
Assert(coll.Intersects(other));
Assert(users.Satisfies(u => u.Age > 18, u => u.IsAdmin));

// Proximity assertions
Assert(value.BeCloseTo(target, 0.01));
Assert(value.BeApproximately(pi, 0.001));
Assert(dt.BeCloseTo(expected, TimeSpan.FromMilliseconds(100)));

Full Changelog: v1.2.0...v1.3.0

v1.2.0

15 Dec 07:45

Choose a tag to compare

What's New

Custom Expectations

Create reusable, composable expectations by inheriting from Expectation:

sealed class IsEvenExpectation(int value) : Expectation
{
    public override EvaluationResult Evaluate(ExpectationContext context) =>
        value % 2 == 0
            ? ExpectationResults.Pass(context.Expression)
            : ExpectationResults.Fail(context.Expression, $"Expected even, got {value}");
}

// Use with static factories or extension methods
Assert(IsEven(4));
Assert(4.IsEven() & !5.IsEven());

String Pattern Matching

Wildcard patterns with * (any sequence) and ? (single character):

using SharpAssert.Features.Strings;

Assert("hello world".Matches("hello *"));
Assert("test.txt".Matches("*.txt"));
Assert("HELLO".MatchesIgnoringCase("hello"));

Occurrence counting for substrings and regex:

Assert("error, error".Contains("error", Occur.Exactly(2)));
Assert("warn, warn".Contains("warn", Occur.AtLeast(1)));
Assert("test123 test456".MatchesRegex(@"test\d+", Occur.Exactly(2)));

Collection Ordering

Validate collections are sorted:

using SharpAssert.Features.Collections;

Assert(numbers.IsInAscendingOrder());
Assert(scores.IsInDescendingOrder());
Assert(names.IsInAscendingOrder(StringComparer.OrdinalIgnoreCase));

Collection Uniqueness

Validate collections contain no duplicates:

Assert(items.AllUnique());
Assert(users.AllUnique(u => u.Email));  // By key

Object Equivalency

Structural comparison with fluent configuration:

Assert(actual.IsEquivalentTo(expected));
Assert(actual.IsEquivalentTo(expected, c => c.Excluding(p => p.Id)));
Assert(actual.IsEquivalentTo(expected, c => c.Including(p => p.Name)));
Assert(team1.IsEquivalentTo(team2, c => c.WithoutStrictOrdering()));

Improvements

  • Programmatic access to results - SharpAssertionException.Result property provides structured access to assertion evaluation results
  • Hardened expression evaluation - Better fallbacks and sentinels for edge cases in expression compilation
  • Improved expression display - Fixed display for complex nested binary operations and nullable unwrapping

Internal

  • Feature-based code organization
  • Decoupled evaluation from formatting
  • Structured comparison results with rendering pushed into record types
  • Simplified rendering without visitors

Full Changelog: v1.1.0...v1.2.0

Release 1.1.0

18 Oct 21:36

Choose a tag to compare

v1.1.0

Tidy up CONSTITUTION.md

Release 1.0.6

13 Aug 19:22

Choose a tag to compare

v1.0.6

Fix(build): Correct MSBuild targets for cross-platform compatibility

Release 1.0.5

13 Aug 18:21

Choose a tag to compare

v1.0.5

Add project brief

Release 1.0.4

13 Aug 15:31

Choose a tag to compare

Fix MSBuild targets to preserve unprocessed source files

The previous implementation removed ALL source files from compilation
and only included rewritten files. This broke namespace resolution for
types in files without Assert() calls (like PipeMock).

This fix:
- Only removes files that were actually rewritten
- Includes both rewritten files AND unprocessed source files
- Preserves the complete compilation context

Fixes namespace resolution errors like:
CS0103: The name 'PipeMock' does not exist in the current context

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>

1.0.3

13 Aug 14:20

Choose a tag to compare

First beta release for early adopters to gather feedback

1.0.0

13 Aug 13:59

Choose a tag to compare

1.0.0 Pre-release
Pre-release

Zero value release to test the pipeline