Skip to content

Commit 008d479

Browse files
authored
Feat/deep merge config optimizations (#224)
* fix: implement CSS variable filtering for inline styles with rightIsInline parameter - Added comprehensive test suite (32 tests) for rightIsInline functionality - Fixed CSS variable filtering in deepMergeConfig for inline styles - Added filterCssVariables() to recursively remove VAR_SYMBOL objects - Added flattenStyleArray() to optimize style arrays when possible - Fixed null/undefined handling for inline styles - Fixed important styles merging with existing style arrays - All 960 existing tests + 32 new tests passing The rightIsInline parameter serves two critical purposes: 1. Strips CSS variable objects (VAR_SYMBOL) from inline styles to prevent runtime errors in React Native 2. Cleans up source properties (e.g., className) when mapped to targets This fix ensures that CSS variable objects never leak into React Native component props, which would cause crashes or unexpected behavior. * refactor: harden style filtering with security and performance fixes Critical fixes: - Add null safety checks to prevent crashes - Use hasOwnProperty to prevent prototype pollution attacks - Add depth limit (100) to prevent stack overflow on deep nesting - Fix falsy value filtering (preserve 0, false, empty strings) - Use Object.keys() to properly filter Symbol properties - Replace spread operator with reduce() for large array performance Performance improvements: - Extract hasNonOverlappingProperties() helper (DRY) - Single-pass array filtering in filterCssVariables() - Early exit in flattenStyleArray() - Handle arrays with 10k+ items without stack overflow Security hardening: - Prevent prototype pollution via for...in loops - Filter inherited properties with hasOwnProperty checks - Remove Symbol properties for React Native compatibility All 960 existing + 32 new tests passing with no regressions. * test: add comprehensive edge case tests for security hardening Red team validation tests covering: - Circular references with depth limit protection - Sparse arrays with undefined holes - Objects with only Symbol properties (RN filtering) - Mixed null/undefined/falsy values preservation - Frozen/sealed objects handling - Very deep nesting (105 levels) beyond limit - Prototype pollution prevention (__proto__) - Arrays with custom properties - Empty arrays and objects - Numeric string keys All 10 edge case tests pass, validating: ✅ No crashes on malformed input ✅ Prototype pollution prevention ✅ Circular reference handling ✅ Stack overflow prevention ✅ Symbol filtering for React Native compatibility ✅ Null safety across all code paths Total test coverage: 970 passing tests (42 rightIsInline-specific) * docs: enhance JSDoc for style filtering functions - Add comprehensive JSDoc for filterCssVariables with explicit return type - Clarify hasNonOverlappingProperties one-directional design is intentional - Document empty array handling convention in flattenStyleArray - Address GitHub Copilot review concerns with improved documentation All concerns were about documentation clarity, not logic bugs. Code remains functionally identical with 970 tests passing.
1 parent 2c68396 commit 008d479

File tree

3 files changed

+1105
-32
lines changed

3 files changed

+1105
-32
lines changed

src/__tests__/native/className-with-style.test.tsx

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { render } from "@testing-library/react-native";
22
import { Text } from "react-native-css/components/Text";
3+
import { View } from "react-native-css/components/View";
34
import { registerCSS, testID } from "react-native-css/jest";
45

56
test("className with inline style props should coexist when different properties", () => {
@@ -46,3 +47,44 @@ test("only inline style should not create array", () => {
4647
// Only inline style should be a flat object
4748
expect(component.props.style).toEqual({ color: "blue" });
4849
});
50+
51+
test("important should overwrite the inline style", () => {
52+
registerCSS(`.text-red\\! { color: red !important; }`);
53+
54+
const component = render(
55+
<Text testID={testID} className="text-red!" style={{ color: "blue" }} />,
56+
).getByTestId(testID);
57+
58+
expect(component.props.style).toEqual({ color: "#f00" });
59+
});
60+
61+
test("View with multiple className properties where inline style takes precedence", () => {
62+
registerCSS(`
63+
.px-4 { padding-left: 16px; padding-right: 16px; }
64+
.pt-4 { padding-top: 16px; }
65+
.mb-4 { margin-bottom: 16px; }
66+
`);
67+
68+
const component = render(
69+
<View
70+
testID={testID}
71+
className="px-4 pt-4 mb-4"
72+
style={{ width: 300, paddingRight: 0 }}
73+
/>,
74+
).getByTestId(testID);
75+
76+
// Inline style should override paddingRight from px-4 class
77+
// Other className styles should be preserved in array
78+
expect(component.props.style).toEqual([
79+
{
80+
paddingLeft: 16,
81+
paddingRight: 16,
82+
paddingTop: 16,
83+
marginBottom: 16,
84+
},
85+
{
86+
width: 300,
87+
paddingRight: 0,
88+
},
89+
]);
90+
});

0 commit comments

Comments
 (0)