Skip to content

Commit c4fb393

Browse files
authored
feat(DateField): add parseDateFromString property (#190)
1 parent 23dee0b commit c4fb393

File tree

7 files changed

+259
-71
lines changed

7 files changed

+259
-71
lines changed

src/components/DateField/README.md

Lines changed: 61 additions & 35 deletions
Large diffs are not rendered by default.
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {dateTime} from '@gravity-ui/date-utils';
2+
import {act, renderHook} from '@testing-library/react';
3+
4+
import {useDateFieldState} from '../hooks/useDateFieldState';
5+
import {parseDateFromString} from '../utils';
6+
7+
jest.mock('../utils', () => ({
8+
...jest.requireActual('../utils'),
9+
parseDateFromString: jest.fn(),
10+
}));
11+
12+
const mockedParseDateFromString = parseDateFromString as jest.MockedFunction<
13+
typeof parseDateFromString
14+
>;
15+
16+
describe('DateField: parseDateFromString', () => {
17+
beforeEach(() => {
18+
jest.clearAllMocks();
19+
mockedParseDateFromString.mockImplementation((str, format, timeZone) => {
20+
return dateTime({input: str, format, timeZone});
21+
});
22+
});
23+
24+
it('should call custom parseDateFromString when provided', () => {
25+
const customParser = jest.fn().mockReturnValue(dateTime({input: '2024-01-15T00:00:00Z'}));
26+
27+
const {result} = renderHook(() =>
28+
useDateFieldState({
29+
format: 'DD.MM.YYYY',
30+
parseDateFromString: customParser,
31+
}),
32+
);
33+
34+
act(() => {
35+
result.current.setValueFromString('15.01.2024');
36+
});
37+
38+
expect(customParser).toHaveBeenCalledWith('15.01.2024', 'DD.MM.YYYY', 'default');
39+
expect(mockedParseDateFromString).not.toHaveBeenCalled();
40+
});
41+
42+
it('should use default parseDateFromString when parseDateFromString is not provided', () => {
43+
const validDate = dateTime({input: '2024-01-15T00:00:00Z'});
44+
mockedParseDateFromString.mockReturnValue(validDate);
45+
46+
const {result} = renderHook(() => useDateFieldState({format: 'DD.MM.YYYY'}));
47+
48+
act(() => {
49+
result.current.setValueFromString('15.01.2024');
50+
});
51+
52+
expect(mockedParseDateFromString).toHaveBeenCalledWith(
53+
'15.01.2024',
54+
'DD.MM.YYYY',
55+
'default',
56+
);
57+
});
58+
});

src/components/DateField/hooks/useDateFieldState.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,8 @@ export function useDateFieldState(props: DateFieldStateOptions): DateFieldState
194194
}
195195

196196
function setValueFromString(str: string) {
197-
const date = parseDateFromString(str, format, timeZone);
197+
const parseDate = props.parseDateFromString ?? parseDateFromString;
198+
const date = parseDate(str, format, timeZone);
198199
if (date.isValid()) {
199200
handleUpdateDate(date);
200201
return true;

src/components/DatePicker/README.md

Lines changed: 59 additions & 33 deletions
Large diffs are not rendered by default.
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import {dateTime} from '@gravity-ui/date-utils';
2+
import {act, renderHook} from '@testing-library/react';
3+
4+
import {parseDateFromString} from '../../DateField/utils';
5+
import {useRangeDateFieldState} from '../hooks/useRangeDateFieldState';
6+
7+
jest.mock('../../DateField/utils', () => ({
8+
...jest.requireActual('../../DateField/utils'),
9+
parseDateFromString: jest.fn(),
10+
}));
11+
12+
const mockedParseDateFromString = parseDateFromString as jest.MockedFunction<
13+
typeof parseDateFromString
14+
>;
15+
16+
describe('RangeDateField: parseDateFromString', () => {
17+
beforeEach(() => {
18+
jest.clearAllMocks();
19+
mockedParseDateFromString.mockImplementation((str, format, timeZone) => {
20+
return dateTime({input: str, format, timeZone});
21+
});
22+
});
23+
24+
it('should call custom parseDateFromString when provided for range dates', () => {
25+
const customParser = jest
26+
.fn()
27+
.mockReturnValueOnce(dateTime({input: '2024-01-15T00:00:00Z'}))
28+
.mockReturnValueOnce(dateTime({input: '2024-01-20T00:00:00Z'}));
29+
30+
const {result} = renderHook(() =>
31+
useRangeDateFieldState({
32+
format: 'DD.MM.YYYY',
33+
parseDateFromString: customParser,
34+
}),
35+
);
36+
37+
act(() => {
38+
result.current.setValueFromString('15.01.2024 — 20.01.2024');
39+
});
40+
41+
expect(customParser).toHaveBeenCalledTimes(2);
42+
expect(customParser).toHaveBeenNthCalledWith(1, '15.01.2024', 'DD.MM.YYYY', 'default');
43+
expect(customParser).toHaveBeenNthCalledWith(2, '20.01.2024', 'DD.MM.YYYY', 'default');
44+
expect(mockedParseDateFromString).not.toHaveBeenCalled();
45+
});
46+
47+
it('should use default parseDateFromString when parseDateFromString is not provided', () => {
48+
const validStartDate = dateTime({input: '2024-01-15T00:00:00Z'});
49+
const validEndDate = dateTime({input: '2024-01-20T00:00:00Z'});
50+
mockedParseDateFromString
51+
.mockReturnValueOnce(validStartDate)
52+
.mockReturnValueOnce(validEndDate);
53+
54+
const {result} = renderHook(() => useRangeDateFieldState({format: 'DD.MM.YYYY'}));
55+
56+
act(() => {
57+
result.current.setValueFromString('15.01.2024 — 20.01.2024');
58+
});
59+
60+
expect(mockedParseDateFromString).toHaveBeenCalledTimes(2);
61+
expect(mockedParseDateFromString).toHaveBeenNthCalledWith(
62+
1,
63+
'15.01.2024',
64+
'DD.MM.YYYY',
65+
'default',
66+
);
67+
expect(mockedParseDateFromString).toHaveBeenNthCalledWith(
68+
2,
69+
'20.01.2024',
70+
'DD.MM.YYYY',
71+
'default',
72+
);
73+
});
74+
});

src/components/RangeDateField/hooks/useRangeDateFieldState.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -224,8 +224,9 @@ export function useRangeDateFieldState(props: RangeDateFieldStateOptions): Range
224224

225225
function setValueFromString(str: string) {
226226
const list = str.split(delimiter);
227-
const start = parseDateFromString(list?.[0], format, timeZone);
228-
const end = parseDateFromString(list?.[1], format, timeZone);
227+
const parseDate = props.parseDateFromString ?? parseDateFromString;
228+
const start = parseDate(list?.[0], format, timeZone);
229+
const end = parseDate(list?.[1], format, timeZone);
229230
const range = {start, end};
230231
if (range.start.isValid() && range.end.isValid()) {
231232
handleUpdateRange(range);

src/components/types/datePicker.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ export interface DateFieldBase<T = DateTime> extends ValueBase<T | null>, InputB
1919
* @default The timezone of the `value` or `defaultValue` or `placeholderValue`, 'default' otherwise.
2020
*/
2121
timeZone?: string;
22+
/** Custom parser function for parsing pasted date strings. If not provided, the default parser will be used. */
23+
parseDateFromString?: (dateStr: string, format: string, timeZone?: string) => DateTime;
2224
}
2325

2426
export interface PopupTriggerProps<Args extends unknown[] = []> {

0 commit comments

Comments
 (0)