[MM-19968] Ensure SELECT_CHANNEL_WITH_MEMBER is dispatched when app opened from a push notification (#3533) (#3547)
* Dispatch SELECT_CHANNEL_WITH_MEMBER * Call handleSelectChannel with true when from any push notification * Always dispatch SELECT_CHANNEL_WITH_MEMBER with passed channelId * Add missing comma * Update unit tests * Fix makrChannelViewedAndRead call
This commit is contained in:
parent
15c34ea486
commit
5b5e057bb7
6 changed files with 143 additions and 44 deletions
|
|
@ -379,45 +379,23 @@ export function handleSelectChannel(channelId, fromPushNotification = false) {
|
|||
dispatch(loadPostsIfNecessaryWithRetry(channelId));
|
||||
}
|
||||
|
||||
let previousChannelId;
|
||||
if (!fromPushNotification && !sameChannel) {
|
||||
previousChannelId = currentChannelId;
|
||||
}
|
||||
|
||||
const actions = [
|
||||
selectChannel(channelId),
|
||||
getChannelStats(channelId),
|
||||
setChannelDisplayName(channel.display_name),
|
||||
{
|
||||
type: ViewTypes.SET_INITIAL_POST_VISIBILITY,
|
||||
data: channelId,
|
||||
},
|
||||
setInitialPostVisibility(channelId),
|
||||
setChannelLoading(false),
|
||||
{
|
||||
type: ViewTypes.SET_LAST_CHANNEL_FOR_TEAM,
|
||||
teamId: currentTeamId,
|
||||
channelId,
|
||||
},
|
||||
setLastChannelForTeam(currentTeamId, channelId),
|
||||
selectChannelWithMember(channelId, channel, member),
|
||||
markChannelViewedAndRead(channelId, previousChannelId),
|
||||
];
|
||||
|
||||
let markPreviousChannelId;
|
||||
if (!fromPushNotification && !sameChannel) {
|
||||
markPreviousChannelId = currentChannelId;
|
||||
actions.push({
|
||||
type: ViewTypes.SELECT_CHANNEL_WITH_MEMBER,
|
||||
data: currentChannelId,
|
||||
channel: getChannel(state, currentChannelId),
|
||||
member: getMyChannelMember(state, currentChannelId),
|
||||
});
|
||||
}
|
||||
|
||||
if (!fromPushNotification) {
|
||||
actions.push({
|
||||
type: ViewTypes.SELECT_CHANNEL_WITH_MEMBER,
|
||||
data: channelId,
|
||||
channel,
|
||||
member,
|
||||
});
|
||||
}
|
||||
|
||||
dispatch(batchActions(actions));
|
||||
|
||||
dispatch(markChannelViewedAndRead(channelId, markPreviousChannelId));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -686,3 +664,27 @@ function setLoadMorePostsVisible(visible) {
|
|||
data: visible,
|
||||
};
|
||||
}
|
||||
|
||||
function setInitialPostVisibility(channelId) {
|
||||
return {
|
||||
type: ViewTypes.SET_INITIAL_POST_VISIBILITY,
|
||||
data: channelId,
|
||||
};
|
||||
}
|
||||
|
||||
function setLastChannelForTeam(teamId, channelId) {
|
||||
return {
|
||||
type: ViewTypes.SET_LAST_CHANNEL_FOR_TEAM,
|
||||
teamId,
|
||||
channelId,
|
||||
};
|
||||
}
|
||||
|
||||
function selectChannelWithMember(channelId, channel, member) {
|
||||
return {
|
||||
type: ViewTypes.SELECT_CHANNEL_WITH_MEMBER,
|
||||
data: channelId,
|
||||
channel,
|
||||
member,
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,27 +5,24 @@ import configureStore from 'redux-mock-store';
|
|||
import thunk from 'redux-thunk';
|
||||
|
||||
import initialState from 'app/initial_state';
|
||||
import {ViewTypes} from 'app/constants';
|
||||
import testHelper from 'test/test_helper';
|
||||
|
||||
import {
|
||||
import * as ChannelActions from 'app/actions/views/channel';
|
||||
const {
|
||||
handleSelectChannel,
|
||||
handleSelectChannelByName,
|
||||
loadPostsIfNecessaryWithRetry,
|
||||
} from 'app/actions/views/channel';
|
||||
} = ChannelActions;
|
||||
|
||||
import postReducer from 'mattermost-redux/reducers/entities/posts';
|
||||
|
||||
jest.mock('mattermost-redux/selectors/entities/channels', () => ({
|
||||
getChannel: () => ({data: 'received-channel-id'}),
|
||||
getCurrentChannelId: () => 'current-channel-id',
|
||||
getMyChannelMember: () => ({data: {member: {}}}),
|
||||
}));
|
||||
|
||||
jest.mock('mattermost-redux/actions/channels', () => {
|
||||
const channelActions = require.requireActual('mattermost-redux/actions/channels');
|
||||
return {
|
||||
...channelActions,
|
||||
markChannelAsRead: jest.fn(),
|
||||
markChannelAsViewed: jest.fn(),
|
||||
markChannelAsRead: jest.fn().mockReturnValue({type: ''}),
|
||||
markChannelAsViewed: jest.fn().mockReturnValue({type: ''}),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
@ -130,6 +127,11 @@ describe('Actions.Views.Channel', () => {
|
|||
},
|
||||
};
|
||||
|
||||
const channelSelectors = require('mattermost-redux/selectors/entities/channels');
|
||||
channelSelectors.getChannel = jest.fn((state, channelId) => ({data: channelId}));
|
||||
channelSelectors.getCurrentChannelId = jest.fn(() => currentChannelId);
|
||||
channelSelectors.getMyChannelMember = jest.fn(() => ({data: {member: {}}}));
|
||||
|
||||
test('handleSelectChannelByName success', async () => {
|
||||
store = mockStore(storeObj);
|
||||
|
||||
|
|
@ -238,4 +240,33 @@ describe('Actions.Views.Channel', () => {
|
|||
expect(postActions.getPostsSince).toHaveBeenCalledWith(currentChannelId, store.getState().views.channel.lastGetPosts[currentChannelId]);
|
||||
expect(receivedPostsSince).not.toBe(null);
|
||||
});
|
||||
|
||||
const handleSelectChannelCases = [
|
||||
[currentChannelId, true],
|
||||
[currentChannelId, false],
|
||||
[`not-${currentChannelId}`, true],
|
||||
[`not-${currentChannelId}`, false],
|
||||
];
|
||||
test.each(handleSelectChannelCases)('handleSelectChannel dispatches selectChannelWithMember', async (channelId, fromPushNotification) => {
|
||||
store = mockStore({...storeObj});
|
||||
|
||||
await store.dispatch(handleSelectChannel(channelId, fromPushNotification));
|
||||
const storeBatchActions = store.getActions().find(({type}) => type === 'BATCHING_REDUCER.BATCH');
|
||||
const selectChannelWithMember = storeBatchActions.payload.find(({type}) => type === ViewTypes.SELECT_CHANNEL_WITH_MEMBER);
|
||||
|
||||
const expectedSelectChannelWithMember = {
|
||||
type: ViewTypes.SELECT_CHANNEL_WITH_MEMBER,
|
||||
data: channelId,
|
||||
channel: {
|
||||
data: channelId,
|
||||
},
|
||||
member: {
|
||||
data: {
|
||||
member: {},
|
||||
},
|
||||
},
|
||||
|
||||
};
|
||||
expect(selectChannelWithMember).toStrictEqual(expectedSelectChannelWithMember);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ export function loadConfigAndLicense() {
|
|||
};
|
||||
}
|
||||
|
||||
export function loadFromPushNotification(notification, startAppFromPushNotification) {
|
||||
export function loadFromPushNotification(notification) {
|
||||
return async (dispatch, getState) => {
|
||||
const state = getState();
|
||||
const {data} = notification;
|
||||
|
|
@ -84,7 +84,7 @@ export function loadFromPushNotification(notification, startAppFromPushNotificat
|
|||
dispatch(selectTeam({id: teamId}));
|
||||
}
|
||||
|
||||
dispatch(handleSelectChannel(channelId, startAppFromPushNotification));
|
||||
dispatch(handleSelectChannel(channelId, true));
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`Notification should match snapshot 1`] = `null`;
|
||||
63
app/screens/notification/notification.test.js
Normal file
63
app/screens/notification/notification.test.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React from 'react';
|
||||
import {shallow} from 'enzyme';
|
||||
import {InteractionManager} from 'react-native';
|
||||
|
||||
import Preferences from 'mattermost-redux/constants/preferences';
|
||||
|
||||
import Notification from './notification.js';
|
||||
|
||||
jest.mock('react-native-navigation', () => ({
|
||||
Navigation: {
|
||||
events: jest.fn(() => ({
|
||||
registerComponentDidDisappearListener: jest.fn(),
|
||||
})),
|
||||
},
|
||||
}));
|
||||
|
||||
InteractionManager.runAfterInteractions = jest.fn((cb) => cb());
|
||||
|
||||
describe('Notification', () => {
|
||||
const baseProps = {
|
||||
actions: {
|
||||
loadFromPushNotification: jest.fn(),
|
||||
},
|
||||
componentId: 'component-id',
|
||||
deviceWidth: 100,
|
||||
notification: {},
|
||||
theme: Preferences.THEMES.default,
|
||||
};
|
||||
|
||||
test('should match snapshot', () => {
|
||||
const wrapper = shallow(
|
||||
<Notification {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper.getElement()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call loadFromPushNotification on notification tap for non-local notification', () => {
|
||||
const props = {
|
||||
...baseProps,
|
||||
notification: {
|
||||
localNotification: true,
|
||||
},
|
||||
};
|
||||
const wrapper = shallow(
|
||||
<Notification {...props}/>,
|
||||
);
|
||||
const instance = wrapper.instance();
|
||||
|
||||
instance.notificationTapped();
|
||||
expect(baseProps.actions.loadFromPushNotification).not.toHaveBeenCalled();
|
||||
|
||||
const notification = {
|
||||
localNotification: false,
|
||||
};
|
||||
wrapper.setProps({notification});
|
||||
instance.notificationTapped();
|
||||
expect(baseProps.actions.loadFromPushNotification).toHaveBeenCalledWith(notification);
|
||||
});
|
||||
});
|
||||
|
|
@ -47,7 +47,7 @@ class PushNotificationUtils {
|
|||
loadFromNotification = async (notification) => {
|
||||
// Set appStartedFromPushNotification to avoid channel screen to call selectInitialChannel
|
||||
EphemeralStore.appStartedFromPushNotification = true;
|
||||
await this.store.dispatch(loadFromPushNotification(notification, true));
|
||||
await this.store.dispatch(loadFromPushNotification(notification));
|
||||
|
||||
// if we have a componentId means that the app is already initialized
|
||||
const componentId = EphemeralStore.getNavigationTopComponentId();
|
||||
|
|
|
|||
Loading…
Reference in a new issue