Scrum4Me/__tests__/actions/active-sprint-action.test.ts
Madhura68 2af6f24598 feat(PBI-79/ST-1333): active-sprint null-contract + clearActiveSprintAction
- lib/user-settings.ts: activeSprints values nullable in Zod-schema.
  Key-aanwezigheid heeft nu betekenis (key+null = bewust geen sprint;
  key ontbreekt = fallback-cascade).
- lib/active-sprint.ts: nieuwe readStoredActiveSprintState helper +
  resolveActiveSprint respecteert expliciet 'cleared' state zonder fallback.
  clearActiveSprintInSettings schrijft null i.p.v. de key te verwijderen.
- actions/active-sprint.ts: nieuwe clearActiveSprintAction met auth +
  membership-check.
- components/shared/sprint-switcher.tsx: '— Geen actieve sprint —'-optie
  in dropdown, disabled wanneer er geen actieve sprint is.
- Tests: nieuwe active-sprint.test.ts (resolver-paden + clear),
  active-sprint-action.test.ts (action-laag), uitbreiding user-settings.test.ts.

Plan: docs/plans/PBI-79-backlog-sprint-workflow.md

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:35:32 +02:00

103 lines
3 KiB
TypeScript

import { describe, it, expect, vi, beforeEach } from 'vitest'
vi.mock('next/cache', () => ({ revalidatePath: vi.fn() }))
vi.mock('next/headers', () => ({
cookies: vi.fn().mockResolvedValue({
set: vi.fn(),
get: vi.fn(),
delete: vi.fn(),
}),
}))
vi.mock('iron-session', () => ({
getIronSession: vi.fn().mockResolvedValue({ userId: 'user-1', isDemo: false }),
}))
vi.mock('@/lib/session', () => ({
sessionOptions: { cookieName: 'test', password: 'test' },
}))
vi.mock('@/lib/product-access', () => ({
productAccessFilter: vi.fn().mockReturnValue({}),
}))
vi.mock('@/lib/prisma', () => ({
prisma: {
sprint: { findFirst: vi.fn() },
product: { findFirst: vi.fn() },
user: {
findUnique: vi.fn(),
update: vi.fn().mockResolvedValue({}),
},
$executeRaw: vi.fn().mockResolvedValue(1),
},
}))
import { prisma } from '@/lib/prisma'
import { clearActiveSprintAction } from '@/actions/active-sprint'
const mockPrisma = prisma as unknown as {
product: { findFirst: ReturnType<typeof vi.fn> }
user: {
findUnique: ReturnType<typeof vi.fn>
update: ReturnType<typeof vi.fn>
}
}
describe('clearActiveSprintAction', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('writes null instead of deleting the key', async () => {
mockPrisma.product.findFirst.mockResolvedValueOnce({ id: 'p1' })
mockPrisma.user.findUnique.mockResolvedValueOnce({
settings: { layout: { activeSprints: { p1: 'sprint-1', p2: 'sprint-2' } } },
})
const result = await clearActiveSprintAction('p1')
expect(result).toEqual({ success: true })
const updateArg = mockPrisma.user.update.mock.calls[0][0] as {
data: { settings: { layout?: { activeSprints?: Record<string, string | null> } } }
}
expect(updateArg.data.settings.layout?.activeSprints).toEqual({
p1: null,
p2: 'sprint-2',
})
})
it('preserves other product keys when clearing one', async () => {
mockPrisma.product.findFirst.mockResolvedValueOnce({ id: 'p1' })
mockPrisma.user.findUnique.mockResolvedValueOnce({
settings: {
layout: {
activeSprints: { p1: 'sprint-1', p2: 'sprint-2', p3: null },
},
},
})
await clearActiveSprintAction('p1')
const updateArg = mockPrisma.user.update.mock.calls[0][0] as {
data: { settings: { layout?: { activeSprints?: Record<string, string | null> } } }
}
expect(updateArg.data.settings.layout?.activeSprints).toEqual({
p1: null,
p2: 'sprint-2',
p3: null,
})
})
it('rejects when product is not accessible', async () => {
mockPrisma.product.findFirst.mockResolvedValueOnce(null)
const result = await clearActiveSprintAction('p1')
expect(result).toEqual({ error: 'Product niet gevonden of niet toegankelijk' })
expect(mockPrisma.user.update).not.toHaveBeenCalled()
})
it('rejects invalid productId', async () => {
const result = await clearActiveSprintAction('')
expect(result).toEqual({ error: 'Ongeldig product-id' })
expect(mockPrisma.user.update).not.toHaveBeenCalled()
})
})