From 2d24930f122166534005567d2c7bac0945bbb206 Mon Sep 17 00:00:00 2001 From: Shailesh Parmar Date: Thu, 8 Feb 2024 15:55:09 +0530 Subject: [PATCH] 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 --- .../PersonaDetailsPage.test.tsx | 187 ++++++++++++++++ .../PersonaDetailsPage/PersonaDetailsPage.tsx | 30 +-- .../PersonaListPage/PersonaPage.test.tsx | 175 +++++++++++++++ .../Persona/PersonaListPage/PersonaPage.tsx | 19 +- .../ResetPassword/ResetPassword.component.tsx | 207 ++++++++---------- .../ResetPassword/ResetPassword.test.tsx | 107 +++++++++ .../ui/src/pages/TourPage/TourPage.test.tsx | 105 +++++++++ .../ui/src/utils/QuillLink/QuillLink.test.ts | 55 +++++ .../ui/src/utils/ResetPassword.utils.ts | 28 --- 9 files changed, 732 insertions(+), 181 deletions(-) create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaDetailsPage/PersonaDetailsPage.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaListPage/PersonaPage.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/ResetPassword/ResetPassword.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/pages/TourPage/TourPage.test.tsx create mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/QuillLink/QuillLink.test.ts delete mode 100644 openmetadata-ui/src/main/resources/ui/src/utils/ResetPassword.utils.ts diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaDetailsPage/PersonaDetailsPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaDetailsPage/PersonaDetailsPage.test.tsx new file mode 100644 index 00000000000..94ca7bb25f2 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaDetailsPage/PersonaDetailsPage.test.tsx @@ -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 }) =>
{children}
); +}); +jest.mock('../../../components/PageHeader/PageHeader.component', () => { + return jest.fn().mockImplementation(() =>
PageHeader.component
); +}); +jest.mock('../../../components/common/EntityDescription/DescriptionV1', () => { + return jest.fn().mockImplementation(() =>
DescriptionV1.component
); +}); +jest.mock( + '../../../components/common/EntityPageInfos/ManageButton/ManageButton', + () => { + return jest + .fn() + .mockImplementation(({ afterDeleteAction, onEditDisplayName }) => ( +
+ ManageButton.component + + +
+ )); + } +); +jest.mock( + '../../../components/common/UserSelectableList/UserSelectableList.component', + () => { + return { + UserSelectableList: jest + .fn() + .mockImplementation(({ children, onUpdate }) => ( +
onUpdate({ id: 'ID', type: 'user' })}> + {children} +
+ )), + }; + } +); +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(() =>
NoDataPlaceholder.component
); + } +); +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(); + + 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(); + + expect( + await screen.findByText('NoDataPlaceholder.component') + ).toBeInTheDocument(); + }); + + it('handleAfterDeleteAction should call after delete', async () => { + render(); + + 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(); + + 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(); + + 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', + }, + }, + ]); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaDetailsPage/PersonaDetailsPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaDetailsPage/PersonaDetailsPage.tsx index d8a03b96699..f019edd63ec 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaDetailsPage/PersonaDetailsPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaDetailsPage/PersonaDetailsPage.tsx @@ -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) => { if (!personaDetails) { @@ -167,10 +152,14 @@ export const PersonaDetailsPage = () => { ); }; - if (isLoading || !personaDetails) { + if (isLoading) { return ; } + if (isUndefined(personaDetails)) { + return ; + } + return ( @@ -196,7 +185,6 @@ export const PersonaDetailsPage = () => { entityName={personaDetails.name} entityType={EntityType.PERSONA} onEditDisplayName={handleDisplayNameUpdate} - onRestoreEntity={handleRestorePersona} /> diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaListPage/PersonaPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaListPage/PersonaPage.test.tsx new file mode 100644 index 00000000000..97810cd8b07 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaListPage/PersonaPage.test.tsx @@ -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 }) =>
{children}
); +}); +jest.mock('../../../components/PageHeader/PageHeader.component', () => { + return jest.fn().mockImplementation(() =>
PageHeader.component
); +}); +jest.mock( + '../../../components/common/TitleBreadcrumb/TitleBreadcrumb.component', + () => { + return jest + .fn() + .mockImplementation(() =>
TitleBreadcrumb.component
); + } +); +jest.mock('../../../components/common/NextPrevious/NextPrevious', () => { + return jest.fn().mockImplementation(() =>
NextPrevious.component
); +}); +jest.mock( + '../../../components/common/ErrorWithPlaceholder/ErrorPlaceHolder', + () => { + return jest + .fn() + .mockImplementation(() =>
ErrorPlaceHolder.component
); + } +); +jest.mock('../../../components/common/DeleteWidget/DeleteWidgetModal', () => { + return jest + .fn() + .mockImplementation(() =>
DeleteWidgetModal.component
); +}); +jest.mock( + '../../../components/Persona/PersonaDetailsCard/PersonaDetailsCard', + () => { + return { + PersonaDetailsCard: jest + .fn() + .mockImplementation(() =>
PersonaDetailsCard.component
), + }; + } +); +jest.mock( + '../../../components/Persona/AddEditPersona/AddEditPersona.component', + () => { + return { + AddEditPersonaForm: jest.fn().mockImplementation(({ onSave }) => ( +
+ AddEditPersonaForm.component + +
+ )), + }; + } +); +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(); + }); + + 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(); + }); + 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(); + }); + 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(); + }); + + expect( + await screen.findAllByText('PersonaDetailsCard.component') + ).toHaveLength(2); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaListPage/PersonaPage.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaListPage/PersonaPage.tsx index f7090aab190..b15a81fc768 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaListPage/PersonaPage.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/Persona/PersonaListPage/PersonaPage.tsx @@ -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(); const [isLoading, setIsLoading] = useState(false); - - const [personaDeleting, setPersonaDeleting] = useState(); const { currentPage, handlePageChange, @@ -132,7 +127,7 @@ export const PersonaPage = () => { + gutter={[16, 16]}> @@ -185,18 +180,6 @@ export const PersonaPage = () => { onSave={handlePersonaAddEditSave} /> )} - - fetchPersonas()} - allowSoftDelete={false} - entityId={personaDeleting?.id ?? ''} - entityName={getEntityName(personaDeleting)} - entityType={EntityType.PERSONA} - visible={Boolean(personaDeleting)} - onCancel={() => { - setPersonaDeleting(undefined); - }} - />
); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ResetPassword/ResetPassword.component.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ResetPassword/ResetPassword.component.tsx index ea5c23a7763..2afbdec6bd6 100644 --- a/openmetadata-ui/src/main/resources/ui/src/pages/ResetPassword/ResetPassword.component.tsx +++ b/openmetadata-ui/src/main/resources/ui/src/pages/ResetPassword/ResetPassword.component.tsx @@ -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 ( -
- {tokenValid ? ( - -
- -
+
+ + + + + -
- - {t('label.re-verify')} - -
-
- ) : ( - - - - - + + + {t('label.reset-your-password')} + + - - - {t('label.reset-your-password')} - - + +
+ + + + { + if (password === value) { + return Promise.resolve(); + } - - - - - - { - if (password === value) { - return Promise.resolve(); - } + }, + ]}> + + - return Promise.reject(t('label.password-not-match')); - }, - }, - ]}> - - - - -
- -
-
- )} + + + + +
); }; diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/ResetPassword/ResetPassword.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/ResetPassword/ResetPassword.test.tsx new file mode 100644 index 00000000000..aa961def6ed --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/ResetPassword/ResetPassword.test.tsx @@ -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(); + + 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(); + + 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(); + + 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(); + + 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); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/pages/TourPage/TourPage.test.tsx b/openmetadata-ui/src/main/resources/ui/src/pages/TourPage/TourPage.test.tsx new file mode 100644 index 00000000000..e55b06d0b81 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/pages/TourPage/TourPage.test.tsx @@ -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 }) => ( +
+ Tour.component{' '} + +
+ )); +}); +jest.mock('../MyDataPage/MyDataPage.component', () => { + return jest.fn().mockImplementation(() =>
MyDataPage.component
); +}); +jest.mock('../ExplorePage/ExplorePageV1.component', () => { + return jest + .fn() + .mockImplementation(() =>
ExplorePageV1Component.component
); +}); +jest.mock('../TableDetailsPageV1/TableDetailsPageV1', () => { + return jest + .fn() + .mockImplementation(() =>
TableDetailsPageV1.component
); +}); +jest.mock('../../utils/TourUtils', () => ({ + getTourSteps: jest.fn().mockImplementation((props) => props), +})); + +describe('TourPage component', () => { + it('should render correctly', async () => { + render(); + + expect(await screen.findByText('Tour.component')).toBeInTheDocument(); + }); + + it('clear search term should work correctly', async () => { + render(); + + 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(); + + 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(); + + 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(); + + expect( + await screen.findByText('TableDetailsPageV1.component') + ).toBeInTheDocument(); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/QuillLink/QuillLink.test.ts b/openmetadata-ui/src/main/resources/ui/src/utils/QuillLink/QuillLink.test.ts new file mode 100644 index 00000000000..12de98eb117 --- /dev/null +++ b/openmetadata-ui/src/main/resources/ui/src/utils/QuillLink/QuillLink.test.ts @@ -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'); + }); +}); diff --git a/openmetadata-ui/src/main/resources/ui/src/utils/ResetPassword.utils.ts b/openmetadata-ui/src/main/resources/ui/src/utils/ResetPassword.utils.ts deleted file mode 100644 index 47ea3cd4ff8..00000000000 --- a/openmetadata-ui/src/main/resources/ui/src/utils/ResetPassword.utils.ts +++ /dev/null @@ -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; -};