Skip to content

Commit

Permalink
Apply actions to multiple users (#1259)
Browse files Browse the repository at this point in the history
  • Loading branch information
marmorrei authored May 22, 2024
1 parent caaab30 commit 7b7e03f
Show file tree
Hide file tree
Showing 28 changed files with 663 additions and 144 deletions.
9 changes: 8 additions & 1 deletion web/usuaris/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,19 @@

All notable changes to this project will be documented in this file.

## [0.21.0] - 2024-05-22

### Added

- Added ActionsDropdown component
- Added functionality to apply actions to multiple users

## [0.20.0] - 2024-05-22

### Added

- Added sort option on headers table
-

## [0.19.0] - 2024-05-15

### Added
Expand Down
8 changes: 4 additions & 4 deletions web/usuaris/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions web/usuaris/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "web_usuaris",
"private": true,
"version": "0.20.0",
"version": "0.21.0",
"type": "module",
"scripts": {
"dev": "vite",
Expand All @@ -14,7 +14,7 @@
"dependencies": {
"@hookform/resolvers": "^3.0.0",
"@itacademy/schemas": "^0.3.0",
"@itacademy/ui": "^0.31.7",
"@itacademy/ui": "^0.31.8",
"@tanstack/react-query": "^4.29.5",
"@tanstack/react-query-devtools": "^4.29.6",
"@tanstack/react-table": "^8.13.2",
Expand Down Expand Up @@ -60,4 +60,4 @@
"vite": "5.0.13",
"vitest": "1.2.1"
}
}
}
13 changes: 13 additions & 0 deletions web/usuaris/src/__mocks__/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,16 @@ export const handlers = [
`${urls.deleteUser}1`,
() => new HttpResponse(null, { status: 204 })
),

http.delete(
urls.deleteMultipleUsers,
() => new HttpResponse(null, { status: 204 })
),

http.post(
urls.changeUsersStatus,
() => new HttpResponse(null, { status: 204 })
),
]

export const errorHandlers = [
Expand All @@ -158,4 +168,7 @@ export const errorHandlers = [
http.delete(`${urls.deleteUser}1`, () =>
HttpResponse.json({ message: 'Database error' }, { status: 500 })
),
http.delete(urls.deleteMultipleUsers, () =>
HttpResponse.json({ message: 'Database error' }, { status: 500 })
),
]
60 changes: 60 additions & 0 deletions web/usuaris/src/__tests__/hooks/useDeleteMultipleUsers.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { renderHook, waitFor } from '@testing-library/react'
import { QueryClientProvider } from '@tanstack/react-query'
import { act } from 'react-dom/test-utils'
import { useDeleteMultipleUsers } from '../../hooks'
import { queryClient } from '../setup'
import { server } from '../../__mocks__/server'

beforeEach(() => {
server.listen()
})

afterEach(() => {
server.resetHandlers()
queryClient.clear()
})

afterAll(() => {
server.close()
})

describe('useDeleteMultipleUsers hook', () => {
it('should initialize the useDeleteMultipleUsers hook', async () => {
const { result } = renderHook(() => useDeleteMultipleUsers(), {
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
),
})

await waitFor(() => {
expect(result.current.deleteMultipleUsersMutation).toBeDefined()
expect(result.current.isError).toBe(false)
expect(result.current.isLoading).toBe(false)
expect(result.current.isSuccess).toBe(false)
})
})

it('should delete users successfully and invalidate queries', async () => {
const { result } = renderHook(() => useDeleteMultipleUsers(), {
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
),
})
const mockUsersIds = ['1', '3', '4']

act(() => {
result.current.deleteMultipleUsersMutation(mockUsersIds)
})

await waitFor(() => {
expect(result.current.isSuccess).toBe(true)
expect(result.current.isError).toBe(false)
expect(result.current.isLoading).toBe(false)
expect(queryClient.getQueryData(['users'])).toBeUndefined()
})
})
})
61 changes: 61 additions & 0 deletions web/usuaris/src/__tests__/hooks/useUpdateUsersStatus.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { renderHook, waitFor } from '@testing-library/react'
import { QueryClientProvider } from '@tanstack/react-query'
import { act } from 'react-dom/test-utils'
import { useUpdateUsersStatus } from '../../hooks'
import { queryClient } from '../setup'
import { server } from '../../__mocks__/server'
import { UserStatus } from '../../types'

beforeEach(() => {
server.listen()
})

afterEach(() => {
server.resetHandlers()
queryClient.clear()
})

afterAll(() => {
server.close()
})

describe('useUpdateUsersStatus hook', () => {
it('should initialize the useUpdateUsersStatus hook', async () => {
const { result } = renderHook(() => useUpdateUsersStatus(), {
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
),
})
await waitFor(() => {
expect(result.current.changeUsersStatus).toBeDefined()
expect(result.current.error).toBe(null)
expect(result.current.isSuccess).toBe(false)
})
})

it('should call changeUsersStatus on users status update and refetch users on success', async () => {
const { result } = renderHook(() => useUpdateUsersStatus(), {
wrapper: ({ children }) => (
<QueryClientProvider client={queryClient}>
{children}
</QueryClientProvider>
),
})

const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries')

act(() => {
result.current.changeUsersStatus.mutate({
ids: ['1', '2', '5'],
status: UserStatus.BLOCKED,
})
})
await waitFor(() => {
expect(result.current.error).toBe(null)
expect(result.current.isSuccess).toBe(true)
expect(invalidateQueriesSpy).toHaveBeenCalledWith(['users'])
})
})
})
52 changes: 52 additions & 0 deletions web/usuaris/src/__tests__/molecules/ActionsDropdown.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { vi } from 'vitest'
import { fireEvent, render, screen } from '../test-utils'
import { ActionsDropdown } from '../../components/molecules'
import { UserStatus } from '../../types'

const mockHandleClick = vi.fn()

afterEach(() => {
vi.restoreAllMocks()
})

describe('ActionsDropdown', () => {
it('renders correctly', () => {
render(
<ActionsDropdown
selectedStatus={undefined}
handleAction={mockHandleClick}
isActionFinished={false}
/>
)
const actionsDropdown = screen.getByTestId('actions-dropdown')

expect(actionsDropdown).toHaveTextContent(/accions/i)
expect(actionsDropdown).toHaveAttribute('disabled')
expect(screen.getByTitle(/obre/i)).toBeInTheDocument()
})

it('renders corresponding actions when users selected and returns selected action to parent', () => {
render(
<ActionsDropdown
selectedStatus={UserStatus.ACTIVE}
handleAction={mockHandleClick}
isActionFinished={false}
/>
)

const actionsHeader = screen.getByTestId('dropdown-header')

expect(actionsHeader).toHaveTextContent(/accions/i)

fireEvent.click(actionsHeader)

const blockOption = screen.getByTestId('BLOCKED')
expect(blockOption).toBeInTheDocument()
expect(screen.getByTestId('DELETE')).toBeInTheDocument()

fireEvent.click(blockOption)

expect(actionsHeader).toHaveTextContent(/bloquejar/i)
expect(mockHandleClick).toHaveBeenCalledWith(UserStatus.BLOCKED)
})
})
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,25 @@ import { server } from '../../__mocks__/server'
import { DeleteConfirmationModal } from '../../components/molecules/DeleteConfirmationModal'
import { fireEvent, render, screen, waitFor } from '../test-utils'

const defaultProps = {
const defaultDeleteUser = {
open: true,
toggleModal: vi.fn(),
idToDelete: '1',
}

const defaultDeleteMultipleUsers = {
open: true,
toggleModal: vi.fn(),
idsToDelete: ['1', '3', '4'],
}

describe('DeleteConfirmationModal', () => {
it('renders correctly', async () => {
render(<DeleteConfirmationModal {...defaultProps} />)
render(<DeleteConfirmationModal {...defaultDeleteUser} />)

await waitFor(() => {
expect(
screen.getByText('Estàs segur que vols eliminar aquest usuari?')
screen.getByText('Estàs segur que vols eliminar aquest/s usuari/s?')
).toBeInTheDocument()
expect(screen.getByTestId('confirm-button')).toBeInTheDocument()
expect(screen.getByTestId('cancel-button')).toBeInTheDocument()
Expand All @@ -25,12 +31,12 @@ describe('DeleteConfirmationModal', () => {
fireEvent.click(cancelButton)

await waitFor(() => {
expect(defaultProps.toggleModal).toHaveBeenCalled()
expect(defaultDeleteUser.toggleModal).toHaveBeenCalled()
})
})

it('handles successful deletion', async () => {
render(<DeleteConfirmationModal {...defaultProps} />)
render(<DeleteConfirmationModal {...defaultDeleteUser} />)

await waitFor(() => {
const confirmButton = screen.getByTestId('confirm-button')
Expand All @@ -39,15 +45,51 @@ describe('DeleteConfirmationModal', () => {

await waitFor(() => {
expect(
screen.getByText('Usuario eliminado correctamente')
screen.getByText('Usuari/s eliminat/s correctament')
).toBeInTheDocument()
expect(defaultProps.toggleModal).toHaveBeenCalled()
expect(defaultDeleteUser.toggleModal).toHaveBeenCalled()
})
})

it('handles deletion error', async () => {
server.use(...errorHandlers)
render(<DeleteConfirmationModal {...defaultProps} />)
render(<DeleteConfirmationModal {...defaultDeleteUser} />)

await waitFor(() => {
const confirmButton = screen.getByTestId('confirm-button')
fireEvent.click(confirmButton)
})

await waitFor(() => {
expect(
screen.getByText(`Error en eliminar l'usuari/s`)
).toBeInTheDocument()
})
})

it('handles multiple deletion successfully', async () => {
render(<DeleteConfirmationModal {...defaultDeleteMultipleUsers} />)

await waitFor(() => {
const confirmButton = screen.getByTestId('confirm-button')
fireEvent.click(confirmButton)
})

await waitFor(
() => {
expect(
screen.getByText('Usuari/s eliminat/s correctament')
).toBeInTheDocument()

expect(defaultDeleteMultipleUsers.toggleModal).toHaveBeenCalled()
},
{ timeout: 5000 }
)
})

it('handles multiple deletion error', async () => {
server.use(...errorHandlers)
render(<DeleteConfirmationModal {...defaultDeleteMultipleUsers} />)

await waitFor(() => {
const confirmButton = screen.getByTestId('confirm-button')
Expand All @@ -56,7 +98,7 @@ describe('DeleteConfirmationModal', () => {

await waitFor(() => {
expect(
screen.getByText(/Error en eliminar l'usuari/i)
screen.getByText(`Error en eliminar l'usuari/s`)
).toBeInTheDocument()
})
})
Expand Down
Loading

0 comments on commit 7b7e03f

Please sign in to comment.