@nocobase/client-v2 2.2.0-beta.3 → 2.2.0-beta.5

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.
Files changed (27) hide show
  1. package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
  2. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +1 -0
  3. package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
  4. package/es/index.mjs +110 -90
  5. package/lib/index.js +89 -69
  6. package/package.json +7 -7
  7. package/src/flow/actions/dateTimeFormat.tsx +42 -28
  8. package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
  9. package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
  10. package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
  11. package/src/flow/flows/editMarkdownFlow.tsx +1 -1
  12. package/src/flow/index.ts +1 -1
  13. package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
  14. package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
  15. package/src/flow/models/blocks/table/TableColumnModel.tsx +36 -3
  16. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +159 -1
  17. package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +16 -4
  18. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +24 -1
  19. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +12 -0
  20. package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
  21. package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
  22. package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
  23. package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
  24. package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
  25. package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
  26. package/src/flow/utils/__tests__/dateTimeFormat.test.ts +258 -0
  27. package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
@@ -231,6 +231,20 @@ export function getLatestSubTableRowRecord(form: any, fieldIndex: unknown, fallb
231
231
  return typeof latestRecord === 'undefined' ? fallbackRecord : latestRecord;
232
232
  }
233
233
 
234
+ export function buildSubTableColumnNamePath(
235
+ fieldPath: Array<string | number>,
236
+ rowIdx: number,
237
+ namePath: string | number,
238
+ rowFieldIndex: unknown,
239
+ ): Array<string | number> {
240
+ const currentRowPath = buildRowPathFromFieldIndex(rowFieldIndex);
241
+ if (currentRowPath?.length) {
242
+ return [...currentRowPath, namePath];
243
+ }
244
+
245
+ return [...fieldPath, rowIdx, namePath];
246
+ }
247
+
234
248
  function shouldCommitImmediately(value: any) {
235
249
  if (Array.isArray(value)) {
236
250
  return true;
@@ -372,9 +386,18 @@ const MemoCell: React.FC<CellProps> = React.memo(
372
386
  {parent.mapSubModels('field', (action: FieldModel) => {
373
387
  const fieldPath = action.context.fieldPath.split('.');
374
388
  const namePath = fieldPath.pop();
389
+ if (typeof namePath === 'undefined') {
390
+ return null;
391
+ }
392
+ const fieldNamePath = buildSubTableColumnNamePath(fieldPath, rowIdx, namePath, rowFork?.context?.fieldIndex);
393
+ const formItemName = buildDynamicNamePath([...fieldPath, rowIdx, namePath], parentFieldIndex);
375
394
 
376
395
  const fork: any = action.createFork({}, `${id}`);
377
396
  fork.context.defineProperty('currentObject', { get: () => record });
397
+ fork.context.defineProperty('fieldPathArray', {
398
+ get: () => fieldNamePath,
399
+ cache: false,
400
+ });
378
401
  if (rowFork) {
379
402
  const itemOptions = rowFork.context.getPropertyOptions?.('item');
380
403
  const { value: _value, ...itemOptionsWithoutValue } = (itemOptions || {}) as any;
@@ -415,7 +438,7 @@ const MemoCell: React.FC<CellProps> = React.memo(
415
438
  <FormItem
416
439
  {...parent.props}
417
440
  key={id}
418
- name={buildDynamicNamePath([...fieldPath, rowIdx, namePath], parentFieldIndex)}
441
+ name={formItemName}
419
442
  style={{ marginBottom: 0 }}
420
443
  showLabel={false}
421
444
  disabled={
@@ -15,6 +15,7 @@ import {
15
15
  SubTableColumnModel,
16
16
  getLatestSubTableRowRecord,
17
17
  buildRowPathFromFieldIndex,
18
+ buildSubTableColumnNamePath,
18
19
  isSubTableColumnConfiguredReadPretty,
19
20
  getSubTableColumnTitleField,
20
21
  getSubTableColumnReadPrettyFieldProps,
@@ -47,6 +48,17 @@ describe('SubTableColumnModel row record helpers', () => {
47
48
  expect(buildRowPathFromFieldIndex(['users:1', 'roles:2'])).toEqual(['users', 1, 'roles', 2]);
48
49
  });
49
50
 
51
+ it('builds absolute form item paths for nested sub-table columns', () => {
52
+ expect(buildSubTableColumnNamePath(['roles'], 0, 'name', ['roles:0'])).toEqual(['roles', 0, 'name']);
53
+ expect(buildSubTableColumnNamePath(['orders', 'lines'], 1, 'product', ['orders:0', 'lines:1'])).toEqual([
54
+ 'orders',
55
+ 0,
56
+ 'lines',
57
+ 1,
58
+ 'product',
59
+ ]);
60
+ });
61
+
50
62
  it('prefers the latest row value from form over the fallback record', () => {
51
63
  const form = {
52
64
  getFieldValue: vi.fn((path: any) => {
@@ -28,6 +28,7 @@ import {
28
28
  normalizeRecordPickerValue,
29
29
  shouldClearRecordPickerValueOnMultipleChange,
30
30
  } from '../RecordPickerFieldModel';
31
+ import { getAssociationHydrationNamePath, getAssociationHydrationSetterContext } from '../RecordSelectFieldModel';
31
32
 
32
33
  function createMockCollection() {
33
34
  return {
@@ -93,6 +94,28 @@ describe('RecordPickerFieldModel item context', () => {
93
94
  expect(shouldClearRecordPickerValueOnMultipleChange({ type: 'belongsTo' }, true, false)).toBe(false);
94
95
  });
95
96
 
97
+ it('uses the current field context for association hydration writes', () => {
98
+ const setFormValue = vi.fn();
99
+ const blockSetFormValue = vi.fn();
100
+ const model = {
101
+ props: {
102
+ name: 'orders',
103
+ },
104
+ context: {
105
+ fieldPathArray: ['orders', 0, 'lines', 1, 'product'],
106
+ setFormValue,
107
+ blockModel: {
108
+ context: {
109
+ setFormValue: blockSetFormValue,
110
+ },
111
+ },
112
+ },
113
+ };
114
+
115
+ expect(getAssociationHydrationNamePath(model)).toEqual(['orders', 0, 'lines', 1, 'product']);
116
+ expect(getAssociationHydrationSetterContext(model)).toBe(model.context);
117
+ });
118
+
96
119
  it('itemChain helpers: createParentItemAccessorsFromInputArgs works', () => {
97
120
  const inputArgs = {
98
121
  parentItem: { value: { id: 1 } },
@@ -197,6 +197,11 @@ export class ClickableFieldModel extends FieldModel {
197
197
  titleField,
198
198
  overflowMode,
199
199
  disabled,
200
+ dateOnly,
201
+ dateFormat,
202
+ format,
203
+ picker,
204
+ showTime,
200
205
  timeFormat,
201
206
  ...restProps
202
207
  } = this.props;
@@ -8,14 +8,42 @@
8
8
  */
9
9
 
10
10
  import { DisplayItemModel, tExpr } from '@nocobase/flow-engine';
11
+ import { getDateTimeFormat, getPickerFormat } from '@nocobase/utils/client';
11
12
  import dayjs from 'dayjs';
12
13
  import React from 'react';
13
14
  import { ClickableFieldModel } from './ClickableFieldModel';
14
15
 
16
+ const stripTimeFromFormat = (format?: string) =>
17
+ format ? format.replace(/\s*[Hh]{1,2}:mm(?::ss)?(?:\.SSS)?(?:\s*[aA])?/g, '').trim() : format;
18
+
19
+ interface DisplayDateTimeFormatProps {
20
+ dateOnly?: boolean;
21
+ picker?: string;
22
+ format?: string;
23
+ dateFormat?: string;
24
+ showTime?: boolean;
25
+ timeFormat?: string;
26
+ }
27
+
28
+ const resolveDisplayDateTimeFormat = (props: DisplayDateTimeFormatProps) => {
29
+ const { dateOnly, picker = 'date', format, dateFormat, showTime, timeFormat } = props;
30
+ const normalizedFormat = stripTimeFromFormat(format);
31
+ if (picker !== 'date') {
32
+ return dateFormat || normalizedFormat || getPickerFormat(picker);
33
+ }
34
+
35
+ if (!dateOnly && !dateFormat && typeof showTime === 'undefined' && !timeFormat && normalizedFormat) {
36
+ return format;
37
+ }
38
+
39
+ const normalizedDateFormat = dateFormat || normalizedFormat || getPickerFormat(picker);
40
+ return getDateTimeFormat(picker, normalizedDateFormat, showTime, timeFormat);
41
+ };
42
+
15
43
  export class DisplayDateTimeFieldModel extends ClickableFieldModel {
16
44
  public renderComponent(value) {
17
45
  const { className, style } = this.props;
18
- const finalFormat = this.props.format;
46
+ const finalFormat = resolveDisplayDateTimeFormat(this.props);
19
47
  let formattedValue = '';
20
48
  if (value) {
21
49
  const day = dayjs(value);
@@ -15,17 +15,20 @@ import { MobileSelect } from './mobile-components/MobileSelect';
15
15
  import { enumToOptions, getSelectedEnumLabels, translateOptionLabel } from '../../internal/utils/enumOptionsUtils';
16
16
 
17
17
  const getOriginalEnumOptions = (model: SelectFieldModel) => {
18
- const fromEnum = enumToOptions(model.context.collectionField?.uiSchema?.enum, (text) => text) || [];
18
+ const fromEnum = enumToOptions(model.context.collectionField?.uiSchema?.enum, model.translate) || [];
19
19
  if (fromEnum.length > 0) {
20
20
  return fromEnum.map((option) => ({ ...option }));
21
21
  }
22
22
  const current = Array.isArray(model.props.options) ? model.props.options : [];
23
- return current.map((option) => ({ ...option }));
23
+ return current.map((option) => ({
24
+ ...option,
25
+ label: translateOptionLabel(option.label, model.translate),
26
+ }));
24
27
  };
25
-
26
28
  export class SelectFieldModel extends FieldModel {
27
29
  render() {
28
30
  const fallbackOptions = getOriginalEnumOptions(this);
31
+
29
32
  const options = this.props.options?.map((v) => {
30
33
  return {
31
34
  ...v,
@@ -46,7 +49,6 @@ export class SelectFieldModel extends FieldModel {
46
49
  if (this.context.isMobileLayout) {
47
50
  return <MobileSelect {...this.props} options={options} displayValue={value} />;
48
51
  }
49
-
50
52
  return (
51
53
  <Select
52
54
  {...this.props}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import { FlowEngine } from '@nocobase/flow-engine';
11
+ import { render, screen } from '@testing-library/react';
12
+ import { describe, expect, it } from 'vitest';
13
+ import { DisplayDateTimeFieldModel } from '../DisplayDateTimeFieldModel';
14
+
15
+ describe('DisplayDateTimeFieldModel', () => {
16
+ it('uses dateFormat, showTime, and timeFormat when rendering read pretty datetime text', () => {
17
+ const engine = new FlowEngine();
18
+ engine.registerModels({ DisplayDateTimeFieldModel });
19
+
20
+ const model = engine.createModel<DisplayDateTimeFieldModel>({
21
+ use: DisplayDateTimeFieldModel,
22
+ uid: 'display-datetime-field-format',
23
+ props: {
24
+ value: '2026-06-15 13:05:06',
25
+ dateFormat: 'YYYY-MM-DD',
26
+ showTime: true,
27
+ timeFormat: 'HH:mm:ss',
28
+ },
29
+ });
30
+
31
+ render(model.render());
32
+
33
+ expect(screen.getByText('2026-06-15 13:05:06')).toBeInTheDocument();
34
+ });
35
+
36
+ it('does not reuse a time-only format when rendering datetime text', () => {
37
+ const engine = new FlowEngine();
38
+ engine.registerModels({ DisplayDateTimeFieldModel });
39
+
40
+ const model = engine.createModel<DisplayDateTimeFieldModel>({
41
+ use: DisplayDateTimeFieldModel,
42
+ uid: 'display-datetime-field-stale-time-format',
43
+ props: {
44
+ value: '2026-06-15 13:05:06',
45
+ format: 'HH:mm:ss',
46
+ showTime: true,
47
+ timeFormat: 'HH:mm:ss',
48
+ },
49
+ });
50
+
51
+ render(model.render());
52
+
53
+ expect(screen.getByText('2026-06-15 13:05:06')).toBeInTheDocument();
54
+ expect(screen.queryByText('13:05:06')).not.toBeInTheDocument();
55
+ });
56
+
57
+ it('keeps an existing complete datetime format when no split format props are configured', () => {
58
+ const engine = new FlowEngine();
59
+ engine.registerModels({ DisplayDateTimeFieldModel });
60
+
61
+ const model = engine.createModel<DisplayDateTimeFieldModel>({
62
+ use: DisplayDateTimeFieldModel,
63
+ uid: 'display-datetime-field-complete-format',
64
+ props: {
65
+ value: '2026-06-15 13:05:06',
66
+ format: 'YYYY/MM/DD HH:mm:ss',
67
+ },
68
+ });
69
+
70
+ render(model.render());
71
+
72
+ expect(screen.getByText('2026/06/15 13:05:06')).toBeInTheDocument();
73
+ });
74
+
75
+ it('renders only the date for date-only fields even when a datetime format remains', () => {
76
+ const engine = new FlowEngine();
77
+ engine.registerModels({ DisplayDateTimeFieldModel });
78
+
79
+ const model = engine.createModel<DisplayDateTimeFieldModel>({
80
+ use: DisplayDateTimeFieldModel,
81
+ uid: 'display-datetime-field-date-only',
82
+ props: {
83
+ value: '2026-06-15 13:05:06',
84
+ dateOnly: true,
85
+ format: 'YYYY-MM-DD HH:mm:ss',
86
+ showTime: false,
87
+ timeFormat: 'HH:mm:ss',
88
+ },
89
+ });
90
+
91
+ render(model.render());
92
+
93
+ expect(screen.getByText('2026-06-15')).toBeInTheDocument();
94
+ expect(screen.queryByText('2026-06-15 13:05:06')).not.toBeInTheDocument();
95
+ });
96
+ });
@@ -0,0 +1,43 @@
1
+ /**
2
+ * This file is part of the NocoBase (R) project.
3
+ * Copyright (c) 2020-2024 NocoBase Co., Ltd.
4
+ * Authors: NocoBase Team.
5
+ *
6
+ * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
7
+ * For more information, please refer to: https://www.nocobase.com/agreement.
8
+ */
9
+
10
+ import React from 'react';
11
+ import { describe, expect, it } from 'vitest';
12
+ import { SelectFieldModel } from '../SelectFieldModel';
13
+
14
+ function mockT(text: string) {
15
+ if (text === '{{t("Yes")}}') return '是';
16
+ if (text === '{{t("No")}}') return '否';
17
+ return text;
18
+ }
19
+
20
+ describe('SelectFieldModel', () => {
21
+ it('translates enum fallback labels for selected values', () => {
22
+ const model = {
23
+ props: {
24
+ value: true,
25
+ },
26
+ context: {
27
+ collectionField: {
28
+ uiSchema: {
29
+ enum: [
30
+ { label: '{{t("Yes")}}', value: true },
31
+ { label: '{{t("No")}}', value: false },
32
+ ],
33
+ },
34
+ },
35
+ },
36
+ translate: mockT,
37
+ } as unknown as SelectFieldModel;
38
+
39
+ const element = SelectFieldModel.prototype.render.call(model) as React.ReactElement;
40
+
41
+ expect(element.props.value).toEqual({ label: '是', value: true });
42
+ });
43
+ });
@@ -48,6 +48,264 @@ describe('dateTimeFormat', () => {
48
48
  });
49
49
  });
50
50
 
51
+ it('uses the association title field when deciding the format schema', () => {
52
+ const ctx = {
53
+ model: {
54
+ props: {
55
+ titleField: 'shipmentsTime',
56
+ },
57
+ context: {
58
+ collectionField: {
59
+ type: 'belongsTo',
60
+ targetCollection: {
61
+ getField: (name) =>
62
+ name === 'shipmentsTime'
63
+ ? {
64
+ type: 'time',
65
+ interface: 'time',
66
+ }
67
+ : null,
68
+ },
69
+ },
70
+ },
71
+ },
72
+ };
73
+
74
+ expect(Object.keys(dateTimeFormat.uiSchema(ctx))).toEqual(['timeFormat']);
75
+ });
76
+
77
+ it('saves association title time field format as a time format', () => {
78
+ const setProps = vi.fn();
79
+ const ctx = {
80
+ model: {
81
+ props: {
82
+ titleField: 'shipmentsTime',
83
+ },
84
+ context: {
85
+ collectionField: {
86
+ type: 'belongsTo',
87
+ targetCollection: {
88
+ getField: (name) =>
89
+ name === 'shipmentsTime'
90
+ ? {
91
+ type: 'time',
92
+ interface: 'time',
93
+ }
94
+ : null,
95
+ },
96
+ },
97
+ },
98
+ setProps,
99
+ },
100
+ };
101
+
102
+ dateTimeFormat.handler(ctx, { timeFormat: 'hh:mm:ss a' });
103
+
104
+ expect(setProps).toHaveBeenCalledWith({
105
+ timeFormat: 'hh:mm:ss a',
106
+ format: 'hh:mm:ss a',
107
+ });
108
+ });
109
+
110
+ it('applies and persists date time format params when settings are saved', async () => {
111
+ const setProps = vi.fn();
112
+ const save = vi.fn();
113
+ const ctx = {
114
+ model: {
115
+ props: {
116
+ titleField: 'shipmentsDatetime',
117
+ },
118
+ context: {
119
+ collectionField: {
120
+ type: 'belongsTo',
121
+ targetCollection: {
122
+ getField: (name) =>
123
+ name === 'shipmentsDatetime'
124
+ ? {
125
+ type: 'datetime',
126
+ interface: 'datetime',
127
+ }
128
+ : null,
129
+ },
130
+ },
131
+ },
132
+ setProps,
133
+ save,
134
+ },
135
+ };
136
+
137
+ await dateTimeFormat.beforeParamsSave(ctx, {
138
+ picker: 'date',
139
+ dateFormat: 'YYYY-MM-DD',
140
+ showTime: true,
141
+ timeFormat: 'hh:mm:ss a',
142
+ });
143
+
144
+ expect(setProps).toHaveBeenCalledWith({
145
+ picker: 'date',
146
+ dateFormat: 'YYYY-MM-DD',
147
+ showTime: true,
148
+ timeFormat: 'hh:mm:ss a',
149
+ format: 'YYYY-MM-DD hh:mm:ss a',
150
+ });
151
+ expect(save).toHaveBeenCalled();
152
+ });
153
+
154
+ it('syncs table association column props when title date time format settings are saved', async () => {
155
+ const setProps = vi.fn();
156
+ const save = vi.fn();
157
+ const setParentProps = vi.fn();
158
+ const model = {
159
+ props: {
160
+ titleField: 'shipmentsDatetime',
161
+ },
162
+ context: {
163
+ collectionField: {
164
+ type: 'belongsTo',
165
+ targetCollection: {
166
+ getField: (name) =>
167
+ name === 'shipmentsDatetime'
168
+ ? {
169
+ type: 'datetime',
170
+ interface: 'datetime',
171
+ }
172
+ : null,
173
+ },
174
+ },
175
+ },
176
+ setProps,
177
+ save,
178
+ parent: {
179
+ use: 'TableColumnModel',
180
+ collectionField: {
181
+ isAssociationField: () => true,
182
+ },
183
+ setProps: setParentProps,
184
+ },
185
+ };
186
+ model.parent['subModels'] = {
187
+ field: model,
188
+ };
189
+
190
+ await dateTimeFormat.beforeParamsSave(
191
+ { model },
192
+ {
193
+ picker: 'date',
194
+ dateFormat: 'YYYY-MM-DD',
195
+ showTime: true,
196
+ timeFormat: 'hh:mm:ss a',
197
+ },
198
+ );
199
+
200
+ expect(setParentProps).toHaveBeenCalledWith({
201
+ picker: 'date',
202
+ dateFormat: 'YYYY-MM-DD',
203
+ showTime: true,
204
+ timeFormat: 'hh:mm:ss a',
205
+ format: 'YYYY-MM-DD hh:mm:ss a',
206
+ });
207
+ expect(save).toHaveBeenCalled();
208
+ });
209
+
210
+ it('hides time format for association title date-only fields', () => {
211
+ const ctx = {
212
+ model: {
213
+ props: {
214
+ titleField: 'shipmentsDateOnly',
215
+ showTime: true,
216
+ },
217
+ context: {
218
+ collectionField: {
219
+ type: 'belongsTo',
220
+ targetCollection: {
221
+ getField: (name) =>
222
+ name === 'shipmentsDateOnly'
223
+ ? {
224
+ type: 'dateOnly',
225
+ interface: 'date',
226
+ getComponentProps: () => ({
227
+ dateOnly: true,
228
+ showTime: false,
229
+ }),
230
+ }
231
+ : null,
232
+ },
233
+ },
234
+ },
235
+ },
236
+ };
237
+ const schema = dateTimeFormat.uiSchema(ctx);
238
+ const timeFormatField: any = schema.timeFormat;
239
+ const showTimeField: any = schema.showTime;
240
+ const timeFormatState = {
241
+ hidden: false,
242
+ form: {
243
+ values: {
244
+ picker: 'date',
245
+ showTime: true,
246
+ },
247
+ },
248
+ };
249
+ const showTimeState = {
250
+ hidden: false,
251
+ value: true,
252
+ form: {
253
+ values: {
254
+ picker: 'date',
255
+ },
256
+ },
257
+ };
258
+
259
+ timeFormatField['x-reactions'][0](timeFormatState);
260
+ showTimeField['x-reactions'][1](showTimeState);
261
+
262
+ expect(timeFormatState.hidden).toBe(true);
263
+ expect(showTimeState.hidden).toBe(true);
264
+ expect(showTimeState.value).toBe(false);
265
+ expect(dateTimeFormat.defaultParams(ctx)).toMatchObject({
266
+ showTime: false,
267
+ });
268
+ });
269
+
270
+ it('saves association title date-only field format without time even when params contain showTime', () => {
271
+ const setProps = vi.fn();
272
+ const ctx = {
273
+ model: {
274
+ props: {
275
+ titleField: 'shipmentsDateOnly',
276
+ },
277
+ context: {
278
+ collectionField: {
279
+ type: 'belongsTo',
280
+ targetCollection: {
281
+ getField: (name) =>
282
+ name === 'shipmentsDateOnly'
283
+ ? {
284
+ type: 'dateOnly',
285
+ interface: 'date',
286
+ }
287
+ : null,
288
+ },
289
+ },
290
+ },
291
+ setProps,
292
+ },
293
+ };
294
+
295
+ dateTimeFormat.handler(ctx, {
296
+ dateFormat: 'YYYY-MM-DD',
297
+ showTime: true,
298
+ timeFormat: 'HH:mm:ss',
299
+ });
300
+
301
+ expect(setProps).toHaveBeenCalledWith({
302
+ dateFormat: 'YYYY-MM-DD',
303
+ showTime: false,
304
+ timeFormat: 'HH:mm:ss',
305
+ format: 'YYYY-MM-DD',
306
+ });
307
+ });
308
+
51
309
  it('uses format as the default time format when timeFormat is missing', () => {
52
310
  const ctx = {
53
311
  model: {