UNPKG

mattermost-redux

Version:

Common code (API client, Redux stores, logic, utility functions) for building a Mattermost client

1,127 lines (918 loc) 59.1 kB
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import nock from 'nock'; import configureStore from 'test/test_store'; import {Client4} from 'client'; import {General} from '../constants'; import {CategoryTypes} from '../constants/channel_categories'; import {MarkUnread} from '../constants/channels'; import {getAllCategoriesByIds} from 'selectors/entities/channel_categories'; import {isFavoriteChannel} from 'selectors/entities/channels'; import {getMyPreferences} from 'selectors/entities/preferences'; import TestHelper, {DEFAULT_SERVER} from 'test/test_helper'; import {CategorySorting} from 'types/channel_categories'; import {isFavoriteChannelOld} from 'utils/channel_utils'; import * as Actions from './channel_categories'; const OK_RESPONSE = {status: 'OK'}; beforeAll(() => { Client4.setUrl(DEFAULT_SERVER); }); describe('setCategorySorting', () => { test('should call the correct API', async () => { const currentUserId = TestHelper.generateId(); const teamId = TestHelper.generateId(); const category1 = {id: 'category1', team_id: teamId}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, }, }, users: { currentUserId, }, }, }); const mock = nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories/${category1.id}`). reply(200, {...category1, sorting: CategorySorting.Recency}); await store.dispatch(Actions.setCategorySorting('category1', CategorySorting.Recency)); // The response to this is handled in the websocket code, so just confirm that the mock was called correctly expect(mock.isDone()); }); }); describe('setCategoryMuted', () => { test('should call the correct API', async () => { const currentUserId = TestHelper.generateId(); const teamId = TestHelper.generateId(); const category1 = {id: 'category1', team_id: teamId, channel_ids: []}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, }, }, users: { currentUserId, }, }, }); const mock = nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories/${category1.id}`). reply(200, {...category1, muted: true}); await store.dispatch(Actions.setCategoryMuted('category1', true)); // The response to this is handled in the websocket code, so just confirm that the mock was called correctly expect(mock.isDone()); }); test('should mute the category and all of its channels', async () => { const currentUserId = TestHelper.generateId(); const teamId = TestHelper.generateId(); const category1 = { id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2'], }; const store = await configureStore({ entities: { channelCategories: { byId: { category1, }, }, channels: { myMembers: { channel1: {notify_props: {mark_unread: MarkUnread.ALL}}, channel2: {notify_props: {mark_unread: MarkUnread.ALL}}, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories/${category1.id}`). reply(200, {...category1, muted: true}); await store.dispatch(Actions.setCategoryMuted('category1', true)); const state = store.getState(); expect(state.entities.channelCategories.byId.category1.muted).toBe(true); expect(state.entities.channels.myMembers.channel1.notify_props.mark_unread).toBe(MarkUnread.MENTION); expect(state.entities.channels.myMembers.channel2.notify_props.mark_unread).toBe(MarkUnread.MENTION); }); test('should unmute the category and all of its channels', async () => { const currentUserId = TestHelper.generateId(); const teamId = TestHelper.generateId(); const category1 = { id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2'], muted: true, }; const store = await configureStore({ entities: { channelCategories: { byId: { category1, }, }, channels: { myMembers: { channel1: {notify_props: {mark_unread: MarkUnread.MENTION}}, channel2: {notify_props: {mark_unread: MarkUnread.MENTION}}, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories/${category1.id}`). reply(200, {...category1, muted: false}); await store.dispatch(Actions.setCategoryMuted('category1', false)); const state = store.getState(); expect(state.entities.channelCategories.byId.category1.muted).toBe(false); expect(state.entities.channels.myMembers.channel1.notify_props.mark_unread).toBe(MarkUnread.ALL); expect(state.entities.channels.myMembers.channel2.notify_props.mark_unread).toBe(MarkUnread.ALL); }); }); describe('fetchMyCategories', () => { test('should populate state correctly', async () => { const currentUserId = TestHelper.generateId(); const teamId = TestHelper.generateId(); const categories = [ { id: 'category1', type: CategoryTypes.FAVORITES, team_id: teamId, }, { id: 'category2', type: CategoryTypes.FAVORITES, team_id: teamId, }, ]; const store = await configureStore({ entities: { users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). get(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, {categories, order: categories.map((category) => category.id)}); await store.dispatch(Actions.fetchMyCategories(teamId)); const state = store.getState(); expect(state.entities.channelCategories.byId.category1).toEqual(categories[0]); expect(state.entities.channelCategories.byId.category2).toEqual(categories[1]); expect(state.entities.channelCategories.orderByTeam[teamId]).toEqual(['category1', 'category2']); }); }); describe('addChannelToInitialCategory', () => { test('should add new DM channel to Direct Messages categories on all teams', async () => { const store = await configureStore({ entities: { channelCategories: { byId: { dmCategory1: {id: 'dmCategory1', team_id: 'team1', type: CategoryTypes.DIRECT_MESSAGES, channel_ids: ['dmChannel1', 'dmChannel2']}, dmCategory2: {id: 'dmCategory2', team_id: 'team2', type: CategoryTypes.DIRECT_MESSAGES, channel_ids: ['dmChannel1', 'dmChannel2']}, channelsCategory1: {id: 'channelsCategory1', team_id: 'team1', type: CategoryTypes.CHANNELS, channel_ids: ['publicChannel1', 'privateChannel1']}, }, }, }, }); const newDmChannel = {id: 'newDmChannel', type: General.DM_CHANNEL}; store.dispatch(Actions.addChannelToInitialCategory(newDmChannel)); const categoriesById = getAllCategoriesByIds(store.getState()); expect(categoriesById.dmCategory1.channel_ids).toEqual(['newDmChannel', 'dmChannel1', 'dmChannel2']); expect(categoriesById.dmCategory2.channel_ids).toEqual(['newDmChannel', 'dmChannel1', 'dmChannel2']); expect(categoriesById.channelsCategory1.channel_ids).not.toContain('newDmChannel'); }); test('should do nothing if categories have not been loaded yet for the given team', async () => { const store = await configureStore({ entities: { channelCategories: { byId: { channelsCategory1: {id: 'channelsCategory1', team_id: 'team1', type: CategoryTypes.DIRECT_MESSAGES, channel_ids: ['publicChannel1', 'privateChannel1']}, }, orderByTeam: { team1: ['channelsCategory1'], }, }, }, }); const publicChannel1 = {id: 'publicChannel1', type: General.OPEN_CHANNEL, team_id: 'team2'}; store.dispatch(Actions.addChannelToInitialCategory(publicChannel1)); const categoriesById = getAllCategoriesByIds(store.getState()); expect(categoriesById.channelsCategory1.channel_ids).toEqual(['publicChannel1', 'privateChannel1']); }); test('should add new channel to Channels category', async () => { const store = await configureStore({ entities: { channelCategories: { byId: { channelsCategory1: {id: 'channelsCategory1', team_id: 'team1', type: CategoryTypes.CHANNELS, channel_ids: ['publicChannel1', 'privateChannel1']}, dmCategory1: {id: 'dmCategory1', team_id: 'team1', type: CategoryTypes.DIRECT_MESSAGES, channel_ids: ['dmChannel1', 'dmChannel2']}, channelsCategory2: {id: 'channelsCategory2', team_id: 'team2', type: CategoryTypes.CHANNELS, channel_ids: ['publicChannel2', 'privateChannel2']}, }, orderByTeam: { team1: ['channelsCategory1', 'dmCategory1'], team2: ['channelsCategory2'], }, }, }, }); const newChannel = {id: 'newChannel', type: General.OPEN_CHANNEL, team_id: 'team1'}; store.dispatch(Actions.addChannelToInitialCategory(newChannel)); const categoriesById = getAllCategoriesByIds(store.getState()); expect(categoriesById.channelsCategory1.channel_ids).toEqual(['newChannel', 'publicChannel1', 'privateChannel1']); expect(categoriesById.dmCategory1.channel_ids).not.toContain('newChannel'); expect(categoriesById.channelsCategory2.channel_ids).not.toContain('newChannel'); }); test('should not add duplicate channel to Channels category', async () => { const store = await configureStore({ entities: { channelCategories: { byId: { channelsCategory1: {id: 'channelsCategory1', team_id: 'team1', type: CategoryTypes.CHANNELS, channel_ids: ['publicChannel1', 'privateChannel1']}, }, orderByTeam: { team1: ['channelsCategory1'], }, }, }, }); const publicChannel1 = {id: 'publicChannel1', type: General.OPEN_CHANNEL, team_id: 'team1'}; store.dispatch(Actions.addChannelToInitialCategory(publicChannel1)); const categoriesById = getAllCategoriesByIds(store.getState()); expect(categoriesById.channelsCategory1.channel_ids).toEqual(['publicChannel1', 'privateChannel1']); }); test('should not add GM channel to DIRECT_MESSAGES categories on team if it exists in a category', async () => { const store = await configureStore({ entities: { channelCategories: { byId: { dmCategory1: {id: 'dmCategory1', team_id: 'team1', type: CategoryTypes.DIRECT_MESSAGES, channel_ids: ['dmChannel1', 'dmChannel2']}, dmCategory2: {id: 'dmCategory2', team_id: 'team2', type: CategoryTypes.DIRECT_MESSAGES, channel_ids: ['dmChannel1', 'dmChannel2']}, channelsCategory1: {id: 'custom', team_id: 'team1', type: CategoryTypes.CUSTOM, channel_ids: ['publicChannel1', 'gmChannel']}, }, }, }, }); const newDmChannel = {id: 'gmChannel', type: General.GM_CHANNEL}; store.dispatch(Actions.addChannelToInitialCategory(newDmChannel)); const categoriesById = getAllCategoriesByIds(store.getState()); expect(categoriesById.dmCategory1.channel_ids).toEqual(['dmChannel1', 'dmChannel2']); expect(categoriesById.dmCategory2.channel_ids).toEqual(['gmChannel', 'dmChannel1', 'dmChannel2']); expect(categoriesById.channelsCategory1.channel_ids).toEqual(['publicChannel1', 'gmChannel']); }); }); describe('addChannelToCategory', () => { const currentUserId = TestHelper.generateId(); const teamId = TestHelper.generateId(); test('should add the channel to the given category', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2'], sorting: CategorySorting.Default}; const category2 = {id: 'category2', team_id: teamId, channel_ids: ['channel3', 'channel4'], sorting: CategorySorting.Default}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, category2, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [{...category1, channel_ids: ['channel5', 'channel1', 'channel2']}]); await store.dispatch(Actions.addChannelToCategory('category1', 'channel5')); const state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel5', 'channel1', 'channel2']); expect(state.entities.channelCategories.byId.category2).toBe(category2); // Also should not change the sort order of the category expect(state.entities.channelCategories.byId.category1.sorting).toBe(CategorySorting.Default); }); test('should remove the channel from its previous category', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2'], sorting: CategorySorting.Default}; const category2 = {id: 'category2', team_id: teamId, channel_ids: ['channel3', 'channel4'], sorting: CategorySorting.Default}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, category2, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [ {...category1, channel_ids: ['channel3', 'channel1', 'channel2']}, {...category2, channel_ids: ['channel4']}, ]); await store.dispatch(Actions.addChannelToCategory('category1', 'channel3')); const state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel3', 'channel1', 'channel2']); expect(state.entities.channelCategories.byId.category2.channel_ids).toEqual(['channel4']); // Also should not change the sort order of either category expect(state.entities.channelCategories.byId.category1.sorting).toBe(CategorySorting.Default); expect(state.entities.channelCategories.byId.category2.sorting).toBe(CategorySorting.Default); }); }); describe('moveChannelToCategory', () => { const currentUserId = TestHelper.generateId(); const teamId = TestHelper.generateId(); test('should add the channel to the given category at the correct index', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2']}; const category2 = {id: 'category2', team_id: teamId, channel_ids: ['channel3', 'channel4']}; const otherTeamCategory = {id: 'otherTeamCategory', team_id: 'team2', channel_ids: ['channel1', 'channel2']}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, category2, otherTeamCategory, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [{...category1, channel_ids: ['channel1', 'channel5', 'channel2']}]); await store.dispatch(Actions.moveChannelToCategory('category1', 'channel5', 1)); let state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel1', 'channel5', 'channel2']); expect(state.entities.channelCategories.byId.category2).toBe(category2); expect(state.entities.channelCategories.byId.otherTeamCategory).toBe(otherTeamCategory); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [{...category1, channel_ids: ['channel1', 'channel5', 'channel6', 'channel2']}]); await store.dispatch(Actions.moveChannelToCategory('category1', 'channel6', 2)); state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel1', 'channel5', 'channel6', 'channel2']); expect(state.entities.channelCategories.byId.category2).toBe(category2); expect(state.entities.channelCategories.byId.otherTeamCategory).toBe(otherTeamCategory); }); test('should remove the channel from its previous category', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2']}; const category2 = {id: 'category2', team_id: teamId, channel_ids: ['channel3', 'channel4']}; const otherTeamCategory = {id: 'otherTeamCategory', team_id: 'team2', channel_ids: ['channel1', 'channel2']}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, category2, otherTeamCategory, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [ {...category1, channel_ids: ['channel2']}, {...category2, channel_ids: ['channel3', 'channel4', 'channel1']}, ]); await store.dispatch(Actions.moveChannelToCategory('category2', 'channel1', 2)); const state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel2']); expect(state.entities.channelCategories.byId.category2.channel_ids).toEqual(['channel3', 'channel4', 'channel1']); expect(state.entities.channelCategories.byId.otherTeamCategory).toBe(otherTeamCategory); }); test('should move channel within its current category', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2', 'channel3', 'channel4', 'channel5']}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [{...category1, channel_ids: ['channel1', 'channel5', 'channel2', 'channel3', 'channel4']}]); await store.dispatch(Actions.moveChannelToCategory('category1', 'channel5', 1)); let state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel1', 'channel5', 'channel2', 'channel3', 'channel4']); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [{...category1, channel_ids: ['channel5', 'channel2', 'channel3', 'channel1', 'channel4']}]); await store.dispatch(Actions.moveChannelToCategory('category1', 'channel1', 3)); state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel5', 'channel2', 'channel3', 'channel1', 'channel4']); }); test('moving a channel to the favorites category should also favorite the channel in preferences', async () => { const favoritesCategory = {id: 'favoritesCategory', team_id: teamId, type: CategoryTypes.FAVORITES, channel_ids: []}; const otherCategory = {id: 'otherCategory', team_id: teamId, type: CategoryTypes.CUSTOM, channel_ids: ['channel1']}; const store = await configureStore({ entities: { channelCategories: { byId: { favoritesCategory, otherCategory, }, }, preferences: { myPreferences: {}, }, users: { currentUserId, }, channels: { channels: { channel1: { id: 'channel1', team_id: teamId, }, channel2: { id: 'channel2', team_id: teamId, }, }, }, }, }); let state = store.getState(); expect(isFavoriteChannel(state, 'channel1')).toBe(false); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [ {...favoritesCategory, channel_ids: ['channel1']}, {...otherCategory, channel_ids: []}, ]); // Move the channel into favorites await store.dispatch(Actions.moveChannelToCategory('favoritesCategory', 'channel1', 0)); state = store.getState(); expect(state.entities.channelCategories.byId.favoritesCategory.channel_ids).toEqual(['channel1']); expect(state.entities.channelCategories.byId.otherCategory.channel_ids).toEqual([]); expect(isFavoriteChannel(state, 'channel1')).toBe(true); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [ {...favoritesCategory, channel_ids: []}, {...otherCategory, channel_ids: ['channel1']}, ]); // And back out await store.dispatch(Actions.moveChannelToCategory('otherCategory', 'channel1', 0)); state = store.getState(); expect(state.entities.channelCategories.byId.favoritesCategory.channel_ids).toEqual([]); expect(state.entities.channelCategories.byId.otherCategory.channel_ids).toEqual(['channel1']); expect(isFavoriteChannel(state, 'channel1')).toBe(false); }); describe('changes to sorting method', () => { test('if the category was sorted by default, should set the destination category to manual sorting', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2'], sorting: CategorySorting.Default}; const category2 = {id: 'category2', team_id: teamId, channel_ids: ['channel3', 'channel4'], sorting: CategorySorting.Default}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, category2, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`, (body) => { return body.find((category) => category.id === 'category1').sorting !== CategorySorting.Manual && body.find((category) => category.id === 'category2').sorting === CategorySorting.Manual; }). reply(200, [ {...category1, channel_ids: ['channel2']}, {...category2, channel_ids: ['channel1', 'channel3', 'channel4'], sorting: CategorySorting.Manual}, ]); await store.dispatch(Actions.moveChannelToCategory(category2.id, 'channel1', 0)); let state = store.getState(); expect(state.entities.channelCategories.byId.category1.sorting).toBe(CategorySorting.Default); expect(state.entities.channelCategories.byId.category2.sorting).toBe(CategorySorting.Manual); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`, (body) => { return body.find((category) => category.id === 'category1').sorting === CategorySorting.Manual && body.find((category) => category.id === 'category2').sorting === CategorySorting.Manual; }). reply(200, [ {...category1, channel_ids: ['channel2', 'channel1'], sorting: CategorySorting.Manual}, {...category2, channel_ids: ['channel3', 'channel4'], sorting: CategorySorting.Manual}, ]); await store.dispatch(Actions.moveChannelToCategory(category1.id, 'channel1', 2)); state = store.getState(); expect(state.entities.channelCategories.byId.category1.sorting).toBe(CategorySorting.Manual); expect(state.entities.channelCategories.byId.category2.sorting).toBe(CategorySorting.Manual); }); test('if the category was sorted automatically, should not change the sorting method', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2'], sorting: CategorySorting.Alphabetical}; const category2 = {id: 'category2', team_id: teamId, channel_ids: ['channel3', 'channel4'], sorting: CategorySorting.Recency}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, category2, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`, (body) => { return body.find((category) => category.id === 'category1').sorting !== CategorySorting.Manual && body.find((category) => category.id === 'category2').sorting !== CategorySorting.Manual; }). reply(200, [ {...category1, channel_ids: ['channel2']}, {...category2, channel_ids: ['channel1', 'channel3', 'channel4']}, ]); await store.dispatch(Actions.moveChannelToCategory(category2.id, 'channel1', 0)); let state = store.getState(); expect(state.entities.channelCategories.byId.category1.sorting).toBe(CategorySorting.Alphabetical); expect(state.entities.channelCategories.byId.category2.sorting).toBe(CategorySorting.Recency); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`, (body) => { return body.find((category) => category.id === 'category1').sorting !== CategorySorting.Manual && body.find((category) => category.id === 'category2').sorting !== CategorySorting.Manual; }). reply(200, [ {...category1, channel_ids: ['channel2', 'channel1']}, {...category2, channel_ids: ['channel3', 'channel4']}, ]); await store.dispatch(Actions.moveChannelToCategory(category1.id, 'channel1', 2)); state = store.getState(); expect(state.entities.channelCategories.byId.category1.sorting).toBe(CategorySorting.Alphabetical); expect(state.entities.channelCategories.byId.category2.sorting).toBe(CategorySorting.Recency); }); }); test('should optimistically update the modified categories', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2'], sorting: CategorySorting.Default}; const favoritesCategory = {id: 'favoritesCategory', type: CategoryTypes.FAVORITES, team_id: teamId, channel_ids: [], sorting: CategorySorting.Default}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, favoritesCategory, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). delayBody(100). reply(200, [ {...category1, channel_ids: ['channel2']}, {...favoritesCategory, channel_ids: ['channel1'], sorting: CategorySorting.Manual}, ]); const moveRequest = store.dispatch(Actions.moveChannelToCategory(favoritesCategory.id, 'channel1', 0)); // At this point, the category should have already been optimistically updated, but the favorites preferences // won't be updated until after the request completes let state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel2']); expect(state.entities.channelCategories.byId.favoritesCategory.channel_ids).toEqual(['channel1']); expect(isFavoriteChannelOld(getMyPreferences(state), 'channel1')).toBe(false); await moveRequest; // And now that the request has finished, the favorites should have been updated state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel2']); expect(state.entities.channelCategories.byId.favoritesCategory.channel_ids).toEqual(['channel1']); expect(isFavoriteChannelOld(getMyPreferences(state), 'channel1')).toBe(true); }); test('should optimistically update the modified categories', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2'], sorting: CategorySorting.Default}; const favoritesCategory = {id: 'favoritesCategory', type: CategoryTypes.FAVORITES, team_id: teamId, channel_ids: [], sorting: CategorySorting.Default}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, favoritesCategory, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). delayBody(100). reply(400); const moveRequest = store.dispatch(Actions.moveChannelToCategory(favoritesCategory.id, 'channel1', 0)); // At this point, the category should have already been optimistically updated, even though it will fail let state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel2']); expect(state.entities.channelCategories.byId.favoritesCategory.channel_ids).toEqual(['channel1']); await moveRequest; // And now that the request has finished, the changes should've been rolled back state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel1', 'channel2']); expect(state.entities.channelCategories.byId.favoritesCategory.channel_ids).toEqual([]); }); }); describe('moveCategory', () => { const currentUserId = TestHelper.generateId(); test('should call the correct API', async () => { const store = await configureStore({ entities: { channelCategories: { orderByTeam: { team1: ['category1', 'category2', 'category3', 'category4'], team2: ['category5', 'category6'], }, }, users: { currentUserId, }, }, }); const mock = nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/team1/channels/categories/order`). reply(200, ['category2', 'category3', 'category4', 'category1']); await store.dispatch(Actions.moveCategory('team1', 'category1', 3)); // The response to this is handled in the websocket code, so just confirm that the mock was called correctly expect(mock.isDone()); }); }); describe('moveChannelsToCategory', () => { const currentUserId = TestHelper.generateId(); const teamId = TestHelper.generateId(); test('should add channels to the given category at the correct index', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2']}; const category2 = {id: 'category2', team_id: teamId, channel_ids: ['channel3', 'channel4']}; const otherTeamCategory = {id: 'otherTeamCategory', team_id: 'team2', channel_ids: ['channel1', 'channel2']}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, category2, otherTeamCategory, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [{...category1, channel_ids: ['channel1', 'channel5', 'channel6', 'channel2']}]); await store.dispatch(Actions.moveChannelsToCategory('category1', ['channel5', 'channel6'], 1)); let state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel1', 'channel5', 'channel6', 'channel2']); expect(state.entities.channelCategories.byId.category2).toBe(category2); expect(state.entities.channelCategories.byId.otherTeamCategory).toBe(otherTeamCategory); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [{...category1, channel_ids: ['channel1', 'channel5', 'channel6', 'channel7', 'channel8', 'channel2']}]); await store.dispatch(Actions.moveChannelsToCategory('category1', ['channel7', 'channel8'], 3)); state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel1', 'channel5', 'channel6', 'channel7', 'channel8', 'channel2']); expect(state.entities.channelCategories.byId.category2).toBe(category2); expect(state.entities.channelCategories.byId.otherTeamCategory).toBe(otherTeamCategory); }); test('should remove the channels from their previous category', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2', 'channel3']}; const category2 = {id: 'category2', team_id: teamId, channel_ids: ['channel4', 'channel5', 'channel6']}; const otherTeamCategory = {id: 'otherTeamCategory', team_id: 'team2', channel_ids: ['channel1', 'channel2']}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, category2, otherTeamCategory, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [ {...category1, channel_ids: ['channel2']}, {...category2, channel_ids: ['channel4', 'channel5', 'channel6', 'channel1', 'channel3']}, ]); await store.dispatch(Actions.moveChannelsToCategory('category2', ['channel1', 'channel3'], 3)); const state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel2']); expect(state.entities.channelCategories.byId.category2.channel_ids).toEqual(['channel4', 'channel5', 'channel6', 'channel1', 'channel3']); expect(state.entities.channelCategories.byId.otherTeamCategory).toBe(otherTeamCategory); }); test('should move channel within its current category', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2', 'channel3', 'channel4', 'channel5']}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [{...category1, channel_ids: ['channel1', 'channel5', 'channel3', 'channel2', 'channel4']}]); await store.dispatch(Actions.moveChannelsToCategory('category1', ['channel5', 'channel3'], 1)); let state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel1', 'channel5', 'channel3', 'channel2', 'channel4']); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [{...category1, channel_ids: ['channel5', 'channel3', 'channel2', 'channel1', 'channel4']}]); await store.dispatch(Actions.moveChannelsToCategory('category1', ['channel1', 'channel4'], 3)); state = store.getState(); expect(state.entities.channelCategories.byId.category1.channel_ids).toEqual(['channel5', 'channel3', 'channel2', 'channel1', 'channel4']); }); test('moving a channel to the favorites category should also favorite the channel in preferences', async () => { const favoritesCategory = {id: 'favoritesCategory', team_id: teamId, type: CategoryTypes.FAVORITES, channel_ids: []}; const otherCategory = {id: 'otherCategory', team_id: teamId, type: CategoryTypes.CUSTOM, channel_ids: ['channel1', 'channel2']}; const store = await configureStore({ entities: { channelCategories: { byId: { favoritesCategory, otherCategory, }, }, preferences: { myPreferences: {}, }, users: { currentUserId, }, channels: { channels: { channel1: { id: 'channel1', team_id: teamId, }, channel2: { id: 'channel2', team_id: teamId, }, }, }, }, }); let state = store.getState(); expect(isFavoriteChannel(state, 'channel1')).toBe(false); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [ {...favoritesCategory, channel_ids: ['channel1', 'channel2']}, {...otherCategory, channel_ids: []}, ]); // Move the channel into favorites await store.dispatch(Actions.moveChannelsToCategory('favoritesCategory', ['channel1', 'channel2'], 0)); state = store.getState(); expect(state.entities.channelCategories.byId.favoritesCategory.channel_ids).toEqual(['channel1', 'channel2']); expect(state.entities.channelCategories.byId.otherCategory.channel_ids).toEqual([]); expect(isFavoriteChannel(state, 'channel1')).toBe(true); expect(isFavoriteChannel(state, 'channel2')).toBe(true); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`). reply(200, [ {...favoritesCategory, channel_ids: []}, {...otherCategory, channel_ids: ['channel1']}, ]); // And back out await store.dispatch(Actions.moveChannelsToCategory('otherCategory', ['channel1', 'channel2'], 0)); state = store.getState(); expect(state.entities.channelCategories.byId.favoritesCategory.channel_ids).toEqual([]); expect(state.entities.channelCategories.byId.otherCategory.channel_ids).toEqual(['channel1', 'channel2']); expect(isFavoriteChannel(state, 'channel1')).toBe(false); expect(isFavoriteChannel(state, 'channel2')).toBe(false); }); describe('changes to sorting method', () => { test('if the category was sorted by default, should set the destination category to manual sorting', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2'], sorting: CategorySorting.Default}; const category2 = {id: 'category2', team_id: teamId, channel_ids: ['channel3', 'channel4'], sorting: CategorySorting.Default}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, category2, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`, (body) => { return body.find((category) => category.id === 'category1').sorting !== CategorySorting.Manual && body.find((category) => category.id === 'category2').sorting === CategorySorting.Manual; }). reply(200, [ {...category1, channel_ids: ['channel2']}, {...category2, channel_ids: ['channel1', 'channel3', 'channel4'], sorting: CategorySorting.Manual}, ]); await store.dispatch(Actions.moveChannelsToCategory(category2.id, ['channel1'], 0)); let state = store.getState(); expect(state.entities.channelCategories.byId.category1.sorting).toBe(CategorySorting.Default); expect(state.entities.channelCategories.byId.category2.sorting).toBe(CategorySorting.Manual); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`, (body) => { return body.find((category) => category.id === 'category1').sorting === CategorySorting.Manual && body.find((category) => category.id === 'category2').sorting === CategorySorting.Manual; }). reply(200, [ {...category1, channel_ids: ['channel2', 'channel1'], sorting: CategorySorting.Manual}, {...category2, channel_ids: ['channel3', 'channel4'], sorting: CategorySorting.Manual}, ]); await store.dispatch(Actions.moveChannelsToCategory(category1.id, ['channel1'], 2)); state = store.getState(); expect(state.entities.channelCategories.byId.category1.sorting).toBe(CategorySorting.Manual); expect(state.entities.channelCategories.byId.category2.sorting).toBe(CategorySorting.Manual); }); test('if the category was sorted automatically, should not change the sorting method', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2'], sorting: CategorySorting.Alphabetical}; const category2 = {id: 'category2', team_id: teamId, channel_ids: ['channel3', 'channel4'], sorting: CategorySorting.Recency}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, category2, }, }, users: { currentUserId, }, }, }); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`, (body) => { return body.find((category) => category.id === 'category1').sorting !== CategorySorting.Manual && body.find((category) => category.id === 'category2').sorting !== CategorySorting.Manual; }). reply(200, [ {...category1, channel_ids: ['channel2']}, {...category2, channel_ids: ['channel1', 'channel3', 'channel4']}, ]); await store.dispatch(Actions.moveChannelsToCategory(category2.id, ['channel1'], 0)); let state = store.getState(); expect(state.entities.channelCategories.byId.category1.sorting).toBe(CategorySorting.Alphabetical); expect(state.entities.channelCategories.byId.category2.sorting).toBe(CategorySorting.Recency); nock(Client4.getBaseRoute()). put(`/users/${currentUserId}/teams/${teamId}/channels/categories`, (body) => { return body.find((category) => category.id === 'category1').sorting !== CategorySorting.Manual && body.find((category) => category.id === 'category2').sorting !== CategorySorting.Manual; }). reply(200, [ {...category1, channel_ids: ['channel2', 'channel1']}, {...category2, channel_ids: ['channel3', 'channel4']}, ]); await store.dispatch(Actions.moveChannelsToCategory(category1.id, ['channel1'], 2)); state = store.getState(); expect(state.entities.channelCategories.byId.category1.sorting).toBe(CategorySorting.Alphabetical); expect(state.entities.channelCategories.byId.category2.sorting).toBe(CategorySorting.Recency); }); }); test('should set the destination category to manual sorting', async () => { const category1 = {id: 'category1', team_id: teamId, channel_ids: ['channel1', 'channel2'], sorting: CategorySorting.Default}; const category2 = {id: 'category2', team_id: teamId, channel_ids: ['channel3', 'channel4'], sorting: CategorySorting.Default}; const store = await configureStore({ entities: { channelCategories: { byId: { category1, category2,