Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clone task #772

Merged
merged 13 commits into from
Jan 10, 2024
4 changes: 3 additions & 1 deletion frontend/src/_lib/errors/BaseError.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ export class BaseError<M> extends Error implements Exception<M> {
this.meta = props.meta
this.message = props.message

Error.captureStackTrace(this, BaseError)
if (Error.captureStackTrace) {
Error.captureStackTrace(this, BaseError)
}
}
}
9 changes: 6 additions & 3 deletions frontend/src/app/tasks/DayView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Template } from '@/domain/Template'

import { TaskList } from './components/TaskList'
import { CreateTask } from './components/CreateTask'
import { CreateTaskFormProvider } from './providers/CreateTaskFormProvider'

type DayViewProps = {
projects: Array<Project>
Expand Down Expand Up @@ -37,11 +38,13 @@ export const DayView = ({ projects, taskTypes, templates }: DayViewProps) => {
rowGap: '16px'
}}
>
<CreateTask projects={projects} taskTypes={taskTypes} templates={templates} />
<CreateTaskFormProvider>
<CreateTask projects={projects} taskTypes={taskTypes} templates={templates} />

<Divider sx={{ gridArea: 'divider' }} />
<Divider sx={{ gridArea: 'divider' }} />

<TaskList sx={{ gridArea: 'task-list' }} projects={projects} taskTypes={taskTypes} />
<TaskList sx={{ gridArea: 'task-list' }} projects={projects} taskTypes={taskTypes} />
</CreateTaskFormProvider>
</Box>
)
}
11 changes: 7 additions & 4 deletions frontend/src/app/tasks/__tests__/components/CreateTask.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { screen, renderWithUser, act } from '@/test-utils/test-utils'
import { useCreateTask } from '../../hooks/useCreateTask'
import { useGetTasks } from '../../hooks/useGetTasks'
import { useGetCurrentUser } from '@/hooks/useGetCurrentUser/useGetCurrentUser'
import { CreateTaskFormProvider } from '../../providers/CreateTaskFormProvider'

jest.mock('../../hooks/useCreateTask')
jest.mock('../../hooks/useGetTasks')
Expand Down Expand Up @@ -48,7 +49,9 @@ const setupTaskForm = () => {
]

return renderWithUser(
<CreateTask projects={projects} templates={templates} taskTypes={taskTypes} />,
<CreateTaskFormProvider>
<CreateTask projects={projects} templates={templates} taskTypes={taskTypes} />
</CreateTaskFormProvider>,
{
advanceTimers: jest.advanceTimersByTime
}
Expand Down Expand Up @@ -276,7 +279,7 @@ describe('TaskForm', () => {

await user.click(screen.getByRole('button', { name: 'Save' }))

expect(addTask).toHaveBeenCalledWith({
expect(addTask.mock.lastCall[0].task).toEqual({
date: '2023-01-01',
description: 'description!',
endTime: '13:00',
Expand Down Expand Up @@ -312,7 +315,7 @@ describe('TaskForm', () => {

await user.keyboard('{Control>}s')

expect(addTask).toHaveBeenCalledWith({
expect(addTask.mock.lastCall[0].task).toEqual({
date: '2023-01-01',
description: 'description!',
endTime: '13:00',
Expand Down Expand Up @@ -348,7 +351,7 @@ describe('TaskForm', () => {

await user.keyboard('{Enter}')

expect(addTask).toHaveBeenCalledWith({
expect(addTask.mock.lastCall[0].task).toEqual({
date: '2023-01-01',
description: 'description!',
endTime: '13:00',
Expand Down
100 changes: 100 additions & 0 deletions frontend/src/app/tasks/__tests__/components/TaskBox.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import { renderWithUser, screen } from '@/test-utils/test-utils'
import { TaskBox } from '../../components/TaskBox'
import { useDeleteTask } from '../../hooks/useDeleteTask'
import { useCreateTaskForm } from '../../hooks/useCreateTaskForm'

jest.mock('../../hooks/useDeleteTask')
jest.mock('../../hooks/useCreateTaskForm')

const setupTaskBox = () => {
const task = {
date: '2023-11-01',
story: 'task story',
description: 'task description',
taskType: 'task type one',
projectId: 1,
userId: 4,
startTime: '11:12',
endTime: '11:14',
id: 18,
projectName: 'Holidays',
customerName: 'Internal'
}

return renderWithUser(<TaskBox projects={[]} taskTypes={[]} task={task} />)
}

describe('TaskBox', () => {
beforeEach(() => {
;(useDeleteTask as jest.Mock).mockReturnValue({ removeTask: () => {} })
;(useCreateTaskForm as jest.Mock).mockReturnValue({ cloneTask: () => {} })
})

it('Shows the task info', () => {
setupTaskBox()

expect(screen.getByText('Holidays - Internal')).toBeInTheDocument()
expect(screen.getByText('task type one')).toBeInTheDocument()
expect(screen.getByText('11:12-11:14 (0h 2m)')).toBeInTheDocument()
})

describe('Clicking expand Button', () => {
it('shows the complete task information', async () => {
const { user } = setupTaskBox()

await user.click(screen.getByRole('button', { name: 'Expand Task' }))

expect(screen.getByText('task description')).toBeInTheDocument()
expect(screen.getByText('task story')).toBeInTheDocument()
})
})

describe('Clicking delete button', () => {
it('Opens the delete confirmation modal', async () => {
const { user } = setupTaskBox()

await user.click(screen.getByRole('button', { name: 'Delete task 18' }))

expect(screen.getByRole('heading', { name: 'Confirm Deletion' })).toBeInTheDocument()
})

it('deletes the task from the list', async () => {
const deleteTask = jest.fn()
;(useDeleteTask as jest.Mock).mockReturnValue({ deleteTask })

const { user } = setupTaskBox()

await user.click(screen.getByRole('button', { name: 'Delete task 18' }))

await user.click(screen.getByRole('button', { name: 'Delete' }))

expect(deleteTask).toBeCalledWith(18)
})

it('closes the modal without submit if cancel is clicked', async () => {
const deleteTask = jest.fn()
;(useDeleteTask as jest.Mock).mockReturnValue({ deleteTask })

const { user } = setupTaskBox()

await user.click(screen.getByRole('button', { name: 'Delete task 18' }))

await user.click(screen.getByRole('button', { name: 'Cancel' }))

expect(deleteTask).not.toBeCalled()
})
})

describe('Clicking clone task button', () => {
it('calls the clone task function', async () => {
const cloneTask = jest.fn()
;(useCreateTaskForm as jest.Mock).mockReturnValue({ cloneTask })

const { user } = setupTaskBox()

await user.click(screen.getByRole('button', { name: 'Clone Task' }))

expect(cloneTask).toBeCalled()
})
})
})
55 changes: 2 additions & 53 deletions frontend/src/app/tasks/__tests__/components/TaskList.test.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { TaskList } from '../../components/TaskList'
import { screen, renderWithUser, within } from '@/test-utils/test-utils'
import { screen, renderWithUser } from '@/test-utils/test-utils'
import { useGetTasks } from '../../hooks/useGetTasks'
import { useDeleteTask } from '../../hooks/useDeleteTask'
import { Task } from '@/domain/Task'
Expand Down Expand Up @@ -47,62 +47,11 @@ describe('TaskList', () => {
;(useDeleteTask as jest.Mock).mockReturnValue({ removeTask: () => {} })
})

it('renders a list of tasks with name, customer name, task type and the task duration', () => {
it('renders a list of tasks', () => {
setupTaskList()

const listItems = screen.getAllByRole('listitem')

expect(listItems.length).toBe(2)

listItems.forEach((item, index) => {
const { getByText } = within(item)
const task = tasks[index]

const timeDifference = index === 0 ? '0h 2m' : '2h 2m'

expect(getByText(`${task.projectName} - ${task.customerName}`)).toBeInTheDocument()
expect(getByText(`${task.taskType}`)).toBeInTheDocument()
expect(
getByText(`${task.startTime}-${task.endTime} (${timeDifference})`, {
exact: false
})
).toBeInTheDocument()
})
})

describe('When the trash icon is clicked', () => {
it('opens the delete confirmation modal', async () => {
const { user } = setupTaskList()

await user.click(screen.getByRole('button', { name: 'Delete task 18' }))

expect(screen.getByRole('heading', { name: 'Confirm Deletion' })).toBeInTheDocument()
})

it('deletes the task from the list', async () => {
const deleteTask = jest.fn()
;(useDeleteTask as jest.Mock).mockReturnValue({ deleteTask })

const { user } = setupTaskList()

await user.click(screen.getByRole('button', { name: 'Delete task 18' }))

await user.click(screen.getByRole('button', { name: 'Delete' }))

expect(deleteTask).toBeCalledWith(18)
})

it('closes the modal without submit if cancel is clicked', async () => {
const deleteTask = jest.fn()
;(useDeleteTask as jest.Mock).mockReturnValue({ deleteTask })

const { user } = setupTaskList()

await user.click(screen.getByRole('button', { name: 'Delete task 18' }))

await user.click(screen.getByRole('button', { name: 'Cancel' }))

expect(deleteTask).not.toBeCalled()
})
})
})
27 changes: 16 additions & 11 deletions frontend/src/app/tasks/components/CreateTask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import { TextArea } from '@/ui/TextArea/TextArea'
import { Play16Filled, RecordStop24Regular } from '@fluentui/react-icons'

import { TimePicker } from '../components/TimePicker'
import { useTaskForm } from '../hooks/useCreateTaskForm'
import { useCreateTaskForm } from '../hooks/useCreateTaskForm'
import { useTaskFormTimer } from '../hooks/useTaskFormTimer'

import { Project } from '@/domain/Project'
import { TaskType } from '@/domain/TaskType'
Expand All @@ -24,15 +25,17 @@ export const CreateTask = ({ projects, taskTypes, templates }: CreateTaskProps)
task,
handleChange,
resetForm,
toggleTimer,
loggedTime,
isTimerRunning,
selectStartTime,
handleSubmit,
formRef,
selectTemplate,
template
} = useTaskForm()
template,
isLoading
} = useCreateTaskForm()
const { loggedTime, toggleTimer, isTimerRunning } = useTaskFormTimer({
handleChange,
startTime: task.startTime,
endTime: task.endTime
})

return (
<>
Expand All @@ -51,7 +54,7 @@ export const CreateTask = ({ projects, taskTypes, templates }: CreateTaskProps)
/>
<Button
sx={{
width: { xs: '100%', sm: '120px' },
width: { xs: '100%', sm: '125px' },
display: 'flex',
gap: '8px',
gridArea: 'start-timer'
Expand Down Expand Up @@ -95,7 +98,9 @@ export const CreateTask = ({ projects, taskTypes, templates }: CreateTaskProps)
name="startTime"
label="From"
value={task.startTime}
onChange={selectStartTime}
onChange={(option: string) => {
handleChange('startTime', option)
}}
sx={{ width: { xs: '120px', sm: '166px' } }}
disabled={isTimerRunning}
required
Expand Down Expand Up @@ -163,8 +168,8 @@ export const CreateTask = ({ projects, taskTypes, templates }: CreateTaskProps)
<Button variant="outlined" onClick={resetForm} sx={{ width: '82px' }}>
Clear
</Button>
<Button sx={{ width: '82px' }} type="submit">
Save
<Button disabled={isLoading} sx={{ width: '82px' }} type="submit">
{isLoading ? 'Saving...' : 'Save'}
</Button>
</Stack>
</Stack>
Expand Down
4 changes: 1 addition & 3 deletions frontend/src/app/tasks/components/EditTask.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,14 @@ import { useEditTaskForm } from '../hooks/useEditTaskForm'

type EditTaskProps = {
task: Task
tasks: Array<Task>
projects: Array<Project>
taskTypes: Array<TaskType>
closeForm: () => void
}

export const EditTask = ({ task, tasks, projects, taskTypes, closeForm }: EditTaskProps) => {
export const EditTask = ({ task, projects, taskTypes, closeForm }: EditTaskProps) => {
const { handleChange, formState, handleSubmit, resetForm, formRef } = useEditTaskForm({
task,
tasks,
closeForm
})

Expand Down
Loading