import { describe, expect, it } from 'vitest' import { extractLabelFacts } from '../labels/label-schema.js' import type { LifecycleEvent } from '../lifecycle/lifecycle-v0.js' import type { GiteaIssue } from '../gitea/types.js' import { buildProjectView, type ProjectSnapshot } from './query-project.js' const asOf = new Date('2026-02-12T00:00:00Z') function issue(over: Partial): GiteaIssue { const labels = over.labels ?? ['est/2d'] return { number: 1, title: '#1', body: '', state: 'open', labels, facts: extractLabelFacts(labels), milestone: null, assignee: null, assignees: [], createdAt: '2026-02-02T09:00:00Z', updatedAt: '2026-02-02T09:00:00Z', closedAt: null, url: '', ...over, } } describe('buildProjectView: standup (#28)', () => { it('surfaces schedule drift + a stale blocker, with a per-person plan', () => { // #7 started work 6 working days ago (steeping) and blocks #8; est is 2d → past estimate. const steeping = issue({ number: 7, labels: ['est/2d', 'p/1'], assignee: 'christian' }) const blocked = issue({ number: 8, labels: ['est/3d', 'p/2'], assignee: 'stephen' }) const timelines: Record = { 7: [{ type: 'commit', at: '2026-02-04T09:00:00Z' }], } const snap: ProjectSnapshot = { issues: [steeping, blocked], timelines, deps: [{ issue: 8, dependsOn: 7 }] } const v = buildProjectView('standup', undefined, snap, asOf) as { plan: { who: string; issue: number; why: string }[] drift: { issue: number; note: string }[] nag: { issue: number; steepingDays: number; blocks: number[] } | null } // a stale blocker is nagged, and it's the steeping one that blocks another expect(v.nag).not.toBeNull() expect(v.nag!.issue).toBe(7) expect(v.nag!.steepingDays).toBeGreaterThan(0) expect(v.nag!.blocks).toContain(8) // drift caught the past-estimate steep expect(v.drift.some((d) => d.issue === 7)).toBe(true) // plan gives an earliest pick per person (both assignees represented) expect(v.plan.map((p) => p.who)).toEqual(expect.arrayContaining(['christian', 'stephen'])) // #7 leads its person's plan on the critical path expect(v.plan.find((p) => p.issue === 7)?.why).toContain('critical') }) it('is calm when nothing is steeping (no nag, empty drift)', () => { const snap: ProjectSnapshot = { issues: [issue({ number: 1, labels: ['est/2d'] })], timelines: {}, deps: [], } const v = buildProjectView('standup', undefined, snap, asOf) as { drift: unknown[] nag: unknown | null } expect(v.nag).toBeNull() expect(v.drift).toEqual([]) }) })