Skip to content

Commit 85e07db

Browse files
Abbondanzometa-codesync[bot]
authored andcommitted
Add tests for the PlatformColor lazy color fallback (#57708)
Summary: Pull Request resolved: #57708 An implementation for the RFC in react-native-community/discussions-and-proposals#1008 Adds unit and integration coverage for the PlatformColor raw-color fallback introduced in the parent diff: - CSSColorTest.cpp: additional CSS `<color>` parse cases pinning the shared parser used on the native fallback path - PlatformColorFallback-itest.js: JS integration test for token-miss -> fallback Changelog: [Internal] Reviewed By: christophpurrer Differential Revision: D111917092 fbshipit-source-id: 412387d0d598b5556cf7a3eafbc0da60ceaf2725
1 parent 31e119a commit 85e07db

2 files changed

Lines changed: 185 additions & 0 deletions

File tree

Lines changed: 149 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,149 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*
7+
* @flow strict-local
8+
* @format
9+
* @oncall react_native
10+
*/
11+
12+
import '@react-native/fantom/src/setUpDefaultReactNativeEnvironment';
13+
14+
import type {ColorValue} from 'react-native';
15+
16+
import * as Fantom from '@react-native/fantom';
17+
import * as React from 'react';
18+
import {PlatformColor, View} from 'react-native';
19+
20+
const processColor = require('../processColor').default;
21+
22+
// Fantom runs as `Platform.OS === 'android'` with no host resource system, so
23+
// PlatformColor tokens never resolve and the fallback is carried through, not
24+
// applied (the real miss -> fallback visual is covered by RNTester screenshots).
25+
// These tests verify each fallback format renders to the expected color and that
26+
// the raw fallback string survives the color pipeline into the view props.
27+
28+
function renderedBackgroundColor(color: ColorValue): unknown {
29+
const root = Fantom.createRoot();
30+
Fantom.runTask(() => {
31+
root.render(<View style={{backgroundColor: color}} />);
32+
});
33+
return root.getRenderedOutput({props: ['backgroundColor']}).toJSX();
34+
}
35+
36+
describe('PlatformColor lazy fallback', () => {
37+
describe('fallback color-format strings render to the expected color', () => {
38+
// Opaque formats only: alpha serialization is exercised via the pipeline
39+
// assertions below, keeping these rendered-output checks deterministic.
40+
const cases: Array<[string, string, string]> = [
41+
['#RRGGBB hex', '#FF0000', 'rgba(255, 0, 0, 1)'],
42+
['rgb()', 'rgb(255, 0, 128)', 'rgba(255, 0, 128, 1)'],
43+
['hsl()', 'hsl(120, 100%, 50%)', 'rgba(0, 255, 0, 1)'],
44+
['named color', 'cornflowerblue', 'rgba(100, 149, 237, 1)'],
45+
];
46+
for (const [name, input, expected] of cases) {
47+
it(`renders ${name}`, () => {
48+
expect(renderedBackgroundColor(input)).toEqual(
49+
<rn-view backgroundColor={expected} />,
50+
);
51+
});
52+
}
53+
});
54+
55+
// The `PlatformColor()` arguments must be literals (enforced by the
56+
// @react-native/platform-colors lint rule), so each case is spelled out.
57+
describe('PlatformColor carries the raw, unprocessed fallback', () => {
58+
it('carries a #RRGGBB fallback', () => {
59+
expect(
60+
processColor(
61+
PlatformColor('?attr/nonExistentColor', {fallback: '#FF0000'}),
62+
),
63+
).toEqual({
64+
resource_paths: ['?attr/nonExistentColor'],
65+
fallback: '#FF0000',
66+
});
67+
});
68+
69+
it('carries a #RRGGBBAA fallback', () => {
70+
expect(
71+
processColor(
72+
PlatformColor('?attr/nonExistentColor', {fallback: '#FF000080'}),
73+
),
74+
).toEqual({
75+
resource_paths: ['?attr/nonExistentColor'],
76+
fallback: '#FF000080',
77+
});
78+
});
79+
80+
it('carries an rgb() fallback', () => {
81+
expect(
82+
processColor(
83+
PlatformColor('?attr/nonExistentColor', {
84+
fallback: 'rgb(255, 0, 128)',
85+
}),
86+
),
87+
).toEqual({
88+
resource_paths: ['?attr/nonExistentColor'],
89+
fallback: 'rgb(255, 0, 128)',
90+
});
91+
});
92+
93+
it('carries an rgba() fallback', () => {
94+
expect(
95+
processColor(
96+
PlatformColor('?attr/nonExistentColor', {
97+
fallback: 'rgba(0, 128, 255, 0.7)',
98+
}),
99+
),
100+
).toEqual({
101+
resource_paths: ['?attr/nonExistentColor'],
102+
fallback: 'rgba(0, 128, 255, 0.7)',
103+
});
104+
});
105+
106+
it('carries an hsl() fallback', () => {
107+
expect(
108+
processColor(
109+
PlatformColor('?attr/nonExistentColor', {
110+
fallback: 'hsl(120, 100%, 50%)',
111+
}),
112+
),
113+
).toEqual({
114+
resource_paths: ['?attr/nonExistentColor'],
115+
fallback: 'hsl(120, 100%, 50%)',
116+
});
117+
});
118+
119+
it('carries an hsla() fallback', () => {
120+
expect(
121+
processColor(
122+
PlatformColor('?attr/nonExistentColor', {
123+
fallback: 'hsla(280, 100%, 60%, 0.8)',
124+
}),
125+
),
126+
).toEqual({
127+
resource_paths: ['?attr/nonExistentColor'],
128+
fallback: 'hsla(280, 100%, 60%, 0.8)',
129+
});
130+
});
131+
132+
it('carries a named-color fallback', () => {
133+
expect(
134+
processColor(
135+
PlatformColor('?attr/nonExistentColor', {fallback: 'cornflowerblue'}),
136+
),
137+
).toEqual({
138+
resource_paths: ['?attr/nonExistentColor'],
139+
fallback: 'cornflowerblue',
140+
});
141+
});
142+
});
143+
144+
it('omits the fallback field when none is provided (miss stays transparent)', () => {
145+
expect(processColor(PlatformColor('?attr/nonExistentColor'))).toEqual({
146+
resource_paths: ['?attr/nonExistentColor'],
147+
});
148+
});
149+
});

packages/react-native/ReactCommon/react/renderer/css/tests/CSSColorTest.cpp

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <gtest/gtest.h>
99
#include <react/renderer/css/CSSColor.h>
1010
#include <react/renderer/css/CSSValueParser.h>
11+
#include <string_view>
1112

1213
namespace facebook::react {
1314

@@ -492,4 +493,39 @@ TEST(CSSColor, constexpr_values) {
492493
parseCSSProperty<CSSColor>("rgb(255, 255, 255)");
493494
}
494495

496+
// The PlatformColor fallback is a raw CSS <color> parsed by this same parser on
497+
// a token miss. Pins the promised fallback formats to their RGBA, and checks
498+
// that unparseable input yields std::monostate so native degrades to
499+
// transparent.
500+
TEST(CSSColor, platform_color_fallback_contract) {
501+
auto expectColor = [](std::string_view input, int r, int g, int b, int a) {
502+
auto value = parseCSSProperty<CSSColor>(input);
503+
ASSERT_TRUE(std::holds_alternative<CSSColor>(value)) << input;
504+
EXPECT_EQ(static_cast<int>(std::get<CSSColor>(value).r), r) << input;
505+
EXPECT_EQ(static_cast<int>(std::get<CSSColor>(value).g), g) << input;
506+
EXPECT_EQ(static_cast<int>(std::get<CSSColor>(value).b), b) << input;
507+
EXPECT_EQ(static_cast<int>(std::get<CSSColor>(value).a), a) << input;
508+
};
509+
510+
expectColor("#0f0", 0, 255, 0, 255); // #RGB
511+
expectColor("#ff0000", 255, 0, 0, 255); // #RRGGBB
512+
expectColor(
513+
"#ff000080", 255, 0, 0, 128); // #RRGGBBAA — alpha is the LAST byte
514+
expectColor("rgb(0, 128, 255)", 0, 128, 255, 255);
515+
expectColor("rgba(0, 128, 255, 0.5)", 0, 128, 255, 128);
516+
expectColor("hsl(120, 100%, 50%)", 0, 255, 0, 255);
517+
expectColor("hsla(120, 100%, 50%, 0.5)", 0, 255, 0, 128);
518+
expectColor("cornflowerblue", 100, 149, 237, 255);
519+
expectColor("transparent", 0, 0, 0, 0);
520+
521+
EXPECT_TRUE(
522+
std::holds_alternative<std::monostate>(parseCSSProperty<CSSColor>("")));
523+
EXPECT_TRUE(
524+
std::holds_alternative<std::monostate>(
525+
parseCSSProperty<CSSColor>("not-a-color")));
526+
EXPECT_TRUE(
527+
std::holds_alternative<std::monostate>(
528+
parseCSSProperty<CSSColor>("#GG0000")));
529+
}
530+
495531
} // namespace facebook::react

0 commit comments

Comments
 (0)