// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import React from 'react';
import {FormattedMessage, useIntl} from 'react-intl';
import {connect, useDispatch, useSelector} from 'react-redux';
import {Link, Route} from 'react-router-dom';
import {GenericModal} from '@mattermost/components';
import {UserTypes} from 'mattermost-redux/action_types';
import {getUser} from 'mattermost-redux/selectors/entities/users';
import {openModal} from 'actions/views/modals';
import ModalController from 'components/modal_controller';
import mergeObjects from 'packages/mattermost-redux/test/merge_objects';
import {TestHelper} from 'utils/test_helper';
import type {GlobalState} from 'types/store';
import {
renderWithContext,
screen,
userEvent,
waitFor,
} from './react_testing_utils';
describe('renderWithContext', () => {
test('should be able to render anything', () => {
const TestComponent = () => {
return
{'Anything'}
;
};
renderWithContext(
,
{},
);
expect(screen.getByText('Anything')).toBeInTheDocument();
});
test('should be able to render react-intl components', () => {
const TestComponent = () => {
return (
);
};
renderWithContext(
,
);
expect(screen.getByText('Build Number:')).toBeInTheDocument();
});
test('should be able to render components using react-intl hooks', () => {
const TestComponent = () => {
const intl = useIntl();
return
;
};
renderWithContext(
,
);
expect(screen.getByText('Build Hash:')).toBeInTheDocument();
});
test('should be able to render react-router components', () => {
const RouteComponent = () => {
return
{'this is the route component'}
;
};
const TestComponent = () => {
return (
{'Test Link'}
);
};
renderWithContext(
,
);
expect(screen.getByText('this is the route component')).toBeInTheDocument();
expect(screen.getByRole('link', {name: 'Test Link'})).toBeInTheDocument();
});
test('should be able to render components that use connect to access the Redux store', () => {
const UnconnectedTestComponent = (props: {numProfiles: number}) => {
return
{`There are ${props.numProfiles} users loaded`}
;
};
const TestComponent = connect((state: GlobalState) => ({
numProfiles: Object.keys(state.entities.users.profiles).length,
}))(UnconnectedTestComponent);
renderWithContext(
,
);
expect(screen.getByText('There are 0 users loaded')).toBeInTheDocument();
});
test('should be able to render components that use hooks to access the Redux store', () => {
const TestComponent = () => {
const numProfiles = useSelector((state: GlobalState) => Object.keys(state.entities.users.profiles).length);
return
{`There are ${numProfiles} users loaded`}
;
};
renderWithContext(
,
);
expect(screen.getByText('There are 0 users loaded')).toBeInTheDocument();
});
test('should be able to rerender components without losing context', () => {
const TestComponent = (props: {appTitle: string}) => {
return (
);
};
const {rerender} = renderWithContext(
,
);
expect(screen.getByText('About Mattermost')).toBeInTheDocument();
rerender(
,
);
expect(screen.getByText('About Mattermots')).toBeInTheDocument();
});
test('should be able to inject store state and replace it later', () => {
const initialState = {
entities: {
users: {
profiles: {
user1: TestHelper.getUserMock({id: 'user1', username: 'Alpha'}),
user2: TestHelper.getUserMock({id: 'user2', username: 'Bravo'}),
},
},
},
};
const TestComponent = () => {
const user1 = useSelector((state: GlobalState) => getUser(state, 'user1'));
const user2 = useSelector((state: GlobalState) => getUser(state, 'user2'));
return
{`User1 is ${user1.username} and User2 is ${user2.username}!`}
;
};
const {replaceStoreState} = renderWithContext(
,
initialState,
);
expect(screen.getByText('User1 is Alpha and User2 is Bravo!')).toBeInTheDocument();
replaceStoreState(mergeObjects(initialState, {
entities: {
users: {
profiles: {
user1: {username: 'Charlie'},
},
},
},
}));
expect(screen.getByText('User1 is Charlie and User2 is Bravo!')).toBeInTheDocument();
replaceStoreState(mergeObjects(initialState, {
entities: {
users: {
profiles: {
user2: {username: 'Delta'},
},
},
},
}));
// Since this replaces the state, user1's username goes back to the initial value
expect(screen.getByText('User1 is Alpha and User2 is Delta!')).toBeInTheDocument();
});
test('should be able to update store state', () => {
const initialState = {
entities: {
users: {
profiles: {
user1: TestHelper.getUserMock({id: 'user1', username: 'Echo'}),
user2: TestHelper.getUserMock({id: 'user2', username: 'Foxtrot'}),
},
},
},
};
const TestComponent = () => {
const user1 = useSelector((state: GlobalState) => getUser(state, 'user1'));
const user2 = useSelector((state: GlobalState) => getUser(state, 'user2'));
return
{`User1 is ${user1.username} and User2 is ${user2.username}!`}
;
};
const {updateStoreState} = renderWithContext(
,
initialState,
);
expect(screen.getByText('User1 is Echo and User2 is Foxtrot!')).toBeInTheDocument();
updateStoreState({
entities: {
users: {
profiles: {
user1: {username: 'Golf'},
},
},
},
});
expect(screen.getByText('User1 is Golf and User2 is Foxtrot!')).toBeInTheDocument();
updateStoreState({
entities: {
users: {
profiles: {
user2: {username: 'Hotel'},
},
},
},
});
expect(screen.getByText('User1 is Golf and User2 is Hotel!')).toBeInTheDocument();
});
test('should be able to mix rerendering and updating store state', () => {
const initialState = {
entities: {
users: {
profiles: {
user1: TestHelper.getUserMock({id: 'user1', username: 'India'}),
},
},
},
};
const TestComponent = (props: {greeting: string}) => {
const user1 = useSelector((state: GlobalState) => getUser(state, 'user1'));
return
);
};
renderWithContext(
<>
>,
);
expect(screen.getByText('Open Modal')).toBeVisible();
userEvent.click(screen.getByText('Open Modal'));
// Use waitFor because the modal animates in and out
await waitFor(() => {
expect(screen.queryByText('This is a modal!')).toBeInTheDocument();
});
userEvent.click(screen.getByLabelText('Close'));
await waitFor(() => {
expect(screen.queryByText('This is a modal!')).not.toBeInTheDocument();
});
});
});