Untitled
unknown
plain_text
9 months ago
24 kB
13
Indexable
import React from 'react';
import userEvent from '@testing-library/user-event';
import ClosedWorkOrders from '../../components/ClosedWorkOrders';
import { render, screen, createTestStore, within } from '../test-utils';
// Mock Apollo useQuery
const mockUseQuery = jest.fn();
jest.mock('@apollo/client', () => ({
...jest.requireActual('@apollo/client'),
useQuery: () => mockUseQuery(),
gql: jest.requireActual('@apollo/client').gql,
}));
// Mock MUI X DataGrid
jest.mock('@mui/x-data-grid', () => ({
...jest.requireActual('@mui/x-data-grid'),
DataGrid: ({ rows, columns, ..._props }: any) => (
<div data-testid="data-grid" role="grid">
<div data-testid="grid-rows-count">{rows?.length || 0} rows</div>
<div data-testid="grid-columns-count">{columns?.length || 0} columns</div>
{rows?.map((row: any, index: number) => (
<div key={index} data-testid={`grid-row-${row.id || index}`}>
{row.workOrderNo} - {row.assignedTo}
</div>
))}
</div>
),
useGridApiRef: () => ({
current: {
setColumns: jest.fn(),
getColumns: jest.fn(() => []),
updateColumns: jest.fn(),
},
}),
}));
// Mock WorkOrderColumns if it exists
jest.mock('../../components/WorkOrderColumns', () => ({
getWorkOrderColumns: jest.fn(() => [
{ field: 'workOrderNo', headerName: 'Work Order', width: 150 },
{ field: 'assignedTo', headerName: 'Assigned', width: 120 },
{ field: 'shift', headerName: 'Shift', width: 100 },
{ field: 'notes', headerName: 'Notes', width: 200 },
]),
applyConfigurations: jest.fn().mockResolvedValue([
{ field: 'workOrderNo', headerName: 'Work Order', width: 150 },
{ field: 'assignedTo', headerName: 'Assigned', width: 120 },
{ field: 'shift', headerName: 'Shift', width: 100 },
{ field: 'notes', headerName: 'Notes', width: 200 },
]),
}));
// Mock the custom hooks
const mockUseDragAndDrop = jest.fn();
const mockUseDropZone = jest.fn();
jest.mock('../../hooks/useDragAndDrop', () => ({
useDragAndDrop: () => mockUseDragAndDrop(),
}));
jest.mock('../../hooks/useDropZone', () => ({
useDropZone: () => mockUseDropZone(),
}));
// Mock ConfirmDialog
jest.mock('../../components/ConfirmDialog', () => {
return function MockConfirmDialog({ open, title, message, confirmText, cancelText, onConfirm, onCancel }: any) {
if (!open) return null;
return (
<div data-testid="confirm-dialog">
<h2>{title}</h2>
<div>{message}</div>
<button onClick={onConfirm}>{confirmText}</button>
<button onClick={onCancel}>{cancelText}</button>
</div>
);
};
});
// Mock DraggableTableRow
jest.mock('../../components/DraggableTableRow', () => {
return function MockDraggableTableRow({
order,
handleCommitChange,
editingId,
editValue,
editingShiftId,
shiftValue,
editingNotesId,
notesValue,
setEditingId,
setEditValue,
setEditingShiftId,
setShiftValue,
setEditingNotesId,
setNotesValue,
handleSave,
handleSaveShift,
handleSaveNotes,
handleDeleteRow
}: any) {
return (
<tr data-testid={`work-order-row-${order.workOrderNo}`}>
<td>
<input
type="checkbox"
checked={order.committed}
onChange={(e) => handleCommitChange(order, e.target.checked)}
data-testid={`commit-checkbox-${order.workOrderNo}`}
/>
</td>
<td data-testid={`work-order-no-${order.workOrderNo}`}>{order.workOrderNo}</td>
<td>{order.ncOpenCount}</td>
<td>{order.defaultWoDescription}</td>
<td>
{editingId === order.workOrderNo ? (
<input
value={editValue}
onChange={(e) => setEditValue(e.target.value)}
onBlur={() => handleSave(order.workOrderNo, editValue)}
data-testid={`assigned-input-${order.workOrderNo}`}
/>
) : (
<span
onClick={() => {
setEditingId(order.workOrderNo);
setEditValue(order.assignedTo);
}}
data-testid={`assigned-text-${order.workOrderNo}`}
>
{order.assignedTo}
</span>
)}
</td>
<td>
{editingShiftId === order.workOrderNo ? (
<input
value={shiftValue}
onChange={(e) => setShiftValue(e.target.value)}
onBlur={() => handleSaveShift(order.workOrderNo, shiftValue)}
data-testid={`shift-input-${order.workOrderNo}`}
/>
) : (
<span
onClick={() => {
setEditingShiftId(order.workOrderNo);
setShiftValue(order.shift);
}}
data-testid={`shift-text-${order.workOrderNo}`}
>
{order.shift}
</span>
)}
</td>
<td>
{editingNotesId === order.workOrderNo ? (
<input
value={notesValue}
onChange={(e) => setNotesValue(e.target.value)}
onBlur={() => handleSaveNotes(order.workOrderNo, notesValue)}
data-testid={`notes-input-${order.workOrderNo}`}
/>
) : (
<span
onClick={() => {
setEditingNotesId(order.workOrderNo);
setNotesValue(order.notes);
}}
data-testid={`notes-text-${order.workOrderNo}`}
>
{order.notes}
</span>
)}
</td>
<td>
<button
onClick={() => handleDeleteRow(order.workOrderNo)}
data-testid={`delete-button-${order.workOrderNo}`}
>
Delete
</button>
</td>
</tr>
);
};
});
describe('ClosedWorkOrders', () => {
const mockWorkOrders = [
{
workOrderNo: 'WO001',
tailNumber: 'TAIL001',
committed: false,
committedOn: null,
assignedTo: 'John Doe',
shift: 'Day',
notes: 'Test notes',
ncOpenCount: 2,
defaultWoDescription: 'Test Description',
workType: 'REP',
category: 'electrical',
planDate: '2024-01-15',
budgetHours: 8,
createdOn: '2024-01-15T08:00:00Z',
estimatedCompletedOn: null,
},
{
workOrderNo: 'WO002',
tailNumber: 'TAIL001',
committed: true,
committedOn: '2024-01-15T10:00:00Z',
assignedTo: 'Jane Smith',
shift: 'Night',
notes: 'Urgent repair',
ncOpenCount: 1,
defaultWoDescription: 'Critical Fix',
workType: 'DIS',
category: 'mechanical',
planDate: '2024-01-15',
budgetHours: 4,
createdOn: '2024-01-15T09:00:00Z',
estimatedCompletedOn: '2024-01-15T14:00:00Z',
},
];
const mockDropZoneHandlers = {
onDragOver: jest.fn(),
onDragEnter: jest.fn(),
onDragLeave: jest.fn(),
onDrop: jest.fn(),
};
const mockProps = {
tailNumber: 'TEST-001',
closedWorkOrderRow: jest.fn(),
saveConfigurations: jest.fn(),
applyConfigurations: jest.fn().mockResolvedValue([
{ field: 'workOrderNo', headerName: 'Work Order', width: 150 },
{ field: 'assignedTo', headerName: 'Assigned', width: 120 },
{ field: 'shift', headerName: 'Shift', width: 100 },
{ field: 'notes', headerName: 'Notes', width: 200 },
]),
};
const renderClosedWorkOrders = (props = {}, initialState: any = {}) => {
const store = createTestStore({
whiteBoard: {
electrical: [],
structures: [],
mechanical: [],
closedWorkOrders: mockWorkOrders,
locked: false,
planDate: '2024-01-15',
...initialState.whiteBoard,
},
});
const ClosedWorkOrdersComponent = ClosedWorkOrders as any;
return render(<ClosedWorkOrdersComponent {...mockProps} {...props} />, { store });
};
beforeEach(() => {
jest.clearAllMocks();
mockUseQuery.mockReturnValue({
data: null,
loading: false,
error: null,
});
mockUseDropZone.mockReturnValue({
isDraggingOver: false,
dropZoneHandlers: mockDropZoneHandlers,
dropZoneRef: { current: null },
});
mockUseDragAndDrop.mockReturnValue({
dragHandlers: {
onDragStart: jest.fn(),
onDragEnd: jest.fn(),
},
isDragging: false,
});
});
describe('Basic Rendering', () => {
// Tests that the component renders with proper structure and title
it('renders ClosedWorkOrders with correct title and structure', () => {
renderClosedWorkOrders();
expect(screen.getByText('Closed Work Orders')).toBeInTheDocument();
expect(screen.getByRole('table')).toBeInTheDocument();
// Check table headers
expect(screen.getByText('Committed')).toBeInTheDocument();
expect(screen.getByText('Work Order')).toBeInTheDocument();
expect(screen.getByText('MC Open')).toBeInTheDocument();
expect(screen.getByText('EDUC')).toBeInTheDocument();
expect(screen.getByText('Assigned')).toBeInTheDocument();
expect(screen.getByText('Shift')).toBeInTheDocument();
expect(screen.getByText('Notes')).toBeInTheDocument();
expect(screen.getByText('X')).toBeInTheDocument();
});
// Tests that work orders are displayed correctly
it('displays work orders from Redux state', () => {
renderClosedWorkOrders();
expect(screen.getByTestId('work-order-row-WO001')).toBeInTheDocument();
expect(screen.getByTestId('work-order-row-WO002')).toBeInTheDocument();
expect(screen.getByTestId('work-order-no-WO001')).toHaveTextContent('WO001');
expect(screen.getByTestId('work-order-no-WO002')).toHaveTextContent('WO002');
});
// Tests rendering with empty work orders list
it('renders empty table when no work orders exist', () => {
renderClosedWorkOrders({}, {
whiteBoard: {
closedWorkOrders: [],
locked: false,
},
});
expect(screen.getByText('Closed Work Orders')).toBeInTheDocument();
expect(screen.getByRole('table')).toBeInTheDocument();
expect(screen.queryByTestId('work-order-row-WO001')).not.toBeInTheDocument();
});
// Tests that clear table button is present
it('renders clear table button with correct icon', () => {
renderClosedWorkOrders();
const clearButton = screen.getByRole('button', { name: /clear table/i });
expect(clearButton).toBeInTheDocument();
expect(clearButton).not.toBeDisabled();
});
});
describe('Redux State Management', () => {
// Tests that committed checkbox updates Redux state
it('updates work order committed status via Redux', async () => {
const user = userEvent.setup();
renderClosedWorkOrders();
const checkbox = screen.getByTestId('commit-checkbox-WO001');
expect(checkbox).not.toBeChecked();
await user.click(checkbox);
// The mock will handle the change, we just verify the interaction
expect(checkbox).toBeInTheDocument();
});
// Tests that work order data is properly passed to child components
it('passes correct work order data to DraggableTableRow components', () => {
renderClosedWorkOrders();
// Verify work order data is displayed
expect(screen.getByTestId('assigned-text-WO001')).toHaveTextContent('John Doe');
expect(screen.getByTestId('assigned-text-WO002')).toHaveTextContent('Jane Smith');
expect(screen.getByTestId('shift-text-WO001')).toHaveTextContent('Day');
expect(screen.getByTestId('shift-text-WO002')).toHaveTextContent('Night');
expect(screen.getByTestId('notes-text-WO001')).toHaveTextContent('Test notes');
expect(screen.getByTestId('notes-text-WO002')).toHaveTextContent('Urgent repair');
});
// Tests behavior when whiteboard is locked
it('disables clear table button when whiteboard is locked', () => {
renderClosedWorkOrders({}, {
whiteBoard: {
closedWorkOrders: mockWorkOrders,
locked: true,
planDate: '2024-01-10', // Past date to ensure locked state
},
});
const clearButton = screen.getByRole('button', { name: /clear table/i });
expect(clearButton).toBeDisabled();
});
});
describe('Inline Editing Functionality', () => {
// Tests editing assigned field
it('allows editing assigned field inline', async () => {
const user = userEvent.setup();
renderClosedWorkOrders();
const assignedText = screen.getByTestId('assigned-text-WO001');
await user.click(assignedText);
const assignedInput = screen.getByTestId('assigned-input-WO001');
expect(assignedInput).toBeInTheDocument();
expect(assignedInput).toHaveValue('John Doe');
await user.clear(assignedInput);
await user.type(assignedInput, 'Updated Name');
await user.tab(); // Trigger blur event
// The mock handles the save, we verify the interaction occurred
expect(assignedInput).toHaveValue('Updated Name');
});
// Tests editing shift field
it('allows editing shift field inline', async () => {
const user = userEvent.setup();
renderClosedWorkOrders();
const shiftText = screen.getByTestId('shift-text-WO001');
await user.click(shiftText);
const shiftInput = screen.getByTestId('shift-input-WO001');
expect(shiftInput).toBeInTheDocument();
expect(shiftInput).toHaveValue('Day');
await user.clear(shiftInput);
await user.type(shiftInput, 'Night');
await user.tab(); // Trigger blur event
expect(shiftInput).toHaveValue('Night');
});
// Tests editing notes field
it('allows editing notes field inline', async () => {
const user = userEvent.setup();
renderClosedWorkOrders();
const notesText = screen.getByTestId('notes-text-WO001');
await user.click(notesText);
const notesInput = screen.getByTestId('notes-input-WO001');
expect(notesInput).toBeInTheDocument();
expect(notesInput).toHaveValue('Test notes');
await user.clear(notesInput);
await user.type(notesInput, 'Updated notes');
await user.tab(); // Trigger blur event
expect(notesInput).toHaveValue('Updated notes');
});
// Tests that only one field can be edited at a time
it('allows editing only one field at a time', async () => {
const user = userEvent.setup();
renderClosedWorkOrders();
// Start editing assigned field
const assignedText = screen.getByTestId('assigned-text-WO001');
await user.click(assignedText);
expect(screen.getByTestId('assigned-input-WO001')).toBeInTheDocument();
// Try to edit shift field - should not show input since assigned is being edited
const shiftText = screen.getByTestId('shift-text-WO001');
await user.click(shiftText);
// Both inputs should not be present simultaneously in a real scenario
// This test verifies the component structure
expect(screen.getByTestId('assigned-input-WO001')).toBeInTheDocument();
});
});
describe('Drag and Drop Functionality', () => {
// Tests that drop zone is properly configured
it('configures drop zone with correct handlers', () => {
renderClosedWorkOrders();
// Verify that useDropZone was called with correct parameters
expect(mockUseDropZone).toHaveBeenCalledWith({
onDrop: expect.any(Function),
tableType: 'closedWorkOrders',
});
});
// Tests drag over visual feedback
it('shows visual feedback when dragging over drop zone', () => {
mockUseDropZone.mockReturnValue({
isDraggingOver: true,
dropZoneHandlers: mockDropZoneHandlers,
dropZoneRef: { current: null },
});
renderClosedWorkOrders();
// The drop zone should have visual feedback styling
const dropZone = screen.getByRole('table').closest('[data-droppable="true"]');
expect(dropZone).toBeInTheDocument();
expect(dropZone).toHaveAttribute('data-table-type', 'closedWorkOrders');
});
// Tests that drop zone handlers are attached
it('attaches drop zone event handlers correctly', () => {
renderClosedWorkOrders();
const dropZone = screen.getByRole('table').closest('[data-droppable="true"]');
expect(dropZone).toBeInTheDocument();
// Verify the drop zone is configured for touch interactions
expect(dropZone).toHaveStyle('touch-action: none');
});
});
describe('Confirmation Dialogs', () => {
// Tests clear table confirmation dialog
it('shows confirmation dialog when clear table button is clicked', async () => {
const user = userEvent.setup();
renderClosedWorkOrders();
const clearButton = screen.getByRole('button', { name: /clear table/i });
await user.click(clearButton);
const dialog = screen.getByTestId('confirm-dialog');
expect(dialog).toBeInTheDocument();
expect(within(dialog).getByText('Clear Table')).toBeInTheDocument();
expect(within(dialog).getByText('Are you sure you want to clear this table?')).toBeInTheDocument();
expect(within(dialog).getByText('Clear')).toBeInTheDocument();
expect(within(dialog).getByText('Cancel')).toBeInTheDocument();
});
// Tests delete row confirmation dialog
it('shows confirmation dialog when delete button is clicked', async () => {
const user = userEvent.setup();
renderClosedWorkOrders();
const deleteButton = screen.getByTestId('delete-button-WO001');
await user.click(deleteButton);
const dialog = screen.getByTestId('confirm-dialog');
expect(dialog).toBeInTheDocument();
expect(within(dialog).getByText('Clear the row')).toBeInTheDocument();
expect(within(dialog).getByText('Are you sure you want to clear this row?')).toBeInTheDocument();
expect(within(dialog).getByText('Work Order:')).toBeInTheDocument();
expect(within(dialog).getByText('WO001')).toBeInTheDocument();
});
// Tests canceling clear table dialog
it('closes clear table dialog when cancel is clicked', async () => {
const user = userEvent.setup();
renderClosedWorkOrders();
const clearButton = screen.getByRole('button', { name: /clear table/i });
await user.click(clearButton);
const dialog = screen.getByTestId('confirm-dialog');
const cancelButton = within(dialog).getByText('Cancel');
await user.click(cancelButton);
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument();
});
// Tests confirming clear table action
it('handles clear table confirmation correctly', async () => {
const user = userEvent.setup();
renderClosedWorkOrders();
const clearButton = screen.getByRole('button', { name: /clear table/i });
await user.click(clearButton);
const dialog = screen.getByTestId('confirm-dialog');
const confirmButton = within(dialog).getByText('Clear');
await user.click(confirmButton);
// Dialog should close after confirmation
expect(screen.queryByTestId('confirm-dialog')).not.toBeInTheDocument();
});
});
describe('Component Layout and Styling', () => {
// Tests that component has correct width and layout
it('renders with correct layout and width', () => {
renderClosedWorkOrders();
// The component should be wrapped in PaperFrame with 33% width
const container = screen.getByText('Closed Work Orders').closest('[class*="MuiPaper-root"]');
expect(container).toBeInTheDocument();
});
// Tests table container scrolling behavior
it('configures table container for proper scrolling', () => {
renderClosedWorkOrders();
const tableContainer = screen.getByRole('table').closest('[class*="MuiTableContainer-root"]');
expect(tableContainer).toBeInTheDocument();
expect(tableContainer).toHaveStyle('touch-action: none');
});
// Tests header styling and layout
it('renders header with correct styling and controls', () => {
renderClosedWorkOrders();
const header = screen.getByText('Closed Work Orders').parentElement;
expect(header).toBeInTheDocument();
// Should contain both title and clear button
expect(within(header!).getByText('Closed Work Orders')).toBeInTheDocument();
expect(within(header!).getByRole('button', { name: /clear table/i })).toBeInTheDocument();
});
});
describe('Error Handling and Edge Cases', () => {
// Tests behavior with malformed work order data
it('handles malformed work order data gracefully', () => {
const malformedWorkOrders = [
{
workOrderNo: 'WO003',
// Missing some required fields
committed: false,
assignedTo: '',
shift: '',
notes: '',
},
];
renderClosedWorkOrders({}, {
whiteBoard: {
closedWorkOrders: malformedWorkOrders,
locked: false,
},
});
expect(screen.getByText('Closed Work Orders')).toBeInTheDocument();
expect(screen.getByTestId('work-order-row-WO003')).toBeInTheDocument();
});
// Tests behavior when Redux state is undefined
it('handles undefined Redux state gracefully', () => {
renderClosedWorkOrders({}, {
whiteBoard: {
closedWorkOrders: undefined,
locked: false,
},
});
expect(screen.getByText('Closed Work Orders')).toBeInTheDocument();
expect(screen.getByRole('table')).toBeInTheDocument();
});
// Tests behavior with very long work order numbers
it('handles long work order numbers correctly', () => {
const longWorkOrderNumber = 'WO' + '1'.repeat(50);
const workOrderWithLongNumber = [{
...mockWorkOrders[0],
workOrderNo: longWorkOrderNumber,
}];
renderClosedWorkOrders({}, {
whiteBoard: {
closedWorkOrders: workOrderWithLongNumber,
locked: false,
},
});
expect(screen.getByTestId(`work-order-row-${longWorkOrderNumber}`)).toBeInTheDocument();
expect(screen.getByTestId(`work-order-no-${longWorkOrderNumber}`)).toHaveTextContent(longWorkOrderNumber);
});
});
describe('Accessibility', () => {
// Tests that table has proper accessibility structure
it('provides proper table accessibility structure', () => {
renderClosedWorkOrders();
const table = screen.getByRole('table');
expect(table).toBeInTheDocument();
// Should have proper table headers
const columnHeaders = screen.getAllByRole('columnheader');
expect(columnHeaders).toHaveLength(8); // 8 columns as defined in the component
});
// Tests that interactive elements have proper accessibility
it('provides proper accessibility for interactive elements', () => {
renderClosedWorkOrders();
const clearButton = screen.getByRole('button', { name: /clear table/i });
expect(clearButton).toBeInTheDocument();
expect(clearButton).toHaveAttribute('title', 'Clear Table');
// Checkboxes should be accessible
const checkboxes = screen.getAllByRole('checkbox');
expect(checkboxes.length).toBeGreaterThan(0);
});
// Tests keyboard navigation support
it('supports keyboard navigation for interactive elements', async () => {
const user = userEvent.setup();
renderClosedWorkOrders();
const clearButton = screen.getByRole('button', { name: /clear table/i });
// Should be focusable
await user.tab();
expect(clearButton).toHaveFocus();
// Should be activatable with Enter
await user.keyboard('{Enter}');
expect(screen.getByTestId('confirm-dialog')).toBeInTheDocument();
});
});
});Editor is loading...
Leave a Comment