Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

test(ui): unit tests for users tab and user profile icon component #15053

Merged
merged 11 commits into from
Feb 7, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ export const UserProfileIcon = () => {
useEffect(() => {
if (profilePicture) {
setIsImgUrlValid(true);
} else {
setIsImgUrlValid(false);
}
}, [profilePicture]);

Expand All @@ -133,7 +135,9 @@ export const UserProfileIcon = () => {

const personaLabelRenderer = useCallback(
(item: EntityReference) => (
<span onClick={() => handleSelectedPersonaChange(item)}>
<span
data-testid="persona-label"
onClick={() => handleSelectedPersonaChange(item)}>
{getEntityName(item)}{' '}
{selectedPersona?.id === item.id && (
<CheckOutlined className="m-l-xs" style={{ color: '#4CAF50' }} />
Expand Down Expand Up @@ -318,6 +322,7 @@ export const UserProfileIcon = () => {
<img
alt="user"
className="app-bar-user-avatar"
data-testid="app-bar-user-avatar"
harsh-vador marked this conversation as resolved.
Show resolved Hide resolved
referrerPolicy="no-referrer"
src={profilePicture ?? ''}
onError={handleOnImageError}
Expand All @@ -333,6 +338,7 @@ export const UserProfileIcon = () => {
</Tooltip>
<Typography.Text
className="text-grey-muted text-xs w-28"
data-testid="default-persona"
ellipsis={{ tooltip: true }}>
{isEmpty(selectedPersona)
? t('label.default')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
/*
* Copyright 2024 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, screen } from '@testing-library/react';
import React from 'react';
import { getImageWithResolutionAndFallback } from '../../../utils/ProfilerUtils';
import { useApplicationConfigContext } from '../../ApplicationConfigProvider/ApplicationConfigProvider';
import { useAuthContext } from '../../Auth/AuthProviders/AuthProvider';
import { mockUserData } from '../mocks/User.mocks';
import { UserProfileIcon } from './UserProfileIcon.component';

const mockLogout = jest.fn();
const mockUpdateSelectedPersona = jest.fn();

jest.mock('../../ApplicationConfigProvider/ApplicationConfigProvider', () => ({
useApplicationConfigContext: jest.fn().mockImplementation(() => ({
selectedPersona: {},
updateSelectedPersona: mockUpdateSelectedPersona,
})),
}));

jest.mock('../../../utils/EntityUtils', () => ({
getEntityName: jest.fn().mockReturnValue('Test User'),
}));

jest.mock('../../../utils/ProfilerUtils', () => ({
getImageWithResolutionAndFallback: jest
.fn()
.mockImplementation(() => 'valid-image-url'),
ImageQuality: jest.fn().mockReturnValue('6x'),
}));

jest.mock('../../common/AvatarComponent/Avatar', () =>
jest.fn().mockReturnValue(<div>Avatar</div>)
);

jest.mock('react-router-dom', () => ({
Link: jest
.fn()
.mockImplementation(({ children }: { children: React.ReactNode }) => (
<p data-testid="link">{children}</p>
)),
}));

jest.mock('../../Auth/AuthProviders/AuthProvider', () => ({
useAuthContext: jest.fn(() => ({
currentUser: mockUserData,
})),
onLogoutHandler: mockLogout,
}));

describe('UserProfileIcon', () => {
it('should render User Profile Icon', () => {
const { getByTestId } = render(<UserProfileIcon />);

expect(getByTestId('dropdown-profile')).toBeInTheDocument();
});

it('should display the user name', () => {
const { getByText } = render(<UserProfileIcon />);

expect(getByText('Test User')).toBeInTheDocument();
});

it('should display default in case of no persona is selected', () => {
const { getByText } = render(<UserProfileIcon />);

expect(getByText('label.default')).toBeInTheDocument();
});

it('should display image if image url is valid', () => {
const { getByTestId } = render(<UserProfileIcon />);

expect(getByTestId('app-bar-user-avatar')).toBeInTheDocument();
});

it('should not display avatar if image url is valid', () => {
harsh-vador marked this conversation as resolved.
Show resolved Hide resolved
(getImageWithResolutionAndFallback as jest.Mock).mockImplementation(
() => undefined
);
const { queryByTestId, getByText } = render(<UserProfileIcon />);

expect(queryByTestId('app-bar-user-avatar')).not.toBeInTheDocument();
harsh-vador marked this conversation as resolved.
Show resolved Hide resolved
expect(getByText('Avatar')).toBeInTheDocument();
});

it('should display the user team', () => {
(useApplicationConfigContext as jest.Mock).mockImplementation(() => ({
selectedPersona: {
id: '3362fe18-05ad-4457-9632-84f22887dda6',
type: 'team',
},
updateSelectedPersona: jest.fn(),
}));
const { getByTestId } = render(<UserProfileIcon />);

expect(getByTestId('default-persona')).toHaveTextContent('Test User');
});

it('should show empty placeholder when no teams data', async () => {
(useAuthContext as jest.Mock).mockImplementation(() => ({
currentUser: { ...mockUserData, teams: [] },
onLogoutHandler: mockLogout,
}));
const teamLabels = screen.queryAllByText('label.team-plural');

teamLabels.forEach((label) => {
expect(label).toHaveTextContent('--');
});
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
/*
* Copyright 2024 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 { act, fireEvent, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { getUserById } from '../../../rest/userAPI';
import { mockUsersTabData } from '../mocks/User.mocks';
import { UsersTab } from './UsersTabs.component';

jest.mock('../../../rest/userAPI', () => ({
getUserById: jest
.fn()
.mockImplementation(() => Promise.resolve(mockUsersTabData)),
}));

jest.mock('../../common/PopOverCard/UserPopOverCard', () =>
jest.fn().mockReturnValue('Aaron Johnson')
);
jest.mock('react-router-dom', () => ({
Link: jest
.fn()
.mockImplementation(({ children }: { children: React.ReactNode }) => (
<p data-testid="link">{children}</p>
)),
useHistory: jest.fn(),
}));

const mockUsers = [
{
deleted: false,
displayName: 'Aaron Johnson',
fullyQualifiedName: 'aaron_johnson0',
href: 'http://localhost:8585/api/v1/users/f281e7fd-5fd3-4279-8a2d-ade80febd743',
id: 'f281e7fd-5fd3-4279-8a2d-ade80febd743',
name: 'aaron_johnson0',
type: 'user',
},
];

const mockOnRemoveUser = jest.fn();

describe('UsersTab', () => {
it('should renders Users Tab', async () => {
await act(async () => {
render(<UsersTab users={mockUsers} onRemoveUser={mockOnRemoveUser} />, {
wrapper: MemoryRouter,
});
});

expect(await screen.findByText('label.username')).toBeInTheDocument();
expect(await screen.findByText('label.team-plural')).toBeInTheDocument();
expect(await screen.findByText('label.role-plural')).toBeInTheDocument();
expect(await screen.findByText('label.action-plural')).toBeInTheDocument();
});

it('should display the user details', async () => {
await act(async () => {
render(<UsersTab users={mockUsers} onRemoveUser={mockOnRemoveUser} />, {
wrapper: MemoryRouter,
});
});

expect(await screen.findByText('Aaron Johnson')).toBeInTheDocument();
expect(await screen.findByText('Sales')).toBeInTheDocument();
expect(await screen.findByText('Data Steward')).toBeInTheDocument();
});

it('should render empty placeholder if no data', async () => {
(getUserById as jest.Mock).mockImplementation(() => Promise.resolve([])),
await act(async () => {
render(<UsersTab users={[]} onRemoveUser={mockOnRemoveUser} />, {
wrapper: MemoryRouter,
});
});

expect(
await screen.findByTestId('assign-error-placeholder-label.user')
).toBeInTheDocument();
});

it('should display the remove confirmation modal when remove button is clicked', async () => {
await act(async () => {
render(<UsersTab users={mockUsers} onRemoveUser={mockOnRemoveUser} />, {
wrapper: MemoryRouter,
});
});
await act(async () => {
fireEvent.click(screen.getByTestId('remove-user-btn'));
});

expect(
await screen.getByTestId('remove-confirmation-modal')
).toBeInTheDocument();
});

it('should close the remove confirmation modal when cancel button is clicked', async () => {
await act(async () => {
render(<UsersTab users={mockUsers} onRemoveUser={mockOnRemoveUser} />, {
wrapper: MemoryRouter,
});
});
await act(async () => {
userEvent.click(screen.getByTestId('remove-user-btn'));
});
await act(async () => {
userEvent.click(screen.getByText('label.cancel'));
});

expect(
screen.queryByTestId('remove-confirmation-modal')
).not.toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -134,20 +134,22 @@ export const UsersTab = ({ users, onRemoveUser }: UsersTabProps) => {
rowKey="fullyQualifiedName"
size="small"
/>
<Modal
cancelText={t('label.cancel')}
data-testid="remove-confirmation-modal"
okText={t('label.confirm')}
open={Boolean(removeUserDetails?.state)}
title={t('label.removing-user')}
onCancel={handleRemoveCancel}
onOk={handleRemoveConfirm}>
{t('message.are-you-sure-want-to-text', {
text: t('label.remove-entity-lowercase', {
entity: removeUserDetails?.user.name,
}),
})}
</Modal>
{Boolean(removeUserDetails?.state) && (
<Modal
cancelText={t('label.cancel')}
data-testid="remove-confirmation-modal"
okText={t('label.confirm')}
open={Boolean(removeUserDetails?.state)}
title={t('label.removing-user')}
onCancel={handleRemoveCancel}
onOk={handleRemoveConfirm}>
{t('message.are-you-sure-want-to-text', {
text: t('label.remove-entity-lowercase', {
entity: removeUserDetails?.user.name,
}),
})}
</Modal>
)}
</>
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,71 @@ export const mockTeamsData = {
],
paging: { total: 7 },
};
export const mockUsersTabData = {
id: 'f281e7fd-5fd3-4279-8a2d-ade80febd743',
name: 'aaron_johnson0',
fullyQualifiedName: 'aaron_johnson0',
displayName: 'Aaron Johnson',
version: 1.5,
updatedAt: 1707198736848,
updatedBy: 'admin',
email: 'aaron_johnson0@gmail.com',
href: 'http://localhost:8585/api/v1/users/f281e7fd-5fd3-4279-8a2d-ade80febd743',
isAdmin: false,
teams: [
{
id: '35c03c5c-5160-41af-b08d-ef2b2b9e6adf',
type: 'team',
name: 'Sales',
fullyQualifiedName: 'Sales',
deleted: false,
href: 'http://localhost:8585/api/v1/teams/35c03c5c-5160-41af-b08d-ef2b2b9e6adf',
},
],
changeDescription: {
fieldsAdded: [
{
name: 'teams',
newValue:
'[{"id":"35c03c5c-5160-41af-b08d-ef2b2b9e6adf","type":"team","name":"Sales","fullyQualifiedName":"Sales","deleted":false}]',
},
],
fieldsUpdated: [],
fieldsDeleted: [
{
name: 'teams',
oldValue: '[{"id":"f39f326a-81a1-42ca-976]',
},
],
previousVersion: 1.4,
},
deleted: false,
roles: [
{
id: 'e4b20aef-c6c4-4416-aaae-f60185c7cac0',
type: 'role',
name: 'DataSteward',
fullyQualifiedName: 'DataSteward',
description: 'Users with Data Steward',
displayName: 'Data Steward',
deleted: false,
href: 'http://localhost:8585/api/v1/roles/e4b20aef-c6c4-4416-aaae-f60185c7cac0',
},
],
inheritedRoles: [
{
id: '5f1445a7-c299-4dde-8c5b-704c6cd68ee6',
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/5f1445a7-c299-4dde-8c5b-704c6cd68ee6',
},
],
};

export const mockUserRole = {
data: [
Expand Down
Loading