diff --git a/app/actions/views/channel.js b/app/actions/views/channel.js index f919e6c73..64b29a3c0 100644 --- a/app/actions/views/channel.js +++ b/app/actions/views/channel.js @@ -199,7 +199,7 @@ export function loadPostsIfNecessaryWithRetry(channelId) { }); } } else { - const {lastConnectAt} = state.websocket; + const lastConnectAt = state.websocket?.lastConnectAt || 0; const lastGetPosts = state.views.channel.lastGetPosts[channelId]; let since; diff --git a/app/actions/views/channel.test.js b/app/actions/views/channel.test.js index 2b06a97d4..3775ad84b 100644 --- a/app/actions/views/channel.test.js +++ b/app/actions/views/channel.test.js @@ -4,7 +4,15 @@ import configureStore from 'redux-mock-store'; import thunk from 'redux-thunk'; -import {handleSelectChannelByName} from 'app/actions/views/channel'; +import initialState from 'app/initial_state'; +import testHelper from 'test/test_helper'; + +import { + handleSelectChannelByName, + loadPostsIfNecessaryWithRetry, +} from 'app/actions/views/channel'; + +import postReducer from 'mattermost-redux/reducers/entities/posts'; jest.mock('mattermost-redux/selectors/entities/channels', () => ({ getChannel: () => ({data: 'received-channel-id'}), @@ -19,6 +27,9 @@ describe('Actions.Views.Channel', () => { const MOCK_SELECT_CHANNEL_TYPE = 'MOCK_SELECT_CHANNEL_TYPE'; const MOCK_RECEIVE_CHANNEL_TYPE = 'MOCK_RECEIVE_CHANNEL_TYPE'; + const MOCK_RECEIVED_POSTS = 'RECEIVED_POSTS'; + const MOCK_RECEIVED_POSTS_IN_CHANNEL = 'RECEIVED_POSTS_IN_CHANNEL'; + const MOCK_RECEIVED_POSTS_SINCE = 'MOCK_RECEIVED_POSTS_SINCE'; const actions = require('mattermost-redux/actions/channels'); actions.getChannelByNameAndTeamName = jest.fn((teamName) => { @@ -38,14 +49,51 @@ describe('Actions.Views.Channel', () => { type: MOCK_SELECT_CHANNEL_TYPE, data: 'selected-channel-id', }); + const postActions = require('mattermost-redux/actions/posts'); + postActions.getPostsSince = jest.fn(() => { + return { + type: MOCK_RECEIVED_POSTS_SINCE, + data: { + order: [], + posts: {}, + }, + }; + }); + postActions.getPosts = jest.fn((channelId) => { + const order = []; + const posts = {}; + + for (let i = 0; i < 60; i++) { + const p = testHelper.fakePost(channelId); + order.push(p.id); + posts[p.id] = p; + } + + return { + type: MOCK_RECEIVED_POSTS, + data: { + order, + posts, + }, + }; + }); + + const postUtils = require('mattermost-redux/utils/post_utils'); + postUtils.getLastCreateAt = jest.fn((array) => { + return array[0].create_at; + }); + + let nextPostState = {}; const currentUserId = 'current-user-id'; const currentChannelId = 'channel-id'; const currentChannelName = 'channel-name'; const currentTeamId = 'current-team-id'; const currentTeamName = 'current-team-name'; const storeObj = { + ...initialState, entities: { + ...initialState.entities, users: { currentUserId, }, @@ -93,4 +141,84 @@ describe('Actions.Views.Channel', () => { const storeBatchActions = storeActions.some(({type}) => type === 'BATCHING_REDUCER.BATCH'); expect(storeBatchActions).toBe(false); }); + + test('loadPostsIfNecessaryWithRetry for the first time', async () => { + store = mockStore(storeObj); + + await store.dispatch(loadPostsIfNecessaryWithRetry(currentChannelId)); + expect(postActions.getPosts).toBeCalled(); + + const storeActions = store.getActions(); + const storeBatchActions = storeActions.filter(({type}) => type === 'BATCHING_REDUCER.BATCH'); + const receivedPosts = storeActions.find(({type}) => type === MOCK_RECEIVED_POSTS); + const receivedPostsAtAction = storeBatchActions[0].payload.some((action) => action.type === 'RECEIVED_POSTS_FOR_CHANNEL_AT_TIME'); + + nextPostState = postReducer(store.getState().entities.posts, receivedPosts); + nextPostState = postReducer(nextPostState, { + type: MOCK_RECEIVED_POSTS_IN_CHANNEL, + channelId: currentChannelId, + data: receivedPosts.data, + recent: true, + }); + + expect(receivedPostsAtAction).toBe(true); + }); + + test('loadPostsIfNecessaryWithRetry get posts since', async () => { + store = mockStore({ + ...storeObj, + entities: { + ...storeObj.entities, + posts: nextPostState, + }, + views: { + ...storeObj.views, + channel: { + ...storeObj.views.channel, + lastGetPosts: { + [currentChannelId]: Date.now(), + }, + }, + }, + }); + + await store.dispatch(loadPostsIfNecessaryWithRetry(currentChannelId)); + const storeActions = store.getActions(); + const receivedPostsSince = storeActions.find(({type}) => type === MOCK_RECEIVED_POSTS_SINCE); + + expect(postUtils.getLastCreateAt).toBeCalled(); + expect(postActions.getPostsSince).toHaveBeenCalledWith(currentChannelId, Object.values(store.getState().entities.posts.posts)[0].create_at); + expect(receivedPostsSince).not.toBe(null); + }); + + test('loadPostsIfNecessaryWithRetry get posts since the websocket reconnected', async () => { + const time = Date.now(); + store = mockStore({ + ...storeObj, + entities: { + ...storeObj.entities, + posts: nextPostState, + }, + views: { + ...storeObj.views, + channel: { + ...storeObj.views.channel, + lastGetPosts: { + [currentChannelId]: time, + }, + }, + }, + websocket: { + lastConnectAt: time + (1 * 60 * 1000), + }, + }); + + await store.dispatch(loadPostsIfNecessaryWithRetry(currentChannelId)); + const storeActions = store.getActions(); + const receivedPostsSince = storeActions.find(({type}) => type === MOCK_RECEIVED_POSTS_SINCE); + + expect(postUtils.getLastCreateAt).not.toBeCalled(); + expect(postActions.getPostsSince).toHaveBeenCalledWith(currentChannelId, store.getState().views.channel.lastGetPosts[currentChannelId]); + expect(receivedPostsSince).not.toBe(null); + }); }); diff --git a/test/test_helper.js b/test/test_helper.js index c4624397f..8bfe368ff 100644 --- a/test/test_helper.js +++ b/test/test_helper.js @@ -5,7 +5,7 @@ import assert from 'assert'; import Config from 'assets/config.json'; -import Client from 'mattermost-redux/client/client'; +import Client from 'mattermost-redux/client/client4'; const PASSWORD = 'password1'; @@ -103,8 +103,13 @@ class TestHelper { }; fakePost = (channelId) => { + const time = Date.now(); + return { + id: this.generateId(), channel_id: channelId, + create_at: time, + update_at: time, message: `Unit Test ${this.generateId()}`, }; };