diff --git a/app/screens/permalink/__snapshots__/permalink.test.js.snap b/app/screens/permalink/__snapshots__/permalink.test.js.snap new file mode 100644 index 000000000..5cdb8ba52 --- /dev/null +++ b/app/screens/permalink/__snapshots__/permalink.test.js.snap @@ -0,0 +1,152 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Permalink should match snapshot 1`] = ` + + + + + + + + + + channel_name + + + + + + + + + + + + +`; + +exports[`Permalink should match snapshot 2`] = ` + + + + +`; diff --git a/app/screens/permalink/permalink.js b/app/screens/permalink/permalink.js index 1b73d9691..1accbe827 100644 --- a/app/screens/permalink/permalink.js +++ b/app/screens/permalink/permalink.js @@ -86,10 +86,49 @@ export default class Permalink extends PureComponent { intl: intlShape.isRequired, }; + static getDerivedStateFromProps(nextProps, prevState) { + const newState = {}; + if (nextProps.focusedPostId !== prevState.focusedPostIdState) { + newState.focusedPostIdState = nextProps.focusedPostId; + } + + if (nextProps.channelId && nextProps.channelId !== prevState.channelIdState) { + newState.channelIdState = nextProps.channelId; + } + + if (nextProps.channelName && nextProps.channelName !== prevState.channelNameState) { + newState.channelNameState = nextProps.channelName; + } + + if (nextProps.postIds.length > 0 && nextProps.postIds !== prevState.postIdsState) { + newState.postIdsState = nextProps.postIds; + } + + if (nextProps.focusedPostId !== prevState.focusedPostIdState) { + let loading = true; + if (nextProps.postIds && nextProps.postIds.length >= 10) { + loading = false; + } + + newState.loading = loading; + } + + if (Object.keys(newState).length === 0) { + return null; + } + + return newState; + } + constructor(props) { super(props); - const {postIds, channelName} = props; + const { + postIds, + channelId, + channelName, + focusedPostId, + } = props; let loading = true; if (postIds && postIds.length >= 10) { @@ -103,10 +142,14 @@ export default class Permalink extends PureComponent { loading, error: '', retry: false, + channelIdState: channelId, + channelNameState: channelName, + focusedPostIdState: focusedPostId, + postIdsState: postIds, }; } - componentWillMount() { + componentDidMount() { this.mounted = true; if (this.state.loading) { @@ -114,18 +157,9 @@ export default class Permalink extends PureComponent { } } - componentWillReceiveProps(nextProps) { - if (this.props.channelName !== nextProps.channelName && this.mounted) { - this.setState({title: nextProps.channelName}); - } - - if (this.props.focusedPostId !== nextProps.focusedPostId && this.mounted) { - this.setState({loading: true}); - if (nextProps.postIds && nextProps.postIds.length < 10) { - this.loadPosts(nextProps); - } else { - this.setState({loading: false}); - } + componentDidUpdate() { + if (this.state.loading) { + this.loadPosts(this.props); } } @@ -176,11 +210,11 @@ export default class Permalink extends PureComponent { }; handlePress = () => { - const {channelId, channelName} = this.props; + const {channelIdState, channelNameState} = this.state; if (this.refs.view) { this.refs.view.growOut().then(() => { - this.jumpToChannel(channelId, channelName); + this.jumpToChannel(channelIdState, channelNameState); }); } }; @@ -303,18 +337,21 @@ export default class Permalink extends PureComponent { } }; - archivedIcon = (style) => { - let ico = null; + archivedIcon = () => { + const style = getStyleSheet(this.props.theme); + let icon = null; if (this.props.channelIsArchived) { - ico = ( - - {' '} - ); + icon = ( + + + {' '} + + ); } - return ico; + return icon; }; render() { @@ -324,10 +361,15 @@ export default class Permalink extends PureComponent { navigator, onHashtagPress, onPermalinkPress, - postIds, theme, } = this.props; - const {error, retry, loading, title} = this.state; + const { + error, + retry, + loading, + postIdsState, + title, + } = this.state; const style = getStyleSheet(theme); let postList; @@ -359,7 +401,7 @@ export default class Permalink extends PureComponent { onHashtagPress={onHashtagPress} onPermalinkPress={onPermalinkPress} onPostPress={this.goToThread} - postIds={postIds} + postIds={postIdsState} currentUserId={currentUserId} lastViewedAt={0} navigator={navigator} @@ -404,7 +446,7 @@ export default class Permalink extends PureComponent { numberOfLines={1} style={style.title} > - {this.archivedIcon(style)} + {this.archivedIcon()} {title} diff --git a/app/screens/permalink/permalink.test.js b/app/screens/permalink/permalink.test.js new file mode 100644 index 000000000..4f7ad2657 --- /dev/null +++ b/app/screens/permalink/permalink.test.js @@ -0,0 +1,132 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {shallow} from 'enzyme'; + +import Preferences from 'mattermost-redux/constants/preferences'; + +import Permalink from './permalink.js'; + +jest.mock('react-intl'); + +describe('Permalink', () => { + const navigator = { + dismissAllModals: jest.fn(), + dismissModal: jest.fn(), + push: jest.fn(), + resetTo: jest.fn(), + setOnNavigatorEvent: jest.fn(), + }; + + const actions = { + getPostsAfter: jest.fn(), + getPostsBefore: jest.fn(), + getPostThread: jest.fn(), + getChannel: jest.fn(), + handleSelectChannel: jest.fn(), + handleTeamChange: jest.fn(), + joinChannel: jest.fn(), + loadThreadIfNecessary: jest.fn(), + markChannelAsRead: jest.fn(), + markChannelAsViewed: jest.fn(), + selectPost: jest.fn(), + setChannelDisplayName: jest.fn(), + setChannelLoading: jest.fn(), + }; + + const baseProps = { + actions, + channelId: 'channel_id', + channelIsArchived: false, + channelName: 'channel_name', + channelTeamId: 'team_id', + currentTeamId: 'current_team_id', + currentUserId: 'current_user_id', + focusedPostId: 'focused_post_id', + isPermalink: true, + myMembers: {}, + navigator, + onClose: jest.fn(), + onHashtagPress: jest.fn(), + onPermalinkPress: jest.fn(), + onPress: jest.fn(), + postIds: ['post_id_1', 'focused_post_id', 'post_id_3'], + theme: Preferences.THEMES.default, + }; + + test('should match snapshot', () => { + const wrapper = shallow( + , + {context: {intl: {formatMessage: jest.fn()}}}, + ); + + expect(wrapper.getElement()).toMatchSnapshot(); + + // match archived icon + wrapper.setProps({channelIsArchived: true}); + expect(wrapper.instance().archivedIcon()).toMatchSnapshot(); + }); + + test('should match state and call loadPosts on retry', () => { + const wrapper = shallow( + , + {context: {intl: {formatMessage: jest.fn()}}}, + ); + + wrapper.instance().loadPosts = jest.fn(); + wrapper.instance().retry(); + expect(wrapper.instance().loadPosts).toHaveBeenCalledTimes(2); + expect(wrapper.instance().loadPosts).toBeCalledWith(baseProps); + }); + + test('should call handleClose on onNavigatorEvent(backPress)', () => { + const wrapper = shallow( + , + {context: {intl: {formatMessage: jest.fn()}}}, + ); + + wrapper.instance().handleClose = jest.fn(); + wrapper.instance().onNavigatorEvent({id: 'backPress'}); + expect(wrapper.instance().handleClose).toHaveBeenCalledTimes(1); + }); + + test('should match state', () => { + const wrapper = shallow( + , + {context: {intl: {formatMessage: jest.fn()}}}, + ); + + expect(wrapper.state('channelIdState')).toEqual(baseProps.channelId); + expect(wrapper.state('channelNameState')).toEqual(baseProps.channelName); + expect(wrapper.state('focusedPostIdState')).toEqual(baseProps.focusedPostId); + expect(wrapper.state('postIdsState')).toEqual(baseProps.postIds); + + wrapper.setProps({channelId: ''}); + expect(wrapper.state('channelIdState')).toEqual(baseProps.channelId); + wrapper.setProps({channelId: null}); + expect(wrapper.state('channelIdState')).toEqual(baseProps.channelId); + wrapper.setProps({channelId: 'new_channel_id'}); + expect(wrapper.state('channelIdState')).toEqual('new_channel_id'); + + wrapper.setProps({channelName: ''}); + expect(wrapper.state('channelNameState')).toEqual(baseProps.channelName); + wrapper.setProps({channelName: null}); + expect(wrapper.state('channelNameState')).toEqual(baseProps.channelName); + wrapper.setProps({channelName: 'new_channel_name'}); + expect(wrapper.state('channelNameState')).toEqual('new_channel_name'); + + wrapper.setProps({focusedPostId: 'new_focused_post_id'}); + expect(wrapper.state('focusedPostIdState')).toEqual('new_focused_post_id'); + + wrapper.setProps({postIds: []}); + expect(wrapper.state('postIdsState')).toEqual(baseProps.postIds); + wrapper.setProps({postIds: ['post_id_1', 'focused_post_id']}); + expect(wrapper.state('postIdsState')).toEqual(['post_id_1', 'focused_post_id']); + + wrapper.setProps({postIds: baseProps.postIds, focusedPostId: baseProps.focusedPostId}); + expect(wrapper.state('loading')).toEqual(true); + wrapper.setProps({postIds: ['1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11'], focusedPostId: 'new_focused_post_id'}); + expect(wrapper.state('loading')).toEqual(false); + }); +}); diff --git a/app/selectors/post_list.js b/app/selectors/post_list.js index ab738a329..1cb608043 100644 --- a/app/selectors/post_list.js +++ b/app/selectors/post_list.js @@ -44,7 +44,10 @@ export function makePreparePostIdsForPostList() { for (let i = posts.length - 1; i >= 0; i--) { const post = posts[i]; - if (post.type === Posts.POST_TYPES.EPHEMERAL_ADD_TO_CHANNEL && !selectedPostId) { + if ( + !post || + (post.type === Posts.POST_TYPES.EPHEMERAL_ADD_TO_CHANNEL && !selectedPostId) + ) { continue; }