MM-52297 Fix reactions disappearing with search open and add testing utilities (#23510)
* MM-52297 Fix reactions disappearing when search is open * Add unit tests and extra utilities * Fix typing issue
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
00ba62b300
Коммит
789f84af90
@@ -59,12 +59,11 @@ describe('components/external_link', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const store: GlobalState = JSON.parse(JSON.stringify(state));
|
|
||||||
renderWithIntlAndStore(
|
renderWithIntlAndStore(
|
||||||
<ExternalLink href='https://mattermost.com'>
|
<ExternalLink href='https://mattermost.com'>
|
||||||
{'Click Me'}
|
{'Click Me'}
|
||||||
</ExternalLink>,
|
</ExternalLink>,
|
||||||
store,
|
state,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.queryByText('Click Me')).toHaveAttribute(
|
expect(screen.queryByText('Click Me')).toHaveAttribute(
|
||||||
@@ -86,12 +85,11 @@ describe('components/external_link', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const store: GlobalState = JSON.parse(JSON.stringify(state));
|
|
||||||
renderWithIntlAndStore(
|
renderWithIntlAndStore(
|
||||||
<ExternalLink href='https://mattermost.com?test=true'>
|
<ExternalLink href='https://mattermost.com?test=true'>
|
||||||
{'Click Me'}
|
{'Click Me'}
|
||||||
</ExternalLink>,
|
</ExternalLink>,
|
||||||
store,
|
state,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.queryByText('Click Me')).toHaveAttribute(
|
expect(screen.queryByText('Click Me')).toHaveAttribute(
|
||||||
@@ -113,12 +111,11 @@ describe('components/external_link', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const store: GlobalState = JSON.parse(JSON.stringify(state));
|
|
||||||
renderWithIntlAndStore(
|
renderWithIntlAndStore(
|
||||||
<ExternalLink href='https://google.com'>
|
<ExternalLink href='https://google.com'>
|
||||||
{'Click Me'}
|
{'Click Me'}
|
||||||
</ExternalLink>,
|
</ExternalLink>,
|
||||||
store,
|
state,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.queryByText('Click Me')).not.toHaveAttribute(
|
expect(screen.queryByText('Click Me')).not.toHaveAttribute(
|
||||||
@@ -140,14 +137,13 @@ describe('components/external_link', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const store: GlobalState = JSON.parse(JSON.stringify(state));
|
|
||||||
renderWithIntlAndStore(
|
renderWithIntlAndStore(
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
target='test'
|
target='test'
|
||||||
rel='test'
|
rel='test'
|
||||||
href='https://google.com'
|
href='https://google.com'
|
||||||
>{'Click Me'}</ExternalLink>,
|
>{'Click Me'}</ExternalLink>,
|
||||||
store,
|
state,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.queryByText('Click Me')).toHaveAttribute(
|
expect(screen.queryByText('Click Me')).toHaveAttribute(
|
||||||
@@ -175,14 +171,13 @@ describe('components/external_link', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const store: GlobalState = JSON.parse(JSON.stringify(state));
|
|
||||||
renderWithIntlAndStore(
|
renderWithIntlAndStore(
|
||||||
<ExternalLink
|
<ExternalLink
|
||||||
href='https://mattermost.com#desktop'
|
href='https://mattermost.com#desktop'
|
||||||
>
|
>
|
||||||
{'Click Me'}
|
{'Click Me'}
|
||||||
</ExternalLink>,
|
</ExternalLink>,
|
||||||
store,
|
state,
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(screen.queryByText('Click Me')).toHaveAttribute(
|
expect(screen.queryByText('Click Me')).toHaveAttribute(
|
||||||
|
|||||||
131
webapp/channels/src/components/post/post_component.test.tsx
Обычный файл
131
webapp/channels/src/components/post/post_component.test.tsx
Обычный файл
@@ -0,0 +1,131 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {screen} from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
import {DeepPartial} from '@mattermost/types/utilities';
|
||||||
|
|
||||||
|
import {renderWithFullContext} from 'tests/react_testing_utils';
|
||||||
|
|
||||||
|
import {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
import {Locations} from 'utils/constants';
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import PostComponent from './post_component';
|
||||||
|
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
|
||||||
|
|
||||||
|
describe('PostComponent', () => {
|
||||||
|
const baseProps = {
|
||||||
|
center: false,
|
||||||
|
currentTeam: TestHelper.getTeamMock(),
|
||||||
|
currentUserId: 'currentUserId',
|
||||||
|
displayName: '',
|
||||||
|
isBot: false,
|
||||||
|
isFlagged: false,
|
||||||
|
isMobileView: false,
|
||||||
|
isPostAcknowledgementsEnabled: false,
|
||||||
|
isPostPriorityEnabled: false,
|
||||||
|
location: Locations.CENTER,
|
||||||
|
post: TestHelper.getPostMock(),
|
||||||
|
recentEmojis: [],
|
||||||
|
actions: {
|
||||||
|
markPostAsUnread: jest.fn(),
|
||||||
|
emitShortcutReactToLastPostFrom: jest.fn(),
|
||||||
|
setActionsMenuInitialisationState: jest.fn(),
|
||||||
|
selectPost: jest.fn(),
|
||||||
|
selectPostFromRightHandSideSearch: jest.fn(),
|
||||||
|
removePost: jest.fn(),
|
||||||
|
closeRightHandSide: jest.fn(),
|
||||||
|
selectPostCard: jest.fn(),
|
||||||
|
setRhsExpanded: jest.fn(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('reactions', () => {
|
||||||
|
const baseState: DeepPartial<GlobalState> = {
|
||||||
|
entities: {
|
||||||
|
posts: {
|
||||||
|
reactions: {
|
||||||
|
[baseProps.post.id]: {
|
||||||
|
[`${baseProps.currentUserId}-taco`]: TestHelper.getReactionMock({emoji_name: 'taco'}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
test('should show reactions in the center channel', () => {
|
||||||
|
renderWithFullContext(
|
||||||
|
<PostComponent
|
||||||
|
{...baseProps}
|
||||||
|
/>,
|
||||||
|
baseState,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('reactions')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show reactions in thread view', () => {
|
||||||
|
const state = mergeObjects(baseState, {
|
||||||
|
views: {
|
||||||
|
rhs: {
|
||||||
|
selectedPostId: baseProps.post.id,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const {rerender} = renderWithFullContext(
|
||||||
|
<PostComponent
|
||||||
|
{...baseProps}
|
||||||
|
location={Locations.RHS_ROOT}
|
||||||
|
/>,
|
||||||
|
state,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('reactions')).toBeInTheDocument();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<PostComponent
|
||||||
|
{...baseProps}
|
||||||
|
location={Locations.RHS_COMMENT}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('reactions')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should show only show reactions in search results with pinned/saved posts visible', () => {
|
||||||
|
const {rerender} = renderWithFullContext(
|
||||||
|
<PostComponent
|
||||||
|
{...baseProps}
|
||||||
|
location={Locations.SEARCH}
|
||||||
|
/>,
|
||||||
|
baseState,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.queryByLabelText('reactions')).not.toBeInTheDocument();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<PostComponent
|
||||||
|
{...baseProps}
|
||||||
|
location={Locations.SEARCH}
|
||||||
|
isPinnedPosts={true}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('reactions')).toBeInTheDocument();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<PostComponent
|
||||||
|
{...baseProps}
|
||||||
|
location={Locations.SEARCH}
|
||||||
|
isFlaggedPosts={true}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByLabelText('reactions')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -480,7 +480,7 @@ const PostComponent = (props: Props): JSX.Element => {
|
|||||||
) : null;
|
) : null;
|
||||||
const currentPostDay = getDateForUnixTicks(post.create_at);
|
const currentPostDay = getDateForUnixTicks(post.create_at);
|
||||||
const channelDisplayName = getChannelName();
|
const channelDisplayName = getChannelName();
|
||||||
const showReactions = props.location !== Locations.SEARCH && !props.isPinnedPosts && !props.isFlaggedPosts;
|
const showReactions = props.location !== Locations.SEARCH || props.isPinnedPosts || props.isFlaggedPosts;
|
||||||
|
|
||||||
const getTestId = () => {
|
const getTestId = () => {
|
||||||
let idPrefix: string;
|
let idPrefix: string;
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import React, {ReactNode} from 'react';
|
import React, {ReactNode} from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import Constants, {Locations} from 'utils/constants';
|
import {Locations} from 'utils/constants';
|
||||||
import {fromAutoResponder, isFromWebhook} from 'utils/post_utils';
|
import {fromAutoResponder, isFromWebhook} from 'utils/post_utils';
|
||||||
|
|
||||||
import Tag from 'components/widgets/tag/tag';
|
import Tag from 'components/widgets/tag/tag';
|
||||||
@@ -17,7 +17,6 @@ import {Post} from '@mattermost/types/posts';
|
|||||||
type Props = {
|
type Props = {
|
||||||
post: Post;
|
post: Post;
|
||||||
compactDisplay?: boolean;
|
compactDisplay?: boolean;
|
||||||
currentUserId: string;
|
|
||||||
colorizeUsernames?: boolean;
|
colorizeUsernames?: boolean;
|
||||||
enablePostUsernameOverride?: boolean;
|
enablePostUsernameOverride?: boolean;
|
||||||
isConsecutivePost?: boolean;
|
isConsecutivePost?: boolean;
|
||||||
@@ -142,7 +141,6 @@ const PostUserProfile = (props: Props): JSX.Element | null => {
|
|||||||
/>
|
/>
|
||||||
}
|
}
|
||||||
userId={post.user_id}
|
userId={post.user_id}
|
||||||
overwriteImage={Constants.SYSTEM_MESSAGE_PROFILE_IMAGE}
|
|
||||||
disablePopover={true}
|
disablePopover={true}
|
||||||
channelId={post.channel_id}
|
channelId={post.channel_id}
|
||||||
colorize={colorize}
|
colorize={colorize}
|
||||||
|
|||||||
@@ -21,7 +21,6 @@ exports[`components/post_view/CommentedOn should match snapshot 1`] = `
|
|||||||
hasMention={true}
|
hasMention={true}
|
||||||
hideStatus={false}
|
hideStatus={false}
|
||||||
isRHS={false}
|
isRHS={false}
|
||||||
overwriteImage=""
|
|
||||||
overwriteName=""
|
overwriteName=""
|
||||||
userId=""
|
userId=""
|
||||||
/>
|
/>
|
||||||
@@ -60,7 +59,6 @@ exports[`components/post_view/CommentedOn should match snapshot 2`] = `
|
|||||||
hasMention={true}
|
hasMention={true}
|
||||||
hideStatus={false}
|
hideStatus={false}
|
||||||
isRHS={false}
|
isRHS={false}
|
||||||
overwriteImage=""
|
|
||||||
overwriteName=""
|
overwriteName=""
|
||||||
userId=""
|
userId=""
|
||||||
/>
|
/>
|
||||||
@@ -99,7 +97,6 @@ exports[`components/post_view/CommentedOn should match snapshot 3`] = `
|
|||||||
hasMention={true}
|
hasMention={true}
|
||||||
hideStatus={false}
|
hideStatus={false}
|
||||||
isRHS={false}
|
isRHS={false}
|
||||||
overwriteImage=""
|
|
||||||
overwriteName=""
|
overwriteName=""
|
||||||
userId=""
|
userId=""
|
||||||
/>
|
/>
|
||||||
@@ -140,7 +137,6 @@ exports[`components/post_view/CommentedOn should match snapshots for post with p
|
|||||||
hasMention={true}
|
hasMention={true}
|
||||||
hideStatus={false}
|
hideStatus={false}
|
||||||
isRHS={false}
|
isRHS={false}
|
||||||
overwriteImage=""
|
|
||||||
overwriteName=""
|
overwriteName=""
|
||||||
userId=""
|
userId=""
|
||||||
/>
|
/>
|
||||||
@@ -179,7 +175,6 @@ exports[`components/post_view/CommentedOn should match snapshots for post with p
|
|||||||
hasMention={true}
|
hasMention={true}
|
||||||
hideStatus={false}
|
hideStatus={false}
|
||||||
isRHS={false}
|
isRHS={false}
|
||||||
overwriteImage=""
|
|
||||||
overwriteName=""
|
overwriteName=""
|
||||||
userId=""
|
userId=""
|
||||||
/>
|
/>
|
||||||
@@ -218,7 +213,6 @@ exports[`components/post_view/CommentedOn should match snapshots for post with p
|
|||||||
hasMention={true}
|
hasMention={true}
|
||||||
hideStatus={false}
|
hideStatus={false}
|
||||||
isRHS={false}
|
isRHS={false}
|
||||||
overwriteImage=""
|
|
||||||
overwriteName=""
|
overwriteName=""
|
||||||
userId=""
|
userId=""
|
||||||
/>
|
/>
|
||||||
@@ -257,7 +251,6 @@ exports[`components/post_view/CommentedOn should match snapshots for post with p
|
|||||||
hasMention={true}
|
hasMention={true}
|
||||||
hideStatus={false}
|
hideStatus={false}
|
||||||
isRHS={false}
|
isRHS={false}
|
||||||
overwriteImage=""
|
|
||||||
overwriteName=""
|
overwriteName=""
|
||||||
userId=""
|
userId=""
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ import {DeepPartial} from '@mattermost/types/utilities';
|
|||||||
|
|
||||||
import SelfHostedExpansionModal, {makeInitialState, canSubmit, FormState} from './';
|
import SelfHostedExpansionModal, {makeInitialState, canSubmit, FormState} from './';
|
||||||
import moment from 'moment-timezone';
|
import moment from 'moment-timezone';
|
||||||
|
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
|
||||||
|
|
||||||
interface MockCardInputProps {
|
interface MockCardInputProps {
|
||||||
onCardInputChange: (event: {complete: boolean}) => void;
|
onCardInputChange: (event: {complete: boolean}) => void;
|
||||||
@@ -345,13 +346,19 @@ describe('SelfHostedExpansionModal RHS Card', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('New seats input cannot be less than 1', () => {
|
it('New seats input cannot be less than 1', () => {
|
||||||
if (initialState.entities?.users?.filteredStats?.total_users_count) {
|
const state = mergeObjects(initialState, {
|
||||||
initialState.entities.users.filteredStats.total_users_count = 50;
|
entities: {
|
||||||
}
|
users: {
|
||||||
|
filteredStats: {
|
||||||
|
total_users_count: 50,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const expectedAddNewSeats = '1';
|
const expectedAddNewSeats = '1';
|
||||||
|
|
||||||
renderWithIntlAndStore(<div id='root-portal'><SelfHostedExpansionModal/></div>, initialState);
|
renderWithIntlAndStore(<div id='root-portal'><SelfHostedExpansionModal/></div>, state);
|
||||||
fillForm(defaultSuccessForm);
|
fillForm(defaultSuccessForm);
|
||||||
|
|
||||||
// Try to set a negative value.
|
// Try to set a negative value.
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ export type UserProfileProps = {
|
|||||||
hasMention?: boolean;
|
hasMention?: boolean;
|
||||||
hideStatus?: boolean;
|
hideStatus?: boolean;
|
||||||
isRHS?: boolean;
|
isRHS?: boolean;
|
||||||
overwriteImage?: React.ReactNode;
|
|
||||||
channelId?: string;
|
channelId?: string;
|
||||||
theme?: Theme;
|
theme?: Theme;
|
||||||
}
|
}
|
||||||
@@ -49,7 +48,6 @@ export default class UserProfile extends PureComponent<UserProfileProps> {
|
|||||||
hasMention: false,
|
hasMention: false,
|
||||||
hideStatus: false,
|
hideStatus: false,
|
||||||
isRHS: false,
|
isRHS: false,
|
||||||
overwriteImage: '',
|
|
||||||
overwriteName: '',
|
overwriteName: '',
|
||||||
colorize: false,
|
colorize: false,
|
||||||
};
|
};
|
||||||
|
|||||||
Двоичные данные
webapp/channels/src/images/logo_compact.png
Двоичные данные
webapp/channels/src/images/logo_compact.png
Двоичный файл не отображается.
|
До Ширина: | Высота: | Размер: 2.4 KiB |
244
webapp/channels/src/tests/react_testing_utils.test.tsx
Обычный файл
244
webapp/channels/src/tests/react_testing_utils.test.tsx
Обычный файл
@@ -0,0 +1,244 @@
|
|||||||
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
|
import {screen} from '@testing-library/react';
|
||||||
|
import React from 'react';
|
||||||
|
import {FormattedMessage, useIntl} from 'react-intl';
|
||||||
|
import {connect, useSelector} from 'react-redux';
|
||||||
|
import {Link, Route} from 'react-router-dom';
|
||||||
|
|
||||||
|
import {getUser} from 'mattermost-redux/selectors/entities/users';
|
||||||
|
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
|
||||||
|
|
||||||
|
import {GlobalState} from 'types/store';
|
||||||
|
|
||||||
|
import {TestHelper} from 'utils/test_helper';
|
||||||
|
|
||||||
|
import {renderWithFullContext} from './react_testing_utils';
|
||||||
|
|
||||||
|
describe('renderWithFullContext', () => {
|
||||||
|
test('should be able to render anything', () => {
|
||||||
|
const TestComponent = () => {
|
||||||
|
return <div>{'Anything'}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithFullContext(
|
||||||
|
<TestComponent/>,
|
||||||
|
{},
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Anything')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to render react-intl components', () => {
|
||||||
|
const TestComponent = () => {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='about.buildnumber'
|
||||||
|
defaultMessage='Build Number:'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithFullContext(
|
||||||
|
<TestComponent/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Build Number:')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to render components using react-intl hooks', () => {
|
||||||
|
const TestComponent = () => {
|
||||||
|
const intl = useIntl();
|
||||||
|
|
||||||
|
return <div>{intl.formatMessage({id: 'about.hash', defaultMessage: 'Build Hash:'})}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithFullContext(
|
||||||
|
<TestComponent/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('Build Hash:')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to render react-router components', () => {
|
||||||
|
const RouteComponent = () => {
|
||||||
|
return <div>{'this is the route component'}</div>;
|
||||||
|
};
|
||||||
|
const TestComponent = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<Route
|
||||||
|
path={''}
|
||||||
|
component={RouteComponent}
|
||||||
|
/>
|
||||||
|
<Link to={'/another_page'}>{'Test Link'}</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithFullContext(
|
||||||
|
<TestComponent/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('this is the route component')).toBeInTheDocument();
|
||||||
|
expect(screen.getByRole('link', {name: 'Test Link'})).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to render components that use connect to access the Redux store', () => {
|
||||||
|
const UnconnectedTestComponent = (props: {numProfiles: number}) => {
|
||||||
|
return <div>{`There are ${props.numProfiles} users loaded`}</div>;
|
||||||
|
};
|
||||||
|
const TestComponent = connect((state: GlobalState) => ({
|
||||||
|
numProfiles: Object.keys(state.entities.users.profiles).length,
|
||||||
|
}))(UnconnectedTestComponent);
|
||||||
|
|
||||||
|
renderWithFullContext(
|
||||||
|
<TestComponent/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('There are 0 users loaded')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to render components that use hooks to access the Redux store', () => {
|
||||||
|
const TestComponent = () => {
|
||||||
|
const numProfiles = useSelector((state: GlobalState) => Object.keys(state.entities.users.profiles).length);
|
||||||
|
return <div>{`There are ${numProfiles} users loaded`}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
renderWithFullContext(
|
||||||
|
<TestComponent/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('There are 0 users loaded')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to rerender components without losing context', () => {
|
||||||
|
const TestComponent = (props: {appTitle: string}) => {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='about.title'
|
||||||
|
defaultMessage='About {appTitle}'
|
||||||
|
values={{
|
||||||
|
appTitle: props.appTitle,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const {rerender} = renderWithFullContext(
|
||||||
|
<TestComponent appTitle='Mattermost'/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('About Mattermost')).toBeInTheDocument();
|
||||||
|
|
||||||
|
rerender(
|
||||||
|
<TestComponent appTitle='Mattermots'/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('About Mattermots')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to inject store state and replace it later', () => {
|
||||||
|
const initialState = {
|
||||||
|
entities: {
|
||||||
|
users: {
|
||||||
|
profiles: {
|
||||||
|
user1: TestHelper.getUserMock({id: 'user1', username: 'Alpha'}),
|
||||||
|
user2: TestHelper.getUserMock({id: 'user2', username: 'Bravo'}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestComponent = () => {
|
||||||
|
const user1 = useSelector((state: GlobalState) => getUser(state, 'user1'));
|
||||||
|
const user2 = useSelector((state: GlobalState) => getUser(state, 'user2'));
|
||||||
|
|
||||||
|
return <div>{`User1 is ${user1.username} and User2 is ${user2.username}!`}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const {replaceStoreState} = renderWithFullContext(
|
||||||
|
<TestComponent/>,
|
||||||
|
initialState,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('User1 is Alpha and User2 is Bravo!')).toBeInTheDocument();
|
||||||
|
|
||||||
|
replaceStoreState(mergeObjects(initialState, {
|
||||||
|
entities: {
|
||||||
|
users: {
|
||||||
|
profiles: {
|
||||||
|
user1: {username: 'Charlie'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
expect(screen.getByText('User1 is Charlie and User2 is Bravo!')).toBeInTheDocument();
|
||||||
|
|
||||||
|
replaceStoreState(mergeObjects(initialState, {
|
||||||
|
entities: {
|
||||||
|
users: {
|
||||||
|
profiles: {
|
||||||
|
user2: {username: 'Delta'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Since this replaces the state, user1's username goes back to the initial value
|
||||||
|
expect(screen.getByText('User1 is Alpha and User2 is Delta!')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('should be able to update store state', () => {
|
||||||
|
const initialState = {
|
||||||
|
entities: {
|
||||||
|
users: {
|
||||||
|
profiles: {
|
||||||
|
user1: TestHelper.getUserMock({id: 'user1', username: 'Echo'}),
|
||||||
|
user2: TestHelper.getUserMock({id: 'user2', username: 'Foxtrot'}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const TestComponent = () => {
|
||||||
|
const user1 = useSelector((state: GlobalState) => getUser(state, 'user1'));
|
||||||
|
const user2 = useSelector((state: GlobalState) => getUser(state, 'user2'));
|
||||||
|
|
||||||
|
return <div>{`User1 is ${user1.username} and User2 is ${user2.username}!`}</div>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const {updateStoreState} = renderWithFullContext(
|
||||||
|
<TestComponent/>,
|
||||||
|
initialState,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(screen.getByText('User1 is Echo and User2 is Foxtrot!')).toBeInTheDocument();
|
||||||
|
|
||||||
|
updateStoreState({
|
||||||
|
entities: {
|
||||||
|
users: {
|
||||||
|
profiles: {
|
||||||
|
user1: {username: 'Golf'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('User1 is Golf and User2 is Foxtrot!')).toBeInTheDocument();
|
||||||
|
|
||||||
|
updateStoreState({
|
||||||
|
entities: {
|
||||||
|
users: {
|
||||||
|
profiles: {
|
||||||
|
user2: {username: 'Hotel'},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(screen.getByText('User1 is Golf and User2 is Hotel!')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,22 +1,32 @@
|
|||||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||||
// See LICENSE.txt for license information.
|
// See LICENSE.txt for license information.
|
||||||
|
|
||||||
import React from 'react';
|
|
||||||
import {render} from '@testing-library/react';
|
import {render} from '@testing-library/react';
|
||||||
import {Provider} from 'react-redux';
|
import {createBrowserHistory} from 'history';
|
||||||
|
import React from 'react';
|
||||||
import {IntlProvider} from 'react-intl';
|
import {IntlProvider} from 'react-intl';
|
||||||
|
import {Provider} from 'react-redux';
|
||||||
|
import {Router} from 'react-router-dom';
|
||||||
|
|
||||||
|
import {DeepPartial} from '@mattermost/types/utilities';
|
||||||
|
|
||||||
|
import configureStore from 'store';
|
||||||
|
|
||||||
import mockStore from 'tests/test_store';
|
import mockStore from 'tests/test_store';
|
||||||
import {GlobalState} from '@mattermost/types/store';
|
|
||||||
import {DeepPartial} from '@mattermost/types/utilities';
|
import {GlobalState} from 'types/store';
|
||||||
|
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
|
||||||
|
|
||||||
export const renderWithIntl = (component: React.ReactNode | React.ReactNodeArray, locale = 'en') => {
|
export const renderWithIntl = (component: React.ReactNode | React.ReactNodeArray, locale = 'en') => {
|
||||||
return render(<IntlProvider locale={locale}>{component}</IntlProvider>);
|
return render(<IntlProvider locale={locale}>{component}</IntlProvider>);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const renderWithIntlAndStore = (component: React.ReactNode | React.ReactNodeArray, initialState: DeepPartial<GlobalState>, locale = 'en') => {
|
export const renderWithIntlAndStore = (component: React.ReactNode | React.ReactNodeArray, initialState: DeepPartial<GlobalState>, locale = 'en') => {
|
||||||
const store = mockStore(initialState);
|
// We use a redux-mock-store store for testing, but we set up a real store to ensure the initial state is complete
|
||||||
|
const realStore = configureStore(initialState);
|
||||||
|
|
||||||
|
const store = mockStore(realStore.getState());
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
<IntlProvider locale={locale}>
|
<IntlProvider locale={locale}>
|
||||||
<Provider store={store}>
|
<Provider store={store}>
|
||||||
@@ -25,3 +35,66 @@ export const renderWithIntlAndStore = (component: React.ReactNode | React.ReactN
|
|||||||
</IntlProvider>,
|
</IntlProvider>,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const renderWithFullContext = (component: React.ReactNode | React.ReactNodeArray, initialState: DeepPartial<GlobalState> = {}, locale = 'en') => {
|
||||||
|
// We use a redux-mock-store store for testing, but we set up a real store to ensure the initial state is complete
|
||||||
|
const testState = configureStore(initialState).getState();
|
||||||
|
|
||||||
|
// Store these in an object so that they can be maintained through rerenders
|
||||||
|
const renderState = {
|
||||||
|
component,
|
||||||
|
history: createBrowserHistory(),
|
||||||
|
locale,
|
||||||
|
state: testState,
|
||||||
|
store: mockStore(testState),
|
||||||
|
};
|
||||||
|
|
||||||
|
// This should wrap the component in roughly the same providers used in App and RootProvider
|
||||||
|
function wrapComponent() {
|
||||||
|
// Every time this is called, these values should be updated from `renderState`
|
||||||
|
return (
|
||||||
|
<Provider store={renderState.store}>
|
||||||
|
<Router history={renderState.history}>
|
||||||
|
<IntlProvider
|
||||||
|
locale={renderState.locale}
|
||||||
|
>
|
||||||
|
{renderState.component}
|
||||||
|
</IntlProvider>
|
||||||
|
</Router>
|
||||||
|
</Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = render(wrapComponent());
|
||||||
|
|
||||||
|
return {
|
||||||
|
...results,
|
||||||
|
rerender: (newComponent: React.ReactNode | React.ReactNodeArray) => {
|
||||||
|
renderState.component = newComponent;
|
||||||
|
|
||||||
|
results.rerender(wrapComponent());
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rerenders the component after replacing the entire store state with the provided one.
|
||||||
|
*/
|
||||||
|
replaceStoreState: (newInitialState: DeepPartial<GlobalState>) => {
|
||||||
|
const newTestState = configureStore(newInitialState).getState();
|
||||||
|
renderState.state = newTestState;
|
||||||
|
renderState.store = mockStore(newTestState);
|
||||||
|
|
||||||
|
results.rerender(wrapComponent());
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rerenders the component after merging the current store state with the provided one.
|
||||||
|
*/
|
||||||
|
updateStoreState: (stateDiff: DeepPartial<GlobalState>) => {
|
||||||
|
const newTestState = mergeObjects(renderState.state, stateDiff);
|
||||||
|
renderState.state = newTestState;
|
||||||
|
renderState.store = mockStore(newTestState);
|
||||||
|
|
||||||
|
results.rerender(wrapComponent());
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ import pdfIcon from 'images/icons/pdf.svg';
|
|||||||
import pptIcon from 'images/icons/ppt.svg';
|
import pptIcon from 'images/icons/ppt.svg';
|
||||||
import videoIcon from 'images/icons/video.svg';
|
import videoIcon from 'images/icons/video.svg';
|
||||||
import wordIcon from 'images/icons/word.svg';
|
import wordIcon from 'images/icons/word.svg';
|
||||||
import logoImage from 'images/logo_compact.png';
|
|
||||||
import githubIcon from 'images/themes/code_themes/github.png';
|
import githubIcon from 'images/themes/code_themes/github.png';
|
||||||
import monokaiIcon from 'images/themes/code_themes/monokai.png';
|
import monokaiIcon from 'images/themes/code_themes/monokai.png';
|
||||||
import solarizedDarkIcon from 'images/themes/code_themes/solarized-dark.png';
|
import solarizedDarkIcon from 'images/themes/code_themes/solarized-dark.png';
|
||||||
@@ -1508,7 +1507,6 @@ export const Constants = {
|
|||||||
MENTION_NAME_PADDING_LEFT: 2.4,
|
MENTION_NAME_PADDING_LEFT: 2.4,
|
||||||
AVATAR_WIDTH: 24,
|
AVATAR_WIDTH: 24,
|
||||||
AUTO_RESPONDER: 'system_auto_responder',
|
AUTO_RESPONDER: 'system_auto_responder',
|
||||||
SYSTEM_MESSAGE_PROFILE_IMAGE: logoImage,
|
|
||||||
RESERVED_TEAM_NAMES: [
|
RESERVED_TEAM_NAMES: [
|
||||||
'signup',
|
'signup',
|
||||||
'login',
|
'login',
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user