690617:1443 237 #01.3
This commit is contained in:
@@ -0,0 +1,79 @@
|
||||
// File: frontend/components/numbering/__tests__/audit-logs-table.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { AuditLogsTable } from '../audit-logs-table';
|
||||
import { documentNumberingService } from '@/lib/services/document-numbering.service';
|
||||
|
||||
vi.mock('@/lib/services/document-numbering.service', () => ({
|
||||
documentNumberingService: {
|
||||
getMetrics: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('AuditLogsTable', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders loading state initially', () => {
|
||||
vi.mocked(documentNumberingService.getMetrics).mockImplementation(() => new Promise(() => {}));
|
||||
render(<AuditLogsTable />);
|
||||
expect(screen.getByText('Loading logs...')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('renders empty state when no logs returned', async () => {
|
||||
vi.mocked(documentNumberingService.getMetrics).mockResolvedValue({ audit: [] } as any);
|
||||
render(<AuditLogsTable />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('No logs found.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders error state silently as empty state when API fails', async () => {
|
||||
vi.mocked(documentNumberingService.getMetrics).mockRejectedValue(new Error('API failed'));
|
||||
render(<AuditLogsTable />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('No logs found.')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('renders logs correctly', async () => {
|
||||
const mockLogs = [
|
||||
{
|
||||
id: 1,
|
||||
createdAt: '2023-10-27T10:00:00Z',
|
||||
operation: 'GENERATE',
|
||||
documentNumber: 'DOC-001',
|
||||
createdBy: 'UserA',
|
||||
status: 'SUCCESS',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
createdAt: '2023-10-27T11:00:00Z',
|
||||
operation: 'VOID',
|
||||
documentNumber: 'DOC-002',
|
||||
createdBy: null,
|
||||
status: 'FAILED',
|
||||
},
|
||||
];
|
||||
vi.mocked(documentNumberingService.getMetrics).mockResolvedValue({ audit: mockLogs } as any);
|
||||
|
||||
render(<AuditLogsTable />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('DOC-001')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(screen.getByText('GENERATE')).toBeInTheDocument();
|
||||
expect(screen.getByText('UserA')).toBeInTheDocument();
|
||||
expect(screen.getByText('SUCCESS')).toBeInTheDocument();
|
||||
|
||||
expect(screen.getByText('DOC-002')).toBeInTheDocument();
|
||||
expect(screen.getByText('VOID')).toBeInTheDocument();
|
||||
expect(screen.getByText('System')).toBeInTheDocument(); // Falls back to System
|
||||
expect(screen.getByText('FAILED')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,83 @@
|
||||
// File: frontend/components/numbering/__tests__/bulk-import-form.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { BulkImportForm } from '../bulk-import-form';
|
||||
import { documentNumberingService } from '@/lib/services/document-numbering.service';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
vi.mock('@/lib/services/document-numbering.service', () => ({
|
||||
documentNumberingService: {
|
||||
bulkImport: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('BulkImportForm', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
render(<BulkImportForm projectId={1} />);
|
||||
expect(screen.getByText('Bulk Import Numbers')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('CSV File')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Upload & Import' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('enables submit button when file is selected and handles successful upload', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<BulkImportForm projectId={1} />);
|
||||
|
||||
const file = new File(['test'], 'test.csv', { type: 'text/csv' });
|
||||
const input = screen.getByLabelText('CSV File') as HTMLInputElement;
|
||||
|
||||
await user.upload(input, file);
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Upload & Import' });
|
||||
expect(button).not.toBeDisabled();
|
||||
|
||||
vi.mocked(documentNumberingService.bulkImport).mockResolvedValue({} as any);
|
||||
|
||||
await user.click(button);
|
||||
|
||||
expect(documentNumberingService.bulkImport).toHaveBeenCalledWith(expect.any(FormData));
|
||||
const formDataArg = vi.mocked(documentNumberingService.bulkImport).mock.calls[0][0] as FormData;
|
||||
expect(formDataArg.get('file')).toBe(file);
|
||||
expect(formDataArg.get('projectId')).toBe('1');
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.success).toHaveBeenCalledWith('Bulk import initiated. Check audit logs for progress.');
|
||||
});
|
||||
|
||||
// File input reset means button is disabled again
|
||||
expect(button).toBeDisabled();
|
||||
});
|
||||
|
||||
it('handles upload failure', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<BulkImportForm projectId={1} />);
|
||||
|
||||
const file = new File(['test'], 'test.csv', { type: 'text/csv' });
|
||||
const input = screen.getByLabelText('CSV File') as HTMLInputElement;
|
||||
|
||||
await user.upload(input, file);
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Upload & Import' });
|
||||
|
||||
vi.mocked(documentNumberingService.bulkImport).mockRejectedValue(new Error('Failed'));
|
||||
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalledWith('Failed to import numbers.');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,113 @@
|
||||
// File: frontend/components/numbering/__tests__/cancel-number-form.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { CancelNumberForm } from '../cancel-number-form';
|
||||
import { documentNumberingService } from '@/lib/services/document-numbering.service';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
vi.mock('@/lib/services/document-numbering.service', () => ({
|
||||
documentNumberingService: {
|
||||
cancelNumber: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('CancelNumberForm', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
render(<CancelNumberForm />);
|
||||
expect(screen.getByRole('heading', { name: 'Cancel Number' })).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Document Number')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Reason')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Cancel Number' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows validation error for empty fields', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<CancelNumberForm />);
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Cancel Number' });
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Number is required')).toBeInTheDocument();
|
||||
expect(screen.getByText('Reason must be at least 5 characters')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(documentNumberingService.cancelNumber).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows validation error for short reason', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<CancelNumberForm />);
|
||||
|
||||
await user.type(screen.getByLabelText('Document Number'), 'DOC-001');
|
||||
await user.type(screen.getByLabelText('Reason'), 'abc'); // too short
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Cancel Number' });
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Reason must be at least 5 characters')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(documentNumberingService.cancelNumber).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('handles successful cancellation', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<CancelNumberForm />);
|
||||
|
||||
await user.type(screen.getByLabelText('Document Number'), 'DOC-001');
|
||||
await user.type(screen.getByLabelText('Reason'), 'Generated by mistake');
|
||||
|
||||
vi.mocked(documentNumberingService.cancelNumber).mockResolvedValue({} as any);
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Cancel Number' });
|
||||
await user.click(button);
|
||||
|
||||
expect(documentNumberingService.cancelNumber).toHaveBeenCalledWith({
|
||||
documentNumber: 'DOC-001',
|
||||
reason: 'Generated by mistake',
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.success).toHaveBeenCalledWith('Number cancelled successfully.');
|
||||
});
|
||||
|
||||
// Check if form was reset
|
||||
expect(screen.getByLabelText('Document Number')).toHaveValue('');
|
||||
expect(screen.getByLabelText('Reason')).toHaveValue('');
|
||||
});
|
||||
|
||||
it('handles cancellation failure', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<CancelNumberForm />);
|
||||
|
||||
await user.type(screen.getByLabelText('Document Number'), 'DOC-001');
|
||||
await user.type(screen.getByLabelText('Reason'), 'Generated by mistake');
|
||||
|
||||
vi.mocked(documentNumberingService.cancelNumber).mockRejectedValue(new Error('Failed'));
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Cancel Number' });
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalledWith('Failed to cancel number. It may not exist or is already cancelled.');
|
||||
});
|
||||
|
||||
// Form is not reset on error
|
||||
expect(screen.getByLabelText('Document Number')).toHaveValue('DOC-001');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,158 @@
|
||||
// File: frontend/components/numbering/__tests__/template-editor.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor, fireEvent } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { TemplateEditor } from '../template-editor';
|
||||
import { CorrespondenceType, Discipline } from '@/types/master-data';
|
||||
|
||||
const mockTypes: CorrespondenceType[] = [
|
||||
{ publicId: 'type1', typeCode: 'RFA', typeName: 'Request for Approval', isActive: true } as any,
|
||||
{ publicId: 'type2', typeCode: 'TRN', typeName: 'Transmittal', isActive: true } as any,
|
||||
];
|
||||
|
||||
const mockDisciplines: Discipline[] = [
|
||||
{ publicId: 'disc1', disciplineCode: 'STR', codeNameEn: 'Structural', isActive: true } as any,
|
||||
];
|
||||
|
||||
describe('TemplateEditor', () => {
|
||||
const onSave = vi.fn();
|
||||
const onCancel = vi.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders correctly for new template', () => {
|
||||
render(
|
||||
<TemplateEditor
|
||||
projectId={1}
|
||||
projectName="Test Project"
|
||||
correspondenceTypes={mockTypes}
|
||||
disciplines={mockDisciplines}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('New Template')).toBeInTheDocument();
|
||||
expect(screen.getByText('Project: Test Project')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Template Format *')).toHaveValue('');
|
||||
expect(screen.getByRole('button', { name: 'Save Template' })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('renders correctly with existing template data', () => {
|
||||
render(
|
||||
<TemplateEditor
|
||||
template={{
|
||||
formatTemplate: '{ORG}-{TYPE}-{SEQ:4}',
|
||||
correspondenceTypeId: 'type1' as any,
|
||||
disciplineId: 'disc1' as any,
|
||||
resetSequenceYearly: false,
|
||||
} as any}
|
||||
projectId={1}
|
||||
projectName="Test Project"
|
||||
correspondenceTypes={mockTypes}
|
||||
disciplines={mockDisciplines}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(screen.getByText('Edit Template')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Template Format *')).toHaveValue('{ORG}-{TYPE}-{SEQ:4}');
|
||||
expect(screen.getByRole('button', { name: 'Save Template' })).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('allows inserting variables into format', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<TemplateEditor
|
||||
projectId={1}
|
||||
projectName="Test Project"
|
||||
correspondenceTypes={mockTypes}
|
||||
disciplines={mockDisciplines}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
|
||||
const formatInput = screen.getByLabelText('Template Format *');
|
||||
await user.type(formatInput, 'TEST-');
|
||||
|
||||
// Click a variable button
|
||||
const orgButton = screen.getByRole('button', { name: '{ORG}' });
|
||||
await user.click(orgButton);
|
||||
|
||||
expect(formatInput).toHaveValue('TEST-{ORG}');
|
||||
});
|
||||
|
||||
it('updates preview when format changes', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<TemplateEditor
|
||||
projectId={1}
|
||||
projectName="Test Project"
|
||||
correspondenceTypes={mockTypes}
|
||||
disciplines={mockDisciplines}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
|
||||
const formatInput = screen.getByLabelText('Template Format *');
|
||||
fireEvent.change(formatInput, { target: { value: '{PROJECT}-{SEQ:4}' } });
|
||||
|
||||
expect(screen.getByText('LCBP3-0001')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('calls onCancel when cancel button clicked', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<TemplateEditor
|
||||
projectId={1}
|
||||
projectName="Test Project"
|
||||
correspondenceTypes={mockTypes}
|
||||
disciplines={mockDisciplines}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
|
||||
const cancelButton = screen.getByRole('button', { name: 'Cancel' });
|
||||
await user.click(cancelButton);
|
||||
|
||||
expect(onCancel).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('calls onSave with form data', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(
|
||||
<TemplateEditor
|
||||
projectId={1}
|
||||
projectName="Test Project"
|
||||
correspondenceTypes={mockTypes}
|
||||
disciplines={mockDisciplines}
|
||||
onSave={onSave}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
);
|
||||
|
||||
const formatInput = screen.getByLabelText('Template Format *');
|
||||
fireEvent.change(formatInput, { target: { value: '{PROJECT}-{SEQ:4}' } });
|
||||
|
||||
// We cannot easily test Radix Select interactions in jsdom without massive pointer mocking,
|
||||
// so we'll test the default values submission first.
|
||||
|
||||
const saveButton = screen.getByRole('button', { name: 'Save Template' });
|
||||
await user.click(saveButton);
|
||||
|
||||
expect(onSave).toHaveBeenCalledWith(expect.objectContaining({
|
||||
projectId: 1,
|
||||
formatTemplate: '{PROJECT}-{SEQ:4}',
|
||||
resetSequenceYearly: true,
|
||||
correspondenceTypeId: null,
|
||||
disciplineId: 0,
|
||||
}));
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,87 @@
|
||||
// File: frontend/components/numbering/__tests__/template-tester.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { TemplateTester } from '../template-tester';
|
||||
import { numberingApi } from '@/lib/api/numbering';
|
||||
|
||||
vi.mock('@/lib/api/numbering', () => ({
|
||||
numberingApi: {
|
||||
previewNumber: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/hooks/use-master-data', () => ({
|
||||
useOrganizations: vi.fn(() => ({ data: [{ publicId: 'org1', organizationCode: 'ORG', organizationName: 'Org1' }] })),
|
||||
useCorrespondenceTypes: vi.fn(() => ({ data: [{ id: 1, typeCode: 'TYPE', typeName: 'Type1' }] })),
|
||||
useContracts: vi.fn(() => ({ data: [{ id: 1 }] })),
|
||||
useDisciplines: vi.fn(() => ({ data: [{ id: 1, disciplineCode: 'DISC' }] })),
|
||||
}));
|
||||
|
||||
describe('TemplateTester', () => {
|
||||
const onOpenChange = vi.fn();
|
||||
const mockTemplate = {
|
||||
projectId: 1,
|
||||
formatTemplate: '{ORG}-{TYPE}-{SEQ:4}',
|
||||
} as any;
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders correctly when open', () => {
|
||||
render(<TemplateTester open={true} onOpenChange={onOpenChange} template={mockTemplate} />);
|
||||
|
||||
expect(screen.getByText('Test Number Generation')).toBeInTheDocument();
|
||||
expect(screen.getByText('{ORG}-{TYPE}-{SEQ:4}')).toBeInTheDocument();
|
||||
expect(screen.getByRole('button', { name: 'Generate Test Number' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('does not render when closed', () => {
|
||||
render(<TemplateTester open={false} onOpenChange={onOpenChange} template={mockTemplate} />);
|
||||
expect(screen.queryByText('Test Number Generation')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('handles successful generation', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TemplateTester open={true} onOpenChange={onOpenChange} template={mockTemplate} />);
|
||||
|
||||
vi.mocked(numberingApi.previewNumber).mockResolvedValue({
|
||||
previewNumber: 'ORG-TYPE-0001',
|
||||
isDefault: true,
|
||||
} as any);
|
||||
|
||||
const generateBtn = screen.getByRole('button', { name: 'Generate Test Number' });
|
||||
await user.click(generateBtn);
|
||||
|
||||
expect(numberingApi.previewNumber).toHaveBeenCalledWith({
|
||||
projectId: 1,
|
||||
originatorOrganizationId: '0',
|
||||
recipientOrganizationId: '0',
|
||||
correspondenceTypeId: 0,
|
||||
disciplineId: 0,
|
||||
year: new Date().getFullYear(),
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('ORG-TYPE-0001')).toBeInTheDocument();
|
||||
expect(screen.getByText('Default Template')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it('handles API error', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<TemplateTester open={true} onOpenChange={onOpenChange} template={mockTemplate} />);
|
||||
|
||||
vi.mocked(numberingApi.previewNumber).mockRejectedValue(new Error('Generation failed'));
|
||||
|
||||
const generateBtn = screen.getByRole('button', { name: 'Generate Test Number' });
|
||||
await user.click(generateBtn);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Error: Generation failed')).toBeInTheDocument();
|
||||
expect(screen.getByText('Generation Failed:')).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,136 @@
|
||||
// File: frontend/components/numbering/__tests__/void-replace-form.test.tsx
|
||||
import React from 'react';
|
||||
import { render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
||||
import { VoidReplaceForm } from '../void-replace-form';
|
||||
import { documentNumberingService } from '@/lib/services/document-numbering.service';
|
||||
import { toast } from 'sonner';
|
||||
|
||||
vi.mock('@/lib/services/document-numbering.service', () => ({
|
||||
documentNumberingService: {
|
||||
voidAndReplace: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('sonner', () => ({
|
||||
toast: {
|
||||
success: vi.fn(),
|
||||
error: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
describe('VoidReplaceForm', () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
it('renders correctly', () => {
|
||||
render(<VoidReplaceForm projectId={1} />);
|
||||
expect(screen.getByText('Void & Replace Number')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Document Number')).toBeInTheDocument();
|
||||
expect(screen.getByLabelText('Reason')).toBeInTheDocument();
|
||||
expect(screen.getByRole('checkbox', { name: 'Generate Replacement?' })).not.toBeChecked();
|
||||
expect(screen.getByRole('button', { name: 'Void Number' })).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('shows validation errors for empty fields', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<VoidReplaceForm projectId={1} />);
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Void Number' });
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Document Number is required')).toBeInTheDocument();
|
||||
expect(screen.getByText('Reason must be at least 5 characters')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(documentNumberingService.voidAndReplace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('shows validation error for short reason', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<VoidReplaceForm projectId={1} />);
|
||||
|
||||
await user.type(screen.getByLabelText('Document Number'), 'DOC-001');
|
||||
await user.type(screen.getByLabelText('Reason'), '123'); // Too short
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Void Number' });
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText('Reason must be at least 5 characters')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
expect(documentNumberingService.voidAndReplace).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('handles successful voiding without replacement', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<VoidReplaceForm projectId={1} />);
|
||||
|
||||
await user.type(screen.getByLabelText('Document Number'), 'DOC-001');
|
||||
await user.type(screen.getByLabelText('Reason'), 'Voided because of typo');
|
||||
|
||||
vi.mocked(documentNumberingService.voidAndReplace).mockResolvedValue({} as any);
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Void Number' });
|
||||
await user.click(button);
|
||||
|
||||
expect(documentNumberingService.voidAndReplace).toHaveBeenCalledWith({
|
||||
documentNumber: 'DOC-001',
|
||||
reason: 'Voided because of typo',
|
||||
replace: false,
|
||||
projectId: 1,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.success).toHaveBeenCalledWith('Number voided successfully. ');
|
||||
});
|
||||
});
|
||||
|
||||
it('handles successful voiding with replacement', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<VoidReplaceForm projectId={1} />);
|
||||
|
||||
await user.type(screen.getByLabelText('Document Number'), 'DOC-002');
|
||||
await user.type(screen.getByLabelText('Reason'), 'Voided because of typo');
|
||||
|
||||
const checkbox = screen.getByRole('checkbox', { name: 'Generate Replacement?' });
|
||||
await user.click(checkbox);
|
||||
|
||||
vi.mocked(documentNumberingService.voidAndReplace).mockResolvedValue({} as any);
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Void Number' });
|
||||
await user.click(button);
|
||||
|
||||
expect(documentNumberingService.voidAndReplace).toHaveBeenCalledWith({
|
||||
documentNumber: 'DOC-002',
|
||||
reason: 'Voided because of typo',
|
||||
replace: true,
|
||||
projectId: 1,
|
||||
});
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.success).toHaveBeenCalledWith('Number voided successfully. Replacement generated.');
|
||||
});
|
||||
});
|
||||
|
||||
it('handles API error', async () => {
|
||||
const user = userEvent.setup();
|
||||
render(<VoidReplaceForm projectId={1} />);
|
||||
|
||||
await user.type(screen.getByLabelText('Document Number'), 'DOC-001');
|
||||
await user.type(screen.getByLabelText('Reason'), 'Voided because of typo');
|
||||
|
||||
vi.mocked(documentNumberingService.voidAndReplace).mockRejectedValue(new Error('Failed'));
|
||||
|
||||
const button = screen.getByRole('button', { name: 'Void Number' });
|
||||
await user.click(button);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(toast.error).toHaveBeenCalledWith('Failed to void number. Check if it exists.');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -164,8 +164,9 @@ export function TemplateEditor({
|
||||
{/* Format Column */}
|
||||
<div className="space-y-4">
|
||||
<div>
|
||||
<Label>Template Format *</Label>
|
||||
<Label htmlFor="template-format">Template Format *</Label>
|
||||
<Input
|
||||
id="template-format"
|
||||
value={format}
|
||||
onChange={(e) => setFormat(e.target.value)}
|
||||
placeholder="{ORG}-{TYPE}-{SEQ:4}"
|
||||
|
||||
Reference in New Issue
Block a user