Mono repo -> Master (#22553)
Combines the following repositories into one: https://github.com/mattermost/mattermost-server https://github.com/mattermost/mattermost-webapp https://github.com/mattermost/focalboard https://github.com/mattermost/mattermost-plugin-playbooks
Этот коммит содержится в:
@@ -0,0 +1,49 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`components/PermalinkView should match snapshot 1`] = `
|
||||
<div
|
||||
className="app__content"
|
||||
id="app-content"
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`components/PermalinkView should match snapshot with archived channel 1`] = `
|
||||
<Memo(PermalinkView)
|
||||
actions={
|
||||
Object {
|
||||
"focusPost": [MockFunction] {
|
||||
"calls": Array [
|
||||
Array [
|
||||
"post_id",
|
||||
"return_to",
|
||||
"current_user",
|
||||
],
|
||||
],
|
||||
"results": Array [
|
||||
Object {
|
||||
"type": "return",
|
||||
"value": undefined,
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
}
|
||||
channelId="channel_id"
|
||||
channelIsArchived={true}
|
||||
currentUserId="current_user"
|
||||
match={
|
||||
Object {
|
||||
"params": Object {
|
||||
"postid": "post_id",
|
||||
},
|
||||
}
|
||||
}
|
||||
returnTo="return_to"
|
||||
teamName="team_name"
|
||||
>
|
||||
<div
|
||||
className="app__content"
|
||||
id="app-content"
|
||||
/>
|
||||
</Memo(PermalinkView)>
|
||||
`;
|
||||
177
webapp/channels/src/components/permalink_view/actions.ts
Обычный файл
177
webapp/channels/src/components/permalink_view/actions.ts
Обычный файл
@@ -0,0 +1,177 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {getChannel, getChannelMember, selectChannel, joinChannel, getChannelStats} from 'mattermost-redux/actions/channels';
|
||||
import {getPostThread} from 'mattermost-redux/actions/posts';
|
||||
import {getMissingProfilesByIds} from 'mattermost-redux/actions/users';
|
||||
import {getCurrentTeam, getTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUser} from 'mattermost-redux/selectors/entities/users';
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {isCollapsedThreadsEnabled} from 'mattermost-redux/selectors/entities/preferences';
|
||||
import {getUserIdFromChannelName} from 'mattermost-redux/utils/channel_utils';
|
||||
import {DispatchFunc, GetStateFunc} from 'mattermost-redux/types/actions';
|
||||
|
||||
import {Post} from '@mattermost/types/posts';
|
||||
import {Channel} from '@mattermost/types/channels';
|
||||
|
||||
import {loadChannelsForCurrentUser} from 'actions/channel_actions';
|
||||
import {loadNewDMIfNeeded, loadNewGMIfNeeded} from 'actions/user_actions';
|
||||
import {selectPostAndHighlight} from 'actions/views/rhs';
|
||||
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
import {joinPrivateChannelPrompt} from 'utils/channel_utils';
|
||||
import {ActionTypes, Constants, ErrorPageTypes} from 'utils/constants';
|
||||
import {isSystemAdmin} from 'mattermost-redux/utils/user_utils';
|
||||
import {isComment, getPostURL} from 'utils/post_utils';
|
||||
import {GlobalState} from 'types/store';
|
||||
|
||||
let privateChannelJoinPromptVisible = false;
|
||||
|
||||
type Option = {
|
||||
skipRedirectReplyPermalink: boolean;
|
||||
}
|
||||
|
||||
function focusRootPost(post: Post, channel: Channel) {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const postURL = getPostURL(getState() as GlobalState, post);
|
||||
|
||||
dispatch(selectChannel(channel.id));
|
||||
dispatch({
|
||||
type: ActionTypes.RECEIVED_FOCUSED_POST,
|
||||
data: post.id,
|
||||
channelId: channel.id,
|
||||
});
|
||||
|
||||
getHistory().replace(postURL);
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
function focusReplyPost(post: Post, channel: Channel, teamId: string, returnTo: string, option: Option) {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
const {data} = await dispatch(getPostThread(post.root_id));
|
||||
|
||||
if (data.first_inaccessible_post_time) {
|
||||
getHistory().replace(`/error?type=${ErrorPageTypes.CLOUD_ARCHIVED}&returnTo=${returnTo}`);
|
||||
return {data: false};
|
||||
}
|
||||
|
||||
const state = getState() as GlobalState;
|
||||
|
||||
const team = getTeam(state, channel.team_id || teamId);
|
||||
const currentChannel = getCurrentChannel(state);
|
||||
|
||||
const sameTeam = currentChannel && currentChannel.team_id === team.id;
|
||||
|
||||
const {skipRedirectReplyPermalink} = option;
|
||||
|
||||
if (!sameTeam) {
|
||||
dispatch(selectChannel(channel.id));
|
||||
}
|
||||
|
||||
if (sameTeam && returnTo && !skipRedirectReplyPermalink) {
|
||||
getHistory().replace(returnTo);
|
||||
} else if (!sameTeam || !skipRedirectReplyPermalink) {
|
||||
const postURL = getPostURL(state, post);
|
||||
getHistory().replace(postURL);
|
||||
}
|
||||
|
||||
dispatch(selectPostAndHighlight(post));
|
||||
return {data: true};
|
||||
};
|
||||
}
|
||||
|
||||
export function focusPost(postId: string, returnTo = '', currentUserId: string, option: Option = {skipRedirectReplyPermalink: false}) {
|
||||
return async (dispatch: DispatchFunc, getState: GetStateFunc) => {
|
||||
// Ignore if prompt is still visible
|
||||
if (privateChannelJoinPromptVisible) {
|
||||
return;
|
||||
}
|
||||
const {data} = await dispatch(getPostThread(postId));
|
||||
|
||||
if (!data) {
|
||||
getHistory().replace(`/error?type=${ErrorPageTypes.PERMALINK_NOT_FOUND}&returnTo=${returnTo}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.first_inaccessible_post_time) {
|
||||
getHistory().replace(`/error?type=${ErrorPageTypes.CLOUD_ARCHIVED}&returnTo=${returnTo}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const state = getState();
|
||||
const isCollapsed = isCollapsedThreadsEnabled(state);
|
||||
|
||||
const channelId = data.posts[data.order[0]].channel_id;
|
||||
let channel = state.entities.channels.channels[channelId];
|
||||
const currentTeam = getCurrentTeam(state);
|
||||
const teamId = currentTeam.id;
|
||||
|
||||
if (!channel) {
|
||||
const {data: channelData} = await dispatch(getChannel(channelId));
|
||||
|
||||
if (!channelData) {
|
||||
getHistory().replace(`/error?type=${ErrorPageTypes.PERMALINK_NOT_FOUND}&returnTo=${returnTo}`);
|
||||
return;
|
||||
}
|
||||
|
||||
channel = channelData;
|
||||
}
|
||||
|
||||
let myMember = state.entities.channels.myMembers[channelId];
|
||||
|
||||
if (!myMember) {
|
||||
// If it's a DM or GM channel and we don't have a channel member for it already, user is not a member
|
||||
if (channel.type === Constants.DM_CHANNEL || channel.type === Constants.GM_CHANNEL) {
|
||||
getHistory().replace(`/error?type=${ErrorPageTypes.PERMALINK_NOT_FOUND}&returnTo=${returnTo}`);
|
||||
return;
|
||||
}
|
||||
|
||||
const membership = await dispatch(getChannelMember(channel.id, currentUserId));
|
||||
if ('data' in membership) {
|
||||
myMember = membership.data;
|
||||
}
|
||||
|
||||
if (!myMember) {
|
||||
// Prompt system admin before joining the private channel
|
||||
const user = getCurrentUser(state);
|
||||
if (channel.type === Constants.PRIVATE_CHANNEL && isSystemAdmin(user.roles)) {
|
||||
privateChannelJoinPromptVisible = true;
|
||||
const joinPromptResult = await dispatch(joinPrivateChannelPrompt(currentTeam, channel));
|
||||
privateChannelJoinPromptVisible = false;
|
||||
if ('data' in joinPromptResult && !joinPromptResult.data.join) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
await dispatch(joinChannel(currentUserId, '', channelId, channel.name));
|
||||
}
|
||||
}
|
||||
|
||||
if (channel.team_id && channel.team_id !== teamId) {
|
||||
getHistory().replace(`/error?type=${ErrorPageTypes.PERMALINK_NOT_FOUND}&returnTo=${returnTo}`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (channel && channel.type === Constants.DM_CHANNEL) {
|
||||
const userId = getUserIdFromChannelName(currentUserId, channel.name);
|
||||
await dispatch(getMissingProfilesByIds([userId]));
|
||||
dispatch(loadNewDMIfNeeded(channel.id));
|
||||
} else if (channel && channel.type === Constants.GM_CHANNEL) {
|
||||
dispatch(loadNewGMIfNeeded(channel.id));
|
||||
}
|
||||
|
||||
const post = data.posts[postId];
|
||||
|
||||
if (isCollapsed && isComment(post)) {
|
||||
const {data} = await dispatch(focusReplyPost(post, channel, teamId, returnTo, option));
|
||||
if (!data) {
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
dispatch(focusRootPost(post, channel));
|
||||
}
|
||||
|
||||
dispatch(loadChannelsForCurrentUser());
|
||||
dispatch(getChannelStats(channelId));
|
||||
};
|
||||
}
|
||||
38
webapp/channels/src/components/permalink_view/index.ts
Обычный файл
38
webapp/channels/src/components/permalink_view/index.ts
Обычный файл
@@ -0,0 +1,38 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {connect} from 'react-redux';
|
||||
import {bindActionCreators, Dispatch} from 'redux';
|
||||
|
||||
import {getCurrentChannel} from 'mattermost-redux/selectors/entities/channels';
|
||||
import {getCurrentTeam} from 'mattermost-redux/selectors/entities/teams';
|
||||
import {getCurrentUserId} from 'mattermost-redux/selectors/entities/users';
|
||||
|
||||
import {GlobalState} from '@mattermost/types/store';
|
||||
|
||||
import {focusPost} from './actions';
|
||||
import PermalinkView from './permalink_view';
|
||||
|
||||
function mapStateToProps(state: GlobalState) {
|
||||
const team = getCurrentTeam(state);
|
||||
const channel = getCurrentChannel(state);
|
||||
const currentUserId = getCurrentUserId(state);
|
||||
const channelId = channel ? channel.id : '';
|
||||
const teamName = team ? team.name : '';
|
||||
|
||||
return {
|
||||
channelId,
|
||||
teamName,
|
||||
currentUserId,
|
||||
};
|
||||
}
|
||||
|
||||
function mapDispatchToProps(dispatch: Dispatch) {
|
||||
return {
|
||||
actions: bindActionCreators({
|
||||
focusPost,
|
||||
}, dispatch),
|
||||
};
|
||||
}
|
||||
|
||||
export default connect(mapStateToProps, mapDispatchToProps)(PermalinkView);
|
||||
@@ -0,0 +1,353 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import {ReactWrapper, shallow} from 'enzyme';
|
||||
import React, {ComponentProps} from 'react';
|
||||
import nock from 'nock';
|
||||
import {match} from 'react-router-dom';
|
||||
|
||||
import {act} from 'react-dom/test-utils';
|
||||
|
||||
import {Client4} from 'mattermost-redux/client';
|
||||
import {getPostThread} from 'mattermost-redux/actions/posts';
|
||||
|
||||
import {Preferences} from 'mattermost-redux/constants';
|
||||
import TestHelper from 'packages/mattermost-redux/test/test_helper';
|
||||
|
||||
import mockStore from 'tests/test_store';
|
||||
import {mountWithIntl} from 'tests/helpers/intl-test-helper';
|
||||
|
||||
import {ErrorPageTypes} from 'utils/constants';
|
||||
import {getHistory} from 'utils/browser_history';
|
||||
|
||||
import {focusPost} from 'components/permalink_view/actions';
|
||||
import PermalinkView from 'components/permalink_view/permalink_view';
|
||||
|
||||
import * as Channels from 'mattermost-redux/selectors/entities/channels';
|
||||
|
||||
jest.mock('actions/channel_actions', () => ({
|
||||
loadChannelsForCurrentUser: jest.fn(() => {
|
||||
return {type: 'MOCK_LOAD_CHANNELS_FOR_CURRENT_USER'};
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('actions/views/rhs.ts', () => ({
|
||||
selectPostAndHighlight: jest.fn((post) => {
|
||||
return {type: 'MOCK_SELECT_POST_AND_HIGHLIGHT', args: [post]};
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('mattermost-redux/actions/posts', () => ({
|
||||
getPostThread: jest.fn((postId) => {
|
||||
const post = {id: 'postid1', message: 'some message', channel_id: 'channelid1'};
|
||||
const post2 = {id: 'postid2', message: 'some message', channel_id: 'channelid2'};
|
||||
const replyPost1 = {id: 'replypostid1', message: 'some message', channel_id: 'channelid1', root_id: 'postid1'};
|
||||
const dmPost = {id: 'dmpostid1', message: 'some message', channel_id: 'dmchannelid'};
|
||||
const gmPost = {id: 'gmpostid1', message: 'some message', channel_id: 'gmchannelid'};
|
||||
|
||||
switch (postId) {
|
||||
case 'postid1':
|
||||
return {type: 'MOCK_GET_POST_THREAD', data: {posts: {replypostid1: replyPost1, postid1: post}, order: [post.id, replyPost1.id]}};
|
||||
case 'postid2':
|
||||
return {type: 'MOCK_GET_POST_THREAD', data: {posts: {postid2: post2}, order: [post2.id]}};
|
||||
case 'dmpostid1':
|
||||
return {type: 'MOCK_GET_POST_THREAD', data: {posts: {dmpostid1: dmPost}, order: [dmPost.id]}};
|
||||
case 'gmpostid1':
|
||||
return {type: 'MOCK_GET_POST_THREAD', data: {posts: {gmpostid1: gmPost}, order: [gmPost.id]}};
|
||||
case 'replypostid1':
|
||||
return {type: 'MOCK_GET_POST_THREAD', data: {posts: {replypostid1: replyPost1, postid1: post}, order: [post.id, replyPost1.id]}};
|
||||
default:
|
||||
return {type: 'MOCK_GET_POST_THREAD'};
|
||||
}
|
||||
}),
|
||||
}));
|
||||
|
||||
jest.mock('mattermost-redux/actions/users', () => ({
|
||||
getMissingProfilesByIds: (userIds: string[]) => ({type: 'MOCK_GET_MISSING_PROFILES', userIds}),
|
||||
}));
|
||||
|
||||
jest.mock('mattermost-redux/actions/channels', () => ({
|
||||
selectChannel: (...args: any) => ({type: 'MOCK_SELECT_CHANNEL', args}),
|
||||
joinChannel: (...args: any) => ({type: 'MOCK_JOIN_CHANNEL', args}),
|
||||
getChannelStats: (...args: any) => ({type: 'MOCK_GET_CHANNEL_STATS', args}),
|
||||
getChannel: jest.fn((channelId) => {
|
||||
switch (channelId) {
|
||||
case 'channelid2':
|
||||
return {type: 'MOCK_GET_CHANNEL', data: {id: 'channelid2', type: 'O', team_id: 'current_team_id'}};
|
||||
default:
|
||||
return {type: 'MOCK_GET_CHANNEL', args: [channelId]};
|
||||
}
|
||||
}),
|
||||
}));
|
||||
|
||||
describe('components/PermalinkView', () => {
|
||||
const baseProps: ComponentProps<typeof PermalinkView> = {
|
||||
channelId: 'channel_id',
|
||||
match: {params: {postid: 'post_id'}} as match<{ postid: string }>,
|
||||
returnTo: 'return_to',
|
||||
teamName: 'team_name',
|
||||
actions: {
|
||||
focusPost: jest.fn(),
|
||||
},
|
||||
currentUserId: 'current_user',
|
||||
};
|
||||
|
||||
test('should match snapshot', async () => {
|
||||
const wrapper = shallow(
|
||||
<PermalinkView {...baseProps}/>,
|
||||
);
|
||||
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
test('should call baseProps.actions.focusPost on doPermalinkEvent', async () => {
|
||||
await act(async () => {
|
||||
mountWithIntl(
|
||||
<PermalinkView {...baseProps}/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(baseProps.actions.focusPost).toHaveBeenCalledTimes(1);
|
||||
expect(baseProps.actions.focusPost).toBeCalledWith(baseProps.match.params.postid, baseProps.returnTo, baseProps.currentUserId);
|
||||
});
|
||||
|
||||
test('should call baseProps.actions.focusPost when postid changes', async () => {
|
||||
let wrapper: ReactWrapper<JSX.Element>;
|
||||
await act(async () => {
|
||||
wrapper = mountWithIntl(
|
||||
<PermalinkView {...baseProps}/>,
|
||||
);
|
||||
});
|
||||
const newPostid = `${baseProps.match.params.postid}_new`;
|
||||
await wrapper!.setProps({...baseProps, match: {params: {postid: newPostid}}} as any);
|
||||
|
||||
expect(baseProps.actions.focusPost).toHaveBeenCalledTimes(2);
|
||||
expect(baseProps.actions.focusPost).toBeCalledWith(newPostid, baseProps.returnTo, baseProps.currentUserId);
|
||||
});
|
||||
|
||||
test('should match snapshot with archived channel', async () => {
|
||||
const props = {...baseProps, channelIsArchived: true};
|
||||
|
||||
let wrapper: ReactWrapper<any>;
|
||||
await act(async () => {
|
||||
wrapper = mountWithIntl(
|
||||
<PermalinkView {...props}/>,
|
||||
);
|
||||
});
|
||||
|
||||
expect(wrapper!).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe('actions', () => {
|
||||
const initialState = {
|
||||
entities: {
|
||||
general: {},
|
||||
users: {
|
||||
currentUserId: 'current_user_id',
|
||||
profiles: {
|
||||
dmchannel: {
|
||||
id: 'dmchannel',
|
||||
username: 'otherUser',
|
||||
},
|
||||
},
|
||||
},
|
||||
channels: {
|
||||
channels: {
|
||||
channelid1: {id: 'channelid1', name: 'channel1', type: 'O', team_id: 'current_team_id'},
|
||||
dmchannelid: {id: 'dmchannelid', name: 'dmchannel__current_user_id', type: 'D', team_id: ''},
|
||||
gmchannelid: {id: 'gmchannelid', name: 'gmchannel', type: 'G', team_id: ''},
|
||||
},
|
||||
myMembers: {channelid1: {channel_id: 'channelid1', user_id: 'current_user_id'}},
|
||||
},
|
||||
preferences: {
|
||||
myPreferences: {},
|
||||
},
|
||||
teams: {
|
||||
currentTeamId: 'current_team_id',
|
||||
teams: {
|
||||
current_team_id: {
|
||||
id: 'current_team_id',
|
||||
display_name: 'currentteam',
|
||||
name: 'currentteam',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('focusPost', () => {
|
||||
test('should redirect to error page for DM channel not a member of', async () => {
|
||||
const testStore = await mockStore(initialState);
|
||||
await testStore.dispatch(focusPost('dmpostid1', undefined, baseProps.currentUserId));
|
||||
|
||||
expect(getPostThread).toHaveBeenCalledWith('dmpostid1');
|
||||
expect(testStore.getActions()).toEqual([
|
||||
{type: 'MOCK_GET_POST_THREAD', data: {posts: {dmpostid1: {id: 'dmpostid1', message: 'some message', channel_id: 'dmchannelid'}}, order: ['dmpostid1']}},
|
||||
]);
|
||||
expect(getHistory().replace).toHaveBeenCalledWith(`/error?type=${ErrorPageTypes.PERMALINK_NOT_FOUND}&returnTo=`);
|
||||
});
|
||||
|
||||
test('should redirect to error page for GM channel not a member of', async () => {
|
||||
const testStore = await mockStore(initialState);
|
||||
await testStore.dispatch(focusPost('gmpostid1', undefined, baseProps.currentUserId));
|
||||
|
||||
expect(getPostThread).toHaveBeenCalledWith('gmpostid1');
|
||||
expect(testStore.getActions()).toEqual([
|
||||
{type: 'MOCK_GET_POST_THREAD', data: {posts: {gmpostid1: {id: 'gmpostid1', message: 'some message', channel_id: 'gmchannelid'}}, order: ['gmpostid1']}},
|
||||
]);
|
||||
expect(getHistory().replace).toHaveBeenCalledWith(`/error?type=${ErrorPageTypes.PERMALINK_NOT_FOUND}&returnTo=`);
|
||||
});
|
||||
|
||||
test('should redirect to DM link with postId for permalink', async () => {
|
||||
const dateNowOrig = Date.now;
|
||||
Date.now = () => new Date(0).getMilliseconds();
|
||||
const nextTick = () => new Promise((res) => process.nextTick(res));
|
||||
|
||||
TestHelper.initBasic(Client4);
|
||||
nock(Client4.getUsersRoute()).
|
||||
put('/current_user_id/preferences').
|
||||
reply(200, {status: 'OK'});
|
||||
|
||||
const modifiedState = {
|
||||
entities: {
|
||||
...initialState.entities,
|
||||
channels: {
|
||||
...initialState.entities.channels,
|
||||
myMembers: {
|
||||
channelid1: {channel_id: 'channelid1', user_id: 'current_user_id'},
|
||||
dmchannelid: {channel_id: 'dmchannelid', name: 'dmchannel', type: 'D', user_id: 'current_user_id'},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const testStore = mockStore(modifiedState);
|
||||
testStore.dispatch(focusPost('dmpostid1', undefined, baseProps.currentUserId));
|
||||
|
||||
await nextTick();
|
||||
expect.assertions(3);
|
||||
expect(getPostThread).toHaveBeenCalledWith('dmpostid1');
|
||||
expect(testStore.getActions()).toEqual([
|
||||
{type: 'MOCK_GET_POST_THREAD', data: {posts: {dmpostid1: {id: 'dmpostid1', message: 'some message', channel_id: 'dmchannelid'}}, order: ['dmpostid1']}},
|
||||
{type: 'MOCK_GET_MISSING_PROFILES', userIds: ['dmchannel']},
|
||||
{
|
||||
type: 'RECEIVED_PREFERENCES',
|
||||
data: [
|
||||
{user_id: 'current_user_id', category: Preferences.CATEGORY_DIRECT_CHANNEL_SHOW, name: 'dmchannel', value: 'true'},
|
||||
{user_id: 'current_user_id', category: Preferences.CATEGORY_CHANNEL_OPEN_TIME, name: 'dmchannelid', value: '0'},
|
||||
],
|
||||
},
|
||||
{type: 'MOCK_SELECT_CHANNEL', args: ['dmchannelid']},
|
||||
{type: 'RECEIVED_FOCUSED_POST', channelId: 'dmchannelid', data: 'dmpostid1'},
|
||||
{type: 'MOCK_LOAD_CHANNELS_FOR_CURRENT_USER'},
|
||||
{type: 'MOCK_GET_CHANNEL_STATS', args: ['dmchannelid']},
|
||||
]);
|
||||
|
||||
expect(getHistory().replace).toHaveBeenCalledWith('/currentteam/messages/@otherUser/dmpostid1');
|
||||
Date.now = dateNowOrig;
|
||||
TestHelper.tearDown();
|
||||
});
|
||||
|
||||
test('should redirect to GM link with postId for permalink', async () => {
|
||||
const modifiedState = {
|
||||
entities: {
|
||||
...initialState.entities,
|
||||
channels: {
|
||||
...initialState.entities.channels,
|
||||
myMembers: {
|
||||
channelid1: {channel_id: 'channelid1', user_id: 'current_user_id'},
|
||||
gmchannelid: {channel_id: 'gmchannelid', name: 'gmchannel', type: 'G', user_id: 'current_user_id'},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const testStore = await mockStore(modifiedState);
|
||||
await testStore.dispatch(focusPost('gmpostid1', undefined, baseProps.currentUserId));
|
||||
|
||||
expect(getPostThread).toHaveBeenCalledWith('gmpostid1');
|
||||
expect(testStore.getActions()).toEqual([
|
||||
{type: 'MOCK_GET_POST_THREAD', data: {posts: {gmpostid1: {id: 'gmpostid1', message: 'some message', channel_id: 'gmchannelid'}}, order: ['gmpostid1']}},
|
||||
{type: 'MOCK_SELECT_CHANNEL', args: ['gmchannelid']},
|
||||
{type: 'RECEIVED_FOCUSED_POST', channelId: 'gmchannelid', data: 'gmpostid1'},
|
||||
{type: 'MOCK_LOAD_CHANNELS_FOR_CURRENT_USER'},
|
||||
{type: 'MOCK_GET_CHANNEL_STATS', args: ['gmchannelid']},
|
||||
]);
|
||||
expect(getHistory().replace).toHaveBeenCalledWith('/currentteam/messages/gmchannel/gmpostid1');
|
||||
});
|
||||
|
||||
test('should redirect to channel link with postId for permalink', async () => {
|
||||
const testStore = await mockStore(initialState);
|
||||
await testStore.dispatch(focusPost('postid1', undefined, baseProps.currentUserId));
|
||||
|
||||
expect(getPostThread).toHaveBeenCalledWith('postid1');
|
||||
expect(testStore.getActions()).toEqual([
|
||||
{
|
||||
type: 'MOCK_GET_POST_THREAD',
|
||||
data: {
|
||||
posts: {
|
||||
replypostid1: {id: 'replypostid1', message: 'some message', channel_id: 'channelid1', root_id: 'postid1'},
|
||||
postid1: {id: 'postid1', message: 'some message', channel_id: 'channelid1'},
|
||||
},
|
||||
order: ['postid1', 'replypostid1'],
|
||||
},
|
||||
},
|
||||
{type: 'MOCK_SELECT_CHANNEL', args: ['channelid1']},
|
||||
{type: 'RECEIVED_FOCUSED_POST', channelId: 'channelid1', data: 'postid1'},
|
||||
{type: 'MOCK_LOAD_CHANNELS_FOR_CURRENT_USER'},
|
||||
{type: 'MOCK_GET_CHANNEL_STATS', args: ['channelid1']},
|
||||
]);
|
||||
expect(getHistory().replace).toHaveBeenCalledWith('/currentteam/channels/channel1/postid1');
|
||||
});
|
||||
|
||||
test('should not redirect to channel link with postId for a reply permalink when collapsedThreads enabled and option is set true', async () => {
|
||||
const newState = {
|
||||
entities: {
|
||||
...initialState.entities,
|
||||
general: {
|
||||
config: {
|
||||
CollapsedThreads: 'default_on',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
jest.spyOn<typeof Channels, keyof typeof Channels>(Channels, 'getCurrentChannel').mockReturnValue({id: 'channelid1', name: 'channel1', type: 'O', team_id: 'current_team_id'});
|
||||
|
||||
const testStore = await mockStore(newState);
|
||||
await testStore.dispatch(focusPost('replypostid1', '#', initialState.entities.users.currentUserId, {skipRedirectReplyPermalink: true}));
|
||||
|
||||
expect(getPostThread).toHaveBeenCalledWith('replypostid1');
|
||||
|
||||
expect(testStore.getActions()).toEqual([
|
||||
{
|
||||
type: 'MOCK_GET_POST_THREAD',
|
||||
data: {
|
||||
posts: {
|
||||
replypostid1: {id: 'replypostid1', message: 'some message', channel_id: 'channelid1', root_id: 'postid1'},
|
||||
postid1: {id: 'postid1', message: 'some message', channel_id: 'channelid1'},
|
||||
|
||||
},
|
||||
order: ['postid1', 'replypostid1'],
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'MOCK_GET_POST_THREAD',
|
||||
data: {
|
||||
posts: {
|
||||
replypostid1: {id: 'replypostid1', message: 'some message', channel_id: 'channelid1', root_id: 'postid1'},
|
||||
postid1: {id: 'postid1', message: 'some message', channel_id: 'channelid1'},
|
||||
|
||||
},
|
||||
order: ['postid1', 'replypostid1'],
|
||||
},
|
||||
},
|
||||
{type: 'MOCK_SELECT_POST_AND_HIGHLIGHT', args: [{id: 'replypostid1', message: 'some message', channel_id: 'channelid1', root_id: 'postid1'}]},
|
||||
{type: 'MOCK_LOAD_CHANNELS_FOR_CURRENT_USER'},
|
||||
{type: 'MOCK_GET_CHANNEL_STATS', args: ['channelid1']},
|
||||
]);
|
||||
expect(getHistory().replace).not.toBeCalled();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
59
webapp/channels/src/components/permalink_view/permalink_view.tsx
Обычный файл
59
webapp/channels/src/components/permalink_view/permalink_view.tsx
Обычный файл
@@ -0,0 +1,59 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useState, useEffect, useCallback, memo, useRef} from 'react';
|
||||
import {match} from 'react-router-dom';
|
||||
|
||||
type Props = {
|
||||
channelId: string;
|
||||
|
||||
/*
|
||||
* Object from react-router
|
||||
*/
|
||||
match: match<{postid: string}>;
|
||||
returnTo: string;
|
||||
teamName?: string;
|
||||
actions: {
|
||||
focusPost: (postId: string, returnTo: string, currentUserId: string) => void;
|
||||
};
|
||||
currentUserId: string;
|
||||
}
|
||||
|
||||
const PermalinkView = (props: Props) => {
|
||||
const mounted = useRef(false);
|
||||
const [valid, setValid] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
mounted.current = true;
|
||||
return () => {
|
||||
mounted.current = false;
|
||||
};
|
||||
}, []);
|
||||
|
||||
const doPermalinkAction = useCallback(async () => {
|
||||
const postId = props.match.params.postid;
|
||||
await props.actions.focusPost(postId, props.returnTo, props.currentUserId);
|
||||
if (mounted.current) {
|
||||
setValid(true);
|
||||
}
|
||||
}, [props.match.params.postid, props.returnTo, props.currentUserId, props.actions]);
|
||||
|
||||
useEffect(() => {
|
||||
document.body.classList.add('app__body');
|
||||
doPermalinkAction();
|
||||
}, [doPermalinkAction]);
|
||||
|
||||
// it is returning null because main idea of this component is to fire focusPost redux action
|
||||
if (valid && props.channelId && props.teamName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
id='app-content'
|
||||
className='app__content'
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default memo(PermalinkView);
|
||||
Ссылка в новой задаче
Block a user