Skip to content

Commit 26a5feb

Browse files
author
Ahtesham Quraish
committed
fix: Bug: Date picker controls cause MFE error on Safari #2323
1 parent 8326257 commit 26a5feb

File tree

9 files changed

+232
-50
lines changed

9 files changed

+232
-50
lines changed

package-lock.json

Lines changed: 69 additions & 35 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@
8080
"moment-shortformat": "^2.1.0",
8181
"prop-types": "^15.8.1",
8282
"react": "^18.3.1",
83-
"react-datepicker": "^4.13.0",
83+
"react-datepicker": "^7.6.0",
8484
"react-dom": "^18.3.1",
8585
"react-error-boundary": "^4.0.13",
8686
"react-helmet": "^6.1.0",

src/course-outline/CourseOutline.test.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -934,7 +934,7 @@ describe('<CourseOutline />', () => {
934934
it('check configure modal for section', async () => {
935935
const { findByTestId, findAllByTestId } = renderComponent();
936936
const section = courseOutlineIndexMock.courseStructure.childInfo.children[0];
937-
const newReleaseDateIso = '2025-09-10T22:00:00Z';
937+
const newReleaseDateIso = '2025-09-10T22:00:00.000Z';
938938
const newReleaseDate = '09/10/2025';
939939
axiosMock
940940
.onPost(getCourseItemApiUrl(section.id), {
@@ -999,7 +999,7 @@ describe('<CourseOutline />', () => {
999999
prereqMinCompletion: 100,
10001000
metadata: {
10011001
visible_to_staff_only: null,
1002-
due: '2025-09-10T05:00:00Z',
1002+
due: '2025-09-10T05:00:00.000Z',
10031003
hide_after_due: true,
10041004
show_correctness: 'always',
10051005
is_practice_exam: false,
@@ -1008,7 +1008,7 @@ describe('<CourseOutline />', () => {
10081008
exam_review_rules: '',
10091009
default_time_limit_minutes: 3270,
10101010
is_onboarding_exam: false,
1011-
start: '2025-08-10T00:00:00Z',
1011+
start: '2025-08-10T00:00:00.000Z',
10121012
},
10131013
};
10141014

src/course-updates/hooks.jsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import { useEffect, useState } from 'react';
44
import { useToggle } from '@openedx/paragon';
55

66
import { COMMA_SEPARATED_DATE_FORMAT } from '../constants';
7-
import { convertToDateFromString } from '../utils';
87
import { getCourseHandouts, getCourseUpdates } from './data/selectors';
98
import { REQUEST_TYPES } from './constants';
109
import {
@@ -15,6 +14,7 @@ import {
1514
fetchCourseHandoutsQuery,
1615
fetchCourseUpdatesQuery,
1716
} from './data/thunk';
17+
import { convertToDateFromString } from '../utils';
1818

1919
const useCourseUpdates = ({ courseId }) => {
2020
const dispatch = useDispatch();

src/course-updates/hooks.test.jsx

Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import moment from 'moment';
2+
import { REQUEST_TYPES } from './constants';
3+
import {
4+
createCourseUpdateQuery,
5+
editCourseUpdateQuery,
6+
editCourseHandoutsQuery,
7+
} from './data/thunk';
8+
import { COMMA_SEPARATED_DATE_FORMAT } from '../constants';
9+
10+
jest.mock('react-redux', () => ({
11+
useDispatch: jest.fn(),
12+
useSelector: jest.fn(),
13+
}));
14+
15+
jest.mock('./data/thunk', () => ({
16+
createCourseUpdateQuery: jest.fn(),
17+
editCourseUpdateQuery: jest.fn(),
18+
editCourseHandoutsQuery: jest.fn(),
19+
}));
20+
21+
jest.mock('../constants', () => ({
22+
COMMA_SEPARATED_DATE_FORMAT: 'YYYY,MM,DD',
23+
}));
24+
25+
describe('handleUpdatesSubmit', () => {
26+
let dispatchMock;
27+
let closeUpdateFormMock;
28+
let setCurrentUpdateMock;
29+
30+
const courseId = 'course-v1:test+T101+2025_T1';
31+
32+
beforeEach(() => {
33+
dispatchMock = jest.fn();
34+
closeUpdateFormMock = jest.fn();
35+
setCurrentUpdateMock = jest.fn();
36+
37+
jest.requireActual('./hooks');
38+
});
39+
40+
const getMockHookContext = (requestType) => {
41+
jest.requireActual('../hooks');
42+
43+
return {
44+
requestType,
45+
courseId,
46+
closeUpdateForm: closeUpdateFormMock,
47+
setCurrentUpdate: setCurrentUpdateMock,
48+
dispatch: dispatchMock,
49+
initialUpdate: { id: 0, date: moment().toDate(), content: '' },
50+
};
51+
};
52+
53+
const testData = {
54+
id: 5,
55+
content: 'Sample content',
56+
date: new Date('2025-08-01T00:00:00Z'),
57+
};
58+
59+
it('dispatches createCourseUpdateQuery when requestType is add_new_update', () => {
60+
const formattedDate = moment(testData.date).format(COMMA_SEPARATED_DATE_FORMAT);
61+
createCourseUpdateQuery.mockReturnValue('mockCreateAction');
62+
63+
const context = getMockHookContext(REQUEST_TYPES.add_new_update);
64+
const submitFn = (data) => {
65+
const date = moment(data.date).format(COMMA_SEPARATED_DATE_FORMAT);
66+
const action = createCourseUpdateQuery(context.courseId, {
67+
date,
68+
content: data.content,
69+
});
70+
context.closeUpdateForm();
71+
context.setCurrentUpdate(context.initialUpdate);
72+
context.dispatch(action);
73+
};
74+
75+
submitFn(testData);
76+
77+
expect(createCourseUpdateQuery).toHaveBeenCalledWith(courseId, {
78+
date: formattedDate,
79+
content: 'Sample content',
80+
});
81+
expect(dispatchMock).toHaveBeenCalledWith('mockCreateAction');
82+
expect(closeUpdateFormMock).toHaveBeenCalled();
83+
expect(setCurrentUpdateMock).toHaveBeenCalledWith(expect.objectContaining({ id: 0 }));
84+
});
85+
86+
it('dispatches editCourseUpdateQuery when requestType is edit_update', () => {
87+
const formattedDate = moment(testData.date).format(COMMA_SEPARATED_DATE_FORMAT);
88+
editCourseUpdateQuery.mockReturnValue('mockEditAction');
89+
90+
const context = getMockHookContext(REQUEST_TYPES.edit_update);
91+
const submitFn = (data) => {
92+
const date = moment(data.date).format(COMMA_SEPARATED_DATE_FORMAT);
93+
const action = editCourseUpdateQuery(context.courseId, {
94+
id: data.id,
95+
date,
96+
content: data.content,
97+
});
98+
context.closeUpdateForm();
99+
context.setCurrentUpdate(context.initialUpdate);
100+
context.dispatch(action);
101+
};
102+
103+
submitFn(testData);
104+
105+
expect(editCourseUpdateQuery).toHaveBeenCalledWith(courseId, {
106+
id: 5,
107+
date: formattedDate,
108+
content: 'Sample content',
109+
});
110+
expect(dispatchMock).toHaveBeenCalledWith('mockEditAction');
111+
expect(closeUpdateFormMock).toHaveBeenCalled();
112+
expect(setCurrentUpdateMock).toHaveBeenCalledWith(expect.objectContaining({ id: 0 }));
113+
});
114+
115+
it('dispatches editCourseHandoutsQuery when requestType is edit_handouts', () => {
116+
editCourseHandoutsQuery.mockReturnValue('mockHandoutAction');
117+
118+
const context = getMockHookContext(REQUEST_TYPES.edit_handouts);
119+
const submitFn = (data) => {
120+
const formatted = {
121+
...data,
122+
date: moment(data.date).format(COMMA_SEPARATED_DATE_FORMAT),
123+
data: data.data || '',
124+
};
125+
const action = editCourseHandoutsQuery(context.courseId, formatted);
126+
context.closeUpdateForm();
127+
context.setCurrentUpdate(context.initialUpdate);
128+
context.dispatch(action);
129+
};
130+
131+
submitFn(testData);
132+
133+
expect(editCourseHandoutsQuery).toHaveBeenCalledWith(courseId, expect.objectContaining({
134+
date: expect.any(String),
135+
content: 'Sample content',
136+
}));
137+
expect(dispatchMock).toHaveBeenCalledWith('mockHandoutAction');
138+
expect(closeUpdateFormMock).toHaveBeenCalled();
139+
expect(setCurrentUpdateMock).toHaveBeenCalledWith(expect.objectContaining({ id: 0 }));
140+
});
141+
it('extracts date without formatting for internal logic', () => {
142+
getMockHookContext(REQUEST_TYPES.edit_update);
143+
144+
const submitFn = (data) => {
145+
const dateWithoutTimezone = data.date;
146+
expect(dateWithoutTimezone).toEqual(new Date('2025-08-01T00:00:00Z'));
147+
};
148+
149+
submitFn(testData);
150+
});
151+
});

src/course-updates/update-form/UpdateForm.jsx

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@ import { Calendar as CalendarIcon, Error as ErrorIcon } from '@openedx/paragon/i
1313
import { Formik } from 'formik';
1414

1515
import {
16-
convertToStringFromDate,
17-
convertToDateFromString,
1816
isValidDate,
1917
} from '../../utils';
2018
import { DATE_FORMAT, DEFAULT_EMPTY_WYSIWYG_VALUE } from '../../constants';
@@ -73,7 +71,7 @@ const UpdateForm = ({
7371
<DatePicker
7472
name="date"
7573
data-testid="course-updates-datepicker"
76-
selected={isValidDate(values.date) ? convertToDateFromString(values.date) : ''}
74+
selected={isValidDate(values.date) ? values.date : ''}
7775
dateFormat={DATE_FORMAT}
7876
className={classNames('datepicker-custom-control', {
7977
'datepicker-custom-control_isInvalid': !isValid,
@@ -85,7 +83,7 @@ const UpdateForm = ({
8583
if (!isValidDate(value)) {
8684
return;
8785
}
88-
setFieldValue('date', convertToStringFromDate(value));
86+
setFieldValue('date', value);
8987
}}
9088
/>
9189
</div>

0 commit comments

Comments
 (0)