[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 */
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;');
});
});
});
@@ -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;
}
};
}