forked from Ivasoft/mattermost-mobile
* [MM-30857] request postssince on reconnect * [MM-30857] add test * Address CR, add debounce in case of unstable connection * remove leftovers * Address review comments * remove previous changes * fix tests
553 lines
17 KiB
JavaScript
553 lines
17 KiB
JavaScript
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
|
// See LICENSE.txt for license information.
|
|
|
|
/* eslint-disable no-import-assign */
|
|
|
|
import assert from 'assert';
|
|
import nock from 'nock';
|
|
import {Server, WebSocket as MockWebSocket} from 'mock-socket';
|
|
import thunk from 'redux-thunk';
|
|
import configureMockStore from 'redux-mock-store';
|
|
|
|
import {GeneralTypes, UserTypes} from '@mm-redux/action_types';
|
|
import {notVisibleUsersActions} from '@mm-redux/actions/helpers';
|
|
import {Client4} from '@mm-redux/client';
|
|
import {General, Posts, RequestStatus} from '@mm-redux/constants';
|
|
|
|
import * as Actions from '@actions/websocket';
|
|
import {WebsocketEvents} from '@constants';
|
|
|
|
import TestHelper from 'test/test_helper';
|
|
import configureStore from 'test/test_store';
|
|
|
|
global.WebSocket = MockWebSocket;
|
|
|
|
const mockConfigRequest = (config = {}) => {
|
|
nock(Client4.getBaseRoute()).
|
|
get('/config/client?format=old').
|
|
reply(200, config);
|
|
};
|
|
|
|
const mockChanelsRequest = (teamId, channels = []) => {
|
|
nock(Client4.getUserRoute('me')).
|
|
get(`/teams/${teamId}/channels?include_deleted=true`).
|
|
reply(200, channels);
|
|
};
|
|
|
|
const mockGetKnownUsersRequest = (userIds = []) => {
|
|
nock(Client4.getBaseRoute()).
|
|
get('/users/known').
|
|
reply(200, userIds);
|
|
};
|
|
|
|
const mockRolesRequest = (rolesToLoad = []) => {
|
|
nock(Client4.getRolesRoute()).
|
|
post('/names', JSON.stringify(rolesToLoad)).
|
|
reply(200, rolesToLoad);
|
|
};
|
|
|
|
const mockTeamMemberRequest = (tm = []) => {
|
|
nock(Client4.getUserRoute('me')).
|
|
get('/teams/members').
|
|
reply(200, tm);
|
|
};
|
|
|
|
describe('Actions.Websocket', () => {
|
|
let store;
|
|
let mockServer;
|
|
beforeAll(async () => {
|
|
store = await configureStore();
|
|
await TestHelper.initBasic(Client4);
|
|
|
|
const connUrl = (Client4.getUrl() + '/api/v4/websocket').replace(/^http:/, 'ws:');
|
|
mockServer = new Server(connUrl);
|
|
return store.dispatch(Actions.init({websocketUrl: Client4.getUrl().replace(/^http:/, 'ws:')}));
|
|
});
|
|
|
|
afterAll(async () => {
|
|
Actions.close()();
|
|
mockServer.stop();
|
|
await TestHelper.tearDown();
|
|
});
|
|
|
|
it('WebSocket Connect', () => {
|
|
const ws = store.getState().requests.general.websocket;
|
|
assert.ok(ws.status === RequestStatus.SUCCESS);
|
|
});
|
|
});
|
|
|
|
describe('Actions.Websocket doReconnect', () => {
|
|
const mockStore = configureMockStore([thunk]);
|
|
const me = TestHelper.fakeUserWithId();
|
|
const team = TestHelper.fakeTeamWithId();
|
|
const teamMember = TestHelper.fakeTeamMember(me.id, team.id);
|
|
const channel1 = TestHelper.fakeChannelWithId(team.id);
|
|
const channel2 = TestHelper.fakeChannelWithId(team.id);
|
|
const cMember1 = TestHelper.fakeChannelMember(me.id, channel1.id);
|
|
const cMember2 = TestHelper.fakeChannelMember(me.id, channel2.id);
|
|
|
|
const currentTeamId = team.id;
|
|
const currentUserId = me.id;
|
|
const currentChannelId = channel1.id;
|
|
|
|
const initialState = {
|
|
entities: {
|
|
general: {
|
|
config: {},
|
|
},
|
|
teams: {
|
|
currentTeamId,
|
|
myMembers: {
|
|
[currentTeamId]: teamMember,
|
|
},
|
|
teams: {
|
|
[currentTeamId]: team,
|
|
},
|
|
},
|
|
channels: {
|
|
currentChannelId,
|
|
channels: {
|
|
currentChannelId: channel1,
|
|
},
|
|
},
|
|
users: {
|
|
currentUserId,
|
|
profiles: {
|
|
[me.id]: me,
|
|
},
|
|
},
|
|
preferences: {
|
|
myPreferences: {},
|
|
},
|
|
posts: {
|
|
posts: {},
|
|
postsInChannel: {},
|
|
},
|
|
},
|
|
websocket: {
|
|
connected: false,
|
|
lastConnectAt: 0,
|
|
lastDisconnectAt: 0,
|
|
},
|
|
};
|
|
|
|
beforeAll(async () => {
|
|
return TestHelper.initBasic(Client4);
|
|
});
|
|
|
|
beforeEach(() => {
|
|
nock(Client4.getBaseRoute()).
|
|
get('/users/me').
|
|
reply(200, me);
|
|
|
|
nock(Client4.getUserRoute('me')).
|
|
get('/teams').
|
|
reply(200, [team]);
|
|
|
|
nock(Client4.getUserRoute('me')).
|
|
get('/teams/unread').
|
|
reply(200, [{id: team.id, msg_count: 0, mention_count: 0}]);
|
|
|
|
nock(Client4.getBaseRoute()).
|
|
get('/users/me/preferences').
|
|
reply(200, []);
|
|
|
|
nock(Client4.getUserRoute('me')).
|
|
get(`/teams/${team.id}/channels/members`).
|
|
reply(200, [cMember1, cMember2]);
|
|
|
|
nock(Client4.getChannelRoute(channel1.id)).
|
|
get(`/posts?page=0&per_page=${Posts.POST_CHUNK_SIZE}`).
|
|
reply(200, {
|
|
posts: {
|
|
post1: {id: 'post1', create_at: 0, message: 'hey'},
|
|
},
|
|
order: ['post1'],
|
|
});
|
|
});
|
|
|
|
afterAll(async () => {
|
|
Actions.close()();
|
|
await TestHelper.tearDown();
|
|
});
|
|
|
|
it('handle doReconnect', async () => {
|
|
const state = {...initialState};
|
|
const testStore = await mockStore(state);
|
|
const timestamp = 1000;
|
|
const expectedActions = [
|
|
GeneralTypes.WEBSOCKET_SUCCESS,
|
|
];
|
|
const expectedMissingActions = [
|
|
'BATCH_WS_RECONNECT',
|
|
];
|
|
|
|
mockConfigRequest();
|
|
mockTeamMemberRequest([teamMember]);
|
|
mockChanelsRequest(team.id, [channel1, channel2]);
|
|
|
|
let rolesToLoad = Array.from(new Set(me.roles.split(' ').
|
|
concat(teamMember.roles.split(' '))));
|
|
mockRolesRequest(rolesToLoad);
|
|
|
|
rolesToLoad = Array.from(new Set(cMember1.roles.split(' ').
|
|
concat(cMember2.roles.split(' '))));
|
|
mockRolesRequest(rolesToLoad);
|
|
|
|
await testStore.dispatch(Actions.doReconnect(timestamp));
|
|
await TestHelper.wait(300);
|
|
const actionTypes = testStore.getActions().map((a) => a.type);
|
|
expect(actionTypes).toEqual(expectedActions);
|
|
expect(actionTypes).not.toEqual(expect.arrayContaining(expectedMissingActions));
|
|
});
|
|
|
|
it('handle doReconnect after the current channel was archived or the user left it', async () => {
|
|
const state = {
|
|
...initialState,
|
|
entities: {
|
|
...initialState.entities,
|
|
channels: {
|
|
...initialState.entities.channels,
|
|
currentChannelId: 'channel-3',
|
|
},
|
|
},
|
|
};
|
|
const testStore = await mockStore(state);
|
|
const timestamp = 1000;
|
|
const expectedActions = [
|
|
GeneralTypes.WEBSOCKET_SUCCESS,
|
|
'BATCH_WS_RECONNECT',
|
|
];
|
|
const expectedMissingActions = [
|
|
'BATCH_GET_POSTS_SINCE',
|
|
];
|
|
|
|
mockConfigRequest();
|
|
mockTeamMemberRequest([teamMember]);
|
|
mockChanelsRequest(team.id, [channel1, channel2]);
|
|
|
|
let rolesToLoad = Array.from(new Set(me.roles.split(' ').
|
|
concat(teamMember.roles.split(' '))));
|
|
mockRolesRequest(rolesToLoad);
|
|
|
|
rolesToLoad = Array.from(new Set(cMember1.roles.split(' ').
|
|
concat(cMember2.roles.split(' '))));
|
|
mockRolesRequest(rolesToLoad);
|
|
|
|
await testStore.dispatch(Actions.doReconnect(timestamp));
|
|
await TestHelper.wait(300);
|
|
|
|
const actions = testStore.getActions().map((a) => a.type);
|
|
|
|
expect(actions).toEqual(expect.arrayContaining(expectedActions));
|
|
expect(actions).not.toEqual(expect.arrayContaining(expectedMissingActions));
|
|
});
|
|
|
|
it('handle doReconnect after the current channel was archived and setting is on', async () => {
|
|
const archived = {
|
|
...channel1,
|
|
delete_at: 123,
|
|
};
|
|
const state = {
|
|
...initialState,
|
|
channels: {
|
|
currentChannelId,
|
|
channels: {
|
|
currentChannelId: archived,
|
|
},
|
|
},
|
|
};
|
|
const testStore = await mockStore(state);
|
|
const timestamp = 1000;
|
|
const expectedActions = [
|
|
GeneralTypes.WEBSOCKET_SUCCESS,
|
|
];
|
|
const expectedMissingActions = [
|
|
'BATCH_WS_RECONNECT',
|
|
];
|
|
|
|
mockConfigRequest({ExperimentalViewArchivedChannels: 'true'});
|
|
mockTeamMemberRequest([teamMember]);
|
|
mockChanelsRequest(team.id, [archived, channel2]);
|
|
|
|
let rolesToLoad = Array.from(new Set(me.roles.split(' ').
|
|
concat(teamMember.roles.split(' '))));
|
|
mockRolesRequest(rolesToLoad);
|
|
|
|
rolesToLoad = Array.from(new Set(cMember1.roles.split(' ').
|
|
concat(cMember2.roles.split(' '))));
|
|
mockRolesRequest(rolesToLoad);
|
|
|
|
await testStore.dispatch(Actions.doReconnect(timestamp));
|
|
await TestHelper.wait(300);
|
|
|
|
const actions = testStore.getActions().map((a) => a.type);
|
|
expect(actions).toEqual(expect.arrayContaining(expectedActions));
|
|
expect(actions).not.toEqual(expect.arrayContaining(expectedMissingActions));
|
|
});
|
|
|
|
it('handle doReconnect after the current channel was archived and setting is off', async () => {
|
|
const archived = {
|
|
...channel1,
|
|
delete_at: 123,
|
|
};
|
|
|
|
const state = {
|
|
...initialState,
|
|
channels: {
|
|
currentChannelId,
|
|
channels: {
|
|
currentChannelId: archived,
|
|
},
|
|
},
|
|
};
|
|
const testStore = await mockStore(state);
|
|
const timestamp = 1000;
|
|
const expectedActions = [
|
|
GeneralTypes.WEBSOCKET_SUCCESS,
|
|
'BATCH_WS_RECONNECT',
|
|
];
|
|
const expectedMissingActions = [
|
|
'BATCH_GET_POSTS_SINCE',
|
|
];
|
|
|
|
mockConfigRequest({ExperimentalViewArchivedChannels: 'false'});
|
|
mockTeamMemberRequest([teamMember]);
|
|
mockChanelsRequest(team.id, [archived, channel2]);
|
|
|
|
let rolesToLoad = Array.from(new Set(me.roles.split(' ').
|
|
concat(teamMember.roles.split(' '))));
|
|
mockRolesRequest(rolesToLoad);
|
|
|
|
rolesToLoad = Array.from(new Set(cMember1.roles.split(' ').
|
|
concat(cMember2.roles.split(' '))));
|
|
mockRolesRequest(rolesToLoad);
|
|
|
|
await testStore.dispatch(Actions.doReconnect(timestamp));
|
|
await TestHelper.wait(300);
|
|
|
|
const actions = testStore.getActions().map((a) => a.type);
|
|
expect(actions).toEqual(expectedActions);
|
|
expect(actions).not.toEqual(expect.arrayContaining(expectedMissingActions));
|
|
});
|
|
|
|
it('handle doReconnect after user left current team', async () => {
|
|
const state = {...initialState};
|
|
state.entities.teams.myMembers = {};
|
|
const testStore = await mockStore(state);
|
|
const timestamp = 1000;
|
|
const expectedActions = [
|
|
GeneralTypes.WEBSOCKET_SUCCESS,
|
|
'BATCH_WS_LEAVE_TEAM',
|
|
'BATCH_WS_RECONNECT',
|
|
];
|
|
const expectedMissingActions = [
|
|
'BATCH_GET_POSTS_SINCE',
|
|
];
|
|
|
|
mockConfigRequest();
|
|
mockTeamMemberRequest([]);
|
|
mockChanelsRequest(team.id, [channel1, channel2]);
|
|
|
|
let rolesToLoad = me.roles.split(' ');
|
|
mockRolesRequest(rolesToLoad);
|
|
|
|
rolesToLoad = Array.from(new Set(cMember1.roles.split(' ').
|
|
concat(cMember2.roles.split(' '))));
|
|
mockRolesRequest(rolesToLoad);
|
|
|
|
await testStore.dispatch(Actions.doReconnect(timestamp));
|
|
await TestHelper.wait(300);
|
|
const actions = testStore.getActions().map((a) => a.type);
|
|
expect(actions).toEqual(expectedActions);
|
|
expect(actions).not.toEqual(expect.arrayContaining(expectedMissingActions));
|
|
});
|
|
});
|
|
|
|
describe('Actions.Websocket notVisibleUsersActions', () => {
|
|
configureMockStore([thunk]);
|
|
|
|
const me = TestHelper.fakeUserWithId();
|
|
const user = TestHelper.fakeUserWithId();
|
|
const user2 = TestHelper.fakeUserWithId();
|
|
const user3 = TestHelper.fakeUserWithId();
|
|
const user4 = TestHelper.fakeUserWithId();
|
|
const user5 = TestHelper.fakeUserWithId();
|
|
|
|
it('should do nothing if the known users and the profiles list are the same', async () => {
|
|
const profiles = {
|
|
[me.id]: me,
|
|
[user.id]: user,
|
|
[user2.id]: user2,
|
|
[user3.id]: user3,
|
|
};
|
|
Client4.serverVersion = '5.23.0';
|
|
|
|
const state = {
|
|
entities: {
|
|
users: {
|
|
currentUserId: me.id,
|
|
profiles,
|
|
},
|
|
},
|
|
};
|
|
|
|
mockGetKnownUsersRequest([user.id, user2.id, user3.id]);
|
|
|
|
const actions = await notVisibleUsersActions(state);
|
|
expect(actions.length).toEqual(0);
|
|
});
|
|
|
|
it('should do nothing if there are known users in my memberships but not in the profiles list', async () => {
|
|
const profiles = {
|
|
[me.id]: me,
|
|
[user3.id]: user3,
|
|
};
|
|
Client4.serverVersion = '5.23.0';
|
|
|
|
const state = {
|
|
entities: {
|
|
users: {
|
|
currentUserId: me.id,
|
|
profiles,
|
|
},
|
|
},
|
|
};
|
|
|
|
mockGetKnownUsersRequest([user.id, user2.id, user3.id]);
|
|
|
|
const actions = await notVisibleUsersActions(state);
|
|
expect(actions.length).toEqual(0);
|
|
});
|
|
|
|
it('should remove the users if there are unknown users in the profiles list', async () => {
|
|
const profiles = {
|
|
[me.id]: me,
|
|
[user.id]: user,
|
|
[user2.id]: user2,
|
|
[user3.id]: user3,
|
|
[user4.id]: user4,
|
|
[user5.id]: user5,
|
|
};
|
|
Client4.serverVersion = '5.23.0';
|
|
|
|
const state = {
|
|
entities: {
|
|
users: {
|
|
currentUserId: me.id,
|
|
profiles,
|
|
},
|
|
},
|
|
};
|
|
|
|
mockGetKnownUsersRequest([user.id, user3.id]);
|
|
|
|
const expectedAction = [
|
|
{type: UserTypes.PROFILE_NO_LONGER_VISIBLE, data: {user_id: user2.id}},
|
|
{type: UserTypes.PROFILE_NO_LONGER_VISIBLE, data: {user_id: user4.id}},
|
|
{type: UserTypes.PROFILE_NO_LONGER_VISIBLE, data: {user_id: user5.id}},
|
|
];
|
|
const actions = await notVisibleUsersActions(state);
|
|
expect(actions.length).toEqual(3);
|
|
expect(actions).toEqual(expectedAction);
|
|
});
|
|
|
|
it('should do nothing if the server version is less than 5.23', async () => {
|
|
const profiles = {
|
|
[me.id]: me,
|
|
[user.id]: user,
|
|
[user2.id]: user2,
|
|
[user3.id]: user3,
|
|
[user4.id]: user4,
|
|
[user5.id]: user5,
|
|
};
|
|
Client4.serverVersion = '5.22.0';
|
|
|
|
const state = {
|
|
entities: {
|
|
users: {
|
|
currentUserId: me.id,
|
|
profiles,
|
|
},
|
|
},
|
|
};
|
|
|
|
mockGetKnownUsersRequest([user.id, user3.id]);
|
|
|
|
const actions = await notVisibleUsersActions(state);
|
|
expect(actions.length).toEqual(0);
|
|
});
|
|
});
|
|
|
|
describe('Actions.Websocket handleUserTypingEvent', () => {
|
|
const mockStore = configureMockStore([thunk]);
|
|
|
|
const currentUserId = 'user-id';
|
|
const otherUserId = 'other-user-id';
|
|
const currentChannelId = 'channel-id';
|
|
const otherChannelId = 'other-channel-id';
|
|
|
|
const initialState = {
|
|
entities: {
|
|
general: {
|
|
config: {},
|
|
},
|
|
channels: {
|
|
currentChannelId,
|
|
channels: {
|
|
currentChannelId: {
|
|
id: currentChannelId,
|
|
name: 'channel',
|
|
},
|
|
},
|
|
},
|
|
users: {
|
|
currentUserId,
|
|
profiles: {
|
|
[currentUserId]: {},
|
|
[otherUserId]: {},
|
|
},
|
|
statuses: {
|
|
[currentUserId]: General.ONLINE,
|
|
[otherUserId]: General.OFFLINE,
|
|
},
|
|
},
|
|
preferences: {
|
|
myPreferences: {},
|
|
},
|
|
},
|
|
};
|
|
|
|
it('dispatches actions for current channel if other user is typing', async () => {
|
|
const state = {...initialState};
|
|
const testStore = await mockStore(state);
|
|
const msg = {broadcast: {channel_id: currentChannelId}, data: {parent_id: 'parent-id', user_id: otherUserId}};
|
|
|
|
nock(Client4.getUsersRoute()).
|
|
post('/status/ids', JSON.stringify([otherUserId])).
|
|
reply(200, ['away']);
|
|
|
|
const expectedActionsTypes = [
|
|
WebsocketEvents.TYPING,
|
|
UserTypes.RECEIVED_STATUSES,
|
|
];
|
|
|
|
await testStore.dispatch(Actions.handleUserTypingEvent(msg));
|
|
await TestHelper.wait(300);
|
|
const actionTypes = testStore.getActions().map((action) => action.type);
|
|
expect(actionTypes).toEqual(expectedActionsTypes);
|
|
});
|
|
|
|
it('does not dispatch actions for non current channel', async () => {
|
|
const state = {...initialState};
|
|
const testStore = await mockStore(state);
|
|
const msg = {broadcast: {channel_id: otherChannelId}, data: {parent_id: 'parent-id', user_id: otherUserId}};
|
|
|
|
const expectedActionsTypes = [];
|
|
|
|
await testStore.dispatch(Actions.handleUserTypingEvent(msg));
|
|
const actionTypes = testStore.getActions().map((action) => action.type);
|
|
expect(actionTypes).toEqual(expectedActionsTypes);
|
|
});
|
|
});
|