Minor: improve unit test coverage (#15061)

* Minor: improve unit test coverage

* added unit test for persona page component

* remove unwanted code

* added unit test for reset password component

* added test for tour page

* added unit test for quillLink

* re-name the testUtils to jestTestUtils

* miner fix

* miner fix
This commit is contained in:
Shailesh Parmar 2024-02-08 15:55:09 +05:30 committed by GitHub
parent 24bbed8496
commit 2d24930f12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 732 additions and 181 deletions

View File

@ -0,0 +1,187 @@
/*
* 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 { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { getPersonaByName, updatePersona } from '../../../rest/PersonaAPI';
import { PersonaDetailsPage } from './PersonaDetailsPage';
jest.mock('../../../components/PageLayoutV1/PageLayoutV1', () => {
return jest.fn().mockImplementation(({ children }) => <div>{children}</div>);
});
jest.mock('../../../components/PageHeader/PageHeader.component', () => {
return jest.fn().mockImplementation(() => <div>PageHeader.component</div>);
});
jest.mock('../../../components/common/EntityDescription/DescriptionV1', () => {
return jest.fn().mockImplementation(() => <div>DescriptionV1.component</div>);
});
jest.mock(
'../../../components/common/EntityPageInfos/ManageButton/ManageButton',
() => {
return jest
.fn()
.mockImplementation(({ afterDeleteAction, onEditDisplayName }) => (
<div>
ManageButton.component
<button data-testid="delete-btn" onClick={afterDeleteAction}>
Delete
</button>
<button
data-testid="display-name-btn"
onClick={() => onEditDisplayName({ displayName: 'Updated Name' })}>
Update Display Name
</button>
</div>
));
}
);
jest.mock(
'../../../components/common/UserSelectableList/UserSelectableList.component',
() => {
return {
UserSelectableList: jest
.fn()
.mockImplementation(({ children, onUpdate }) => (
<div
data-testid="user-selectable-list"
onClick={() => onUpdate({ id: 'ID', type: 'user' })}>
{children}
</div>
)),
};
}
);
const mockPersona = {
id: '3cd223f3-fe1f-4ed8-9d74-dc80f5f91838',
name: 'testPersona',
fullyQualifiedName: 'testPersona',
displayName: 'Test Persona',
users: [
{
id: '1496d0c6-ebb2-453c-a8cc-b275c553f61f',
type: 'user',
name: 'admin',
fullyQualifiedName: 'admin',
},
],
};
jest.mock('../../../rest/PersonaAPI', () => {
return {
getPersonaByName: jest
.fn()
.mockImplementation(() => Promise.resolve(mockPersona)),
updatePersona: jest.fn().mockImplementation(() => Promise.resolve()),
};
});
jest.mock('../../../hooks/useFqn', () => {
return { useFqn: jest.fn().mockReturnValue({ fqn: 'fqn' }) };
});
const mockUseHistory = {
push: jest.fn(),
};
jest.mock('react-router-dom', () => {
return {
useHistory: jest.fn().mockImplementation(() => mockUseHistory),
};
});
jest.mock(
'../../../components/common/ErrorWithPlaceholder/NoDataPlaceholder',
() => {
return jest
.fn()
.mockImplementation(() => <div>NoDataPlaceholder.component</div>);
}
);
jest.mock('../../../components/PermissionProvider/PermissionProvider', () => ({
usePermissionProvider: jest.fn().mockReturnValue({
getEntityPermissionByFqn: jest.fn().mockResolvedValue({
Create: true,
Delete: true,
ViewAll: true,
EditAll: true,
EditDescription: true,
EditDisplayName: true,
EditCustomFields: true,
}),
}),
}));
describe('PersonaDetailsPage', () => {
it('Component should render', async () => {
render(<PersonaDetailsPage />);
expect(await screen.findByText('PageHeader.component')).toBeInTheDocument();
expect(
await screen.findByText('ManageButton.component')
).toBeInTheDocument();
expect(
await screen.findByText('DescriptionV1.component')
).toBeInTheDocument();
expect(await screen.findByTestId('add-user-button')).toBeInTheDocument();
});
it('NoDataPlaceholder', async () => {
(getPersonaByName as jest.Mock).mockImplementationOnce(() =>
Promise.reject()
);
render(<PersonaDetailsPage />);
expect(
await screen.findByText('NoDataPlaceholder.component')
).toBeInTheDocument();
});
it('handleAfterDeleteAction should call after delete', async () => {
render(<PersonaDetailsPage />);
const deleteBtn = await screen.findByTestId('delete-btn');
fireEvent.click(deleteBtn);
expect(mockUseHistory.push).toHaveBeenCalledWith(
'/settings/members/persona'
);
});
it('handleDisplayNameUpdate should call after updating displayName', async () => {
const mockUpdatePersona = updatePersona as jest.Mock;
render(<PersonaDetailsPage />);
const updateName = await screen.findByTestId('display-name-btn');
fireEvent.click(updateName);
expect(mockUpdatePersona).toHaveBeenCalledWith(mockPersona.id, [
{ op: 'replace', path: '/displayName', value: 'Updated Name' },
]);
});
it('add user should work', async () => {
const mockUpdatePersona = updatePersona as jest.Mock;
render(<PersonaDetailsPage />);
const addUser = await screen.findByTestId('user-selectable-list');
fireEvent.click(addUser);
expect(mockUpdatePersona).toHaveBeenCalledWith(mockPersona.id, [
{
op: 'replace',
path: '/users',
value: {
id: 'ID',
type: 'user',
},
},
]);
});
});

View File

@ -10,15 +10,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { Button, Row, Tabs } from 'antd';
import Col from 'antd/es/grid/col';
import { Button, Col, Row, Tabs } from 'antd';
import { AxiosError } from 'axios';
import { compare } from 'fast-json-patch';
import { isUndefined } from 'lodash';
import React, { useCallback, useEffect, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory } from 'react-router-dom';
import DescriptionV1 from '../../../components/common/EntityDescription/DescriptionV1';
import ManageButton from '../../../components/common/EntityPageInfos/ManageButton/ManageButton';
import NoDataPlaceholder from '../../../components/common/ErrorWithPlaceholder/NoDataPlaceholder';
import { UserSelectableList } from '../../../components/common/UserSelectableList/UserSelectableList.component';
import Loader from '../../../components/Loader/Loader';
import { EntityName } from '../../../components/Modals/EntityNameModal/EntityNameModal.interface';
@ -31,6 +32,7 @@ import {
GlobalSettingOptions,
GlobalSettingsMenuCategory,
} from '../../../constants/GlobalSettings.constants';
import { SIZE } from '../../../enums/common.enum';
import { EntityType } from '../../../enums/entity.enum';
import { Persona } from '../../../generated/entity/teams/persona';
import { useFqn } from '../../../hooks/useFqn';
@ -111,23 +113,6 @@ export const PersonaDetailsPage = () => {
}
};
const handleRestorePersona = async () => {
if (!personaDetails) {
return;
}
const updatedData = { ...personaDetails };
const diff = compare(personaDetails, updatedData);
try {
const response = await updatePersona(personaDetails?.id, diff);
setPersonaDetails(response);
} catch (error) {
showErrorToast(error as AxiosError);
} finally {
setIsEdit(false);
}
};
const handlePersonaUpdate = useCallback(
async (data: Partial<Persona>) => {
if (!personaDetails) {
@ -167,10 +152,14 @@ export const PersonaDetailsPage = () => {
);
};
if (isLoading || !personaDetails) {
if (isLoading) {
return <Loader />;
}
if (isUndefined(personaDetails)) {
return <NoDataPlaceholder size={SIZE.LARGE} />;
}
return (
<PageLayoutV1 pageTitle={personaDetails.name}>
<Row className="m-b-md page-container" gutter={[0, 16]}>
@ -196,7 +185,6 @@ export const PersonaDetailsPage = () => {
entityName={personaDetails.name}
entityType={EntityType.PERSONA}
onEditDisplayName={handleDisplayNameUpdate}
onRestoreEntity={handleRestorePersona}
/>
</div>
</Col>

View File

@ -0,0 +1,175 @@
/*
* 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 React from 'react';
import { getAllPersonas } from '../../../rest/PersonaAPI';
import { PersonaPage } from './PersonaPage';
jest.mock('../../../components/PageLayoutV1/PageLayoutV1', () => {
return jest.fn().mockImplementation(({ children }) => <div>{children}</div>);
});
jest.mock('../../../components/PageHeader/PageHeader.component', () => {
return jest.fn().mockImplementation(() => <div>PageHeader.component</div>);
});
jest.mock(
'../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component',
() => {
return jest
.fn()
.mockImplementation(() => <div>TitleBreadcrumb.component</div>);
}
);
jest.mock('../../../components/common/NextPrevious/NextPrevious', () => {
return jest.fn().mockImplementation(() => <div>NextPrevious.component</div>);
});
jest.mock(
'../../../components/common/ErrorWithPlaceholder/ErrorPlaceHolder',
() => {
return jest
.fn()
.mockImplementation(() => <div>ErrorPlaceHolder.component</div>);
}
);
jest.mock('../../../components/common/DeleteWidget/DeleteWidgetModal', () => {
return jest
.fn()
.mockImplementation(() => <div>DeleteWidgetModal.component</div>);
});
jest.mock(
'../../../components/Persona/PersonaDetailsCard/PersonaDetailsCard',
() => {
return {
PersonaDetailsCard: jest
.fn()
.mockImplementation(() => <div>PersonaDetailsCard.component</div>),
};
}
);
jest.mock(
'../../../components/Persona/AddEditPersona/AddEditPersona.component',
() => {
return {
AddEditPersonaForm: jest.fn().mockImplementation(({ onSave }) => (
<div>
AddEditPersonaForm.component
<button data-testid="save-edit-persona-save" onClick={onSave}>
Save
</button>
</div>
)),
};
}
);
jest.mock('../../../hooks/paging/usePaging', () => ({
usePaging: jest.fn().mockReturnValue({
currentPage: 1,
showPagination: true,
pageSize: 10,
handlePageChange: jest.fn(),
handlePagingChange: jest.fn(),
handlePageSizeChange: jest.fn(),
}),
}));
jest.mock('../../../rest/PersonaAPI', () => {
return {
getAllPersonas: jest
.fn()
.mockImplementation(() => Promise.resolve({ data: [] })),
};
});
describe('PersonaPage', () => {
it('Component should render', async () => {
act(() => {
render(<PersonaPage />);
});
expect(
await screen.findByTestId('user-list-v1-component')
).toBeInTheDocument();
expect(await screen.findByTestId('add-persona-button')).toBeInTheDocument();
expect(
await screen.findByText('TitleBreadcrumb.component')
).toBeInTheDocument();
expect(await screen.findByText('PageHeader.component')).toBeInTheDocument();
expect(
await screen.findByText('ErrorPlaceHolder.component')
).toBeInTheDocument();
});
it('AddEditPersonaForm should render onclick of add persona', async () => {
act(() => {
render(<PersonaPage />);
});
const addPersonaButton = await screen.findByTestId('add-persona-button');
await act(async () => {
fireEvent.click(addPersonaButton);
});
expect(
await screen.findByText('AddEditPersonaForm.component')
).toBeInTheDocument();
});
it('handlePersonaAddEditSave should be called onClick of save button', async () => {
const mockGetAllPersonas = getAllPersonas as jest.Mock;
act(() => {
render(<PersonaPage />);
});
const addPersonaButton = await screen.findByTestId('add-persona-button');
fireEvent.click(addPersonaButton);
expect(
await screen.findByText('AddEditPersonaForm.component')
).toBeInTheDocument();
await act(async () => {
fireEvent.click(await screen.findByTestId('save-edit-persona-save'));
});
expect(mockGetAllPersonas).toHaveBeenCalledWith({
after: undefined,
before: undefined,
fields: 'users',
limit: 10,
});
expect(mockGetAllPersonas).toHaveBeenCalledTimes(2);
});
it('should render PersonaDetailsCard when data is available', async () => {
(getAllPersonas as jest.Mock).mockImplementationOnce(() =>
Promise.resolve({
data: [
{
id: 'id1',
name: 'sales',
fullyQualifiedName: 'sales',
displayName: 'Sales',
},
{
id: 'id2',
name: 'purchase',
fullyQualifiedName: 'purchase',
displayName: 'purchase',
},
],
})
);
act(() => {
render(<PersonaPage />);
});
expect(
await screen.findAllByText('PersonaDetailsCard.component')
).toHaveLength(2);
});
});

View File

@ -15,7 +15,6 @@ import Card from 'antd/lib/card/Card';
import { isEmpty } from 'lodash';
import React, { useCallback, useEffect, useMemo, useState } from 'react';
import { useTranslation } from 'react-i18next';
import DeleteWidgetModal from '../../../components/common/DeleteWidget/DeleteWidgetModal';
import ErrorPlaceHolder from '../../../components/common/ErrorWithPlaceholder/ErrorPlaceHolder';
import NextPrevious from '../../../components/common/NextPrevious/NextPrevious';
import { PagingHandlerParams } from '../../../components/common/NextPrevious/NextPrevious.interface';
@ -28,13 +27,11 @@ import { PersonaDetailsCard } from '../../../components/Persona/PersonaDetailsCa
import { GlobalSettingsMenuCategory } from '../../../constants/GlobalSettings.constants';
import { PAGE_HEADERS } from '../../../constants/PageHeaders.constant';
import { ERROR_PLACEHOLDER_TYPE } from '../../../enums/common.enum';
import { EntityType } from '../../../enums/entity.enum';
import { Persona } from '../../../generated/entity/teams/persona';
import { Paging } from '../../../generated/type/paging';
import { useAuth } from '../../../hooks/authHooks';
import { usePaging } from '../../../hooks/paging/usePaging';
import { getAllPersonas } from '../../../rest/PersonaAPI';
import { getEntityName } from '../../../utils/EntityUtils';
import { getSettingPageEntityBreadCrumb } from '../../../utils/GlobalSettingsUtils';
export const PersonaPage = () => {
@ -46,8 +43,6 @@ export const PersonaPage = () => {
const [addEditPersona, setAddEditPersona] = useState<Persona>();
const [isLoading, setIsLoading] = useState(false);
const [personaDeleting, setPersonaDeleting] = useState<Persona>();
const {
currentPage,
handlePageChange,
@ -132,7 +127,7 @@ export const PersonaPage = () => {
<Row
className="user-listing page-container p-b-md"
data-testid="user-list-v1-component"
gutter={[0, 16]}>
gutter={[16, 16]}>
<Col span={24}>
<TitleBreadcrumb titleLinks={breadcrumbs} />
</Col>
@ -185,18 +180,6 @@ export const PersonaPage = () => {
onSave={handlePersonaAddEditSave}
/>
)}
<DeleteWidgetModal
afterDeleteAction={() => fetchPersonas()}
allowSoftDelete={false}
entityId={personaDeleting?.id ?? ''}
entityName={getEntityName(personaDeleting)}
entityType={EntityType.PERSONA}
visible={Boolean(personaDeleting)}
onCancel={() => {
setPersonaDeleting(undefined);
}}
/>
</Row>
</PageLayoutV1>
);

View File

@ -11,9 +11,10 @@
* limitations under the License.
*/
import { Alert, Button, Card, Col, Form, Input, Row, Typography } from 'antd';
import { Button, Card, Col, Form, Input, Row, Typography } from 'antd';
import { AxiosError } from 'axios';
import React, { useEffect, useMemo } from 'react';
import QueryString from 'qs';
import React, { useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useHistory, useLocation } from 'react-router-dom';
import { useBasicAuth } from '../../components/Auth/AuthProviders/BasicAuthProvider';
@ -21,7 +22,6 @@ import BrandImage from '../../components/common/BrandImage/BrandImage';
import { ROUTES, VALIDATION_MESSAGES } from '../../constants/constants';
import { passwordRegex } from '../../constants/regex.constants';
import { PasswordResetRequest } from '../../generated/auth/passwordResetRequest';
import { getUserNameAndToken } from '../../utils/ResetPassword.utils';
import { showErrorToast } from '../../utils/ToastUtils';
import './reset-password.style.less';
@ -36,24 +36,24 @@ const ResetPassword = () => {
const location = useLocation();
const { handleResetPassword } = useBasicAuth();
const tokenValid = false;
useEffect(() => {
// check for token validity
}, []);
const history = useHistory();
const params = useMemo(
() => getUserNameAndToken(location.search),
[location]
);
const params = useMemo(() => {
const search = location.search;
const data = QueryString.parse(
search.startsWith('?') ? search.substring(1) : search
);
return data as { token: string; user: string };
}, [location]);
const password = Form.useWatch('password', form);
const handleSubmit = async (data: ResetFormData) => {
const ResetRequest = {
token: params?.token,
username: params?.userName,
username: params?.user,
password: data.password,
confirmPassword: data.confirmPassword,
} as PasswordResetRequest;
@ -66,114 +66,93 @@ const ResetPassword = () => {
}
};
const handleReVerify = () => history.push(ROUTES.FORGOT_PASSWORD);
return (
<div className="h-full p-y-36">
{tokenValid ? (
<Card
bodyStyle={{ padding: '48px' }}
className="m-auto p-x-lg"
style={{ maxWidth: '450px' }}>
<div className="mt-24">
<Alert
showIcon
description="Please re-initiate email verification process"
message={t('message.email-verification-token-expired')}
type="error"
/>
</div>
<div className="h-full p-y-36" data-testid="reset-password-container">
<Card
bodyStyle={{ padding: '48px' }}
className="m-auto p-x-lg"
style={{ maxWidth: '450px' }}>
<Row gutter={[16, 24]}>
<Col className="text-center" data-testid="brand-image" span={24}>
<BrandImage className="m-auto" height="auto" width={200} />
</Col>
<div className="mt-20 flex-center">
<Typography.Link underline onClick={handleReVerify}>
{t('label.re-verify')}
</Typography.Link>
</div>
</Card>
) : (
<Card
bodyStyle={{ padding: '48px' }}
className="m-auto p-x-lg"
style={{ maxWidth: '450px' }}>
<Row gutter={[16, 24]}>
<Col className="text-center" span={24}>
<BrandImage className="m-auto" height="auto" width={200} />
</Col>
<Col className="mt-12 text-center" span={24}>
<Typography.Text className="text-xl font-medium text-grey-muted">
{t('label.reset-your-password')}
</Typography.Text>
</Col>
<Col className="mt-12 text-center" span={24}>
<Typography.Text className="text-xl font-medium text-grey-muted">
{t('label.reset-your-password')}
</Typography.Text>
</Col>
<Col span={24}>
<Form
className="w-full"
form={form}
layout="vertical"
validateMessages={VALIDATION_MESSAGES}
onFinish={handleSubmit}>
<Form.Item
label={t('label.new-password')}
name="password"
rules={[
{
required: true,
message: t('message.field-text-is-required', {
fieldText: t('label.password'),
}),
},
{
pattern: passwordRegex,
message: t('message.password-pattern-error'),
},
]}>
<Input.Password
autoComplete="off"
className="w-full"
data-testid="password"
placeholder={t('label.enter-entity', {
entity: t('label.new-password'),
})}
/>
</Form.Item>
<Form.Item
label={t('label.confirm-new-password')}
name="confirmPassword"
rules={[
{
required: true,
message: t('message.field-text-is-required', {
fieldText: t('label.confirm-new-password'),
}),
},
{
validator: (_, value) => {
if (password === value) {
return Promise.resolve();
}
<Col span={24}>
<Form
className="w-full"
form={form}
layout="vertical"
validateMessages={VALIDATION_MESSAGES}
onFinish={handleSubmit}>
<Form.Item
label={t('label.new-password')}
name="password"
rules={[
{
required: true,
message: t('message.field-text-is-required', {
fieldText: t('label.password'),
}),
return Promise.reject(t('label.password-not-match'));
},
{
pattern: passwordRegex,
message: t('message.password-pattern-error'),
},
]}>
<Input.Password
autoComplete="off"
className="w-full"
placeholder={t('label.enter-entity', {
entity: t('label.new-password'),
})}
/>
</Form.Item>
<Form.Item
label={t('label.confirm-new-password')}
name="confirmPassword"
rules={[
{
required: true,
message: t('message.field-text-is-required', {
fieldText: t('label.confirm-new-password'),
}),
},
{
validator: (_, value) => {
if (password === value) {
return Promise.resolve();
}
},
]}>
<Input.Password
autoComplete="off"
className="w-full"
data-testid="confirm-password"
placeholder={t('label.re-enter-new-password')}
/>
</Form.Item>
return Promise.reject(t('label.password-not-match'));
},
},
]}>
<Input.Password
autoComplete="off"
className="w-full"
placeholder={t('label.re-enter-new-password')}
/>
</Form.Item>
<Button
className="w-full m-t-lg"
htmlType="submit"
type="primary">
{t('label.submit')}
</Button>
</Form>
</Col>
</Row>
</Card>
)}
<Button
className="w-full m-t-lg"
data-testid="submit-button"
htmlType="submit"
type="primary">
{t('label.submit')}
</Button>
</Form>
</Col>
</Row>
</Card>
</div>
);
};

View File

@ -0,0 +1,107 @@
/*
* 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 React from 'react';
import ResetPassword from './ResetPassword.component';
jest.mock('react-router-dom', () => {
return {
useHistory: jest.fn(),
useLocation: jest
.fn()
.mockImplementation(() => ({ search: '?user=admin&token=token' })),
};
});
const mockHandleResetPassword = jest.fn();
jest.mock('../../components/Auth/AuthProviders/BasicAuthProvider', () => {
return {
useBasicAuth: jest.fn().mockImplementation(() => ({
handleResetPassword: mockHandleResetPassword,
})),
};
});
describe('ResetPassword', () => {
it('should render correctly', async () => {
render(<ResetPassword />);
expect(
await screen.findByTestId('reset-password-container')
).toBeInTheDocument();
expect(await screen.findByTestId('brand-image')).toBeInTheDocument();
expect(await screen.findByTestId('password')).toBeInTheDocument();
expect(await screen.findByTestId('confirm-password')).toBeInTheDocument();
expect(await screen.findByTestId('submit-button')).toBeInTheDocument();
expect(
await screen.findByText('label.reset-your-password')
).toBeInTheDocument();
});
it('form submit should work', async () => {
jest.useFakeTimers();
render(<ResetPassword />);
const submitButton = await screen.findByTestId('submit-button');
const password = await screen.findByTestId('password');
const confirmPwd = await screen.findByTestId('confirm-password');
await act(async () => {
fireEvent.change(password, { target: { value: 'Password@123' } });
fireEvent.change(confirmPwd, { target: { value: 'Password@123' } });
fireEvent.click(submitButton);
});
expect(mockHandleResetPassword).toHaveBeenCalledWith({
confirmPassword: 'Password@123',
password: 'Password@123',
token: 'token',
username: 'admin',
});
});
it('confirm password alert should be visible', async () => {
jest.useFakeTimers();
render(<ResetPassword />);
const submitButton = await screen.findByTestId('submit-button');
const password = await screen.findByTestId('password');
const confirmPwd = await screen.findByTestId('confirm-password');
await act(async () => {
fireEvent.change(password, { target: { value: 'Password@123' } });
fireEvent.change(confirmPwd, { target: { value: 'Password@1234' } });
fireEvent.click(submitButton);
});
jest.advanceTimersByTime(20);
expect(
await screen.findByText('label.password-not-match')
).toBeInTheDocument();
});
it('required field validation should work', async () => {
jest.useFakeTimers();
render(<ResetPassword />);
const submitButton = await screen.findByTestId('submit-button');
await act(async () => {
fireEvent.click(submitButton);
});
jest.advanceTimersByTime(20);
expect(
await screen.findAllByText('message.field-text-is-required')
).toHaveLength(2);
});
});

View File

@ -0,0 +1,105 @@
/*
* 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 { fireEvent, render, screen } from '@testing-library/react';
import React from 'react';
import { useTourProvider } from '../../components/TourProvider/TourProvider';
import { CurrentTourPageType } from '../../enums/tour.enum';
import TourPage from './TourPage.component';
const mockUseTourProvider = {
updateIsTourOpen: jest.fn(),
currentTourPage: '',
updateActiveTab: jest.fn(),
updateTourPage: jest.fn(),
updateTourSearch: jest.fn(),
};
jest.mock('../../components/TourProvider/TourProvider', () => ({
useTourProvider: jest.fn().mockImplementation(() => mockUseTourProvider),
}));
jest.mock('../../components/AppTour/Tour', () => {
return jest.fn().mockImplementation(({ steps }) => (
<div>
Tour.component{' '}
<button data-testid="clear-btn" onClick={steps.clearSearchTerm}>
clear
</button>
</div>
));
});
jest.mock('../MyDataPage/MyDataPage.component', () => {
return jest.fn().mockImplementation(() => <div>MyDataPage.component</div>);
});
jest.mock('../ExplorePage/ExplorePageV1.component', () => {
return jest
.fn()
.mockImplementation(() => <div>ExplorePageV1Component.component</div>);
});
jest.mock('../TableDetailsPageV1/TableDetailsPageV1', () => {
return jest
.fn()
.mockImplementation(() => <div>TableDetailsPageV1.component</div>);
});
jest.mock('../../utils/TourUtils', () => ({
getTourSteps: jest.fn().mockImplementation((props) => props),
}));
describe('TourPage component', () => {
it('should render correctly', async () => {
render(<TourPage />);
expect(await screen.findByText('Tour.component')).toBeInTheDocument();
});
it('clear search term should work correctly', async () => {
render(<TourPage />);
const clearBtn = await screen.findByTestId('clear-btn');
fireEvent.click(clearBtn);
expect(mockUseTourProvider.updateTourSearch).toHaveBeenCalledWith('');
});
it('MyDataPage Component should be visible, if currentTourPage is myDataPage', async () => {
(useTourProvider as jest.Mock).mockImplementationOnce(() => ({
...mockUseTourProvider,
currentTourPage: CurrentTourPageType.MY_DATA_PAGE,
}));
render(<TourPage />);
expect(await screen.findByText('MyDataPage.component')).toBeInTheDocument();
});
it('ExplorePage Component should be visible, if currentTourPage is explorePage', async () => {
(useTourProvider as jest.Mock).mockImplementationOnce(() => ({
...mockUseTourProvider,
currentTourPage: CurrentTourPageType.EXPLORE_PAGE,
}));
render(<TourPage />);
expect(
await screen.findByText('ExplorePageV1Component.component')
).toBeInTheDocument();
});
it('TableDetailsPage Component should be visible, if currentTourPage is datasetPage', async () => {
(useTourProvider as jest.Mock).mockImplementationOnce(() => ({
...mockUseTourProvider,
currentTourPage: CurrentTourPageType.DATASET_PAGE,
}));
render(<TourPage />);
expect(
await screen.findByText('TableDetailsPageV1.component')
).toBeInTheDocument();
});
});

View File

@ -0,0 +1,55 @@
/*
* 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.
*/
// Mock MentionBlot as a class
class MentionBlot {
static create(data: { value: string; link: string; id: string }) {
const element = document.createElement('a');
element.innerText = data.value;
element.href = data.link;
element.id = data.id;
return element;
}
}
// Mock the Quill.import function
jest.mock('react-quill', () => ({
Quill: {
import: jest.fn().mockImplementation(() => {
return MentionBlot;
}),
},
}));
import { LinkBlot } from './QuillLink';
describe('LinkBlot', () => {
it('should create a link element with correct properties', () => {
const data = {
value: 'Link Text',
link: 'https://example.com/',
id: 'linkId',
};
const linkElement = LinkBlot.render(data);
expect(linkElement.tagName).toBe('A');
expect(linkElement.innerText).toBe(data.value);
expect(linkElement.href).toBe(data.link);
expect(linkElement.id).toBe(data.id);
});
it('should have correct blotName', () => {
expect(LinkBlot.blotName).toBe('link-mention');
});
});

View File

@ -1,28 +0,0 @@
/*
* 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.
*/
export const getUserNameAndToken = (searchParam: string) => {
const searchParams = new URLSearchParams(searchParam);
if (searchParams) {
const userName = searchParams.get('user');
const token = searchParams.get('token');
return {
userName,
token,
};
}
return;
};