Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/**
* 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.
*
* @flow strict-local
* @format
*/

'use strict';

import * as React from 'react';

const flattenStyle = require('../../../StyleSheet/flattenStyle').default;
const RefreshControl = require('../../RefreshControl/RefreshControl').default;
const ScrollView = require('../ScrollView').default;
const ReactTestRenderer = require('react-test-renderer');

// This test is about the element tree `ScrollView.render()` builds, so the
// default component mocks have to be turned off.
jest.unmock('react-native/Libraries/Components/ScrollView/ScrollView');
jest.unmock('react-native/Libraries/Components/RefreshControl/RefreshControl');
// On Android a `ScrollView` with a `RefreshControl` is wrapped in an
// `AndroidSwipeRefreshLayout`, and `style` is split across the two nodes.
jest.mock('../../../Utilities/Platform', () =>
// $FlowFixMe[missing-platform-support]
require('../../../Utilities/Platform.android'),
);

async function renderScrollView(style: $FlowFixMe): Promise<$FlowFixMe> {
let testRenderer: $FlowFixMe = null;
await ReactTestRenderer.act(() => {
testRenderer = ReactTestRenderer.create(
<ScrollView
style={style}
refreshControl={
<RefreshControl refreshing={false} onRefresh={() => {}} />
}
/>,
);
});
return testRenderer.toJSON();
}

describe('ScrollView with a RefreshControl on Android', () => {
it('applies zIndex to the wrapper, not to the inner ScrollView', async () => {
const wrapper = await renderScrollView({zIndex: 7});

// `zIndex` has to reach the wrapper: that is the node parented by the
// user's view, and on Android z-ordering is applied by the parent.
expect(wrapper.type).toBe('AndroidSwipeRefreshLayout');
expect(flattenStyle(wrapper.props.style)?.zIndex).toBe(7);

// The inner scroll view is an only child, so a `zIndex` left here could
// never affect stacking.
const scrollView = wrapper.children[0];
expect(scrollView.type).toBe('RCTScrollView');
expect(flattenStyle(scrollView.props.style)?.zIndex).toBeUndefined();
});

it('keeps non-layout style on the inner ScrollView', async () => {
const wrapper = await renderScrollView({backgroundColor: 'red'});

expect(flattenStyle(wrapper.props.style)?.backgroundColor).toBeUndefined();
expect(flattenStyle(wrapper.children[0].props.style)?.backgroundColor).toBe(
'red',
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,18 @@ test('does not copy values to both returned objects', () => {
`);
});

test('splits zIndex into the outer style', () => {
// On Android a ScrollView with a RefreshControl is wrapped in an
// AndroidSwipeRefreshLayout, and the outer style is applied to that wrapper.
// `zIndex` has to travel outwards along with the other positioning props,
// because the wrapper is the node that participates in the parent's
// stacking context.
const style = {zIndex: 1, top: 0, backgroundColor: 'red', padding: 8};
const {outer, inner} = splitLayoutProps(style);
expect(outer).toEqual({zIndex: 1, top: 0});
expect(inner).toEqual({backgroundColor: 'red', padding: 8});
});

test('returns null values if argument is null', () => {
const {outer, inner} = splitLayoutProps(null);
expect(outer).toBe(null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ export default function splitLayoutProps(props: ?____ViewStyle_Internal): {
case 'minWidth':
case 'maxWidth':
case 'position':
case 'zIndex':
case 'left':
case 'right':
case 'bottom':
Expand Down
Loading