Skip to content

Commit 059a824

Browse files
fix(ui): auto-close date-time-picker on selection and add tests (#449)
* fix(ui): auto-close date-time-picker on selection and add tests * move ui tests to __tests__ and revert functional changes * code format
1 parent 2ffc61a commit 059a824

4 files changed

Lines changed: 146 additions & 1 deletion

File tree

frontend/jest.setup.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@ import '@testing-library/jest-dom';
22
import { expect } from '@jest/globals';
33
import type { Plugin } from 'pretty-format';
44

5+
// ResizeObserver polyfill
6+
global.ResizeObserver = class ResizeObserver {
7+
observe() {}
8+
unobserve() {}
9+
disconnect() {}
10+
};
11+
12+
// ScrollIntoView mock
13+
window.HTMLElement.prototype.scrollIntoView = jest.fn();
14+
15+
// PointerEvent mock
16+
class MockPointerEvent extends Event {
17+
button: number;
18+
ctrlKey: boolean;
19+
pointerType: string;
20+
21+
constructor(type: string, props: PointerEventInit) {
22+
super(type, props);
23+
this.button = props.button || 0;
24+
this.ctrlKey = props.ctrlKey || false;
25+
this.pointerType = props.pointerType || 'mouse';
26+
}
27+
}
28+
window.PointerEvent = MockPointerEvent as any;
29+
window.HTMLElement.prototype.hasPointerCapture = jest.fn();
30+
window.HTMLElement.prototype.releasePointerCapture = jest.fn();
31+
532
let isSerializing = false;
633

734
const radixSnapshotSerializer: Plugin = {
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
2+
import userEvent from '@testing-library/user-event';
3+
import { DateTimePicker } from '../date-time-picker';
4+
import '@testing-library/jest-dom';
5+
6+
describe('DateTimePicker', () => {
7+
it('renders without crashing', () => {
8+
const mockOnDateTimeChange = jest.fn();
9+
render(
10+
<DateTimePicker
11+
date={undefined}
12+
onDateTimeChange={mockOnDateTimeChange}
13+
/>
14+
);
15+
expect(
16+
screen.getByRole('button', { name: /calender-button/i })
17+
).toBeInTheDocument();
18+
});
19+
20+
it('opens and closes the popover when the trigger button is clicked', async () => {
21+
const user = userEvent.setup();
22+
const mockOnDateTimeChange = jest.fn();
23+
render(
24+
<DateTimePicker
25+
date={undefined}
26+
onDateTimeChange={mockOnDateTimeChange}
27+
/>
28+
);
29+
30+
const triggerButton = screen.getByRole('button', {
31+
name: /calender-button/i,
32+
});
33+
34+
// Open popover
35+
await user.click(triggerButton);
36+
expect(screen.getByRole('dialog')).toBeInTheDocument(); // Popover content is a dialog
37+
expect(screen.getByText(/February 2026/)).toBeInTheDocument(); // Check for specific content inside the calendar
38+
39+
// Close popover using Escape key
40+
fireEvent.keyDown(document, { key: 'Escape' });
41+
await waitFor(() => {
42+
expect(screen.queryByText(/February 2026/)).not.toBeInTheDocument(); // Check for absence of specific content
43+
});
44+
});
45+
46+
it('allows selecting a date from the calendar', async () => {
47+
const user = userEvent.setup();
48+
const mockOnDateTimeChange = jest.fn();
49+
render(
50+
<DateTimePicker
51+
date={undefined}
52+
onDateTimeChange={mockOnDateTimeChange}
53+
/>
54+
);
55+
56+
await user.click(screen.getByRole('button', { name: /calender-button/i })); // Open popover
57+
58+
// Find a date in the current month (e.g., the 15th)
59+
const dateToSelect = screen.getByRole('gridcell', { name: '15' });
60+
await user.click(dateToSelect);
61+
62+
// Expect the popover to close after selecting a date
63+
await waitFor(() => {
64+
expect(screen.queryByText(/February 2026/)).not.toBeInTheDocument();
65+
});
66+
67+
// Check if onDateTimeChange was called with the correct date (year, month, and day)
68+
expect(mockOnDateTimeChange).toHaveBeenCalledTimes(1);
69+
const calledDate = mockOnDateTimeChange.mock.calls[0][0];
70+
expect(calledDate).toBeInstanceOf(Date);
71+
expect(calledDate.getDate()).toBe(15);
72+
expect(calledDate.getMonth()).toBe(new Date().getMonth()); // Assuming current month for simplicity
73+
expect(calledDate.getFullYear()).toBe(new Date().getFullYear()); // Assuming current year for simplicity
74+
expect(calledDate.getHours()).toBe(0); // Should reset time to 00:00:00
75+
expect(mockOnDateTimeChange.mock.calls[0][1]).toBe(false); // hasTime should be false
76+
});
77+
78+
it('allows selecting an hour, minute, and AM/PM', async () => {
79+
const user = userEvent.setup();
80+
const mockOnDateTimeChange = jest.fn();
81+
const initialDate = new Date(2024, 0, 15, 10, 30); // Jan 15, 2024, 10:30 AM
82+
render(
83+
<DateTimePicker
84+
date={initialDate}
85+
onDateTimeChange={mockOnDateTimeChange}
86+
/>
87+
);
88+
89+
await user.click(screen.getByRole('button', { name: /calender-button/i })); // Open popover
90+
91+
// Verify time selection elements are present
92+
expect(screen.getByText('AM')).toBeInTheDocument();
93+
expect(screen.getByText('PM')).toBeInTheDocument();
94+
95+
// Select an hour (e.g., 2 PM)
96+
await user.click(screen.getByRole('button', { name: '2' })); // Select hour 2
97+
expect(mockOnDateTimeChange).toHaveBeenCalledTimes(1); // One call for hour selection
98+
let calledDate = mockOnDateTimeChange.mock.calls[0][0];
99+
expect(calledDate.getHours()).toBe(2); // Should be 2 AM initially before PM is clicked
100+
101+
await user.click(screen.getByRole('button', { name: 'PM' })); // Select PM
102+
expect(mockOnDateTimeChange).toHaveBeenCalledTimes(2); // Second call for AM/PM selection
103+
calledDate = mockOnDateTimeChange.mock.calls[1][0];
104+
expect(calledDate.getHours()).toBe(14); // 2 PM
105+
expect(mockOnDateTimeChange.mock.calls[1][1]).toBe(true); // hasTime should be true
106+
107+
// Select a minute (e.g., 45 minutes)
108+
await user.click(screen.getByRole('button', { name: '45' }));
109+
expect(mockOnDateTimeChange).toHaveBeenCalledTimes(3); // Third call for minute selection
110+
calledDate = mockOnDateTimeChange.mock.calls[2][0];
111+
expect(calledDate.getMinutes()).toBe(45);
112+
expect(mockOnDateTimeChange.mock.calls[2][1]).toBe(true); // hasTime should be true
113+
});
114+
});

frontend/src/components/ui/date-time-picker.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ export const DateTimePicker = React.forwardRef<
5858

5959
isInternalUpdate.current = true;
6060
onDateTimeChange(newDate, false);
61+
setIsOpen((prev) => {
62+
console.log('Closing popover, prev was:', prev);
63+
return false;
64+
});
6165
} else {
6266
setInternalDate(undefined);
6367
setHasTime(false);

frontend/tsconfig.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@
2727
"@/*": ["./src/*"]
2828
}
2929
},
30-
"include": ["src"],
30+
"include": ["src", "../../extra/multi-select-filter.test.tsx"],
3131
"references": [{ "path": "./tsconfig.node.json" }]
3232
}

0 commit comments

Comments
 (0)