mirror of
https://github.com/zoriya/react-native-web.git
synced 2026-05-31 01:36:11 +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 */
|
/* 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;');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
+52
-46
@@ -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,52 +28,58 @@ 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 { height, width } = StyleSheet.flatten(style);
|
||||||
const viewRef = this._viewRef;
|
|
||||||
if (viewRef) {
|
const containerRef = useRef(null);
|
||||||
ensureComponentIsNative(viewRef);
|
|
||||||
viewRef.setNativeProps(props);
|
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) => {
|
ImageBackground.displayName = 'ImageBackground';
|
||||||
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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user