Files
lcbp3/frontend/components/rfas/__tests__/detail.test.tsx
T

351 lines
9.4 KiB
TypeScript

// File: frontend/components/rfas/__tests__/detail.test.tsx
// Change Log:
// - 2026-06-13: Initial creation - test coverage for RFADetail component
import { describe, it, expect, vi, beforeEach } from 'vitest';
import { render, screen, fireEvent } from '@testing-library/react';
import { RFADetail } from '../detail';
import { RFA } from '@/types/rfa';
// Mock dependencies
vi.mock('@/hooks/use-rfa', () => ({
useProcessRFA: () => ({
mutate: vi.fn(),
isPending: false,
}),
useSubmitRFA: () => ({
mutate: vi.fn(),
isPending: false,
}),
}));
vi.mock('@/components/review-team/ReviewTeamSelector', () => ({
ReviewTeamSelector: () => <div data-testid="review-team-selector">Review Team Selector</div>,
}));
describe('RFADetail', () => {
const mockRFA: RFA = {
publicId: '019505a1-7c3e-7000-8000-abc123def456',
correspondence: {
publicId: '019505a1-7c3e-7000-8000-abc123def457',
correspondenceNumber: 'RFA-001',
project: {
publicId: '019505a1-7c3e-7000-8000-abc123def458',
projectName: 'Test Project',
},
discipline: {
publicId: '019505a1-7c3e-7000-8000-abc123def459',
codeNameEn: 'Structural',
codeNameTh: 'โครงสร้าง',
disciplineCode: 'STR',
},
createdAt: '2026-01-01T00:00:00Z',
},
discipline: {
publicId: '019505a1-7c3e-7000-8000-abc123def460',
name: 'Structural',
},
revisions: [
{
publicId: '019505a1-7c3e-7000-8000-abc123def461',
isCurrent: true,
subject: 'Test Subject',
description: 'Test Description',
statusCode: {
publicId: '019505a1-7c3e-7000-8000-abc123def462',
statusName: 'Draft',
statusCode: 'DFT',
},
createdAt: '2026-01-01T00:00:00Z',
items: [
{
id: 1,
itemType: 'SHOP_DRAWING',
shopDrawingRevision: {
shopDrawing: {
drawingNumber: 'SD-001',
},
revisionLabel: 'A',
title: 'Test Drawing',
},
},
],
},
],
};
beforeEach(() => {
vi.clearAllMocks();
});
it('should render RFA detail with data', () => {
render(<RFADetail data={mockRFA} />);
expect(screen.getByText('RFA-001')).toBeInTheDocument();
expect(screen.getByText('Test Subject')).toBeInTheDocument();
expect(screen.getByText('Test Description')).toBeInTheDocument();
expect(screen.getByText('Test Project')).toBeInTheDocument();
expect(screen.getByText('Structural')).toBeInTheDocument();
});
it('should display created date', () => {
render(<RFADetail data={mockRFA} />);
expect(screen.getByText(/Created on/)).toBeInTheDocument();
});
it('should display status badge', () => {
render(<RFADetail data={mockRFA} />);
expect(screen.getByText('Draft')).toBeInTheDocument();
});
it('should show Edit and Submit buttons for DFT status', () => {
render(<RFADetail data={mockRFA} />);
expect(screen.getByText('Edit')).toBeInTheDocument();
expect(screen.getByText('Submit RFA')).toBeInTheDocument();
});
it('should show Approve and Reject buttons for FAP status', () => {
const fapRFA: RFA = {
...mockRFA,
revisions: [
{
...mockRFA.revisions[0],
statusCode: {
...mockRFA.revisions[0].statusCode,
statusName: 'For Approval',
statusCode: 'FAP',
},
},
],
};
render(<RFADetail data={fapRFA} />);
expect(screen.getByText('Reject')).toBeInTheDocument();
expect(screen.getByText('Approve')).toBeInTheDocument();
});
it('should show Approve and Reject buttons for FRE status', () => {
const freRFA: RFA = {
...mockRFA,
revisions: [
{
...mockRFA.revisions[0],
statusCode: {
...mockRFA.revisions[0].statusCode,
statusName: 'For Review',
statusCode: 'FRE',
},
},
],
};
render(<RFADetail data={freRFA} />);
expect(screen.getByText('Reject')).toBeInTheDocument();
expect(screen.getByText('Approve')).toBeInTheDocument();
});
it('should render RFA items table', () => {
render(<RFADetail data={mockRFA} />);
expect(screen.getByText('Type')).toBeInTheDocument();
expect(screen.getByText('Drawing No.')).toBeInTheDocument();
expect(screen.getByText('Revision')).toBeInTheDocument();
expect(screen.getByText('Title')).toBeInTheDocument();
expect(screen.getByText('SHOP_DRAWING')).toBeInTheDocument();
expect(screen.getByText('SD-001')).toBeInTheDocument();
expect(screen.getByText('A')).toBeInTheDocument();
expect(screen.getByText('Test Drawing')).toBeInTheDocument();
});
it('should show empty state when no items', () => {
const rfaWithoutItems: RFA = {
...mockRFA,
revisions: [
{
...mockRFA.revisions[0],
items: [],
},
],
};
render(<RFADetail data={rfaWithoutItems} />);
expect(screen.getByText('No drawing items linked to this RFA.')).toBeInTheDocument();
});
it('should handle missing project name', () => {
const rfaWithoutProject: RFA = {
...mockRFA,
correspondence: {
...mockRFA.correspondence,
project: undefined,
},
};
render(<RFADetail data={rfaWithoutProject} />);
expect(screen.getByText('-')).toBeInTheDocument();
});
it('should handle missing discipline', () => {
const rfaWithoutDiscipline: RFA = {
...mockRFA,
correspondence: {
...mockRFA.correspondence,
discipline: undefined,
},
};
render(<RFADetail data={rfaWithoutDiscipline} />);
expect(screen.getByText('-')).toBeInTheDocument();
});
it('should handle missing subject', () => {
const rfaWithoutSubject: RFA = {
...mockRFA,
revisions: [
{
...mockRFA.revisions[0],
subject: undefined,
},
],
};
render(<RFADetail data={rfaWithoutSubject} />);
expect(screen.getByText('Untitled RFA')).toBeInTheDocument();
});
it('should handle missing description', () => {
const rfaWithoutDescription: RFA = {
...mockRFA,
revisions: [
{
...mockRFA.revisions[0],
description: undefined,
},
],
};
render(<RFADetail data={rfaWithoutDescription} />);
expect(screen.getByText('No description provided.')).toBeInTheDocument();
});
it('should open submit dialog when Submit RFA clicked', () => {
render(<RFADetail data={mockRFA} />);
const submitButton = screen.getByText('Submit RFA');
fireEvent.click(submitButton);
expect(screen.getByText('Submit RFA to Workflow')).toBeInTheDocument();
expect(screen.getByText('Routing Template ID')).toBeInTheDocument();
});
it('should show review team selector when project has publicId', () => {
render(<RFADetail data={mockRFA} />);
const submitButton = screen.getByText('Submit RFA');
fireEvent.click(submitButton);
expect(screen.getByTestId('review-team-selector')).toBeInTheDocument();
});
it('should close submit dialog when Cancel clicked', () => {
render(<RFADetail data={mockRFA} />);
const submitButton = screen.getByText('Submit RFA');
fireEvent.click(submitButton);
const cancelButton = screen.getByText('Cancel');
fireEvent.click(cancelButton);
expect(screen.queryByText('Submit RFA to Workflow')).not.toBeInTheDocument();
});
it('should open approve dialog when Approve clicked', () => {
const fapRFA: RFA = {
...mockRFA,
revisions: [
{
...mockRFA.revisions[0],
statusCode: {
...mockRFA.revisions[0].statusCode,
statusCode: 'FAP',
},
},
],
};
render(<RFADetail data={fapRFA} />);
const approveButton = screen.getByText('Approve');
fireEvent.click(approveButton);
expect(screen.getByText('Confirm Approval')).toBeInTheDocument();
expect(screen.getByText('Comments')).toBeInTheDocument();
});
it('should open reject dialog when Reject clicked', () => {
const fapRFA: RFA = {
...mockRFA,
revisions: [
{
...mockRFA.revisions[0],
statusCode: {
...mockRFA.revisions[0].statusCode,
statusCode: 'FAP',
},
},
],
};
render(<RFADetail data={fapRFA} />);
const rejectButton = screen.getByText('Reject');
fireEvent.click(rejectButton);
expect(screen.getByText('Confirm Rejection')).toBeInTheDocument();
expect(screen.getByText('Comments')).toBeInTheDocument();
});
it('should handle missing correspondence number', () => {
const rfaWithoutNumber: RFA = {
...mockRFA,
correspondence: {
...mockRFA.correspondence,
correspondenceNumber: undefined,
},
};
render(<RFADetail data={rfaWithoutNumber} />);
expect(screen.getByText('RFA')).toBeInTheDocument();
});
it('should use fallback discipline codes', () => {
const rfaWithDisciplineCodes: RFA = {
...mockRFA,
correspondence: {
...mockRFA.correspondence,
discipline: {
publicId: '019505a1-7c3e-7000-8000-abc123def460',
codeNameEn: undefined,
codeNameTh: undefined,
disciplineCode: 'STR',
},
},
};
render(<RFADetail data={rfaWithDisciplineCodes} />);
expect(screen.getByText('STR')).toBeInTheDocument();
});
});