diff --git a/android/app/build.gradle b/android/app/build.gradle
index 35cd1ccb1..2f38a30ed 100644
--- a/android/app/build.gradle
+++ b/android/app/build.gradle
@@ -140,7 +140,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
missingDimensionStrategy "RNN.reactNativeVersion", "reactNative60"
- versionCode 243
+ versionCode 245
versionName "1.25.0"
multiDexEnabled = true
ndk {
diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js
index 122901c02..4d89dcd77 100644
--- a/app/actions/views/channel.js
+++ b/app/actions/views/channel.js
@@ -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),
];
- 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));
+ dispatch(markChannelViewedAndRead(channelId, previousChannelId));
};
}
@@ -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,
+ };
+}
diff --git a/app/actions/views/channel.test.js b/app/actions/views/channel.test.js
index 3af66604b..de26b10c7 100644
--- a/app/actions/views/channel.test.js
+++ b/app/actions/views/channel.test.js
@@ -5,27 +5,27 @@ 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: {}}}),
-}));
+const MOCK_CHANNEL_MARK_AS_READ = 'MOCK_CHANNEL_MARK_AS_READ';
+const MOCK_CHANNEL_MARK_AS_VIEWED = 'MOCK_CHANNEL_MARK_AS_VIEWED';
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: 'MOCK_CHANNEL_MARK_AS_READ'}),
+ markChannelAsViewed: jest.fn().mockReturnValue({type: 'MOCK_CHANNEL_MARK_AS_VIEWED'}),
};
});
@@ -130,6 +130,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 +243,38 @@ 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 storeActions = store.getActions();
+ const storeBatchActions = storeActions.find(({type}) => type === 'BATCHING_REDUCER.BATCH');
+ const selectChannelWithMember = storeBatchActions.payload.find(({type}) => type === ViewTypes.SELECT_CHANNEL_WITH_MEMBER);
+ const viewedAction = storeActions.find(({type}) => type === MOCK_CHANNEL_MARK_AS_VIEWED);
+ const readAction = storeActions.find(({type}) => type === MOCK_CHANNEL_MARK_AS_READ);
+
+ const expectedSelectChannelWithMember = {
+ type: ViewTypes.SELECT_CHANNEL_WITH_MEMBER,
+ data: channelId,
+ channel: {
+ data: channelId,
+ },
+ member: {
+ data: {
+ member: {},
+ },
+ },
+
+ };
+ expect(selectChannelWithMember).toStrictEqual(expectedSelectChannelWithMember);
+ expect(viewedAction).not.toBe(null);
+ expect(readAction).not.toBe(null);
+ });
});
diff --git a/app/actions/views/root.js b/app/actions/views/root.js
index 8d3077c3c..6bed1877d 100644
--- a/app/actions/views/root.js
+++ b/app/actions/views/root.js
@@ -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));
};
}
diff --git a/app/screens/notification/__snapshots__/notification.test.js.snap b/app/screens/notification/__snapshots__/notification.test.js.snap
new file mode 100644
index 000000000..87b26a683
--- /dev/null
+++ b/app/screens/notification/__snapshots__/notification.test.js.snap
@@ -0,0 +1,3 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`Notification should match snapshot 1`] = `null`;
diff --git a/app/screens/notification/notification.test.js b/app/screens/notification/notification.test.js
new file mode 100644
index 000000000..b89a5c4c1
--- /dev/null
+++ b/app/screens/notification/notification.test.js
@@ -0,0 +1,60 @@
+// 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 Notification from './notification.js';
+
+jest.mock('react-native-navigation', () => ({
+ Navigation: {
+ events: jest.fn(() => ({
+ registerComponentDidDisappearListener: jest.fn(),
+ })),
+ },
+}));
+
+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(
+ ,
+ );
+
+ expect(wrapper.getElement()).toMatchSnapshot();
+ });
+
+ test('should call loadFromPushNotification on notification tap for non-local notification', () => {
+ const props = {
+ ...baseProps,
+ notification: {
+ localNotification: true,
+ },
+ };
+ const wrapper = shallow(
+ ,
+ );
+ 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);
+ });
+});
diff --git a/app/screens/settings/notification_settings/notification_settings.js b/app/screens/settings/notification_settings/notification_settings.js
index 9c832cd07..a878b43ea 100644
--- a/app/screens/settings/notification_settings/notification_settings.js
+++ b/app/screens/settings/notification_settings/notification_settings.js
@@ -134,12 +134,13 @@ export default class NotificationSettings extends PureComponent {
saveNotificationProps = (notifyProps) => {
const {currentUser} = this.props;
+ const prevProps = getNotificationProps(currentUser);
const updatedProps = {
- ...getNotificationProps(currentUser),
+ ...prevProps,
...notifyProps,
};
- if (!deepEqual(updatedProps, notifyProps)) {
+ if (!deepEqual(prevProps, notifyProps)) {
this.props.actions.updateMe({notify_props: updatedProps});
}
};
diff --git a/app/screens/settings/notification_settings/notification_settings.test.js b/app/screens/settings/notification_settings/notification_settings.test.js
index 96cacca59..863a966f6 100644
--- a/app/screens/settings/notification_settings/notification_settings.test.js
+++ b/app/screens/settings/notification_settings/notification_settings.test.js
@@ -9,13 +9,16 @@ import {shallowWithIntl} from 'test/intl-test-helper';
import NotificationSettings from './notification_settings.js';
+import {getNotificationProps} from 'app/utils/notify_props';
+
describe('NotificationSettings', () => {
+ const currentUser = {id: 'current_user_id'};
const baseProps = {
actions: {
updateMe: jest.fn(),
},
componentId: 'component-id',
- currentUser: {id: 'current_user_id'},
+ currentUser,
theme: Preferences.THEMES.default,
updateMeRequest: {},
currentUserStatus: 'status',
@@ -32,17 +35,22 @@ describe('NotificationSettings', () => {
});
test('should include previous notification props when saving new ones', () => {
- baseProps.currentUser.notify_props = {previous: 'previous'};
const wrapper = shallowWithIntl(
);
const instance = wrapper.instance();
+
+ const defaultNotifyProps = getNotificationProps(currentUser);
+ instance.saveNotificationProps(defaultNotifyProps);
+ expect(baseProps.actions.updateMe).toHaveBeenCalledTimes(0);
+
const newProps = {new: 'new'};
instance.saveNotificationProps(newProps);
+ expect(baseProps.actions.updateMe).toHaveBeenCalledTimes(1);
expect(baseProps.actions.updateMe).toHaveBeenCalledWith({
notify_props: {
- ...baseProps.currentUser.notify_props,
+ ...defaultNotifyProps,
...newProps,
},
});
diff --git a/app/utils/push_notifications.js b/app/utils/push_notifications.js
index 94f1dd674..6d8b480ea 100644
--- a/app/utils/push_notifications.js
+++ b/app/utils/push_notifications.js
@@ -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();
diff --git a/ios/Mattermost.xcodeproj/project.pbxproj b/ios/Mattermost.xcodeproj/project.pbxproj
index 29bc87906..5b90b036a 100644
--- a/ios/Mattermost.xcodeproj/project.pbxproj
+++ b/ios/Mattermost.xcodeproj/project.pbxproj
@@ -1060,7 +1060,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 243;
+ CURRENT_PROJECT_VERSION = 245;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
@@ -1098,7 +1098,7 @@
CODE_SIGN_ENTITLEMENTS = Mattermost/Mattermost.entitlements;
CODE_SIGN_IDENTITY = "iPhone Developer";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
- CURRENT_PROJECT_VERSION = 243;
+ CURRENT_PROJECT_VERSION = 245;
DEAD_CODE_STRIPPING = NO;
DEVELOPMENT_TEAM = UQ8HT4Q2XM;
ENABLE_BITCODE = NO;
diff --git a/ios/Mattermost/Info.plist b/ios/Mattermost/Info.plist
index 4fd49fdc3..f15b3e80a 100644
--- a/ios/Mattermost/Info.plist
+++ b/ios/Mattermost/Info.plist
@@ -34,7 +34,7 @@
CFBundleVersion
- 243
+ 245
ITSAppUsesNonExemptEncryption
LSRequiresIPhoneOS
diff --git a/ios/MattermostShare/Info.plist b/ios/MattermostShare/Info.plist
index 2aa2a57c8..15f3ed814 100644
--- a/ios/MattermostShare/Info.plist
+++ b/ios/MattermostShare/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
1.25.0
CFBundleVersion
- 243
+ 245
NSAppTransportSecurity
NSAllowsArbitraryLoads
diff --git a/ios/MattermostTests/Info.plist b/ios/MattermostTests/Info.plist
index ae0cd033f..fe65b8357 100644
--- a/ios/MattermostTests/Info.plist
+++ b/ios/MattermostTests/Info.plist
@@ -19,6 +19,6 @@
CFBundleSignature
????
CFBundleVersion
- 243
+ 245
diff --git a/ios/NotificationService/Info.plist b/ios/NotificationService/Info.plist
index 47766f21b..541cacf20 100644
--- a/ios/NotificationService/Info.plist
+++ b/ios/NotificationService/Info.plist
@@ -19,7 +19,7 @@
CFBundleShortVersionString
1.25.0
CFBundleVersion
- 243
+ 245
NSExtension
NSExtensionPointIdentifier
diff --git a/test/setup.js b/test/setup.js
index 73bb310d6..1833f6182 100644
--- a/test/setup.js
+++ b/test/setup.js
@@ -23,6 +23,7 @@ jest.doMock('react-native', () => {
ImagePickerManager,
requireNativeComponent,
Alert: RNAlert,
+ InteractionManager: RNInteractionManager,
NativeModules: RNNativeModules,
} = ReactNative;
@@ -31,6 +32,11 @@ jest.doMock('react-native', () => {
alert: jest.fn(),
};
+ const InteractionManager = {
+ ...RNInteractionManager,
+ runAfterInteractions: jest.fn((cb) => cb()),
+ };
+
const NativeModules = {
...RNNativeModules,
UIManager: {
@@ -87,6 +93,7 @@ jest.doMock('react-native', () => {
ImagePickerManager,
requireNativeComponent,
Alert,
+ InteractionManager,
NativeModules,
}, ReactNative);
});