diff --git a/frontend/jest.setup.ts b/frontend/jest.setup.ts
index dcc614ba..6bc5b584 100644
--- a/frontend/jest.setup.ts
+++ b/frontend/jest.setup.ts
@@ -2,6 +2,33 @@ import '@testing-library/jest-dom';
import { expect } from '@jest/globals';
import type { Plugin } from 'pretty-format';
+// ResizeObserver polyfill
+global.ResizeObserver = class ResizeObserver {
+ observe() {}
+ unobserve() {}
+ disconnect() {}
+};
+
+// ScrollIntoView mock
+window.HTMLElement.prototype.scrollIntoView = jest.fn();
+
+// PointerEvent mock
+class MockPointerEvent extends Event {
+ button: number;
+ ctrlKey: boolean;
+ pointerType: string;
+
+ constructor(type: string, props: PointerEventInit) {
+ super(type, props);
+ this.button = props.button || 0;
+ this.ctrlKey = props.ctrlKey || false;
+ this.pointerType = props.pointerType || 'mouse';
+ }
+}
+window.PointerEvent = MockPointerEvent as any;
+window.HTMLElement.prototype.hasPointerCapture = jest.fn();
+window.HTMLElement.prototype.releasePointerCapture = jest.fn();
+
let isSerializing = false;
const radixSnapshotSerializer: Plugin = {
diff --git a/frontend/src/components/ui/__tests__/date-time-picker.test.tsx b/frontend/src/components/ui/__tests__/date-time-picker.test.tsx
new file mode 100644
index 00000000..73f7feb5
--- /dev/null
+++ b/frontend/src/components/ui/__tests__/date-time-picker.test.tsx
@@ -0,0 +1,114 @@
+import { render, screen, fireEvent, waitFor } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import { DateTimePicker } from '../date-time-picker';
+import '@testing-library/jest-dom';
+
+describe('DateTimePicker', () => {
+ it('renders without crashing', () => {
+ const mockOnDateTimeChange = jest.fn();
+ render(
+
+ );
+ expect(
+ screen.getByRole('button', { name: /calender-button/i })
+ ).toBeInTheDocument();
+ });
+
+ it('opens and closes the popover when the trigger button is clicked', async () => {
+ const user = userEvent.setup();
+ const mockOnDateTimeChange = jest.fn();
+ render(
+
+ );
+
+ const triggerButton = screen.getByRole('button', {
+ name: /calender-button/i,
+ });
+
+ // Open popover
+ await user.click(triggerButton);
+ expect(screen.getByRole('dialog')).toBeInTheDocument(); // Popover content is a dialog
+ expect(screen.getByText(/February 2026/)).toBeInTheDocument(); // Check for specific content inside the calendar
+
+ // Close popover using Escape key
+ fireEvent.keyDown(document, { key: 'Escape' });
+ await waitFor(() => {
+ expect(screen.queryByText(/February 2026/)).not.toBeInTheDocument(); // Check for absence of specific content
+ });
+ });
+
+ it('allows selecting a date from the calendar', async () => {
+ const user = userEvent.setup();
+ const mockOnDateTimeChange = jest.fn();
+ render(
+
+ );
+
+ await user.click(screen.getByRole('button', { name: /calender-button/i })); // Open popover
+
+ // Find a date in the current month (e.g., the 15th)
+ const dateToSelect = screen.getByRole('gridcell', { name: '15' });
+ await user.click(dateToSelect);
+
+ // Expect the popover to close after selecting a date
+ await waitFor(() => {
+ expect(screen.queryByText(/February 2026/)).not.toBeInTheDocument();
+ });
+
+ // Check if onDateTimeChange was called with the correct date (year, month, and day)
+ expect(mockOnDateTimeChange).toHaveBeenCalledTimes(1);
+ const calledDate = mockOnDateTimeChange.mock.calls[0][0];
+ expect(calledDate).toBeInstanceOf(Date);
+ expect(calledDate.getDate()).toBe(15);
+ expect(calledDate.getMonth()).toBe(new Date().getMonth()); // Assuming current month for simplicity
+ expect(calledDate.getFullYear()).toBe(new Date().getFullYear()); // Assuming current year for simplicity
+ expect(calledDate.getHours()).toBe(0); // Should reset time to 00:00:00
+ expect(mockOnDateTimeChange.mock.calls[0][1]).toBe(false); // hasTime should be false
+ });
+
+ it('allows selecting an hour, minute, and AM/PM', async () => {
+ const user = userEvent.setup();
+ const mockOnDateTimeChange = jest.fn();
+ const initialDate = new Date(2024, 0, 15, 10, 30); // Jan 15, 2024, 10:30 AM
+ render(
+
+ );
+
+ await user.click(screen.getByRole('button', { name: /calender-button/i })); // Open popover
+
+ // Verify time selection elements are present
+ expect(screen.getByText('AM')).toBeInTheDocument();
+ expect(screen.getByText('PM')).toBeInTheDocument();
+
+ // Select an hour (e.g., 2 PM)
+ await user.click(screen.getByRole('button', { name: '2' })); // Select hour 2
+ expect(mockOnDateTimeChange).toHaveBeenCalledTimes(1); // One call for hour selection
+ let calledDate = mockOnDateTimeChange.mock.calls[0][0];
+ expect(calledDate.getHours()).toBe(2); // Should be 2 AM initially before PM is clicked
+
+ await user.click(screen.getByRole('button', { name: 'PM' })); // Select PM
+ expect(mockOnDateTimeChange).toHaveBeenCalledTimes(2); // Second call for AM/PM selection
+ calledDate = mockOnDateTimeChange.mock.calls[1][0];
+ expect(calledDate.getHours()).toBe(14); // 2 PM
+ expect(mockOnDateTimeChange.mock.calls[1][1]).toBe(true); // hasTime should be true
+
+ // Select a minute (e.g., 45 minutes)
+ await user.click(screen.getByRole('button', { name: '45' }));
+ expect(mockOnDateTimeChange).toHaveBeenCalledTimes(3); // Third call for minute selection
+ calledDate = mockOnDateTimeChange.mock.calls[2][0];
+ expect(calledDate.getMinutes()).toBe(45);
+ expect(mockOnDateTimeChange.mock.calls[2][1]).toBe(true); // hasTime should be true
+ });
+});
diff --git a/frontend/src/components/ui/date-time-picker.tsx b/frontend/src/components/ui/date-time-picker.tsx
index 0ee83ab1..d7e43f50 100644
--- a/frontend/src/components/ui/date-time-picker.tsx
+++ b/frontend/src/components/ui/date-time-picker.tsx
@@ -58,6 +58,10 @@ export const DateTimePicker = React.forwardRef<
isInternalUpdate.current = true;
onDateTimeChange(newDate, false);
+ setIsOpen((prev) => {
+ console.log('Closing popover, prev was:', prev);
+ return false;
+ });
} else {
setInternalDate(undefined);
setHasTime(false);
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
index a575d8bd..952983f9 100644
--- a/frontend/tsconfig.json
+++ b/frontend/tsconfig.json
@@ -27,6 +27,6 @@
"@/*": ["./src/*"]
}
},
- "include": ["src"],
+ "include": ["src", "../../extra/multi-select-filter.test.tsx"],
"references": [{ "path": "./tsconfig.node.json" }]
}