[MM-26855] Ensure More Messages button works as expected when app is launched from a push notification (#4569)
* Fix more messages button with push notif * Don't cancel in showMoreText
This commit is contained in:
parent
e2e33e05cc
commit
7c41c83196
7 changed files with 91 additions and 40 deletions
|
|
@ -16,7 +16,7 @@ import EventEmitter from '@mm-redux/utils/event_emitter';
|
|||
import initialState from '@store/initial_state';
|
||||
import {getStateForReset} from '@store/utils';
|
||||
|
||||
import {markChannelViewedAndRead} from './channel';
|
||||
import {markAsViewedAndReadBatch} from './channel';
|
||||
|
||||
export function startDataCleanup() {
|
||||
return async (dispatch, getState) => {
|
||||
|
|
@ -107,7 +107,7 @@ export function handleSelectTeamAndChannel(teamId, channelId) {
|
|||
const {currentTeamId} = state.entities.teams;
|
||||
const channel = channels[channelId];
|
||||
const member = myMembers[channelId];
|
||||
const actions = [];
|
||||
const actions = markAsViewedAndReadBatch(state, channelId);
|
||||
|
||||
// when the notification is from a team other than the current team
|
||||
if (teamId !== currentTeamId) {
|
||||
|
|
@ -124,8 +124,6 @@ export function handleSelectTeamAndChannel(teamId, channelId) {
|
|||
teamId: channel.team_id || currentTeamId,
|
||||
},
|
||||
});
|
||||
|
||||
dispatch(markChannelViewedAndRead(channelId));
|
||||
}
|
||||
|
||||
if (actions.length) {
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@
|
|||
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {resetUnreadMessageCount} from '@actions/views/channel';
|
||||
|
||||
import MoreMessagesButton from './more_messages_button';
|
||||
|
||||
function mapStateToProps(state, ownProps) {
|
||||
|
|
@ -18,4 +20,8 @@ function mapStateToProps(state, ownProps) {
|
|||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps)(MoreMessagesButton);
|
||||
const mapDispatchToProps = {
|
||||
resetUnreadMessageCount,
|
||||
};
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(MoreMessagesButton);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {ActivityIndicator, Animated, Text, View} from 'react-native';
|
||||
import {ActivityIndicator, Animated, AppState, Text, View} from 'react-native';
|
||||
import {intlShape} from 'react-intl';
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
|
|
@ -51,6 +51,7 @@ export default class MoreMessageButton extends React.PureComponent {
|
|||
scrollToIndex: PropTypes.func.isRequired,
|
||||
registerViewableItemsListener: PropTypes.func.isRequired,
|
||||
registerScrollEndIndexListener: PropTypes.func.isRequired,
|
||||
resetUnreadMessageCount: PropTypes.func.isRequired,
|
||||
deepLinkURL: PropTypes.string,
|
||||
};
|
||||
|
||||
|
|
@ -68,12 +69,14 @@ export default class MoreMessageButton extends React.PureComponent {
|
|||
}
|
||||
|
||||
componentDidMount() {
|
||||
AppState.addEventListener('change', this.onAppStateChange);
|
||||
EventEmitter.on(ViewTypes.INDICATOR_BAR_VISIBLE, this.onIndicatorBarVisible);
|
||||
this.removeViewableItemsListener = this.props.registerViewableItemsListener(this.onViewableItemsChanged);
|
||||
this.removeScrollEndIndexListener = this.props.registerScrollEndIndexListener(this.onScrollEndIndex);
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
AppState.removeEventListener('change', this.onAppStateChange);
|
||||
EventEmitter.off(ViewTypes.INDICATOR_BAR_VISIBLE, this.onIndicatorBarVisible);
|
||||
if (this.removeViewableItemsListener) {
|
||||
this.removeViewableItemsListener();
|
||||
|
|
@ -114,6 +117,17 @@ export default class MoreMessageButton extends React.PureComponent {
|
|||
if (unreadCount > prevProps.unreadCount && prevProps.unreadCount === 0) {
|
||||
this.onViewableItemsChanged(this.viewableItems);
|
||||
}
|
||||
|
||||
if (unreadCount === 0 && prevProps.unreadCount > 0) {
|
||||
this.hide();
|
||||
}
|
||||
}
|
||||
|
||||
onAppStateChange = (appState) => {
|
||||
const isActive = appState === 'active';
|
||||
if (!isActive) {
|
||||
this.props.resetUnreadMessageCount(this.props.channelId);
|
||||
}
|
||||
}
|
||||
|
||||
onIndicatorBarVisible = (indicatorVisible) => {
|
||||
|
|
@ -201,9 +215,9 @@ export default class MoreMessageButton extends React.PureComponent {
|
|||
|
||||
onViewableItemsChanged = (viewableItems) => {
|
||||
this.viewableItems = viewableItems;
|
||||
const {newMessageLineIndex, scrollToIndex} = this.props;
|
||||
|
||||
const {newMessageLineIndex, scrollToIndex, unreadCount} = this.props;
|
||||
if (newMessageLineIndex <= 0 || unreadCount === 0 || viewableItems.length === 0 || this.disableViewableItems) {
|
||||
if (newMessageLineIndex <= 0 || viewableItems.length === 0 || this.disableViewableItems) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -248,13 +262,10 @@ export default class MoreMessageButton extends React.PureComponent {
|
|||
showMoreText = (readCount) => {
|
||||
const moreCount = this.props.unreadCount - readCount;
|
||||
|
||||
if (moreCount <= 0) {
|
||||
this.cancel(true);
|
||||
return;
|
||||
if (moreCount > 0) {
|
||||
const moreText = this.moreText(moreCount);
|
||||
this.setState({moreText}, this.show);
|
||||
}
|
||||
|
||||
const moreText = this.moreText(moreCount);
|
||||
this.setState({moreText}, this.show);
|
||||
}
|
||||
|
||||
getReadCount = (lastViewableIndex) => {
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {Animated} from 'react-native';
|
||||
import {Animated, AppState} from 'react-native';
|
||||
import {shallowWithIntl} from 'test/intl-test-helper';
|
||||
|
||||
import Preferences from '@mm-redux/constants/preferences';
|
||||
|
|
@ -28,6 +28,7 @@ describe('MoreMessagesButton', () => {
|
|||
manuallyUnread: false,
|
||||
newMessageLineIndex: 0,
|
||||
scrollToIndex: jest.fn(),
|
||||
resetUnreadMessageCount: jest.fn(),
|
||||
registerViewableItemsListener: jest.fn(() => {
|
||||
return jest.fn();
|
||||
}),
|
||||
|
|
@ -54,12 +55,16 @@ describe('MoreMessagesButton', () => {
|
|||
});
|
||||
|
||||
describe('lifecycle methods', () => {
|
||||
test('componentDidMount should register indicator visibility listener, viewable items listener, and scroll end index listener', () => {
|
||||
AppState.addEventListener = jest.fn();
|
||||
AppState.removeEventListener = jest.fn();
|
||||
|
||||
test('componentDidMount should register app state listener, indicator visibility listener, viewable items listener, and scroll end index listener', () => {
|
||||
EventEmitter.on = jest.fn();
|
||||
const wrapper = shallowWithIntl(
|
||||
<MoreMessagesButton {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
instance.onAppStateChange = jest.fn();
|
||||
instance.onIndicatorBarVisible = jest.fn();
|
||||
instance.onViewableItemsChanged = jest.fn();
|
||||
instance.onScrollEndIndex = jest.fn();
|
||||
|
|
@ -69,6 +74,7 @@ describe('MoreMessagesButton', () => {
|
|||
// have not yet been mocked so we call componentDidMount again.
|
||||
instance.componentDidMount();
|
||||
|
||||
expect(AppState.addEventListener).toHaveBeenCalledWith('change', instance.onAppStateChange);
|
||||
expect(EventEmitter.on).toHaveBeenCalledWith(ViewTypes.INDICATOR_BAR_VISIBLE, instance.onIndicatorBarVisible);
|
||||
expect(baseProps.registerViewableItemsListener).toHaveBeenCalledWith(instance.onViewableItemsChanged);
|
||||
expect(instance.removeViewableItemsListener).toBeDefined();
|
||||
|
|
@ -76,18 +82,20 @@ describe('MoreMessagesButton', () => {
|
|||
expect(instance.removeScrollEndIndexListener).toBeDefined();
|
||||
});
|
||||
|
||||
test('componentWillUnmount should remove the indicator bar visible listener, the viewable items listener, the scroll end index listener, and clear all timers', () => {
|
||||
test('componentWillUnmount should remove the app state listener, the indicator bar visible listener, the viewable items listener, the scroll end index listener, and clear all timers', () => {
|
||||
jest.useFakeTimers();
|
||||
EventEmitter.off = jest.fn();
|
||||
const wrapper = shallowWithIntl(
|
||||
<MoreMessagesButton {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
instance.onAppStateChange = jest.fn();
|
||||
instance.onIndicatorBarVisible = jest.fn();
|
||||
instance.removeViewableItemsListener = jest.fn();
|
||||
instance.removeScrollEndIndexListener = jest.fn();
|
||||
|
||||
instance.componentWillUnmount();
|
||||
expect(AppState.removeEventListener).toHaveBeenCalledWith('change', instance.onAppStateChange);
|
||||
expect(EventEmitter.off).toHaveBeenCalledWith(ViewTypes.INDICATOR_BAR_VISIBLE, instance.onIndicatorBarVisible);
|
||||
expect(instance.removeViewableItemsListener).toHaveBeenCalled();
|
||||
expect(instance.removeScrollEndIndexListener).toHaveBeenCalled();
|
||||
|
|
@ -217,6 +225,52 @@ describe('MoreMessagesButton', () => {
|
|||
wrapper.setProps({unreadCount: 2});
|
||||
expect(instance.onViewableItemsChanged).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('componentDidUpdate should call hide when the unreadCount decreases to 0', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<MoreMessagesButton {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
instance.hide = jest.fn();
|
||||
instance.viewableItems = [{index: 1}];
|
||||
|
||||
wrapper.setProps({unreadCount: 2});
|
||||
expect(instance.hide).not.toHaveBeenCalled();
|
||||
|
||||
wrapper.setProps({unreadCount: 1});
|
||||
expect(instance.hide).not.toHaveBeenCalled();
|
||||
|
||||
wrapper.setProps({unreadCount: 0});
|
||||
expect(instance.hide).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onAppStateChange', () => {
|
||||
it('should call resetUnreadMessageCount when app state is not active', () => {
|
||||
const wrapper = shallowWithIntl(
|
||||
<MoreMessagesButton {...baseProps}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
let appState = 'active';
|
||||
instance.onAppStateChange(appState);
|
||||
expect(baseProps.resetUnreadMessageCount).not.toHaveBeenCalled();
|
||||
|
||||
appState = 'inactive';
|
||||
instance.onAppStateChange(appState);
|
||||
expect(baseProps.resetUnreadMessageCount).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.resetUnreadMessageCount).toHaveBeenCalledWith(baseProps.channelId);
|
||||
|
||||
appState = 'background';
|
||||
instance.onAppStateChange(appState);
|
||||
expect(baseProps.resetUnreadMessageCount).toHaveBeenCalledTimes(2);
|
||||
expect(baseProps.resetUnreadMessageCount).toHaveBeenCalledWith(baseProps.channelId);
|
||||
|
||||
appState = '';
|
||||
instance.onAppStateChange(appState);
|
||||
expect(baseProps.resetUnreadMessageCount).toHaveBeenCalledTimes(3);
|
||||
expect(baseProps.resetUnreadMessageCount).toHaveBeenCalledWith(baseProps.channelId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onIndicatorBarVisible', () => {
|
||||
|
|
@ -574,18 +628,6 @@ describe('MoreMessagesButton', () => {
|
|||
expect(clearTimeout).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return early when unreadCount is 0', () => {
|
||||
const viewableItems = [{index: 0}, {index: 1}];
|
||||
|
||||
wrapper.setProps({newMessageLineIndex: 1, unreadCount: 0});
|
||||
instance.onViewableItemsChanged(viewableItems);
|
||||
expect(clearTimeout).not.toHaveBeenCalled();
|
||||
|
||||
wrapper.setProps({newMessageLineIndex: -1});
|
||||
instance.onViewableItemsChanged(viewableItems);
|
||||
expect(clearTimeout).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should return early when viewableItems length is 0', () => {
|
||||
const viewableItems = [];
|
||||
wrapper.setProps({newMessageLineIndex: 1, unreadCount: 10});
|
||||
|
|
@ -732,21 +774,19 @@ describe('MoreMessagesButton', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should force cancel when props.unreadCount - readCount is <= 0', () => {
|
||||
it('should not set moreText when props.unreadCount - readCount is <= 0', () => {
|
||||
let readCount = props.unreadCount + 1;
|
||||
instance.showMoreText(readCount);
|
||||
expect(instance.cancel).toHaveBeenCalledTimes(1);
|
||||
expect(instance.cancel.mock.calls[0][0]).toBe(true);
|
||||
expect(instance.moreText).not.toHaveBeenCalled();
|
||||
expect(instance.setState).not.toHaveBeenCalled();
|
||||
|
||||
readCount = props.unreadCount;
|
||||
instance.showMoreText(readCount);
|
||||
expect(instance.cancel).toHaveBeenCalledTimes(2);
|
||||
expect(instance.cancel.mock.calls[1][0]).toBe(true);
|
||||
expect(instance.moreText).not.toHaveBeenCalled();
|
||||
expect(instance.setState).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should set moreTextd when props.unreadCount - readCount is > 0', () => {
|
||||
it('should set moreText when props.unreadCount - readCount is > 0', () => {
|
||||
const moreCount = 1;
|
||||
const readCount = props.unreadCount - moreCount;
|
||||
instance.showMoreText(readCount);
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ export default class ChannelBase extends PureComponent {
|
|||
selectDefaultTeam: PropTypes.func.isRequired,
|
||||
selectInitialChannel: PropTypes.func.isRequired,
|
||||
recordLoadTime: PropTypes.func.isRequired,
|
||||
resetUnreadMessageCount: PropTypes.func.isRequired,
|
||||
}).isRequired,
|
||||
componentId: PropTypes.string.isRequired,
|
||||
currentChannelId: PropTypes.string,
|
||||
|
|
@ -92,7 +91,6 @@ export default class ChannelBase extends PureComponent {
|
|||
}
|
||||
|
||||
if (currentChannelId) {
|
||||
actions.resetUnreadMessageCount(this.props.currentChannelId);
|
||||
PushNotifications.clearChannelNotifications(currentChannelId);
|
||||
requestAnimationFrame(() => {
|
||||
actions.getChannelStats(currentChannelId);
|
||||
|
|
|
|||
|
|
@ -28,7 +28,6 @@ describe('ChannelBase', () => {
|
|||
recordLoadTime: jest.fn(),
|
||||
selectDefaultTeam: jest.fn(),
|
||||
selectInitialChannel: jest.fn(),
|
||||
resetUnreadMessageCount: jest.fn(),
|
||||
},
|
||||
componentId: channelBaseComponentId,
|
||||
theme: Preferences.THEMES.default,
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@
|
|||
import {bindActionCreators} from 'redux';
|
||||
import {connect} from 'react-redux';
|
||||
|
||||
import {loadChannelsForTeam, selectInitialChannel, resetUnreadMessageCount} from '@actions/views/channel';
|
||||
import {loadChannelsForTeam, selectInitialChannel} from '@actions/views/channel';
|
||||
import {recordLoadTime} from '@actions/views/root';
|
||||
import {selectDefaultTeam} from '@actions/views/select_team';
|
||||
import {ViewTypes} from '@constants';
|
||||
|
|
@ -49,7 +49,6 @@ function mapDispatchToProps(dispatch) {
|
|||
selectDefaultTeam,
|
||||
selectInitialChannel,
|
||||
recordLoadTime,
|
||||
resetUnreadMessageCount,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue