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
65 changes: 47 additions & 18 deletions Src/FluentAssertions/Primitives/StringAssertions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1882,12 +1882,12 @@ public AndConstraint<TAssertions> BeNullOrWhiteSpace([StringSyntax("CompositeFor
}

/// <summary>
/// Asserts that all characters in a string are in upper casing.
/// Asserts that all cased characters in a string are upper-case. That is, that the string could be the result of a call to
/// <see cref="string.ToUpperInvariant()"/>.
/// </summary>
/// <remarks>
/// Be careful that numbers and special characters don't have casing, so <see cref="BeUpperCased"/>
/// will always fail on a string that contains anything but alphabetic characters.
/// In those cases, we recommend using <see cref="NotBeLowerCased"/>.
/// Numbers, special characters, and many Asian characters don't have casing, so <see cref="BeUpperCased"/>
/// will ignore these and will fail only in the presence of lower-case characters.
/// </remarks>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
Expand All @@ -1899,16 +1899,21 @@ public AndConstraint<TAssertions> BeNullOrWhiteSpace([StringSyntax("CompositeFor
public AndConstraint<TAssertions> BeUpperCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs)
{
Execute.Assertion
.ForCondition(Subject?.All(char.IsUpper) == true)
.ForCondition(Subject is not null && !Subject.Any(char.IsLower))
.BecauseOf(because, becauseArgs)
.FailWith("Expected all characters in {context:string} to be upper cased{reason}, but found {0}.", Subject);
.FailWith("Expected all alphabetic characters in {context:string} to be upper-case{reason}, but found {0}.", Subject);

return new AndConstraint<TAssertions>((TAssertions)this);
}

/// <summary>
/// Asserts that all characters in a string are not in upper casing.
/// Asserts that of all the cased characters in a string, some are not upper-case. That is, the string could not be
/// the result of a call to <see cref="string.ToUpperInvariant()"/>.
/// </summary>
/// <remarks>
/// Numbers, special characters, and many Asian characters don't have casing, so <see cref="NotBeUpperCased"/>
/// will ignore these and will fail only if the string contains cased characters and they are all upper-case.
/// </remarks>
/// <param name="because">
Comment thread
dennisdoomen marked this conversation as resolved.
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
Expand All @@ -1919,20 +1924,20 @@ public AndConstraint<TAssertions> BeUpperCased([StringSyntax("CompositeFormat")]
public AndConstraint<TAssertions> NotBeUpperCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs)
{
Execute.Assertion
.ForCondition(Subject is null || Subject.Any(ch => !char.IsUpper(ch)))
.ForCondition(Subject is null || HasMixedOrNoCase(Subject))
.BecauseOf(because, becauseArgs)
.FailWith("Did not expect any characters in {context:string} to be upper cased{reason}.");
.FailWith("Expected some characters in {context:string} to be lower-case{reason}.");
Comment thread
dennisdoomen marked this conversation as resolved.

return new AndConstraint<TAssertions>((TAssertions)this);
}

/// <summary>
/// Asserts that all characters in a string are in lower casing.
/// Asserts that all cased characters in a string are lower-case. That is, that the string could be the result of a call to
/// <see cref="string.ToLowerInvariant()"/>,
/// </summary>
/// <remarks>
/// Be careful that numbers and special characters don't have casing, so <see cref="BeLowerCased"/> will always fail on
/// a string that contains anything but alphabetic characters.
/// In those cases, we recommend using <see cref="NotBeUpperCased"/>.
/// Numbers, special characters, and many Asian characters don't have casing, so <see cref="BeLowerCased"/>
/// will ignore these and will fail only in the presence of upper-case characters.
/// </remarks>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
Expand All @@ -1944,16 +1949,21 @@ public AndConstraint<TAssertions> NotBeUpperCased([StringSyntax("CompositeFormat
public AndConstraint<TAssertions> BeLowerCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs)
{
Execute.Assertion
.ForCondition(Subject?.All(char.IsLower) == true)
.ForCondition(Subject is not null && !Subject.Any(char.IsUpper))
.BecauseOf(because, becauseArgs)
.FailWith("Expected all characters in {context:string} to be lower cased{reason}, but found {0}.", Subject);
.FailWith("Expected all alphabetic characters in {context:string} to be lower cased{reason}, but found {0}.", Subject);

return new AndConstraint<TAssertions>((TAssertions)this);
}

/// <summary>
/// Asserts that all characters in a string are not in lower casing.
/// Asserts that of all the cased characters in a string, some are not lower-case. That is, the string could not be
/// the result of a call to <see cref="string.ToLowerInvariant()"/>.
/// </summary>
/// <remarks>
/// Numbers, special characters, and many Asian characters don't have casing, so <see cref="NotBeLowerCased"/>
/// will ignore these and will fail only if the string contains cased characters and they are all lower-case.
/// </remarks>
/// <param name="because">
/// A formatted phrase as is supported by <see cref="string.Format(string,object[])" /> explaining why the assertion
/// is needed. If the phrase does not start with the word <i>because</i>, it is prepended automatically.
Expand All @@ -1964,13 +1974,32 @@ public AndConstraint<TAssertions> BeLowerCased([StringSyntax("CompositeFormat")]
public AndConstraint<TAssertions> NotBeLowerCased([StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs)
{
Execute.Assertion
.ForCondition(Subject is null || Subject.Any(ch => !char.IsLower(ch)))
.ForCondition(Subject is null || HasMixedOrNoCase(Subject))
.BecauseOf(because, becauseArgs)
.FailWith("Did not expect any characters in {context:string} to be lower cased{reason}.");
.FailWith("Expected some characters in {context:string} to be upper-case{reason}.");

return new AndConstraint<TAssertions>((TAssertions)this);
}

private static bool HasMixedOrNoCase(string value)
{
var hasUpperCase = false;
var hasLowerCase = false;

foreach (var ch in value)
{
hasUpperCase |= char.IsUpper(ch);
hasLowerCase |= char.IsLower(ch);

if (hasUpperCase && hasLowerCase)
{
return true;
}
}

return !hasUpperCase && !hasLowerCase;
}

internal AndConstraint<TAssertions> Be(string expected,
Func<EquivalencyOptions<string>, EquivalencyOptions<string>> config,
[StringSyntax("CompositeFormat")] string because = "", params object[] becauseArgs)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public partial class StringAssertionSpecs
public class BeLowerCased
{
[Fact]
public void When_a_lower_string_is_supposed_to_be_lower_it_should_succeed()
public void Lower_case_characters_are_okay()
{
// Arrange
string actual = "abc";
Expand All @@ -22,7 +22,7 @@ public void When_a_lower_string_is_supposed_to_be_lower_it_should_succeed()
}

[Fact]
public void When_an_empty_string_is_supposed_to_be_lower_it_should_succeed()
public void The_empty_string_is_okay()
{
// Arrange
string actual = "";
Expand All @@ -32,7 +32,7 @@ public void When_an_empty_string_is_supposed_to_be_lower_it_should_succeed()
}

[Fact]
public void When_a_non_lower_string_is_supposed_to_be_lower_it_should_fail()
public void Upper_case_characters_are_not_okay()
{
// Arrange
string actual = "ABC";
Expand All @@ -45,10 +45,10 @@ public void When_a_non_lower_string_is_supposed_to_be_lower_it_should_fail()
}

[Fact]
public void When_a_lower_case_string_with_numbers_is_supposed_to_be_in_lower_case_only_it_should_throw()
public void A_mixed_case_string_is_not_okay()
{
// Arrange
string actual = "a1";
string actual = "AbC";

// Act
Action act = () => actual.Should().BeLowerCased();
Expand All @@ -58,7 +58,27 @@ public void When_a_lower_case_string_with_numbers_is_supposed_to_be_in_lower_cas
}

[Fact]
public void When_a_non_lower_string_is_supposed_to_be_lower_it_should_fail_with_descriptive_message()
public void Lower_case_and_caseless_characters_are_okay()
{
// Arrange
string actual = "a1!";

// Act / Assert
actual.Should().BeLowerCased();
}

[Fact]
public void Caseless_characters_are_okay()
{
// Arrange
string actual = "1!漢字";

// Act / Assert
actual.Should().BeLowerCased();
}

[Fact]
public void The_assertion_fails_with_a_descriptive_message()
{
// Arrange
string actual = "ABC";
Expand All @@ -68,11 +88,11 @@ public void When_a_non_lower_string_is_supposed_to_be_lower_it_should_fail_with_

// Assert
act.Should().Throw<XunitException>().WithMessage(
"Expected all characters in actual to be lower cased because we want to test the failure message, but found \"ABC\".");
"Expected all alphabetic characters in actual to be lower cased because we want to test the failure message, but found \"ABC\".");
}

[Fact]
public void When_checking_for_a_lower_string_and_it_is_null_it_should_throw()
public void The_null_string_is_not_okay()
{
// Arrange
string nullString = null;
Expand All @@ -82,24 +102,24 @@ public void When_checking_for_a_lower_string_and_it_is_null_it_should_throw()

// Assert
act.Should().Throw<XunitException>().WithMessage(
"Expected all characters in nullString to be lower cased because strings should never be null, but found <null>.");
"Expected all alphabetic characters in nullString to be lower cased because strings should never be null, but found <null>.");
}
}

public class NotBeLowerCased
{
[Fact]
public void When_a_non_lower_string_is_supposed_to_be_upper_it_should_succeed()
public void A_mixed_case_string_is_okay()
{
// Arrange
string actual = "ABC";
string actual = "AbC";

// Act / Assert
actual.Should().NotBeLowerCased();
}

[Fact]
public void When_a_null_string_is_not_supposed_to_be_lower_it_should_succeed()
public void The_null_string_is_okay()
{
// Arrange
string actual = null;
Expand All @@ -109,7 +129,17 @@ public void When_a_null_string_is_not_supposed_to_be_lower_it_should_succeed()
}

[Fact]
public void When_a_lower_string_is_supposed_to_be_upper_it_should_throw()
public void The_empty_string_is_okay()
{
// Arrange
string actual = "";

// Act / Assert
actual.Should().NotBeLowerCased();
}

[Fact]
public void A_lower_case_string_is_not_okay()
{
// Arrange
string actual = "abc";
Expand All @@ -122,17 +152,40 @@ public void When_a_lower_string_is_supposed_to_be_upper_it_should_throw()
}

[Fact]
public void When_a_lower_case_string_with_numbers_is_not_supposed_to_be_in_lower_case_only_it_should_succeed()
public void Lower_case_characters_with_upper_case_characters_are_okay()
{
// Arrange
string actual = "Ab1!";

// Act / Assert
actual.Should().NotBeLowerCased();
}

[Fact]
public void All_cased_characters_being_lower_case_is_not_okay()
{
// Arrange
string actual = "a1b!";

// Act
Action act = () => actual.Should().NotBeLowerCased();

// Assert
act.Should().Throw<XunitException>();
}

[Fact]
public void Caseless_characters_are_okay()
{
// Arrange
string actual = "A1";
string actual = "1!漢字";

// Act / Assert
actual.Should().NotBeLowerCased();
}

[Fact]
public void When_a_lower_string_is_not_supposed_to_be_lower_it_should_fail_with_descriptive_message()
public void The_assertion_fails_with_a_descriptive_message()
{
// Arrange
string actual = "abc";
Expand All @@ -142,7 +195,7 @@ public void When_a_lower_string_is_not_supposed_to_be_lower_it_should_fail_with_

// Assert
act.Should().Throw<XunitException>().WithMessage(
"Did not expect any characters in actual to be lower cased because we want to test the failure message.");
"Expected some characters in actual to be upper-case because we want to test the failure message.");
}
}
}
Loading