From 3c988de085e5d6f150952523cf140919300ab362 Mon Sep 17 00:00:00 2001 From: prv-proton Date: Mon, 6 Jan 2025 21:18:35 -0800 Subject: [PATCH] update test cases --- .../__tests__/BCDateFloatingFilter.test.jsx | 168 ++++++++++++++++++ .../__tests__/BCSelectFloatingFilter.test.jsx | 166 +++++++++++++++++ .../Admin/AdminMenu/components/_schema.js | 7 +- 3 files changed, 338 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/__tests__/BCDateFloatingFilter.test.jsx create mode 100644 frontend/src/components/__tests__/BCSelectFloatingFilter.test.jsx diff --git a/frontend/src/components/__tests__/BCDateFloatingFilter.test.jsx b/frontend/src/components/__tests__/BCDateFloatingFilter.test.jsx new file mode 100644 index 000000000..9865f281f --- /dev/null +++ b/frontend/src/components/__tests__/BCDateFloatingFilter.test.jsx @@ -0,0 +1,168 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { BCDateFloatingFilter } from './BCDateFloatingFilter' +import { format } from 'date-fns' + +// Mock Material-UI components +vi.mock('@mui/material', () => ({ + FormControl: ({ children, ...props }) =>
{children}
, + IconButton: ({ children, onClick, ...props }) => ( + + ), + InputAdornment: ({ children }) =>
{children}
+})) + +vi.mock('@mui/x-date-pickers', () => ({ + DatePicker: ({ value, onChange, onOpen, onClose, slotProps, ...props }) => ( +
+ onChange(new Date(e.target.value))} + {...props} + /> + {slotProps.textField.InputProps.startAdornment} + {slotProps.textField.InputProps.endAdornment} +
+ ) +})) + +describe('BCDateFloatingFilter', () => { + const mockOnModelChange = vi.fn() + const defaultProps = { + model: null, + onModelChange: mockOnModelChange, + disabled: false, + minDate: '2013-01-01', + maxDate: '2040-01-01', + initialFilterType: 'equals', + label: 'Select Date' + } + + beforeEach(() => { + mockOnModelChange.mockClear() + }) + + it('renders with default props', () => { + render() + + expect(screen.getByRole('group')).toBeInTheDocument() + expect(screen.getByLabelText('Open calendar')).toBeInTheDocument() + expect(screen.getByLabelText('Date Picker')).toBeInTheDocument() + }) + + it('handles date selection', async () => { + render() + + const input = screen.getByLabelText('Date Picker') + const testDate = '2024-01-01' + + fireEvent.change(input, { target: { value: testDate } }) + + expect(mockOnModelChange).toHaveBeenCalledWith({ + type: 'equals', + dateFrom: testDate, + dateTo: null, + filterType: 'date' + }) + }) + + it('handles date clearing', async () => { + const initialModel = { + type: 'equals', + dateFrom: '2024-01-01', + dateTo: null, + filterType: 'date' + } + + render() + + const clearButton = screen.getByLabelText('Clear date') + fireEvent.click(clearButton) + + expect(mockOnModelChange).toHaveBeenCalledWith(null) + }) + + it('initializes with model date when provided', () => { + const modelWithDate = { + type: 'equals', + dateFrom: '2024-01-01', + dateTo: null, + filterType: 'date' + } + + render() + + const input = screen.getByLabelText('Date Picker') + expect(input).toHaveValue('2024-01-01') + }) + + it('disables input when disabled prop is true', () => { + render() + + const input = screen.getByLabelText('Date Picker') + expect(input).toBeDisabled() + }) + + it('handles invalid date input', async () => { + render() + + const input = screen.getByLabelText('Date Picker') + fireEvent.change(input, { target: { value: 'invalid-date' } }) + + expect(mockOnModelChange).toHaveBeenCalledWith(null) + }) + + it('opens calendar on calendar icon click', () => { + render() + + const calendarButton = screen.getByLabelText('Open calendar') + fireEvent.click(calendarButton) + + // Since we're mocking the DatePicker, we can't directly test if the calendar opens, + // but we can verify the click handler was called + expect(calendarButton).toBeInTheDocument() + }) + + it('maintains proper ARIA attributes', () => { + render() + + const container = screen.getByRole('group') + expect(container).toHaveAttribute('aria-labelledby', 'date-picker-label') + + const datePicker = screen.getByLabelText('Date Picker') + expect(datePicker).toHaveAttribute('aria-describedby', 'date-picker-description') + }) + + it('respects min and max date constraints', () => { + const customProps = { + ...defaultProps, + minDate: '2023-01-01', + maxDate: '2025-01-01' + } + + render() + + const input = screen.getByLabelText('Date Picker') + expect(input).toBeInTheDocument() + // Note: Actual min/max date validation would be handled by the DatePicker component + }) + + it('updates when model changes externally', () => { + const { rerender } = render() + + const newModel = { + type: 'equals', + dateFrom: '2024-02-01', + dateTo: null, + filterType: 'date' + } + + rerender() + + const input = screen.getByLabelText('Date Picker') + expect(input).toHaveValue('2024-02-01') + }) +}) \ No newline at end of file diff --git a/frontend/src/components/__tests__/BCSelectFloatingFilter.test.jsx b/frontend/src/components/__tests__/BCSelectFloatingFilter.test.jsx new file mode 100644 index 000000000..e09c0600b --- /dev/null +++ b/frontend/src/components/__tests__/BCSelectFloatingFilter.test.jsx @@ -0,0 +1,166 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest' +import { render, screen, fireEvent, waitFor } from '@testing-library/react' +import { BCSelectFloatingFilter } from '../BCDataGrid/components' + +describe('BCSelectFloatingFilter', () => { + const mockOnModelChange = vi.fn() + const defaultProps = { + model: null, + onModelChange: mockOnModelChange, + optionsQuery: () => ({ + data: [ + { value: '1', label: 'Option 1' }, + { value: '2', label: 'Option 2' }, + { value: '3', label: 'Option 3' } + ], + isLoading: false, + isError: false, + error: null + }), + valueKey: 'value', + labelKey: 'label', + disabled: false, + params: {}, + initialFilterType: 'equals', + multiple: false, + initialSelectedValues: [] + } + + beforeEach(() => { + mockOnModelChange.mockClear() + }) + + it('renders with default props', () => { + render() + + const select = screen.getByRole('combobox') + expect(select).toBeInTheDocument() + expect(screen.getByText('Select')).toBeInTheDocument() + expect(screen.getAllByRole('option')).toHaveLength(4) // Including the default "Select" option + }) + + it('handles single selection', async () => { + render() + + const select = screen.getByRole('combobox') + fireEvent.change(select, { target: { value: '1' } }) + + expect(mockOnModelChange).toHaveBeenCalledWith({ + type: 'equals', + filter: '1' + }) + }) + + it('handles multiple selection when multiple prop is true', () => { + render() + + const select = screen.getByRole('combobox') + fireEvent.change(select, { + target: { + options: [ + { selected: true, value: '1' }, + { selected: true, value: '2' } + ] + } + }) + + expect(mockOnModelChange).toHaveBeenCalledWith({ + type: 'equals', + filter: ['1', '2'] + }) + }) + + it('shows loading state', () => { + const loadingProps = { + ...defaultProps, + optionsQuery: () => ({ + data: null, + isLoading: true, + isError: false, + error: null + }) + } + + render() + expect(screen.getByText('Loading...')).toBeInTheDocument() + }) + + it('shows error state', () => { + const errorProps = { + ...defaultProps, + optionsQuery: () => ({ + data: null, + isLoading: false, + isError: true, + error: { message: 'Failed to load' } + }) + } + + render() + expect( + screen.getByText('Error loading options: Failed to load') + ).toBeInTheDocument() + }) + + it('clears selection when clear button is clicked', async () => { + render() + + // First select a value + const select = screen.getByRole('combobox') + fireEvent.change(select, { target: { value: '1' } }) + + // Then clear it + const clearButton = screen.getByLabelText('Clear selection') + fireEvent.click(clearButton) + + expect(mockOnModelChange).toHaveBeenLastCalledWith(null) + }) + + it('initializes with model value when provided', () => { + const modelProps = { + ...defaultProps, + model: { type: 'equals', filter: '2' } + } + + render() + const select = screen.getByRole('combobox') + expect(select).toHaveValue('2') + }) + + it('initializes with initialSelectedValues when provided', () => { + const initialValueProps = { + ...defaultProps, + initialSelectedValues: ['1'] + } + + render() + const select = screen.getByRole('combobox') + expect(select).toHaveValue('1') + }) + + it('disables select when disabled prop is true', () => { + render() + const select = screen.getByRole('combobox') + expect(select).toBeDisabled() + }) + + it('handles empty/null selection in single select mode', () => { + render() + + const select = screen.getByRole('combobox') + fireEvent.change(select, { target: { value: '0' } }) + + expect(mockOnModelChange).toHaveBeenCalledWith(null) + }) + + it('maintains proper ARIA attributes', () => { + render() + + const container = screen.getByRole('group') + expect(container).toHaveAttribute('aria-labelledby', 'select-filter-label') + + const combobox = screen.getByRole('combobox') + expect(combobox).toHaveAttribute('aria-controls', 'select-filter') + expect(combobox).toHaveAttribute('aria-expanded', 'false') + }) +}) diff --git a/frontend/src/views/Admin/AdminMenu/components/_schema.js b/frontend/src/views/Admin/AdminMenu/components/_schema.js index 03438e25a..eadea1422 100644 --- a/frontend/src/views/Admin/AdminMenu/components/_schema.js +++ b/frontend/src/views/Admin/AdminMenu/components/_schema.js @@ -164,12 +164,13 @@ export const userActivityColDefs = [ headerName: 'Transaction Type', floatingFilterComponent: BCSelectFloatingFilter, floatingFilterComponentParams: { - valueKey: 'action', - labelKey: 'action', + valueKey: 'value', + labelKey: 'label', optionsQuery: () => ({ data: [ ...Object.values(TRANSACTION_TYPES).map((value) => ({ - action: value + label: value.replace(/([A-Z])/g, ' $1').trim(), + value })) ], isLoading: false