Skip to content

Commit

Permalink
Improve correctness of textTransform: capitalize (#47219)
Browse files Browse the repository at this point in the history
Summary:
Pull Request resolved: #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
  • Loading branch information
javache authored and facebook-github-bot committed Oct 27, 2024
1 parent 33e1ae1 commit dc2000c
Show file tree
Hide file tree
Showing 5 changed files with 98 additions and 22 deletions.
3 changes: 1 addition & 2 deletions packages/react-native/Libraries/Text/RCTTextAttributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@

#import <React/RCTDynamicTypeRamp.h>
#import <React/RCTTextDecorationLineType.h>

#import "RCTTextTransform.h"
#import <React/RCTTextTransform.h>

NS_ASSUME_NONNULL_BEGIN

Expand Down
22 changes: 9 additions & 13 deletions packages/react-native/Libraries/Text/RCTTextAttributes.mm
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 49 additions & 0 deletions packages/react-native/React/Tests/Text/RCTTextAttributesTest.mm
Original file line number Diff line number Diff line change
@@ -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 <Foundation/Foundation.h>
#import <XCTest/XCTest.h>

#import <React/RCTTextAttributes.h>

@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
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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)
}
}

0 comments on commit dc2000c

Please sign in to comment.