Files
upstream-mattermost/webapp/channels/src/actions/views/rhs.test.ts
upstream-mirror 240db4e2a7
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s
snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
2026-09-16 00:29:55 +05:00

1003 lines
34 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import cloneDeep from 'lodash/cloneDeep';
import set from 'lodash/set';
import type {Dispatch} from 'redux';
import {batchActions} from 'redux-batched-actions';
import type {MockStoreEnhanced} from 'redux-mock-store';
import type {Post} from '@mattermost/types/posts';
import type {UserProfile} from '@mattermost/types/users';
import type {IDMappedObjects} from '@mattermost/types/utilities';
import {SearchTypes} from 'mattermost-redux/action_types';
import * as SearchActions from 'mattermost-redux/actions/search';
import {getCurrentTimezone} from 'mattermost-redux/selectors/entities/timezone';
import {
updateRhsState,
selectPostFromRightHandSideSearch,
selectPostAndHighlight,
updateSearchTerms,
performSearch,
showSearchResults,
showFlaggedPosts,
showPinnedPosts,
showMentions,
closeRightHandSide,
showRHSPlugin,
hideRHSPlugin,
toggleRHSPlugin,
toggleMenu,
openMenu,
closeMenu,
openAtPrevious,
updateSearchType,
suppressRHS,
unsuppressRHS,
goBack,
showChannelMembers,
openShowEditHistory,
updateSearchTeam,
} from 'actions/views/rhs';
import mockStore from 'tests/test_store';
import {ActionTypes, RHSStates, Constants} from 'utils/constants';
import {TestHelper} from 'utils/test_helper';
import {getUtcOffsetForTimeZone} from 'utils/timezone';
import type {GlobalState} from 'types/store';
import type {RhsState} from 'types/store/rhs';
import type {ViewsState} from 'types/store/views';
const currentChannelId = '123';
const currentTeamId = '321';
const currentUserId = 'user123';
const currentUsername = 'user-name';
const currentUserFirstName = 'first-name';
const pluggableId = 'pluggableId';
const previousSelectedPost = {
id: 'post123',
channel_id: 'channel123',
root_id: 'root123',
} as Post;
const UserSelectors = require('mattermost-redux/selectors/entities/users');
UserSelectors.getCurrentUserMentionKeys = jest.fn(() => [{key: '@here'}, {key: '@mattermost'}, {key: '@channel'}, {key: '@all'}]);
// Mock Date.now() to return a constant value.
const POST_CREATED_TIME = Date.now();
global.Date.now = jest.fn(() => POST_CREATED_TIME);
jest.mock('mattermost-redux/actions/posts', () => ({
getPostThread: (...args: any) => ({type: 'MOCK_GET_POST_THREAD', args}),
getMentionsAndStatusesForPosts: (...args: any) => ({type: 'MOCK_GET_MENTIONS_AND_STATUSES_FOR_POSTS', args}),
}));
jest.mock('mattermost-redux/actions/search', () => ({
searchPostsWithParams: (...args: any) => ({type: 'MOCK_SEARCH_POSTS', args}),
searchFilesWithParams: (...args: any) => ({type: 'MOCK_SEARCH_FILES', args}),
clearSearch: (...args: any) => ({type: 'MOCK_CLEAR_SEARCH', args}),
getFlaggedPosts: jest.fn(),
getPinnedPosts: jest.fn(),
}));
describe('rhs view actions', () => {
const initialState = {
entities: {
channels: {
currentChannelId,
},
teams: {
currentTeamId,
},
users: {
currentUserId,
profiles: {
user123: {
username: currentUsername,
first_name: currentUserFirstName,
timezone: {
useAutomaticTimezone: true,
automaticTimezone: 'UTC+3', // set an arbitrary timezone
manualTimezone: '',
},
} as UserProfile,
} as IDMappedObjects<UserProfile>,
},
posts: {
posts: {
[previousSelectedPost.id]: previousSelectedPost,
} as IDMappedObjects<Post>,
},
preferences: {myPreferences: {}},
},
views: {
rhs: {
rhsState: null,
filesSearchExtFilter: [] as string[],
searchType: '',
},
posts: {
editingPost: {
show: true,
postId: '818f3dprzb8mtmyoobmzcgnb8y',
isRHS: false,
},
},
},
} as GlobalState;
let store: MockStoreEnhanced<GlobalState>;
beforeEach(() => {
store = mockStore(initialState);
});
describe('updateRhsState', () => {
test(`it dispatches ${ActionTypes.UPDATE_RHS_STATE} correctly with defaults`, () => {
store.dispatch(updateRhsState(RHSStates.PIN));
const action = {
type: ActionTypes.UPDATE_RHS_STATE,
state: RHSStates.PIN,
channelId: currentChannelId,
};
expect(store.getActions()).toEqual([action]);
});
test(`it dispatches ${ActionTypes.UPDATE_RHS_STATE} correctly`, () => {
store.dispatch(updateRhsState(RHSStates.PIN, 'channelId', RHSStates.CHANNEL_INFO as RhsState));
const action = {
type: ActionTypes.UPDATE_RHS_STATE,
state: RHSStates.PIN,
channelId: 'channelId',
previousRhsState: RHSStates.CHANNEL_INFO,
};
expect(store.getActions()).toEqual([action]);
});
});
describe('selectPostFromRightHandSideSearch', () => {
const post = {
id: 'post123',
channel_id: 'channel123',
root_id: 'root123',
} as Post;
describe(`it dispatches ${ActionTypes.SELECT_POST} correctly`, () => {
it('with mocked date', async () => {
store = mockStore({
...initialState,
views: {
rhs: {
rhsState: RHSStates.FLAG,
filesSearchExtFilter: [] as string[],
},
} as ViewsState,
});
await store.dispatch(selectPostFromRightHandSideSearch(post));
const action = {
type: ActionTypes.SELECT_POST,
postId: post.root_id,
channelId: post.channel_id,
previousRhsState: RHSStates.FLAG,
timestamp: POST_CREATED_TIME,
};
expect(store.getActions()[0]).toEqual(action);
});
});
});
describe('updateSearchTerms', () => {
test(`it dispatches ${ActionTypes.UPDATE_RHS_SEARCH_TERMS} correctly`, () => {
const terms = '@here test terms';
store.dispatch(updateSearchTerms(terms));
const action = {
type: ActionTypes.UPDATE_RHS_SEARCH_TERMS,
terms,
};
expect(store.getActions()).toEqual([action]);
});
});
describe('performSearch', () => {
// timezone offset in seconds
let timeZoneOffset = getUtcOffsetForTimeZone(getCurrentTimezone(initialState)) * 60;
// Avoid problems with negative cero
if (timeZoneOffset === 0) {
timeZoneOffset = 0;
}
test('it dispatches searchPosts correctly', () => {
const terms = '@here test search';
store.dispatch(performSearch(terms, currentTeamId, false));
const compareStore = mockStore(initialState);
compareStore.dispatch(SearchActions.searchPostsWithParams(currentTeamId, {include_deleted_channels: true, terms, is_or_search: false, time_zone_offset: timeZoneOffset, page: 0, per_page: 20}));
compareStore.dispatch(SearchActions.searchFilesWithParams(currentTeamId, {include_deleted_channels: true, terms, is_or_search: false, time_zone_offset: timeZoneOffset, page: 0, per_page: 20}));
expect(store.getActions()).toEqual(compareStore.getActions());
});
test('it dispatches searchFiles correctly', () => {
store = mockStore({
...initialState,
views: {
...initialState.views,
rhs: {
...initialState.views.rhs,
filesSearchExtFilter: ['txt', 'jpeg'],
},
} as ViewsState,
});
const terms = '@here test search';
store.dispatch(performSearch(terms, currentTeamId, false));
const filesExtTerms = '@here test search ext:txt ext:jpeg';
const compareStore = mockStore(initialState);
compareStore.dispatch(SearchActions.searchPostsWithParams(currentTeamId, {include_deleted_channels: true, terms, is_or_search: false, time_zone_offset: timeZoneOffset, page: 0, per_page: 20}));
compareStore.dispatch(SearchActions.searchFilesWithParams(currentTeamId, {include_deleted_channels: true, terms: filesExtTerms, is_or_search: false, time_zone_offset: timeZoneOffset, page: 0, per_page: 20}));
expect(store.getActions()).toEqual(compareStore.getActions());
});
test('it dispatches searchPosts correctly for Recent Mentions', () => {
const terms = `@here test search ${currentUsername} @${currentUsername} ${currentUserFirstName} custom-hyphenated-term`;
store.dispatch(performSearch(terms, '', true));
const mentionsQuotedTerms = `"@here" "test" "search" "${currentUsername}" "@${currentUsername}" "${currentUserFirstName}" "custom-hyphenated-term"`;
const compareStore = mockStore(initialState);
compareStore.dispatch(SearchActions.searchPostsWithParams('', {include_deleted_channels: true, terms: mentionsQuotedTerms, is_or_search: true, time_zone_offset: timeZoneOffset, page: 0, per_page: 20}));
compareStore.dispatch(SearchActions.searchFilesWithParams('', {include_deleted_channels: true, terms, is_or_search: true, time_zone_offset: timeZoneOffset, page: 0, per_page: 20}));
expect(store.getActions()).toEqual(compareStore.getActions());
});
});
describe('showSearchResults', () => {
const terms = '@here test search';
const testInitialState = {
...initialState,
views: {
rhs: {
searchTerms: terms,
searchTeam: null,
searchType: 'messages',
filesSearchExtFilter: [] as string[],
},
},
} as GlobalState;
test('it dispatches the right actions', () => {
store = mockStore(testInitialState);
store.dispatch(showSearchResults());
const compareStore = mockStore(testInitialState);
compareStore.dispatch(updateRhsState(RHSStates.SEARCH));
compareStore.dispatch({
type: ActionTypes.UPDATE_RHS_SEARCH_RESULTS_TERMS,
terms,
});
compareStore.dispatch({
type: ActionTypes.UPDATE_RHS_SEARCH_RESULTS_TYPE,
searchType: 'messages',
});
compareStore.dispatch(performSearch(terms, currentTeamId));
expect(store.getActions()).toEqual(compareStore.getActions());
});
});
describe('showFlaggedPosts', () => {
test('it dispatches the right actions', async () => {
(SearchActions.getFlaggedPosts as jest.Mock).mockReturnValue((dispatch: Dispatch) => {
dispatch({type: 'MOCK_GET_FLAGGED_POSTS'});
return {data: 'data'};
});
await store.dispatch(showFlaggedPosts());
expect(SearchActions.getFlaggedPosts).toHaveBeenCalled();
expect(store.getActions()).toEqual([
{
type: ActionTypes.UPDATE_RHS_STATE,
state: RHSStates.FLAG,
},
{
type: 'MOCK_GET_FLAGGED_POSTS',
},
{
type: 'BATCHING_REDUCER.BATCH',
meta: {
batch: true,
},
payload: [
{
type: SearchTypes.RECEIVED_SEARCH_POSTS,
data: 'data',
},
{
type: SearchTypes.RECEIVED_SEARCH_TERM,
data: {
teamId: currentTeamId,
terms: null,
isOrSearch: false,
},
},
],
},
]);
});
});
describe('showPinnedPosts', () => {
test('it dispatches the right actions for the current channel', async () => {
(SearchActions.getPinnedPosts as jest.Mock).mockReturnValue((dispatch: Dispatch) => {
dispatch({type: 'MOCK_GET_PINNED_POSTS'});
return {data: 'data'};
});
await store.dispatch(showPinnedPosts());
expect(SearchActions.getPinnedPosts).toHaveBeenCalledWith(currentChannelId);
expect(store.getActions()).toEqual([
{
type: ActionTypes.UPDATE_RHS_STATE,
channelId: currentChannelId,
state: RHSStates.PIN,
previousRhsState: null,
},
{
type: 'MOCK_GET_PINNED_POSTS',
},
{
type: 'BATCHING_REDUCER.BATCH',
meta: {
batch: true,
},
payload: [
{
type: SearchTypes.RECEIVED_SEARCH_POSTS,
data: 'data',
},
{
type: SearchTypes.RECEIVED_SEARCH_TERM,
data: {
teamId: currentTeamId,
terms: null,
isOrSearch: false,
},
},
],
},
]);
});
test('it dispatches the right actions for a specific channel', async () => {
const channelId = 'channel1';
(SearchActions.getPinnedPosts as jest.Mock).mockReturnValue((dispatch: Dispatch) => {
dispatch({type: 'MOCK_GET_PINNED_POSTS'});
return {data: 'data'};
});
await store.dispatch(showPinnedPosts(channelId));
expect(SearchActions.getPinnedPosts).toHaveBeenCalledWith(channelId);
expect(store.getActions()).toEqual([
{
type: ActionTypes.UPDATE_RHS_STATE,
channelId,
state: RHSStates.PIN,
previousRhsState: null,
},
{
type: 'MOCK_GET_PINNED_POSTS',
},
{
type: 'BATCHING_REDUCER.BATCH',
meta: {
batch: true,
},
payload: [
{
type: SearchTypes.RECEIVED_SEARCH_POSTS,
data: 'data',
},
{
type: SearchTypes.RECEIVED_SEARCH_TERM,
data: {
teamId: currentTeamId,
terms: null,
isOrSearch: false,
},
},
],
},
]);
});
});
describe('showChannelMembers', () => {
test('it dispatches the right actions', async () => {
await store.dispatch(showChannelMembers(currentChannelId));
expect(store.getActions()).toEqual([
{
type: ActionTypes.UPDATE_RHS_STATE,
channelId: currentChannelId,
state: RHSStates.CHANNEL_MEMBERS,
previousRhsState: null,
},
]);
});
});
describe('openShowEditHistory', () => {
test('it dispatches the right actions', async () => {
const post = TestHelper.getPostMock();
await store.dispatch(openShowEditHistory(post));
expect(store.getActions()).toEqual([
{
type: ActionTypes.UPDATE_RHS_STATE,
state: RHSStates.EDIT_HISTORY,
postId: post.root_id || post.id,
channelId: post.channel_id,
timestamp: POST_CREATED_TIME,
},
]);
});
});
describe('showMentions', () => {
test('it dispatches the right actions', () => {
store.dispatch(showMentions());
const compareStore = mockStore(initialState);
compareStore.dispatch(performSearch('@mattermost ', '', true));
compareStore.dispatch(batchActions([
{
type: ActionTypes.UPDATE_RHS_SEARCH_TERMS,
terms: '@mattermost ',
},
{
type: ActionTypes.UPDATE_RHS_STATE,
state: RHSStates.MENTION,
},
]));
expect(store.getActions()).toEqual(compareStore.getActions());
});
});
describe('closeRightHandSide', () => {
test('it dispatches the right actions without editingPost', () => {
const state = cloneDeep(initialState);
set(state, 'views.posts.editingPost', {});
store = mockStore(state);
store.dispatch(closeRightHandSide());
const expectedActions = [{
type: 'BATCHING_REDUCER.BATCH',
meta: {
batch: true,
},
payload: [
{
type: 'UPDATE_RHS_STATE',
state: null,
},
{
type: 'SELECT_POST',
postId: '',
channelId: '',
timestamp: 0,
},
],
}];
expect(store.getActions()).toEqual(expectedActions);
});
test('it dispatches the right actions with editingPost in center channel', () => {
const state = cloneDeep(initialState);
set(state, 'views.posts.editingPost.isRHS', false);
store = mockStore(state);
store.dispatch(closeRightHandSide());
const expectedActions = [{
type: 'BATCHING_REDUCER.BATCH',
meta: {
batch: true,
},
payload: [
{
type: 'UPDATE_RHS_STATE',
state: null,
},
{
type: 'SELECT_POST',
postId: '',
channelId: '',
timestamp: 0,
},
],
}];
expect(store.getActions()).toEqual(expectedActions);
});
test('it dispatches the right actions with editingPost in RHS', () => {
const state = cloneDeep(initialState);
set(state, 'views.posts.editingPost.isRHS', true);
store = mockStore(state);
store.dispatch(closeRightHandSide());
const expectedActions = [{
type: 'BATCHING_REDUCER.BATCH',
meta: {
batch: true,
},
payload: [
{
type: 'UPDATE_RHS_STATE',
state: null,
},
{
type: 'SELECT_POST',
postId: '',
channelId: '',
timestamp: 0,
},
],
}];
expect(store.getActions()).toEqual(expectedActions);
});
});
it('toggleMenu dispatches the right action', () => {
store.dispatch(toggleMenu());
const compareStore = mockStore(initialState);
compareStore.dispatch({
type: ActionTypes.TOGGLE_RHS_MENU,
});
expect(store.getActions()).toEqual(compareStore.getActions());
});
it('openMenu dispatches the right action', () => {
store.dispatch(openMenu());
const compareStore = mockStore(initialState);
compareStore.dispatch({
type: ActionTypes.OPEN_RHS_MENU,
});
expect(store.getActions()).toEqual(compareStore.getActions());
});
it('closeMenu dispatches the right action', () => {
store.dispatch(closeMenu());
const compareStore = mockStore(initialState);
compareStore.dispatch({
type: ActionTypes.CLOSE_RHS_MENU,
});
expect(store.getActions()).toEqual(compareStore.getActions());
});
describe('Plugin actions', () => {
const stateWithPluginRhs = cloneDeep(initialState);
set(stateWithPluginRhs, `views.rhs.${pluggableId}`, pluggableId);
set(stateWithPluginRhs, 'views.rhs.rhsState', RHSStates.PLUGIN);
const stateWithoutPluginRhs = cloneDeep(initialState);
set(stateWithoutPluginRhs, 'views.rhs.rhsState', RHSStates.PIN);
describe('showRHSPlugin', () => {
it('dispatches the right action', () => {
store.dispatch(showRHSPlugin(pluggableId));
const compareStore = mockStore(initialState);
compareStore.dispatch({
type: ActionTypes.UPDATE_RHS_STATE,
state: RHSStates.PLUGIN,
pluggableId,
});
expect(store.getActions()).toEqual(compareStore.getActions());
});
});
describe('hideRHSPlugin', () => {
it('it dispatches the right action when plugin rhs is opened', () => {
store = mockStore(stateWithPluginRhs);
store.dispatch(hideRHSPlugin(pluggableId));
const compareStore = mockStore(stateWithPluginRhs);
compareStore.dispatch(closeRightHandSide());
expect(store.getActions()).toEqual(compareStore.getActions());
});
it('it doesn\'t dispatch the action when plugin rhs is closed', () => {
store = mockStore(stateWithoutPluginRhs);
store.dispatch(hideRHSPlugin(pluggableId));
const compareStore = mockStore(initialState);
expect(store.getActions()).toEqual(compareStore.getActions());
});
it('it doesn\'t dispatch the action when other plugin rhs is opened', () => {
store = mockStore(stateWithPluginRhs);
store.dispatch(hideRHSPlugin('pluggableId2'));
const compareStore = mockStore(initialState);
expect(store.getActions()).toEqual(compareStore.getActions());
});
});
describe('toggleRHSPlugin', () => {
it('it dispatches hide action when rhs is open', () => {
store = mockStore(stateWithPluginRhs);
store.dispatch(toggleRHSPlugin(pluggableId));
const compareStore = mockStore(initialState);
compareStore.dispatch(closeRightHandSide());
expect(store.getActions()).toEqual(compareStore.getActions());
});
it('it dispatches hide action when rhs is closed', () => {
store = mockStore(stateWithoutPluginRhs);
store.dispatch(toggleRHSPlugin(pluggableId));
const compareStore = mockStore(initialState);
compareStore.dispatch(showRHSPlugin(pluggableId));
expect(store.getActions()).toEqual(compareStore.getActions());
});
});
});
describe('openAtPrevious', () => {
const batchingReducerBatch = {
type: 'BATCHING_REDUCER.BATCH',
meta: {
batch: true,
},
payload: [
{
type: SearchTypes.RECEIVED_SEARCH_POSTS,
data: 'data',
},
{
type: SearchTypes.RECEIVED_SEARCH_TERM,
data: {
teamId: currentTeamId,
terms: null,
isOrSearch: false,
},
},
],
};
function actionsForEmptySearch() {
const compareStore = mockStore(initialState);
compareStore.dispatch(SearchActions.clearSearch());
compareStore.dispatch(updateSearchTerms(''));
compareStore.dispatch(updateSearchTeam(null));
compareStore.dispatch({
type: ActionTypes.UPDATE_RHS_SEARCH_RESULTS_TERMS,
terms: '',
});
compareStore.dispatch(updateRhsState(RHSStates.SEARCH));
return compareStore.getActions();
}
it('opens to empty search when not previously opened', () => {
store.dispatch(openAtPrevious(null));
expect(store.getActions()).toEqual(actionsForEmptySearch());
});
it('opens a mention search', () => {
store.dispatch(openAtPrevious({isMentionSearch: true}));
const compareStore = mockStore(initialState);
compareStore.dispatch(performSearch('@mattermost ', '', true));
compareStore.dispatch(batchActions([
{
type: ActionTypes.UPDATE_RHS_SEARCH_TERMS,
terms: '@mattermost ',
},
{
type: ActionTypes.UPDATE_RHS_STATE,
state: RHSStates.MENTION,
},
]));
expect(store.getActions()).toEqual(compareStore.getActions());
});
it('opens pinned posts', async () => {
(SearchActions.getPinnedPosts as jest.Mock).mockReturnValue((dispatch: Dispatch) => {
dispatch({type: 'MOCK_GET_PINNED_POSTS'});
return {data: 'data'};
});
await store.dispatch(openAtPrevious({isPinnedPosts: true}));
expect(SearchActions.getPinnedPosts).toHaveBeenCalledWith(currentChannelId);
expect(store.getActions()).toEqual([
{
type: ActionTypes.UPDATE_RHS_STATE,
channelId: currentChannelId,
state: RHSStates.PIN,
previousRhsState: null,
},
{
type: 'MOCK_GET_PINNED_POSTS',
},
batchingReducerBatch,
]);
});
it('opens flagged posts', async () => {
(SearchActions.getFlaggedPosts as jest.Mock).mockReturnValue((dispatch: Dispatch) => {
dispatch({type: 'MOCK_GET_FLAGGED_POSTS'});
return {data: 'data'};
});
await store.dispatch(openAtPrevious({isFlaggedPosts: true}));
expect(SearchActions.getFlaggedPosts).toHaveBeenCalled();
expect(store.getActions()).toEqual([
{
type: ActionTypes.UPDATE_RHS_STATE,
state: RHSStates.FLAG,
},
{
type: 'MOCK_GET_FLAGGED_POSTS',
},
batchingReducerBatch,
]);
});
it('opens selected post', async () => {
const previousState = 'flag';
await store.dispatch(openAtPrevious({selectedPostId: previousSelectedPost.id, previousRhsState: previousState}));
const compareStore = mockStore(initialState);
compareStore.dispatch({
type: ActionTypes.SELECT_POST,
postId: previousSelectedPost.root_id,
channelId: previousSelectedPost.channel_id,
previousRhsState: previousState,
timestamp: POST_CREATED_TIME,
});
expect(store.getActions()).toEqual(compareStore.getActions());
});
it('opens empty search when selected post does not exist', async () => {
await store.dispatch(openAtPrevious({selectedPostId: 'postxyz'}));
expect(store.getActions()).toEqual(actionsForEmptySearch());
});
it('opens selected post card', async () => {
const previousState = 'flag';
await store.dispatch(openAtPrevious({selectedPostCardId: previousSelectedPost.id, previousRhsState: previousState}));
const compareStore = mockStore(initialState);
compareStore.dispatch({
type: ActionTypes.SELECT_POST_CARD,
postId: previousSelectedPost.id,
channelId: previousSelectedPost.channel_id,
previousRhsState: previousState,
});
expect(store.getActions()).toEqual(compareStore.getActions());
});
it('opens empty search when selected post card does not exist', async () => {
await store.dispatch(openAtPrevious({selectedPostCardId: 'postxyz'}));
expect(store.getActions()).toEqual(actionsForEmptySearch());
});
it('opens search results', async () => {
const terms = '@here test search';
const searchInitialState = {
...initialState,
views: {
rhs: {
searchTerms: terms,
searchTeam: null,
searchType: 'messages',
filesSearchExtFilter: [] as string[],
},
},
} as GlobalState;
store = mockStore(searchInitialState);
await store.dispatch(openAtPrevious({searchVisible: true}));
const compareStore = mockStore(searchInitialState);
compareStore.dispatch(updateRhsState(RHSStates.SEARCH));
compareStore.dispatch({
type: ActionTypes.UPDATE_RHS_SEARCH_RESULTS_TERMS,
terms,
});
compareStore.dispatch({
type: ActionTypes.UPDATE_RHS_SEARCH_RESULTS_TYPE,
searchType: 'messages',
});
compareStore.dispatch(performSearch(terms, currentTeamId));
expect(store.getActions()).toEqual(compareStore.getActions());
});
it('opens empty search when no other options set', () => {
store.dispatch(openAtPrevious({}));
expect(store.getActions()).toEqual(actionsForEmptySearch());
});
});
describe('searchType', () => {
test('updateSearchType', () => {
const store = mockStore(initialState);
store.dispatch(updateSearchType('files'));
expect(store.getActions()).toEqual([{
type: ActionTypes.UPDATE_RHS_SEARCH_TYPE,
searchType: 'files',
}]);
});
});
describe('selectPostAndHighlight', () => {
const post1 = {id: '42'} as Post;
const post2 = {id: '43'} as Post;
const post3 = {id: '44'} as Post;
beforeEach(() => {
jest.useFakeTimers();
jest.setSystemTime(POST_CREATED_TIME);
});
const batchedActions = (postId: string, delay = 0) => [{
meta: {batch: true},
payload: [{
postId,
timestamp: POST_CREATED_TIME + delay,
type: ActionTypes.SELECT_POST,
}, {
postId,
type: ActionTypes.HIGHLIGHT_REPLY,
}],
type: 'BATCHING_REDUCER.BATCH',
}];
it('should select, and highlight a post, and after a delay clear the highlight', () => {
const store = mockStore(initialState);
store.dispatch(selectPostAndHighlight(post1));
expect(store.getActions()).toEqual([
...batchedActions('42'),
]);
expect(jest.getTimerCount()).toBe(1);
jest.advanceTimersByTime(Constants.PERMALINK_FADEOUT);
expect(jest.getTimerCount()).toBe(0);
expect(store.getActions()).toEqual([
...batchedActions('42'),
{type: ActionTypes.CLEAR_HIGHLIGHT_REPLY},
]);
});
it('should clear highlight only once after last call no matter how many times called', () => {
const store = mockStore(initialState);
store.dispatch(selectPostAndHighlight(post1));
jest.advanceTimersByTime(1000);
store.dispatch(selectPostAndHighlight(post2));
jest.advanceTimersByTime(1000);
store.dispatch(selectPostAndHighlight(post3));
expect(jest.getTimerCount()).toBe(1);
jest.advanceTimersByTime(Constants.PERMALINK_FADEOUT - 2000);
expect(jest.getTimerCount()).toBe(1);
jest.advanceTimersByTime(2000);
expect(jest.getTimerCount()).toBe(0);
expect(store.getActions()).toEqual([
...batchedActions('42'),
...batchedActions('43', 1000),
...batchedActions('44', 2000),
{type: ActionTypes.CLEAR_HIGHLIGHT_REPLY},
]);
});
});
describe('rhs suppress actions', () => {
it('should suppress rhs', () => {
const store = mockStore(initialState);
store.dispatch(suppressRHS);
expect(store.getActions()).toEqual([{
type: ActionTypes.SUPPRESS_RHS,
}]);
});
it('should unsuppresses rhs', () => {
store.dispatch(unsuppressRHS);
expect(store.getActions()).toEqual([{
type: ActionTypes.UNSUPPRESS_RHS,
}]);
});
});
describe('rhs go back', () => {
it('should be able to go back', () => {
const testState = {...initialState};
testState.views.rhs.previousRhsStates = [RHSStates.CHANNEL_FILES as RhsState];
const store = mockStore(testState);
store.dispatch(goBack());
expect(store.getActions()).toEqual([{
type: ActionTypes.RHS_GO_BACK,
state: RHSStates.CHANNEL_FILES as RhsState,
}]);
});
});
});