From e33a16d4f1989f0a3c9acf5b95b42d1e312e225c Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Wed, 10 Feb 2021 14:41:30 -0700 Subject: [PATCH] Add retriable FastImage component (#5167) --- .../progressive_image/progressive_image.js | 4 +- app/components/retriable_fast_image/index.tsx | 41 +++++++++++++++ .../retriable_fast_image.test.js | 52 +++++++++++++++++++ 3 files changed, 95 insertions(+), 2 deletions(-) create mode 100644 app/components/retriable_fast_image/index.tsx create mode 100644 app/components/retriable_fast_image/retriable_fast_image.test.js diff --git a/app/components/progressive_image/progressive_image.js b/app/components/progressive_image/progressive_image.js index 475ba3063..bcba39af7 100644 --- a/app/components/progressive_image/progressive_image.js +++ b/app/components/progressive_image/progressive_image.js @@ -4,14 +4,14 @@ import React, {PureComponent} from 'react'; import PropTypes from 'prop-types'; import {Animated, ImageBackground, Image, Platform, View, StyleSheet} from 'react-native'; -import FastImage from 'react-native-fast-image'; import thumb from '@assets/images/thumb.png'; import CustomPropTypes from '@constants/custom_prop_types'; +import RetriableFastImage from '@components/retriable_fast_image'; import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme'; const AnimatedImageBackground = Animated.createAnimatedComponent(ImageBackground); -const AnimatedFastImage = Animated.createAnimatedComponent(FastImage); +const AnimatedFastImage = Animated.createAnimatedComponent(RetriableFastImage); export default class ProgressiveImage extends PureComponent { static propTypes = { diff --git a/app/components/retriable_fast_image/index.tsx b/app/components/retriable_fast_image/index.tsx new file mode 100644 index 000000000..a07027aab --- /dev/null +++ b/app/components/retriable_fast_image/index.tsx @@ -0,0 +1,41 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React, {PureComponent} from 'react'; +import FastImage, {FastImageProps} from 'react-native-fast-image'; + +export const FAST_IMAGE_MAX_RETRIES = 3; + +type RetriableFastImageProps = FastImageProps & { + id: string +} + +type RetriableFastImageState = { + retry: number +} + +export default class RetriableFastImage extends PureComponent { + state = { + retry: 0, + } + + onError = () => { + const retry = this.state.retry + 1; + if (retry > FAST_IMAGE_MAX_RETRIES && this.props.onError) { + this.props.onError(); + return; + } + + this.setState({retry}); + } + + render() { + return ( + + ); + } +} diff --git a/app/components/retriable_fast_image/retriable_fast_image.test.js b/app/components/retriable_fast_image/retriable_fast_image.test.js new file mode 100644 index 000000000..56154931c --- /dev/null +++ b/app/components/retriable_fast_image/retriable_fast_image.test.js @@ -0,0 +1,52 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {shallow} from 'enzyme'; +import FastImage from 'react-native-fast-image'; + +import RetriableFastImage, {FAST_IMAGE_MAX_RETRIES} from './index'; + +describe('RetriableFastImage', () => { + const baseProps = { + id: 'id', + onError: jest.fn(), + }; + + it('should update the FastImage element key on error until max retries has been reached', () => { + const wrapper = shallow( + , + ); + const instance = wrapper.instance(); + + let retry = 0; + expect(wrapper.containsMatchingElement()).toEqual(true); + while (instance.state.retry < FAST_IMAGE_MAX_RETRIES) { + instance.onError(); + retry += 1; + expect(wrapper.containsMatchingElement()).toEqual(true); + } + + instance.onError(); + expect(wrapper.containsMatchingElement()).toEqual(true); + }); + + it('should call props.onError only after max retries has been reached', () => { + const wrapper = shallow( + , + ); + const instance = wrapper.instance(); + + let retry = 0; + while (instance.state.retry < FAST_IMAGE_MAX_RETRIES) { + instance.onError(); + retry += 1; + expect(instance.state.retry).toEqual(retry); + expect(baseProps.onError).not.toHaveBeenCalled(); + } + + instance.onError(); + expect(instance.state.retry).toEqual(retry); + expect(baseProps.onError).toHaveBeenCalled(); + }); +});