From dc2000c8750f42b2f01bdad75455750814d567c6 Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Sat, 26 Oct 2024 17:57:26 -0700 Subject: [PATCH] Improve correctness of textTransform: capitalize (#47219) Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/47219 We received reports that textTransform does not correctly match the web behaviour for capitalize, eg. capitalized characters should not be lowercased when using `capitalize`. Example input: 'hello WORLD', should become 'Hello WORLD'. Changelog: [General][Fixed] TextTransform: capitalize better reflects the web behaviour Reviewed By: NickGerleman Differential Revision: D65023821 fbshipit-source-id: 8ba5fdbd7afb1460193bf82a2f4021c3aff2110a --- .../Libraries/Text/RCTTextAttributes.h | 3 +- .../Libraries/Text/RCTTextAttributes.mm | 22 ++++----- .../React/Tests/Text/RCTTextAttributesTest.mm | 49 +++++++++++++++++++ .../react/views/text/TextTransform.java | 9 +--- .../react/views/text/TextTransformTest.kt | 37 ++++++++++++++ 5 files changed, 98 insertions(+), 22 deletions(-) create mode 100644 packages/react-native/React/Tests/Text/RCTTextAttributesTest.mm create mode 100644 packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/text/TextTransformTest.kt diff --git a/packages/react-native/Libraries/Text/RCTTextAttributes.h b/packages/react-native/Libraries/Text/RCTTextAttributes.h index c6923928d2d224..1b72abe51df44e 100644 --- a/packages/react-native/Libraries/Text/RCTTextAttributes.h +++ b/packages/react-native/Libraries/Text/RCTTextAttributes.h @@ -9,8 +9,7 @@ #import #import - -#import "RCTTextTransform.h" +#import NS_ASSUME_NONNULL_BEGIN diff --git a/packages/react-native/Libraries/Text/RCTTextAttributes.mm b/packages/react-native/Libraries/Text/RCTTextAttributes.mm index 57283d1820a60c..43dd1f7c18ee0e 100644 --- a/packages/react-native/Libraries/Text/RCTTextAttributes.mm +++ b/packages/react-native/Libraries/Text/RCTTextAttributes.mm @@ -278,19 +278,15 @@ - (UIColor *)effectiveBackgroundColor static NSString *capitalizeText(NSString *text) { - NSArray *words = [text componentsSeparatedByString:@" "]; - NSMutableArray *newWords = [NSMutableArray new]; - NSNumberFormatter *num = [NSNumberFormatter new]; - for (NSString *item in words) { - NSString *word; - if ([item length] > 0 && [num numberFromString:[item substringWithRange:NSMakeRange(0, 1)]] == nil) { - word = [item capitalizedString]; - } else { - word = [item lowercaseString]; - } - [newWords addObject:word]; - } - return [newWords componentsJoinedByString:@" "]; + NSMutableString *result = [[NSMutableString alloc] initWithString:text]; + [result + enumerateSubstringsInRange:NSMakeRange(0, text.length) + options:NSStringEnumerationByWords + usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) { + [result replaceCharactersInRange:NSMakeRange(substringRange.location, 1) + withString:[[substring substringToIndex:1] uppercaseString]]; + }]; + return result; } - (NSString *)applyTextAttributesToText:(NSString *)text diff --git a/packages/react-native/React/Tests/Text/RCTTextAttributesTest.mm b/packages/react-native/React/Tests/Text/RCTTextAttributesTest.mm new file mode 100644 index 00000000000000..227cd12beb4335 --- /dev/null +++ b/packages/react-native/React/Tests/Text/RCTTextAttributesTest.mm @@ -0,0 +1,49 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +#import +#import + +#import + +@interface RCTTextAttributesTest : XCTestCase + +@end + +@implementation RCTTextAttributesTest + +- (void)testCapitalize +{ + RCTTextAttributes *attrs = [RCTTextAttributes new]; + attrs.textTransform = RCTTextTransformCapitalize; + + NSString *input = @"hello WORLD from ReAcT nAtIvE 2a !b c"; + NSString *output = @"Hello WORLD From ReAcT NAtIvE 2a !B C"; + XCTAssertEqualObjects([attrs applyTextAttributesToText:input], output); +} + +- (void)testUppercase +{ + RCTTextAttributes *attrs = [RCTTextAttributes new]; + attrs.textTransform = RCTTextTransformUppercase; + + NSString *input = @"hello WORLD from ReAcT nAtIvE 2a !b c"; + NSString *output = @"HELLO WORLD FROM REACT NATIVE 2A !B C"; + XCTAssertEqualObjects([attrs applyTextAttributesToText:input], output); +} + +- (void)testLowercase +{ + RCTTextAttributes *attrs = [RCTTextAttributes new]; + attrs.textTransform = RCTTextTransformLowercase; + + NSString *input = @"hello WORLD from ReAcT nAtIvE 2a !b c"; + NSString *output = @"hello world from react native 2a !b c"; + XCTAssertEqualObjects([attrs applyTextAttributesToText:input], output); +} + +@end diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextTransform.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextTransform.java index 364b52401c8198..6670ec5247e5f4 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextTransform.java +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/text/TextTransform.java @@ -48,13 +48,8 @@ private static String capitalize(String text) { StringBuilder res = new StringBuilder(text.length()); int start = wordIterator.first(); for (int end = wordIterator.next(); end != BreakIterator.DONE; end = wordIterator.next()) { - String word = text.substring(start, end); - if (Character.isLetterOrDigit(word.charAt(0))) { - res.append(Character.toUpperCase(word.charAt(0))); - res.append(word.substring(1).toLowerCase()); - } else { - res.append(word); - } + res.append(Character.toUpperCase(text.charAt(start))); + res.append(text.substring(start + 1, end)); start = end; } diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/text/TextTransformTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/text/TextTransformTest.kt new file mode 100644 index 00000000000000..6bd1c2dbe09db5 --- /dev/null +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/text/TextTransformTest.kt @@ -0,0 +1,37 @@ +/* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +package com.facebook.react.views.text + +import org.assertj.core.api.Assertions.assertThat +import org.junit.Test +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class TextTransformTest { + @Test + fun textTransformCapitalize() { + val input = "hello WORLD from ReAcT nAtIvE 2a !b c" + val output = "Hello WORLD From ReAcT NAtIvE 2a !B C" + assertThat(TextTransform.apply(input, TextTransform.CAPITALIZE)).isEqualTo(output) + } + + @Test + fun textTransformUppercase() { + val input = "hello WORLD from ReAcT nAtIvE 2a !b c" + val output = "HELLO WORLD FROM REACT NATIVE 2A !B C" + assertThat(TextTransform.apply(input, TextTransform.UPPERCASE)).isEqualTo(output) + } + + @Test + fun textTransformLowercase() { + val input = "hello WORLD from ReAcT nAtIvE 2a !b c" + val output = "hello world from react native 2a !b c" + assertThat(TextTransform.apply(input, TextTransform.LOWERCASE)).isEqualTo(output) + } +}