Skip to content

Commit 7b52591

Browse files
authored
Merge pull request marmelab#10299 from marmelab/fix-date-input-timezone
Fix DateInput messes up dates in some timezones
2 parents b53b8b5 + 2049767 commit 7b52591

File tree

4 files changed

+273
-208
lines changed

4 files changed

+273
-208
lines changed

packages/ra-core/src/form/getSimpleValidationResolver.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ const isRaTranslationObj = (obj: object) =>
101101
Object.keys(obj).includes('message') && Object.keys(obj).includes('args');
102102

103103
const isEmptyObject = (obj: object) =>
104-
Object.getOwnPropertyNames(obj).length === 0;
104+
obj == null || Object.getOwnPropertyNames(obj).length === 0;
105105

106106
export type ValidateForm = (
107107
data: FieldValues

packages/ra-ui-materialui/src/input/DateInput.spec.tsx

Lines changed: 100 additions & 128 deletions
Original file line numberDiff line numberDiff line change
@@ -8,50 +8,35 @@ import { useFormState } from 'react-hook-form';
88
import { AdminContext } from '../AdminContext';
99
import { SimpleForm } from '../form';
1010
import { DateInput } from './DateInput';
11+
import { Basic, Parse } from './DateInput.stories';
1112

1213
describe('<DateInput />', () => {
1314
const defaultProps = {
1415
source: 'publishedAt',
1516
};
1617

1718
it('should render a date input', () => {
18-
render(
19-
<AdminContext dataProvider={testDataProvider()}>
20-
<ResourceContextProvider value="posts">
21-
<SimpleForm onSubmit={jest.fn()}>
22-
<DateInput {...defaultProps} />
23-
</SimpleForm>
24-
</ResourceContextProvider>
25-
</AdminContext>
26-
);
27-
const input = screen.getByLabelText(
28-
'resources.posts.fields.publishedAt'
29-
) as HTMLInputElement;
19+
render(<Basic />);
20+
const input = screen.getByLabelText('Published at') as HTMLInputElement;
3021
expect(input.type).toBe('date');
3122
});
3223

3324
it('should accept a date string as value', async () => {
3425
let onSubmit = jest.fn();
3526
render(
36-
<AdminContext dataProvider={testDataProvider()}>
37-
<ResourceContextProvider value="posts">
38-
<SimpleForm
39-
onSubmit={onSubmit}
40-
defaultValues={{ publishedAt: '2021-09-11' }}
41-
>
42-
<DateInput {...defaultProps} />
43-
</SimpleForm>
44-
</ResourceContextProvider>
45-
</AdminContext>
27+
<Basic
28+
simpleFormProps={{
29+
onSubmit,
30+
defaultValues: { publishedAt: '2021-09-11' },
31+
}}
32+
/>
4633
);
47-
const input = screen.getByLabelText(
48-
'resources.posts.fields.publishedAt'
49-
) as HTMLInputElement;
34+
const input = screen.getByLabelText('Published at') as HTMLInputElement;
5035
expect(input.value).toBe('2021-09-11');
5136
fireEvent.change(input, {
5237
target: { value: '2021-10-22' },
5338
});
54-
fireEvent.click(screen.getByLabelText('ra.action.save'));
39+
fireEvent.click(screen.getByLabelText('Save'));
5540
await waitFor(() => {
5641
expect(onSubmit).toHaveBeenCalledWith(
5742
{
@@ -65,27 +50,19 @@ describe('<DateInput />', () => {
6550
it('should accept a date time string as value', async () => {
6651
let onSubmit = jest.fn();
6752
render(
68-
<AdminContext dataProvider={testDataProvider()}>
69-
<ResourceContextProvider value="posts">
70-
<SimpleForm
71-
onSubmit={onSubmit}
72-
defaultValues={{
73-
publishedAt: '2021-09-11T06:51:17.772Z',
74-
}}
75-
>
76-
<DateInput {...defaultProps} />
77-
</SimpleForm>
78-
</ResourceContextProvider>
79-
</AdminContext>
53+
<Basic
54+
simpleFormProps={{
55+
onSubmit,
56+
defaultValues: { publishedAt: '2021-09-11T06:51:17.772Z' },
57+
}}
58+
/>
8059
);
81-
const input = screen.getByLabelText(
82-
'resources.posts.fields.publishedAt'
83-
) as HTMLInputElement;
60+
const input = screen.getByLabelText('Published at') as HTMLInputElement;
8461
expect(input.value).toBe('2021-09-11');
8562
fireEvent.change(input, {
8663
target: { value: '2021-10-22' },
8764
});
88-
fireEvent.click(screen.getByLabelText('ra.action.save'));
65+
fireEvent.click(screen.getByLabelText('Save'));
8966
await waitFor(() => {
9067
expect(onSubmit).toHaveBeenCalledWith(
9168
{
@@ -99,25 +76,19 @@ describe('<DateInput />', () => {
9976
it('should accept a date object as value', async () => {
10077
let onSubmit = jest.fn();
10178
render(
102-
<AdminContext dataProvider={testDataProvider()}>
103-
<ResourceContextProvider value="posts">
104-
<SimpleForm
105-
onSubmit={onSubmit}
106-
defaultValues={{ publishedAt: new Date('2021-09-11') }}
107-
>
108-
<DateInput {...defaultProps} />
109-
</SimpleForm>
110-
</ResourceContextProvider>
111-
</AdminContext>
79+
<Basic
80+
simpleFormProps={{
81+
onSubmit,
82+
defaultValues: { publishedAt: new Date('2021-09-11') },
83+
}}
84+
/>
11285
);
113-
const input = screen.getByLabelText(
114-
'resources.posts.fields.publishedAt'
115-
) as HTMLInputElement;
86+
const input = screen.getByLabelText('Published at') as HTMLInputElement;
11687
expect(input.value).toBe('2021-09-11');
11788
fireEvent.change(input, {
11889
target: { value: '2021-10-22' },
11990
});
120-
fireEvent.click(screen.getByLabelText('ra.action.save'));
91+
fireEvent.click(screen.getByLabelText('Save'));
12192
await waitFor(() => {
12293
expect(onSubmit).toHaveBeenCalledWith(
12394
{
@@ -128,31 +99,59 @@ describe('<DateInput />', () => {
12899
});
129100
});
130101

102+
describe('TimeZones', () => {
103+
it.each([
104+
'2021-09-11T20:46:20.000+02:00',
105+
'2021-09-11 20:46:20.000+02:00',
106+
'2021-09-11T20:46:20.000-04:00',
107+
'2021-09-11 20:46:20.000-04:00',
108+
'2021-09-11T20:46:20.000Z',
109+
'2021-09-11 20:46:20.000Z',
110+
])('should accept a value with timezone %s', async publishedAt => {
111+
let onSubmit = jest.fn();
112+
render(
113+
<Basic
114+
simpleFormProps={{
115+
onSubmit,
116+
defaultValues: { publishedAt },
117+
}}
118+
/>
119+
);
120+
const input = screen.getByLabelText(
121+
'Published at'
122+
) as HTMLInputElement;
123+
expect(input.value).toBe('2021-09-11');
124+
fireEvent.change(input, {
125+
target: { value: '2021-10-22' },
126+
});
127+
fireEvent.click(screen.getByLabelText('Save'));
128+
await waitFor(() => {
129+
expect(onSubmit).toHaveBeenCalledWith(
130+
{
131+
publishedAt: '2021-10-22',
132+
},
133+
expect.anything()
134+
);
135+
});
136+
});
137+
});
138+
131139
it('should accept a parse function', async () => {
132140
const onSubmit = jest.fn();
133141
render(
134-
<AdminContext dataProvider={testDataProvider()}>
135-
<ResourceContextProvider value="posts">
136-
<SimpleForm
137-
onSubmit={onSubmit}
138-
defaultValues={{ publishedAt: new Date('2021-09-11') }}
139-
>
140-
<DateInput
141-
{...defaultProps}
142-
parse={val => new Date(val)}
143-
/>
144-
</SimpleForm>
145-
</ResourceContextProvider>
146-
</AdminContext>
142+
<Parse
143+
simpleFormProps={{
144+
onSubmit,
145+
defaultValues: { publishedAt: new Date('2021-09-11') },
146+
}}
147+
/>
147148
);
148-
const input = screen.getByLabelText(
149-
'resources.posts.fields.publishedAt'
150-
) as HTMLInputElement;
149+
const input = screen.getByLabelText('Published at') as HTMLInputElement;
151150
expect(input.value).toBe('2021-09-11');
152151
fireEvent.change(input, {
153152
target: { value: '2021-10-22' },
154153
});
155-
fireEvent.click(screen.getByLabelText('ra.action.save'));
154+
fireEvent.click(screen.getByLabelText('Save'));
156155
await waitFor(() => {
157156
expect(onSubmit).toHaveBeenCalledWith(
158157
{
@@ -166,26 +165,23 @@ describe('<DateInput />', () => {
166165
it('should accept a parse function returning null', async () => {
167166
const onSubmit = jest.fn();
168167
render(
169-
<AdminContext dataProvider={testDataProvider()}>
170-
<ResourceContextProvider value="posts">
171-
<SimpleForm
172-
onSubmit={onSubmit}
173-
defaultValues={{ publishedAt: new Date('2021-09-11') }}
174-
>
175-
<DateInput {...defaultProps} parse={() => null} />
176-
</SimpleForm>
177-
</ResourceContextProvider>
178-
</AdminContext>
168+
<Basic
169+
simpleFormProps={{
170+
onSubmit,
171+
defaultValues: { publishedAt: new Date('2021-09-11') },
172+
}}
173+
dateInputProps={{
174+
parse: () => null,
175+
}}
176+
/>
179177
);
180-
const input = screen.getByLabelText(
181-
'resources.posts.fields.publishedAt'
182-
) as HTMLInputElement;
178+
const input = screen.getByLabelText('Published at') as HTMLInputElement;
183179
expect(input.value).toBe('2021-09-11');
184180
fireEvent.change(input, {
185181
target: { value: '' },
186182
});
187183
fireEvent.blur(input);
188-
fireEvent.click(screen.getByLabelText('ra.action.save'));
184+
fireEvent.click(screen.getByLabelText('Save'));
189185
await waitFor(() => {
190186
expect(onSubmit).toHaveBeenCalledWith(
191187
{
@@ -226,26 +222,20 @@ describe('<DateInput />', () => {
226222
it('should return null when date is empty', async () => {
227223
const onSubmit = jest.fn();
228224
render(
229-
<AdminContext dataProvider={testDataProvider()}>
230-
<ResourceContextProvider value="posts">
231-
<SimpleForm
232-
onSubmit={onSubmit}
233-
defaultValues={{ publishedAt: new Date('2021-09-11') }}
234-
>
235-
<DateInput {...defaultProps} />
236-
</SimpleForm>
237-
</ResourceContextProvider>
238-
</AdminContext>
225+
<Basic
226+
simpleFormProps={{
227+
onSubmit,
228+
defaultValues: { publishedAt: new Date('2021-09-11') },
229+
}}
230+
/>
239231
);
240-
const input = screen.getByLabelText(
241-
'resources.posts.fields.publishedAt'
242-
) as HTMLInputElement;
232+
const input = screen.getByLabelText('Published at') as HTMLInputElement;
243233
expect(input.value).toBe('2021-09-11');
244234
fireEvent.change(input, {
245235
target: { value: '' },
246236
});
247237
fireEvent.blur(input);
248-
fireEvent.click(screen.getByLabelText('ra.action.save'));
238+
fireEvent.click(screen.getByLabelText('Save'));
249239
await waitFor(() => {
250240
expect(onSubmit).toHaveBeenCalledWith(
251241
{
@@ -258,39 +248,21 @@ describe('<DateInput />', () => {
258248

259249
describe('error message', () => {
260250
it('should not be displayed if field is pristine', () => {
261-
render(
262-
<AdminContext dataProvider={testDataProvider()}>
263-
<ResourceContextProvider value="posts">
264-
<SimpleForm onSubmit={jest.fn()}>
265-
<DateInput
266-
{...defaultProps}
267-
validate={required()}
268-
/>
269-
</SimpleForm>
270-
</ResourceContextProvider>
271-
</AdminContext>
272-
);
273-
expect(screen.queryByText('ra.validation.required')).toBeNull();
251+
render(<Basic dateInputProps={{ validate: required() }} />);
252+
expect(screen.queryByText('Required')).toBeNull();
274253
});
275254

276255
it('should be displayed if field has been touched and is invalid', async () => {
277256
render(
278-
<AdminContext dataProvider={testDataProvider()}>
279-
<ResourceContextProvider value="posts">
280-
<SimpleForm onSubmit={jest.fn()} mode="onBlur">
281-
<DateInput
282-
{...defaultProps}
283-
validate={required()}
284-
/>
285-
</SimpleForm>
286-
</ResourceContextProvider>
287-
</AdminContext>
288-
);
289-
const input = screen.getByLabelText(
290-
'resources.posts.fields.publishedAt *'
257+
<Basic
258+
simpleFormProps={{ mode: 'onBlur' }}
259+
dateInputProps={{ validate: required() }}
260+
/>
291261
);
262+
263+
const input = screen.getByLabelText('Published at *');
292264
fireEvent.blur(input);
293-
await screen.findByText('ra.validation.required');
265+
await screen.findByText('Required');
294266
});
295267
});
296268
});

0 commit comments

Comments
 (0)