mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-11-01 19:18:05 +00:00
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:
parent
24bbed8496
commit
2d24930f12
@ -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',
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
});
|
||||
@ -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>
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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>
|
||||
);
|
||||
|
||||
@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
@ -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);
|
||||
});
|
||||
});
|
||||
@ -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();
|
||||
});
|
||||
});
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
@ -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;
|
||||
};
|
||||
Loading…
x
Reference in New Issue
Block a user