diff --git a/frontend/src/__tests__/api/tasksApi.test.ts b/frontend/src/__tests__/api/tasksApi.test.ts index 1a530e4..dd66772 100644 --- a/frontend/src/__tests__/api/tasksApi.test.ts +++ b/frontend/src/__tests__/api/tasksApi.test.ts @@ -1,5 +1,6 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' import { + activateTask, createTask, deleteTask, getAllTasks, @@ -152,6 +153,24 @@ describe('tasksApi', () => { ]) }) + it('should_markTaskActive_when_activateTaskCalled', async () => { + localStorage.setItem('condado:entity-tasks', JSON.stringify([taskOne, taskTwo])) + + const updatedTask = await activateTask('task-2') + + expect(updatedTask).toEqual({ + ...taskTwo, + active: true, + }) + expect(JSON.parse(localStorage.getItem('condado:entity-tasks') ?? '[]')).toEqual([ + taskOne, + { + ...taskTwo, + active: true, + }, + ]) + }) + it('should_removeTask_when_deleteTaskCalled', async () => { localStorage.setItem('condado:entity-tasks', JSON.stringify([taskOne, taskTwo])) diff --git a/frontend/src/__tests__/pages/EditTaskPage.test.tsx b/frontend/src/__tests__/pages/EditTaskPage.test.tsx index 786b687..2929334 100644 --- a/frontend/src/__tests__/pages/EditTaskPage.test.tsx +++ b/frontend/src/__tests__/pages/EditTaskPage.test.tsx @@ -6,6 +6,10 @@ import EditTaskPage from '@/pages/EditTaskPage' import * as entitiesApi from '@/api/entitiesApi' import * as tasksApi from '@/api/tasksApi' +type RenderPageOptions = { + task?: typeof mockTask +} + const mockNavigate = vi.fn() vi.mock('react-router-dom', async () => { const actual = await vi.importActual('react-router-dom') @@ -15,7 +19,7 @@ vi.mock('react-router-dom', async () => { vi.mock('@/api/entitiesApi') vi.mock('@/api/tasksApi') -function renderPage() { +function renderPage(options: RenderPageOptions = {}) { const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } }) const wrapper = ({ children }: { children: React.ReactNode }) => ( @@ -27,6 +31,8 @@ function renderPage() { ) + vi.mocked(tasksApi.getTask).mockResolvedValue(options.task ?? mockTask) + render(, { wrapper }) return { queryClient } @@ -57,8 +63,10 @@ const mockTask = { describe('EditTaskPage', () => { beforeEach(() => { + vi.clearAllMocks() vi.mocked(entitiesApi.getEntity).mockResolvedValue(mockEntity) vi.mocked(tasksApi.getTask).mockResolvedValue(mockTask) + vi.mocked(tasksApi.activateTask).mockResolvedValue({ ...mockTask, active: true }) vi.mocked(tasksApi.inactivateTask).mockResolvedValue({ ...mockTask, active: false }) vi.mocked(tasksApi.deleteTask).mockResolvedValue(undefined) mockNavigate.mockClear() @@ -137,15 +145,47 @@ describe('EditTaskPage', () => { }) }) + it('should_renderActivateButton_when_taskIsInactive', async () => { + renderPage({ task: { ...mockTask, active: false } }) + + const activateButton = await screen.findByRole('button', { name: /activate/i }) + + expect(activateButton).toHaveClass('border-emerald-700') + expect(activateButton).toHaveClass('text-emerald-200') + expect(screen.queryByRole('button', { name: /^inactivate$/i })).not.toBeInTheDocument() + }) + it('should_inactivateTaskAndNavigateBack_when_inactivateClicked', async () => { const { queryClient } = renderPage() const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries') - await screen.findByRole('link', { name: /back to entity a/i }) - fireEvent.click(screen.getByRole('button', { name: /inactivate/i })) + const inactivateButton = await screen.findByRole('button', { name: /^inactivate$/i }) + + expect(inactivateButton).toHaveClass('border-amber-700') + expect(inactivateButton).toHaveClass('text-amber-200') + + fireEvent.click(inactivateButton) await waitFor(() => { expect(tasksApi.inactivateTask).toHaveBeenCalledWith('task-1') + expect(tasksApi.activateTask).not.toHaveBeenCalled() + expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks', 'entity-1'] }) + expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-task', 'task-1'] }) + expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks'] }) + expect(mockNavigate).toHaveBeenCalledWith('/entities/entity-1') + }) + }) + + it('should_activateTaskAndNavigateBack_when_activateClicked', async () => { + const { queryClient } = renderPage({ task: { ...mockTask, active: false } }) + const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries') + + const activateButton = await screen.findByRole('button', { name: /^activate$/i }) + fireEvent.click(activateButton) + + await waitFor(() => { + expect(tasksApi.activateTask).toHaveBeenCalledWith('task-1') + expect(tasksApi.inactivateTask).not.toHaveBeenCalled() expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks', 'entity-1'] }) expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-task', 'task-1'] }) expect(invalidateQueriesSpy).toHaveBeenCalledWith({ queryKey: ['entity-tasks'] }) diff --git a/frontend/src/api/tasksApi.ts b/frontend/src/api/tasksApi.ts index 101b36c..92c5621 100644 --- a/frontend/src/api/tasksApi.ts +++ b/frontend/src/api/tasksApi.ts @@ -134,6 +134,24 @@ export async function inactivateTask(taskId: string): Promise { + const current = readTasks() + const existingTask = current.find((task) => task.id === taskId) + + if (!existingTask) { + return null + } + + const updatedTask: EntityTaskResponse = { + ...existingTask, + active: true, + } + + writeTasks(current.map((task) => (task.id === taskId ? updatedTask : task))) + return updatedTask +} + /** Deletes one scheduled task from local storage. */ export async function deleteTask(taskId: string): Promise { writeTasks(readTasks().filter((task) => task.id !== taskId)) diff --git a/frontend/src/pages/EditTaskPage.tsx b/frontend/src/pages/EditTaskPage.tsx index 901e72a..d3f8a24 100644 --- a/frontend/src/pages/EditTaskPage.tsx +++ b/frontend/src/pages/EditTaskPage.tsx @@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query' import { Link, useNavigate, useParams } from 'react-router-dom' import { getEntity } from '../api/entitiesApi' import { + activateTask, deleteTask, generateTaskPreview, getTask, @@ -165,6 +166,14 @@ export default function EditTaskPage() { }, }) + const activateTaskMutation = useMutation({ + mutationFn: () => activateTask(taskId), + onSuccess: async () => { + await invalidateTaskQueries(queryClient, entityId, taskId) + navigate(`/entities/${entityId}`) + }, + }) + const deleteTaskMutation = useMutation({ mutationFn: () => deleteTask(taskId), onSuccess: async () => { @@ -417,16 +426,41 @@ export default function EditTaskPage() {