Aggregates task.status=DONE counts across last N completed sprints (default 5), filtered by productAccessFilter and returned in chronological order for x-axis rendering. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
77 lines
2.5 KiB
TypeScript
77 lines
2.5 KiB
TypeScript
import { describe, it, expect, vi } from 'vitest'
|
|
|
|
const { mockFindMany } = vi.hoisted(() => ({ mockFindMany: vi.fn() }))
|
|
|
|
vi.mock('@/lib/prisma', () => ({
|
|
prisma: {
|
|
sprint: { findMany: mockFindMany },
|
|
},
|
|
}))
|
|
|
|
vi.mock('@/lib/product-access', () => ({
|
|
productAccessFilter: () => ({ some: 'filter' }),
|
|
}))
|
|
|
|
import { getVelocity } from '@/lib/insights/velocity'
|
|
|
|
const completedAt = (iso: string) => new Date(iso)
|
|
|
|
function makeSprint(id: string, goal: string, productId: string, productName: string, doneCounts: number, completedIso: string) {
|
|
const tasks = Array.from({ length: doneCounts }, () => ({ status: 'DONE' }))
|
|
return {
|
|
id,
|
|
sprint_goal: goal,
|
|
completed_at: completedAt(completedIso),
|
|
product: { id: productId, name: productName },
|
|
tasks,
|
|
}
|
|
}
|
|
|
|
describe('getVelocity', () => {
|
|
it('returns 3 sprints in chronological order with correct done counts', async () => {
|
|
// DB returns newest-first (orderBy: completed_at desc), getVelocity reverses to oldest-first
|
|
mockFindMany.mockResolvedValue([
|
|
makeSprint('s3', 'Sprint C', 'p1', 'Prod A', 3, '2024-03-01T00:00:00.000Z'),
|
|
makeSprint('s2', 'Sprint B', 'p1', 'Prod A', 5, '2024-02-01T00:00:00.000Z'),
|
|
makeSprint('s1', 'Sprint A', 'p1', 'Prod A', 2, '2024-01-01T00:00:00.000Z'),
|
|
])
|
|
|
|
const result = await getVelocity('user-1')
|
|
|
|
expect(result.sprints).toHaveLength(3)
|
|
expect(result.sprints.map(s => s.doneCount)).toEqual([2, 5, 3])
|
|
expect(result.sprints.map(s => s.sprintId)).toEqual(['s1', 's2', 's3'])
|
|
})
|
|
|
|
it('deduplicates productNames from sprints', async () => {
|
|
mockFindMany.mockResolvedValue([
|
|
makeSprint('s2', 'Sprint B', 'p2', 'Prod B', 1, '2024-02-01T00:00:00.000Z'),
|
|
makeSprint('s1', 'Sprint A', 'p1', 'Prod A', 2, '2024-01-01T00:00:00.000Z'),
|
|
])
|
|
|
|
const result = await getVelocity('user-1')
|
|
|
|
const ids = result.productNames.map(p => p.id)
|
|
expect(new Set(ids).size).toBe(ids.length)
|
|
expect(result.productNames).toHaveLength(2)
|
|
})
|
|
|
|
it('returns empty sprints and productNames when no completed sprints exist', async () => {
|
|
mockFindMany.mockResolvedValue([])
|
|
|
|
const result = await getVelocity('user-1')
|
|
|
|
expect(result.sprints).toEqual([])
|
|
expect(result.productNames).toEqual([])
|
|
})
|
|
|
|
it('passes sprintsBack as take parameter', async () => {
|
|
mockFindMany.mockResolvedValue([])
|
|
|
|
await getVelocity('user-1', 3)
|
|
|
|
expect(mockFindMany).toHaveBeenCalledWith(
|
|
expect.objectContaining({ take: 3 }),
|
|
)
|
|
})
|
|
})
|