mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
Add protected person deletion
This commit is contained in:
@@ -0,0 +1,128 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals'
|
||||
import { render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { MemoryRouter, Route, Routes } from 'react-router-dom'
|
||||
import { PersonDetails } from '@/components/Person/PersonDetails'
|
||||
import { PersonDetailsType, Role } from '@/shared/types'
|
||||
|
||||
jest.mock('@/components/DetailView/DetailView', () => ({
|
||||
DetailView: (props: { deleteFunction?: () => Promise<void> }) => {
|
||||
const handleDelete = () => {
|
||||
if (props.deleteFunction) void props.deleteFunction()
|
||||
}
|
||||
return <button onClick={handleDelete}>Delete</button>
|
||||
},
|
||||
}))
|
||||
|
||||
jest.mock('@/components/Person/Tabs/PersonTab', () => ({
|
||||
PersonTab: () => <div data-testid="person-tab" />,
|
||||
}))
|
||||
|
||||
const mockUseGetPersonDetailsQuery = jest.fn()
|
||||
const mockUseGetPersonDetailsIdMutation = jest.fn()
|
||||
const mockUseEditPersonMutation = jest.fn()
|
||||
const mockUseDeletePersonMutation = jest.fn()
|
||||
const mockUseUser = jest.fn()
|
||||
const mockNotify = jest.fn()
|
||||
const mockNavigate = jest.fn()
|
||||
let deletePersonMock: jest.Mock
|
||||
|
||||
jest.mock('@/redux/personReducer', () => ({
|
||||
useGetPersonDetailsQuery: (id: string, options?: unknown) => mockUseGetPersonDetailsQuery(id, options),
|
||||
useGetPersonDetailsIdMutation: () => mockUseGetPersonDetailsIdMutation(),
|
||||
useEditPersonMutation: () => mockUseEditPersonMutation(),
|
||||
useDeletePersonMutation: () => mockUseDeletePersonMutation(),
|
||||
}))
|
||||
|
||||
jest.mock('@/hooks/user', () => ({
|
||||
useUser: () => mockUseUser(),
|
||||
}))
|
||||
|
||||
jest.mock('@/hooks/notification', () => ({
|
||||
useNotify: () => ({ notify: mockNotify }),
|
||||
}))
|
||||
|
||||
jest.mock('react-router-dom', () => {
|
||||
const actualRouterDom = jest.requireActual<typeof import('react-router-dom')>('react-router-dom')
|
||||
return {
|
||||
...actualRouterDom,
|
||||
useNavigate: () => mockNavigate,
|
||||
}
|
||||
})
|
||||
|
||||
const person: PersonDetailsType = {
|
||||
initials: 'DEL-UI',
|
||||
first_name: 'Delete',
|
||||
surname: 'Ui',
|
||||
full_name: 'Delete Ui',
|
||||
format: null,
|
||||
email: 'delete-ui@example.com',
|
||||
user_id: null,
|
||||
organization: 'Test organization',
|
||||
country: 'Finland',
|
||||
password_set: null,
|
||||
used_morph: null,
|
||||
used_now: null,
|
||||
used_gene: null,
|
||||
user: null,
|
||||
now_user_group: '',
|
||||
}
|
||||
|
||||
const renderPersonDetails = () => {
|
||||
render(
|
||||
<MemoryRouter initialEntries={['/person/DEL-UI']}>
|
||||
<Routes>
|
||||
<Route path="/person/:id" element={<PersonDetails />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
)
|
||||
}
|
||||
|
||||
describe('PersonDetails', () => {
|
||||
beforeEach(() => {
|
||||
mockUseUser.mockReturnValue({ initials: 'TEST-SU', role: Role.Admin })
|
||||
mockUseGetPersonDetailsQuery.mockReturnValue({ data: person, isLoading: false, isError: false })
|
||||
mockUseGetPersonDetailsIdMutation.mockReturnValue([jest.fn()])
|
||||
mockUseEditPersonMutation.mockReturnValue([jest.fn(), { isLoading: false }])
|
||||
deletePersonMock = jest.fn(() => ({ unwrap: () => Promise.resolve() }))
|
||||
mockUseDeletePersonMutation.mockReturnValue([deletePersonMock, { isLoading: false }])
|
||||
mockNotify.mockReset()
|
||||
mockNavigate.mockReset()
|
||||
})
|
||||
|
||||
it('deletes the person and navigates back to the list', async () => {
|
||||
renderPersonDetails()
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /delete/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(deletePersonMock).toHaveBeenCalledWith(person.initials)
|
||||
})
|
||||
expect(mockNotify).toHaveBeenCalledWith('Deleted person successfully.')
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/person')
|
||||
})
|
||||
|
||||
it('shows the backend message when deleting is blocked', async () => {
|
||||
deletePersonMock.mockReturnValue({
|
||||
unwrap: () =>
|
||||
Promise.reject(
|
||||
Object.assign(new Error('Delete blocked'), {
|
||||
status: 409,
|
||||
data: { message: 'Person cannot be deleted because they are still linked to database data.' },
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
renderPersonDetails()
|
||||
|
||||
await userEvent.click(screen.getByRole('button', { name: /delete/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockNotify).toHaveBeenCalledWith(
|
||||
'Person cannot be deleted because they are still linked to database data.',
|
||||
'error'
|
||||
)
|
||||
})
|
||||
expect(mockNavigate).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
@@ -1,5 +1,6 @@
|
||||
import { useParams, useNavigate } from 'react-router-dom'
|
||||
import {
|
||||
useDeletePersonMutation,
|
||||
useEditPersonMutation,
|
||||
useGetPersonDetailsIdMutation,
|
||||
useGetPersonDetailsQuery,
|
||||
@@ -13,11 +14,30 @@ import { validatePerson } from '@/shared/validators/person'
|
||||
import { useNotify } from '@/hooks/notification'
|
||||
import { useEffect } from 'react'
|
||||
import { emptyPerson } from '../DetailView/common/defaultValues'
|
||||
import type { FetchBaseQueryError } from '@reduxjs/toolkit/query'
|
||||
|
||||
const getDeleteErrorMessage = (error: unknown): string => {
|
||||
const fetchError = error as FetchBaseQueryError
|
||||
if (
|
||||
fetchError &&
|
||||
typeof fetchError === 'object' &&
|
||||
'data' in fetchError &&
|
||||
fetchError.data &&
|
||||
typeof fetchError.data === 'object' &&
|
||||
'message' in fetchError.data &&
|
||||
typeof fetchError.data.message === 'string'
|
||||
) {
|
||||
return fetchError.data.message
|
||||
}
|
||||
|
||||
return 'Could not delete person.'
|
||||
}
|
||||
|
||||
export const PersonDetails = () => {
|
||||
const { id: idFromUrl } = useParams()
|
||||
const user = useUser()
|
||||
const [editPersonRequest, { isLoading: mutationLoading }] = useEditPersonMutation()
|
||||
const [deletePersonRequest, { isLoading: deleteLoading }] = useDeletePersonMutation()
|
||||
const { notify } = useNotify()
|
||||
const navigate = useNavigate()
|
||||
|
||||
@@ -70,10 +90,22 @@ export const PersonDetails = () => {
|
||||
}
|
||||
|
||||
if (isError) return <div>Error loading data</div>
|
||||
if (isLoading || (!data && !isNew) || mutationLoading) return <CircularProgress />
|
||||
if (isLoading || (!data && !isNew) || mutationLoading || deleteLoading) return <CircularProgress />
|
||||
|
||||
document.title = isNew ? 'New person' : `User - ${data!.user?.user_name}`
|
||||
|
||||
const deleteFunction = async () => {
|
||||
if (!id) return
|
||||
|
||||
try {
|
||||
await deletePersonRequest(id).unwrap()
|
||||
notify('Deleted person successfully.')
|
||||
navigate('/person')
|
||||
} catch (error) {
|
||||
notify(getDeleteErrorMessage(error), 'error')
|
||||
}
|
||||
}
|
||||
|
||||
const tabs: TabType[] = [
|
||||
{
|
||||
title: 'Person',
|
||||
@@ -90,6 +122,7 @@ export const PersonDetails = () => {
|
||||
tabs={tabs}
|
||||
data={isNew ? emptyPerson : data!}
|
||||
validator={validatePerson}
|
||||
deleteFunction={deleteFunction}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,8 +28,20 @@ const personsApi = api.injectEndpoints({
|
||||
}),
|
||||
invalidatesTags: (result, _error, { initials }) => (result ? [{ type: 'person', id: initials }, 'persons'] : []),
|
||||
}),
|
||||
deletePerson: builder.mutation<void, string>({
|
||||
query: initials => ({
|
||||
url: `/person/${initials}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: (_result, _error, initials) => [{ type: 'person', id: initials }, 'persons'],
|
||||
}),
|
||||
}),
|
||||
})
|
||||
|
||||
export const { useGetAllPersonsQuery, useGetPersonDetailsQuery, useEditPersonMutation, useGetPersonDetailsIdMutation } =
|
||||
personsApi
|
||||
export const {
|
||||
useGetAllPersonsQuery,
|
||||
useGetPersonDetailsQuery,
|
||||
useEditPersonMutation,
|
||||
useGetPersonDetailsIdMutation,
|
||||
useDeletePersonMutation,
|
||||
} = personsApi
|
||||
|
||||
Reference in New Issue
Block a user