feat(frontend): add task inactivate and delete actions
Extend the local task store with active state, inactivation, and hard delete support. Update the edit task page and tests so inactive tasks are hidden from normal lists and task lifecycle actions are available from the details view.
This commit is contained in:
@@ -15,15 +15,22 @@ vi.mock('react-router-dom', async () => {
|
||||
vi.mock('@/api/entitiesApi')
|
||||
vi.mock('@/api/tasksApi')
|
||||
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={new QueryClient({ defaultOptions: { queries: { retry: false } } })}>
|
||||
<MemoryRouter initialEntries={['/entities/entity-1/tasks/task-1']}>
|
||||
<Routes>
|
||||
<Route path="/entities/:entityId/tasks/:taskId" element={children} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
function renderPage() {
|
||||
const queryClient = new QueryClient({ defaultOptions: { queries: { retry: false } } })
|
||||
const wrapper = ({ children }: { children: React.ReactNode }) => (
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<MemoryRouter initialEntries={['/entities/entity-1/tasks/task-1']}>
|
||||
<Routes>
|
||||
<Route path="/entities/:entityId/tasks/:taskId" element={children} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</QueryClientProvider>
|
||||
)
|
||||
|
||||
render(<EditTaskPage />, { wrapper })
|
||||
|
||||
return { queryClient }
|
||||
}
|
||||
|
||||
const mockEntity = {
|
||||
id: 'entity-1',
|
||||
@@ -44,6 +51,7 @@ const mockTask = {
|
||||
prompt: 'Summarize jokes',
|
||||
scheduleCron: '0 9 * * 1',
|
||||
emailLookback: 'last_week' as const,
|
||||
active: true,
|
||||
createdAt: '2026-03-26T10:00:00Z',
|
||||
}
|
||||
|
||||
@@ -51,11 +59,13 @@ describe('EditTaskPage', () => {
|
||||
beforeEach(() => {
|
||||
vi.mocked(entitiesApi.getEntity).mockResolvedValue(mockEntity)
|
||||
vi.mocked(tasksApi.getTask).mockResolvedValue(mockTask)
|
||||
vi.mocked(tasksApi.inactivateTask).mockResolvedValue({ ...mockTask, active: false })
|
||||
vi.mocked(tasksApi.deleteTask).mockResolvedValue(undefined)
|
||||
mockNavigate.mockClear()
|
||||
})
|
||||
|
||||
it('should_renderPrefilledTaskForm_when_pageLoads', async () => {
|
||||
render(<EditTaskPage />, { wrapper })
|
||||
renderPage()
|
||||
|
||||
await screen.findByRole('link', { name: /back to entity a/i })
|
||||
|
||||
@@ -80,7 +90,8 @@ describe('EditTaskPage', () => {
|
||||
emailLookback: 'last_day',
|
||||
})
|
||||
|
||||
render(<EditTaskPage />, { wrapper })
|
||||
const { queryClient } = renderPage()
|
||||
const invalidateQueriesSpy = vi.spyOn(queryClient, 'invalidateQueries')
|
||||
await screen.findByRole('link', { name: /back to entity a/i })
|
||||
|
||||
fireEvent.change(screen.getByLabelText(/task name/i), { target: { value: 'Daily Check-in' } })
|
||||
@@ -119,6 +130,41 @@ describe('EditTaskPage', () => {
|
||||
scheduleCron: '0 8 * * 1-5',
|
||||
emailLookback: 'last_day',
|
||||
})
|
||||
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_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 }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(tasksApi.inactivateTask).toHaveBeenCalledWith('task-1')
|
||||
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_deleteTaskAndNavigateBack_when_deleteClicked', 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: /delete/i }))
|
||||
|
||||
await waitFor(() => {
|
||||
expect(tasksApi.deleteTask).toHaveBeenCalledWith('task-1')
|
||||
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')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user