feat: add task creation board
This commit is contained in:
@ -1,4 +1,4 @@
|
||||
import { cleanup, fireEvent, render, screen } from '@testing-library/react'
|
||||
import { cleanup, fireEvent, render, screen, waitFor, within } from '@testing-library/react'
|
||||
import { afterEach, beforeEach, expect, test, vi } from 'vitest'
|
||||
import App from './App'
|
||||
|
||||
@ -15,6 +15,30 @@ const users = [
|
||||
},
|
||||
]
|
||||
|
||||
const tasks = [
|
||||
{
|
||||
id: '00000000-0000-0000-0000-000000000001',
|
||||
title: 'Dammsuga',
|
||||
description: 'Bottenvåningen',
|
||||
status: 'WAITING',
|
||||
createdAt: '2026-07-24T10:00:00Z',
|
||||
},
|
||||
{
|
||||
id: '00000000-0000-0000-0000-000000000002',
|
||||
title: 'Diska',
|
||||
description: null,
|
||||
status: 'IN_PROGRESS',
|
||||
createdAt: '2026-07-24T10:01:00Z',
|
||||
},
|
||||
{
|
||||
id: '00000000-0000-0000-0000-000000000003',
|
||||
title: 'Vattna blommor',
|
||||
description: null,
|
||||
status: 'COMPLETED',
|
||||
createdAt: '2026-07-24T10:02:00Z',
|
||||
},
|
||||
]
|
||||
|
||||
beforeEach(() => {
|
||||
window.localStorage.clear()
|
||||
})
|
||||
@ -42,13 +66,14 @@ test('befintliga användare utan lokalt val visar Vem är du', async () => {
|
||||
expect(screen.getByRole('button', { name: 'Anna' })).toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('val av användare lagrar id och visar startsidan', async () => {
|
||||
mockJsonResponse(users)
|
||||
test('val av användare lagrar id och visar brädan', async () => {
|
||||
mockUsersAndTasks(users, [])
|
||||
render(<App />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Urban' }))
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'Hej Urban' })).toBeInTheDocument()
|
||||
expect(await screen.findByRole('heading', { name: 'Uppgifter' })).toBeInTheDocument()
|
||||
expect(screen.getByText('Urban')).toBeInTheDocument()
|
||||
expect(window.localStorage.getItem('hemhub.activeUserId')).toBe(users[0].id)
|
||||
})
|
||||
|
||||
@ -57,22 +82,23 @@ test('skapad användare blir automatiskt aktiv', async () => {
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse([]))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse(createdUser, 201))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse([]))
|
||||
|
||||
render(<App />)
|
||||
|
||||
fireEvent.change(await screen.findByLabelText('Namn'), { target: { value: ' Urban ' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Skapa användare' }))
|
||||
|
||||
expect(await screen.findByRole('heading', { name: 'Hej Urban' })).toBeInTheDocument()
|
||||
expect(await screen.findByRole('heading', { name: 'Uppgifter' })).toBeInTheDocument()
|
||||
expect(window.localStorage.getItem('hemhub.activeUserId')).toBe(createdUser.id)
|
||||
expect(fetchMock).toHaveBeenLastCalledWith('/api/users', {
|
||||
expect(fetchMock).toHaveBeenNthCalledWith(2, '/api/users', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ name: 'Urban' }),
|
||||
})
|
||||
})
|
||||
|
||||
test('skapandefel visas utan att namnet försvinner', async () => {
|
||||
test('skapandefel för användare visas utan att namnet försvinner', async () => {
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse([]))
|
||||
fetchMock.mockResolvedValueOnce(
|
||||
@ -97,18 +123,20 @@ test('skapandefel visas utan att namnet försvinner', async () => {
|
||||
expect(input).toHaveValue('Urban')
|
||||
})
|
||||
|
||||
test('Byt användare rensar valt id och visar användarvalet', async () => {
|
||||
test('Logga ut rensar valt id och väljer inte användaren automatiskt', async () => {
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
mockJsonResponse(users)
|
||||
mockUsersAndTasks(users, [])
|
||||
render(<App />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Byt användare' }))
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Logga ut' }))
|
||||
|
||||
expect(screen.getByRole('heading', { name: 'Vem är du?' })).toBeInTheDocument()
|
||||
expect(screen.getByRole('button', { name: 'Urban' })).toBeInTheDocument()
|
||||
expect(screen.queryByRole('heading', { name: 'Uppgifter' })).not.toBeInTheDocument()
|
||||
expect(window.localStorage.getItem('hemhub.activeUserId')).toBeNull()
|
||||
})
|
||||
|
||||
test('hämtningsfel visas som fel och kan återförsökas', async () => {
|
||||
test('hämtningsfel för användare visas som fel och kan återförsökas', async () => {
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
||||
fetchMock.mockRejectedValueOnce(new Error('Nätverksfel'))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse(users))
|
||||
@ -133,6 +161,147 @@ test('ogiltigt lagrat användar-id rensas', async () => {
|
||||
expect(window.localStorage.getItem('hemhub.activeUserId')).toBeNull()
|
||||
})
|
||||
|
||||
test('brädan visar tre kolumner och grupperar hämtade uppgifter', async () => {
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
mockUsersAndTasks(users, tasks)
|
||||
|
||||
render(<App />)
|
||||
|
||||
const waiting = await screen.findByRole('region', { name: 'Väntande' })
|
||||
const inProgress = screen.getByRole('region', { name: 'Pågående' })
|
||||
const completed = screen.getByRole('region', { name: 'Klart' })
|
||||
|
||||
expect(within(waiting).getByText('Dammsuga')).toBeInTheDocument()
|
||||
expect(within(waiting).getByText('Bottenvåningen')).toBeInTheDocument()
|
||||
expect(within(inProgress).getByText('Diska')).toBeInTheDocument()
|
||||
expect(within(completed).getByText('Vattna blommor')).toBeInTheDocument()
|
||||
expect(screen.queryByText(/Inga uppgifter/i)).not.toBeInTheDocument()
|
||||
})
|
||||
|
||||
test('Ny uppgift öppnar modalen med fokus i titelfältet', async () => {
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
mockUsersAndTasks(users, [])
|
||||
render(<App />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Ny uppgift' }))
|
||||
|
||||
expect(screen.getByRole('dialog', { name: 'Skapa ny uppgift' })).toBeInTheDocument()
|
||||
expect(screen.getByLabelText('Titel')).toHaveFocus()
|
||||
})
|
||||
|
||||
test.each([
|
||||
{
|
||||
sätt: 'stängningskrysset',
|
||||
close: () => fireEvent.click(screen.getByRole('button', { name: 'Stäng' })),
|
||||
},
|
||||
{
|
||||
sätt: 'Escape',
|
||||
close: () => fireEvent.keyDown(window, { key: 'Escape' }),
|
||||
},
|
||||
{
|
||||
sätt: 'modalens bakgrund',
|
||||
close: () => {
|
||||
const backdrop = screen.getByRole('dialog', { name: 'Skapa ny uppgift' }).parentElement
|
||||
|
||||
if (!backdrop) {
|
||||
throw new Error('Modalens bakgrund saknas')
|
||||
}
|
||||
|
||||
fireEvent.mouseDown(backdrop)
|
||||
},
|
||||
},
|
||||
])('modalen stängs med $sätt och rensar formuläret', async ({ close }) => {
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
mockUsersAndTasks(users, [])
|
||||
render(<App />)
|
||||
|
||||
fireEvent.click(await screen.findByRole('button', { name: 'Ny uppgift' }))
|
||||
fireEvent.change(screen.getByLabelText('Titel'), { target: { value: 'Dammsuga' } })
|
||||
fireEvent.change(screen.getByLabelText('Beskrivning (valfri)'), {
|
||||
target: { value: 'Bottenvåningen' },
|
||||
})
|
||||
|
||||
close()
|
||||
|
||||
expect(screen.queryByRole('dialog', { name: 'Skapa ny uppgift' })).not.toBeInTheDocument()
|
||||
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Ny uppgift' }))
|
||||
|
||||
expect(screen.getByLabelText('Titel')).toHaveValue('')
|
||||
expect(screen.getByLabelText('Beskrivning (valfri)')).toHaveValue('')
|
||||
})
|
||||
|
||||
test('en skapad uppgift visas längst ned i Väntande och modalen stängs', async () => {
|
||||
const createdTask = {
|
||||
id: '00000000-0000-0000-0000-000000000004',
|
||||
title: 'Putsa fönster',
|
||||
description: 'Köket',
|
||||
status: 'WAITING',
|
||||
createdAt: '2026-07-24T10:03:00Z',
|
||||
}
|
||||
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(createdTask, 201))
|
||||
|
||||
render(<App />)
|
||||
|
||||
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Ny uppgift' }))
|
||||
fireEvent.change(screen.getByLabelText('Titel'), { target: { value: ' Putsa fönster ' } })
|
||||
fireEvent.change(screen.getByLabelText('Beskrivning (valfri)'), {
|
||||
target: { value: ' Köket ' },
|
||||
})
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Skapa uppgift' }))
|
||||
|
||||
await waitFor(() =>
|
||||
expect(screen.queryByRole('dialog', { name: 'Skapa ny uppgift' })).not.toBeInTheDocument(),
|
||||
)
|
||||
const waiting = screen.getByRole('region', { name: 'Väntande' })
|
||||
expect(within(waiting).getAllByRole('article').map((card) => card.textContent)).toEqual([
|
||||
'DammsugaBottenvåningen',
|
||||
'Putsa fönsterKöket',
|
||||
])
|
||||
expect(fetchMock).toHaveBeenLastCalledWith('/api/tasks', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ title: 'Putsa fönster', description: 'Köket' }),
|
||||
})
|
||||
})
|
||||
|
||||
test('formulärdata bevaras när skapande av uppgift misslyckas', async () => {
|
||||
window.localStorage.setItem('hemhub.activeUserId', users[0].id)
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse(users))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse([]))
|
||||
fetchMock.mockResolvedValueOnce(
|
||||
jsonResponse({ code: 'INVALID_TASK', message: 'Uppgiften är ogiltig.' }, 400),
|
||||
)
|
||||
|
||||
render(<App />)
|
||||
|
||||
await waitFor(() => expect(fetchMock).toHaveBeenCalledTimes(2))
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Ny uppgift' }))
|
||||
const title = screen.getByLabelText('Titel')
|
||||
const description = screen.getByLabelText('Beskrivning (valfri)')
|
||||
fireEvent.change(title, { target: { value: 'Dammsuga' } })
|
||||
fireEvent.change(description, { target: { value: 'Bottenvåningen' } })
|
||||
fireEvent.click(screen.getByRole('button', { name: 'Skapa uppgift' }))
|
||||
|
||||
expect(await screen.findByRole('alert')).toHaveTextContent('Uppgiften är ogiltig.')
|
||||
expect(screen.getByRole('dialog', { name: 'Skapa ny uppgift' })).toBeInTheDocument()
|
||||
expect(title).toHaveValue('Dammsuga')
|
||||
expect(description).toHaveValue('Bottenvåningen')
|
||||
})
|
||||
|
||||
function mockUsersAndTasks(userResponse: unknown, taskResponse: unknown) {
|
||||
const fetchMock = vi.spyOn(globalThis, 'fetch')
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse(userResponse))
|
||||
fetchMock.mockResolvedValueOnce(jsonResponse(taskResponse))
|
||||
return fetchMock
|
||||
}
|
||||
|
||||
function mockJsonResponse(body: unknown, status = 200) {
|
||||
return vi.spyOn(globalThis, 'fetch').mockResolvedValue(jsonResponse(body, status))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user