MM-18767 Remove deprecated lifecycle methods from post textbox (#3697)

* MM-18767 Remove deprecated lifecycle methods from post textbox

* fix current issues

* change deep equal compare to just compare, fix test

* remove redundant state initialization

* restore state

* move state from constructor
This commit is contained in:
Vladimir Lebedev 2020-01-09 18:07:55 +03:00 committed by Saturnino Abril
parent ce53a7ac29
commit 422fc5ee4a
8 changed files with 159 additions and 57 deletions

View file

@ -43,14 +43,13 @@ export default class FileAttachmentList extends PureComponent {
files: [],
};
state = {};
constructor(props) {
super(props);
this.items = [];
this.filesForGallery = this.getFilesForGallery(props);
this.state = {
loadingFiles: props.files.length === 0,
};
this.buildGalleryFiles().then((results) => {
this.galleryFiles = results;
@ -71,17 +70,14 @@ export default class FileAttachmentList extends PureComponent {
}
}
componentWillReceiveProps(nextProps) {
if (this.props.files !== nextProps.files) {
this.filesForGallery = this.getFilesForGallery(nextProps);
componentDidUpdate(prevProps) {
if (prevProps.files !== this.props.files) {
this.filesForGallery = this.getFilesForGallery(this.props);
this.buildGalleryFiles().then((results) => {
this.galleryFiles = results;
});
}
if (!this.state.loadingFiles && nextProps.files.length === 0) {
this.setState({
loadingFiles: true,
});
if (this.props.files !== prevProps.files && this.props.files.length === 0) {
this.loadFilesForPost();
}
}
@ -206,9 +202,6 @@ export default class FileAttachmentList extends PureComponent {
loadFilesForPost = async () => {
await this.props.actions.loadFilesForPostIfNecessary(this.props.postId);
this.setState({
loadingFiles: false,
});
}
renderItems = (items, moreImagesCount, includeGutter = false) => {

View file

@ -69,7 +69,7 @@ describe('FileAttachmentList', () => {
test('should match snapshot with a single image file', () => {
const wrapper = shallow(
<FileAttachment {...baseProps}/>
<FileAttachment {...baseProps}/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
@ -82,7 +82,7 @@ describe('FileAttachmentList', () => {
};
const wrapper = shallow(
<FileAttachment {...props}/>
<FileAttachment {...props}/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
@ -96,7 +96,7 @@ describe('FileAttachmentList', () => {
};
const wrapper = shallow(
<FileAttachment {...props}/>
<FileAttachment {...props}/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
@ -112,7 +112,7 @@ describe('FileAttachmentList', () => {
};
const wrapper = shallow(
<FileAttachment {...props}/>
<FileAttachment {...props}/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
@ -130,7 +130,7 @@ describe('FileAttachmentList', () => {
};
const wrapper = shallow(
<FileAttachment {...props}/>
<FileAttachment {...props}/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
@ -143,7 +143,7 @@ describe('FileAttachmentList', () => {
};
const wrapper = shallow(
<FileAttachment {...props}/>
<FileAttachment {...props}/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
@ -156,7 +156,7 @@ describe('FileAttachmentList', () => {
};
const wrapper = shallow(
<FileAttachment {...props}/>
<FileAttachment {...props}/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
@ -168,14 +168,10 @@ describe('FileAttachmentList', () => {
files: undefined,
};
const wrapper = shallow(
<FileAttachment {...props}/>
shallow(
<FileAttachment {...props}/>,
);
expect(wrapper.state('loadingFiles')).toBe(true);
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledWith(props.postId);
await loadFilesForPostIfNecessary();
wrapper.setProps({files: baseProps.files});
expect(wrapper.state('loadingFiles')).toBe(false);
});
test('should call loadFilesForPostIfNecessary on componentUpdate and when files does not exist', async () => {
@ -188,17 +184,50 @@ describe('FileAttachmentList', () => {
},
};
const wrapper = shallow(
<FileAttachment {...props}/>
shallow(
<FileAttachment {...props}/>,
);
expect(wrapper.state('loadingFiles')).toBe(true);
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledTimes(1);
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledWith(props.postId);
await loadFilesForPostIfNecessaryMock();
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledTimes(2);
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledWith(props.postId);
await loadFilesForPostIfNecessaryMock();
wrapper.setProps({files: baseProps.files});
expect(wrapper.state('loadingFiles')).toBe(false);
});
test('should call getFilesForGallery on props change', async () => {
const loadFilesForPostIfNecessaryMock = jest.fn().mockImplementationOnce(() => Promise.resolve({data: {}}));
const props = {
...baseProps,
actions: {
loadFilesForPostIfNecessary: loadFilesForPostIfNecessaryMock,
},
};
const wrapper = shallow(
<FileAttachment {...props}/>,
);
wrapper.instance().getFilesForGallery = jest.fn().mockImplementationOnce(() => []);
wrapper.setProps({files: [files[0], files[1]]});
expect(wrapper.instance().getFilesForGallery).toHaveBeenCalled();
});
test('should call loadFilesForPostIfNecessary when files object empty', async () => {
const loadFilesForPostIfNecessaryMock = jest.fn().mockImplementationOnce(() => Promise.resolve({data: {}}));
const props = {
...baseProps,
actions: {
loadFilesForPostIfNecessary: loadFilesForPostIfNecessaryMock,
},
};
const wrapper = shallow(
<FileAttachment {...props}/>,
);
wrapper.setProps({files: []});
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledTimes(1);
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledWith(props.postId);
wrapper.setProps({test: 'test'});
expect(props.actions.loadFilesForPostIfNecessary).toHaveBeenCalledTimes(1);
});
});

View file

@ -43,11 +43,11 @@ export default class FileUploadItem extends PureComponent {
}
}
componentWillReceiveProps(nextProps) {
componentDidUpdate(prevProps) {
const {file: prevFile} = prevProps;
const {file} = this.props;
const {file: nextFile} = nextProps;
if (file.failed !== nextFile.failed && nextFile.loading) {
if (prevFile.failed !== file.failed && file.loading) {
this.downloadAndUploadFile(file);
}
}

View file

@ -44,5 +44,17 @@ describe('FileUploadItem', () => {
localPath: 'path/to/downloaded/image',
});
});
test('should upload next file when we pass new props', async () => {
const component = shallow(<FileUploadItem {...props}/>);
component.instance().uploadFile = jest.fn();
component.setProps({file: {
loading: true,
failed: false,
localPath: 'path/to/downloaded/image',
}});
expect(component.instance().uploadFile).toHaveBeenCalledTimes(1);
});
});
});

View file

@ -27,10 +27,10 @@ export default class Typing extends PureComponent {
typingHeight: new Animated.Value(0),
}
componentWillReceiveProps(nextProps) {
if (nextProps.typing.length && !this.props.typing.length) {
componentDidUpdate(prevProps) {
if (this.props.typing.length && !prevProps.typing.length) {
this.animateTyping(true);
} else if (!nextProps.typing.length) {
} else if (!this.props.typing.length) {
this.animateTyping();
}
}

View file

@ -0,0 +1,54 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {shallow} from 'enzyme';
import {
Animated,
} from 'react-native';
const {View: AnimatedView} = Animated;
import Typing from './typing';
describe('Typing', () => {
const baseProps = {
typing: ['user1', 'user2'],
theme: {
centerChannelColor: 'blue',
},
};
test('should render component without error', () => {
const wrapper = shallow(
<Typing {...baseProps}/>,
);
expect(wrapper.find(AnimatedView).exists()).toBe(true);
});
test('should call animateTyping when next typing props is not empty and current is empty', () => {
const props = {
...baseProps,
typing: [],
};
const wrapper = shallow(
<Typing {...props}/>,
);
wrapper.instance().animateTyping = jest.fn();
wrapper.setProps({typing: ['user2']});
expect(wrapper.instance().animateTyping).toHaveBeenCalledWith(true);
});
test('should call animateTyping when next typing props is not empty', () => {
const props = {
...baseProps,
};
const wrapper = shallow(
<Typing {...props}/>,
);
wrapper.instance().animateTyping = jest.fn();
wrapper.setProps({typing: []});
expect(wrapper.instance().animateTyping).toHaveBeenCalledWith();
});
});

View file

@ -60,7 +60,7 @@ describe('PostTextBox', () => {
test('should match, full snapshot', () => {
const wrapper = shallowWithIntl(
<PostTextbox {...baseProps}/>
<PostTextbox {...baseProps}/>,
);
expect(wrapper.getElement()).toMatchSnapshot();
@ -68,7 +68,7 @@ describe('PostTextBox', () => {
test('should emit the event but no text is save to draft', () => {
const wrapper = shallowWithIntl(
<PostTextbox {...baseProps}/>
<PostTextbox {...baseProps}/>,
);
wrapper.setState({value: 'some text'});
@ -82,7 +82,7 @@ describe('PostTextBox', () => {
test('should emit the event and text is save to draft', () => {
const wrapper = shallowWithIntl(
<PostTextbox {...baseProps}/>
<PostTextbox {...baseProps}/>,
);
const instance = wrapper.instance();
@ -96,7 +96,7 @@ describe('PostTextBox', () => {
test('should not send multiple alerts when message is too long', () => {
const wrapper = shallowWithIntl(
<PostTextbox {...baseProps}/>
<PostTextbox {...baseProps}/>,
);
const instance = wrapper.instance();
@ -257,7 +257,7 @@ describe('PostTextBox', () => {
},
]) {
const wrapper = shallowWithIntl(
<PostTextbox {...baseProps}/>
<PostTextbox {...baseProps}/>,
);
const containsAtChannel = wrapper.instance().textContainsAtAllAtChannel(data.text);
assert.equal(containsAtChannel, data.result, data.text);
@ -267,7 +267,7 @@ describe('PostTextBox', () => {
describe('send button', () => {
test('should initially disable and hide the send button', () => {
const wrapper = shallowWithIntl(
<PostTextbox {...baseProps}/>
<PostTextbox {...baseProps}/>,
);
expect(wrapper.find(Fade).prop('visible')).toBe(false);
@ -281,7 +281,7 @@ describe('PostTextBox', () => {
};
const wrapper = shallowWithIntl(
<PostTextbox {...props}/>
<PostTextbox {...props}/>,
);
expect(wrapper.find(Fade).prop('visible')).toBe(true);
@ -295,7 +295,7 @@ describe('PostTextBox', () => {
};
const wrapper = shallowWithIntl(
<PostTextbox {...props}/>
<PostTextbox {...props}/>,
);
expect(wrapper.find(Fade).prop('visible')).toBe(true);
@ -309,7 +309,7 @@ describe('PostTextBox', () => {
};
const wrapper = shallowWithIntl(
<PostTextbox {...props}/>
<PostTextbox {...props}/>,
);
expect(wrapper.find(Fade).prop('visible')).toBe(true);
@ -323,7 +323,7 @@ describe('PostTextBox', () => {
};
const wrapper = shallowWithIntl(
<PostTextbox {...props}/>
<PostTextbox {...props}/>,
);
wrapper.setState({sendingMessage: true});
@ -422,7 +422,7 @@ describe('PostTextBox', () => {
<PostTextbox
{...baseProps}
maxFileSize={50 * 1024 * 1024}
/>
/>,
);
wrapper.find(PasteableTextInput).first().simulate('paste', null, [
{
@ -463,6 +463,13 @@ describe('PostTextBox', () => {
]);
expect(baseProps.actions.initUploadFiles).not.toHaveBeenCalled();
});
test('should change state value on props change', () => {
const wrapper = shallowWithIntl(<PostTextbox {...baseProps}/>);
expect(wrapper.state('value')).toEqual('');
wrapper.setProps({value: 'value', channelId: 'channel-id2'});
expect(wrapper.state('value')).toEqual('value');
});
});
});

View file

@ -107,6 +107,8 @@ export default class PostTextBoxBase extends PureComponent {
keyboardType: 'default',
top: 0,
value: props.value,
rootId: props.rootId,
channelId: props.channelId,
channelTimezoneCount: 0,
longMessageAlertShown: false,
};
@ -128,10 +130,15 @@ export default class PostTextBoxBase extends PureComponent {
}
}
componentWillReceiveProps(nextProps) {
if (nextProps.channelId !== this.props.channelId || nextProps.rootId !== this.props.rootId) {
this.setState({value: nextProps.value});
static getDerivedStateFromProps(nextProps, state) {
if (nextProps.channelId !== state.channelId || nextProps.rootId !== state.rootId) {
return {
value: nextProps.value,
channelId: nextProps.channelId,
rootId: nextProps.rootId,
};
}
return null;
}
componentWillUnmount() {
@ -208,7 +215,7 @@ export default class PostTextBoxBase extends PureComponent {
}, {
max: maxMessageLength,
count: valueLength,
})
}),
);
this.setState({longMessageAlertShown: true});
}
@ -465,7 +472,7 @@ export default class PostTextBoxBase extends PureComponent {
{
totalMembers: currentMembersCount - 1,
timezones: channelTimezoneCount,
}
},
)
);
} else {
@ -477,7 +484,7 @@ export default class PostTextBoxBase extends PureComponent {
},
{
totalMembers: currentMembersCount - 1,
}
},
)
);
}
@ -592,7 +599,7 @@ export default class PostTextBoxBase extends PureComponent {
id: 'mobile.commands.error_title',
defaultMessage: 'Error Executing Command',
}),
error.message
error.message,
);
return;
}
@ -686,7 +693,7 @@ export default class PostTextBoxBase extends PureComponent {
defaultMessage: 'Dismiss',
}),
},
]
],
);
};