[MM-12550] Fix hangup on permalink view when user deleted the post in it (#2228)
* fix hangup on permalink view when user deleted the post in it * return null when no need to change state on getDerivedStateFromProps
This commit is contained in:
parent
b3ec19fe17
commit
b8f4757300
4 changed files with 360 additions and 31 deletions
152
app/screens/permalink/__snapshots__/permalink.test.js.snap
Normal file
152
app/screens/permalink/__snapshots__/permalink.test.js.snap
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Permalink should match snapshot 1`] = `
|
||||
<Connect(SafeAreaIos)
|
||||
backgroundColor="transparent"
|
||||
excludeHeader={true}
|
||||
footerColor="transparent"
|
||||
forceTop={44}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"flex": 1,
|
||||
"marginTop": 20,
|
||||
}
|
||||
}
|
||||
>
|
||||
<withAnimatable(Component)
|
||||
animation="zoomIn"
|
||||
delay={0}
|
||||
direction="normal"
|
||||
duration={200}
|
||||
iterationCount={1}
|
||||
iterationDelay={0}
|
||||
onAnimationBegin={[Function]}
|
||||
onAnimationEnd={[Function]}
|
||||
onTransitionBegin={[Function]}
|
||||
onTransitionEnd={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"borderRadius": 6,
|
||||
"flex": 1,
|
||||
"margin": 10,
|
||||
"opacity": 0,
|
||||
}
|
||||
}
|
||||
useNativeDriver={true}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"backgroundColor": "#ffffff",
|
||||
"borderTopLeftRadius": 6,
|
||||
"borderTopRightRadius": 6,
|
||||
"flexDirection": "row",
|
||||
"height": 44,
|
||||
"paddingRight": 16,
|
||||
"width": "100%",
|
||||
}
|
||||
}
|
||||
>
|
||||
<TouchableOpacity
|
||||
activeOpacity={0.2}
|
||||
onPress={[Function]}
|
||||
style={
|
||||
Object {
|
||||
"height": 44,
|
||||
"justifyContent": "center",
|
||||
"paddingLeft": 7,
|
||||
"width": 40,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
color="#3d3c40"
|
||||
name="close"
|
||||
size={20}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"alignItems": "center",
|
||||
"flex": 1,
|
||||
"paddingRight": 40,
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
ellipsizeMode="tail"
|
||||
numberOfLines={1}
|
||||
style={
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 17,
|
||||
"fontWeight": "600",
|
||||
}
|
||||
}
|
||||
>
|
||||
channel_name
|
||||
</Component>
|
||||
</Component>
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
}
|
||||
}
|
||||
>
|
||||
<Component
|
||||
style={
|
||||
Object {
|
||||
"backgroundColor": "rgba(61,60,64,0.2)",
|
||||
"height": 1,
|
||||
}
|
||||
}
|
||||
/>
|
||||
</Component>
|
||||
<Component
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"backgroundColor": "#ffffff",
|
||||
"flex": 1,
|
||||
},
|
||||
null,
|
||||
]
|
||||
}
|
||||
>
|
||||
<Loading
|
||||
color="grey"
|
||||
size="large"
|
||||
style={Object {}}
|
||||
/>
|
||||
</Component>
|
||||
</withAnimatable(Component)>
|
||||
</Component>
|
||||
</Connect(SafeAreaIos)>
|
||||
`;
|
||||
|
||||
exports[`Permalink should match snapshot 2`] = `
|
||||
<Component>
|
||||
<Icon
|
||||
allowFontScaling={false}
|
||||
name="archive"
|
||||
size={12}
|
||||
style={
|
||||
Array [
|
||||
Object {
|
||||
"color": "#3d3c40",
|
||||
"fontSize": 16,
|
||||
"paddingRight": 20,
|
||||
},
|
||||
]
|
||||
}
|
||||
/>
|
||||
|
||||
</Component>
|
||||
`;
|
||||
|
|
@ -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 = (<Text>
|
||||
<AwesomeIcon
|
||||
name='archive'
|
||||
style={[style.archiveIcon]}
|
||||
/>
|
||||
{' '}
|
||||
</Text>);
|
||||
icon = (
|
||||
<Text>
|
||||
<AwesomeIcon
|
||||
name='archive'
|
||||
style={[style.archiveIcon]}
|
||||
/>
|
||||
{' '}
|
||||
</Text>
|
||||
);
|
||||
}
|
||||
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}
|
||||
</Text>
|
||||
</View>
|
||||
|
|
|
|||
132
app/screens/permalink/permalink.test.js
Normal file
132
app/screens/permalink/permalink.test.js
Normal file
|
|
@ -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(
|
||||
<Permalink {...baseProps}/>,
|
||||
{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(
|
||||
<Permalink {...baseProps}/>,
|
||||
{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(
|
||||
<Permalink {...baseProps}/>,
|
||||
{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(
|
||||
<Permalink {...baseProps}/>,
|
||||
{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);
|
||||
});
|
||||
});
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue