mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-23 23:06:24 +00:00
[change] modernize ImageBackground
Rewrite ImageBackground to use function components and hooks. Rewrite the tests to replace enzyme with testing-library.
This commit is contained in:
@@ -1,36 +1,39 @@
|
||||
/* eslint-env jasmine, jest */
|
||||
|
||||
import Image from '../../Image';
|
||||
import ImageBackground from '..';
|
||||
import React from 'react';
|
||||
import { shallow } from 'enzyme';
|
||||
import { render } from '@testing-library/react';
|
||||
import Text from '../../Text';
|
||||
|
||||
function findImage(container) {
|
||||
return container.firstChild.firstChild;
|
||||
}
|
||||
|
||||
describe('components/ImageBackground', () => {
|
||||
describe('prop "children"', () => {
|
||||
it('render child content', () => {
|
||||
const component = shallow(
|
||||
test('render child content', () => {
|
||||
const { getByText } = render(
|
||||
<ImageBackground>
|
||||
<Text>Hello World!</Text>
|
||||
</ImageBackground>
|
||||
);
|
||||
expect(component.find(Text)).toBeDefined();
|
||||
expect(getByText('Hello World!')).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('prop "imageStyle"', () => {
|
||||
it('sets the style of the underlying Image', () => {
|
||||
test('sets the style of the underlying Image', () => {
|
||||
const imageStyle = { width: 40, height: 60 };
|
||||
const component = shallow(<ImageBackground imageStyle={imageStyle} />);
|
||||
expect(component.find(Image).prop('style')).toContain(imageStyle);
|
||||
const { container } = render(<ImageBackground imageStyle={imageStyle} />);
|
||||
expect(findImage(container).getAttribute('style')).toBe('height: 60px; width: 40px;');
|
||||
});
|
||||
});
|
||||
|
||||
describe('prop "style"', () => {
|
||||
it('sets the style of the container View', () => {
|
||||
test('sets the style of the container View', () => {
|
||||
const style = { margin: 40 };
|
||||
const component = shallow(<ImageBackground style={style} />);
|
||||
expect(component.prop('style')).toEqual(style);
|
||||
const { container } = render(<ImageBackground style={style} />);
|
||||
expect(container.firstChild.getAttribute('style')).toEqual('margin: 40px 40px 40px 40px;');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
+52
-46
@@ -10,11 +10,11 @@
|
||||
import type { ImageProps } from '../Image';
|
||||
import type { ViewProps } from '../View';
|
||||
|
||||
import ensureComponentIsNative from '../../modules/ensureComponentIsNative';
|
||||
import setAndForwardRef from '../../modules/setAndForwardRef';
|
||||
import Image from '../Image';
|
||||
import StyleSheet from '../StyleSheet';
|
||||
import View from '../View';
|
||||
import React from 'react';
|
||||
import React, { forwardRef, useImperativeHandle, useRef } from 'react';
|
||||
|
||||
type ImageBackgroundProps = {
|
||||
...ImageProps,
|
||||
@@ -28,52 +28,58 @@ const emptyObject = {};
|
||||
/**
|
||||
* Very simple drop-in replacement for <Image> which supports nesting views.
|
||||
*/
|
||||
class ImageBackground extends React.Component<ImageBackgroundProps> {
|
||||
setNativeProps(props: Object) {
|
||||
// Work-around flow
|
||||
const viewRef = this._viewRef;
|
||||
if (viewRef) {
|
||||
ensureComponentIsNative(viewRef);
|
||||
viewRef.setNativeProps(props);
|
||||
const ImageBackground = forwardRef<ImageBackgroundProps, *>((props, ref) => {
|
||||
const { children, style = emptyObject, imageStyle, imageRef, ...rest } = props;
|
||||
const { height, width } = StyleSheet.flatten(style);
|
||||
|
||||
const containerRef = useRef(null);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
setNativeProps(props) {
|
||||
if (containerRef.current != null) {
|
||||
containerRef.current.setNativeProps(props);
|
||||
}
|
||||
}
|
||||
}),
|
||||
[containerRef]
|
||||
);
|
||||
|
||||
const setRef = setAndForwardRef({
|
||||
getForwardedRef: () => ref,
|
||||
setLocalRef: c => {
|
||||
containerRef.current = c;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
_viewRef: ?View = null;
|
||||
return (
|
||||
<View ref={setRef} style={style}>
|
||||
<Image
|
||||
{...rest}
|
||||
ref={imageRef}
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
{
|
||||
// Temporary Workaround:
|
||||
// Current (imperfect yet) implementation of <Image> overwrites width and height styles
|
||||
// (which is not quite correct), and these styles conflict with explicitly set styles
|
||||
// of <ImageBackground> and with our internal layout model here.
|
||||
// So, we have to proxy/reapply these styles explicitly for actual <Image> component.
|
||||
// This workaround should be removed after implementing proper support of
|
||||
// intrinsic content size of the <Image>.
|
||||
width,
|
||||
height,
|
||||
zIndex: -1
|
||||
},
|
||||
imageStyle
|
||||
]}
|
||||
/>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
});
|
||||
|
||||
_captureRef = (ref: View) => {
|
||||
this._viewRef = ref;
|
||||
};
|
||||
|
||||
render() {
|
||||
const { children, style = emptyObject, imageStyle, imageRef, ...props } = this.props;
|
||||
const { height, width } = StyleSheet.flatten(style);
|
||||
|
||||
return (
|
||||
<View ref={this._captureRef} style={style}>
|
||||
<Image
|
||||
{...props}
|
||||
ref={imageRef}
|
||||
style={[
|
||||
StyleSheet.absoluteFill,
|
||||
{
|
||||
// Temporary Workaround:
|
||||
// Current (imperfect yet) implementation of <Image> overwrites width and height styles
|
||||
// (which is not quite correct), and these styles conflict with explicitly set styles
|
||||
// of <ImageBackground> and with our internal layout model here.
|
||||
// So, we have to proxy/reapply these styles explicitly for actual <Image> component.
|
||||
// This workaround should be removed after implementing proper support of
|
||||
// intrinsic content size of the <Image>.
|
||||
width,
|
||||
height,
|
||||
zIndex: -1
|
||||
},
|
||||
imageStyle
|
||||
]}
|
||||
/>
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
ImageBackground.displayName = 'ImageBackground';
|
||||
|
||||
export default ImageBackground;
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
|
||||
type Args = $ReadOnly<{|
|
||||
getForwardedRef: () => ?React.Ref<any>,
|
||||
setLocalRef: (ref: React.ElementRef<any>) => mixed
|
||||
|}>;
|
||||
|
||||
/**
|
||||
* This is a helper function for when a component needs to be able to forward a ref
|
||||
* to a child component, but still needs to have access to that component as part of
|
||||
* its implementation.
|
||||
*
|
||||
* Its main use case is in wrappers for native components.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* function MyView(props) {
|
||||
* const ref = useRef(null);
|
||||
*
|
||||
* function setRef = setAndForwardRef({
|
||||
* getForwardedRef: () => props.forwardedRef,
|
||||
* setLocalRef: localRef => {
|
||||
* ref.current = localRef;
|
||||
* },
|
||||
* });
|
||||
*
|
||||
* return <View ref={setRef} />;
|
||||
* }
|
||||
*
|
||||
* const MyViewWithRef = React.forwardRef((props, ref) => (
|
||||
* <MyView {...props} forwardedRef={ref} />
|
||||
* ));
|
||||
*/
|
||||
|
||||
export default function setAndForwardRef({ getForwardedRef, setLocalRef }: Args) {
|
||||
return function forwardRef(ref: React.ElementRef<any>) {
|
||||
const forwardedRef = getForwardedRef();
|
||||
setLocalRef(ref);
|
||||
|
||||
// Forward to user ref prop (if one has been specified)
|
||||
if (typeof forwardedRef === 'function') {
|
||||
// Handle function-based refs. String-based refs are handled as functions.
|
||||
forwardedRef(ref);
|
||||
} else if (typeof forwardedRef === 'object' && forwardedRef != null) {
|
||||
// Handle createRef-based refs
|
||||
forwardedRef.current = ref;
|
||||
}
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user