[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:
Nicolas Gallagher
2020-02-04 11:28:55 -08:00
parent 840a2e91d4
commit 5b40b9d6aa
3 changed files with 124 additions and 57 deletions
@@ -1,36 +1,39 @@
/* eslint-env jasmine, jest */ /* eslint-env jasmine, jest */
import Image from '../../Image';
import ImageBackground from '..'; import ImageBackground from '..';
import React from 'react'; import React from 'react';
import { shallow } from 'enzyme'; import { render } from '@testing-library/react';
import Text from '../../Text'; import Text from '../../Text';
function findImage(container) {
return container.firstChild.firstChild;
}
describe('components/ImageBackground', () => { describe('components/ImageBackground', () => {
describe('prop "children"', () => { describe('prop "children"', () => {
it('render child content', () => { test('render child content', () => {
const component = shallow( const { getByText } = render(
<ImageBackground> <ImageBackground>
<Text>Hello World!</Text> <Text>Hello World!</Text>
</ImageBackground> </ImageBackground>
); );
expect(component.find(Text)).toBeDefined(); expect(getByText('Hello World!')).toBeDefined();
}); });
}); });
describe('prop "imageStyle"', () => { 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 imageStyle = { width: 40, height: 60 };
const component = shallow(<ImageBackground imageStyle={imageStyle} />); const { container } = render(<ImageBackground imageStyle={imageStyle} />);
expect(component.find(Image).prop('style')).toContain(imageStyle); expect(findImage(container).getAttribute('style')).toBe('height: 60px; width: 40px;');
}); });
}); });
describe('prop "style"', () => { describe('prop "style"', () => {
it('sets the style of the container View', () => { test('sets the style of the container View', () => {
const style = { margin: 40 }; const style = { margin: 40 };
const component = shallow(<ImageBackground style={style} />); const { container } = render(<ImageBackground style={style} />);
expect(component.prop('style')).toEqual(style); expect(container.firstChild.getAttribute('style')).toEqual('margin: 40px 40px 40px 40px;');
}); });
}); });
}); });
@@ -10,11 +10,11 @@
import type { ImageProps } from '../Image'; import type { ImageProps } from '../Image';
import type { ViewProps } from '../View'; import type { ViewProps } from '../View';
import ensureComponentIsNative from '../../modules/ensureComponentIsNative'; import setAndForwardRef from '../../modules/setAndForwardRef';
import Image from '../Image'; import Image from '../Image';
import StyleSheet from '../StyleSheet'; import StyleSheet from '../StyleSheet';
import View from '../View'; import View from '../View';
import React from 'react'; import React, { forwardRef, useImperativeHandle, useRef } from 'react';
type ImageBackgroundProps = { type ImageBackgroundProps = {
...ImageProps, ...ImageProps,
@@ -28,30 +28,35 @@ const emptyObject = {};
/** /**
* Very simple drop-in replacement for <Image> which supports nesting views. * Very simple drop-in replacement for <Image> which supports nesting views.
*/ */
class ImageBackground extends React.Component<ImageBackgroundProps> { const ImageBackground = forwardRef<ImageBackgroundProps, *>((props, ref) => {
setNativeProps(props: Object) { const { children, style = emptyObject, imageStyle, imageRef, ...rest } = props;
// Work-around flow
const viewRef = this._viewRef;
if (viewRef) {
ensureComponentIsNative(viewRef);
viewRef.setNativeProps(props);
}
}
_viewRef: ?View = null;
_captureRef = (ref: View) => {
this._viewRef = ref;
};
render() {
const { children, style = emptyObject, imageStyle, imageRef, ...props } = this.props;
const { height, width } = StyleSheet.flatten(style); 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;
}
});
return ( return (
<View ref={this._captureRef} style={style}> <View ref={setRef} style={style}>
<Image <Image
{...props} {...rest}
ref={imageRef} ref={imageRef}
style={[ style={[
StyleSheet.absoluteFill, StyleSheet.absoluteFill,
@@ -73,7 +78,8 @@ class ImageBackground extends React.Component<ImageBackgroundProps> {
{children} {children}
</View> </View>
); );
} });
}
ImageBackground.displayName = 'ImageBackground';
export default 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;
}
};
}