/** * IssueDetailPanel Component Tests * * Comprehensive tests for the issue detail side panel component. */ import { render, screen } from '@testing-library/react'; import { IssueDetailPanel } from '@/features/issues/components/IssueDetailPanel'; import { mockIssueDetail } from '@/features/issues/mocks'; import type { IssueDetail } from '@/features/issues/types'; describe('IssueDetailPanel', () => { const defaultIssue = mockIssueDetail; it('renders the details card header', () => { render(); expect(screen.getByText('Details')).toBeInTheDocument(); }); describe('Assignee section', () => { it('displays assignee name when assigned', () => { render(); expect(screen.getByText('Assignee')).toBeInTheDocument(); expect(screen.getByText(defaultIssue.assignee!.name)).toBeInTheDocument(); }); it('displays assignee avatar text when provided', () => { const issueWithAvatar: IssueDetail = { ...defaultIssue, assignee: { ...defaultIssue.assignee!, avatar: 'BE', }, }; render(); expect(screen.getByText('BE')).toBeInTheDocument(); }); it('displays agent icon for agent assignee without avatar', () => { const issueWithAgentNoAvatar: IssueDetail = { ...defaultIssue, assignee: { id: 'agent-1', name: 'Test Agent', type: 'agent', // No avatar }, }; render(); // Bot icon should be rendered (with aria-hidden) const icons = document.querySelectorAll('[aria-hidden="true"]'); expect(icons.length).toBeGreaterThan(0); }); it('displays human icon for human assignee without avatar', () => { const issueWithHumanNoAvatar: IssueDetail = { ...defaultIssue, assignee: { id: 'human-1', name: 'Test Human', type: 'human', // No avatar }, }; render(); expect(screen.getByText('Test Human')).toBeInTheDocument(); }); it('displays assignee type', () => { render(); expect(screen.getByText(defaultIssue.assignee!.type)).toBeInTheDocument(); }); it('shows "Unassigned" when no assignee', () => { const unassignedIssue: IssueDetail = { ...defaultIssue, assignee: null, }; render(); expect(screen.getByText('Unassigned')).toBeInTheDocument(); }); }); describe('Reporter section', () => { it('displays reporter name', () => { render(); expect(screen.getByText('Reporter')).toBeInTheDocument(); expect(screen.getByText(defaultIssue.reporter.name)).toBeInTheDocument(); }); it('displays reporter avatar text when provided', () => { const issueWithReporterAvatar: IssueDetail = { ...defaultIssue, reporter: { ...defaultIssue.reporter, avatar: 'PO', }, }; render(); expect(screen.getByText('PO')).toBeInTheDocument(); }); it('displays agent icon for agent reporter without avatar', () => { const issueWithAgentReporter: IssueDetail = { ...defaultIssue, reporter: { id: 'agent-1', name: 'Agent Reporter', type: 'agent', }, }; render(); expect(screen.getByText('Agent Reporter')).toBeInTheDocument(); }); it('displays human icon for human reporter without avatar', () => { const issueWithHumanReporter: IssueDetail = { ...defaultIssue, reporter: { id: 'human-1', name: 'Human Reporter', type: 'human', }, }; render(); expect(screen.getByText('Human Reporter')).toBeInTheDocument(); }); }); describe('Sprint section', () => { it('displays sprint name when assigned', () => { render(); expect(screen.getByText('Sprint')).toBeInTheDocument(); expect(screen.getByText(defaultIssue.sprint!)).toBeInTheDocument(); }); it('shows "Backlog" when no sprint', () => { const backlogIssue: IssueDetail = { ...defaultIssue, sprint: null, }; render(); expect(screen.getByText('Backlog')).toBeInTheDocument(); }); }); describe('Story Points section', () => { it('displays story points when present', () => { render(); expect(screen.getByText('Story Points')).toBeInTheDocument(); expect(screen.getByText(String(defaultIssue.story_points))).toBeInTheDocument(); }); it('does not render story points section when null', () => { const issueNoPoints: IssueDetail = { ...defaultIssue, story_points: null, }; render(); expect(screen.queryByText('Story Points')).not.toBeInTheDocument(); }); }); describe('Due Date section', () => { it('displays due date when present', () => { render(); expect(screen.getByText('Due Date')).toBeInTheDocument(); // Date formatting varies by locale, check structure exists const dueDate = new Date(defaultIssue.due_date!).toLocaleDateString(); expect(screen.getByText(dueDate)).toBeInTheDocument(); }); it('does not render due date section when null', () => { const issueNoDueDate: IssueDetail = { ...defaultIssue, due_date: null, }; render(); expect(screen.queryByText('Due Date')).not.toBeInTheDocument(); }); }); describe('Labels section', () => { it('displays all labels', () => { render(); expect(screen.getByText('Labels')).toBeInTheDocument(); defaultIssue.labels.forEach((label) => { expect(screen.getByText(label.name)).toBeInTheDocument(); }); }); it('applies label colors when provided', () => { render(); const labelWithColor = defaultIssue.labels.find((l) => l.color); if (labelWithColor) { const labelElement = screen.getByText(labelWithColor.name); // The parent badge should have inline style expect(labelElement.closest('[class*="Badge"]') || labelElement.parentElement).toBeTruthy(); } }); it('shows "No labels" when labels array is empty', () => { const issueNoLabels: IssueDetail = { ...defaultIssue, labels: [], }; render(); expect(screen.getByText('No labels')).toBeInTheDocument(); }); it('renders labels without color property', () => { const issueWithColorlessLabels: IssueDetail = { ...defaultIssue, labels: [ { id: 'lbl-1', name: 'colorless-label' }, ], }; render(); expect(screen.getByText('colorless-label')).toBeInTheDocument(); }); }); describe('Development section', () => { it('renders development card when branch is present', () => { render(); expect(screen.getByText('Development')).toBeInTheDocument(); expect(screen.getByText(defaultIssue.branch!)).toBeInTheDocument(); }); it('renders development card when pull request is present', () => { render(); expect(screen.getByText(defaultIssue.pull_request!)).toBeInTheDocument(); expect(screen.getByText('Open')).toBeInTheDocument(); }); it('renders branch only when PR is null', () => { const issueNoPR: IssueDetail = { ...defaultIssue, pull_request: null, }; render(); expect(screen.getByText(defaultIssue.branch!)).toBeInTheDocument(); expect(screen.queryByText('Open')).not.toBeInTheDocument(); }); it('renders PR only when branch is null', () => { const issueNoBranch: IssueDetail = { ...defaultIssue, branch: null, }; render(); expect(screen.getByText(defaultIssue.pull_request!)).toBeInTheDocument(); expect(screen.getByText('Development')).toBeInTheDocument(); }); it('does not render development section when both branch and PR are null', () => { const issueNoDev: IssueDetail = { ...defaultIssue, branch: null, pull_request: null, }; render(); expect(screen.queryByText('Development')).not.toBeInTheDocument(); }); }); describe('Styling', () => { it('applies custom className', () => { const { container } = render( ); expect(container.firstChild).toHaveClass('custom-class'); }); it('has default spacing class', () => { const { container } = render(); expect(container.firstChild).toHaveClass('space-y-6'); }); }); });