@object-ui/fields 4.0.1 → 4.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +41 -0
- package/dist/index.cjs +3 -3
- package/dist/index.js +574 -574
- package/package.json +7 -7
- package/dist/dist-BoLAKgtw.cjs +0 -1
- package/dist/dist-qtNk7SzD.js +0 -32
- package/dist/packages/fields/src/__tests__/boolean-checkbox.test.d.ts +0 -0
- package/dist/packages/fields/src/__tests__/cell-renderers.test.d.ts +0 -0
- package/dist/packages/fields/src/__tests__/datetime-cell.test.d.ts +0 -0
- package/dist/packages/fields/src/__tests__/datetime-timezone.test.d.ts +0 -0
- package/dist/packages/fields/src/__tests__/extreme-inputs.test.d.ts +0 -0
- package/dist/packages/fields/src/__tests__/form-variants.test.d.ts +0 -0
- package/dist/packages/fields/src/lookup-depends-on.test.d.ts +0 -1
- package/dist/packages/fields/src/new-widgets.test.d.ts +0 -8
- package/dist/packages/fields/src/record-picker.test.d.ts +0 -1
- package/dist/packages/fields/src/remaining-widgets.test.d.ts +0 -1
- package/dist/packages/fields/src/text-rich-widgets.test.d.ts +0 -1
- package/dist/packages/fields/src/visual-widgets.test.d.ts +0 -1
- package/src/__tests__/boolean-checkbox.test.tsx +0 -74
- package/src/__tests__/cell-renderers.test.tsx +0 -1193
- package/src/__tests__/datetime-cell.test.tsx +0 -46
- package/src/__tests__/datetime-timezone.test.tsx +0 -770
- package/src/__tests__/extreme-inputs.test.tsx +0 -567
- package/src/__tests__/form-variants.test.tsx +0 -306
- package/src/lookup-depends-on.test.tsx +0 -130
- package/src/new-widgets.test.tsx +0 -302
- package/src/record-picker.test.tsx +0 -1703
- package/src/remaining-widgets.test.tsx +0 -211
- package/src/text-rich-widgets.test.tsx +0 -104
- package/src/visual-widgets.test.tsx +0 -97
- /package/dist/{ImageCropperDialog-DXLQcwWZ.cjs → ImageCropperDialog-CK1apFXU.cjs} +0 -0
- /package/dist/{ImageCropperDialog-BYRMqcYm.js → ImageCropperDialog-DNvktnyM.js} +0 -0
|
@@ -1,770 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* ObjectUI
|
|
3
|
-
* Copyright (c) 2024-present ObjectStack Inc.
|
|
4
|
-
*
|
|
5
|
-
* This source code is licensed under the MIT license found in the
|
|
6
|
-
* LICENSE file in the root directory of this source tree.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* P3.2 Field Widget Polish - Date/Time Timezone & Locale Formatting
|
|
11
|
-
*
|
|
12
|
-
* Tests date, time, and datetime picker fields for timezone edge cases,
|
|
13
|
-
* locale formatting in readonly mode, and boundary conditions like
|
|
14
|
-
* midnight, end-of-day, leap years, and DST transitions.
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
18
|
-
import React from 'react';
|
|
19
|
-
import { render, screen, fireEvent } from '@testing-library/react';
|
|
20
|
-
import '@testing-library/jest-dom';
|
|
21
|
-
|
|
22
|
-
import { DateField } from '../widgets/DateField';
|
|
23
|
-
import { TimeField } from '../widgets/TimeField';
|
|
24
|
-
import { DateTimeField } from '../widgets/DateTimeField';
|
|
25
|
-
|
|
26
|
-
const noop = vi.fn();
|
|
27
|
-
|
|
28
|
-
describe('P3.2 Date/Time Timezone & Locale Formatting', () => {
|
|
29
|
-
// ---------------------------------------------------------------
|
|
30
|
-
// DateField with various ISO date strings
|
|
31
|
-
// ---------------------------------------------------------------
|
|
32
|
-
describe('DateField ISO date strings', () => {
|
|
33
|
-
it('handles start-of-year date 2024-01-01', () => {
|
|
34
|
-
render(
|
|
35
|
-
<DateField
|
|
36
|
-
value="2024-01-01"
|
|
37
|
-
onChange={noop}
|
|
38
|
-
field={{ type: 'date' } as any}
|
|
39
|
-
/>
|
|
40
|
-
);
|
|
41
|
-
expect(screen.getByDisplayValue('2024-01-01')).toBeInTheDocument();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('handles end-of-year date 2024-12-31', () => {
|
|
45
|
-
render(
|
|
46
|
-
<DateField
|
|
47
|
-
value="2024-12-31"
|
|
48
|
-
onChange={noop}
|
|
49
|
-
field={{ type: 'date' } as any}
|
|
50
|
-
/>
|
|
51
|
-
);
|
|
52
|
-
expect(screen.getByDisplayValue('2024-12-31')).toBeInTheDocument();
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it('handles leap year date 2024-02-29', () => {
|
|
56
|
-
render(
|
|
57
|
-
<DateField
|
|
58
|
-
value="2024-02-29"
|
|
59
|
-
onChange={noop}
|
|
60
|
-
field={{ type: 'date' } as any}
|
|
61
|
-
/>
|
|
62
|
-
);
|
|
63
|
-
expect(screen.getByDisplayValue('2024-02-29')).toBeInTheDocument();
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('fires onChange with new date value', () => {
|
|
67
|
-
const onChange = vi.fn();
|
|
68
|
-
render(
|
|
69
|
-
<DateField
|
|
70
|
-
value="2024-01-01"
|
|
71
|
-
onChange={onChange}
|
|
72
|
-
field={{ type: 'date' } as any}
|
|
73
|
-
/>
|
|
74
|
-
);
|
|
75
|
-
fireEvent.change(screen.getByDisplayValue('2024-01-01'), {
|
|
76
|
-
target: { value: '2024-06-15' },
|
|
77
|
-
});
|
|
78
|
-
expect(onChange).toHaveBeenCalledWith('2024-06-15');
|
|
79
|
-
});
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
// ---------------------------------------------------------------
|
|
83
|
-
// DateField readonly locale formatting
|
|
84
|
-
// ---------------------------------------------------------------
|
|
85
|
-
describe('DateField readonly locale formatting', () => {
|
|
86
|
-
it('displays locale-formatted date for 2024-01-01', () => {
|
|
87
|
-
const { container } = render(
|
|
88
|
-
<DateField
|
|
89
|
-
value="2024-01-01"
|
|
90
|
-
onChange={noop}
|
|
91
|
-
field={{ type: 'date' } as any}
|
|
92
|
-
readonly={true}
|
|
93
|
-
/>
|
|
94
|
-
);
|
|
95
|
-
const expected = new Date('2024-01-01').toLocaleDateString();
|
|
96
|
-
expect(container.textContent).toBe(expected);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it('displays locale-formatted date for 2024-12-31', () => {
|
|
100
|
-
const { container } = render(
|
|
101
|
-
<DateField
|
|
102
|
-
value="2024-12-31"
|
|
103
|
-
onChange={noop}
|
|
104
|
-
field={{ type: 'date' } as any}
|
|
105
|
-
readonly={true}
|
|
106
|
-
/>
|
|
107
|
-
);
|
|
108
|
-
const expected = new Date('2024-12-31').toLocaleDateString();
|
|
109
|
-
expect(container.textContent).toBe(expected);
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('displays locale-formatted date for leap year 2024-02-29', () => {
|
|
113
|
-
const { container } = render(
|
|
114
|
-
<DateField
|
|
115
|
-
value="2024-02-29"
|
|
116
|
-
onChange={noop}
|
|
117
|
-
field={{ type: 'date' } as any}
|
|
118
|
-
readonly={true}
|
|
119
|
-
/>
|
|
120
|
-
);
|
|
121
|
-
const expected = new Date('2024-02-29').toLocaleDateString();
|
|
122
|
-
expect(container.textContent).toBe(expected);
|
|
123
|
-
});
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// ---------------------------------------------------------------
|
|
127
|
-
// TimeField with various time strings
|
|
128
|
-
// ---------------------------------------------------------------
|
|
129
|
-
describe('TimeField time strings', () => {
|
|
130
|
-
it('handles midnight 00:00', () => {
|
|
131
|
-
render(
|
|
132
|
-
<TimeField
|
|
133
|
-
value="00:00"
|
|
134
|
-
onChange={noop}
|
|
135
|
-
field={{ type: 'time' } as any}
|
|
136
|
-
/>
|
|
137
|
-
);
|
|
138
|
-
expect(screen.getByDisplayValue('00:00')).toBeInTheDocument();
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('handles midday 12:30', () => {
|
|
142
|
-
render(
|
|
143
|
-
<TimeField
|
|
144
|
-
value="12:30"
|
|
145
|
-
onChange={noop}
|
|
146
|
-
field={{ type: 'time' } as any}
|
|
147
|
-
/>
|
|
148
|
-
);
|
|
149
|
-
expect(screen.getByDisplayValue('12:30')).toBeInTheDocument();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
it('handles end-of-day 23:59', () => {
|
|
153
|
-
render(
|
|
154
|
-
<TimeField
|
|
155
|
-
value="23:59"
|
|
156
|
-
onChange={noop}
|
|
157
|
-
field={{ type: 'time' } as any}
|
|
158
|
-
/>
|
|
159
|
-
);
|
|
160
|
-
expect(screen.getByDisplayValue('23:59')).toBeInTheDocument();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
it('handles time with seconds 14:30:45', () => {
|
|
164
|
-
render(
|
|
165
|
-
<TimeField
|
|
166
|
-
value="14:30:45"
|
|
167
|
-
onChange={noop}
|
|
168
|
-
field={{ type: 'time' } as any}
|
|
169
|
-
/>
|
|
170
|
-
);
|
|
171
|
-
expect(screen.getByDisplayValue('14:30:45')).toBeInTheDocument();
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
it('fires onChange with new time value', () => {
|
|
175
|
-
const onChange = vi.fn();
|
|
176
|
-
render(
|
|
177
|
-
<TimeField
|
|
178
|
-
value="12:30"
|
|
179
|
-
onChange={onChange}
|
|
180
|
-
field={{ type: 'time' } as any}
|
|
181
|
-
/>
|
|
182
|
-
);
|
|
183
|
-
fireEvent.change(screen.getByDisplayValue('12:30'), {
|
|
184
|
-
target: { value: '09:15' },
|
|
185
|
-
});
|
|
186
|
-
expect(onChange).toHaveBeenCalledWith('09:15');
|
|
187
|
-
});
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
// ---------------------------------------------------------------
|
|
191
|
-
// TimeField readonly display
|
|
192
|
-
// ---------------------------------------------------------------
|
|
193
|
-
describe('TimeField readonly display', () => {
|
|
194
|
-
it('displays time value directly in readonly', () => {
|
|
195
|
-
const { container } = render(
|
|
196
|
-
<TimeField
|
|
197
|
-
value="14:30"
|
|
198
|
-
onChange={noop}
|
|
199
|
-
field={{ type: 'time' } as any}
|
|
200
|
-
readonly={true}
|
|
201
|
-
/>
|
|
202
|
-
);
|
|
203
|
-
expect(container.textContent).toBe('14:30');
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
it('displays midnight in readonly', () => {
|
|
207
|
-
const { container } = render(
|
|
208
|
-
<TimeField
|
|
209
|
-
value="00:00"
|
|
210
|
-
onChange={noop}
|
|
211
|
-
field={{ type: 'time' } as any}
|
|
212
|
-
readonly={true}
|
|
213
|
-
/>
|
|
214
|
-
);
|
|
215
|
-
expect(container.textContent).toBe('00:00');
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
it('displays end-of-day in readonly', () => {
|
|
219
|
-
const { container } = render(
|
|
220
|
-
<TimeField
|
|
221
|
-
value="23:59"
|
|
222
|
-
onChange={noop}
|
|
223
|
-
field={{ type: 'time' } as any}
|
|
224
|
-
readonly={true}
|
|
225
|
-
/>
|
|
226
|
-
);
|
|
227
|
-
expect(container.textContent).toBe('23:59');
|
|
228
|
-
});
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
// ---------------------------------------------------------------
|
|
232
|
-
// DateTimeField with ISO datetime strings
|
|
233
|
-
// ---------------------------------------------------------------
|
|
234
|
-
describe('DateTimeField ISO datetime strings', () => {
|
|
235
|
-
it('handles datetime-local format', () => {
|
|
236
|
-
render(
|
|
237
|
-
<DateTimeField
|
|
238
|
-
value="2024-01-01T00:00"
|
|
239
|
-
onChange={noop}
|
|
240
|
-
field={{ type: 'datetime' } as any}
|
|
241
|
-
/>
|
|
242
|
-
);
|
|
243
|
-
expect(screen.getByDisplayValue('2024-01-01T00:00')).toBeInTheDocument();
|
|
244
|
-
});
|
|
245
|
-
|
|
246
|
-
it('handles datetime with seconds', () => {
|
|
247
|
-
const { container } = render(
|
|
248
|
-
<DateTimeField
|
|
249
|
-
value="2024-06-15T14:30:00"
|
|
250
|
-
onChange={noop}
|
|
251
|
-
field={{ type: 'datetime' } as any}
|
|
252
|
-
/>
|
|
253
|
-
);
|
|
254
|
-
// datetime-local inputs may normalize seconds away
|
|
255
|
-
const input = container.querySelector('input[type="datetime-local"]') as HTMLInputElement;
|
|
256
|
-
expect(input).toBeInTheDocument();
|
|
257
|
-
expect(input.value).toMatch(/^2024-06-15T14:30/);
|
|
258
|
-
});
|
|
259
|
-
|
|
260
|
-
it('handles end-of-year datetime', () => {
|
|
261
|
-
render(
|
|
262
|
-
<DateTimeField
|
|
263
|
-
value="2024-12-31T23:59"
|
|
264
|
-
onChange={noop}
|
|
265
|
-
field={{ type: 'datetime' } as any}
|
|
266
|
-
/>
|
|
267
|
-
);
|
|
268
|
-
expect(screen.getByDisplayValue('2024-12-31T23:59')).toBeInTheDocument();
|
|
269
|
-
});
|
|
270
|
-
|
|
271
|
-
it('fires onChange with new datetime value', () => {
|
|
272
|
-
const onChange = vi.fn();
|
|
273
|
-
render(
|
|
274
|
-
<DateTimeField
|
|
275
|
-
value="2024-01-01T00:00"
|
|
276
|
-
onChange={onChange}
|
|
277
|
-
field={{ type: 'datetime' } as any}
|
|
278
|
-
/>
|
|
279
|
-
);
|
|
280
|
-
fireEvent.change(screen.getByDisplayValue('2024-01-01T00:00'), {
|
|
281
|
-
target: { value: '2024-07-04T12:00' },
|
|
282
|
-
});
|
|
283
|
-
expect(onChange).toHaveBeenCalledWith('2024-07-04T12:00');
|
|
284
|
-
});
|
|
285
|
-
});
|
|
286
|
-
|
|
287
|
-
// ---------------------------------------------------------------
|
|
288
|
-
// DateTimeField readonly locale formatting
|
|
289
|
-
// ---------------------------------------------------------------
|
|
290
|
-
describe('DateTimeField readonly locale formatting', () => {
|
|
291
|
-
it('displays locale-formatted date and time', () => {
|
|
292
|
-
const value = '2024-06-15T14:30:00';
|
|
293
|
-
const { container } = render(
|
|
294
|
-
<DateTimeField
|
|
295
|
-
value={value}
|
|
296
|
-
onChange={noop}
|
|
297
|
-
field={{ type: 'datetime' } as any}
|
|
298
|
-
readonly={true}
|
|
299
|
-
/>
|
|
300
|
-
);
|
|
301
|
-
const date = new Date(value);
|
|
302
|
-
const expected = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
303
|
-
expect(container.textContent).toBe(expected);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
it('displays locale-formatted midnight datetime', () => {
|
|
307
|
-
const value = '2024-01-01T00:00:00';
|
|
308
|
-
const { container } = render(
|
|
309
|
-
<DateTimeField
|
|
310
|
-
value={value}
|
|
311
|
-
onChange={noop}
|
|
312
|
-
field={{ type: 'datetime' } as any}
|
|
313
|
-
readonly={true}
|
|
314
|
-
/>
|
|
315
|
-
);
|
|
316
|
-
const date = new Date(value);
|
|
317
|
-
const expected = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
318
|
-
expect(container.textContent).toBe(expected);
|
|
319
|
-
});
|
|
320
|
-
|
|
321
|
-
it('displays locale-formatted end-of-day datetime', () => {
|
|
322
|
-
const value = '2024-12-31T23:59:59';
|
|
323
|
-
const { container } = render(
|
|
324
|
-
<DateTimeField
|
|
325
|
-
value={value}
|
|
326
|
-
onChange={noop}
|
|
327
|
-
field={{ type: 'datetime' } as any}
|
|
328
|
-
readonly={true}
|
|
329
|
-
/>
|
|
330
|
-
);
|
|
331
|
-
const date = new Date(value);
|
|
332
|
-
const expected = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
333
|
-
expect(container.textContent).toBe(expected);
|
|
334
|
-
});
|
|
335
|
-
});
|
|
336
|
-
|
|
337
|
-
// ---------------------------------------------------------------
|
|
338
|
-
// Empty, null, and undefined values show placeholder "-"
|
|
339
|
-
// ---------------------------------------------------------------
|
|
340
|
-
describe('empty/null/undefined values', () => {
|
|
341
|
-
it('DateField readonly shows "-" for empty string', () => {
|
|
342
|
-
const { container } = render(
|
|
343
|
-
<DateField
|
|
344
|
-
value=""
|
|
345
|
-
onChange={noop}
|
|
346
|
-
field={{ type: 'date' } as any}
|
|
347
|
-
readonly={true}
|
|
348
|
-
/>
|
|
349
|
-
);
|
|
350
|
-
expect(container.textContent).toBe('—');
|
|
351
|
-
});
|
|
352
|
-
|
|
353
|
-
it('DateField readonly shows "-" for null', () => {
|
|
354
|
-
const { container } = render(
|
|
355
|
-
<DateField
|
|
356
|
-
value={null as any}
|
|
357
|
-
onChange={noop}
|
|
358
|
-
field={{ type: 'date' } as any}
|
|
359
|
-
readonly={true}
|
|
360
|
-
/>
|
|
361
|
-
);
|
|
362
|
-
expect(container.textContent).toBe('—');
|
|
363
|
-
});
|
|
364
|
-
|
|
365
|
-
it('DateField readonly shows "-" for undefined', () => {
|
|
366
|
-
const { container } = render(
|
|
367
|
-
<DateField
|
|
368
|
-
value={undefined as any}
|
|
369
|
-
onChange={noop}
|
|
370
|
-
field={{ type: 'date' } as any}
|
|
371
|
-
readonly={true}
|
|
372
|
-
/>
|
|
373
|
-
);
|
|
374
|
-
expect(container.textContent).toBe('—');
|
|
375
|
-
});
|
|
376
|
-
|
|
377
|
-
it('TimeField readonly shows "-" for empty string', () => {
|
|
378
|
-
const { container } = render(
|
|
379
|
-
<TimeField
|
|
380
|
-
value=""
|
|
381
|
-
onChange={noop}
|
|
382
|
-
field={{ type: 'time' } as any}
|
|
383
|
-
readonly={true}
|
|
384
|
-
/>
|
|
385
|
-
);
|
|
386
|
-
expect(container.textContent).toBe('—');
|
|
387
|
-
});
|
|
388
|
-
|
|
389
|
-
it('TimeField readonly shows "-" for null', () => {
|
|
390
|
-
const { container } = render(
|
|
391
|
-
<TimeField
|
|
392
|
-
value={null as any}
|
|
393
|
-
onChange={noop}
|
|
394
|
-
field={{ type: 'time' } as any}
|
|
395
|
-
readonly={true}
|
|
396
|
-
/>
|
|
397
|
-
);
|
|
398
|
-
expect(container.textContent).toBe('—');
|
|
399
|
-
});
|
|
400
|
-
|
|
401
|
-
it('TimeField readonly shows "-" for undefined', () => {
|
|
402
|
-
const { container } = render(
|
|
403
|
-
<TimeField
|
|
404
|
-
value={undefined as any}
|
|
405
|
-
onChange={noop}
|
|
406
|
-
field={{ type: 'time' } as any}
|
|
407
|
-
readonly={true}
|
|
408
|
-
/>
|
|
409
|
-
);
|
|
410
|
-
expect(container.textContent).toBe('—');
|
|
411
|
-
});
|
|
412
|
-
|
|
413
|
-
it('DateTimeField readonly shows "-" for empty string', () => {
|
|
414
|
-
const { container } = render(
|
|
415
|
-
<DateTimeField
|
|
416
|
-
value=""
|
|
417
|
-
onChange={noop}
|
|
418
|
-
field={{ type: 'datetime' } as any}
|
|
419
|
-
readonly={true}
|
|
420
|
-
/>
|
|
421
|
-
);
|
|
422
|
-
expect(container.textContent).toBe('—');
|
|
423
|
-
});
|
|
424
|
-
|
|
425
|
-
it('DateTimeField readonly shows "-" for null', () => {
|
|
426
|
-
const { container } = render(
|
|
427
|
-
<DateTimeField
|
|
428
|
-
value={null as any}
|
|
429
|
-
onChange={noop}
|
|
430
|
-
field={{ type: 'datetime' } as any}
|
|
431
|
-
readonly={true}
|
|
432
|
-
/>
|
|
433
|
-
);
|
|
434
|
-
expect(container.textContent).toBe('—');
|
|
435
|
-
});
|
|
436
|
-
|
|
437
|
-
it('DateTimeField readonly shows "-" for undefined', () => {
|
|
438
|
-
const { container } = render(
|
|
439
|
-
<DateTimeField
|
|
440
|
-
value={undefined as any}
|
|
441
|
-
onChange={noop}
|
|
442
|
-
field={{ type: 'datetime' } as any}
|
|
443
|
-
readonly={true}
|
|
444
|
-
/>
|
|
445
|
-
);
|
|
446
|
-
expect(container.textContent).toBe('—');
|
|
447
|
-
});
|
|
448
|
-
|
|
449
|
-
it('DateField editable uses empty string for null value', () => {
|
|
450
|
-
render(
|
|
451
|
-
<DateField
|
|
452
|
-
value={null as any}
|
|
453
|
-
onChange={noop}
|
|
454
|
-
field={{ type: 'date' } as any}
|
|
455
|
-
/>
|
|
456
|
-
);
|
|
457
|
-
const input = screen.getByDisplayValue('');
|
|
458
|
-
expect(input).toBeInTheDocument();
|
|
459
|
-
});
|
|
460
|
-
|
|
461
|
-
it('TimeField editable uses empty string for null value', () => {
|
|
462
|
-
render(
|
|
463
|
-
<TimeField
|
|
464
|
-
value={null as any}
|
|
465
|
-
onChange={noop}
|
|
466
|
-
field={{ type: 'time' } as any}
|
|
467
|
-
/>
|
|
468
|
-
);
|
|
469
|
-
const input = screen.getByDisplayValue('');
|
|
470
|
-
expect(input).toBeInTheDocument();
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
it('DateTimeField editable uses empty string for null value', () => {
|
|
474
|
-
render(
|
|
475
|
-
<DateTimeField
|
|
476
|
-
value={null as any}
|
|
477
|
-
onChange={noop}
|
|
478
|
-
field={{ type: 'datetime' } as any}
|
|
479
|
-
/>
|
|
480
|
-
);
|
|
481
|
-
const input = screen.getByDisplayValue('');
|
|
482
|
-
expect(input).toBeInTheDocument();
|
|
483
|
-
});
|
|
484
|
-
});
|
|
485
|
-
|
|
486
|
-
// ---------------------------------------------------------------
|
|
487
|
-
// Invalid date strings handled gracefully
|
|
488
|
-
// ---------------------------------------------------------------
|
|
489
|
-
describe('invalid date strings', () => {
|
|
490
|
-
it('DateField readonly handles invalid date string', () => {
|
|
491
|
-
const { container } = render(
|
|
492
|
-
<DateField
|
|
493
|
-
value="not-a-date"
|
|
494
|
-
onChange={noop}
|
|
495
|
-
field={{ type: 'date' } as any}
|
|
496
|
-
readonly={true}
|
|
497
|
-
/>
|
|
498
|
-
);
|
|
499
|
-
// new Date('not-a-date').toLocaleDateString() produces 'Invalid Date'
|
|
500
|
-
const expected = new Date('not-a-date').toLocaleDateString();
|
|
501
|
-
expect(container.textContent).toBe(expected);
|
|
502
|
-
});
|
|
503
|
-
|
|
504
|
-
it('DateTimeField readonly handles invalid datetime string', () => {
|
|
505
|
-
const { container } = render(
|
|
506
|
-
<DateTimeField
|
|
507
|
-
value="garbage-datetime"
|
|
508
|
-
onChange={noop}
|
|
509
|
-
field={{ type: 'datetime' } as any}
|
|
510
|
-
readonly={true}
|
|
511
|
-
/>
|
|
512
|
-
);
|
|
513
|
-
const date = new Date('garbage-datetime');
|
|
514
|
-
const expected = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
515
|
-
expect(container.textContent).toBe(expected);
|
|
516
|
-
});
|
|
517
|
-
|
|
518
|
-
it('DateField editable renders input for invalid value without crashing', () => {
|
|
519
|
-
const { container } = render(
|
|
520
|
-
<DateField
|
|
521
|
-
value="not-a-date"
|
|
522
|
-
onChange={noop}
|
|
523
|
-
field={{ type: 'date' } as any}
|
|
524
|
-
/>
|
|
525
|
-
);
|
|
526
|
-
const input = container.querySelector('input[type="date"]');
|
|
527
|
-
expect(input).toBeInTheDocument();
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
it('TimeField editable renders input for invalid value without crashing', () => {
|
|
531
|
-
const { container } = render(
|
|
532
|
-
<TimeField
|
|
533
|
-
value="invalid-time"
|
|
534
|
-
onChange={noop}
|
|
535
|
-
field={{ type: 'time' } as any}
|
|
536
|
-
/>
|
|
537
|
-
);
|
|
538
|
-
const input = container.querySelector('input[type="time"]');
|
|
539
|
-
expect(input).toBeInTheDocument();
|
|
540
|
-
});
|
|
541
|
-
|
|
542
|
-
it('DateTimeField editable renders input for invalid value without crashing', () => {
|
|
543
|
-
const { container } = render(
|
|
544
|
-
<DateTimeField
|
|
545
|
-
value="invalid-datetime"
|
|
546
|
-
onChange={noop}
|
|
547
|
-
field={{ type: 'datetime' } as any}
|
|
548
|
-
/>
|
|
549
|
-
);
|
|
550
|
-
const input = container.querySelector('input[type="datetime-local"]');
|
|
551
|
-
expect(input).toBeInTheDocument();
|
|
552
|
-
});
|
|
553
|
-
});
|
|
554
|
-
|
|
555
|
-
// ---------------------------------------------------------------
|
|
556
|
-
// Midnight and end-of-day edge cases
|
|
557
|
-
// ---------------------------------------------------------------
|
|
558
|
-
describe('midnight and end-of-day edge cases', () => {
|
|
559
|
-
it('DateTimeField handles midnight correctly', () => {
|
|
560
|
-
const value = '2024-03-15T00:00:00';
|
|
561
|
-
const { container } = render(
|
|
562
|
-
<DateTimeField
|
|
563
|
-
value={value}
|
|
564
|
-
onChange={noop}
|
|
565
|
-
field={{ type: 'datetime' } as any}
|
|
566
|
-
readonly={true}
|
|
567
|
-
/>
|
|
568
|
-
);
|
|
569
|
-
const date = new Date(value);
|
|
570
|
-
const expected = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
571
|
-
expect(container.textContent).toBe(expected);
|
|
572
|
-
});
|
|
573
|
-
|
|
574
|
-
it('DateTimeField handles 23:59:59 correctly', () => {
|
|
575
|
-
const value = '2024-03-15T23:59:59';
|
|
576
|
-
const { container } = render(
|
|
577
|
-
<DateTimeField
|
|
578
|
-
value={value}
|
|
579
|
-
onChange={noop}
|
|
580
|
-
field={{ type: 'datetime' } as any}
|
|
581
|
-
readonly={true}
|
|
582
|
-
/>
|
|
583
|
-
);
|
|
584
|
-
const date = new Date(value);
|
|
585
|
-
const expected = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
586
|
-
expect(container.textContent).toBe(expected);
|
|
587
|
-
});
|
|
588
|
-
|
|
589
|
-
it('TimeField handles midnight 00:00 in editable mode', () => {
|
|
590
|
-
render(
|
|
591
|
-
<TimeField
|
|
592
|
-
value="00:00"
|
|
593
|
-
onChange={noop}
|
|
594
|
-
field={{ type: 'time' } as any}
|
|
595
|
-
/>
|
|
596
|
-
);
|
|
597
|
-
expect(screen.getByDisplayValue('00:00')).toBeInTheDocument();
|
|
598
|
-
});
|
|
599
|
-
|
|
600
|
-
it('TimeField handles one-second-to-midnight 23:59:59', () => {
|
|
601
|
-
render(
|
|
602
|
-
<TimeField
|
|
603
|
-
value="23:59:59"
|
|
604
|
-
onChange={noop}
|
|
605
|
-
field={{ type: 'time' } as any}
|
|
606
|
-
/>
|
|
607
|
-
);
|
|
608
|
-
expect(screen.getByDisplayValue('23:59:59')).toBeInTheDocument();
|
|
609
|
-
});
|
|
610
|
-
|
|
611
|
-
it('DateField handles New Year boundary', () => {
|
|
612
|
-
const { container } = render(
|
|
613
|
-
<DateField
|
|
614
|
-
value="2024-01-01"
|
|
615
|
-
onChange={noop}
|
|
616
|
-
field={{ type: 'date' } as any}
|
|
617
|
-
readonly={true}
|
|
618
|
-
/>
|
|
619
|
-
);
|
|
620
|
-
const expected = new Date('2024-01-01').toLocaleDateString();
|
|
621
|
-
expect(container.textContent).toBe(expected);
|
|
622
|
-
});
|
|
623
|
-
});
|
|
624
|
-
|
|
625
|
-
// ---------------------------------------------------------------
|
|
626
|
-
// DST boundary dates
|
|
627
|
-
// ---------------------------------------------------------------
|
|
628
|
-
describe('DST boundary dates', () => {
|
|
629
|
-
it('DateField handles US spring-forward date (March 10, 2024)', () => {
|
|
630
|
-
render(
|
|
631
|
-
<DateField
|
|
632
|
-
value="2024-03-10"
|
|
633
|
-
onChange={noop}
|
|
634
|
-
field={{ type: 'date' } as any}
|
|
635
|
-
/>
|
|
636
|
-
);
|
|
637
|
-
expect(screen.getByDisplayValue('2024-03-10')).toBeInTheDocument();
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
it('DateField readonly handles US spring-forward date', () => {
|
|
641
|
-
const { container } = render(
|
|
642
|
-
<DateField
|
|
643
|
-
value="2024-03-10"
|
|
644
|
-
onChange={noop}
|
|
645
|
-
field={{ type: 'date' } as any}
|
|
646
|
-
readonly={true}
|
|
647
|
-
/>
|
|
648
|
-
);
|
|
649
|
-
const expected = new Date('2024-03-10').toLocaleDateString();
|
|
650
|
-
expect(container.textContent).toBe(expected);
|
|
651
|
-
});
|
|
652
|
-
|
|
653
|
-
it('DateField handles US fall-back date (November 3, 2024)', () => {
|
|
654
|
-
render(
|
|
655
|
-
<DateField
|
|
656
|
-
value="2024-11-03"
|
|
657
|
-
onChange={noop}
|
|
658
|
-
field={{ type: 'date' } as any}
|
|
659
|
-
/>
|
|
660
|
-
);
|
|
661
|
-
expect(screen.getByDisplayValue('2024-11-03')).toBeInTheDocument();
|
|
662
|
-
});
|
|
663
|
-
|
|
664
|
-
it('DateField readonly handles US fall-back date', () => {
|
|
665
|
-
const { container } = render(
|
|
666
|
-
<DateField
|
|
667
|
-
value="2024-11-03"
|
|
668
|
-
onChange={noop}
|
|
669
|
-
field={{ type: 'date' } as any}
|
|
670
|
-
readonly={true}
|
|
671
|
-
/>
|
|
672
|
-
);
|
|
673
|
-
const expected = new Date('2024-11-03').toLocaleDateString();
|
|
674
|
-
expect(container.textContent).toBe(expected);
|
|
675
|
-
});
|
|
676
|
-
|
|
677
|
-
it('DateTimeField handles spring-forward at 2:00 AM', () => {
|
|
678
|
-
const value = '2024-03-10T02:00:00';
|
|
679
|
-
const { container } = render(
|
|
680
|
-
<DateTimeField
|
|
681
|
-
value={value}
|
|
682
|
-
onChange={noop}
|
|
683
|
-
field={{ type: 'datetime' } as any}
|
|
684
|
-
readonly={true}
|
|
685
|
-
/>
|
|
686
|
-
);
|
|
687
|
-
const date = new Date(value);
|
|
688
|
-
const expected = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
689
|
-
expect(container.textContent).toBe(expected);
|
|
690
|
-
});
|
|
691
|
-
|
|
692
|
-
it('DateTimeField handles fall-back at 1:00 AM', () => {
|
|
693
|
-
const value = '2024-11-03T01:00:00';
|
|
694
|
-
const { container } = render(
|
|
695
|
-
<DateTimeField
|
|
696
|
-
value={value}
|
|
697
|
-
onChange={noop}
|
|
698
|
-
field={{ type: 'datetime' } as any}
|
|
699
|
-
readonly={true}
|
|
700
|
-
/>
|
|
701
|
-
);
|
|
702
|
-
const date = new Date(value);
|
|
703
|
-
const expected = `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
|
|
704
|
-
expect(container.textContent).toBe(expected);
|
|
705
|
-
});
|
|
706
|
-
|
|
707
|
-
it('DateTimeField editable handles spring-forward datetime', () => {
|
|
708
|
-
render(
|
|
709
|
-
<DateTimeField
|
|
710
|
-
value="2024-03-10T02:30"
|
|
711
|
-
onChange={noop}
|
|
712
|
-
field={{ type: 'datetime' } as any}
|
|
713
|
-
/>
|
|
714
|
-
);
|
|
715
|
-
expect(screen.getByDisplayValue('2024-03-10T02:30')).toBeInTheDocument();
|
|
716
|
-
});
|
|
717
|
-
|
|
718
|
-
it('DateTimeField editable handles fall-back datetime', () => {
|
|
719
|
-
render(
|
|
720
|
-
<DateTimeField
|
|
721
|
-
value="2024-11-03T01:30"
|
|
722
|
-
onChange={noop}
|
|
723
|
-
field={{ type: 'datetime' } as any}
|
|
724
|
-
/>
|
|
725
|
-
);
|
|
726
|
-
expect(screen.getByDisplayValue('2024-11-03T01:30')).toBeInTheDocument();
|
|
727
|
-
});
|
|
728
|
-
});
|
|
729
|
-
|
|
730
|
-
// ---------------------------------------------------------------
|
|
731
|
-
// Disabled state
|
|
732
|
-
// ---------------------------------------------------------------
|
|
733
|
-
describe('disabled state', () => {
|
|
734
|
-
it('DateField is disabled when disabled prop is set', () => {
|
|
735
|
-
render(
|
|
736
|
-
<DateField
|
|
737
|
-
value="2024-01-01"
|
|
738
|
-
onChange={noop}
|
|
739
|
-
field={{ type: 'date' } as any}
|
|
740
|
-
disabled={true}
|
|
741
|
-
/>
|
|
742
|
-
);
|
|
743
|
-
expect(screen.getByDisplayValue('2024-01-01')).toBeDisabled();
|
|
744
|
-
});
|
|
745
|
-
|
|
746
|
-
it('TimeField is disabled when disabled prop is set', () => {
|
|
747
|
-
render(
|
|
748
|
-
<TimeField
|
|
749
|
-
value="12:30"
|
|
750
|
-
onChange={noop}
|
|
751
|
-
field={{ type: 'time' } as any}
|
|
752
|
-
disabled={true}
|
|
753
|
-
/>
|
|
754
|
-
);
|
|
755
|
-
expect(screen.getByDisplayValue('12:30')).toBeDisabled();
|
|
756
|
-
});
|
|
757
|
-
|
|
758
|
-
it('DateTimeField is disabled when disabled prop is set', () => {
|
|
759
|
-
render(
|
|
760
|
-
<DateTimeField
|
|
761
|
-
value="2024-01-01T00:00"
|
|
762
|
-
onChange={noop}
|
|
763
|
-
field={{ type: 'datetime' } as any}
|
|
764
|
-
disabled={true}
|
|
765
|
-
/>
|
|
766
|
-
);
|
|
767
|
-
expect(screen.getByDisplayValue('2024-01-01T00:00')).toBeDisabled();
|
|
768
|
-
});
|
|
769
|
-
});
|
|
770
|
-
});
|