Skip to content

Commit 6913cdb

Browse files
add more tests in processbackgroundimage
1 parent 8bbcc0a commit 6913cdb

File tree

2 files changed

+131
-10
lines changed

2 files changed

+131
-10
lines changed

packages/react-native/Libraries/StyleSheet/__tests__/processBackgroundImage-test.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -727,4 +727,124 @@ describe('processBackgroundImage', () => {
727727
{color: processColor('blue'), position: 1},
728728
]);
729729
});
730+
731+
it('should return empty array for invalid transition hints', () => {
732+
let result = processBackgroundImage('linear-gradient(red, 40, blue)');
733+
expect(result).toEqual([]);
734+
735+
result = processBackgroundImage('linear-gradient(red, 40px, blue)');
736+
expect(result).toEqual([]);
737+
738+
// Multiple hints in a row
739+
result = processBackgroundImage('linear-gradient(red, 20%, 40%, blue)');
740+
expect(result).toEqual([]);
741+
742+
// Invalid object syntax
743+
result = processBackgroundImage([
744+
{
745+
type: 'linearGradient',
746+
colorStops: [{color: 'red'}, {positions: ['40']}, {color: 'blue'}],
747+
},
748+
]);
749+
expect(result).toEqual([]);
750+
});
751+
752+
it('should process complex gradients with multiple transitioon hints', () => {
753+
const input = 'linear-gradient(red, 20%, blue, 60%, green, 80%, yellow)';
754+
const result = processBackgroundImage(input);
755+
expect(result[0].colorStops).toEqual([
756+
{color: processColor('red'), position: 0},
757+
{color: null, position: 0.2},
758+
{color: processColor('blue'), position: 0.4},
759+
{color: null, position: 0.6},
760+
{color: processColor('green'), position: 0.7},
761+
{color: null, position: 0.8},
762+
{color: processColor('yellow'), position: 1},
763+
]);
764+
});
765+
766+
it('should process object syntax with multiple hints', () => {
767+
const input = [
768+
{
769+
type: 'linearGradient',
770+
direction: 'to right',
771+
colorStops: [
772+
{color: 'red'},
773+
{positions: ['20%']},
774+
{color: 'blue'},
775+
{positions: ['60%']},
776+
{color: 'green'},
777+
{positions: ['80%']},
778+
{color: 'yellow'},
779+
],
780+
},
781+
];
782+
const result = processBackgroundImage(input);
783+
expect(result[0].colorStops).toEqual([
784+
{color: processColor('red'), position: 0},
785+
{color: null, position: 0.2},
786+
{color: processColor('blue'), position: 0.4},
787+
{color: null, position: 0.6},
788+
{color: processColor('green'), position: 0.7},
789+
{color: null, position: 0.8},
790+
{color: processColor('yellow'), position: 1},
791+
]);
792+
});
793+
794+
it('should process hints with explicit color stops', () => {
795+
const input = 'linear-gradient(red 0%, 25%, blue 50%, 75%, green 100%)';
796+
const result = processBackgroundImage(input);
797+
expect(result[0].colorStops).toEqual([
798+
{color: processColor('red'), position: 0},
799+
{color: null, position: 0.25},
800+
{color: processColor('blue'), position: 0.5},
801+
{color: null, position: 0.75},
802+
{color: processColor('green'), position: 1},
803+
]);
804+
});
805+
806+
it('should handle very complex gradients', () => {
807+
const input = `linear-gradient(
808+
red 0%,
809+
20% ,
810+
blue 30%,
811+
45%,
812+
green 50%,
813+
65%,
814+
yellow 70% ,
815+
85%,
816+
purple 100%
817+
)`;
818+
const result = processBackgroundImage(input);
819+
expect(result[0].colorStops).toEqual([
820+
{color: processColor('red'), position: 0},
821+
{color: null, position: 0.2},
822+
{color: processColor('blue'), position: 0.3},
823+
{color: null, position: 0.45},
824+
{color: processColor('green'), position: 0.5},
825+
{color: null, position: 0.65},
826+
{color: processColor('yellow'), position: 0.7},
827+
{color: null, position: 0.85},
828+
{color: processColor('purple'), position: 1},
829+
]);
830+
});
831+
832+
it('should handle multiple gradients with hints', () => {
833+
const input = `
834+
linear-gradient(red, 30%, blue),
835+
linear-gradient(to right, green, 60%, yellow)
836+
`;
837+
const result = processBackgroundImage(input);
838+
expect(result).toHaveLength(2);
839+
expect(result[0].colorStops).toEqual([
840+
{color: processColor('red'), position: 0},
841+
{color: null, position: 0.3},
842+
{color: processColor('blue'), position: 1},
843+
]);
844+
expect(result[1].colorStops).toEqual([
845+
{color: processColor('green'), position: 0},
846+
{color: null, position: 0.6},
847+
{color: processColor('yellow'), position: 1},
848+
]);
849+
});
730850
});

packages/react-native/Libraries/StyleSheet/processBackgroundImage.js

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ type LinearGradientDirection =
2222
| {type: 'angle', value: number}
2323
| {type: 'keyword', value: string};
2424

25+
// null color stops indicate that the transition hint syntax is used. e.g. red, 20%, blue
2526
type ColorStopColor = ProcessedColorValue | null;
2627

2728
type ParsedGradientValue = {
@@ -158,13 +159,6 @@ function parseCSSLinearGradient(
158159
let direction: LinearGradientDirection = DEFAULT_DIRECTION;
159160
const trimmedDirection = parts[0].trim().toLowerCase();
160161

161-
// matches individual color stops in a gradient function
162-
// supports various color formats: named colors, hex colors, rgb(a), and hsl(a)
163-
// e.g. "red 20%", "blue 50%", "rgba(0, 0, 0, 0.5) 30% 50%"
164-
// TODO: does not support color hint syntax yet. It is WIP.
165-
const colorStopRegex =
166-
/\s*((?:(?:rgba?|hsla?)\s*\([^)]+\))|#[0-9a-fA-F]+|[a-zA-Z]+)(?:\s+(-?[0-9.]+%?)(?:\s+(-?[0-9.]+%?))?)?\s*/gi;
167-
168162
if (ANGLE_UNIT_REGEX.test(trimmedDirection)) {
169163
const parsedAngle = getAngleInDegrees(trimmedDirection);
170164
if (parsedAngle != null) {
@@ -186,15 +180,13 @@ function parseCSSLinearGradient(
186180
// If a direction is invalid, return an empty array and do not apply any gradient. Same as web.
187181
return [];
188182
}
189-
} else if (!colorStopRegex.test(trimmedDirection)) {
190-
// If first part is not an angle/direction or a color stop, return an empty array and do not apply any gradient. Same as web.
191-
return [];
192183
}
193184

194185
const colorStopsString = parts.join(',');
195186
const colorStops = [];
196187
// split by comma, but not if it's inside a parentheses. e.g. red, rgba(0, 0, 0, 0.5), green => ["red", "rgba(0, 0, 0, 0.5)", "green"]
197188
const stops = colorStopsString.split(/,(?![^(]*\))/);
189+
let lastStop = null;
198190
for (const stop of stops) {
199191
const trimmedStop = stop.trim().toLowerCase();
200192
// Match function like pattern or single words
@@ -250,6 +242,14 @@ function parseCSSLinearGradient(
250242
// Case 4: [position] => transition hint syntax
251243
else if (colorStopParts.length === 1) {
252244
if (colorStopParts[0].endsWith('%')) {
245+
if (
246+
lastStop != null &&
247+
lastStop.length === 1 &&
248+
lastStop[0].endsWith('%')
249+
) {
250+
// If the last stop is a transition hint syntax, return an empty array and do not apply any gradient. Same as web.
251+
return [];
252+
}
253253
colorStops.push({
254254
color: null,
255255
position: parseFloat(colorStopParts[0]) / 100,
@@ -269,6 +269,7 @@ function parseCSSLinearGradient(
269269
// If a color stop is invalid, return an empty array and do not apply any gradient. Same as web.
270270
return [];
271271
}
272+
lastStop = colorStopParts;
272273
}
273274

274275
const fixedColorStops = getFixedColorStops(colorStops);

0 commit comments

Comments
 (0)