Untitled
unknown
plain_text
9 months ago
27 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 MUI X License to avoid license check errors in tests
jest.mock('@mui/x-license', () => ({
LicenseInfo: {
setLicenseKey: jest.fn(),
},
}));
// Mock MUI X DataGrid Pro to avoid license check
jest.mock('@mui/x-data-grid-pro', () => ({
DataGridPro: jest.fn(() => <div data-testid="data-grid-pro">DataGridPro</div>),
DataGrid: jest.fn(() => <div data-testid="data-grid">DataGrid</div>),
useGridApiRef: () => ({
current: {
updateRows: jest.fn(),
setFilterModel: jest.fn(),
setSortModel: jest.fn(),
setColumns: jest.fn(),
getColumns: jest.fn(() => []),
updateColumns: jest.fn(),
updateRow: jest.fn(),
getRow: jest.fn(),
getAllColumns: jest.fn(() => []),
},
}),
getGridDateOperators: jest.fn(() => []),
GridColDef: {},
GridRenderCellParams: {},
GridClasses: {},
gridClasses: {
cell: 'MuiDataGrid-cell',
row: 'MuiDataGrid-row',
'row--editing': 'MuiDataGrid-row--editing',
actionsCell: 'MuiDataGrid-actionsCell',
columnHeader: 'MuiDataGrid-columnHeader',
footerContainer: 'MuiDataGrid-footerContainer',
toolbarContainer: 'MuiDataGrid-toolbarContainer',
},
}));
// Mock Apollo Client useQuery
const mockUseQuery = jest.fn();
jest.mock('@apollo/client', () => ({
...jest.requireActual('@apollo/client'),
useQuery: (query: any, options: any) => mockUseQuery(query, options),
gql: jest.requireActual('@apollo/client').gql,
}));
// Mock Redux dispatch
const mockDispatch = jest.fn();
jest.mock('react-redux', () => ({
...jest.requireActual('react-redux'),
useDispatch: () => mockDispatch,
}));
// Mock hooks
jest.mock('../../hooks/useDropZone', () => ({
useDropZone: jest.fn(() => ({
isDraggingOver: false,
dropZoneHandlers: {},
dropZoneRef: { current: null },
})),
}));
jest.mock('../../hooks/useDragAndDrop', () => ({
useDragAndDrop: () => ({
draggedWorkOrder: null,
dragHandlers: {
onDragStart: jest.fn(),
onDragEnd: jest.fn(),
},
}),
}));
// 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" role="dialog">
<h2>{title}</h2>
<div>{typeof message === 'string' ? message : 'Dialog message'}</div>
<button onClick={onConfirm} data-testid="confirm-button">
{confirmText}
</button>
<button onClick={onCancel} data-testid="cancel-button">
{cancelText}
</button>
</div>
);
};
});
// Mock WorkOrderColumns
jest.mock('../../components/WorkOrderColumns', () => {
return {
__esModule: true,
default: [
{ field: 'workOrderNo', headerName: 'Work Order No', width: 150 },
{ field: 'assignedTo', headerName: 'Assigned', width: 120 },
{ field: 'shift', headerName: 'Shift', width: 100 },
{ field: 'notes', headerName: 'Notes', width: 200 },
],
applyConfigurations: jest.fn((currentApi, columns, _restoreDefault) => {
// Handle case where currentApi might be undefined
if (!currentApi) {
return Promise.resolve(columns || []);
}
return Promise.resolve(columns || [
{ field: 'workOrderNo', headerName: 'Work Order No', width: 150 },
{ field: 'assignedTo', headerName: 'Assigned', width: 120 },
{ field: 'shift', headerName: 'Shift', width: 100 },
{ field: 'notes', headerName: 'Notes', width: 200 },
]);
}),
getWorkOrderColumns: jest.fn(() => [
{ field: 'workOrderNo', headerName: 'Work Order No', width: 150 },
{ field: 'assignedTo', headerName: 'Assigned', width: 120 },
{ field: 'shift', headerName: 'Shift', width: 100 },
{ field: 'notes', headerName: 'Notes', width: 200 },
]),
};
});
// Mock DraggableTableRow
jest.mock('../../components/DraggableTableRow', () => ({
DraggableTableRow: ({ order, handleCommitChange, handleDeleteRow, editingId, editValue, editingShiftId, shiftValue, editingNotesId, notesValue, setEditingId, setEditValue, setEditingShiftId, setShiftValue, setEditingNotesId, setNotesValue, handleSave, handleSaveShift, handleSaveNotes }: any) => (
<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',
},
];
// Create mock functions for component props
const mockClosedWorkOrderRow = jest.fn();
const mockSaveConfigurations = jest.fn();
const mockApplyConfigurations = jest.fn((currentApi, columns, _restoreDefault) => {
return Promise.resolve(columns || [
{ field: 'workOrderNo', headerName: 'Work Order No', width: 150 },
{ field: 'assignedTo', headerName: 'Assigned', width: 120 },
{ field: 'shift', headerName: 'Shift', width: 100 },
{ field: 'notes', headerName: 'Notes', width: 200 },
]);
});
const renderClosedWorkOrders = (initialState: any = {}, queryResult = {}, componentProps: any = {}) => {
// Set up default useQuery mock
mockUseQuery.mockReturnValue({
data: { closedWoList: mockWorkOrders },
loading: false,
error: null,
...queryResult,
});
// Use tomorrow's date to ensure whiteboard is not locked by date logic
const tomorrow = new Date();
tomorrow.setDate(tomorrow.getDate() + 1);
const store = createTestStore({
whiteBoard: {
tailNumber: 'TEST-001',
planDate: tomorrow.toISOString().split('T')[0], // Future date
electrical: [],
structures: [],
mechanical: [],
constraints: [],
closedWorkOrders: mockWorkOrders,
locked: false,
isPrevWhiteBoardImported: false,
constraintBoardStates: {},
...initialState.whiteBoard,
},
});
// Render with props that match how component is used in PlanOfTheDayDashboard
// Using 'as any' because component accepts these props (used in PlanOfTheDayDashboard)
// but TypeScript definition may not reflect this
return render(
<ClosedWorkOrders
{...({
tailNumber: componentProps.tailNumber || 'TEST-001',
closedWorkOrderRow: componentProps.closedWorkOrderRow || mockClosedWorkOrderRow,
saveConfigurations: componentProps.saveConfigurations || mockSaveConfigurations,
applyConfigurations: componentProps.applyConfigurations || mockApplyConfigurations,
} as any)}
/>,
{ store }
);
};
beforeEach(() => {
jest.clearAllMocks();
// Reset useQuery mock to default
mockUseQuery.mockReturnValue({
data: { closedWoList: mockWorkOrders },
loading: false,
error: null,
});
// Reset prop mocks
mockClosedWorkOrderRow.mockClear();
mockSaveConfigurations.mockClear();
mockApplyConfigurations.mockImplementation((currentApi, columns, _restoreDefault) => {
return Promise.resolve(columns || [
{ field: 'workOrderNo', headerName: 'Work Order No', width: 150 },
{ field: 'assignedTo', headerName: 'Assigned', width: 120 },
{ field: 'shift', headerName: 'Shift', width: 100 },
{ field: 'notes', headerName: 'Notes', width: 200 },
]);
});
});
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);
expect(mockDispatch).toHaveBeenCalledWith(
expect.objectContaining({
type: expect.stringContaining('updateWorkOrder'),
payload: expect.objectContaining({
category: 'closedWorkOrders',
workOrder: expect.objectContaining({
workOrderNo: 'WO001',
committed: true,
}),
}),
})
);
});
// 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', () => {
// Use past date to trigger locked state through business logic
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
renderClosedWorkOrders({
whiteBoard: {
closedWorkOrders: mockWorkOrders,
locked: true,
planDate: yesterday.toISOString().split('T')[0],
},
});
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 drop zone integration
it('integrates with drop zone for drag and drop', () => {
renderClosedWorkOrders();
// Should have drop zone attributes
const dropZone = screen.getByRole('table').closest('[data-droppable="true"]');
expect(dropZone).toBeInTheDocument();
expect(dropZone).toHaveAttribute('data-table-type', 'closedWorkOrders');
});
// Tests drag over styling
it('applies drag over styling when dragging', () => {
renderClosedWorkOrders();
// Should apply drag over styling
expect(screen.getByRole('table')).toBeInTheDocument();
});
// 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).getByTestId('confirm-button')).toHaveTextContent('Clear');
expect(within(dialog).getByTestId('cancel-button')).toHaveTextContent('Cancel');
});
// 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).getByTestId('cancel-button');
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).getByTestId('confirm-button');
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