(test): bulk m7-12 enzyme to rtl migration

This commit is contained in:
Saturnino Abril 2026-02-03 12:26:52 +08:00
parent 36479bd721
commit 77340c7b56
54 changed files with 6708 additions and 6492 deletions

View file

@ -1,113 +1,64 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/CommercialSupportModal should match snapshot 1`] = `
<Modal
animation={true}
autoFocus={true}
backdrop={true}
bsClass="modal"
dialogClassName="a11y__modal more-modal more-direct-channels"
dialogComponentClass={[Function]}
enforceFocus={true}
id="commercialSupportModal"
keyboard={true}
manager={
ModalManager {
"add": [Function],
"containers": Array [],
"data": Array [],
"handleContainerOverflow": true,
"hideSiblingNodes": true,
"isTopModal": [Function],
"modals": Array [],
"remove": [Function],
}
}
onExited={[MockFunction]}
onHide={[Function]}
renderBackdrop={[Function]}
restoreFocus={true}
show={true}
>
<ModalHeader
bsClass="modal-header"
closeButton={true}
closeLabel="Close"
>
<ModalTitle
bsClass="modal-title"
componentClass="h4"
>
<MemoizedFormattedMessage
defaultMessage="Commercial Support"
id="commercial_support.title"
/>
</ModalTitle>
</ModalHeader>
<ModalBody
bsClass="modal-body"
componentClass="div"
>
<div
className="CommercialSupportModal"
>
<MemoizedFormattedMessage
defaultMessage="If you're experiencing issues, <supportLink>submit a support ticket</supportLink>. To help with troubleshooting, it's recommended to download the Support Packet below that includes more details about your Mattermost environment."
id="commercial_support_modal.description"
values={
Object {
"supportLink": [Function],
}
}
/>
<div
className="CommercialSupportModal__packet_contents_download"
>
<strong>
<MemoizedFormattedMessage
defaultMessage="Select your Support Packet contents to download"
id="commercial_support_modal.download_contents"
/>
</strong>
</div>
<div
className="CommercialSupportModal__option"
key="basic.server.logs"
>
<input
checked={true}
className="CommercialSupportModal__options__checkbox"
disabled={true}
id="basic.server.logs"
name="basic.server.logs"
onChange={[Function]}
type="checkbox"
/>
<MemoizedFormattedMessage
defaultMessage="Server Logs"
id="mettormost.plugin.metrics.support.packet"
>
<Component />
</MemoizedFormattedMessage>
</div>
<div
className="CommercialSupportModal__download"
>
<a
className="btn btn-primary DownloadSupportPacket"
onClick={[Function]}
rel="noopener noreferrer"
>
<i
className="icon icon-download-outline"
/>
<MemoizedFormattedMessage
defaultMessage="Download Support Packet"
id="commercial_support.download_support_packet"
/>
</a>
<div>
<div>
<div>
<div>
Commercial Support
</div>
</div>
</ModalBody>
</Modal>
<div>
<div
class="CommercialSupportModal"
>
If you're experiencing issues,
<a
href="https://support.mattermost.com/hc/en-us/requests/new"
>
submit a support ticket
</a>
. To help with troubleshooting, it's recommended to download the Support Packet below that includes more details about your Mattermost environment.
<div
class="CommercialSupportModal__packet_contents_download"
>
<strong>
Select your Support Packet contents to download
</strong>
</div>
<div
class="CommercialSupportModal__option"
>
<input
checked=""
class="CommercialSupportModal__options__checkbox"
disabled=""
id="basic.server.logs"
name="basic.server.logs"
type="checkbox"
/>
<label
class="CommercialSupportModal__options_checkbox_label"
for="basic.server.logs"
>
Server Logs
</label>
</div>
<div
class="CommercialSupportModal__download"
>
<a
class="btn btn-primary DownloadSupportPacket"
rel="noopener noreferrer"
>
<i
class="icon icon-download-outline"
/>
Download Support Packet
</a>
</div>
</div>
</div>
</div>
</div>
`;

View file

@ -1,15 +1,33 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {Client4} from 'mattermost-redux/client';
import CommercialSupportModal from 'components/commercial_support_modal/commercial_support_modal';
import {act, renderWithContext, screen, userEvent, waitFor} from 'tests/react_testing_utils';
import {TestHelper} from 'utils/test_helper';
jest.mock('react-bootstrap', () => {
const Modal = ({children, show}: {children: React.ReactNode; show: boolean}) => (show ? <div>{children}</div> : null);
Modal.Header = ({children}: {children: React.ReactNode}) => <div>{children}</div>;
Modal.Body = ({children}: {children: React.ReactNode}) => <div>{children}</div>;
Modal.Title = ({children}: {children: React.ReactNode}) => <div>{children}</div>;
return {Modal};
});
jest.mock('components/alert_banner', () => (props: {message: React.ReactNode}) => (
<div>{props.message}</div>
));
jest.mock('components/external_link', () => ({children, href}: {children: React.ReactNode; href: string}) => (
<a href={href}>{children}</a>
));
jest.mock('components/widgets/loading/loading_spinner', () => () => <div>{'Loading...'}</div>);
describe('components/CommercialSupportModal', () => {
beforeAll(() => {
// Mock getSystemRoute to return a valid URL
@ -37,8 +55,8 @@ describe('components/CommercialSupportModal', () => {
};
test('should match snapshot', () => {
const wrapper = shallow(<CommercialSupportModal {...baseProps}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<CommercialSupportModal {...baseProps}/>);
expect(container).toMatchSnapshot();
});
test('should show error message when download fails', async () => {
@ -56,20 +74,17 @@ describe('components/CommercialSupportModal', () => {
}),
);
const wrapper = shallow<CommercialSupportModal>(<CommercialSupportModal {...baseProps}/>);
renderWithContext(<CommercialSupportModal {...baseProps}/>);
// Trigger download
const instance = wrapper.instance();
await instance.downloadSupportPacket();
wrapper.update();
const user = userEvent.setup();
const downloadLink = screen.getByText('Download Support Packet').closest('a');
if (!downloadLink) {
throw new Error('Download Support Packet link not found');
}
await user.click(downloadLink);
// Verify error message is shown
const errorDiv = wrapper.find('.CommercialSupportModal__error');
expect(errorDiv.exists()).toBe(true);
expect(errorDiv.find('.error-text').text()).toBe(`${errorMessage}: ${detailedError}`);
// Verify loading state is reset
expect(wrapper.state('loading')).toBe(false);
expect(await screen.findByText(`${errorMessage}: ${detailedError}`)).toBeInTheDocument();
});
test('should clear error when starting new download', async () => {
@ -82,16 +97,30 @@ describe('components/CommercialSupportModal', () => {
}),
);
const wrapper = shallow<CommercialSupportModal>(<CommercialSupportModal {...baseProps}/>);
const ref = React.createRef<CommercialSupportModal>();
renderWithContext(
<CommercialSupportModal
{...baseProps}
ref={ref}
/>,
);
// Set initial error state
wrapper.setState({error: 'Previous error'});
act(() => {
ref.current?.setState({error: 'Previous error'});
});
expect(screen.getByText('Previous error')).toBeInTheDocument();
// Start download
const instance = wrapper.instance();
await instance.downloadSupportPacket();
const user = userEvent.setup();
const downloadLink = screen.getByText('Download Support Packet').closest('a');
if (!downloadLink) {
throw new Error('Download Support Packet link not found');
}
await user.click(downloadLink);
// Verify error is cleared
expect(wrapper.state('error')).toBeUndefined();
await waitFor(() => {
expect(screen.queryByText('Previous error')).not.toBeInTheDocument();
});
});
});

View file

@ -1,80 +1,128 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/drafts/drafts_row should match snapshot for channel draft 1`] = `
<ContextProvider
value={
Object {
"getServerState": undefined,
"identityFunctionCheck": "once",
"stabilityCheck": "once",
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Object {
"addNestedSub": [Function],
"getListeners": [Function],
"handleChangeWrapper": [Function],
"isSubscribed": [Function],
"notifyNestedSubs": [Function],
"trySubscribe": [Function],
"tryUnsubscribe": [Function],
},
}
}
>
<Memo(DraftRow)
displayName="test"
isRemote={false}
item={Object {}}
status={Object {}}
user={Object {}}
/>
</ContextProvider>
<div>
<div
aria-label="draft in Channel Name"
class="Panel"
data-testid="draftView"
role="link"
tabindex="0"
>
<div
class="PanelHeader"
>
<div
class="PanelHeader__left"
>
<div>
Draft Title
</div>
</div>
<div
class="PanelHeader__right"
>
<div
class="PanelHeader__actions"
>
<div>
Draft Actions
</div>
</div>
<div
class="PanelHeader__info"
>
<div
class="PanelHeader__timestamp"
>
56 years ago
</div>
<div
class="TagWrapper-kezsCu cA-dXXM Tag Tag--danger Tag--xs"
>
<span
class="TagText-bWoQAI beIloY"
>
draft
</span>
</div>
</div>
</div>
</div>
<div>
Panel Body
</div>
</div>
</div>
`;
exports[`components/drafts/drafts_row should match snapshot for thread draft 1`] = `
<ContextProvider
value={
Object {
"getServerState": undefined,
"identityFunctionCheck": "once",
"stabilityCheck": "once",
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Object {
"addNestedSub": [Function],
"getListeners": [Function],
"handleChangeWrapper": [Function],
"isSubscribed": [Function],
"notifyNestedSubs": [Function],
"trySubscribe": [Function],
"tryUnsubscribe": [Function],
},
}
}
>
<Memo(DraftRow)
displayName="test"
draft={
Object {
"rootId": "some_id",
}
}
isRemote={false}
item={Object {}}
status={Object {}}
user={Object {}}
/>
</ContextProvider>
<div>
<div
aria-label="draft in Channel Name"
class="Panel draftError"
data-testid="draftView"
role="link"
tabindex="0"
>
<div
class="PanelHeader"
>
<div
class="PanelHeader__left"
>
<div>
Draft Title
</div>
</div>
<div
class="PanelHeader__right"
>
<div
class="PanelHeader__actions"
>
<div>
Draft Actions
</div>
</div>
<div
class="PanelHeader__info"
>
<div
class="PanelHeader__timestamp"
>
56 years ago
</div>
<div
class="TagWrapper-kezsCu cA-dXXM Tag Tag--danger Tag--xs"
>
<svg
fill="currentColor"
height="10"
version="1.1"
viewBox="0 0 24 24"
width="10"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M21.638,16.575L14.64,3.578C14.117,2.605,13.105,2,12,2S9.883,2.605,9.359,3.578L2.362,16.575c-0.505,0.939-0.481,2.046,0.065,2.962C2.974,20.453,3.937,21,5.003,21h13.994c1.066,0,2.029-0.547,2.575-1.463
C22.119,18.622,22.143,17.514,21.638,16.575z M18.995,18.998H5.001c-0.757,0-1.239-0.808-0.88-1.475l6.997-12.997
C11.307,4.175,11.652,4,11.998,4s0.691,0.175,0.88,0.526l6.997,12.997C20.234,18.19,19.752,18.998,18.995,18.998z M12.5,13h-1L11,7
h2L12.5,13z M12.999,16c0,0.552-0.448,1-1,1s-1-0.448-1-1s0.448-1,1-1C12.552,15,12.999,15.448,12.999,16z"
/>
</svg>
<span
class="TagText-bWoQAI beIloY"
>
Thread not found
</span>
</div>
</div>
</div>
</div>
<div>
Panel Body
</div>
</div>
</div>
`;

View file

@ -1,23 +1,23 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/drafts/draft_actions/action should match snapshot 1`] = `
<div
className="DraftAction"
>
<WithTooltip
id="draft_tooltip_some-id"
title="some-tooltip-text"
<div>
<div
class="DraftAction"
>
<button
aria-labelledby="draft_tooltip_some-id"
className="DraftAction__button"
id="draft_some-icon_some-id"
onClick={[MockFunction]}
<div
data-testid="with-tooltip"
>
<i
className="icon some-icon"
/>
</button>
</WithTooltip>
<button
aria-labelledby="draft_tooltip_some-id"
class="DraftAction__button"
id="draft_some-icon_some-id"
>
<i
class="icon some-icon"
/>
</button>
</div>
</div>
</div>
`;

View file

@ -1,108 +1,70 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/drafts/draft_actions/delete_draft_modal should have called onConfirm 1`] = `
<GenericModal
autoFocusConfirmButton={true}
compassDesign={true}
confirmButtonText="Yes, delete"
handleCancel={[Function]}
handleConfirm={
[MockFunction] {
"calls": Array [
Array [],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
}
isDeleteModal={true}
modalHeaderText="Delete draft"
onExited={[MockFunction]}
>
<MemoizedFormattedMessage
defaultMessage="Are you sure you want to delete this draft to <strong>{displayName}</strong>?"
id="drafts.confirm.delete.text"
values={
Object {
"displayName": "display_name",
"strong": [Function],
}
}
/>
</GenericModal>
<div>
<div>
<h1>
Delete draft
</h1>
<div>
Are you sure you want to delete this draft to
<strong>
display_name
</strong>
?
</div>
<button>
Yes, delete
</button>
<button>
onExited
</button>
</div>
</div>
`;
exports[`components/drafts/draft_actions/delete_draft_modal should have called onExited 1`] = `
<GenericModal
autoFocusConfirmButton={true}
compassDesign={true}
confirmButtonText="Yes, delete"
handleCancel={[Function]}
handleConfirm={[MockFunction]}
isDeleteModal={true}
modalHeaderText="Delete draft"
onExited={
[MockFunction] {
"calls": Array [
Array [],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
}
>
<MemoizedFormattedMessage
defaultMessage="Are you sure you want to delete this draft to <strong>{displayName}</strong>?"
id="drafts.confirm.delete.text"
values={
Object {
"displayName": "display_name",
"strong": [Function],
}
}
/>
</GenericModal>
<div>
<div>
<h1>
Delete draft
</h1>
<div>
Are you sure you want to delete this draft to
<strong>
display_name
</strong>
?
</div>
<button>
Yes, delete
</button>
<button>
onExited
</button>
</div>
</div>
`;
exports[`components/drafts/draft_actions/delete_draft_modal should match snapshot 1`] = `
<ContextProvider
value={
Object {
"getServerState": undefined,
"identityFunctionCheck": "once",
"stabilityCheck": "once",
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Object {
"addNestedSub": [Function],
"getListeners": [Function],
"handleChangeWrapper": [Function],
"isSubscribed": [Function],
"notifyNestedSubs": [Function],
"trySubscribe": [Function],
"tryUnsubscribe": [Function],
},
}
}
>
<DeleteDraftModal
displayName="display_name"
onConfirm={[MockFunction]}
onExited={[MockFunction]}
/>
</ContextProvider>
<div>
<div>
<h1>
Delete draft
</h1>
<div>
Are you sure you want to delete this draft to
<strong>
display_name
</strong>
?
</div>
<button>
Yes, delete
</button>
<button>
onExited
</button>
</div>
</div>
`;

View file

@ -1,43 +1,74 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/drafts/draft_actions should match snapshot 1`] = `
<ContextProvider
value={
Object {
"getServerState": undefined,
"identityFunctionCheck": "once",
"stabilityCheck": "once",
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Object {
"addNestedSub": [Function],
"getListeners": [Function],
"handleChangeWrapper": [Function],
"isSubscribed": [Function],
"notifyNestedSubs": [Function],
"trySubscribe": [Function],
"tryUnsubscribe": [Function],
},
}
}
>
<Memo(DraftActions)
canEdit={true}
canSend={true}
channelId=""
displayName=""
draftId=""
itemId=""
onDelete={[MockFunction]}
onEdit={[MockFunction]}
onSchedule={[MockFunction]}
onSend={[MockFunction]}
/>
</ContextProvider>
<div>
<div
class="DraftAction"
>
<div
data-testid="with-tooltip"
>
<button
aria-labelledby="draft_tooltip_delete"
class="DraftAction__button DraftAction__button--delete"
id="draft_icon-trash-can-outline_delete"
>
<i
class="icon icon-trash-can-outline"
/>
</button>
</div>
</div>
<div
class="DraftAction"
>
<div
data-testid="with-tooltip"
>
<button
aria-labelledby="draft_tooltip_edit"
class="DraftAction__button"
id="draft_icon-pencil-outline_edit"
>
<i
class="icon icon-pencil-outline"
/>
</button>
</div>
</div>
<div
class="DraftAction"
>
<div
data-testid="with-tooltip"
>
<button
aria-labelledby="draft_tooltip_reschedule"
class="DraftAction__button"
id="draft_icon-clock-send-outline_reschedule"
>
<i
class="icon icon-clock-send-outline"
/>
</button>
</div>
</div>
<div
class="DraftAction"
>
<div
data-testid="with-tooltip"
>
<button
aria-labelledby="draft_tooltip_send"
class="DraftAction__button"
id="draft_icon-send-outline_send"
>
<i
class="icon icon-send-outline"
/>
</button>
</div>
</div>
</div>
`;

View file

@ -1,104 +1,70 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/drafts/draft_actions/send_draft_modal should have called onConfirm 1`] = `
<GenericModal
compassDesign={true}
confirmButtonText="Yes, send now"
handleCancel={[Function]}
handleConfirm={
[MockFunction] {
"calls": Array [
Array [],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
}
modalHeaderText="Send message now"
onExited={[MockFunction]}
>
<MemoizedFormattedMessage
defaultMessage="Are you sure you want to send this message to <strong>{displayName}</strong>?"
id="drafts.confirm.send.text"
values={
Object {
"displayName": "display_name",
"strong": [Function],
}
}
/>
</GenericModal>
<div>
<div>
<h1>
Send message now
</h1>
<div>
Are you sure you want to send this message to
<strong>
display_name
</strong>
?
</div>
<button>
Yes, send now
</button>
<button>
onExited
</button>
</div>
</div>
`;
exports[`components/drafts/draft_actions/send_draft_modal should have called onExited 1`] = `
<GenericModal
compassDesign={true}
confirmButtonText="Yes, send now"
handleCancel={[Function]}
handleConfirm={[MockFunction]}
modalHeaderText="Send message now"
onExited={
[MockFunction] {
"calls": Array [
Array [],
],
"results": Array [
Object {
"type": "return",
"value": undefined,
},
],
}
}
>
<MemoizedFormattedMessage
defaultMessage="Are you sure you want to send this message to <strong>{displayName}</strong>?"
id="drafts.confirm.send.text"
values={
Object {
"displayName": "display_name",
"strong": [Function],
}
}
/>
</GenericModal>
<div>
<div>
<h1>
Send message now
</h1>
<div>
Are you sure you want to send this message to
<strong>
display_name
</strong>
?
</div>
<button>
Yes, send now
</button>
<button>
onExited
</button>
</div>
</div>
`;
exports[`components/drafts/draft_actions/send_draft_modal should match snapshot 1`] = `
<ContextProvider
value={
Object {
"getServerState": undefined,
"identityFunctionCheck": "once",
"stabilityCheck": "once",
"store": Object {
"clearActions": [Function],
"dispatch": [Function],
"getActions": [Function],
"getState": [Function],
"replaceReducer": [Function],
"subscribe": [Function],
},
"subscription": Object {
"addNestedSub": [Function],
"getListeners": [Function],
"handleChangeWrapper": [Function],
"isSubscribed": [Function],
"notifyNestedSubs": [Function],
"trySubscribe": [Function],
"tryUnsubscribe": [Function],
},
}
}
>
<SendDraftModal
displayName="display_name"
onConfirm={[MockFunction]}
onExited={[MockFunction]}
/>
</ContextProvider>
<div>
<div>
<h1>
Send message now
</h1>
<div>
Are you sure you want to send this message to
<strong>
display_name
</strong>
?
</div>
<button>
Yes, send now
</button>
<button>
onExited
</button>
</div>
</div>
`;

View file

@ -1,11 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {render} from 'tests/react_testing_utils';
import Action from './action';
jest.mock('components/with_tooltip', () => ({
__esModule: true,
default: ({children}: {children: React.ReactNode}) => (
<div data-testid='with-tooltip'>{children}</div>
),
}));
describe('components/drafts/draft_actions/action', () => {
const baseProps = {
icon: 'some-icon',
@ -16,11 +24,11 @@ describe('components/drafts/draft_actions/action', () => {
};
it('should match snapshot', () => {
const wrapper = shallow(
const {container} = render(
<Action
{...baseProps}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
});

View file

@ -1,13 +1,32 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {Provider} from 'react-redux';
import {GenericModal} from '@mattermost/components';
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
import mockStore from 'tests/test_store';
jest.mock('@mattermost/components', () => ({
GenericModal: ({
children,
handleConfirm,
onExited,
confirmButtonText,
modalHeaderText,
}: {
children: React.ReactNode;
handleConfirm?: () => void;
onExited?: () => void;
confirmButtonText?: string;
modalHeaderText?: string;
}) => (
<div>
<h1>{modalHeaderText}</h1>
<div>{children}</div>
<button onClick={handleConfirm}>{confirmButtonText || 'Confirm'}</button>
<button onClick={onExited}>{'onExited'}</button>
</div>
),
}));
import DeleteDraftModal from './delete_draft_modal';
@ -19,35 +38,33 @@ describe('components/drafts/draft_actions/delete_draft_modal', () => {
};
it('should match snapshot', () => {
const store = mockStore();
const wrapper = shallow(
<Provider store={store}>
<DeleteDraftModal
{...baseProps}
/>
</Provider>,
const {container} = renderWithContext(
<DeleteDraftModal
{...baseProps}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should have called onConfirm', () => {
const wrapper = shallow(
it('should have called onConfirm', async () => {
const {container} = renderWithContext(
<DeleteDraftModal {...baseProps}/>,
);
wrapper.find(GenericModal).first().props().handleConfirm!();
const user = userEvent.setup();
await user.click(screen.getByRole('button', {name: /yes, delete/i}));
expect(baseProps.onConfirm).toHaveBeenCalledTimes(1);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should have called onExited', () => {
const wrapper = shallow(
it('should have called onExited', async () => {
const {container} = renderWithContext(
<DeleteDraftModal {...baseProps}/>,
);
wrapper.find(GenericModal).first().props().onExited?.();
const user = userEvent.setup();
await user.click(screen.getByRole('button', {name: 'onExited'}));
expect(baseProps.onExited).toHaveBeenCalledTimes(1);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
});

View file

@ -1,14 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {Provider} from 'react-redux';
import mockStore from 'tests/test_store';
import {renderWithContext} from 'tests/react_testing_utils';
import DraftActions from './draft_actions';
jest.mock('components/with_tooltip', () => ({
__esModule: true,
default: ({children}: {children: React.ReactNode}) => (
<div data-testid='with-tooltip'>{children}</div>
),
}));
describe('components/drafts/draft_actions', () => {
const baseProps = {
displayName: '',
@ -24,15 +29,11 @@ describe('components/drafts/draft_actions', () => {
};
it('should match snapshot', () => {
const store = mockStore();
const wrapper = shallow(
<Provider store={store}>
<DraftActions
{...baseProps}
/>
</Provider>,
const {container} = renderWithContext(
<DraftActions
{...baseProps}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
});

View file

@ -1,13 +1,32 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {Provider} from 'react-redux';
import {GenericModal} from '@mattermost/components';
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
import mockStore from 'tests/test_store';
jest.mock('@mattermost/components', () => ({
GenericModal: ({
children,
handleConfirm,
onExited,
confirmButtonText,
modalHeaderText,
}: {
children: React.ReactNode;
handleConfirm?: () => void;
onExited?: () => void;
confirmButtonText?: string;
modalHeaderText?: string;
}) => (
<div>
<h1>{modalHeaderText}</h1>
<div>{children}</div>
<button onClick={handleConfirm}>{confirmButtonText || 'Confirm'}</button>
<button onClick={onExited}>{'onExited'}</button>
</div>
),
}));
import SendDraftModal from './send_draft_modal';
@ -19,35 +38,33 @@ describe('components/drafts/draft_actions/send_draft_modal', () => {
};
it('should match snapshot', () => {
const store = mockStore();
const wrapper = shallow(
<Provider store={store}>
<SendDraftModal
{...baseProps}
/>
</Provider>,
const {container} = renderWithContext(
<SendDraftModal
{...baseProps}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should have called onConfirm', () => {
const wrapper = shallow(
it('should have called onConfirm', async () => {
const {container} = renderWithContext(
<SendDraftModal {...baseProps}/>,
);
wrapper.find(GenericModal).first().props().handleConfirm!();
const user = userEvent.setup();
await user.click(screen.getByRole('button', {name: /yes, send now/i}));
expect(baseProps.onConfirm).toHaveBeenCalledTimes(1);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should have called onExited', () => {
const wrapper = shallow(
it('should have called onExited', async () => {
const {container} = renderWithContext(
<SendDraftModal {...baseProps}/>,
);
wrapper.find(GenericModal).first().props().onExited?.();
const user = userEvent.setup();
await user.click(screen.getByRole('button', {name: 'onExited'}));
expect(baseProps.onExited).toHaveBeenCalledTimes(1);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
});

View file

@ -1,57 +1,119 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import type {ComponentProps} from 'react';
import React from 'react';
import {Provider} from 'react-redux';
import type {ChannelType} from '@mattermost/types/channels';
import type {PostType} from '@mattermost/types/posts';
import type {UserProfile, UserStatus} from '@mattermost/types/users';
import mockStore from 'tests/test_store';
import {renderWithContext} from 'tests/react_testing_utils';
import type {PostDraft} from 'types/store/draft';
import DraftRow from './draft_row';
jest.mock('components/advanced_text_editor/use_priority', () => () => ({onSubmitCheck: jest.fn()}));
jest.mock('components/advanced_text_editor/use_submit', () => () => [jest.fn()]);
jest.mock('components/drafts/draft_actions', () => () => <div>{'Draft Actions'}</div>);
jest.mock('components/drafts/draft_title', () => () => <div>{'Draft Title'}</div>);
jest.mock('components/drafts/panel/panel_body', () => () => <div>{'Panel Body'}</div>);
jest.mock('components/drafts/draft_actions/schedule_post_actions/scheduled_post_actions', () => () => (
<div>{'Scheduled Post Actions'}</div>
));
jest.mock('components/edit_scheduled_post', () => () => <div>{'Edit Scheduled Post'}</div>);
jest.mock('components/drafts/placeholder_scheduled_post_title/placeholder_scheduled_posts_title', () => () => (
<div>{'Placeholder Scheduled Post Title'}</div>
));
jest.mock('mattermost-redux/actions/posts', () => ({
getPost: () => jest.fn(),
}));
jest.mock('mattermost-redux/actions/scheduled_posts', () => ({
deleteScheduledPost: () => jest.fn(),
updateScheduledPost: () => jest.fn(),
}));
jest.mock('mattermost-redux/selectors/entities/roles', () => ({
haveIChannelPermission: () => true,
}));
jest.mock('mattermost-redux/selectors/entities/channels', () => ({
...jest.requireActual('mattermost-redux/selectors/entities/channels'),
isDeactivatedDirectChannel: () => false,
}));
describe('components/drafts/drafts_row', () => {
const channelId = 'channel_id';
const teamId = 'team_id';
const channel = {
id: channelId,
team_id: teamId,
name: 'channel-name',
display_name: 'Channel Name',
type: 'O' as ChannelType,
delete_at: 0,
};
const initialState = {
entities: {
channels: {channels: {[channelId]: channel}},
teams: {
currentTeamId: teamId,
teams: {[teamId]: {id: teamId, name: 'team-name'}},
},
general: {
config: {
MaxPostSize: '16383',
BurnOnReadDurationSeconds: '600',
},
license: {},
},
posts: {posts: {}},
},
websocket: {connectionId: 'connection_id'},
};
const baseProps: ComponentProps<typeof DraftRow> = {
item: {} as PostDraft,
user: {} as UserProfile,
status: {} as UserStatus['status'],
item: {
message: 'draft message',
updateAt: 1234,
createAt: 1234,
fileInfos: [],
uploadsInProgress: [],
channelId,
rootId: '',
type: 'standard' as PostType,
} as PostDraft,
user: {id: 'user_id', username: 'username'} as UserProfile,
status: 'online' as UserStatus['status'],
displayName: 'test',
isRemote: false,
};
it('should match snapshot for channel draft', () => {
const store = mockStore();
const wrapper = shallow(
<Provider store={store}>
<DraftRow
{...baseProps}
/>
</Provider>,
const {container} = renderWithContext(
<DraftRow
{...baseProps}
/>,
initialState,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
it('should match snapshot for thread draft', () => {
const store = mockStore();
const props = {
...baseProps,
draft: {rootId: 'some_id'} as PostDraft,
item: {
...baseProps.item,
rootId: 'some_id',
} as PostDraft,
};
const wrapper = shallow(
<Provider store={store}>
<DraftRow
{...props}
/>
</Provider>,
const {container} = renderWithContext(
<DraftRow
{...props}
/>,
initialState,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
});

View file

@ -1,13 +1,11 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/drafts/panel/ should match snapshot 1`] = `
<div
className="Panel"
onClick={[Function]}
onKeyDown={[Function]}
role="link"
tabIndex={0}
>
<Component />
<div>
<div
class="Panel"
role="link"
tabindex="0"
/>
</div>
`;

View file

@ -1,126 +1,110 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/drafts/panel/panel_header should match snapshot 1`] = `
<div
className="PanelHeader"
>
<div>
<div
className="PanelHeader__left"
>
<div>
title
</div>
</div>
<div
className="PanelHeader__right"
class="PanelHeader"
>
<div
className="PanelHeader__actions"
class="PanelHeader__left"
>
<div>
actions
title
</div>
</div>
<div
className="PanelHeader__info"
class="PanelHeader__right"
>
<div
className="PanelHeader__timestamp"
class="PanelHeader__actions"
>
<Connect(injectIntl(Timestamp))
day="numeric"
units={
Array [
"now",
"minute",
"hour",
"day",
"week",
"month",
"year",
]
}
useSemanticOutput={false}
useTime={false}
value={1970-01-01T00:00:12.345Z}
/>
<div>
actions
</div>
</div>
<div
class="PanelHeader__info"
>
<div
class="PanelHeader__timestamp"
>
56 years ago
</div>
<div
class="TagWrapper-kezsCu cA-dXXM Tag Tag--danger Tag--xs"
>
<span
class="TagText-bWoQAI beIloY"
>
draft
</span>
</div>
</div>
<Memo(Tag)
text="draft"
uppercase={true}
variant="danger"
/>
</div>
</div>
</div>
`;
exports[`components/drafts/panel/panel_header should show sync icon when draft is from server 1`] = `
<div
className="PanelHeader"
>
<div>
<div
className="PanelHeader__left"
>
<div>
title
</div>
</div>
<div
className="PanelHeader__right"
class="PanelHeader"
>
<div
className="PanelHeader__actions"
class="PanelHeader__left"
>
<div>
actions
title
</div>
</div>
<div
className="PanelHeader__info"
class="PanelHeader__right"
>
<div
className="PanelHeader__sync-icon"
class="PanelHeader__actions"
>
<WithTooltip
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Updated from another device"
id="drafts.info.sync"
/>
}
>
<SyncIcon
size={18}
/>
</WithTooltip>
<div>
actions
</div>
</div>
<div
className="PanelHeader__timestamp"
class="PanelHeader__info"
>
<Connect(injectIntl(Timestamp))
day="numeric"
units={
Array [
"now",
"minute",
"hour",
"day",
"week",
"month",
"year",
]
}
useSemanticOutput={false}
useTime={false}
value={1970-01-01T00:00:12.345Z}
/>
<div
class="PanelHeader__sync-icon"
>
<div
data-testid="with-tooltip"
>
<svg
fill="currentColor"
height="18"
version="1.1"
viewBox="0 0 24 24"
width="18"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12,18A6,6 0 0,1 6,12C6,11 6.25,10.03 6.7,9.2L5.24,7.74C4.46,8.97 4,10.43 4,12A8,8 0 0,0 12,20V23L16,19L12,15M12,4V1L8,5L12,9V6A6,6 0 0,1 18,12C18,13 17.75,13.97 17.3,14.8L18.76,16.26C19.54,15.03 20,13.57 20,12A8,8 0 0,0 12,4Z"
/>
</svg>
</div>
</div>
<div
class="PanelHeader__timestamp"
>
56 years ago
</div>
<div
class="TagWrapper-kezsCu cA-dXXM Tag Tag--danger Tag--xs"
>
<span
class="TagText-bWoQAI beIloY"
>
draft
</span>
</div>
</div>
<Memo(Tag)
text="draft"
uppercase={true}
variant="danger"
/>
</div>
</div>
</div>

View file

@ -1,9 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {render} from 'tests/react_testing_utils';
import Panel from './panel';
describe('components/drafts/panel/', () => {
@ -17,12 +18,12 @@ describe('components/drafts/panel/', () => {
};
it('should match snapshot', () => {
const wrapper = shallow(
const {container} = render(
<Panel
{...baseProps}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
});

View file

@ -1,13 +1,19 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import WithTooltip from 'components/with_tooltip';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import PanelHeader from './panel_header';
jest.mock('components/with_tooltip', () => ({
__esModule: true,
default: ({children}: {children: React.ReactNode}) => (
<div data-testid='with-tooltip'>{children}</div>
),
}));
describe('components/drafts/panel/panel_header', () => {
const baseProps: React.ComponentProps<typeof PanelHeader> = {
kind: 'draft' as const,
@ -19,15 +25,15 @@ describe('components/drafts/panel/panel_header', () => {
};
it('should match snapshot', () => {
const wrapper = shallow(
const {container} = renderWithContext(
<PanelHeader
{...baseProps}
/>,
);
expect(wrapper.find('div.PanelHeader__actions').hasClass('PanelHeader__actions show')).toBe(false);
expect(wrapper.find(WithTooltip).exists()).toBe(false);
expect(wrapper).toMatchSnapshot();
expect(screen.queryByTestId('with-tooltip')).not.toBeInTheDocument();
expect(screen.getByText('actions').closest('.PanelHeader__actions')).not.toHaveClass('show');
expect(container).toMatchSnapshot();
});
it('should show sync icon when draft is from server', () => {
@ -36,13 +42,13 @@ describe('components/drafts/panel/panel_header', () => {
remote: true,
};
const wrapper = shallow(
const {container} = renderWithContext(
<PanelHeader
{...props}
/>,
);
expect(wrapper.find(WithTooltip).exists()).toBe(true);
expect(wrapper).toMatchSnapshot();
expect(screen.getByTestId('with-tooltip')).toBeInTheDocument();
expect(container).toMatchSnapshot();
});
});

View file

@ -1,9 +1,9 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import {ModalIdentifiers} from 'utils/constants';
import FeatureRestrictedModal from './feature_restricted_modal';
@ -17,6 +17,31 @@ jest.mock('react-redux', () => ({
useDispatch: () => mockDispatch,
}));
jest.mock('components/common/hooks/useOpenPricingModal', () => () => ({
openPricingModal: jest.fn(),
isAirGapped: false,
}));
jest.mock('components/notify_admin_cta/notify_admin_cta', () => ({
useNotifyAdmin: () => {
const {NotifyStatus} = require('components/common/hooks/useGetNotifyAdmin');
return ['Notify admin', jest.fn(), NotifyStatus.None];
},
}));
jest.mock('components/learn_more_trial_modal/start_trial_btn', () => (props: {onClick: () => void}) => (
<button onClick={props.onClick}>{'Try free for 30 days'}</button>
));
jest.mock('@mattermost/components', () => ({
GenericModal: ({children, modalHeaderText}: {children: React.ReactNode; modalHeaderText?: string}) => (
<div>
<h1>{modalHeaderText}</h1>
{children}
</div>
),
}));
describe('components/global/product_switcher_menu', () => {
const defaultProps = {
titleAdminPreTrial: 'Title admin pre trial',
@ -78,36 +103,34 @@ describe('components/global/product_switcher_menu', () => {
});
test('should show with end user pre trial', () => {
const wrapper = shallow(<FeatureRestrictedModal {...defaultProps}/>);
const {container} = renderWithContext(<FeatureRestrictedModal {...defaultProps}/>);
expect(wrapper.find('.FeatureRestrictedModal__description').text()).toEqual(defaultProps.messageEndUser);
expect(wrapper.find('.FeatureRestrictedModal__terms').length).toEqual(0);
expect(wrapper.find('.FeatureRestrictedModal__buttons').hasClass('single')).toEqual(true);
expect(wrapper.find('.button-plans').length).toEqual(1);
expect(wrapper.find('CloudStartTrialButton').length).toEqual(0);
expect(wrapper.find('StartTrialBtn').length).toEqual(0);
expect(screen.getByText(defaultProps.messageEndUser)).toBeInTheDocument();
expect(container.querySelector('.FeatureRestrictedModal__terms')).not.toBeInTheDocument();
expect(container.querySelector('.FeatureRestrictedModal__buttons')).toHaveClass('single');
expect(screen.getByRole('button', {name: /notify admin/i})).toBeInTheDocument();
expect(screen.queryByRole('button', {name: /try free/i})).not.toBeInTheDocument();
});
test('should show with end user post trial', () => {
const wrapper = shallow(<FeatureRestrictedModal {...defaultProps}/>);
const {container} = renderWithContext(<FeatureRestrictedModal {...defaultProps}/>);
expect(wrapper.find('.FeatureRestrictedModal__description').text()).toEqual(defaultProps.messageEndUser);
expect(wrapper.find('.FeatureRestrictedModal__terms').length).toEqual(0);
expect(wrapper.find('.FeatureRestrictedModal__buttons').hasClass('single')).toEqual(true);
expect(wrapper.find('.button-plans').length).toEqual(1);
expect(wrapper.find('CloudStartTrialButton').length).toEqual(0);
expect(wrapper.find('StartTrialBtn').length).toEqual(0);
expect(screen.getByText(defaultProps.messageEndUser)).toBeInTheDocument();
expect(container.querySelector('.FeatureRestrictedModal__terms')).not.toBeInTheDocument();
expect(container.querySelector('.FeatureRestrictedModal__buttons')).toHaveClass('single');
expect(screen.getByRole('button', {name: /notify admin/i})).toBeInTheDocument();
expect(screen.queryByRole('button', {name: /try free/i})).not.toBeInTheDocument();
});
test('should show with system admin pre trial for self hosted', () => {
mockState.entities.users.profiles.user1.roles = 'system_admin';
const wrapper = shallow(<FeatureRestrictedModal {...defaultProps}/>);
const {container} = renderWithContext(<FeatureRestrictedModal {...defaultProps}/>);
expect(wrapper.find('.FeatureRestrictedModal__description').text()).toEqual(defaultProps.messageAdminPreTrial);
expect(wrapper.find('.FeatureRestrictedModal__terms').length).toEqual(1);
expect(wrapper.find('.FeatureRestrictedModal__buttons').hasClass('single')).toEqual(false);
expect(wrapper.find('.button-plans').length).toEqual(1);
expect(wrapper.find('StartTrialBtn').length).toEqual(1);
expect(screen.getByText(defaultProps.messageAdminPreTrial)).toBeInTheDocument();
expect(container.querySelector('.FeatureRestrictedModal__terms')).toBeInTheDocument();
expect(container.querySelector('.FeatureRestrictedModal__buttons')).not.toHaveClass('single');
expect(screen.getByRole('button', {name: /view plans/i})).toBeInTheDocument();
expect(screen.getByRole('button', {name: /try free/i})).toBeInTheDocument();
});
});

View file

@ -1,355 +1,334 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`FilePreview should match snapshot 1`] = `
<div
className="file-preview__container"
>
<div>
<div
className="file-preview post-image__column"
key="file_id_1"
class="file-preview__container"
>
<div
className="post-image__thumbnail"
class="file-preview post-image__column"
>
<div
className="post-image normal"
style={
Object {
"backgroundImage": "url(/api/v4/files/file_id_1/thumbnail)",
"backgroundSize": "cover",
}
}
/>
</div>
<div
className="post-image__details"
>
<div
className="post-image__detail_wrapper"
class="post-image__thumbnail"
>
<div
className="post-image__detail"
class="post-image normal"
style="background-image: url(\\"/api/v4/files/file_id_1/thumbnail\\"); background-size: cover;"
/>
</div>
<div
class="post-image__details"
>
<div
class="post-image__detail_wrapper"
>
<FilenameOverlay
canDownload={false}
compactDisplay={false}
fileInfo={
Object {
"archived": false,
"channel_id": "channel_id",
"clientId": "",
"create_at": 0,
"delete_at": 0,
"extension": "png",
"has_preview_image": true,
"height": 100,
"id": "file_id_1",
"mime_type": "",
"name": "test_filename",
"size": 100,
"type": "image/png",
"update_at": 0,
"user_id": "",
"width": 100,
}
}
/>
<span
className="post-image__type"
<div
class="post-image__detail"
>
PNG
</span>
<span
className="post-image__size"
<span
class="post-image__name"
>
test_filename
</span>
<span
class="post-image__type"
>
PNG
</span>
<span
class="post-image__size"
>
100B
</span>
</div>
</div>
<div>
<a
class="file-preview__remove"
>
100B
</span>
<i
class="icon icon-close"
/>
</a>
</div>
</div>
<div>
<a
className="file-preview__remove"
onClick={[Function]}
</div>
<div
class="file-preview post-image__column"
data-client-id="clientID_1"
>
<div
class="post-image__thumbnail"
>
<div
class="file-icon generic"
/>
</div>
<div
class="post-image__details"
>
<div
class="post-image__detail_wrapper"
>
<i
className="icon icon-close"
<div
class="post-image__detail"
>
<span
class="post-image__name"
>
file
</span>
<span
class="post-image__uploadingTxt"
>
Uploading...
<span>
(50%)
</span>
</span>
</div>
</div>
<div>
<a
class="file-preview__remove"
>
<i
class="icon icon-close"
/>
</a>
</div>
<div
class="post-image__progressBar progress"
>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="50"
class="progress-bar"
role="progressbar"
style="width: 50%;"
/>
</a>
</div>
</div>
</div>
</div>
<FileProgressPreview
clientId="clientID_1"
fileInfo={
Object {
"archived": false,
"channel_id": "channel_id",
"clientId": "",
"create_at": 0,
"delete_at": 0,
"extension": "image/png",
"has_preview_image": true,
"height": 100,
"id": "file_id_1",
"mime_type": "",
"name": "file",
"percent": 50,
"size": 100,
"update_at": 0,
"user_id": "",
"width": 100,
}
}
handleRemove={[Function]}
key="clientID_1"
/>
</div>
`;
exports[`FilePreview should match snapshot when props are changed 1`] = `
<div
className="file-preview__container"
>
<div>
<div
className="file-preview post-image__column"
key="file_id_1"
class="file-preview__container"
>
<div
className="post-image__thumbnail"
class="file-preview post-image__column"
>
<div
className="post-image normal"
style={
Object {
"backgroundImage": "url(/api/v4/files/file_id_1/thumbnail)",
"backgroundSize": "cover",
}
}
/>
</div>
<div
className="post-image__details"
>
<div
className="post-image__detail_wrapper"
class="post-image__thumbnail"
>
<div
className="post-image__detail"
class="post-image normal"
style="background-image: url(\\"/api/v4/files/file_id_1/thumbnail\\"); background-size: cover;"
/>
</div>
<div
class="post-image__details"
>
<div
class="post-image__detail_wrapper"
>
<FilenameOverlay
canDownload={false}
compactDisplay={false}
fileInfo={
Object {
"archived": false,
"channel_id": "channel_id",
"clientId": "",
"create_at": 0,
"delete_at": 0,
"extension": "png",
"has_preview_image": true,
"height": 100,
"id": "file_id_1",
"mime_type": "",
"name": "test_filename",
"size": 100,
"type": "image/png",
"update_at": 0,
"user_id": "",
"width": 100,
}
}
/>
<span
className="post-image__type"
<div
class="post-image__detail"
>
PNG
</span>
<span
className="post-image__size"
<span
class="post-image__name"
>
test_filename
</span>
<span
class="post-image__type"
>
PNG
</span>
<span
class="post-image__size"
>
100B
</span>
</div>
</div>
<div>
<a
class="file-preview__remove"
>
100B
</span>
<i
class="icon icon-close"
/>
</a>
</div>
</div>
<div>
<a
className="file-preview__remove"
onClick={[Function]}
</div>
<div
class="file-preview post-image__column"
data-client-id="clientID_1"
>
<div
class="post-image__thumbnail"
>
<div
class="file-icon generic"
/>
</div>
<div
class="post-image__details"
>
<div
class="post-image__detail_wrapper"
>
<i
className="icon icon-close"
<div
class="post-image__detail"
>
<span
class="post-image__name"
>
file
</span>
<span
class="post-image__uploadingTxt"
>
Uploading...
<span>
(50%)
</span>
</span>
</div>
</div>
<div>
<a
class="file-preview__remove"
>
<i
class="icon icon-close"
/>
</a>
</div>
<div
class="post-image__progressBar progress"
>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="50"
class="progress-bar"
role="progressbar"
style="width: 50%;"
/>
</a>
</div>
</div>
</div>
</div>
<FileProgressPreview
clientId="clientID_1"
fileInfo={
Object {
"archived": false,
"channel_id": "channel_id",
"clientId": "",
"create_at": 0,
"delete_at": 0,
"extension": "image/png",
"has_preview_image": true,
"height": 100,
"id": "file_id_1",
"mime_type": "",
"name": "file",
"percent": 50,
"size": 100,
"update_at": 0,
"user_id": "",
"width": 100,
}
}
handleRemove={[Function]}
key="clientID_1"
/>
</div>
`;
exports[`FilePreview should match snapshot when props are changed 2`] = `
<div
className="file-preview__container"
>
<div>
<div
className="file-preview post-image__column"
key="file_id_1"
class="file-preview__container"
>
<div
className="post-image__thumbnail"
class="file-preview post-image__column"
>
<div
className="post-image normal"
style={
Object {
"backgroundImage": "url(/api/v4/files/file_id_1/thumbnail)",
"backgroundSize": "cover",
}
}
/>
</div>
<div
className="post-image__details"
>
<div
className="post-image__detail_wrapper"
class="post-image__thumbnail"
>
<div
className="post-image__detail"
>
<FilenameOverlay
canDownload={false}
compactDisplay={false}
fileInfo={
Object {
"archived": false,
"channel_id": "channel_id",
"clientId": "",
"create_at": 0,
"delete_at": 0,
"extension": "png",
"has_preview_image": true,
"height": 100,
"id": "file_id_1",
"mime_type": "",
"name": "test_filename",
"size": 100,
"type": "image/png",
"update_at": 0,
"user_id": "",
"width": 100,
}
}
/>
<span
className="post-image__type"
>
PNG
</span>
<span
className="post-image__size"
>
100B
</span>
</div>
class="post-image normal"
style="background-image: url(\\"/api/v4/files/file_id_1/thumbnail\\"); background-size: cover;"
/>
</div>
<div>
<a
className="file-preview__remove"
onClick={[Function]}
>
<i
className="icon icon-close"
/>
</a>
</div>
</div>
</div>
<div
className="file-preview post-image__column"
key="file_id_2"
>
<div
className="post-image__thumbnail"
>
<div
className="post-image normal"
style={
Object {
"backgroundImage": "url(/api/v4/files/file_id_2/thumbnail)",
"backgroundSize": "cover",
}
}
/>
</div>
<div
className="post-image__details"
>
<div
className="post-image__detail_wrapper"
class="post-image__details"
>
<div
className="post-image__detail"
class="post-image__detail_wrapper"
>
<FilenameOverlay
canDownload={false}
compactDisplay={false}
fileInfo={
Object {
"create_at": "2",
"extension": "jpg",
"height": 100,
"id": "file_id_2",
"width": 100,
}
}
/>
<span
className="post-image__type"
<div
class="post-image__detail"
>
JPG
</span>
<span
className="post-image__size"
<span
class="post-image__name"
>
test_filename
</span>
<span
class="post-image__type"
>
PNG
</span>
<span
class="post-image__size"
>
100B
</span>
</div>
</div>
<div>
<a
class="file-preview__remove"
>
undefinedB
</span>
<i
class="icon icon-close"
/>
</a>
</div>
</div>
<div>
<a
className="file-preview__remove"
onClick={[Function]}
</div>
<div
class="file-preview post-image__column"
>
<div
class="post-image__thumbnail"
>
<div
class="post-image normal"
style="background-image: url(\\"/api/v4/files/file_id_2/thumbnail\\"); background-size: cover;"
/>
</div>
<div
class="post-image__details"
>
<div
class="post-image__detail_wrapper"
>
<i
className="icon icon-close"
/>
</a>
<div
class="post-image__detail"
>
<span
class="post-image__name"
>
file_two.jpg
</span>
<span
class="post-image__type"
>
JPG
</span>
<span
class="post-image__size"
>
120B
</span>
</div>
</div>
<div>
<a
class="file-preview__remove"
>
<i
class="icon icon-close"
/>
</a>
</div>
</div>
</div>
</div>

View file

@ -1,159 +1,114 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`component/file_preview/file_progress_preview should match snapshot 1`] = `
<div
className="file-preview post-image__column"
data-client-id="clientId"
key="clientId"
>
<div>
<div
className="post-image__thumbnail"
class="file-preview post-image__column"
data-client-id="clientId"
>
<div
className="file-icon image"
/>
</div>
<div
className="post-image__details"
>
<div
className="post-image__detail_wrapper"
class="post-image__thumbnail"
>
<div
className="post-image__detail"
class="file-icon image"
/>
</div>
<div
class="post-image__details"
>
<div
class="post-image__detail_wrapper"
>
<FilenameOverlay
canDownload={false}
compactDisplay={false}
fileInfo={
Object {
"archived": false,
"channel_id": "channel_id",
"clientId": "",
"create_at": 0,
"delete_at": 0,
"extension": "png",
"has_preview_image": true,
"height": 80,
"id": "file",
"mime_type": "",
"name": "test_filename",
"percent": 50,
"size": 100,
"type": "image/png",
"update_at": 0,
"user_id": "",
"width": 100,
}
}
/>
<span
className="post-image__uploadingTxt"
<div
class="post-image__detail"
>
<MemoizedFormattedMessage
defaultMessage="Uploading..."
id="admin.plugin.uploading"
/>
<span>
(50%)
<span
class="post-image__name"
>
test_filename
</span>
</span>
<span
class="post-image__uploadingTxt"
>
Uploading...
<span>
(50%)
</span>
</span>
</div>
</div>
<div>
<a
class="file-preview__remove"
>
<i
class="icon icon-close"
/>
</a>
</div>
<div
class="post-image__progressBar progress"
>
<div
aria-valuemax="100"
aria-valuemin="0"
aria-valuenow="50"
class="progress-bar"
role="progressbar"
style="width: 50%;"
/>
</div>
</div>
<div>
<a
className="file-preview__remove"
onClick={[Function]}
>
<i
className="icon icon-close"
/>
</a>
</div>
<ProgressBar
active={false}
bsClass="progress-bar"
className="post-image__progressBar"
isChild={false}
max={100}
min={0}
now={50}
srOnly={false}
striped={false}
/>
</div>
</div>
`;
exports[`component/file_preview/file_progress_preview snapshot for percent value undefined 1`] = `
<div
className="file-preview post-image__column"
data-client-id="clientId"
key="clientId"
>
<div>
<div
className="post-image__thumbnail"
class="file-preview post-image__column"
data-client-id="clientId"
>
<div
className="file-icon image"
/>
</div>
<div
className="post-image__details"
>
<div
className="post-image__detail_wrapper"
class="post-image__thumbnail"
>
<div
className="post-image__detail"
>
<FilenameOverlay
canDownload={false}
compactDisplay={false}
fileInfo={
Object {
"archived": false,
"channel_id": "channel_id",
"clientId": "",
"create_at": 0,
"delete_at": 0,
"extension": "png",
"has_preview_image": true,
"height": 80,
"id": "file",
"mime_type": "",
"name": "test_filename",
"percent": undefined,
"size": 100,
"type": "image/png",
"update_at": 0,
"user_id": "",
"width": 100,
}
}
/>
<span
className="post-image__uploadingTxt"
>
<MemoizedFormattedMessage
defaultMessage="Uploading..."
id="admin.plugin.uploading"
/>
<span>
(0%)
</span>
</span>
</div>
class="file-icon image"
/>
</div>
<div>
<a
className="file-preview__remove"
onClick={[Function]}
<div
class="post-image__details"
>
<div
class="post-image__detail_wrapper"
>
<i
className="icon icon-close"
/>
</a>
<div
class="post-image__detail"
>
<span
class="post-image__name"
>
test_filename
</span>
<span
class="post-image__uploadingTxt"
>
Uploading...
<span>
(0%)
</span>
</span>
</div>
</div>
<div>
<a
class="file-preview__remove"
>
<i
class="icon icon-close"
/>
</a>
</div>
</div>
</div>
</div>

View file

@ -1,11 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {getFileUrl} from 'mattermost-redux/utils/file_utils';
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
import FilePreview from './file_preview';
describe('FilePreview', () => {
@ -62,45 +63,53 @@ describe('FilePreview', () => {
};
test('should match snapshot', () => {
const wrapper = shallow(
const {container} = renderWithContext(
<FilePreview {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when props are changed', () => {
const wrapper = shallow(
const {container, rerender} = renderWithContext(
<FilePreview {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
const fileInfo2 = {
...baseProps.fileInfos[0],
id: 'file_id_2',
create_at: '2',
width: 100,
height: 100,
create_at: 2,
extension: 'jpg',
name: 'file_two.jpg',
size: 120,
};
const newFileInfos = [...fileInfos, fileInfo2];
wrapper.setProps({
fileInfos: newFileInfos,
uploadsInProgress: [],
});
expect(wrapper).toMatchSnapshot();
rerender(
<FilePreview
{...baseProps}
fileInfos={newFileInfos}
uploadsInProgress={[]}
/>,
);
expect(container).toMatchSnapshot();
});
test('should call handleRemove when file removed', () => {
test('should call handleRemove when file removed', async () => {
const newOnRemove = jest.fn();
const props = {...baseProps, onRemove: newOnRemove};
const wrapper = shallow<FilePreview>(
const {container} = renderWithContext(
<FilePreview {...props}/>,
);
wrapper.instance().handleRemove('');
const user = userEvent.setup();
const removeLink = container.querySelector('a.file-preview__remove');
if (!removeLink) {
throw new Error('Remove link not found');
}
await user.click(removeLink);
expect(newOnRemove).toHaveBeenCalled();
});
test('should not render an SVG when SVGs are disabled', () => {
const fileId = 'file_id_1';
const props = {
...baseProps,
fileInfos: [
@ -112,12 +121,12 @@ describe('FilePreview', () => {
],
};
const wrapper = shallow(
const {container} = renderWithContext(
<FilePreview {...props}/>,
);
expect(wrapper.find('img').find({src: getFileUrl(fileId)}).exists()).toBe(false);
expect(wrapper.find('div').find('.file-icon.generic').exists()).toBe(true);
expect(screen.queryByAltText('file preview')).not.toBeInTheDocument();
expect(container.querySelector('.file-icon.generic')).toBeInTheDocument();
});
test('should render an SVG when SVGs are enabled', () => {
@ -134,11 +143,10 @@ describe('FilePreview', () => {
],
};
const wrapper = shallow(
renderWithContext(
<FilePreview {...props}/>,
);
expect(wrapper.find('img').find({src: getFileUrl(fileId)}).exists()).toBe(true);
expect(wrapper.find('div').find('.file-icon.generic').exists()).toBe(false);
expect(screen.getByAltText('file preview')).toHaveAttribute('src', getFileUrl(fileId));
});
});

View file

@ -1,9 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {renderWithContext} from 'tests/react_testing_utils';
import FileProgressPreview from './file_progress_preview';
describe('component/file_preview/file_progress_preview', () => {
@ -36,10 +37,10 @@ describe('component/file_preview/file_progress_preview', () => {
};
test('should match snapshot', () => {
const wrapper = shallow(
const {container} = renderWithContext(
<FileProgressPreview {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('snapshot for percent value undefined', () => {
@ -51,9 +52,9 @@ describe('component/file_preview/file_progress_preview', () => {
},
};
const wrapper = shallow(
const {container} = renderWithContext(
<FileProgressPreview {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
});

View file

@ -1,43 +1,51 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/view_image/ImagePreview should match snapshot, with preview 1`] = `
<a
className="image_preview"
href="#"
>
<img
alt="preview url image"
className="image_preview__image"
data-testid="imagePreview"
loading="lazy"
src="/api/v4/files/file_id_1/preview"
/>
</a>
<div>
<a
class="image_preview"
href="#"
>
<img
alt="preview url image"
class="image_preview__image"
data-testid="imagePreview"
loading="lazy"
src="/api/v4/files/file_id_1/preview"
/>
</a>
</div>
`;
exports[`components/view_image/ImagePreview should match snapshot, with preview, cannot download 1`] = `
<img
src="/api/v4/files/file_id_1/preview"
/>
<div>
<img
src="/api/v4/files/file_id_1/preview"
/>
</div>
`;
exports[`components/view_image/ImagePreview should match snapshot, without preview 1`] = `
<a
className="image_preview"
href="#"
>
<img
alt="preview url image"
className="image_preview__image"
data-testid="imagePreview"
loading="lazy"
src="/api/v4/files/file_id?download=1"
/>
</a>
<div>
<a
class="image_preview"
href="#"
>
<img
alt="preview url image"
class="image_preview__image"
data-testid="imagePreview"
loading="lazy"
src="/api/v4/files/file_id?download=1"
/>
</a>
</div>
`;
exports[`components/view_image/ImagePreview should match snapshot, without preview, cannot download 1`] = `
<img
src="/api/v4/files/file_id?download=1"
/>
<div>
<img
src="/api/v4/files/file_id?download=1"
/>
</div>
`;

View file

@ -1,16 +1,46 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import FilePreviewModal from 'components/file_preview_modal/file_preview_modal';
import {act, render} from 'tests/react_testing_utils';
import Constants from 'utils/constants';
import {TestHelper} from 'utils/test_helper';
import * as Utils from 'utils/utils';
import {generateId} from 'utils/utils';
jest.mock('react-bootstrap', () => {
const Modal = ({children, show}: {children: React.ReactNode; show: boolean}) => (show ? <div>{children}</div> : null);
Modal.Header = ({children}: {children: React.ReactNode}) => <div>{children}</div>;
Modal.Body = ({children}: {children: React.ReactNode}) => <div>{children}</div>;
Modal.Title = ({children}: {children: React.ReactNode}) => <div>{children}</div>;
return {Modal};
});
jest.mock('components/archived_preview', () => () => <div>{'Archived Preview'}</div>);
jest.mock('components/audio_video_preview', () => () => <div>{'Audio Video Preview'}</div>);
jest.mock('components/code_preview', () => ({
__esModule: true,
default: () => <div>{'Code Preview'}</div>,
hasSupportedLanguage: () => true,
}));
jest.mock('components/file_info_preview', () => () => <div>{'File Info Preview'}</div>);
jest.mock('components/loading_image_preview', () => () => <div>{'Loading Image Preview'}</div>);
jest.mock('components/pdf_preview', () => ({
__esModule: true,
default: () => <div>{'PDF Preview'}</div>,
}));
jest.mock('components/file_preview_modal/file_preview_modal_footer/file_preview_modal_footer', () => () => (
<div>{'File Preview Modal Footer'}</div>
));
jest.mock('components/file_preview_modal/file_preview_modal_header/file_preview_modal_header', () => () => (
<div>{'File Preview Modal Header'}</div>
));
jest.mock('components/file_preview_modal/image_preview', () => () => <div>{'Image Preview'}</div>);
jest.mock('components/file_preview_modal/popover_bar', () => () => <div>{'Popover Bar'}</div>);
describe('components/FilePreviewModal', () => {
const baseProps = {
fileInfos: [TestHelper.getFileInfoMock({id: 'file_id', extension: 'jpg'})],
@ -22,53 +52,68 @@ describe('components/FilePreviewModal', () => {
onExited: jest.fn(),
};
test('should match snapshot', () => {
const wrapper = shallow(<FilePreviewModal {...baseProps}/>);
const renderModal = (props = baseProps) => {
const ref = React.createRef<FilePreviewModal>();
const utils = render(
<FilePreviewModal
ref={ref}
{...props}
/>,
);
return {ref, ...utils};
};
expect(wrapper).toMatchSnapshot();
test('should match snapshot', () => {
const {container} = renderModal();
expect(container).toMatchSnapshot();
});
test('should match snapshot, loaded with image', () => {
const wrapper = shallow(<FilePreviewModal {...baseProps}/>);
wrapper.setState({loaded: [true]});
expect(wrapper).toMatchSnapshot();
const {container, ref} = renderModal();
act(() => {
ref.current?.setState({loaded: [true] as any});
});
expect(container).toMatchSnapshot();
});
test('should match snapshot, loaded with .mov file', () => {
const fileInfos = [TestHelper.getFileInfoMock({id: 'file_id', extension: 'mov'})];
const props = {...baseProps, fileInfos};
const wrapper = shallow(<FilePreviewModal {...props}/>);
wrapper.setState({loaded: [true]});
expect(wrapper).toMatchSnapshot();
const {container, ref} = renderModal(props);
act(() => {
ref.current?.setState({loaded: [true] as any});
});
expect(container).toMatchSnapshot();
});
test('should match snapshot, loaded with .m4a file', () => {
const fileInfos = [TestHelper.getFileInfoMock({id: 'file_id', extension: 'm4a'})];
const props = {...baseProps, fileInfos};
const wrapper = shallow(<FilePreviewModal {...props}/>);
wrapper.setState({loaded: [true]});
expect(wrapper).toMatchSnapshot();
const {container, ref} = renderModal(props);
act(() => {
ref.current?.setState({loaded: [true] as any});
});
expect(container).toMatchSnapshot();
});
test('should match snapshot, loaded with .js file', () => {
const fileInfos = [TestHelper.getFileInfoMock({id: 'file_id', extension: 'js'})];
const props = {...baseProps, fileInfos};
const wrapper = shallow(<FilePreviewModal {...props}/>);
wrapper.setState({loaded: [true]});
expect(wrapper).toMatchSnapshot();
const {container, ref} = renderModal(props);
act(() => {
ref.current?.setState({loaded: [true] as any});
});
expect(container).toMatchSnapshot();
});
test('should match snapshot, loaded with other file', () => {
const fileInfos = [TestHelper.getFileInfoMock({id: 'file_id', extension: 'other'})];
const props = {...baseProps, fileInfos};
const wrapper = shallow(<FilePreviewModal {...props}/>);
wrapper.setState({loaded: [true]});
expect(wrapper).toMatchSnapshot();
const {container, ref} = renderModal(props);
act(() => {
ref.current?.setState({loaded: [true] as any});
});
expect(container).toMatchSnapshot();
});
test('should match snapshot, loaded with footer', () => {
@ -78,24 +123,27 @@ describe('components/FilePreviewModal', () => {
TestHelper.getFileInfoMock({id: 'file_id_3', extension: 'mp4'}),
];
const props = {...baseProps, fileInfos};
const wrapper = shallow(<FilePreviewModal {...props}/>);
wrapper.setState({loaded: [true, true, true]});
expect(wrapper).toMatchSnapshot();
const {container, ref} = renderModal(props);
act(() => {
ref.current?.setState({loaded: [true, true, true] as any});
});
expect(container).toMatchSnapshot();
});
test('should match snapshot, loaded', () => {
const wrapper = shallow(<FilePreviewModal {...baseProps}/>);
wrapper.setState({loaded: [true]});
expect(wrapper).toMatchSnapshot();
const {container, ref} = renderModal();
act(() => {
ref.current?.setState({loaded: [true] as any});
});
expect(container).toMatchSnapshot();
});
test('should match snapshot, loaded and showing footer', () => {
const wrapper = shallow(<FilePreviewModal {...baseProps}/>);
wrapper.setState({loaded: [true]});
expect(wrapper).toMatchSnapshot();
const {container, ref} = renderModal();
act(() => {
ref.current?.setState({loaded: [true] as any});
});
expect(container).toMatchSnapshot();
});
test('should go to next or previous upon key press of right or left, respectively', () => {
@ -105,43 +153,59 @@ describe('components/FilePreviewModal', () => {
TestHelper.getFileInfoMock({id: 'file_id_3', extension: 'mp4'}),
];
const props = {...baseProps, fileInfos};
const wrapper = shallow<FilePreviewModal>(<FilePreviewModal {...props}/>);
wrapper.setState({loaded: [true, true, true]});
const {ref} = renderModal(props);
act(() => {
ref.current?.setState({loaded: [true, true, true] as any});
});
let evt = {key: Constants.KeyCodes.RIGHT[0]} as KeyboardEvent;
wrapper.instance().handleKeyPress(evt);
expect(wrapper.state('imageIndex')).toBe(1);
wrapper.instance().handleKeyPress(evt);
expect(wrapper.state('imageIndex')).toBe(2);
act(() => {
ref.current?.handleKeyPress(evt);
});
expect(ref.current?.state.imageIndex).toBe(1);
act(() => {
ref.current?.handleKeyPress(evt);
});
expect(ref.current?.state.imageIndex).toBe(2);
evt = {key: Constants.KeyCodes.LEFT[0]} as KeyboardEvent;
wrapper.instance().handleKeyPress(evt);
expect(wrapper.state('imageIndex')).toBe(1);
wrapper.instance().handleKeyPress(evt);
expect(wrapper.state('imageIndex')).toBe(0);
act(() => {
ref.current?.handleKeyPress(evt);
});
expect(ref.current?.state.imageIndex).toBe(1);
act(() => {
ref.current?.handleKeyPress(evt);
});
expect(ref.current?.state.imageIndex).toBe(0);
});
test('should handle onMouseEnter and onMouseLeave', () => {
const wrapper = shallow<FilePreviewModal>(<FilePreviewModal {...baseProps}/>);
wrapper.setState({loaded: [true]});
const {ref} = renderModal();
act(() => {
ref.current?.setState({loaded: [true] as any});
});
wrapper.instance().onMouseEnterImage();
expect(wrapper.state('showCloseBtn')).toBe(true);
act(() => {
ref.current?.onMouseEnterImage();
});
expect(ref.current?.state.showCloseBtn).toBe(true);
wrapper.instance().onMouseLeaveImage();
expect(wrapper.state('showCloseBtn')).toBe(false);
act(() => {
ref.current?.onMouseLeaveImage();
});
expect(ref.current?.state.showCloseBtn).toBe(false);
});
test('should handle on modal close', () => {
const wrapper = shallow<FilePreviewModal>(<FilePreviewModal {...baseProps}/>);
wrapper.setState({
loaded: [true],
const {ref} = renderModal();
act(() => {
ref.current?.setState({loaded: [true] as any});
});
wrapper.instance().handleModalClose();
expect(wrapper.state('show')).toBe(false);
act(() => {
ref.current?.handleModalClose();
});
expect(ref.current?.state.show).toBe(false);
});
test('should match snapshot for external file', () => {
@ -149,24 +213,24 @@ describe('components/FilePreviewModal', () => {
TestHelper.getFileInfoMock({extension: 'png'}),
];
const props = {...baseProps, fileInfos};
const wrapper = shallow(<FilePreviewModal {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderModal(props);
expect(container).toMatchSnapshot();
});
test('should correctly identify image URLs with isImageUrl method', () => {
const wrapper = shallow<FilePreviewModal>(<FilePreviewModal {...baseProps}/>);
const {ref} = renderModal();
// Test proxied image URLs
expect(wrapper.instance().isImageUrl('http://localhost:8065/api/v4/image?url=https%3A%2F%2Fexample.com%2Fimage.jpg')).toBe(true);
expect(ref.current?.isImageUrl('http://localhost:8065/api/v4/image?url=https%3A%2F%2Fexample.com%2Fimage.jpg')).toBe(true);
// Test URLs with image extensions
expect(wrapper.instance().isImageUrl('https://example.com/image.jpg')).toBe(true);
expect(wrapper.instance().isImageUrl('https://example.com/image.png')).toBe(true);
expect(wrapper.instance().isImageUrl('https://example.com/image.gif')).toBe(true);
expect(ref.current?.isImageUrl('https://example.com/image.jpg')).toBe(true);
expect(ref.current?.isImageUrl('https://example.com/image.png')).toBe(true);
expect(ref.current?.isImageUrl('https://example.com/image.gif')).toBe(true);
// Test non-image URLs
expect(wrapper.instance().isImageUrl('https://example.com/document.pdf')).toBe(false);
expect(wrapper.instance().isImageUrl('https://example.com/file.txt')).toBe(false);
expect(ref.current?.isImageUrl('https://example.com/document.pdf')).toBe(false);
expect(ref.current?.isImageUrl('https://example.com/file.txt')).toBe(false);
});
test('should handle external image URLs correctly', () => {
@ -183,21 +247,24 @@ describe('components/FilePreviewModal', () => {
// Create a LinkInfo object for an external image URL
const externalImageUrl = 'http://localhost:8065/api/v4/image?url=https%3A%2F%2Fexample.com%2Fimage.jpg';
const fileInfos = [{
has_preview_image: false,
link: externalImageUrl,
extension: '',
name: 'External Image',
}];
const fileInfos = [
TestHelper.getFileInfoMock({
id: '',
has_preview_image: false,
link: externalImageUrl,
extension: '',
name: 'External Image',
}),
];
const props = {...baseProps, fileInfos};
const wrapper = shallow<FilePreviewModal>(<FilePreviewModal {...props}/>);
const {ref} = renderModal(props);
// Spy on handleImageLoaded
const handleImageLoadedSpy = jest.spyOn(wrapper.instance(), 'handleImageLoaded');
const handleImageLoadedSpy = jest.spyOn(ref.current as FilePreviewModal, 'handleImageLoaded');
// Call loadImage with the external image URL
wrapper.instance().loadImage(0);
act(() => {
ref.current?.loadImage(0);
});
// Verify that Utils.loadImage was called with the correct URL
expect(loadImageSpy).toHaveBeenCalledWith(
@ -220,17 +287,21 @@ describe('components/FilePreviewModal', () => {
TestHelper.getFileInfoMock({id: 'file_id_3', extension: 'mp4'}),
];
const props = {...baseProps, fileInfos};
const wrapper = shallow<FilePreviewModal>(<FilePreviewModal {...props}/>);
const {ref} = renderModal(props);
let index = 1;
wrapper.setState({loaded: [true, false, false]});
wrapper.instance().loadImage(index);
act(() => {
ref.current?.setState({loaded: [true, false, false] as any});
ref.current?.loadImage(index);
});
expect(wrapper.state('loaded')[index]).toBe(true);
expect(ref.current?.state.loaded[index]).toBe(true);
index = 2;
wrapper.instance().loadImage(index);
expect(wrapper.state('loaded')[index]).toBe(true);
act(() => {
ref.current?.loadImage(index);
});
expect(ref.current?.state.loaded[index]).toBe(true);
});
test('should handle handleImageLoaded', () => {
@ -240,17 +311,21 @@ describe('components/FilePreviewModal', () => {
TestHelper.getFileInfoMock({id: 'file_id_3', extension: 'mp4'}),
];
const props = {...baseProps, fileInfos};
const wrapper = shallow<FilePreviewModal>(<FilePreviewModal {...props}/>);
const {ref} = renderModal(props);
let index = 1;
wrapper.setState({loaded: [true, false, false]});
wrapper.instance().handleImageLoaded(index);
act(() => {
ref.current?.setState({loaded: [true, false, false] as any});
ref.current?.handleImageLoaded(index);
});
expect(wrapper.state('loaded')[index]).toBe(true);
expect(ref.current?.state.loaded[index]).toBe(true);
index = 2;
wrapper.instance().handleImageLoaded(index);
expect(wrapper.state('loaded')[index]).toBe(true);
act(() => {
ref.current?.handleImageLoaded(index);
});
expect(ref.current?.state.loaded[index]).toBe(true);
});
test('should handle handleImageProgress', () => {
@ -260,36 +335,46 @@ describe('components/FilePreviewModal', () => {
TestHelper.getFileInfoMock({id: 'file_id_3', extension: 'mp4'}),
];
const props = {...baseProps, fileInfos};
const wrapper = shallow<FilePreviewModal>(<FilePreviewModal {...props}/>);
const {ref} = renderModal(props);
const index = 1;
let completedPercentage = 30;
wrapper.setState({loaded: [true, false, false]});
wrapper.instance().handleImageProgress(index, completedPercentage);
act(() => {
ref.current?.setState({loaded: [true, false, false] as any});
ref.current?.handleImageProgress(index, completedPercentage);
});
expect(wrapper.state('progress')[index]).toBe(completedPercentage);
expect(ref.current?.state.progress[index]).toBe(completedPercentage);
completedPercentage = 70;
wrapper.instance().handleImageProgress(index, completedPercentage);
act(() => {
ref.current?.handleImageProgress(index, completedPercentage);
});
expect(wrapper.state('progress')[index]).toBe(completedPercentage);
expect(ref.current?.state.progress[index]).toBe(completedPercentage);
});
test('should pass componentWillReceiveProps', () => {
const wrapper = shallow<FilePreviewModal>(<FilePreviewModal {...baseProps}/>);
const {ref, rerender} = renderModal();
expect(Object.keys(wrapper.state('loaded')).length).toBe(1);
expect(Object.keys(wrapper.state('progress')).length).toBe(1);
expect(Object.keys(ref.current?.state.loaded || {})).toHaveLength(1);
expect(Object.keys(ref.current?.state.progress || {})).toHaveLength(1);
wrapper.setProps({
fileInfos: [
TestHelper.getFileInfoMock({id: 'file_id_1', extension: 'gif'}),
TestHelper.getFileInfoMock({id: 'file_id_2', extension: 'wma'}),
TestHelper.getFileInfoMock({id: 'file_id_3', extension: 'mp4'}),
],
act(() => {
rerender(
<FilePreviewModal
{...baseProps}
ref={ref}
fileInfos={[
TestHelper.getFileInfoMock({id: 'file_id_1', extension: 'gif'}),
TestHelper.getFileInfoMock({id: 'file_id_2', extension: 'wma'}),
TestHelper.getFileInfoMock({id: 'file_id_3', extension: 'mp4'}),
]}
/>,
);
});
expect(Object.keys(wrapper.state('loaded')).length).toBe(3);
expect(Object.keys(wrapper.state('progress')).length).toBe(3);
expect(Object.keys(ref.current?.state.loaded || {})).toHaveLength(3);
expect(Object.keys(ref.current?.state.progress || {})).toHaveLength(3);
});
test('should match snapshot when plugin overrides the preview component', () => {
@ -300,9 +385,8 @@ describe('components/FilePreviewModal', () => {
component: () => <div>{'Preview'}</div>,
}];
const props = {...baseProps, pluginFilePreviewComponents};
const wrapper = shallow(<FilePreviewModal {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderModal(props);
expect(container).toMatchSnapshot();
});
test('should fall back to default preview if plugin does not need to override preview component', () => {
@ -313,8 +397,7 @@ describe('components/FilePreviewModal', () => {
component: () => <div>{'Preview'}</div>,
}];
const props = {...baseProps, pluginFilePreviewComponents};
const wrapper = shallow(<FilePreviewModal {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderModal(props);
expect(container).toMatchSnapshot();
});
});

View file

@ -1,149 +1,31 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/file_preview_modal/file_preview_modal_footer/FilePreviewModalFooter should match snapshot the desktop view 1`] = `
<div
className="file-preview-modal-footer"
>
<Memo(FilePreviewModalInfo)
filename="img.png"
post={
Object {
"channel_id": "",
"create_at": 0,
"delete_at": 0,
"edit_at": 0,
"hashtags": "",
"id": "id",
"is_pinned": false,
"message": "post message",
"metadata": Object {
"embeds": Array [],
"emojis": Array [],
"files": Array [],
"images": Object {},
"reactions": Array [],
},
"original_id": "",
"pending_post_id": "",
"props": Object {},
"reply_count": 0,
"root_id": "",
"type": "system_add_remove",
"update_at": 0,
"user_id": "user_id",
}
}
showFileName={false}
/>
<Memo(FilePreviewModalMainActions)
canCopyContent={false}
canDownloadFiles={true}
content=""
enablePublicLink={false}
fileIndex={1}
fileInfo={
Object {
"archived": false,
"channel_id": "channel_id",
"clientId": "client_id",
"create_at": 1,
"delete_at": 1,
"extension": "jpg",
"has_preview_image": true,
"height": 200,
"id": "file_info_id",
"mime_type": "mime_type",
"name": "name",
"size": 1,
"update_at": 1,
"user_id": "user_id",
"width": 350,
}
}
fileURL="https://example.com/img.png"
filename="img.png"
handleModalClose={[MockFunction]}
isExternalFile={false}
isMobile={false}
onGetPublicLink={[MockFunction]}
showClose={false}
showOnlyClose={false}
showPublicLink={false}
totalFiles={3}
/>
<div>
<div
class="file-preview-modal-footer"
>
<div>
FilePreviewModalInfo
</div>
<div>
FilePreviewModalMainActions
</div>
</div>
</div>
`;
exports[`components/file_preview_modal/file_preview_modal_footer/FilePreviewModalFooter should match snapshot the mobile view 1`] = `
<div
className="file-preview-modal-footer"
>
<Memo(FilePreviewModalInfo)
filename="img.png"
post={
Object {
"channel_id": "",
"create_at": 0,
"delete_at": 0,
"edit_at": 0,
"hashtags": "",
"id": "id",
"is_pinned": false,
"message": "post message",
"metadata": Object {
"embeds": Array [],
"emojis": Array [],
"files": Array [],
"images": Object {},
"reactions": Array [],
},
"original_id": "",
"pending_post_id": "",
"props": Object {},
"reply_count": 0,
"root_id": "",
"type": "system_add_remove",
"update_at": 0,
"user_id": "user_id",
}
}
showFileName={false}
/>
<Memo(FilePreviewModalMainActions)
canCopyContent={false}
canDownloadFiles={true}
content=""
enablePublicLink={false}
fileIndex={1}
fileInfo={
Object {
"archived": false,
"channel_id": "channel_id",
"clientId": "client_id",
"create_at": 1,
"delete_at": 1,
"extension": "jpg",
"has_preview_image": true,
"height": 200,
"id": "file_info_id",
"mime_type": "mime_type",
"name": "name",
"size": 1,
"update_at": 1,
"user_id": "user_id",
"width": 350,
}
}
fileURL="https://example.com/img.png"
filename="img.png"
handleModalClose={[MockFunction]}
isExternalFile={false}
isMobile={true}
onGetPublicLink={[MockFunction]}
showClose={false}
showOnlyClose={false}
showPublicLink={false}
totalFiles={3}
/>
<div>
<div
class="file-preview-modal-footer"
>
<div>
FilePreviewModalInfo
</div>
<div>
FilePreviewModalMainActions
</div>
</div>
</div>
`;

View file

@ -1,13 +1,18 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {renderWithContext} from 'tests/react_testing_utils';
import {TestHelper} from 'utils/test_helper';
import FilePreviewModalFooter from './file_preview_modal_footer';
jest.mock('../file_preview_modal_info/file_preview_modal_info', () => () => <div>{'FilePreviewModalInfo'}</div>);
jest.mock('../file_preview_modal_main_actions/file_preview_modal_main_actions', () => () => (
<div>{'FilePreviewModalMainActions'}</div>
));
describe('components/file_preview_modal/file_preview_modal_footer/FilePreviewModalFooter', () => {
const defaultProps = {
enablePublicLink: false,
@ -32,8 +37,8 @@ describe('components/file_preview_modal/file_preview_modal_footer/FilePreviewMod
...defaultProps,
};
const wrapper = shallow(<FilePreviewModalFooter {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<FilePreviewModalFooter {...props}/>);
expect(container).toMatchSnapshot();
});
test('should match snapshot the mobile view', () => {
@ -42,7 +47,7 @@ describe('components/file_preview_modal/file_preview_modal_footer/FilePreviewMod
isMobile: true,
};
const wrapper = shallow(<FilePreviewModalFooter {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<FilePreviewModalFooter {...props}/>);
expect(container).toMatchSnapshot();
});
});

View file

@ -1,102 +1,148 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/file_preview_modal/file_preview_modal_header/FilePreviewModalHeader should match snapshot the desktop view 1`] = `
<div
className="file-preview-modal-header"
>
<Memo(FilePreviewModalInfo)
filename="img.png"
post={Object {}}
showFileName={true}
/>
<Memo(FilePreviewModalMainNav)
fileIndex={1}
handleNext={[MockFunction]}
handlePrev={[MockFunction]}
totalFiles={3}
/>
<Memo(FilePreviewModalMainActions)
canCopyContent={true}
canDownloadFiles={true}
content=""
enablePublicLink={false}
fileInfo={
Object {
"archived": false,
"channel_id": "channel_id",
"clientId": "client_id",
"create_at": 1,
"delete_at": 1,
"extension": "jpg",
"has_preview_image": true,
"height": 200,
"id": "file_info_id",
"mime_type": "mime_type",
"name": "name",
"size": 1,
"update_at": 1,
"user_id": "user_id",
"width": 350,
}
}
fileURL="http://example.com/img.png"
filename="img.png"
handleModalClose={[MockFunction]}
handleNext={[MockFunction]}
handlePrev={[MockFunction]}
isExternalFile={false}
isMobileView={false}
onGetPublicLink={[MockFunction]}
showOnlyClose={false}
showPublicLink={false}
/>
<div>
<div
class="file-preview-modal-header"
>
<div
class="file-preview-modal__info"
>
<div
class="file-preview-modal__info-details"
>
<h5
class="file-preview-modal__file-name"
>
img.png
</h5>
<span
class="file-preview-modal__file-details"
>
<span
class="file-preview-modal__file-details-user-name"
>
Someone
</span>
<span
class="file-preview-modal__channel"
/>
</span>
</div>
</div>
<div
class="file_preview_modal_main_nav"
>
<button
aria-label="Previous file"
class="file_preview_modal_main_nav__prev"
id="previewArrowLeft"
>
<i
class="icon icon-chevron-left"
/>
</button>
<span
aria-atomic="true"
aria-live="polite"
class="modal-bar-file-count"
>
2 of 3
</span>
<button
aria-label="Next file"
class="file_preview_modal_main_nav__next"
id="previewArrowRight"
>
<i
class="icon icon-chevron-right"
/>
</button>
</div>
<div
class="file-preview-modal-main-actions__actions"
>
<span
aria-label="Copy code"
class="post-code__clipboard file-preview-modal-main-actions__action-item"
role="button"
>
<i
class="icon icon-content-copy"
/>
</span>
<a
aria-label="Download"
class="file-preview-modal-main-actions__action-item"
download="img.png"
href="http://example.com/img.png"
location="file_preview_modal_main_actions"
rel="noopener noreferrer"
target="_blank"
>
<i
class="icon icon-download-outline"
/>
</a>
<button
aria-label="Close"
class="file-preview-modal-main-actions__action-item"
>
<i
class="icon icon-close"
/>
</button>
</div>
</div>
</div>
`;
exports[`components/file_preview_modal/file_preview_modal_header/FilePreviewModalHeader should match snapshot the mobile view 1`] = `
<div
className="file-preview-modal-header"
>
<Memo(FilePreviewModalMainActions)
canCopyContent={true}
canDownloadFiles={true}
content=""
enablePublicLink={false}
fileInfo={
Object {
"archived": false,
"channel_id": "channel_id",
"clientId": "client_id",
"create_at": 1,
"delete_at": 1,
"extension": "jpg",
"has_preview_image": true,
"height": 200,
"id": "file_info_id",
"mime_type": "mime_type",
"name": "name",
"size": 1,
"update_at": 1,
"user_id": "user_id",
"width": 350,
}
}
fileURL="http://example.com/img.png"
filename="img.png"
handleModalClose={[MockFunction]}
handleNext={[MockFunction]}
handlePrev={[MockFunction]}
isExternalFile={false}
isMobileView={true}
onGetPublicLink={[MockFunction]}
showOnlyClose={true}
showPublicLink={false}
/>
<Memo(FilePreviewModalMainNav)
fileIndex={1}
handleNext={[MockFunction]}
handlePrev={[MockFunction]}
totalFiles={3}
/>
<div>
<div
class="file-preview-modal-header"
>
<div
class="file-preview-modal-main-actions__actions"
>
<button
aria-label="Close"
class="file-preview-modal-main-actions__action-item"
>
<i
class="icon icon-close"
/>
</button>
</div>
<div
class="file_preview_modal_main_nav"
>
<button
aria-label="Previous file"
class="file_preview_modal_main_nav__prev"
id="previewArrowLeft"
>
<i
class="icon icon-chevron-left"
/>
</button>
<span
aria-atomic="true"
aria-live="polite"
class="modal-bar-file-count"
>
2 of 3
</span>
<button
aria-label="Next file"
class="file_preview_modal_main_nav__next"
id="previewArrowRight"
>
<i
class="icon icon-chevron-right"
/>
</button>
</div>
</div>
</div>
`;

View file

@ -1,11 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import type {Post} from '@mattermost/types/posts';
import {renderWithContext} from 'tests/react_testing_utils';
import {TestHelper} from 'utils/test_helper';
import FilePreviewModalHeader from './file_preview_modal_header';
@ -36,8 +36,8 @@ describe('components/file_preview_modal/file_preview_modal_header/FilePreviewMod
...defaultProps,
};
const wrapper = shallow(<FilePreviewModalHeader {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<FilePreviewModalHeader {...props}/>);
expect(container).toMatchSnapshot();
});
test('should match snapshot the mobile view', () => {
@ -46,7 +46,7 @@ describe('components/file_preview_modal/file_preview_modal_header/FilePreviewMod
isMobileView: true,
};
const wrapper = shallow(<FilePreviewModalHeader {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<FilePreviewModalHeader {...props}/>);
expect(container).toMatchSnapshot();
});
});

View file

@ -1,56 +1,51 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/FilePreviewModalInfo should match snapshot 1`] = `
<div
className="file-preview-modal__info"
>
<Memo(Avatar)
alt=""
className="file-preview-modal__avatar"
size="lg"
url="/api/v4/users/user_id/image?_=0"
username="some-user"
/>
<div>
<div
className="file-preview-modal__info-details"
class="file-preview-modal__info"
>
<h5
className="file-preview-modal__user-name"
<img
alt=""
class="Avatar Avatar-lg file-preview-modal__avatar"
loading="lazy"
src="/api/v4/users/user_id/image?_=0"
/>
<div
class="file-preview-modal__info-details"
>
some-user
</h5>
<span
className="file-preview-modal__channel"
>
<MemoizedFormattedMessage
defaultMessage="Shared in ~{name}"
id="file_preview_modal_info.shared_in"
values={
Object {
"name": "name",
}
}
/>
</span>
<h5
class="file-preview-modal__user-name"
>
some-user
</h5>
<span
class="file-preview-modal__channel"
>
Shared in ~name
</span>
</div>
</div>
</div>
`;
exports[`components/FilePreviewModalInfo should match snapshot where post is missing and avoid crash 1`] = `
<div
className="file-preview-modal__info"
>
<div>
<div
className="file-preview-modal__info-details"
class="file-preview-modal__info"
>
<h5
className="file-preview-modal__user-name"
<div
class="file-preview-modal__info-details"
>
Someone
</h5>
<span
className="file-preview-modal__channel"
/>
<h5
class="file-preview-modal__user-name"
>
Someone
</h5>
<span
class="file-preview-modal__channel"
/>
</div>
</div>
</div>
`;

View file

@ -1,10 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import type {ComponentProps} from 'react';
import {renderWithContext} from 'tests/react_testing_utils';
import {TestHelper} from 'utils/test_helper';
import type {GlobalState} from 'types/store';
@ -45,23 +45,23 @@ describe('components/FilePreviewModalInfo', () => {
test('should match snapshot', () => {
mockState.entities.users.profiles = {user_id: mockedUser};
mockState.entities.channels.channels = {channel_id: mockedChannel};
const wrapper = shallow<typeof FilePreviewModalInfo>(
const {container} = renderWithContext(
<FilePreviewModalInfo
{...baseProps}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot where post is missing and avoid crash', () => {
mockState.entities.users.profiles = {user_id: mockedUser};
mockState.entities.channels.channels = {channel_id: mockedChannel};
baseProps.post = undefined;
const wrapper = shallow<typeof FilePreviewModalInfo>(
const {container} = renderWithContext(
<FilePreviewModalInfo
{...baseProps}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
});

View file

@ -1,64 +1,35 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/file_preview_modal/file_preview_modal_main_nav/FilePreviewModalMainNav should match snapshot with multiple files 1`] = `
<div
className="file_preview_modal_main_nav"
>
<WithTooltip
key="previewArrowLeft"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Close"
id="generic.close"
/>
}
<div>
<div
class="file_preview_modal_main_nav"
>
<button
aria-label="Previous file"
className="file_preview_modal_main_nav__prev"
class="file_preview_modal_main_nav__prev"
id="previewArrowLeft"
onClick={[MockFunction]}
>
<i
className="icon icon-chevron-left"
class="icon icon-chevron-left"
/>
</button>
</WithTooltip>
<span
aria-atomic="true"
aria-live="polite"
className="modal-bar-file-count"
>
<MemoizedFormattedMessage
defaultMessage="{count, number} of {total, number}"
id="file_preview_modal_main_nav.file"
values={
Object {
"count": 2,
"total": 2,
}
}
/>
</span>
<WithTooltip
key="publicLink"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Next"
id="generic.next"
/>
}
>
<span
aria-atomic="true"
aria-live="polite"
class="modal-bar-file-count"
>
2 of 2
</span>
<button
aria-label="Next file"
className="file_preview_modal_main_nav__next"
class="file_preview_modal_main_nav__next"
id="previewArrowRight"
onClick={[MockFunction]}
>
<i
className="icon icon-chevron-right"
class="icon icon-chevron-right"
/>
</button>
</WithTooltip>
</div>
</div>
`;

View file

@ -1,9 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {renderWithContext} from 'tests/react_testing_utils';
import FilePreviewModalMainNav from './file_preview_modal_main_nav';
describe('components/file_preview_modal/file_preview_modal_main_nav/FilePreviewModalMainNav', () => {
@ -20,7 +21,7 @@ describe('components/file_preview_modal/file_preview_modal_main_nav/FilePreviewM
enablePublicLink: false,
};
const wrapper = shallow(<FilePreviewModalMainNav {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<FilePreviewModalMainNav {...props}/>);
expect(container).toMatchSnapshot();
});
});

View file

@ -1,11 +1,11 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import ImagePreview from 'components/file_preview_modal/image_preview';
import {render, screen} from 'tests/react_testing_utils';
import {TestHelper} from 'utils/test_helper';
describe('components/view_image/ImagePreview', () => {
@ -16,11 +16,11 @@ describe('components/view_image/ImagePreview', () => {
};
test('should match snapshot, without preview', () => {
const wrapper = shallow(
const {container} = render(
<ImagePreview {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot, with preview', () => {
@ -33,11 +33,11 @@ describe('components/view_image/ImagePreview', () => {
},
};
const wrapper = shallow(
const {container} = render(
<ImagePreview {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot, without preview, cannot download', () => {
@ -46,11 +46,11 @@ describe('components/view_image/ImagePreview', () => {
canDownloadFiles: false,
};
const wrapper = shallow(
const {container} = render(
<ImagePreview {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot, with preview, cannot download', () => {
@ -64,11 +64,11 @@ describe('components/view_image/ImagePreview', () => {
},
};
const wrapper = shallow(
const {container} = render(
<ImagePreview {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should not download link for external file', () => {
@ -82,11 +82,11 @@ describe('components/view_image/ImagePreview', () => {
},
};
const wrapper = shallow(
render(
<ImagePreview {...props}/>,
);
expect(wrapper.find('a').prop('href')).toBe('#');
expect(wrapper.find('img').prop('src')).toBe(props.fileInfo.link);
expect(screen.getByRole('link')).toHaveAttribute('href', '#');
expect(screen.getByTestId('imagePreview')).toHaveAttribute('src', props.fileInfo.link);
});
});

View file

@ -1,64 +1,36 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/file_preview_modal/popover_bar/PopoverBar should match snapshot with zoom controls enabled 1`] = `
<div
className="modal-button-bar file-preview-modal__zoom-bar"
data-testid="fileCountFooter"
>
<div>
<div
className="modal-column"
class="modal-button-bar file-preview-modal__zoom-bar"
data-testid="fileCountFooter"
>
<WithTooltip
key="zoomOut"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Zoom Out"
id="view_image.zoom_out"
/>
}
<div
class="modal-column"
>
<span
className="btn-inactive"
class="btn-inactive"
>
<i
className="icon icon-minus"
class="icon icon-minus"
/>
</span>
</WithTooltip>
<WithTooltip
key="zoomReset"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Reset Zoom"
id="view_image.zoom_reset"
/>
}
>
<span
className="btn-inactive"
class="btn-inactive"
>
<i
className="icon icon-magnify-minus"
class="icon icon-magnify-minus"
/>
</span>
</WithTooltip>
<WithTooltip
key="zoomIn"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Zoom In"
id="view_image.zoom_in"
/>
}
>
<span
className="btn-inactive"
class="btn-inactive"
>
<i
className="icon icon-plus"
class="icon icon-plus"
/>
</span>
</WithTooltip>
</div>
</div>
</div>
`;

View file

@ -1,11 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import PopoverBar from 'components/file_preview_modal/popover_bar/popover_bar';
import {render} from 'tests/react_testing_utils';
describe('components/file_preview_modal/popover_bar/PopoverBar', () => {
const defaultProps = {
showZoomControls: false,
@ -17,7 +18,7 @@ describe('components/file_preview_modal/popover_bar/PopoverBar', () => {
showZoomControls: true,
};
const wrapper = shallow(<PopoverBar {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = render(<PopoverBar {...props}/>);
expect(container).toMatchSnapshot();
});
});

View file

@ -1,272 +1,150 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/post_view/PostAddChannelMember should match snapshot, empty channelType 1`] = `""`;
exports[`components/post_view/PostAddChannelMember should match snapshot, empty channelType 1`] = `<div />`;
exports[`components/post_view/PostAddChannelMember should match snapshot, empty postId 1`] = `""`;
exports[`components/post_view/PostAddChannelMember should match snapshot, empty postId 1`] = `<div />`;
exports[`components/post_view/PostAddChannelMember should match snapshot, more than 3 users 1`] = `
<Fragment>
<p
key="invitable"
>
<div>
<p>
<span>
<Connect(Component)
channelId="channel_id"
key="username_1"
mentionName="username_1"
/>
<span
key="1"
>
,
<span>
@username_1
</span>
<a
className="PostBody_otherUsersLink"
onClick={[Function]}
class="PostBody_otherUsersLink"
>
<MemoizedFormattedMessage
defaultMessage="{numOthers} others"
id="post_body.check_for_out_of_channel_mentions.others"
values={
Object {
"numOthers": 2,
}
}
/>
2 others
</a>
<MemoizedFormattedMessage
defaultMessage=" and "
id="post_body.check_for_out_of_channel_mentions.link.and"
key="1"
/>
<Connect(Component)
channelId="channel_id"
key="username_4"
mentionName="username_4"
/>
<span>
@username_4
</span>
</span>
<MemoizedFormattedMessage
defaultMessage="did not get notified by this mention because they are not in the channel. Would you like to "
id="post_body.check_for_out_of_channel_mentions.message.multiple"
/>
did not get notified by this mention because they are not in the channel. Would you like to
<a
className="PostBody_addChannelMemberLink"
onClick={[Function]}
class="PostBody_addChannelMemberLink"
>
<MemoizedFormattedMessage
defaultMessage="add them to the channel"
id="post_body.check_for_out_of_channel_mentions.link.public"
/>
add them to the channel
</a>
<MemoizedFormattedMessage
defaultMessage="? They will have access to all message history."
id="post_body.check_for_out_of_channel_mentions.message_last"
/>
? They will have access to all message history.
</p>
</Fragment>
</div>
`;
exports[`components/post_view/PostAddChannelMember should match snapshot, more than 3 users 2`] = `
<Fragment>
<p
key="invitable"
>
<div>
<p>
<span>
<Connect(Component)
channelId="channel_id"
key="username_1"
mentionName="username_1"
/>
<span
key="1"
>
,
<span>
@username_1
</span>
<Connect(Component)
channelId="channel_id"
key="username_2"
mentionName="username_2"
/>
<span
key="2"
<a
class="PostBody_otherUsersLink"
>
,
2 others
</a>
<span>
@username_4
</span>
<Connect(Component)
channelId="channel_id"
key="username_3"
mentionName="username_3"
/>
<MemoizedFormattedMessage
defaultMessage=" and "
id="post_body.check_for_out_of_channel_mentions.link.and"
key="3"
/>
<Connect(Component)
channelId="channel_id"
key="username_4"
mentionName="username_4"
/>
</span>
<MemoizedFormattedMessage
defaultMessage="did not get notified by this mention because they are not in the channel. Would you like to "
id="post_body.check_for_out_of_channel_mentions.message.multiple"
/>
did not get notified by this mention because they are not in the channel. Would you like to
<a
className="PostBody_addChannelMemberLink"
onClick={[Function]}
class="PostBody_addChannelMemberLink"
>
<MemoizedFormattedMessage
defaultMessage="add them to the channel"
id="post_body.check_for_out_of_channel_mentions.link.public"
/>
add them to the channel
</a>
<MemoizedFormattedMessage
defaultMessage="? They will have access to all message history."
id="post_body.check_for_out_of_channel_mentions.message_last"
/>
? They will have access to all message history.
</p>
</Fragment>
</div>
`;
exports[`components/post_view/PostAddChannelMember should match snapshot, private channel 1`] = `
<Fragment>
<p
key="invitable"
>
<Connect(Component)
channelId="channel_id"
mentionName="username_1"
/>
<div>
<p>
<span>
<span>
@username_1
</span>
</span>
<MemoizedFormattedMessage
defaultMessage="did not get notified by this mention because they are not in the channel. Would you like to "
id="post_body.check_for_out_of_channel_mentions.message.one"
/>
did not get notified by this mention because they are not in the channel. Would you like to
<a
className="PostBody_addChannelMemberLink"
onClick={[Function]}
class="PostBody_addChannelMemberLink"
>
<MemoizedFormattedMessage
defaultMessage="add them to this private channel"
id="post_body.check_for_out_of_channel_mentions.link.private"
/>
add them to this private channel
</a>
<MemoizedFormattedMessage
defaultMessage="? They will have access to all message history."
id="post_body.check_for_out_of_channel_mentions.message_last"
/>
? They will have access to all message history.
</p>
</Fragment>
</div>
`;
exports[`components/post_view/PostAddChannelMember should match snapshot, public channel 1`] = `
<Fragment>
<p
key="invitable"
>
<Connect(Component)
channelId="channel_id"
mentionName="username_1"
/>
<div>
<p>
<span>
<span>
@username_1
</span>
</span>
<MemoizedFormattedMessage
defaultMessage="did not get notified by this mention because they are not in the channel. Would you like to "
id="post_body.check_for_out_of_channel_mentions.message.one"
/>
did not get notified by this mention because they are not in the channel. Would you like to
<a
className="PostBody_addChannelMemberLink"
onClick={[Function]}
class="PostBody_addChannelMemberLink"
>
<MemoizedFormattedMessage
defaultMessage="add them to the channel"
id="post_body.check_for_out_of_channel_mentions.link.public"
/>
add them to the channel
</a>
<MemoizedFormattedMessage
defaultMessage="? They will have access to all message history."
id="post_body.check_for_out_of_channel_mentions.message_last"
/>
? They will have access to all message history.
</p>
</Fragment>
</div>
`;
exports[`components/post_view/PostAddChannelMember should match snapshot, with ABAC policy enforced 1`] = `
<p>
<span>
<Connect(Component)
channelId="channel_id"
key="username_1"
mentionName="username_1"
/>
<span
key="1"
>
,
<div>
<p>
<span>
<span>
@username_1
</span>
<span>
@username_2
</span>
<span>
@username_3
</span>
</span>
<Connect(Component)
channelId="channel_id"
key="username_2"
mentionName="username_2"
/>
<MemoizedFormattedMessage
defaultMessage=" and "
id="post_body.check_for_out_of_channel_mentions.link.and"
key="2"
/>
<Connect(Component)
channelId="channel_id"
key="username_3"
mentionName="username_3"
/>
</span>
did not get notified by this mention because they are not in the channel.
</p>
did not get notified by this mention because they are not in the channel.
</p>
</div>
`;
exports[`components/post_view/PostAddChannelMember should match snapshot, with no-groups usernames 1`] = `
<Fragment>
<p
key="invitable"
>
<Connect(Component)
channelId="channel_id"
mentionName="username_1"
/>
<div>
<p>
<span>
<span>
@username_1
</span>
</span>
<MemoizedFormattedMessage
defaultMessage="did not get notified by this mention because they are not in the channel. Would you like to "
id="post_body.check_for_out_of_channel_mentions.message.one"
/>
did not get notified by this mention because they are not in the channel. Would you like to
<a
className="PostBody_addChannelMemberLink"
onClick={[Function]}
class="PostBody_addChannelMemberLink"
>
<MemoizedFormattedMessage
defaultMessage="add them to the channel"
id="post_body.check_for_out_of_channel_mentions.link.public"
/>
add them to the channel
</a>
<MemoizedFormattedMessage
defaultMessage="? They will have access to all message history."
id="post_body.check_for_out_of_channel_mentions.message_last"
/>
? They will have access to all message history.
</p>
<p
key="out-of-groups"
>
<Connect(Component)
channelId="channel_id"
mentionName="user_id_2"
/>
<p>
<span>
<span>
@user_id_2
</span>
</span>
<MemoizedFormattedMessage
defaultMessage="did not get notified by this mention because they are not in the channel. They cannot be added to the channel because they are not a member of the linked groups. To add them to this channel, they must be added to the linked groups."
id="post_body.check_for_out_of_channel_groups_mentions.message"
/>
did not get notified by this mention because they are not in the channel. They cannot be added to the channel because they are not a member of the linked groups. To add them to this channel, they must be added to the linked groups.
</p>
</Fragment>
</div>
`;

View file

@ -1,7 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import type {Post} from '@mattermost/types/posts';
@ -12,6 +11,7 @@ import {sendAddToChannelEphemeralPost} from 'actions/global_actions';
import PostAddChannelMember from 'components/post_view/post_add_channel_member/post_add_channel_member';
import type {Props} from 'components/post_view/post_add_channel_member/post_add_channel_member';
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
import {TestHelper} from 'utils/test_helper';
jest.mock('actions/global_actions', () => {
@ -20,7 +20,12 @@ jest.mock('actions/global_actions', () => {
};
});
jest.mock('components/at_mention', () => ({mentionName}: {mentionName: string}) => (
<span>{`@${mentionName}`}</span>
));
describe('components/post_view/PostAddChannelMember', () => {
let generateMentionsSpy: jest.SpyInstance;
const post: Post = TestHelper.getPostMock({
id: 'post_id_1',
root_id: 'root_id',
@ -46,13 +51,40 @@ describe('components/post_view/PostAddChannelMember', () => {
isPolicyEnforced: false,
};
beforeEach(() => {
generateMentionsSpy = jest.spyOn(PostAddChannelMember.prototype, 'generateAtMentions').mockImplementation((usernames = []) => {
if (usernames.length > 3) {
const otherUsersCount = usernames.length - 2;
return (
<span>
<span>{`@${usernames[0]}`}</span>
<a className='PostBody_otherUsersLink'>{`${otherUsersCount} others`}</a>
<span>{`@${usernames[usernames.length - 1]}`}</span>
</span>
);
}
return (
<span>
{usernames.map((username) => (
<span key={username}>{`@${username}`}</span>
))}
</span>
);
});
});
afterEach(() => {
generateMentionsSpy.mockRestore();
});
test('should match snapshot, empty postId', () => {
const props: Props = {
...requiredProps,
postId: '',
};
const wrapper = shallow(<PostAddChannelMember {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<PostAddChannelMember {...props}/>);
expect(container).toMatchSnapshot();
});
test('should match snapshot, empty channelType', () => {
@ -60,13 +92,13 @@ describe('components/post_view/PostAddChannelMember', () => {
...requiredProps,
channelType: '',
};
const wrapper = shallow(<PostAddChannelMember {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<PostAddChannelMember {...props}/>);
expect(container).toMatchSnapshot();
});
test('should match snapshot, public channel', () => {
const wrapper = shallow(<PostAddChannelMember {...requiredProps}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<PostAddChannelMember {...requiredProps}/>);
expect(container).toMatchSnapshot();
});
test('should match snapshot, private channel', () => {
@ -75,11 +107,11 @@ describe('components/post_view/PostAddChannelMember', () => {
channelType: 'P',
};
const wrapper = shallow(<PostAddChannelMember {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<PostAddChannelMember {...props}/>);
expect(container).toMatchSnapshot();
});
test('should match snapshot, more than 3 users', () => {
test('should match snapshot, more than 3 users', async () => {
const userIds = ['user_id_1', 'user_id_2', 'user_id_3', 'user_id_4'];
const usernames = ['username_1', 'username_2', 'username_3', 'username_4'];
const props: Props = {
@ -88,26 +120,34 @@ describe('components/post_view/PostAddChannelMember', () => {
usernames,
};
const wrapper = shallow(<PostAddChannelMember {...props}/>);
expect(wrapper.state('expanded')).toEqual(false);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<PostAddChannelMember {...props}/>);
expect(container).toMatchSnapshot();
wrapper.find('.PostBody_otherUsersLink').simulate('click');
expect(wrapper.state('expanded')).toEqual(true);
expect(wrapper).toMatchSnapshot();
const user = userEvent.setup();
const otherUsersLink = screen.getByText(/others/i).closest('a');
if (!otherUsersLink) {
throw new Error('Other users link not found');
}
await user.click(otherUsersLink);
expect(container).toMatchSnapshot();
});
test('actions should have been called', () => {
test('actions should have been called', async () => {
const actions = {
removePost: jest.fn(),
addChannelMember: jest.fn(),
};
const props: Props = {...requiredProps, actions};
const wrapper = shallow(
renderWithContext(
<PostAddChannelMember {...props}/>,
);
wrapper.find('.PostBody_addChannelMemberLink').simulate('click');
const user = userEvent.setup();
const addToChannelLink = screen.getByText(/add them to the channel/i).closest('a');
if (!addToChannelLink) {
throw new Error('Add to channel link not found');
}
await user.click(addToChannelLink);
expect(actions.addChannelMember).toHaveBeenCalledTimes(1);
expect(actions.addChannelMember).toHaveBeenCalledWith(post.channel_id, requiredProps.userIds[0], post.root_id);
@ -117,7 +157,7 @@ describe('components/post_view/PostAddChannelMember', () => {
expect(actions.removePost).toHaveBeenCalledWith(post);
});
test('addChannelMember should have been called multiple times', () => {
test('addChannelMember should have been called multiple times', async () => {
const userIds = ['user_id_1', 'user_id_2', 'user_id_3', 'user_id_4'];
const usernames = ['username_1', 'username_2', 'username_3', 'username_4'];
const actions = {
@ -125,11 +165,16 @@ describe('components/post_view/PostAddChannelMember', () => {
addChannelMember: jest.fn(),
};
const props: Props = {...requiredProps, userIds, usernames, actions};
const wrapper = shallow(
renderWithContext(
<PostAddChannelMember {...props}/>,
);
wrapper.find('.PostBody_addChannelMemberLink').simulate('click');
const user = userEvent.setup();
const addToChannelLink = screen.getByText(/add them to the channel/i).closest('a');
if (!addToChannelLink) {
throw new Error('Add to channel link not found');
}
await user.click(addToChannelLink);
expect(actions.addChannelMember).toHaveBeenCalledTimes(4);
});
@ -138,8 +183,8 @@ describe('components/post_view/PostAddChannelMember', () => {
...requiredProps,
noGroupsUsernames: ['user_id_2'],
};
const wrapper = shallow(<PostAddChannelMember {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<PostAddChannelMember {...props}/>);
expect(container).toMatchSnapshot();
});
test('should match snapshot, with ABAC policy enforced', () => {
@ -148,8 +193,8 @@ describe('components/post_view/PostAddChannelMember', () => {
usernames: ['username_1', 'username_2', 'username_3'],
isPolicyEnforced: true,
};
const wrapper = shallow(<PostAddChannelMember {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<PostAddChannelMember {...props}/>);
expect(container).toMatchSnapshot();
});
test('should never show invite links when policy is enforced (ABAC channels)', () => {
@ -159,8 +204,8 @@ describe('components/post_view/PostAddChannelMember', () => {
noGroupsUsernames: [],
isPolicyEnforced: true,
};
const wrapper = shallow(<PostAddChannelMember {...props}/>);
expect(wrapper.find('.PostBody_addChannelMemberLink')).toHaveLength(0);
renderWithContext(<PostAddChannelMember {...props}/>);
expect(screen.queryByRole('link', {name: /add them/i})).not.toBeInTheDocument();
});
test('should show single consolidated message for ABAC channels regardless of user types', () => {
@ -170,10 +215,9 @@ describe('components/post_view/PostAddChannelMember', () => {
noGroupsUsernames: ['user3'],
isPolicyEnforced: true,
};
const wrapper = shallow(<PostAddChannelMember {...props}/>);
renderWithContext(<PostAddChannelMember {...props}/>);
// Should render only one consolidated message with no invite links
expect(wrapper.find('p')).toHaveLength(1);
expect(wrapper.find('.PostBody_addChannelMemberLink')).toHaveLength(0);
expect(screen.getByText('did not get notified by this mention because they are not in the channel.')).toBeInTheDocument();
expect(screen.queryByRole('link', {name: /add them/i})).not.toBeInTheDocument();
});
});

View file

@ -1,13 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {mount} from 'enzyme';
import React from 'react';
import * as reactIntl from 'react-intl';
import enMessages from 'i18n/en.json';
import esMessages from 'i18n/es.json';
import {mockStore} from 'tests/test_store';
import {renderWithContext, screen} from 'tests/react_testing_utils';
import {TestHelper} from 'utils/test_helper';
import PostAriaLabelDiv from './post_aria_label_div';
@ -49,42 +48,48 @@ describe('PostAriaLabelDiv', () => {
post: TestHelper.getPostMock({
user_id: author.id,
message: 'This is a test.',
create_at: new Date('2020-01-15T12:00:00Z').getTime(),
}),
} as Omit<Props, 'ref'>;
test('should render aria-label in the given locale', () => {
const {mountOptions} = mockStore(baseState);
(reactIntl.useIntl as jest.Mock).mockImplementation(() => reactIntl.createIntl({locale: 'en', messages: enMessages, defaultLocale: 'en'}));
let wrapper = mount(<PostAriaLabelDiv {...baseProps}/>, mountOptions);
let div = wrapper.childAt(0);
let renderResult = renderWithContext(<PostAriaLabelDiv {...baseProps}/>, baseState, {
locale: 'en',
intlMessages: enMessages,
});
expect(div.prop('aria-label')).toContain(author.username);
expect(div.prop('aria-label')).toContain('January');
let div = renderResult.container.firstChild as HTMLElement;
expect(div.getAttribute('aria-label')).toContain(author.username);
expect(div.getAttribute('aria-label')).toContain('January');
(reactIntl.useIntl as jest.Mock).mockImplementation(() => reactIntl.createIntl({locale: 'es', messages: esMessages, defaultLocale: 'es'}));
wrapper = mount(<PostAriaLabelDiv {...baseProps}/>, mountOptions);
div = wrapper.childAt(0);
renderResult = renderWithContext(<PostAriaLabelDiv {...baseProps}/>, baseState, {
locale: 'es',
intlMessages: esMessages,
});
div = renderResult.container.firstChild as HTMLElement;
expect(div.prop('aria-label')).toContain(author.username);
expect(div.prop('aria-label')).toContain('enero');
expect(div.getAttribute('aria-label')).toContain(author.username);
expect(div.getAttribute('aria-label')).toContain('enero');
});
test('should pass other props through to the rendered div', () => {
const {mountOptions} = mockStore(baseState);
(reactIntl.useIntl as jest.Mock).mockImplementation(() => reactIntl.createIntl({locale: 'en', messages: enMessages, defaultLocale: 'en'}));
let props = baseProps;
let wrapper = mount(<PostAriaLabelDiv {...props}/>, mountOptions);
let div = wrapper.childAt(0);
let renderResult = renderWithContext(<PostAriaLabelDiv {...props}/>, baseState, {
locale: 'en',
intlMessages: enMessages,
});
let div = renderResult.container.firstChild as HTMLElement;
expect(div.prop('className')).toBeUndefined();
expect(div.prop('data-something')).toBeUndefined();
expect(div.children()).toHaveLength(0);
expect(div.className).toBe('');
expect(div.getAttribute('data-something')).toBeNull();
expect(div.childNodes.length).toBe(0);
props = {
...props,
@ -92,16 +97,20 @@ describe('PostAriaLabelDiv', () => {
'data-something': 'something',
} as Props;
wrapper = mount(
renderResult = renderWithContext(
<PostAriaLabelDiv {...props}>
<p>{'This is a paragraph.'}</p>
</PostAriaLabelDiv >,
mountOptions,
baseState,
{
locale: 'en',
intlMessages: enMessages,
},
);
div = wrapper.childAt(0);
div = renderResult.container.firstChild as HTMLElement;
expect(div.prop('className')).toBe('some-class');
expect(div.prop('data-something')).toBe('something');
expect(div.children()).toHaveLength(1);
expect(div.className).toBe('some-class');
expect(div.getAttribute('data-something')).toBe('something');
expect(screen.getByText('This is a paragraph.')).toBeInTheDocument();
});
});

View file

@ -1,47 +1,78 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/post_view/PostFlagIcon should match snapshot 1`] = `
<WithTooltip
key="flagtooltipkey"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Save Message"
id="flag_post.flag"
/>
}
>
<div>
<button
aria-label="save message"
className="post-menu__item"
class="post-menu__item"
id="CENTER_flagIcon_post_id"
onClick={[Function]}
>
<FlagIcon
className="icon icon--small"
/>
<span
class="icon icon--small"
>
<svg
aria-label="Save Icon"
height="16px"
role="img"
viewBox="0 0 16 16"
width="16px"
>
<path
d="M11.744 12.5L8 10.862L4.256 12.5V2.74405H11.744V12.5ZM11.744 1.25005H4.256C3.836 1.25005 3.476 1.40005 3.176 1.70005C2.888 1.98805 2.744 2.33605 2.744 2.74405V14.75L8 12.5L13.256 14.75V2.74405C13.256 2.33605 13.106 1.98805 12.806 1.70005C12.518 1.40005 12.164 1.25005 11.744 1.25005Z"
/>
</svg>
</span>
</button>
</WithTooltip>
</div>
`;
exports[`components/post_view/PostFlagIcon should match snapshot 2`] = `
<WithTooltip
key="flagtooltipkeyflagged"
title={
<Memo(MemoizedFormattedMessage)
defaultMessage="Remove from Saved"
id="flag_post.unflag"
/>
}
>
<div>
<button
aria-label="remove from saved"
className="post-menu__item"
class="post-menu__item"
id="CENTER_flagIcon_post_id"
onClick={[Function]}
>
<FlagIconFilled
className="icon icon--small icon--small-filled post-menu__item--selected"
/>
<span
class="icon icon--small icon--small-filled post-menu__item--selected"
>
<svg
aria-label="Saved Icon"
height="15px"
role="img"
viewBox="0 0 12 15"
width="12px"
>
<g
fill="inherit"
fill-rule="evenodd"
stroke="none"
stroke-width="1"
>
<g
fill="inherit"
fill-rule="nonzero"
transform="translate(-1073.000000, -33.000000)"
>
<g
transform="translate(-1.000000, 0.000000)"
>
<g
transform="translate(1064.000000, 22.000000)"
>
<g
transform="translate(10.000000, 11.000000)"
>
<path
d="M9.76172 0.800049H2.23828C1.83984 0.800049 1.48828 0.952393 1.18359 1.25708C0.902344 1.53833 0.761719 1.88989 0.761719 2.31177V14.3L6 12.05L11.2383 14.3V2.31177C11.2383 1.88989 11.0859 1.53833 10.7812 1.25708C10.5 0.952393 10.1602 0.800049 9.76172 0.800049Z"
/>
</g>
</g>
</g>
</g>
</g>
</svg>
</span>
</button>
</WithTooltip>
</div>
`;

View file

@ -1,11 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import PostFlagIcon from 'components/post_view/post_flag_icon/post_flag_icon';
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
describe('components/post_view/PostFlagIcon', () => {
const baseProps = {
postId: 'post_id',
@ -16,22 +17,26 @@ describe('components/post_view/PostFlagIcon', () => {
},
};
test('should match snapshot', () => {
const wrapper = shallow(<PostFlagIcon {...baseProps}/>);
test('should match snapshot', async () => {
const {container, rerender} = renderWithContext(<PostFlagIcon {...baseProps}/>);
// for unflagged icon
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('button').hasClass('post-menu__item')).toBe(true);
wrapper.find('button').simulate('click', {preventDefault: jest.fn});
expect(container).toMatchSnapshot();
const user = userEvent.setup();
await user.click(screen.getByRole('button', {name: /save message/i}));
expect(baseProps.actions.flagPost).toHaveBeenCalledTimes(1);
expect(baseProps.actions.flagPost).toHaveBeenCalledWith('post_id');
expect(baseProps.actions.unflagPost).not.toHaveBeenCalled();
// for flagged icon
wrapper.setProps({isFlagged: true});
expect(wrapper).toMatchSnapshot();
expect(wrapper.find('button').hasClass('post-menu__item')).toBe(true);
wrapper.find('button').simulate('click', {preventDefault: jest.fn});
rerender(
<PostFlagIcon
{...baseProps}
isFlagged={true}
/>,
);
expect(container).toMatchSnapshot();
await user.click(screen.getByRole('button', {name: /remove from saved/i}));
expect(baseProps.actions.flagPost).toHaveBeenCalledTimes(1);
expect(baseProps.actions.unflagPost).toHaveBeenCalledTimes(1);
expect(baseProps.actions.unflagPost).toHaveBeenCalledWith('post_id');

View file

@ -1,7 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import type {ChannelType} from '@mattermost/types/channels';
@ -10,6 +9,8 @@ import type {UserProfile} from '@mattermost/types/users';
import {General} from 'mattermost-redux/constants';
import {renderWithContext} from 'tests/react_testing_utils';
import PostMessagePreview from './post_message_preview';
import type {Props} from './post_message_preview';
@ -22,11 +23,14 @@ describe('PostMessagePreview', () => {
id: 'post_id',
message: 'post message',
metadata: {},
channel_id: 'channel_id',
create_at: new Date('2020-01-15T12:00:00Z').getTime(),
} as Post;
const user = {
id: 'user_1',
username: 'username1',
roles: 'system_admin',
} as UserProfile;
const baseProps: Props = {
@ -52,47 +56,98 @@ describe('PostMessagePreview', () => {
isPostPriorityEnabled: false,
};
test('should render correctly', () => {
const wrapper = shallow(<PostMessagePreview {...baseProps}/>);
const baseState = {
entities: {
users: {
currentUserId: user.id,
profiles: {
[user.id]: user,
},
},
teams: {
currentTeamId: 'team_id',
teams: {
team_id: {
id: 'team_id',
name: 'team1',
},
},
},
channels: {
channels: {
channel_id: {
id: 'channel_id',
team_id: 'team_id',
type: 'O' as ChannelType,
name: 'channel-name',
display_name: 'Channel Name',
},
},
},
posts: {
posts: {
[previewPost.id]: previewPost,
},
},
preferences: {
myPreferences: {},
},
general: {
config: {},
},
roles: {
roles: {
system_admin: {
permissions: [],
},
},
},
},
};
expect(wrapper).toMatchSnapshot();
test('should render correctly', () => {
const {container} = renderWithContext(<PostMessagePreview {...baseProps}/>, baseState);
expect(container).toMatchSnapshot();
});
test('should render without preview', () => {
const wrapper = shallow(
const {container} = renderWithContext(
<PostMessagePreview
{...baseProps}
previewPost={undefined}
/>,
baseState,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('show render without preview when preview posts becomes undefined after being defined', () => {
const props = {...baseProps};
let wrapper = shallow(
let renderResult = renderWithContext(
<PostMessagePreview
{...props}
/>,
baseState,
);
expect(wrapper).toMatchSnapshot();
let permalink = wrapper.find('.permalink');
expect(permalink.length).toBe(1);
expect(renderResult.container).toMatchSnapshot();
let permalink = renderResult.container.querySelector('.attachment--permalink');
expect(permalink).toBeInTheDocument();
// now we'll set the preview post to undefined. This happens when the
// previewed post is deleted.
props.previewPost = undefined;
wrapper = shallow(
renderResult = renderWithContext(
<PostMessagePreview
{...props}
/>,
baseState,
);
expect(wrapper).toMatchSnapshot();
permalink = wrapper.find('.permalink');
expect(permalink.length).toBe(0);
expect(renderResult.container).toMatchSnapshot();
permalink = renderResult.container.querySelector('.attachment--permalink');
expect(permalink).not.toBeInTheDocument();
});
test('should not render bot icon', () => {
@ -111,13 +166,14 @@ describe('PostMessagePreview', () => {
...baseProps,
previewPost: postPreview,
};
const wrapper = shallow(
const {container} = renderWithContext(
<PostMessagePreview
{...props}
/>,
baseState,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should render bot icon', () => {
@ -137,13 +193,14 @@ describe('PostMessagePreview', () => {
previewPost: postPreview,
enablePostIconOverride: true,
};
const wrapper = shallow(
const {container} = renderWithContext(
<PostMessagePreview
{...props}
/>,
baseState,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
describe('nested previews', () => {
@ -172,9 +229,8 @@ describe('PostMessagePreview', () => {
previewPost: postPreview,
};
const wrapper = shallow(<PostMessagePreview {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<PostMessagePreview {...props}/>, baseState);
expect(container).toMatchSnapshot();
});
test('should render file preview', () => {
@ -188,9 +244,8 @@ describe('PostMessagePreview', () => {
previewPost: postPreview,
};
const wrapper = shallow(<PostMessagePreview {...props}/>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<PostMessagePreview {...props}/>, baseState);
expect(container).toMatchSnapshot();
});
});
@ -210,13 +265,14 @@ describe('PostMessagePreview', () => {
metadata,
};
const wrapper = shallow(
const {container} = renderWithContext(
<PostMessagePreview
{...props}
/>,
baseState,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
});
});

View file

@ -1,231 +1,199 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/post_view/ShowMore should match snapshot 1`] = `
<div
className="post-message post-message--collapsed"
>
<div>
<div
className="post-message__text-container"
style={
Object {
"maxHeight": 200,
}
}
class="post-message post-message--collapsed"
>
<div>
<p>
text
</p>
<div
class="post-message__text-container"
style="max-height: 200px;"
>
<div>
<p>
text
</p>
</div>
</div>
</div>
</div>
`;
exports[`components/post_view/ShowMore should match snapshot, PostAttachment on collapsed view 1`] = `
<div
className="post-message post-message--collapsed post-message--overflow"
>
<div>
<div
className="post-message__text-container"
style={
Object {
"maxHeight": 200,
}
}
/>
<div
className="post-collapse"
class="post-message post-message--collapsed post-message--overflow"
>
<div
className="post-attachment-collapse__show-more"
class="post-message__text-container"
style="max-height: 200px;"
/>
<div
class="post-collapse"
>
<div
className="post-collapse__show-more-line"
/>
<button
className="post-collapse__show-more-button"
id="showMoreButton"
onClick={[Function]}
class="post-attachment-collapse__show-more"
>
<span
className="fa fa-angle-down"
<div
class="post-collapse__show-more-line"
/>
<MemoizedFormattedMessage
defaultMessage="Show more"
id="post_info.message.show_more"
<button
class="post-collapse__show-more-button"
id="showMoreButton"
>
<span
class="fa fa-angle-down"
/>
Show more
</button>
<div
class="post-collapse__show-more-line"
/>
</button>
<div
className="post-collapse__show-more-line"
/>
</div>
</div>
</div>
</div>
`;
exports[`components/post_view/ShowMore should match snapshot, PostAttachment on expanded view 1`] = `
<div
className="post-message post-message--expanded post-message--overflow"
>
<div>
<div
className="post-message__text-container"
style={
Object {
"maxHeight": undefined,
}
}
/>
<div
className="post-collapse"
class="post-message post-message--expanded post-message--overflow"
>
<div
className="post-attachment-collapse__show-more"
class="post-message__text-container"
style=""
/>
<div
class="post-collapse"
>
<div
className="post-collapse__show-more-line"
/>
<button
className="post-collapse__show-more-button"
id="showMoreButton"
onClick={[Function]}
class="post-attachment-collapse__show-more"
>
<span
className="fa fa-angle-up"
<div
class="post-collapse__show-more-line"
/>
<MemoizedFormattedMessage
defaultMessage="Show less"
id="post_info.message.show_less"
<button
class="post-collapse__show-more-button"
id="showMoreButton"
>
<span
class="fa fa-angle-up"
/>
Show less
</button>
<div
class="post-collapse__show-more-line"
/>
</button>
<div
className="post-collapse__show-more-line"
/>
</div>
</div>
</div>
</div>
`;
exports[`components/post_view/ShowMore should match snapshot, PostMessageView on collapsed view 1`] = `
<div
className="post-message post-message--collapsed post-message--overflow"
>
<div>
<div
className="post-message__text-container"
style={
Object {
"maxHeight": 200,
}
}
/>
<div
className="post-collapse"
class="post-message post-message--collapsed post-message--overflow"
>
<div
className="post-collapse__show-more"
class="post-message__text-container"
style="max-height: 200px;"
/>
<div
class="post-collapse"
>
<div
className="post-collapse__show-more-line"
/>
<button
className="post-collapse__show-more-button"
id="showMoreButton"
onClick={[Function]}
class="post-collapse__show-more"
>
<span
className="fa fa-angle-down"
<div
class="post-collapse__show-more-line"
/>
<MemoizedFormattedMessage
defaultMessage="Show more"
id="post_info.message.show_more"
<button
class="post-collapse__show-more-button"
id="showMoreButton"
>
<span
class="fa fa-angle-down"
/>
Show more
</button>
<div
class="post-collapse__show-more-line"
/>
</button>
<div
className="post-collapse__show-more-line"
/>
</div>
</div>
</div>
</div>
`;
exports[`components/post_view/ShowMore should match snapshot, PostMessageView on expanded view 1`] = `
<div
className="post-message post-message--expanded post-message--overflow"
>
<div>
<div
className="post-message__text-container"
style={
Object {
"maxHeight": undefined,
}
}
/>
<div
className="post-collapse"
class="post-message post-message--expanded post-message--overflow"
>
<div
className="post-collapse__show-more"
class="post-message__text-container"
style=""
/>
<div
class="post-collapse"
>
<div
className="post-collapse__show-more-line"
/>
<button
className="post-collapse__show-more-button"
id="showMoreButton"
onClick={[Function]}
class="post-collapse__show-more"
>
<span
className="fa fa-angle-up"
<div
class="post-collapse__show-more-line"
/>
<MemoizedFormattedMessage
defaultMessage="Show less"
id="post_info.message.show_less"
<button
class="post-collapse__show-more-button"
id="showMoreButton"
>
<span
class="fa fa-angle-up"
/>
Show less
</button>
<div
class="post-collapse__show-more-line"
/>
</button>
<div
className="post-collapse__show-more-line"
/>
</div>
</div>
</div>
</div>
`;
exports[`components/post_view/ShowMore should match snapshot, PostMessageView on expanded view with compactDisplay 1`] = `
<div
className="post-message post-message--expanded post-message--overflow"
>
<div>
<div
className="post-message__text-container"
style={
Object {
"maxHeight": undefined,
}
}
/>
<div
className="post-collapse"
class="post-message post-message--expanded post-message--overflow"
>
<div
className="post-collapse__show-more"
class="post-message__text-container"
style=""
/>
<div
class="post-collapse"
>
<div
className="post-collapse__show-more-line"
/>
<button
className="post-collapse__show-more-button"
id="showMoreButton"
onClick={[Function]}
class="post-collapse__show-more"
>
<span
className="fa fa-angle-up"
<div
class="post-collapse__show-more-line"
/>
<MemoizedFormattedMessage
defaultMessage="Show less"
id="post_info.message.show_less"
<button
class="post-collapse__show-more-button"
id="showMoreButton"
>
<span
class="fa fa-angle-up"
/>
Show less
</button>
<div
class="post-collapse__show-more-line"
/>
</button>
<div
className="post-collapse__show-more-line"
/>
</div>
</div>
</div>
</div>

View file

@ -1,11 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import ShowMore from 'components/post_view/show_more/show_more';
import {act, renderWithContext} from 'tests/react_testing_utils';
describe('components/post_view/ShowMore', () => {
const children = (<div><p>{'text'}</p></div>);
const baseProps = {
@ -19,84 +20,166 @@ describe('components/post_view/ShowMore', () => {
};
test('should match snapshot', () => {
const wrapper = shallow(<ShowMore {...baseProps}>{children}</ShowMore>);
expect(wrapper).toMatchSnapshot();
const {container} = renderWithContext(<ShowMore {...baseProps}>{children}</ShowMore>);
expect(container).toMatchSnapshot();
});
test('should match snapshot, PostMessageView on collapsed view', () => {
const wrapper = shallow(<ShowMore {...baseProps}/>);
wrapper.setState({isOverflow: true, isCollapsed: true});
expect(wrapper).toMatchSnapshot();
const ref = React.createRef<ShowMore>();
const {container} = renderWithContext(
<ShowMore
{...baseProps}
ref={ref}
/>,
);
act(() => {
ref.current?.setState({isOverflow: true, isCollapsed: true});
});
expect(container).toMatchSnapshot();
});
test('should match snapshot, PostMessageView on expanded view', () => {
const wrapper = shallow(<ShowMore {...baseProps}/>);
wrapper.setState({isOverflow: true, isCollapsed: false});
expect(wrapper).toMatchSnapshot();
const ref = React.createRef<ShowMore>();
const {container} = renderWithContext(
<ShowMore
{...baseProps}
ref={ref}
/>,
);
act(() => {
ref.current?.setState({isOverflow: true, isCollapsed: false});
});
expect(container).toMatchSnapshot();
});
test('should match snapshot, PostAttachment on collapsed view', () => {
const wrapper = shallow(
const ref = React.createRef<ShowMore>();
const {container} = renderWithContext(
<ShowMore
{...baseProps}
isAttachmentText={true}
ref={ref}
/>,
);
wrapper.setState({isOverflow: true, isCollapsed: true});
expect(wrapper).toMatchSnapshot();
act(() => {
ref.current?.setState({isOverflow: true, isCollapsed: true});
});
expect(container).toMatchSnapshot();
});
test('should match snapshot, PostAttachment on expanded view', () => {
const wrapper = shallow(
const ref = React.createRef<ShowMore>();
const {container} = renderWithContext(
<ShowMore
{...baseProps}
isAttachmentText={true}
ref={ref}
/>,
);
wrapper.setState({isOverflow: true, isCollapsed: false});
expect(wrapper).toMatchSnapshot();
act(() => {
ref.current?.setState({isOverflow: true, isCollapsed: false});
});
expect(container).toMatchSnapshot();
});
test('should match snapshot, PostMessageView on expanded view with compactDisplay', () => {
const wrapper = shallow(
const ref = React.createRef<ShowMore>();
const {container} = renderWithContext(
<ShowMore
{...baseProps}
compactDisplay={true}
ref={ref}
/>,
);
wrapper.setState({isOverflow: true, isCollapsed: false});
expect(wrapper).toMatchSnapshot();
act(() => {
ref.current?.setState({isOverflow: true, isCollapsed: false});
});
expect(container).toMatchSnapshot();
});
test('should call checkTextOverflow', () => {
const wrapper = shallow(<ShowMore {...baseProps}/>);
const instance = wrapper.instance() as ShowMore;
instance.checkTextOverflow = jest.fn();
const ref = React.createRef<ShowMore>();
const {rerender} = renderWithContext(
<ShowMore
{...baseProps}
ref={ref}
/>,
);
const instance = ref.current as ShowMore;
jest.spyOn(instance, 'checkTextOverflow');
expect(instance.checkTextOverflow).not.toHaveBeenCalled();
wrapper.setProps({isRHSExpanded: true});
rerender(
<ShowMore
{...baseProps}
ref={ref}
isRHSExpanded={true}
/>,
);
expect(instance.checkTextOverflow).toHaveBeenCalledTimes(1);
wrapper.setProps({isRHSExpanded: false});
rerender(
<ShowMore
{...baseProps}
ref={ref}
isRHSExpanded={false}
/>,
);
expect(instance.checkTextOverflow).toHaveBeenCalledTimes(2);
wrapper.setProps({isRHSOpen: true});
rerender(
<ShowMore
{...baseProps}
ref={ref}
isRHSOpen={true}
/>,
);
expect(instance.checkTextOverflow).toHaveBeenCalledTimes(3);
wrapper.setProps({isRHSOpen: false});
rerender(
<ShowMore
{...baseProps}
ref={ref}
isRHSOpen={false}
/>,
);
expect(instance.checkTextOverflow).toHaveBeenCalledTimes(4);
wrapper.setProps({text: 'text change'});
rerender(
<ShowMore
{...baseProps}
ref={ref}
text={'text change'}
/>,
);
expect(instance.checkTextOverflow).toHaveBeenCalledTimes(5);
wrapper.setProps({text: 'text another change'});
rerender(
<ShowMore
{...baseProps}
ref={ref}
text={'text another change'}
/>,
);
expect(instance.checkTextOverflow).toHaveBeenCalledTimes(6);
wrapper.setProps({checkOverflow: 1});
rerender(
<ShowMore
{...baseProps}
ref={ref}
checkOverflow={1}
/>,
);
expect(instance.checkTextOverflow).toHaveBeenCalledTimes(7);
wrapper.setProps({checkOverflow: 1});
rerender(
<ShowMore
{...baseProps}
ref={ref}
checkOverflow={1}
/>,
);
expect(instance.checkTextOverflow).toHaveBeenCalledTimes(7);
});
});

View file

@ -1,193 +1,247 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/sidebar/sidebar_channel should match snapshot 1`] = `
<li
className="SidebarChannel expanded"
onAnimationEnd={[Function]}
onAnimationStart={[Function]}
role="listitem"
>
<Connect(SidebarBaseChannel)
channel={
Object {
"create_at": 0,
"creator_id": "",
"delete_at": 0,
"display_name": "channel_display_name",
"group_constrained": false,
"header": "",
"id": "channel_id",
"last_post_at": 0,
"last_root_post_at": 0,
"name": "",
"purpose": "",
"scheme_id": "",
"team_id": "",
"type": "O",
"update_at": 0,
}
}
currentTeamName="team_name"
/>
</li>
<div>
<li
class="SidebarChannel expanded"
role="listitem"
>
<a
aria-label="channel_display_name public channel"
class="SidebarLink"
href="/team_name/channels/"
id="sidebarItem_"
tabindex="0"
>
<i
class="icon icon-globe"
/>
<div
class="SidebarChannelLinkLabel_wrapper"
>
<span
class="SidebarChannelLinkLabel"
>
channel_display_name
</span>
</div>
<div
class="SidebarMenu MenuWrapper"
>
<button
aria-controls="SidebarChannelMenu-MenuList-channel_id"
aria-expanded="false"
aria-haspopup="true"
aria-label="Channel options for "
class="SidebarMenu_menuButton"
id="SidebarChannelMenu-Button-channel_id"
>
<svg
fill="currentColor"
height="16"
version="1.1"
viewBox="0 0 24 24"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"
/>
</svg>
</button>
</div>
</a>
</li>
</div>
`;
exports[`components/sidebar/sidebar_channel should match snapshot when DM channel 1`] = `
<li
className="SidebarChannel expanded"
onAnimationEnd={[Function]}
onAnimationStart={[Function]}
role="listitem"
>
<Connect(injectIntl(SidebarDirectChannel))
channel={
Object {
"create_at": 0,
"creator_id": "",
"delete_at": 0,
"display_name": "channel_display_name",
"group_constrained": false,
"header": "",
"id": "channel_id",
"last_post_at": 0,
"last_root_post_at": 0,
"name": "",
"purpose": "",
"scheme_id": "",
"team_id": "",
"type": "D",
"update_at": 0,
}
}
currentTeamName="team_name"
/>
</li>
<div>
<li
class="SidebarChannel expanded"
role="listitem"
>
<div>
Direct Channel
</div>
</li>
</div>
`;
exports[`components/sidebar/sidebar_channel should match snapshot when GM channel 1`] = `
<li
className="SidebarChannel expanded"
onAnimationEnd={[Function]}
onAnimationStart={[Function]}
role="listitem"
>
<Connect(Component)
channel={
Object {
"create_at": 0,
"creator_id": "",
"delete_at": 0,
"display_name": "channel_display_name",
"group_constrained": false,
"header": "",
"id": "channel_id",
"last_post_at": 0,
"last_root_post_at": 0,
"name": "",
"purpose": "",
"scheme_id": "",
"team_id": "",
"type": "G",
"update_at": 0,
}
}
currentTeamName="team_name"
/>
</li>
<div>
<li
class="SidebarChannel expanded"
role="listitem"
>
<div>
Group Channel
</div>
</li>
</div>
`;
exports[`components/sidebar/sidebar_channel should match snapshot when active 1`] = `
<li
className="SidebarChannel expanded active"
onAnimationEnd={[Function]}
onAnimationStart={[Function]}
role="listitem"
>
<Connect(SidebarBaseChannel)
channel={
Object {
"create_at": 0,
"creator_id": "",
"delete_at": 0,
"display_name": "channel_display_name",
"group_constrained": false,
"header": "",
"id": "channel_id",
"last_post_at": 0,
"last_root_post_at": 0,
"name": "",
"purpose": "",
"scheme_id": "",
"team_id": "",
"type": "O",
"update_at": 0,
}
}
currentTeamName="team_name"
/>
</li>
<div>
<li
class="SidebarChannel expanded active"
role="listitem"
>
<a
aria-label="channel_display_name public channel"
class="SidebarLink"
href="/team_name/channels/"
id="sidebarItem_"
tabindex="0"
>
<i
class="icon icon-globe"
/>
<div
class="SidebarChannelLinkLabel_wrapper"
>
<span
class="SidebarChannelLinkLabel"
>
channel_display_name
</span>
</div>
<div
class="SidebarMenu MenuWrapper"
>
<button
aria-controls="SidebarChannelMenu-MenuList-channel_id"
aria-expanded="false"
aria-haspopup="true"
aria-label="Channel options for "
class="SidebarMenu_menuButton"
id="SidebarChannelMenu-Button-channel_id"
>
<svg
fill="currentColor"
height="16"
version="1.1"
viewBox="0 0 24 24"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"
/>
</svg>
</button>
</div>
</a>
</li>
</div>
`;
exports[`components/sidebar/sidebar_channel should match snapshot when collapsed 1`] = `
<li
className="SidebarChannel collapsed"
onAnimationEnd={[Function]}
onAnimationStart={[Function]}
role="listitem"
>
<Connect(SidebarBaseChannel)
channel={
Object {
"create_at": 0,
"creator_id": "",
"delete_at": 0,
"display_name": "channel_display_name",
"group_constrained": false,
"header": "",
"id": "channel_id",
"last_post_at": 0,
"last_root_post_at": 0,
"name": "",
"purpose": "",
"scheme_id": "",
"team_id": "",
"type": "O",
"update_at": 0,
}
}
currentTeamName="team_name"
/>
</li>
<div>
<li
class="SidebarChannel collapsed"
role="listitem"
>
<a
aria-label="channel_display_name public channel"
class="SidebarLink"
href="/team_name/channels/"
id="sidebarItem_"
tabindex="0"
>
<i
class="icon icon-globe"
/>
<div
class="SidebarChannelLinkLabel_wrapper"
>
<span
class="SidebarChannelLinkLabel"
>
channel_display_name
</span>
</div>
<div
class="SidebarMenu MenuWrapper"
>
<button
aria-controls="SidebarChannelMenu-MenuList-channel_id"
aria-expanded="false"
aria-haspopup="true"
aria-label="Channel options for "
class="SidebarMenu_menuButton"
id="SidebarChannelMenu-Button-channel_id"
>
<svg
fill="currentColor"
height="16"
version="1.1"
viewBox="0 0 24 24"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"
/>
</svg>
</button>
</div>
</a>
</li>
</div>
`;
exports[`components/sidebar/sidebar_channel should match snapshot when unread 1`] = `
<li
className="SidebarChannel expanded unread"
onAnimationEnd={[Function]}
onAnimationStart={[Function]}
role="listitem"
>
<Connect(SidebarBaseChannel)
channel={
Object {
"create_at": 0,
"creator_id": "",
"delete_at": 0,
"display_name": "channel_display_name",
"group_constrained": false,
"header": "",
"id": "channel_id",
"last_post_at": 0,
"last_root_post_at": 0,
"name": "",
"purpose": "",
"scheme_id": "",
"team_id": "",
"type": "O",
"update_at": 0,
}
}
currentTeamName="team_name"
/>
</li>
<div>
<li
class="SidebarChannel expanded unread"
role="listitem"
>
<a
aria-label="channel_display_name public channel"
class="SidebarLink"
href="/team_name/channels/"
id="sidebarItem_"
tabindex="0"
>
<i
class="icon icon-globe"
/>
<div
class="SidebarChannelLinkLabel_wrapper"
>
<span
class="SidebarChannelLinkLabel"
>
channel_display_name
</span>
</div>
<div
class="SidebarMenu MenuWrapper"
>
<button
aria-controls="SidebarChannelMenu-MenuList-channel_id"
aria-expanded="false"
aria-haspopup="true"
aria-label="Channel options for "
class="SidebarMenu_menuButton"
id="SidebarChannelMenu-Button-channel_id"
>
<svg
fill="currentColor"
height="16"
version="1.1"
viewBox="0 0 24 24"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12,16A2,2 0 0,1 14,18A2,2 0 0,1 12,20A2,2 0 0,1 10,18A2,2 0 0,1 12,16M12,10A2,2 0 0,1 14,12A2,2 0 0,1 12,14A2,2 0 0,1 10,12A2,2 0 0,1 12,10M12,4A2,2 0 0,1 14,6A2,2 0 0,1 12,8A2,2 0 0,1 10,6A2,2 0 0,1 12,4Z"
/>
</svg>
</button>
</div>
</a>
</li>
</div>
`;

View file

@ -1,137 +1,61 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot 1`] = `
<Connect(injectIntl(SidebarChannelLink))
ariaLabelPrefix="public channel"
channel={
Object {
"create_at": 0,
"creator_id": "",
"delete_at": 0,
"display_name": "channel_display_name",
"group_constrained": false,
"header": "",
"id": "channel_id",
"last_post_at": 0,
"last_root_post_at": 0,
"name": "",
"purpose": "",
"scheme_id": "",
"team_id": "",
"type": "O",
"update_at": 0,
}
}
channelLeaveHandler={[Function]}
icon={
<SidebarBaseChannelIcon
channelType="O"
/>
}
label="channel_display_name"
link="/team_name/channels/"
/>
<div>
<div>
<button
aria-label="Channel options"
>
Options
</button>
<div>
channel_display_name
</div>
</div>
</div>
`;
exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot when private channel 1`] = `
<Connect(injectIntl(SidebarChannelLink))
ariaLabelPrefix="private channel"
channel={
Object {
"create_at": 0,
"creator_id": "",
"delete_at": 0,
"display_name": "channel_display_name",
"group_constrained": false,
"header": "",
"id": "channel_id",
"last_post_at": 0,
"last_root_post_at": 0,
"name": "",
"purpose": "",
"scheme_id": "",
"team_id": "",
"type": "P",
"update_at": 0,
}
}
channelLeaveHandler={[Function]}
icon={
<SidebarBaseChannelIcon
channelType="P"
/>
}
label="channel_display_name"
link="/team_name/channels/"
/>
<div>
<div>
<button
aria-label="Channel options"
>
Options
</button>
<div>
channel_display_name
</div>
</div>
</div>
`;
exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot when shared channel 1`] = `
<Connect(injectIntl(SidebarChannelLink))
ariaLabelPrefix="public channel"
channel={
Object {
"create_at": 0,
"creator_id": "",
"delete_at": 0,
"display_name": "channel_display_name",
"group_constrained": false,
"header": "",
"id": "channel_id",
"last_post_at": 0,
"last_root_post_at": 0,
"name": "",
"purpose": "",
"scheme_id": "",
"shared": true,
"team_id": "",
"type": "O",
"update_at": 0,
}
}
channelLeaveHandler={[Function]}
icon={
<SidebarBaseChannelIcon
channelType="O"
/>
}
isSharedChannel={true}
label="channel_display_name"
link="/team_name/channels/"
/>
<div>
<div>
<button
aria-label="Channel options"
>
Options
</button>
<div>
channel_display_name
</div>
</div>
</div>
`;
exports[`components/sidebar/sidebar_channel/sidebar_base_channel should match snapshot when shared private channel 1`] = `
<Connect(injectIntl(SidebarChannelLink))
ariaLabelPrefix="private channel"
channel={
Object {
"create_at": 0,
"creator_id": "",
"delete_at": 0,
"display_name": "channel_display_name",
"group_constrained": false,
"header": "",
"id": "channel_id",
"last_post_at": 0,
"last_root_post_at": 0,
"name": "",
"purpose": "",
"scheme_id": "",
"shared": true,
"team_id": "",
"type": "P",
"update_at": 0,
}
}
channelLeaveHandler={[Function]}
icon={
<SidebarBaseChannelIcon
channelType="P"
/>
}
isSharedChannel={true}
label="channel_display_name"
link="/team_name/channels/"
/>
<div>
<div>
<button
aria-label="Channel options"
>
Options
</button>
<div>
channel_display_name
</div>
</div>
</div>
`;

View file

@ -1,15 +1,50 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {screen, waitFor} from '@testing-library/react';
import {shallow} from 'enzyme';
import React from 'react';
import type {ChannelType} from '@mattermost/types/channels';
import SidebarBaseChannel from 'components/sidebar/sidebar_channel/sidebar_base_channel/sidebar_base_channel';
import {renderWithContext, userEvent} from 'tests/react_testing_utils';
import {renderWithContext, screen, userEvent, waitFor} from 'tests/react_testing_utils';
jest.mock('components/tours/onboarding_tour', () => ({
ChannelsAndDirectMessagesTour: () => null,
}));
jest.mock('components/sidebar/sidebar_channel/sidebar_channel_link', () => {
const React = require('react');
return ({label, channelLeaveHandler}: {label: string; channelLeaveHandler?: (callback: () => void) => void}) => {
const [isOpen, setIsOpen] = React.useState(false);
return (
<div>
<button
aria-label='Channel options'
onClick={() => setIsOpen(true)}
>
{'Options'}
</button>
{isOpen && (
<div
role='menu'
aria-label='Edit channel menu'
>
<button
role='menuitem'
onClick={() => channelLeaveHandler?.(() => {})}
>
{'Leave Channel'}
</button>
</div>
)}
<div>{label}</div>
</div>
);
};
});
describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => {
const baseProps = {
@ -38,11 +73,11 @@ describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => {
};
test('should match snapshot', () => {
const wrapper = shallow(
const {container} = renderWithContext(
<SidebarBaseChannel {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when shared channel', () => {
@ -54,11 +89,11 @@ describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => {
},
};
const wrapper = shallow(
const {container} = renderWithContext(
<SidebarBaseChannel {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when private channel', () => {
@ -70,11 +105,11 @@ describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => {
},
};
const wrapper = shallow(
const {container} = renderWithContext(
<SidebarBaseChannel {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when shared private channel', () => {
@ -87,11 +122,11 @@ describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => {
},
};
const wrapper = shallow(
const {container} = renderWithContext(
<SidebarBaseChannel {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('expect leaveChannel to be called when leave public channel ', async () => {
@ -112,14 +147,12 @@ describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => {
};
renderWithContext(<SidebarBaseChannel {...props}/>);
const user = userEvent.setup();
const optionsBtn = screen.getByRole('button');
expect(optionsBtn.classList).toContain('SidebarMenu_menuButton');
const optionsBtn = screen.getByRole('button', {name: /channel options/i});
await userEvent.click(optionsBtn); // open options
const leaveOption: HTMLElement = screen.getByText('Leave Channel').parentElement!;
await userEvent.click(leaveOption);
await user.click(optionsBtn); // open options
await user.click(screen.getByRole('menuitem', {name: 'Leave Channel'}));
await waitFor(() => {
expect(mockfn).toHaveBeenCalledTimes(1);
});
@ -142,14 +175,12 @@ describe('components/sidebar/sidebar_channel/sidebar_base_channel', () => {
};
renderWithContext(<SidebarBaseChannel {...props}/>);
const user = userEvent.setup();
const optionsBtn = screen.getByRole('button');
expect(optionsBtn.classList).toContain('SidebarMenu_menuButton');
const optionsBtn = screen.getByRole('button', {name: /channel options/i});
await userEvent.click(optionsBtn); // open options
const leaveOption: HTMLElement = screen.getByText('Leave Channel').parentElement!;
await userEvent.click(leaveOption);
await user.click(optionsBtn); // open options
await user.click(screen.getByRole('menuitem', {name: 'Leave Channel'}));
await waitFor(() => {
expect(mockfn).toHaveBeenCalledTimes(1);
});

View file

@ -1,13 +1,21 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import type {ChannelType} from '@mattermost/types/channels';
import SidebarChannel from 'components/sidebar/sidebar_channel/sidebar_channel';
import {renderWithContext, screen} from 'tests/react_testing_utils';
jest.mock('components/tours/onboarding_tour', () => ({
ChannelsAndDirectMessagesTour: () => null,
}));
jest.mock('components/sidebar/sidebar_channel/sidebar_direct_channel', () => () => <div>{'Direct Channel'}</div>);
jest.mock('components/sidebar/sidebar_channel/sidebar_group_channel', () => () => <div>{'Group Channel'}</div>);
describe('components/sidebar/sidebar_channel', () => {
const baseProps = {
channel: {
@ -46,11 +54,11 @@ describe('components/sidebar/sidebar_channel', () => {
};
test('should match snapshot', () => {
const wrapper = shallow(
const {container} = renderWithContext(
<SidebarChannel {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when collapsed', () => {
@ -59,11 +67,11 @@ describe('components/sidebar/sidebar_channel', () => {
isCategoryCollapsed: true,
};
const wrapper = shallow(
const {container} = renderWithContext(
<SidebarChannel {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when unread', () => {
@ -73,11 +81,11 @@ describe('components/sidebar/sidebar_channel', () => {
unreadMentions: 1,
};
const wrapper = shallow(
const {container} = renderWithContext(
<SidebarChannel {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when active', () => {
@ -86,11 +94,11 @@ describe('components/sidebar/sidebar_channel', () => {
isCurrentChannel: true,
};
const wrapper = shallow(
const {container} = renderWithContext(
<SidebarChannel {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when DM channel', () => {
@ -102,11 +110,11 @@ describe('components/sidebar/sidebar_channel', () => {
},
};
const wrapper = shallow(
const {container} = renderWithContext(
<SidebarChannel {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when GM channel', () => {
@ -118,11 +126,11 @@ describe('components/sidebar/sidebar_channel', () => {
},
};
const wrapper = shallow(
const {container} = renderWithContext(
<SidebarChannel {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should not be collapsed when there are unread messages', () => {
@ -132,11 +140,11 @@ describe('components/sidebar/sidebar_channel', () => {
isUnread: true,
};
const wrapper = shallow(
renderWithContext(
<SidebarChannel {...props}/>,
);
expect(wrapper.find('.expanded')).toHaveLength(1);
expect(screen.getByRole('listitem')).toHaveClass('expanded');
});
test('should not be collapsed if channel is current channel', () => {
@ -146,10 +154,10 @@ describe('components/sidebar/sidebar_channel', () => {
isCurrentChannel: true,
};
const wrapper = shallow(
renderWithContext(
<SidebarChannel {...props}/>,
);
expect(wrapper.find('.expanded')).toHaveLength(1);
expect(screen.getByRole('listitem')).toHaveClass('expanded');
});
});

View file

@ -1,13 +1,13 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import type {ChannelType} from '@mattermost/types/channels';
import {CategoryTypes} from 'mattermost-redux/constants/channel_categories';
import {renderWithContext, screen, userEvent} from 'tests/react_testing_utils';
import Constants from 'utils/constants';
import {TestHelper} from 'utils/test_helper';
@ -55,83 +55,96 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_menu', () => {
onMenuToggle: jest.fn(),
};
test('should match snapshot and contain correct buttons', () => {
const wrapper = shallow(
const openMenu = async () => {
const user = userEvent.setup();
const menuButton = screen.getByRole('button', {name: /channel options/i});
await user.click(menuButton);
await screen.findByRole('menu', {name: 'Edit channel menu'});
};
test('should match snapshot and contain correct buttons', async () => {
const {baseElement} = renderWithContext(
<SidebarChannelMenu {...baseProps}/>,
);
expect(wrapper.find('#favorite-channel_id')).toHaveLength(1);
expect(wrapper.find('#mute-channel_id')).toHaveLength(1);
expect(wrapper.find('#copyLink-channel_id')).toHaveLength(1);
expect(wrapper.find('#addMembers-channel_id')).toHaveLength(1);
expect(wrapper.find('#leave-channel_id')).toHaveLength(1);
await openMenu();
expect(wrapper).toMatchSnapshot();
expect(screen.getByRole('menuitem', {name: 'Favorite'})).toBeInTheDocument();
expect(screen.getByRole('menuitem', {name: 'Mute Channel'})).toBeInTheDocument();
expect(screen.getByRole('menuitem', {name: 'Copy Link'})).toBeInTheDocument();
expect(screen.getByRole('menuitem', {name: 'Add Members'})).toBeInTheDocument();
expect(screen.getByRole('menuitem', {name: 'Leave Channel'})).toBeInTheDocument();
expect(baseElement).toMatchSnapshot();
});
test('should show correct menu items when channel is unread', () => {
test('should show correct menu items when channel is unread', async () => {
const props = {
...baseProps,
isUnread: true,
};
const wrapper = shallow(
const {baseElement} = renderWithContext(
<SidebarChannelMenu {...props}/>,
);
expect(wrapper.find('#markAsRead-channel_id')).toHaveLength(1);
await openMenu();
expect(screen.getByRole('menuitem', {name: 'Mark as Read'})).toBeInTheDocument();
expect(wrapper).toMatchSnapshot();
expect(baseElement).toMatchSnapshot();
});
test('should show correct menu items when channel is read', () => {
test('should show correct menu items when channel is read', async () => {
const props = {
...baseProps,
isUnread: false,
};
const wrapper = shallow(
const {baseElement} = renderWithContext(
<SidebarChannelMenu {...props}/>,
);
expect(wrapper.find('#markAsUnread-channel_id')).toHaveLength(1);
await openMenu();
expect(screen.getByRole('menuitem', {name: 'Mark as Unread'})).toBeInTheDocument();
expect(wrapper).toMatchSnapshot();
expect(baseElement).toMatchSnapshot();
});
test('should show correct menu items when channel is favorite', () => {
test('should show correct menu items when channel is favorite', async () => {
const props = {
...baseProps,
isFavorite: true,
};
const wrapper = shallow(
const {baseElement} = renderWithContext(
<SidebarChannelMenu {...props}/>,
);
expect(wrapper.find('#favorite-channel_id')).toHaveLength(0);
expect(wrapper.find('#unfavorite-channel_id')).toHaveLength(1);
await openMenu();
expect(screen.queryByRole('menuitem', {name: 'Favorite'})).not.toBeInTheDocument();
expect(screen.getByRole('menuitem', {name: 'Unfavorite'})).toBeInTheDocument();
expect(wrapper).toMatchSnapshot();
expect(baseElement).toMatchSnapshot();
});
test('should show correct menu items when channel is muted', () => {
test('should show correct menu items when channel is muted', async () => {
const props = {
...baseProps,
isMuted: true,
};
const wrapper = shallow(
const {baseElement} = renderWithContext(
<SidebarChannelMenu {...props}/>,
);
expect(wrapper.find('#mute-channel_id')).toHaveLength(0);
expect(wrapper.find('#unmute-channel_id')).toHaveLength(1);
await openMenu();
expect(screen.queryByRole('menuitem', {name: 'Mute Channel'})).not.toBeInTheDocument();
expect(screen.getByRole('menuitem', {name: 'Unmute Channel'})).toBeInTheDocument();
expect(wrapper).toMatchSnapshot();
expect(baseElement).toMatchSnapshot();
});
test('should show correct menu items when channel is private', () => {
test('should show correct menu items when channel is private', async () => {
const props = {
...baseProps,
channel: {
@ -140,15 +153,16 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_menu', () => {
},
};
const wrapper = shallow(
const {baseElement} = renderWithContext(
<SidebarChannelMenu {...props}/>,
);
expect(wrapper.find('#copyLink-channel_id')).toHaveLength(1);
expect(wrapper).toMatchSnapshot();
await openMenu();
expect(screen.getByRole('menuitem', {name: 'Copy Link'})).toBeInTheDocument();
expect(baseElement).toMatchSnapshot();
});
test('should show correct menu items when channel is DM', () => {
test('should show correct menu items when channel is DM', async () => {
const props = {
...baseProps,
channel: {
@ -157,17 +171,18 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_menu', () => {
},
};
const wrapper = shallow(
const {baseElement} = renderWithContext(
<SidebarChannelMenu {...props}/>,
);
expect(wrapper.find('#copyLink-channel_id')).toHaveLength(0);
expect(wrapper.find('#addMembers-channel_id')).toHaveLength(0);
await openMenu();
expect(screen.queryByRole('menuitem', {name: 'Copy Link'})).not.toBeInTheDocument();
expect(screen.queryByRole('menuitem', {name: 'Add Members'})).not.toBeInTheDocument();
expect(wrapper).toMatchSnapshot();
expect(baseElement).toMatchSnapshot();
});
test('should show correct menu items when channel is Town Square', () => {
test('should show correct menu items when channel is Town Square', async () => {
const props = {
...baseProps,
channel: {
@ -176,16 +191,17 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_menu', () => {
},
};
const wrapper = shallow(
const {baseElement} = renderWithContext(
<SidebarChannelMenu {...props}/>,
);
expect(wrapper.find('#leave-channel_id')).toHaveLength(0);
await openMenu();
expect(screen.queryByRole('menuitem', {name: 'Leave Channel'})).not.toBeInTheDocument();
expect(wrapper).toMatchSnapshot();
expect(baseElement).toMatchSnapshot();
});
test('should match snapshot of rendered items when multiselecting channels - public channels and DM category', () => {
test('should match snapshot of rendered items when multiselecting channels - public channels and DM category', async () => {
const props = {
...baseProps,
categories: [
@ -205,14 +221,15 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_menu', () => {
],
};
const wrapper = shallow(
const {baseElement} = renderWithContext(
<SidebarChannelMenu {...props}/>,
);
expect(wrapper).toMatchSnapshot();
await openMenu();
expect(baseElement).toMatchSnapshot();
});
test('should match snapshot of rendered items when multiselecting channels - DM channels and public channels category', () => {
test('should match snapshot of rendered items when multiselecting channels - DM channels and public channels category', async () => {
const props = {
...baseProps,
categories: [
@ -236,10 +253,11 @@ describe('components/sidebar/sidebar_channel/sidebar_channel_menu', () => {
],
};
const wrapper = shallow(
renderWithContext(
<SidebarChannelMenu {...props}/>,
);
expect(wrapper).toMatchSnapshot();
await openMenu();
expect(document.body).toMatchSnapshot();
});
});

View file

@ -1,97 +1,53 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`SidebarList should match snapshot 1`] = `
<Fragment>
<GlobalThreadsLink />
<DraftsLink />
<RecapsLink />
<div>
<div
data-testid="GlobalThreadsLink"
/>
<div
data-testid="DraftsLink"
/>
<div
data-testid="RecapsLink"
/>
<div
aria-label="channel sidebar region"
className="SidebarNavContainer a11y__region"
data-a11y-disable-nav={false}
class="SidebarNavContainer a11y__region"
data-a11y-disable-nav="false"
data-a11y-sort-order="7"
id="sidebar-left"
onTransitionEnd={[Function]}
role="application"
>
<UnreadChannelIndicator
content={
<Memo(MemoizedFormattedMessage)
defaultMessage="More unreads"
id="sidebar.unreads"
/>
}
extraClass="nav-pills__unread-indicator-top"
name="Top"
onClick={[Function]}
show={false}
<div
data-testid="UnreadChannelIndicator"
/>
<UnreadChannelIndicator
content={
<Memo(MemoizedFormattedMessage)
defaultMessage="More unreads"
id="sidebar.unreads"
/>
}
extraClass="nav-pills__unread-indicator-bottom"
name="Bottom"
onClick={[Function]}
show={false}
<div
data-testid="UnreadChannelIndicator"
/>
<Scrollbars
onScroll={[Function]}
>
<DragDropContext
onBeforeCapture={[Function]}
onBeforeDragStart={[Function]}
onDragEnd={[Function]}
onDragStart={[Function]}
<div>
<div
data-rbd-droppable-context-id="0"
data-rbd-droppable-id="droppable-categories"
id="sidebar-droppable-categories"
>
<Connect(Droppable)
direction="vertical"
droppableId="droppable-categories"
getContainerForClone={[Function]}
ignoreContainerClipping={false}
isCombineEnabled={false}
isDropDisabled={false}
mode="standard"
renderClone={null}
type="SIDEBAR_CATEGORY"
>
<Component />
</Connect(Droppable)>
</DragDropContext>
</Scrollbars>
<div
data-testid="sidebar-category"
/>
</div>
</div>
</div>
</Fragment>
</div>
`;
exports[`SidebarList should match snapshot 2`] = `
<div
data-rbd-droppable-context-id="0"
data-rbd-droppable-id="droppable-categories"
id="sidebar-droppable-categories"
>
<Connect(SidebarCategory)
category={
Object {
"channel_ids": Array [
"channel_id",
"channel_id_2",
],
"collapsed": false,
"display_name": "custom_category_1",
"id": "category1",
"muted": false,
"sorting": "alpha",
"team_id": "team1",
"type": "custom",
"user_id": "",
}
}
categoryIndex={0}
handleOpenMoreDirectChannelsModal={[MockFunction]}
isNewCategory={false}
key="category1"
setChannelRef={[Function]}
<div
data-testid="sidebar-category"
/>
</div>
`;

View file

@ -1,7 +1,6 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import type {MovementMode, DropResult} from 'react-beautiful-dnd';
@ -11,13 +10,58 @@ import type {TeamType} from '@mattermost/types/teams';
import {CategoryTypes} from 'mattermost-redux/constants/channel_categories';
import {shallowWithIntl} from 'tests/helpers/intl-test-helper';
import {act, renderWithContext, screen} from 'tests/react_testing_utils';
import {DraggingStates, DraggingStateTypes} from 'utils/constants';
import {TestHelper} from 'utils/test_helper';
import SidebarList, {type SidebarList as SidebarListComponent} from './sidebar_list';
import {SidebarList as SidebarListComponent} from './sidebar_list';
jest.mock('components/async_load', () => ({
makeAsyncComponent: (displayName: string) => {
const Component = (props: {children?: React.ReactNode}) => (
<div data-testid={displayName}>{props.children}</div>
);
Component.displayName = displayName;
return Component;
},
}));
jest.mock('components/common/scrollbars', () => {
const React = require('react');
return React.forwardRef(({children, onScroll}: {children?: React.ReactNode; onScroll?: () => void}, ref: any) => {
const setRef = (node: HTMLDivElement | null) => {
if (!node) {
return;
}
if (!node.scrollTo) {
node.scrollTo = jest.fn();
}
if (typeof ref === 'function') {
ref(node);
} else if (ref) {
ref.current = node;
}
};
return (
<div
ref={setRef}
onScroll={onScroll}
>
{children}
</div>
);
});
});
jest.mock('components/sidebar/sidebar_category', () => () => <div data-testid='sidebar-category'/>);
describe('SidebarList', () => {
const intl = {
formatMessage: ({defaultMessage}: {defaultMessage: string}) => defaultMessage,
} as any;
const currentChannel = TestHelper.getChannelMock({
id: 'channel_id',
display_name: 'channel_display_name',
@ -116,34 +160,49 @@ describe('SidebarList', () => {
};
test('should match snapshot', () => {
const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>,
const {container} = renderWithContext(
<SidebarListComponent
{...baseProps}
intl={intl}
/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
const draggable = wrapper.find('Connect(Droppable)').first();
const children: any = draggable.prop('children')!;
const inner = shallow(
children({}, {}),
);
expect(inner).toMatchSnapshot();
const sidebarRegion = screen.getByRole('application', {name: /channel sidebar region/i});
const droppable = sidebarRegion.querySelector('#sidebar-droppable-categories');
expect(droppable).toBeInTheDocument();
expect(droppable).toMatchSnapshot();
});
test('should close sidebar on mobile when channel is selected (ie. changed)', () => {
const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>,
const {rerender} = renderWithContext(
<SidebarListComponent
{...baseProps}
intl={intl}
/>,
);
wrapper.setProps({currentChannelId: 'new_channel_id'});
rerender(
<SidebarListComponent
{...baseProps}
intl={intl}
currentChannelId='new_channel_id'
/>,
);
expect(baseProps.actions.close).toHaveBeenCalled();
});
test('should scroll to top when team changes', () => {
const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>,
const sidebarListRef = React.createRef<SidebarListComponent>();
const {rerender} = renderWithContext(
<SidebarListComponent
{...baseProps}
intl={intl}
ref={sidebarListRef}
/>,
);
const instance = wrapper.instance() as SidebarListComponent;
const instance = sidebarListRef.current!;
instance.scrollbar = {
current: {
@ -156,15 +215,27 @@ describe('SidebarList', () => {
id: 'new_team',
};
wrapper.setProps({currentTeam: newCurrentTeam});
rerender(
<SidebarListComponent
{...baseProps}
intl={intl}
ref={sidebarListRef}
currentTeam={newCurrentTeam}
/>,
);
expect(instance.scrollbar.current!.scrollTo).toHaveBeenCalledWith({top: 0});
});
test('should display unread scroll indicator when channels appear outside visible area', () => {
const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>,
const sidebarListRef = React.createRef<SidebarListComponent>();
renderWithContext(
<SidebarListComponent
{...baseProps}
intl={intl}
ref={sidebarListRef}
/>,
);
const instance = wrapper.instance() as SidebarListComponent;
const instance = sidebarListRef.current!;
instance.scrollbar = {
current: {
@ -178,7 +249,9 @@ describe('SidebarList', () => {
offsetHeight: 0,
} as any);
instance.updateUnreadIndicators();
act(() => {
instance.updateUnreadIndicators();
});
expect(instance.state.showTopUnread).toBe(true);
instance.channelRefs.set(unreadChannel.id, {
@ -186,15 +259,22 @@ describe('SidebarList', () => {
offsetHeight: 0,
} as any);
instance.updateUnreadIndicators();
act(() => {
instance.updateUnreadIndicators();
});
expect(instance.state.showBottomUnread).toBe(true);
});
test('should scroll to correct position when scrolling to channel', () => {
const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>,
const sidebarListRef = React.createRef<SidebarListComponent>();
renderWithContext(
<SidebarListComponent
{...baseProps}
intl={intl}
ref={sidebarListRef}
/>,
);
const instance = wrapper.instance() as SidebarListComponent;
const instance = sidebarListRef.current!;
instance.scrollToPosition = jest.fn();
@ -216,12 +296,17 @@ describe('SidebarList', () => {
});
test('should set the dragging state based on type', () => {
(global as any).document.querySelectorAll = jest.fn().mockReturnValue([{
const querySpy = jest.spyOn(document, 'querySelectorAll').mockReturnValue([{
style: {},
}]);
}] as any);
const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>,
const sidebarListRef = React.createRef<SidebarListComponent>();
renderWithContext(
<SidebarListComponent
{...baseProps}
intl={intl}
ref={sidebarListRef}
/>,
);
const categoryBefore = {
@ -234,7 +319,7 @@ describe('SidebarList', () => {
type: DraggingStateTypes.CATEGORY,
};
const instance = wrapper.instance() as SidebarListComponent;
const instance = sidebarListRef.current!;
instance.onBeforeCapture(categoryBefore);
expect(baseProps.actions.setDraggingState).toHaveBeenCalledWith(expectedCategoryBefore);
@ -251,13 +336,20 @@ describe('SidebarList', () => {
instance.onBeforeCapture(channelBefore);
expect(baseProps.actions.setDraggingState).toHaveBeenCalledWith(expectedChannelBefore);
querySpy.mockRestore();
});
test('should call correct action on dropping item', () => {
const wrapper = shallowWithIntl(
<SidebarList {...baseProps}/>,
const sidebarListRef = React.createRef<SidebarListComponent>();
renderWithContext(
<SidebarListComponent
{...baseProps}
intl={intl}
ref={sidebarListRef}
/>,
);
const instance = wrapper.instance() as SidebarListComponent;
const instance = sidebarListRef.current!;
const categoryResult: DropResult = {
reason: 'DROP',

View file

@ -1,53 +1,109 @@
// Jest Snapshot v1, https://jestjs.io/docs/snapshot-testing
exports[`UnreadChannelIndicator should match snapshot 1`] = `
<div
className="nav-pills__unread-indicator"
id="unreadIndicatorundefined"
onClick={[MockFunction]}
>
<UnreadBelowIcon
className="icon icon__unread"
/>
<div>
<div
class="nav-pills__unread-indicator"
id="unreadIndicatorundefined"
>
<span
class="icon icon__unread"
>
<svg
aria-label="Down Arrow Icon"
height="16"
role="img"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.696 2H7.184V11L3.062 6.878L2 7.94L7.94 13.88L13.88 7.94L12.818 6.878L8.696 11V2Z"
/>
</svg>
</span>
</div>
</div>
`;
exports[`UnreadChannelIndicator should match snapshot when content is an element 1`] = `
<div
className="nav-pills__unread-indicator nav-pills__unread-indicator--visible"
id="unreadIndicatorundefined"
onClick={[MockFunction]}
>
<UnreadBelowIcon
className="icon icon__unread"
/>
<div>
foo
<div>
<div
class="nav-pills__unread-indicator nav-pills__unread-indicator--visible"
id="unreadIndicatorundefined"
>
<span
class="icon icon__unread"
>
<svg
aria-label="Down Arrow Icon"
height="16"
role="img"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.696 2H7.184V11L3.062 6.878L2 7.94L7.94 13.88L13.88 7.94L12.818 6.878L8.696 11V2Z"
/>
</svg>
</span>
<div>
foo
</div>
</div>
</div>
`;
exports[`UnreadChannelIndicator should match snapshot when content is text 1`] = `
<div
className="nav-pills__unread-indicator nav-pills__unread-indicator--visible"
id="unreadIndicatorundefined"
onClick={[MockFunction]}
>
<UnreadBelowIcon
className="icon icon__unread"
/>
foo
<div>
<div
class="nav-pills__unread-indicator nav-pills__unread-indicator--visible"
id="unreadIndicatorundefined"
>
<span
class="icon icon__unread"
>
<svg
aria-label="Down Arrow Icon"
height="16"
role="img"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.696 2H7.184V11L3.062 6.878L2 7.94L7.94 13.88L13.88 7.94L12.818 6.878L8.696 11V2Z"
/>
</svg>
</span>
foo
</div>
</div>
`;
exports[`UnreadChannelIndicator should match snapshot when show is set 1`] = `
<div
className="nav-pills__unread-indicator nav-pills__unread-indicator--visible"
id="unreadIndicatorundefined"
onClick={[MockFunction]}
>
<UnreadBelowIcon
className="icon icon__unread"
/>
<div>
<div
class="nav-pills__unread-indicator nav-pills__unread-indicator--visible"
id="unreadIndicatorundefined"
>
<span
class="icon icon__unread"
>
<svg
aria-label="Down Arrow Icon"
height="16"
role="img"
viewBox="0 0 16 16"
width="16"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M8.696 2H7.184V11L3.062 6.878L2 7.94L7.94 13.88L13.88 7.94L12.818 6.878L8.696 11V2Z"
/>
</svg>
</span>
</div>
</div>
`;

View file

@ -1,9 +1,10 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {shallow} from 'enzyme';
import React from 'react';
import {render, screen, userEvent} from 'tests/react_testing_utils';
import UnreadChannelIndicator from './unread_channel_indicator';
describe('UnreadChannelIndicator', () => {
@ -18,19 +19,19 @@ describe('UnreadChannelIndicator', () => {
show: false,
};
const wrapper = shallow(
const {container} = render(
<UnreadChannelIndicator {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when show is set', () => {
const wrapper = shallow(
const {container} = render(
<UnreadChannelIndicator {...baseProps}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when content is text', () => {
@ -39,11 +40,11 @@ describe('UnreadChannelIndicator', () => {
content: 'foo',
};
const wrapper = shallow(
const {container} = render(
<UnreadChannelIndicator {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should match snapshot when content is an element', () => {
@ -52,25 +53,26 @@ describe('UnreadChannelIndicator', () => {
content: <div>{'foo'}</div>,
};
const wrapper = shallow(
const {container} = render(
<UnreadChannelIndicator {...props}/>,
);
expect(wrapper).toMatchSnapshot();
expect(container).toMatchSnapshot();
});
test('should have called onClick', () => {
test('should have called onClick', async () => {
const props = {
...baseProps,
content: <div>{'foo'}</div>,
name: 'name',
};
const wrapper = shallow(
render(
<UnreadChannelIndicator {...props}/>,
);
wrapper.simulate('click');
const user = userEvent.setup();
await user.click(screen.getByText('foo'));
expect(props.onClick).toHaveBeenCalledTimes(1);
});
});