From 6bfe6f917cb0247e1d9e73aa91cfd34799fa2e9b Mon Sep 17 00:00:00 2001 From: Miguel Alatzar Date: Mon, 26 Aug 2019 07:17:05 -0700 Subject: [PATCH] Set rebuilt emojis only after search bar animation completes (#3191) --- .../__snapshots__/emoji_picker.test.js.snap | 133 ++++++++++++++++++ app/components/emoji_picker/emoji_picker.js | 19 +-- .../emoji_picker/emoji_picker.test.js | 96 ++++++++++++- app/components/search_bar/search_bar.ios.js | 1 + app/components/search_bar/search_box.js | 5 +- 5 files changed, 244 insertions(+), 10 deletions(-) create mode 100644 app/components/emoji_picker/__snapshots__/emoji_picker.test.js.snap diff --git a/app/components/emoji_picker/__snapshots__/emoji_picker.test.js.snap b/app/components/emoji_picker/__snapshots__/emoji_picker.test.js.snap new file mode 100644 index 000000000..a4a9bb84c --- /dev/null +++ b/app/components/emoji_picker/__snapshots__/emoji_picker.test.js.snap @@ -0,0 +1,133 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`components/emoji_picker/EmojiPicker should match snapshot 1`] = ` + + + + + + + + + + + + + +`; diff --git a/app/components/emoji_picker/emoji_picker.js b/app/components/emoji_picker/emoji_picker.js index 2d6ba17a8..0b54b7d49 100644 --- a/app/components/emoji_picker/emoji_picker.js +++ b/app/components/emoji_picker/emoji_picker.js @@ -95,9 +95,9 @@ export default class EmojiPicker extends PureComponent { } componentWillReceiveProps(nextProps) { - let rebuildEmojis = false; + this.rebuildEmojis = false; if (this.props.deviceWidth !== nextProps.deviceWidth) { - rebuildEmojis = true; + this.rebuildEmojis = true; if (this.refs.search_bar) { this.refs.search_bar.blur(); @@ -105,14 +105,16 @@ export default class EmojiPicker extends PureComponent { } if (this.props.emojis !== nextProps.emojis) { - rebuildEmojis = true; + this.rebuildEmojis = true; + this.deviceWidth = nextProps.deviceWidth; } + } - if (rebuildEmojis) { - const emojis = this.renderableEmojis(this.props.emojisBySection, nextProps.deviceWidth); - this.setState({ - emojis, - }); + setRebuiltEmojis = (searchBarAnimationComplete) => { + if (this.rebuildEmojis && searchBarAnimationComplete) { + this.rebuildEmojis = false; + const emojis = this.renderableEmojis(this.props.emojisBySection, this.deviceWidth); + this.setState({emojis}); } } @@ -489,6 +491,7 @@ export default class EmojiPicker extends PureComponent { autoCapitalize='none' value={searchTerm} keyboardAppearance={getKeyboardAppearanceFromTheme(theme)} + onAnimationComplete={this.setRebuiltEmojis} /> diff --git a/app/components/emoji_picker/emoji_picker.test.js b/app/components/emoji_picker/emoji_picker.test.js index 9d8f995f9..3f8511a13 100644 --- a/app/components/emoji_picker/emoji_picker.test.js +++ b/app/components/emoji_picker/emoji_picker.test.js @@ -1,9 +1,30 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import {filterEmojiSearchInput} from './emoji_picker.js'; +import React from 'react'; + +import Preferences from 'mattermost-redux/constants/preferences'; + +import {shallowWithIntl} from 'test/intl-test-helper'; +import EmojiPicker, {filterEmojiSearchInput} from './emoji_picker.js'; describe('components/emoji_picker/EmojiPicker', () => { + const baseProps = { + actions: { + getCustomEmojis: jest.fn(), + incrementEmojiPickerPage: jest.fn(), + searchCustomEmojis: jest.fn(), + }, + customEmojisEnabled: false, + customEmojiPage: 200, + deviceWidth: 400, + emojis: [], + emojisBySection: [], + fuse: {}, + isLandscape: false, + theme: Preferences.THEMES.default, + }; + const testCases = [ {input: 'smile', output: 'smile'}, {input: 'SMILE', output: 'smile'}, @@ -20,4 +41,77 @@ describe('components/emoji_picker/EmojiPicker', () => { expect(filterEmojiSearchInput(testCase.input)).toEqual(testCase.output); }); }); + + test('should match snapshot', () => { + const wrapper = shallowWithIntl(); + expect(wrapper.getElement()).toMatchSnapshot(); + }); + + test('should set rebuildEmojis to true when deviceWidth changes', () => { + const wrapper = shallowWithIntl(); + const instance = wrapper.instance(); + + expect(instance.rebuildEmojis).toBe(undefined); + + const newDeviceWidth = baseProps.deviceWidth * 2; + wrapper.setProps({deviceWidth: newDeviceWidth}); + + expect(instance.rebuildEmojis).toBe(true); + }); + + test('should set rebuildEmojis to true and new deviceWidth when emojis change', () => { + const wrapper = shallowWithIntl(); + const instance = wrapper.instance(); + + expect(instance.rebuildEmojis).toBe(undefined); + expect(instance.deviceWidth).toBe(undefined); + + const newDeviceWidth = baseProps.deviceWidth * 2; + const newEmojis = [{}]; + wrapper.setProps({deviceWidth: newDeviceWidth, emojis: newEmojis}); + + expect(instance.rebuildEmojis).toBe(true); + expect(instance.deviceWidth).toBe(newDeviceWidth); + }); + + test('should set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is true', () => { + const wrapper = shallowWithIntl(); + const instance = wrapper.instance(); + instance.setState = jest.fn(); + instance.renderableEmojis = jest.spyOn(instance, 'renderableEmojis'); + + instance.rebuildEmojis = true; + const searchBarAnimationComplete = true; + const setRebuiltEmojis = jest.spyOn(instance, 'setRebuiltEmojis'); + setRebuiltEmojis(searchBarAnimationComplete); + + expect(instance.setState).toHaveBeenCalledWith({emojis: []}); + expect(instance.rebuildEmojis).toBe(false); + }); + + test('should not set rebuilt emojis when rebuildEmojis is false and searchBarAnimationComplete is true', () => { + const wrapper = shallowWithIntl(); + const instance = wrapper.instance(); + instance.setState = jest.fn(); + + instance.rebuildEmojis = false; + const searchBarAnimationComplete = true; + const setRebuiltEmojis = jest.spyOn(instance, 'setRebuiltEmojis'); + setRebuiltEmojis(searchBarAnimationComplete); + + expect(instance.setState).not.toHaveBeenCalled(); + }); + + test('should not set rebuilt emojis when rebuildEmojis is true and searchBarAnimationComplete is false', () => { + const wrapper = shallowWithIntl(); + const instance = wrapper.instance(); + instance.setState = jest.fn(); + + instance.rebuildEmojis = true; + const searchBarAnimationComplete = false; + const setRebuiltEmojis = jest.spyOn(instance, 'setRebuiltEmojis'); + setRebuiltEmojis(searchBarAnimationComplete); + + expect(instance.setState).not.toHaveBeenCalled(); + }); }); diff --git a/app/components/search_bar/search_bar.ios.js b/app/components/search_bar/search_bar.ios.js index 6c9de2c16..ad2e6bd8a 100644 --- a/app/components/search_bar/search_bar.ios.js +++ b/app/components/search_bar/search_bar.ios.js @@ -40,6 +40,7 @@ export default class SearchBarIos extends PureComponent { searchIconCollapsedMargin: PropTypes.number, searchIconExpandedMargin: PropTypes.number, keyboardAppearance: PropTypes.string, + onAnimationComplete: PropTypes.func, }; static defaultProps = { diff --git a/app/components/search_bar/search_box.js b/app/components/search_bar/search_box.js index a20cae328..947ae56de 100644 --- a/app/components/search_bar/search_box.js +++ b/app/components/search_bar/search_box.js @@ -18,6 +18,7 @@ import EvilIcon from 'react-native-vector-icons/EvilIcons'; import IonIcon from 'react-native-vector-icons/Ionicons'; import CustomPropTypes from 'app/constants/custom_prop_types'; +import {emptyFunction} from 'app/utils/general'; const AnimatedTextInput = Animated.createAnimatedComponent(TextInput); const AnimatedIonIcon = Animated.createAnimatedComponent(IonIcon); @@ -78,6 +79,7 @@ export default class Search extends Component { leftComponent: PropTypes.element, inputCollapsedMargin: PropTypes.number, keyboardAppearance: PropTypes.string, + onAnimationComplete: PropTypes.func, }; static defaultProps = { @@ -102,6 +104,7 @@ export default class Search extends Component { value: '', leftComponent: null, inputCollapsedMargin: 10, + onAnimationComplete: emptyFunction, }; constructor(props) { @@ -370,7 +373,7 @@ export default class Search extends Component { useNativeDriver: true, } ), - ]).start(); + ]).start(({finished}) => this.props.onAnimationComplete(finished)); this.shadowHeight = this.props.shadowOffsetHeightCollapsed; resolve(); });