Fix #5016: Unit test for UserDetails component (#5430)

* Fix #5016: Unit test for UserDetails component

* Fixed unit tests for components on user-details page
This commit is contained in:
darth-coder00 2022-06-16 20:45:07 +05:30 committed by GitHub
parent 9b93149fd3
commit 08501fa63f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 496 additions and 49 deletions

View File

@ -58,4 +58,26 @@ describe('Test Ingestion modal component', () => {
expect(mockConfirmation).toBeCalled();
});
it('waiting', () => {
const { getByTestId } = render(
<ConfirmationModal
bodyClassName=""
bodyText="Are you sure?"
cancelButtonCss=""
cancelText="Cancel"
confirmButtonCss=""
confirmText="Save"
footerClassName=""
header="confirmation modal"
headerClassName=""
loadingState="waiting"
onCancel={mockCancel}
onConfirm={mockConfirmation}
/>
);
const loader = getByTestId('loading-button');
expect(loader).toBeInTheDocument();
});
});

View File

@ -11,23 +11,29 @@
* limitations under the License.
*/
import { findByTestId, render } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import UserDataCard from './UserDataCard';
import { act } from 'react-test-renderer';
import UserDataCard, { Item, Props } from './UserDataCard';
const mockItem = {
const onClick = jest.fn();
const onDelete = jest.fn();
const mockItem: Item = {
displayName: 'description1',
name: 'name1',
id: 'id1',
email: 'string@email.com',
isActiveUser: true,
profilePhoto: '',
teamCount: 'Cloud_Infra',
};
const mockSelect = jest.fn();
const mockDelete = jest.fn();
const mockProp: Props = {
item: mockItem,
onClick,
onDelete,
};
jest.mock('../../authentication/auth-provider/AuthProvider', () => {
return {
@ -57,38 +63,69 @@ jest.mock('../../utils/SvgUtils', () => {
};
});
jest.mock('../common/non-admin-action/NonAdminAction', () => {
return jest.fn().mockImplementation(({ children }) => {
return (
<div>
NonAdminAction
{children}
</div>
);
});
});
describe('Test UserDataCard component', () => {
it('Component should render', async () => {
const { container } = render(
<UserDataCard
item={mockItem}
onClick={mockSelect}
onDelete={mockDelete}
/>,
const { findByTestId, findByText } = render(
<UserDataCard {...mockProp} item={{ ...mockItem, id: 'id1' }} />,
{
wrapper: MemoryRouter,
}
);
const cardContainer = await findByTestId(container, 'user-card-container');
const avatar = await findByTestId(container, 'profile-picture');
const cardContainer = await findByTestId('user-card-container');
const avatar = await findByTestId('profile-picture');
const userDisplayName = await findByText(mockItem.displayName);
expect(avatar).toBeInTheDocument();
expect(cardContainer).toBeInTheDocument();
act(() => {
fireEvent.click(userDisplayName);
});
expect(onClick).toBeCalled();
});
it('Data should render', async () => {
const { container } = render(
<UserDataCard
item={mockItem}
onClick={mockSelect}
onDelete={mockDelete}
/>,
{
wrapper: MemoryRouter,
}
const { findByTestId } = render(<UserDataCard {...mockProp} />, {
wrapper: MemoryRouter,
});
expect(await findByTestId('data-container')).toBeInTheDocument();
});
it('Inacive status should be shown for deleted user', () => {
const { getByText } = render(
<UserDataCard {...mockProp} item={{ ...mockItem, isActiveUser: false }} />
);
expect(await findByTestId(container, 'data-container')).toBeInTheDocument();
expect(getByText('Inactive')).toBeInTheDocument();
});
it('User should get removed when deleted', async () => {
const { queryByText, getByTestId } = render(
<UserDataCard {...mockProp} item={{ ...mockItem, id: 'id1' }} />
);
const removeButton = getByTestId('remove');
expect(queryByText(mockItem.displayName)).toBeInTheDocument();
expect(removeButton).toBeInTheDocument();
act(() => {
fireEvent.click(removeButton);
});
expect(onDelete).toHaveBeenCalledWith('id1', mockItem.displayName);
});
});

View File

@ -18,7 +18,7 @@ import SVGIcons, { Icons } from '../../utils/SvgUtils';
import NonAdminAction from '../common/non-admin-action/NonAdminAction';
import ProfilePicture from '../common/ProfilePicture/ProfilePicture';
type Item = {
export type Item = {
displayName: string;
name: string;
id?: string;
@ -28,7 +28,7 @@ type Item = {
teamCount?: string | JSX.Element;
};
type Props = {
export type Props = {
item: Item;
showTeams?: boolean;
onClick?: (value: string) => void;
@ -42,9 +42,9 @@ const UserDataCard = ({ item, onClick, onDelete, showTeams = true }: Props) => {
data-testid="user-card-container">
<div className="tw-flex tw-gap-1">
<ProfilePicture
displayName={item?.displayName}
id={item?.id || ''}
name={item?.name || ''}
displayName={item.displayName}
id={item.id || ''}
name={item.name || ''}
/>
<div
@ -60,7 +60,7 @@ const UserDataCard = ({ item, onClick, onDelete, showTeams = true }: Props) => {
}}>
{item.displayName}
</p>
{!item?.isActiveUser && (
{!item.isActiveUser && (
<span className="tw-text-xs tw-bg-badge tw-border tw-px-2 tw-py-0.5 tw-rounded">
Inactive
</span>
@ -81,7 +81,7 @@ const UserDataCard = ({ item, onClick, onDelete, showTeams = true }: Props) => {
onClick={(e) => {
e.preventDefault();
e.stopPropagation();
onDelete(item.id as string, item.displayName);
onDelete?.(item.id as string, item.displayName);
}}>
<SVGIcons
alt="delete"

View File

@ -0,0 +1,270 @@
/*
* Copyright 2022 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import { act } from 'react-test-renderer';
import { User } from '../../generated/entity/teams/user';
import { Paging } from '../../generated/type/paging';
import UserDetails, { UserDetailsProps } from './UserDetails';
const mockSelectedUserList: User[] = [
{
id: 'c46c12e4-9ac1-4e19-ad64-808424202a1a',
name: 'aaron_johnson1',
fullyQualifiedName: 'aaron_johnson1',
displayName: 'Aaron Johnson',
version: 0.1,
updatedAt: 1654840577439,
updatedBy: 'anonymous',
email: 'aaron_johnson1@gmail.com',
href: 'http://localhost:8585/api/v1/users/c46c12e4-9ac1-4e19-ad64-808424202a1c',
isAdmin: false,
teams: [
{
id: 'd9dfc1c5-b911-4a98-8e37-89db193ef9a3',
type: 'team',
name: 'Cloud_Infra',
fullyQualifiedName: 'Cloud_Infra',
description: 'This is Cloud_Infra description.',
displayName: 'Cloud_Infra',
deleted: false,
href: 'http://localhost:8585/api/v1/teams/d9dfc1c5-b911-4a98-8e37-89db193ef9a3',
},
{
id: '36fa08ff-ce50-4f74-986d-27878dc642b3',
type: 'team',
name: 'Finance',
fullyQualifiedName: 'Finance',
description: 'This is Finance description.',
displayName: 'Finance',
deleted: false,
href: 'http://localhost:8585/api/v1/teams/36fa08ff-ce50-4f74-986d-27878dc642b3',
},
],
deleted: false,
roles: [],
inheritedRoles: [
{
id: '99f67d8c-b3cd-4e5f-9230-1e6b39074e4d',
type: 'role',
name: 'DataConsumer',
fullyQualifiedName: 'DataConsumer',
description:
'Users with Data Consumer role use different data assets for their day to day work.',
displayName: 'Data Consumer',
deleted: false,
href: 'http://localhost:8585/api/v1/roles/99f67d8c-b3cd-4e5f-9230-1e6b39074e4d',
},
],
},
{
id: 'c46c12e4-9ac1-4e19-ad64-808424202a1b',
name: 'aaron_johnson2',
fullyQualifiedName: 'aaron_johnson2',
displayName: 'Aaron Johnson',
version: 0.1,
updatedAt: 1654840577439,
updatedBy: 'anonymous',
email: 'aaron_johnson2@gmail.com',
href: 'http://localhost:8585/api/v1/users/c46c12e4-9ac1-4e19-ad64-808424202a1c',
isAdmin: false,
teams: [
{
id: 'd9dfc1c5-b911-4a98-8e37-89db193ef9a3',
type: 'team',
name: 'Cloud_Infra',
fullyQualifiedName: 'Cloud_Infra',
description: 'This is Cloud_Infra description.',
displayName: 'Cloud_Infra',
deleted: false,
href: 'http://localhost:8585/api/v1/teams/d9dfc1c5-b911-4a98-8e37-89db193ef9a3',
},
],
deleted: false,
roles: [],
inheritedRoles: [
{
id: '99f67d8c-b3cd-4e5f-9230-1e6b39074e4d',
type: 'role',
name: 'DataConsumer',
fullyQualifiedName: 'DataConsumer',
description:
'Users with Data Consumer role use different data assets for their day to day work.',
displayName: 'Data Consumer',
deleted: false,
href: 'http://localhost:8585/api/v1/roles/99f67d8c-b3cd-4e5f-9230-1e6b39074e4d',
},
],
},
{
id: 'c46c12e4-9ac1-4e19-ad64-808424202a1c',
name: 'aaron_johnson3',
fullyQualifiedName: 'aaron_johnson3',
displayName: 'Aaron Johnson3',
version: 0.1,
updatedAt: 1654840577439,
updatedBy: 'anonymous',
email: 'aaron_johnson3@gmail.com',
href: 'http://localhost:8585/api/v1/users/c46c12e4-9ac1-4e19-ad64-808424202a1c',
isAdmin: false,
teams: [
{
id: 'd9dfc1c5-b911-4a98-8e37-89db193ef9a3',
type: 'team',
name: 'Cloud_Infra',
fullyQualifiedName: 'Cloud_Infra',
description: 'This is Cloud_Infra description.',
displayName: 'Cloud_Infra',
deleted: false,
href: 'http://localhost:8585/api/v1/teams/d9dfc1c5-b911-4a98-8e37-89db193ef9a3',
},
],
deleted: false,
roles: [],
inheritedRoles: [
{
id: '99f67d8c-b3cd-4e5f-9230-1e6b39074e4d',
type: 'role',
name: 'DataConsumer',
fullyQualifiedName: 'DataConsumer',
description:
'Users with Data Consumer role use different data assets for their day to day work.',
displayName: 'Data Consumer',
deleted: false,
href: 'http://localhost:8585/api/v1/roles/99f67d8c-b3cd-4e5f-9230-1e6b39074e4d',
},
],
},
];
const mockUserPaging: Paging = {
after: 'YW1hbmRhX3lvcms4',
total: 101,
};
const mockProps: UserDetailsProps = {
selectedUserList: mockSelectedUserList,
handleUserSearchTerm: jest.fn(),
userSearchTerm: '',
isUsersLoading: false,
currentUserPage: 1,
userPaging: mockUserPaging,
userPagingHandler: jest.fn(),
handleDeleteUser: jest.fn(),
};
jest.mock('react-router-dom', () => ({
useHistory: () => ({
push: jest.fn(),
}),
}));
jest.mock('../common/error-with-placeholder/ErrorPlaceHolder', () => {
return jest.fn().mockReturnValue(<p>ErrorPlaceHolder</p>);
});
jest.mock('../common/next-previous/NextPrevious', () => {
return jest.fn().mockReturnValue(<p>NextPrevious</p>);
});
jest.mock('../common/popover/PopOver', () => {
return jest.fn().mockReturnValue(<p>PopOver</p>);
});
jest.mock('../common/searchbar/Searchbar', () => {
return jest.fn().mockReturnValue(<p>Searchbar</p>);
});
jest.mock('../Loader/Loader', () => {
return jest.fn().mockReturnValue(<p>Loader</p>);
});
jest.mock('../Modals/ConfirmationModal/ConfirmationModal', () => {
return jest.fn().mockReturnValue(<p>ConfirmationModal</p>);
});
jest.mock('../UserDataCard/UserDataCard', () => {
return jest.fn().mockImplementation(({ onClick, onDelete }) => (
<>
<p>UserDataCard</p>
<p onClick={() => onClick(mockSelectedUserList[1].name)}>
handleUserRedirection
</p>
<p
onClick={() =>
onDelete(
mockSelectedUserList[1].id,
mockSelectedUserList[1].displayName
)
}>
handleDeleteUserModal
</p>
</>
));
});
describe('Test UserDetails component', () => {
it('Checking if loader is diplaying properly while loading the user list', () => {
const { getByText } = render(<UserDetails {...mockProps} isUsersLoading />);
const loader = getByText('Loader');
expect(loader).toBeInTheDocument();
});
it('Checking if all the components are rendering properly after getting list of users', () => {
const { getByText, getAllByText } = render(<UserDetails {...mockProps} />);
const searchBar = getByText('Searchbar');
const userDataCard = getAllByText('UserDataCard');
const nextPrevious = getByText('NextPrevious');
const handleUserRedirection = getAllByText('handleUserRedirection');
expect(searchBar).toBeInTheDocument();
expect(userDataCard.length).toBe(3);
expect(nextPrevious).toBeInTheDocument();
act(() => {
fireEvent.click(handleUserRedirection[0]);
});
});
it('Checking if conformationModal is showing after delete button clicked', () => {
const { getAllByText, queryByText } = render(
<UserDetails {...mockProps} />
);
const userDataCard = getAllByText('UserDataCard');
const handleDeleteUserModal = getAllByText('handleDeleteUserModal');
expect(userDataCard.length).toBe(3);
expect(queryByText('ConfirmationModal')).toBeNull();
act(() => {
fireEvent.click(handleDeleteUserModal[0]);
});
expect(queryByText('ConfirmationModal')).toBeInTheDocument();
});
it('Checking if Error is displayed when no users present', () => {
const { getByText } = render(
<UserDetails {...mockProps} selectedUserList={[]} />
);
const errorPlaceHolder = getByText('ErrorPlaceHolder');
expect(errorPlaceHolder).toBeInTheDocument();
});
});

View File

@ -26,7 +26,7 @@ import Loader from '../Loader/Loader';
import ConfirmationModal from '../Modals/ConfirmationModal/ConfirmationModal';
import UserDataCard from '../UserDataCard/UserDataCard';
type UserDetailsProps = {
export type UserDetailsProps = {
selectedUserList: User[];
handleUserSearchTerm: (value: string) => void;
userSearchTerm: string;

View File

@ -48,7 +48,15 @@ afterAll(() => {
describe('Test ProfilePicture component', () => {
it('ProfilePicture component should render with Avatar', async () => {
const { container } = render(<ProfilePicture {...mockData} />);
const { container } = render(
<ProfilePicture
{...mockData}
className=""
textClass=""
type="square"
width="36"
/>
);
const avatar = await findByText(container, 'Avatar');

View File

@ -0,0 +1,42 @@
/*
* Copyright 2022 Collate
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* http://www.apache.org/licenses/LICENSE-2.0
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { render } from '@testing-library/react';
import React from 'react';
import Avatar from './Avatar';
describe('Test for Avatar component', () => {
it('Component should render avatar properly', () => {
const { getByTestId } = render(<Avatar name="Avatar1" />);
const avatar = getByTestId('avatar');
expect(avatar).toBeInTheDocument();
});
it('Component should render avatar properly for type circle', () => {
const { getByTestId } = render(
<Avatar
className=""
name="Avatar1"
textClass=""
type="circle"
width="36"
/>
);
const avatar = getByTestId('avatar');
expect(avatar).toBeInTheDocument();
});
});

View File

@ -37,6 +37,7 @@ const Avatar = ({
'tw-flex tw-flex-shrink-0 tw-justify-center tw-items-center tw-align-middle',
className
)}
data-testid="avatar"
style={{
height: `${width}px`,
width: `${width}px`,

View File

@ -11,15 +11,20 @@
* limitations under the License.
*/
import { getByTestId, render } from '@testing-library/react';
import { getByTestId, getByText, render } from '@testing-library/react';
import React from 'react';
import ErrorPlaceHolder from './ErrorPlaceHolder';
describe('Test Error place holder Component', () => {
it('Component should render', () => {
const { container } = render(<ErrorPlaceHolder />);
const { container } = render(
<ErrorPlaceHolder>
<p>Children1</p>
</ErrorPlaceHolder>
);
expect(getByTestId(container, 'error')).toBeInTheDocument();
expect(getByTestId(container, 'no-data-image')).toBeInTheDocument();
expect(getByText(container, 'Children1')).toBeInTheDocument();
});
});

View File

@ -15,6 +15,14 @@ import { getByTestId, getByText, render } from '@testing-library/react';
import React from 'react';
import ErrorPlaceHolderES from './ErrorPlaceHolderES';
jest.mock('../../../AppState', () => ({
userDetails: {
name: 'testUser',
displayName: 'Test User',
},
users: [{ name: 'user1', displayName: 'User1DN' }],
}));
jest.mock('../../../authentication/auth-provider/AuthProvider', () => {
return {
useAuthContext: jest.fn(() => ({
@ -26,15 +34,6 @@ jest.mock('../../../authentication/auth-provider/AuthProvider', () => {
};
});
jest.mock('../../../AppState', () =>
jest.fn().mockReturnValue({
users: [],
userDetails: {
displayName: 'Test User',
},
})
);
const mockErrorMessage =
'An exception with message [Elasticsearch exception [type=index_not_found_exception, reason=no such index [test_search_index]]] was thrown while processing request.';

View File

@ -11,8 +11,9 @@
* limitations under the License.
*/
import { render } from '@testing-library/react';
import { fireEvent, render } from '@testing-library/react';
import React from 'react';
import { act } from 'react-test-renderer';
import { PAGE_SIZE } from '../../../constants/constants';
import NextPrevious from './NextPrevious';
@ -223,4 +224,39 @@ describe('Test Pagination Component', () => {
expect(pageIndicator).toHaveTextContent(`${totalPage}/${totalPage} Page`);
});
it('On clicking Previous and Next button respective pages should be rendered', () => {
const paging = {
before: 'test',
after: '',
total: PAGE_SIZE * 2,
};
const totalPage = computeTotalPages(PAGE_SIZE, PAGE_SIZE * 2);
const { getByTestId } = render(
<NextPrevious
isNumberBased
currentPage={totalPage}
pageSize={PAGE_SIZE}
paging={paging}
pagingHandler={mockCallback}
totalCount={paging.total}
/>
);
const nextButton = getByTestId('next');
const prevButton = getByTestId('previous');
act(() => {
fireEvent.click(prevButton);
});
expect(mockCallback).toHaveBeenCalledTimes(1);
act(() => {
fireEvent.click(nextButton);
});
expect(mockCallback).toHaveBeenCalledTimes(2);
});
});

View File

@ -11,8 +11,9 @@
* limitations under the License.
*/
import { findByTestId, render } from '@testing-library/react';
import { findByTestId, fireEvent, render } from '@testing-library/react';
import React from 'react';
import { act } from 'react-test-renderer';
import NonAdminAction from './NonAdminAction';
const mockAuth = {
@ -66,13 +67,23 @@ describe('Test AddUsersModal component', () => {
mockAuth.userPermissions.UpdateOwner = false;
const { container } = render(
<NonAdminAction title="test popup" trigger="click">
<NonAdminAction
className=""
isOwner={false}
position="top"
title="test popup"
trigger="click">
<button data-testid="test-button">test</button>
</NonAdminAction>
);
const popover = await findByTestId(container, 'popover');
const testButton = await findByTestId(container, 'test-button');
expect(popover).toBeInTheDocument();
act(() => {
fireEvent.click(testButton);
});
});
});

View File

@ -35,7 +35,16 @@ global.document.createRange = () => ({
describe('Test Popover Component', () => {
it('Component should render', () => {
const { container } = render(
<PopOver position="bottom" trigger="click">
<PopOver
arrow
className=""
delay={500}
hideDelay={0}
position="bottom"
size="regular"
sticky={false}
theme="dark"
trigger="click">
<span>Hello World</span>
</PopOver>
);

View File

@ -36,13 +36,20 @@ describe('Test Searchbar Component', () => {
it('Renders the user typed text when a change event is fired', () => {
const onSearch = jest.fn();
const { container } = render(<Searchbar onSearch={onSearch} />);
const { container, getByText } = render(
<Searchbar showLoadingStatus label="Test Label" onSearch={onSearch} />
);
const searchElement = getByTestId(container, 'searchbar');
expect(searchElement.value).toBe('');
expect(getByText('Test Label')).toBeInTheDocument();
fireEvent.focus(searchElement);
fireEvent.change(searchElement, { target: { value: 'Test Search' } });
fireEvent.blur(searchElement);
expect(searchElement.value).toBe('Test Search');
});