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
26 changes: 26 additions & 0 deletions CaseConverter.Test/CaseConverterTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,32 @@ public void ToUppercaseFirstCharShouldUppercaseAnAstralFirstLetter()
Assert.AreEqual($"{DeseretCapitalLongI}abc", result);
}

[TestMethod]
[DataRow(" hello", "Hello")]
[DataRow("\thello", "Hello")]
[DataRow(" hello ", "Hello")]
public void ToUppercaseFirstCharShouldSkipLeadingWhitespace(string input, string expected)
{
Assert.AreEqual(expected, input.ToUppercaseFirstChar());
}

[TestMethod]
[DataRow(" Hello", "hello")]
[DataRow("\tHello", "hello")]
[DataRow(" Hello ", "hello")]
public void ToLowercaseFirstCharShouldSkipLeadingWhitespace(string input, string expected)
{
Assert.AreEqual(expected, input.ToLowercaseFirstChar());
}

[TestMethod]
public void FirstCharHelpersShouldCollapseRunsOfSpaces()
{
// Documented behaviour: both helpers collapse runs of spaces to one.
Assert.AreEqual("A b", "a b".ToUppercaseFirstChar());
Assert.AreEqual("a b", "A b".ToLowercaseFirstChar());
}

[TestMethod]
public void ToSnakeCaseShouldStillDropAstralCharactersThatAreNotLetters()
{
Expand Down
12 changes: 8 additions & 4 deletions CaseConverter/CaseConverter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,13 @@ private static bool IsWordBoundary(string input, int previousStart, int start, i
}

/// <summary>
/// Returns a copy of this string with the first character converted to lowercase.
/// Returns a copy of this string, trimmed and with runs of spaces collapsed to one, with the first character converted to lowercase.
/// </summary>
/// <param name="input">The string to convert.</param>
/// <returns>A new string with the first character converted to lowercase.</returns>
/// <remarks>
/// The string is trimmed before the first character is chosen, so leading whitespace does not
/// take the place of the first letter.
/// The first character is the first code point, so a letter outside the Basic Multilingual
/// Plane is case-mapped as a whole rather than through its high surrogate alone, which would
/// leave it unchanged.
Expand All @@ -158,22 +160,24 @@ private static bool IsWordBoundary(string input, int previousStart, int start, i
public static string ToLowercaseFirstChar(this string input)
{
Ensure.NotNull(input);
return CollapseSpaces(MapFirstCodePoint(input, static first => first.ToLowerInvariant())).Trim();
return MapFirstCodePoint(CollapseSpaces(input).Trim(), static first => first.ToLowerInvariant());
}

/// <summary>
/// Returns a copy of this string with the first character converted to uppercase.
/// Returns a copy of this string, trimmed and with runs of spaces collapsed to one, with the first character converted to uppercase.
/// </summary>
/// <param name="input">The string to convert.</param>
/// <returns>A new string with the first character converted to uppercase.</returns>
/// <remarks>
/// The string is trimmed before the first character is chosen, so leading whitespace does not
/// take the place of the first letter.
/// The first character is the first code point, for the same reason as in
/// <see cref="ToLowercaseFirstChar(string)"/>.
/// </remarks>
public static string ToUppercaseFirstChar(this string input)
{
Ensure.NotNull(input);
return CollapseSpaces(MapFirstCodePoint(input, static first => first.ToUpperInvariant())).Trim();
return MapFirstCodePoint(CollapseSpaces(input).Trim(), static first => first.ToUpperInvariant());
}

/// <summary>
Expand Down
Loading