mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
delete files
This commit is contained in:
@@ -1,40 +0,0 @@
|
||||
import { useMemo } from 'react'
|
||||
import { skipToken } from '@reduxjs/toolkit/query'
|
||||
|
||||
import { projectToFormValues } from '@/api/projectsApi'
|
||||
import type { ProjectFormValues } from '@/components/Project/ProjectForm'
|
||||
import { useGetProjectDetailsQuery } from '@/redux/projectsSlice'
|
||||
import type { ProjectDetailsType } from '@/shared/types'
|
||||
import { useUsersApi } from './useUsersApi'
|
||||
|
||||
type UseProjectResult = {
|
||||
project: ProjectDetailsType | undefined
|
||||
initialValues: ProjectFormValues | null
|
||||
users: ReturnType<typeof useUsersApi>['users']
|
||||
isLoading: boolean
|
||||
isError: boolean
|
||||
refetch: () => Promise<unknown>
|
||||
}
|
||||
|
||||
export const useProject = (id: string | number | null): UseProjectResult => {
|
||||
const projectId = id ? String(id) : null
|
||||
const projectQuery = useGetProjectDetailsQuery(projectId ?? skipToken)
|
||||
const usersQuery = useUsersApi()
|
||||
|
||||
const initialValues = useMemo(() => {
|
||||
if (!projectQuery.data) return null
|
||||
if (!usersQuery.users.length) return null
|
||||
return projectToFormValues(projectQuery.data, usersQuery.users)
|
||||
}, [projectQuery.data, usersQuery.users])
|
||||
|
||||
return {
|
||||
project: projectQuery.data,
|
||||
initialValues,
|
||||
users: usersQuery.users,
|
||||
isLoading: projectQuery.isLoading || usersQuery.isLoading,
|
||||
isError: projectQuery.isError || usersQuery.isError,
|
||||
refetch: projectQuery.refetch,
|
||||
}
|
||||
}
|
||||
|
||||
export default useProject
|
||||
@@ -1,8 +0,0 @@
|
||||
export type { CreateProjectPayload, UpdateProjectPayload } from './projectReducer'
|
||||
export {
|
||||
useGetAllProjectsQuery,
|
||||
useGetProjectDetailsQuery,
|
||||
useCreateProjectMutation,
|
||||
useUpdateProjectMutation,
|
||||
useDeleteProjectMutation,
|
||||
} from './projectReducer'
|
||||
@@ -1,206 +0,0 @@
|
||||
import React from 'react'
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals'
|
||||
import '@testing-library/jest-dom'
|
||||
import { Link, MemoryRouter, Route, RouterProvider, Routes, createMemoryRouter, useLocation } from 'react-router-dom'
|
||||
import { act, fireEvent, render, screen, waitFor } from '@testing-library/react'
|
||||
import userEvent from '@testing-library/user-event'
|
||||
import { Provider } from 'react-redux'
|
||||
import { Role } from '@/shared/types'
|
||||
import { ProjectEditPage } from '@/pages/projects/ProjectEditPage'
|
||||
import type { ProjectFormValues } from '@/components/Project/ProjectForm'
|
||||
import { store } from '@/redux/store'
|
||||
|
||||
const mockUseUser = jest.fn()
|
||||
const mockUseProject = jest.fn()
|
||||
const mockUseUpdateProjectMutation = jest.fn()
|
||||
const mockNotify = jest.fn()
|
||||
const mockNavigate = jest.fn()
|
||||
type UpdateProjectTrigger = (payload: ProjectFormValues) => {
|
||||
unwrap: () => Promise<ProjectFormValues & { pid: number }>
|
||||
}
|
||||
let updateProjectMock: jest.MockedFunction<UpdateProjectTrigger>
|
||||
|
||||
jest.mock('@/hooks/user', () => ({
|
||||
useUser: () => mockUseUser(),
|
||||
}))
|
||||
|
||||
jest.mock('@/hooks/useProject', () => ({
|
||||
useProject: (arg?: unknown) => mockUseProject(arg),
|
||||
}))
|
||||
|
||||
jest.mock('@/redux/projectReducer', () => ({
|
||||
useUpdateProjectMutation: () => mockUseUpdateProjectMutation(),
|
||||
}))
|
||||
|
||||
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,
|
||||
}
|
||||
})
|
||||
|
||||
type User = { userId: number; label: string; initials: string }
|
||||
|
||||
const userFactory = (overrides: Partial<User> = {}): User => ({
|
||||
userId: 1,
|
||||
label: 'Doe, Jane',
|
||||
initials: 'JD',
|
||||
...overrides,
|
||||
})
|
||||
|
||||
const defaultInitialValues: ProjectFormValues = {
|
||||
projectCode: 'PRJ-100',
|
||||
projectName: 'Existing Project',
|
||||
coordinatorUserId: 1,
|
||||
projectStatus: 'current',
|
||||
recordStatus: true,
|
||||
memberUserIds: [2],
|
||||
}
|
||||
|
||||
const renderWithRouter = () => {
|
||||
const Wrapper = () => {
|
||||
const location = useLocation()
|
||||
return (
|
||||
<React.Fragment>
|
||||
<div data-testid="location">{location.pathname}</div>
|
||||
<ProjectEditPage />
|
||||
</React.Fragment>
|
||||
)
|
||||
}
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<MemoryRouter initialEntries={['/project/7/edit']}>
|
||||
<Routes>
|
||||
<Route path="/project/:id/edit" element={<Wrapper />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</Provider>
|
||||
)
|
||||
}
|
||||
|
||||
const renderWithDataRouter = () => {
|
||||
const router = createMemoryRouter(
|
||||
[
|
||||
{
|
||||
path: '/project/:id/edit',
|
||||
element: (
|
||||
<React.Fragment>
|
||||
<ProjectEditPage />
|
||||
<Link to="/other">Go elsewhere</Link>
|
||||
</React.Fragment>
|
||||
),
|
||||
},
|
||||
{ path: '/other', element: <div>Other page</div> },
|
||||
],
|
||||
{ initialEntries: ['/project/7/edit'] }
|
||||
)
|
||||
|
||||
render(
|
||||
<Provider store={store}>
|
||||
<RouterProvider router={router} />
|
||||
</Provider>
|
||||
)
|
||||
return router
|
||||
}
|
||||
|
||||
describe('ProjectEditPage', () => {
|
||||
beforeEach(() => {
|
||||
mockUseUser.mockReturnValue({ token: 'token-123', role: Role.Admin })
|
||||
mockUseProject.mockReturnValue({
|
||||
project: { pid: 7, proj_name: 'Existing Project' },
|
||||
initialValues: defaultInitialValues,
|
||||
users: [userFactory(), userFactory({ userId: 2, label: 'Smith, Alex', initials: 'AS' })],
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
refetch: jest.fn(),
|
||||
})
|
||||
|
||||
const updateProjectImpl: UpdateProjectTrigger = payload => ({
|
||||
unwrap: () => Promise.resolve({ pid: 7, ...payload }),
|
||||
})
|
||||
updateProjectMock = jest.fn(updateProjectImpl) as jest.MockedFunction<UpdateProjectTrigger>
|
||||
|
||||
mockUseUpdateProjectMutation.mockReturnValue([
|
||||
updateProjectMock as unknown as UpdateProjectTrigger,
|
||||
{ isLoading: false },
|
||||
])
|
||||
mockNotify.mockReset()
|
||||
mockNavigate.mockReset()
|
||||
})
|
||||
|
||||
it('submits form data and navigates to the project detail page', async () => {
|
||||
const user = userEvent.setup()
|
||||
renderWithRouter()
|
||||
|
||||
await user.clear(screen.getByLabelText('Project Name'))
|
||||
await user.type(screen.getByLabelText('Project Name'), 'Updated Project')
|
||||
|
||||
const form = document.querySelector('form')
|
||||
expect(form).toBeTruthy()
|
||||
act(() => {
|
||||
fireEvent.submit(form as HTMLFormElement)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(updateProjectMock).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
projectCode: 'PRJ-100',
|
||||
projectName: 'Updated Project',
|
||||
coordinatorUserId: 1,
|
||||
projectStatus: 'current',
|
||||
recordStatus: true,
|
||||
memberUserIds: [2],
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockNavigate).toHaveBeenCalledWith('/project/7')
|
||||
})
|
||||
})
|
||||
|
||||
it('shows a prompt when attempting to leave a dirty form and stays on cancel', async () => {
|
||||
const user = userEvent.setup()
|
||||
const router = renderWithDataRouter()
|
||||
|
||||
await user.type(screen.getByLabelText('Project Name'), ' Updated')
|
||||
|
||||
await user.click(screen.getByRole('link', { name: /go elsewhere/i }))
|
||||
|
||||
await screen.findByRole('dialog')
|
||||
await user.click(screen.getByRole('button', { name: /stay on page/i }))
|
||||
|
||||
expect(router.state.location.pathname).toBe('/project/7/edit')
|
||||
})
|
||||
|
||||
it('allows navigation when the user confirms leaving a dirty form', async () => {
|
||||
const user = userEvent.setup()
|
||||
const router = renderWithDataRouter()
|
||||
|
||||
await user.type(screen.getByLabelText('Project Code'), ' UPDATED')
|
||||
await user.click(screen.getByRole('link', { name: /go elsewhere/i }))
|
||||
|
||||
await user.click(await screen.findByRole('button', { name: /leave page/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(router.state.location.pathname).toBe('/other')
|
||||
})
|
||||
})
|
||||
|
||||
it('navigates without prompting when the form is clean', async () => {
|
||||
const user = userEvent.setup()
|
||||
const router = renderWithDataRouter()
|
||||
|
||||
await user.click(screen.getByRole('link', { name: /go elsewhere/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(router.state.location.pathname).toBe('/other')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,169 +0,0 @@
|
||||
import { beforeEach, describe, expect, it, jest } from '@jest/globals'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
|
||||
import { useProject } from '@/hooks/useProject'
|
||||
import { useUsersApi } from '@/hooks/useUsersApi'
|
||||
import { useGetProjectDetailsQuery } from '@/redux/projectsSlice'
|
||||
import { mapProjectEditDataToUpdatePayload } from '@/api/projectsApi'
|
||||
import type { EditDataType, ProjectDetailsType } from '@/shared/types'
|
||||
|
||||
jest.mock('@/redux/projectsSlice')
|
||||
jest.mock('@/hooks/useUsersApi')
|
||||
|
||||
const mockUseGetProjectDetailsQuery = useGetProjectDetailsQuery as jest.MockedFunction<typeof useGetProjectDetailsQuery>
|
||||
const mockUseUsersApi = useUsersApi as jest.MockedFunction<typeof useUsersApi>
|
||||
|
||||
describe('useProject', () => {
|
||||
const baseProject = {
|
||||
pid: 42,
|
||||
proj_code: 'PRJ-42',
|
||||
proj_name: 'Demo Project',
|
||||
proj_status: 'current',
|
||||
proj_records: true,
|
||||
contact: 'JD',
|
||||
now_proj_people: [
|
||||
{ pid: 42, initials: 'JD' },
|
||||
{ pid: 42, initials: 'AS' },
|
||||
],
|
||||
} as unknown as ProjectDetailsType
|
||||
|
||||
beforeEach(() => {
|
||||
mockUseGetProjectDetailsQuery.mockReset()
|
||||
mockUseUsersApi.mockReset()
|
||||
})
|
||||
|
||||
it('prefills form values from project and user data', () => {
|
||||
mockUseGetProjectDetailsQuery.mockReturnValue({
|
||||
data: baseProject,
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
refetch: jest.fn(() => Promise.resolve(undefined)),
|
||||
} as unknown as ReturnType<typeof useGetProjectDetailsQuery>)
|
||||
mockUseUsersApi.mockReturnValue({
|
||||
users: [
|
||||
{ userId: 1, label: 'Doe, Jane', initials: 'JD' },
|
||||
{ userId: 2, label: 'Smith, Alex', initials: 'AS' },
|
||||
],
|
||||
isLoading: false,
|
||||
isError: false,
|
||||
} as unknown as ReturnType<typeof useUsersApi>)
|
||||
|
||||
const { result } = renderHook(() => useProject(baseProject.pid))
|
||||
|
||||
expect(result.current.initialValues).toEqual({
|
||||
projectCode: 'PRJ-42',
|
||||
projectName: 'Demo Project',
|
||||
coordinatorUserId: 1,
|
||||
projectStatus: 'current',
|
||||
recordStatus: true,
|
||||
memberUserIds: [2],
|
||||
})
|
||||
expect(result.current.isLoading).toBe(false)
|
||||
expect(result.current.isError).toBe(false)
|
||||
})
|
||||
|
||||
it('surfaces loading and error states from either query', () => {
|
||||
mockUseGetProjectDetailsQuery.mockReturnValue({
|
||||
data: undefined,
|
||||
isLoading: true,
|
||||
isError: false,
|
||||
refetch: jest.fn(() => Promise.resolve(undefined)),
|
||||
} as unknown as ReturnType<typeof useGetProjectDetailsQuery>)
|
||||
mockUseUsersApi.mockReturnValue({
|
||||
users: [],
|
||||
isLoading: false,
|
||||
isError: true,
|
||||
} as unknown as ReturnType<typeof useUsersApi>)
|
||||
|
||||
const { result } = renderHook(() => useProject(baseProject.pid))
|
||||
|
||||
expect(result.current.isLoading).toBe(true)
|
||||
expect(result.current.isError).toBe(true)
|
||||
expect(result.current.initialValues).toBeNull()
|
||||
})
|
||||
|
||||
it('maps edit data to update payload while dropping removed members and keeping coordinator if selected', () => {
|
||||
const editData = {
|
||||
...baseProject,
|
||||
proj_records: 'false',
|
||||
now_proj_people: [
|
||||
{ pid: 42, initials: 'JD', com_people: { user: { user_id: 1 } } },
|
||||
{ pid: 42, initials: 'AS', com_people: { user: { user_id: 2 } }, rowState: 'removed' },
|
||||
{ pid: 42, initials: 'MS', com_people: { user: { user_id: 3 } }, rowState: 'new' },
|
||||
],
|
||||
} as unknown as EditDataType<ProjectDetailsType>
|
||||
|
||||
const payload = mapProjectEditDataToUpdatePayload(editData, [
|
||||
{ userId: 1, label: 'Doe, Jane', initials: 'JD' },
|
||||
{ userId: 3, label: 'Smith, Morgan', initials: 'MS' },
|
||||
])
|
||||
|
||||
expect(payload).toEqual({
|
||||
pid: 42,
|
||||
projectCode: 'PRJ-42',
|
||||
projectName: 'Demo Project',
|
||||
coordinatorUserId: 1,
|
||||
projectStatus: 'current',
|
||||
recordStatus: false,
|
||||
memberUserIds: [1, 3],
|
||||
})
|
||||
})
|
||||
|
||||
it('maps boolean-like record status strings correctly', () => {
|
||||
const editData = {
|
||||
...baseProject,
|
||||
proj_records: 'true',
|
||||
} as unknown as EditDataType<ProjectDetailsType>
|
||||
|
||||
const payload = mapProjectEditDataToUpdatePayload(editData, [
|
||||
{ userId: 1, label: 'Doe, Jane', initials: 'JD' },
|
||||
{ userId: 2, label: 'Smith, Alex', initials: 'AS' },
|
||||
])
|
||||
|
||||
expect(payload?.recordStatus).toBe(true)
|
||||
})
|
||||
|
||||
it('sends an empty member list when all members are removed', () => {
|
||||
const editData = {
|
||||
...baseProject,
|
||||
now_proj_people: [],
|
||||
} as unknown as EditDataType<ProjectDetailsType>
|
||||
|
||||
const payload = mapProjectEditDataToUpdatePayload(editData, [{ userId: 1, label: 'Doe, Jane', initials: 'JD' }])
|
||||
|
||||
expect(payload).toEqual({
|
||||
pid: 42,
|
||||
projectCode: 'PRJ-42',
|
||||
projectName: 'Demo Project',
|
||||
coordinatorUserId: 1,
|
||||
projectStatus: 'current',
|
||||
recordStatus: true,
|
||||
memberUserIds: [],
|
||||
})
|
||||
})
|
||||
|
||||
it('returns null when coordinator cannot be matched to a user', () => {
|
||||
const payload = mapProjectEditDataToUpdatePayload(baseProject as EditDataType<ProjectDetailsType>, [
|
||||
{ userId: 99, label: 'Other, Person', initials: 'XX' },
|
||||
])
|
||||
|
||||
expect(payload).toBeNull()
|
||||
})
|
||||
|
||||
it('keeps a single newly added member when building the payload', () => {
|
||||
const editData = {
|
||||
...baseProject,
|
||||
now_proj_people: [
|
||||
{ pid: 42, initials: 'JD', com_people: { user: { user_id: 1 } } },
|
||||
{ pid: 42, initials: 'AS', com_people: { user: { user_id: 2 } }, rowState: 'new' },
|
||||
],
|
||||
} as unknown as EditDataType<ProjectDetailsType>
|
||||
|
||||
const payload = mapProjectEditDataToUpdatePayload(editData, [
|
||||
{ userId: 1, label: 'Doe, Jane', initials: 'JD' },
|
||||
{ userId: 2, label: 'Smith, Alex', initials: 'AS' },
|
||||
])
|
||||
|
||||
expect(payload?.memberUserIds).toEqual([1, 2])
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user