- Fix vitest.config.ts React alias to point to local node_modules
instead of nonexistent ancestor path (../../../../node_modules/react)
- Remove stale HQWaitingForInputSection tests (component was deleted
in e8d332e0 but test cases were left behind)
339 lines
11 KiB
TypeScript
339 lines
11 KiB
TypeScript
// @vitest-environment happy-dom
|
||
import '@testing-library/jest-dom/vitest'
|
||
import { render, screen, fireEvent } from '@testing-library/react'
|
||
import { vi, describe, it, expect, beforeEach } from 'vitest'
|
||
|
||
const mockNavigate = vi.fn()
|
||
vi.mock('@tanstack/react-router', () => ({
|
||
useNavigate: () => mockNavigate,
|
||
Link: ({ to, children, className }: { to: string; children: React.ReactNode; className?: string }) => (
|
||
<a href={to} className={className}>{children}</a>
|
||
),
|
||
}))
|
||
|
||
// Mock formatRelativeTime to return a predictable string
|
||
vi.mock('@/lib/utils', () => ({
|
||
cn: (...classes: string[]) => classes.filter(Boolean).join(' '),
|
||
formatRelativeTime: () => '5 minutes ago',
|
||
}))
|
||
|
||
import { HQNeedsReviewSection } from './HQNeedsReviewSection'
|
||
import { HQNeedsApprovalSection } from './HQNeedsApprovalSection'
|
||
import { HQResolvingConflictsSection } from './HQResolvingConflictsSection'
|
||
import { HQBlockedSection } from './HQBlockedSection'
|
||
import { HQEmptyState } from './HQEmptyState'
|
||
|
||
const since = new Date(Date.now() - 5 * 60 * 1000).toISOString()
|
||
|
||
// ─── HQNeedsReviewSection ────────────────────────────────────────────────────
|
||
|
||
describe('HQNeedsReviewSection', () => {
|
||
beforeEach(() => vi.clearAllMocks())
|
||
|
||
it('renders section heading "Needs Review"', () => {
|
||
render(<HQNeedsReviewSection initiatives={[]} phases={[]} />)
|
||
expect(screen.getByText('Needs Review')).toBeInTheDocument()
|
||
})
|
||
|
||
it('2a: shows initiative name, "Content ready for review", "Review" CTA navigates correctly', () => {
|
||
render(
|
||
<HQNeedsReviewSection
|
||
initiatives={[
|
||
{ initiativeId: 'init-1', initiativeName: 'Init One', since },
|
||
]}
|
||
phases={[]}
|
||
/>
|
||
)
|
||
expect(screen.getByText('Init One')).toBeInTheDocument()
|
||
expect(screen.getByText('Content ready for review')).toBeInTheDocument()
|
||
fireEvent.click(screen.getByRole('button', { name: /^review$/i }))
|
||
expect(mockNavigate).toHaveBeenCalledWith({
|
||
to: '/initiatives/$id',
|
||
params: { id: 'init-1' },
|
||
search: { tab: 'review' },
|
||
})
|
||
})
|
||
|
||
it('2b: shows initiative › phase, "Phase execution complete — review diff", "Review Diff" navigates correctly', () => {
|
||
render(
|
||
<HQNeedsReviewSection
|
||
initiatives={[]}
|
||
phases={[
|
||
{
|
||
phaseId: 'ph-1',
|
||
phaseName: 'Phase One',
|
||
initiativeId: 'init-1',
|
||
initiativeName: 'Init One',
|
||
since,
|
||
},
|
||
]}
|
||
/>
|
||
)
|
||
expect(screen.getByText('Init One › Phase One')).toBeInTheDocument()
|
||
expect(screen.getByText('Phase execution complete — review diff')).toBeInTheDocument()
|
||
fireEvent.click(screen.getByRole('button', { name: /review diff/i }))
|
||
expect(mockNavigate).toHaveBeenCalledWith({
|
||
to: '/initiatives/$id',
|
||
params: { id: 'init-1' },
|
||
search: { tab: 'review' },
|
||
})
|
||
})
|
||
|
||
it('when only initiatives provided, only 2a cards render', () => {
|
||
render(
|
||
<HQNeedsReviewSection
|
||
initiatives={[{ initiativeId: 'init-1', initiativeName: 'Init One', since }]}
|
||
phases={[]}
|
||
/>
|
||
)
|
||
expect(screen.getByText('Content ready for review')).toBeInTheDocument()
|
||
expect(screen.queryByText('Phase execution complete — review diff')).not.toBeInTheDocument()
|
||
})
|
||
|
||
it('when only phases provided, only 2b cards render', () => {
|
||
render(
|
||
<HQNeedsReviewSection
|
||
initiatives={[]}
|
||
phases={[
|
||
{
|
||
phaseId: 'ph-1',
|
||
phaseName: 'Phase One',
|
||
initiativeId: 'init-1',
|
||
initiativeName: 'Init One',
|
||
since,
|
||
},
|
||
]}
|
||
/>
|
||
)
|
||
expect(screen.getByText('Phase execution complete — review diff')).toBeInTheDocument()
|
||
expect(screen.queryByText('Content ready for review')).not.toBeInTheDocument()
|
||
})
|
||
})
|
||
|
||
// ─── HQNeedsApprovalSection ──────────────────────────────────────────────────
|
||
|
||
describe('HQNeedsApprovalSection', () => {
|
||
beforeEach(() => vi.clearAllMocks())
|
||
|
||
it('renders "Needs Approval to Continue" heading', () => {
|
||
render(<HQNeedsApprovalSection items={[]} />)
|
||
expect(screen.getByText('Needs Approval to Continue')).toBeInTheDocument()
|
||
})
|
||
|
||
it('shows singular phase count: "1 phase awaiting approval"', () => {
|
||
render(
|
||
<HQNeedsApprovalSection
|
||
items={[
|
||
{ initiativeId: 'init-1', initiativeName: 'Init One', pendingPhaseCount: 1, since },
|
||
]}
|
||
/>
|
||
)
|
||
expect(screen.getByText('Plan ready — 1 phase awaiting approval')).toBeInTheDocument()
|
||
})
|
||
|
||
it('shows plural phase count: "3 phases awaiting approval"', () => {
|
||
render(
|
||
<HQNeedsApprovalSection
|
||
items={[
|
||
{ initiativeId: 'init-1', initiativeName: 'Init One', pendingPhaseCount: 3, since },
|
||
]}
|
||
/>
|
||
)
|
||
expect(screen.getByText('Plan ready — 3 phases awaiting approval')).toBeInTheDocument()
|
||
})
|
||
|
||
it('"Review Plan" CTA navigates to /initiatives/$id?tab=plan', () => {
|
||
render(
|
||
<HQNeedsApprovalSection
|
||
items={[
|
||
{ initiativeId: 'init-1', initiativeName: 'Init One', pendingPhaseCount: 2, since },
|
||
]}
|
||
/>
|
||
)
|
||
fireEvent.click(screen.getByRole('button', { name: /review plan/i }))
|
||
expect(mockNavigate).toHaveBeenCalledWith({
|
||
to: '/initiatives/$id',
|
||
params: { id: 'init-1' },
|
||
search: { tab: 'plan' },
|
||
})
|
||
})
|
||
})
|
||
|
||
// ─── HQResolvingConflictsSection ──────────────────────────────────────────────
|
||
|
||
describe('HQResolvingConflictsSection', () => {
|
||
beforeEach(() => vi.clearAllMocks())
|
||
|
||
it('renders "Resolving Conflicts" heading', () => {
|
||
render(<HQResolvingConflictsSection items={[]} />)
|
||
expect(screen.getByText('Resolving Conflicts')).toBeInTheDocument()
|
||
})
|
||
|
||
it('shows initiative name and "Running" badge for running agent', () => {
|
||
render(
|
||
<HQResolvingConflictsSection
|
||
items={[
|
||
{
|
||
initiativeId: 'init-1',
|
||
initiativeName: 'My Initiative',
|
||
agentId: 'a1',
|
||
agentName: 'conflict-1234567890',
|
||
agentStatus: 'running',
|
||
since,
|
||
},
|
||
]}
|
||
/>
|
||
)
|
||
expect(screen.getByText('My Initiative')).toBeInTheDocument()
|
||
expect(screen.getByText('Running')).toBeInTheDocument()
|
||
})
|
||
|
||
it('shows "Needs Input" badge for waiting_for_input agent', () => {
|
||
render(
|
||
<HQResolvingConflictsSection
|
||
items={[
|
||
{
|
||
initiativeId: 'init-1',
|
||
initiativeName: 'My Initiative',
|
||
agentId: 'a1',
|
||
agentName: 'conflict-1234567890',
|
||
agentStatus: 'waiting_for_input',
|
||
since,
|
||
},
|
||
]}
|
||
/>
|
||
)
|
||
expect(screen.getByText('Needs Input')).toBeInTheDocument()
|
||
})
|
||
|
||
it('"View" CTA navigates to /initiatives/$id?tab=execution', () => {
|
||
render(
|
||
<HQResolvingConflictsSection
|
||
items={[
|
||
{
|
||
initiativeId: 'init-1',
|
||
initiativeName: 'My Initiative',
|
||
agentId: 'a1',
|
||
agentName: 'conflict-1234567890',
|
||
agentStatus: 'running',
|
||
since,
|
||
},
|
||
]}
|
||
/>
|
||
)
|
||
fireEvent.click(screen.getByRole('button', { name: /view/i }))
|
||
expect(mockNavigate).toHaveBeenCalledWith({
|
||
to: '/initiatives/$id',
|
||
params: { id: 'init-1' },
|
||
search: { tab: 'execution' },
|
||
})
|
||
})
|
||
})
|
||
|
||
// ─── HQBlockedSection ────────────────────────────────────────────────────────
|
||
|
||
describe('HQBlockedSection', () => {
|
||
beforeEach(() => vi.clearAllMocks())
|
||
|
||
it('renders "Blocked" heading', () => {
|
||
render(<HQBlockedSection items={[]} />)
|
||
expect(screen.getByText('Blocked')).toBeInTheDocument()
|
||
})
|
||
|
||
it('shows initiative › phase with "Blocked" badge', () => {
|
||
render(
|
||
<HQBlockedSection
|
||
items={[
|
||
{
|
||
phaseId: 'ph-1',
|
||
phaseName: 'Phase One',
|
||
initiativeId: 'init-1',
|
||
initiativeName: 'Init One',
|
||
lastMessage: null,
|
||
since,
|
||
},
|
||
]}
|
||
/>
|
||
)
|
||
expect(screen.getByText('Init One › Phase One')).toBeInTheDocument()
|
||
// The "Blocked" badge - there will be one in the heading and one in the card
|
||
const badges = screen.getAllByText('Blocked')
|
||
expect(badges.length).toBeGreaterThanOrEqual(1)
|
||
})
|
||
|
||
it('shows lastMessage when non-null', () => {
|
||
render(
|
||
<HQBlockedSection
|
||
items={[
|
||
{
|
||
phaseId: 'ph-1',
|
||
phaseName: 'Phase One',
|
||
initiativeId: 'init-1',
|
||
initiativeName: 'Init One',
|
||
lastMessage: 'Something went wrong.',
|
||
since,
|
||
},
|
||
]}
|
||
/>
|
||
)
|
||
expect(screen.getByText('Something went wrong.')).toBeInTheDocument()
|
||
})
|
||
|
||
it('omits lastMessage when null', () => {
|
||
render(
|
||
<HQBlockedSection
|
||
items={[
|
||
{
|
||
phaseId: 'ph-1',
|
||
phaseName: 'Phase One',
|
||
initiativeId: 'init-1',
|
||
initiativeName: 'Init One',
|
||
lastMessage: null,
|
||
since,
|
||
},
|
||
]}
|
||
/>
|
||
)
|
||
expect(screen.queryByText('Something went wrong.')).not.toBeInTheDocument()
|
||
})
|
||
|
||
it('"View" CTA navigates to /initiatives/$id?tab=execution', () => {
|
||
render(
|
||
<HQBlockedSection
|
||
items={[
|
||
{
|
||
phaseId: 'ph-1',
|
||
phaseName: 'Phase One',
|
||
initiativeId: 'init-1',
|
||
initiativeName: 'Init One',
|
||
lastMessage: null,
|
||
since,
|
||
},
|
||
]}
|
||
/>
|
||
)
|
||
fireEvent.click(screen.getByRole('button', { name: /view/i }))
|
||
expect(mockNavigate).toHaveBeenCalledWith({
|
||
to: '/initiatives/$id',
|
||
params: { id: 'init-1' },
|
||
search: { tab: 'execution' },
|
||
})
|
||
})
|
||
})
|
||
|
||
// ─── HQEmptyState ────────────────────────────────────────────────────────────
|
||
|
||
describe('HQEmptyState', () => {
|
||
it('renders "All clear." text', () => {
|
||
render(<HQEmptyState />)
|
||
expect(screen.getByText('All clear.')).toBeInTheDocument()
|
||
})
|
||
|
||
it('renders "Browse active work" link pointing to /initiatives', () => {
|
||
render(<HQEmptyState />)
|
||
const link = screen.getByRole('link', { name: /browse active work/i })
|
||
expect(link).toBeInTheDocument()
|
||
expect(link).toHaveAttribute('href', '/initiatives')
|
||
})
|
||
})
|