feat: add task editing
This commit is contained in:
@ -550,6 +550,247 @@ test('statusfel behåller tidigare status och ansvarig och visas på kortet', as
|
||||
expect(within(card).getByText('Ta uppgift')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('redigeringsknappen öppnar rätt uppgift med aktuella värden och titelfokus', async () => {
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
const fetchMock = mockUsersAndTasks(users, [tasks[0], tasks[1]])
|
||||
render(<App />)
|
||||
|
||||
const editButton = await screen.findByRole('button', { name: 'Redigera Dammsuga' })
|
||||
expect(editButton.querySelector('svg')).toBeInTheDocument()
|
||||
fireEvent.pointerDown(editButton)
|
||||
fireEvent.click(editButton)
|
||||
|
||||
const dialog = screen.getByRole('dialog', { name: 'Redigera uppgift' })
|
||||
expect(within(dialog).getByLabelText('Titel')).toHaveValue('Dammsuga')
|
||||
expect(within(dialog).getByLabelText('Titel')).toHaveFocus()
|
||||
expect(within(dialog).getByLabelText('Beskrivning (valfri)')).toHaveValue('Bottenvåningen')
|
||||
expect(within(dialog).getByLabelText('Poäng')).toHaveValue(7)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||
|
||||
fireEvent.click(within(dialog).getByRole('button', { name: 'Avbryt' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Redigera Diska' }))
|
||||
expect(screen.getByLabelText('Beskrivning (valfri)')).toHaveValue('')
|
||||
})
|
||||
|
||||
test('redigeringsmodal stängs normalt och kastar osparade ändringar', async () => {
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
const fetchMock = mockUsersAndTasks(users, [tasks[0]])
|
||||
const { container } = render(<App />)
|
||||
const editButton = await screen.findByRole('button', { name: 'Redigera Dammsuga' })
|
||||
|
||||
fireEvent.click(editButton)
|
||||
fireEvent.change(screen.getByLabelText('Titel'), { target: { value: 'Osparad' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Stäng' }))
|
||||
expect(screen.queryByRole('dialog', { name: 'Redigera uppgift' })).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(editButton)
|
||||
expect(screen.getByLabelText('Titel')).toHaveValue('Dammsuga')
|
||||
fireEvent.keyDown(window, { key: 'Escape' })
|
||||
expect(screen.queryByRole('dialog', { name: 'Redigera uppgift' })).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(editButton)
|
||||
fireEvent.mouseDown(container.querySelector('.modal-backdrop')!)
|
||||
expect(screen.queryByRole('dialog', { name: 'Redigera uppgift' })).not.toBeInTheDocument()
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
test.each([
|
||||
{
|
||||
field: 'Titel',
|
||||
value: ' ',
|
||||
message: 'Titeln måste innehålla mellan 1 och 100 tecken.',
|
||||
},
|
||||
{
|
||||
field: 'Titel',
|
||||
value: 'a'.repeat(101),
|
||||
message: 'Titeln måste innehålla mellan 1 och 100 tecken.',
|
||||
},
|
||||
{
|
||||
field: 'Beskrivning (valfri)',
|
||||
value: 'a'.repeat(501),
|
||||
message: 'Beskrivningen får innehålla högst 500 tecken.',
|
||||
},
|
||||
{
|
||||
field: 'Poäng',
|
||||
value: '1.5',
|
||||
message: 'Poäng måste vara ett heltal mellan 1 och 99.',
|
||||
},
|
||||
{
|
||||
field: 'Poäng',
|
||||
value: '',
|
||||
message: 'Poäng måste vara ett heltal mellan 1 och 99.',
|
||||
},
|
||||
{
|
||||
field: 'Poäng',
|
||||
value: '100',
|
||||
message: 'Poäng måste vara ett heltal mellan 1 och 99.',
|
||||
},
|
||||
])('ogiltig redigeringsdata i $field blockerar API-anrop', async ({ field, value, message }) => {
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
const fetchMock = mockUsersAndTasks(users, [tasks[0]])
|
||||
render(<App />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Redigera Dammsuga' }))
|
||||
fireEvent.change(screen.getByLabelText(field), { target: { value } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Spara' }))
|
||||
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent(message)
|
||||
expect(fetchMock).toHaveBeenCalledTimes(2)
|
||||
})
|
||||
|
||||
test('redigering är serverbekräftad, låser samma task och ersätter hela svaret på samma plats', async () => {
|
||||
const otherTask = {
|
||||
...tasks[0],
|
||||
id: '00000000-0000-0000-0000-000000000010',
|
||||
title: 'Putsa fönster',
|
||||
}
|
||||
const serverTask = {
|
||||
...tasks[0],
|
||||
title: 'Dammsuga övervåningen',
|
||||
description: null,
|
||||
points: 9,
|
||||
assignee: { id: users[1].id, name: users[1].name },
|
||||
}
|
||||
let resolveEdit!: (response: Response) => void
|
||||
const editResponse = new Promise<Response>((resolve) => {
|
||||
resolveEdit = resolve
|
||||
})
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse(users))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse([tasks[0], otherTask]))
|
||||
fetchMock.mockReturnValueOnce(editResponse)
|
||||
const { container } = render(<App />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Redigera Dammsuga' }))
|
||||
fireEvent.change(screen.getByLabelText('Titel'), {
|
||||
target: { value: ' Dammsuga övervåningen ' },
|
||||
})
|
||||
fireEvent.change(screen.getByLabelText('Beskrivning (valfri)'), {
|
||||
target: { value: ' ' },
|
||||
})
|
||||
fireEvent.change(screen.getByLabelText('Poäng'), { target: { value: '9' } })
|
||||
const save = screen.getByRole('button', { name: 'Spara' })
|
||||
fireEvent.click(save)
|
||||
fireEvent.click(save)
|
||||
|
||||
const oldCard = screen.getByText('Dammsuga').closest('article')!
|
||||
const otherCard = screen.getByText('Putsa fönster').closest('article')!
|
||||
const dialog = screen.getByRole('dialog', { name: 'Redigera uppgift' })
|
||||
expect(oldCard).toHaveAttribute('aria-busy', 'true')
|
||||
expect(within(oldCard).getByRole('button', { name: 'Redigera Dammsuga' })).toBeDisabled()
|
||||
expect(within(oldCard).getByRole('button', { name: 'Radera Dammsuga' })).toBeDisabled()
|
||||
expect(within(oldCard).getByRole('button', { name: 'Påbörja' })).toBeDisabled()
|
||||
expect(
|
||||
within(oldCard).getByRole('button', { name: 'Ändra ansvarig för Dammsuga' }),
|
||||
).toBeDisabled()
|
||||
expect(dragAndDrop.disabledTaskIds.has(tasks[0].id)).toBe(true)
|
||||
expect(dragAndDrop.disabledTaskIds.has(otherTask.id)).toBe(false)
|
||||
expect(within(otherCard).getByRole('button', { name: 'Redigera Putsa fönster' })).toBeEnabled()
|
||||
expect(within(dialog).getByLabelText('Titel')).toBeDisabled()
|
||||
expect(within(dialog).getByRole('button', { name: 'Stäng' })).toBeDisabled()
|
||||
expect(within(dialog).getByRole('button', { name: 'Avbryt' })).toBeDisabled()
|
||||
expect(within(dialog).getByRole('button', { name: 'Sparar…' })).toBeDisabled()
|
||||
expect(screen.getByText('Dammsuga')).toBeInTheDocument()
|
||||
expect(screen.queryByText('Dammsuga övervåningen')).not.toBeInTheDocument()
|
||||
expect(fetchMock).toHaveBeenLastCalledWith(`/api/tasks/${tasks[0].id}/details`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
title: 'Dammsuga övervåningen',
|
||||
description: null,
|
||||
points: 9,
|
||||
}),
|
||||
})
|
||||
expect(fetchMock).toHaveBeenCalledTimes(3)
|
||||
|
||||
fireEvent.keyDown(window, { key: 'Escape' })
|
||||
fireEvent.mouseDown(container.querySelector('.modal-backdrop')!)
|
||||
expect(screen.getByRole('dialog', { name: 'Redigera uppgift' })).toBeInTheDocument()
|
||||
|
||||
await act(async () => resolveEdit(jsonResponse(serverTask)))
|
||||
expect(screen.queryByRole('dialog', { name: 'Redigera uppgift' })).not.toBeInTheDocument()
|
||||
const waitingCards = within(screen.getByRole('region', { name: 'Väntande' })).getAllByRole(
|
||||
'article',
|
||||
)
|
||||
expect(within(waitingCards[0]).getByText('Dammsuga övervåningen')).toBeInTheDocument()
|
||||
expect(within(waitingCards[0]).getByText('Anna')).toBeInTheDocument()
|
||||
expect(within(waitingCards[1]).getByText('Putsa fönster')).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('oförändrad redigering skickar alla detaljfälten', async () => {
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse(users))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse([tasks[0]]))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse(tasks[0]))
|
||||
render(<App />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Redigera Dammsuga' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Spara' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(fetchMock).toHaveBeenLastCalledWith(`/api/tasks/${tasks[0].id}/details`, {
|
||||
method: 'PUT',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
title: 'Dammsuga',
|
||||
description: 'Bottenvåningen',
|
||||
points: 7,
|
||||
}),
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
test('vanligt redigeringsfel behåller inmatning och kan återförsökas', async () => {
|
||||
const updatedTask = { ...tasks[0], title: 'Ny titel' }
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse(users))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse([tasks[0]]))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse({ message: 'Serverfel' }, 500))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse(updatedTask))
|
||||
render(<App />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Redigera Dammsuga' }))
|
||||
fireEvent.change(screen.getByLabelText('Titel'), { target: { value: 'Ny titel' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Spara' }))
|
||||
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('Serverfel')
|
||||
expect(screen.getByLabelText('Titel')).toHaveValue('Ny titel')
|
||||
expect(screen.getByRole('button', { name: 'Spara' })).toBeEnabled()
|
||||
expect(screen.getByText('Dammsuga')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Spara' }))
|
||||
expect(await screen.findByText('Ny titel')).toBeInTheDocument()
|
||||
expect(fetchMock).toHaveBeenCalledTimes(4)
|
||||
})
|
||||
|
||||
test('endast 404 TASK_NOT_FOUND tar bort ett inaktuellt kort vid redigering', async () => {
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse(users))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse([tasks[0]]))
|
||||
fetchMock.mockResolvedValueOnce(
|
||||
jsonResponse({ code: 'OTHER_NOT_FOUND', message: 'Annat fel' }, 404),
|
||||
)
|
||||
fetchMock.mockResolvedValueOnce(
|
||||
jsonResponse({ code: 'TASK_NOT_FOUND', message: 'Uppgiften finns inte.' }, 404),
|
||||
)
|
||||
render(<App />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Redigera Dammsuga' }))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Spara' }))
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('Annat fel')
|
||||
expect(screen.getByText('Dammsuga')).toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Spara' }))
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole('button', { name: 'Redigera Dammsuga' })).not.toBeInTheDocument(),
|
||||
)
|
||||
expect(screen.queryByRole('dialog', { name: 'Redigera uppgift' })).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('sopkorgsknappen öppnar delete-modal med Avbryt i fokus och utan delete-anrop', async () => {
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
const fetchMock = mockUsersAndTasks(users, [tasks[0]])
|
||||
|
||||
Reference in New Issue
Block a user