// @vitest-environment happy-dom import '@testing-library/jest-dom/vitest' import { render, screen } from '@testing-library/react' import { vi, describe, it, expect } from 'vitest' vi.mock('@/lib/trpc', () => ({ trpc: { useUtils: () => ({ listInitiatives: { invalidate: vi.fn() } }), updateInitiative: { useMutation: vi.fn(() => ({ mutate: vi.fn() })) }, deleteInitiative: { useMutation: vi.fn(() => ({ mutate: vi.fn() })) }, }, })) vi.mock('@/components/ui/card', () => ({ Card: ({ children, className, onClick }: any) => (
{children}
), })) import { InitiativeCard, type SerializedInitiative } from './InitiativeCard' function makeInitiative(overrides: Partial = {}): SerializedInitiative { return { id: 'init-1', name: 'Test Initiative', status: 'active', branch: null, createdAt: '2024-01-01T00:00:00Z', updatedAt: '2024-01-01T00:00:00Z', projects: [], ...overrides, activity: { state: 'planning', activePhase: null, phasesTotal: 0, phasesCompleted: 0, ...(overrides.activity ?? {}), }, } } describe('InitiativeCard — single-row structure', () => { it('renders no second-row div (no mt-1.5 class in DOM)', () => { render( {}} />) expect(document.querySelector('.mt-1\\.5')).toBeNull() }) it('flex container has items-center class', () => { render( {}} />) const flexContainer = document.querySelector('.flex.items-center') expect(flexContainer).not.toBeNull() }) }) describe('InitiativeCard — project badge overflow', () => { it('1 project → 1 badge with project name, no +N chip', () => { const initiative = makeInitiative({ projects: [{ id: 'p1', name: 'Project Alpha' }], }) render( {}} />) expect(screen.getByText('Project Alpha')).toBeInTheDocument() expect(screen.queryByText(/^\+\d+$/)).toBeNull() }) it('3 projects → first 2 names present, third absent, +1 chip', () => { const initiative = makeInitiative({ projects: [ { id: 'p1', name: 'Alpha' }, { id: 'p2', name: 'Beta' }, { id: 'p3', name: 'Gamma' }, ], }) render( {}} />) expect(screen.getByText('Alpha')).toBeInTheDocument() expect(screen.getByText('Beta')).toBeInTheDocument() expect(screen.queryByText('Gamma')).toBeNull() expect(screen.getByText('+1')).toBeInTheDocument() }) it('5 projects → first 2 names present, +3 chip', () => { const initiative = makeInitiative({ projects: [ { id: 'p1', name: 'Alpha' }, { id: 'p2', name: 'Beta' }, { id: 'p3', name: 'Gamma' }, { id: 'p4', name: 'Delta' }, { id: 'p5', name: 'Epsilon' }, ], }) render( {}} />) expect(screen.getByText('Alpha')).toBeInTheDocument() expect(screen.getByText('Beta')).toBeInTheDocument() expect(screen.getByText('+3')).toBeInTheDocument() }) it('0 projects → no badge elements rendered', () => { const initiative = makeInitiative({ projects: [] }) render( {}} />) // No outline or secondary badges for projects expect(document.querySelectorAll('[class*="rounded-full"][class*="border"]').length).toBe(0) }) }) describe('InitiativeCard — hover-reveal menu', () => { it('dropdown wrapper has opacity-0 class', () => { render( {}} />) const wrapper = document.querySelector('.opacity-0') expect(wrapper).not.toBeNull() }) it('dropdown wrapper has group-hover:opacity-100 class', () => { render( {}} />) const wrapper = document.querySelector('.opacity-0') expect(wrapper).toHaveClass('group-hover:opacity-100') }) it('card has group class', () => { render( {}} />) const card = screen.getByTestId('card') expect(card).toHaveClass('group') }) }) describe('InitiativeCard — phase counter', () => { it('shows "2 / 5" when phasesCompleted=2 and phasesTotal=5', () => { const initiative = makeInitiative({ activity: { state: 'planning', activePhase: null, phasesTotal: 5, phasesCompleted: 2 }, }) render( {}} />) expect(screen.getByText('2 / 5')).toBeInTheDocument() }) it('counter absent when phasesTotal=0', () => { const initiative = makeInitiative({ activity: { state: 'planning', activePhase: null, phasesTotal: 0, phasesCompleted: 0 }, }) render( {}} />) expect(screen.queryByText(/\d+ \/ \d+/)).toBeNull() }) }) describe('InitiativeCard — activity dot pulse', () => { it('state="executing" → StatusDot has animate-status-pulse class', () => { const initiative = makeInitiative({ activity: { state: 'executing', activePhase: null, phasesTotal: 0, phasesCompleted: 0 }, }) render( {}} />) const dot = document.querySelector('[role="status"]') expect(dot).toHaveClass('animate-status-pulse') }) it('state="planning" → StatusDot does not have animate-status-pulse class', () => { const initiative = makeInitiative({ activity: { state: 'planning', activePhase: null, phasesTotal: 0, phasesCompleted: 0 }, }) render( {}} />) const dot = document.querySelector('[role="status"]') expect(dot).not.toHaveClass('animate-status-pulse') }) }) describe('InitiativeCard — active phase name', () => { it('activePhase present → phase name and separator visible', () => { const initiative = makeInitiative({ activity: { state: 'planning', activePhase: { id: 'p1', name: 'Phase Alpha' }, phasesTotal: 0, phasesCompleted: 0, }, }) render( {}} />) expect(screen.getByText('Phase Alpha')).toBeInTheDocument() expect(screen.getByText('·')).toBeInTheDocument() }) it('activePhase=null → no separator or phase name rendered', () => { const initiative = makeInitiative({ activity: { state: 'planning', activePhase: null, phasesTotal: 0, phasesCompleted: 0 }, }) render( {}} />) expect(screen.queryByText('·')).toBeNull() }) })