@nocobase/client-v2 2.1.0-beta.30 → 2.1.0-beta.32

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 (72) hide show
  1. package/es/BaseApplication.d.ts +1 -0
  2. package/es/PluginManager.d.ts +1 -0
  3. package/es/components/form/DrawerFormLayout.d.ts +49 -0
  4. package/es/components/form/EnvVariableInput.d.ts +42 -0
  5. package/es/components/form/FileSizeInput.d.ts +27 -0
  6. package/es/components/form/createFormRegistry.d.ts +33 -0
  7. package/es/components/form/index.d.ts +13 -0
  8. package/es/components/index.d.ts +1 -1
  9. package/es/flow/components/FieldAssignRulesEditor.d.ts +1 -0
  10. package/es/flow/internal/utils/enumOptionsUtils.d.ts +5 -0
  11. package/es/flow/models/actions/AssociationActionUtils.d.ts +5 -0
  12. package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +4 -0
  13. package/es/flow/models/blocks/filter-manager/FilterManager.d.ts +5 -1
  14. package/es/flow/models/blocks/table/TableSelectModel.d.ts +8 -0
  15. package/es/flow/models/fields/DisplayTitleFieldModel.d.ts +1 -1
  16. package/es/flow/models/utils/displayValueUtils.d.ts +12 -0
  17. package/es/flow-compat/passwordUtils.d.ts +1 -1
  18. package/es/index.mjs +87 -76
  19. package/es/utils/remotePlugins.d.ts +0 -4
  20. package/lib/index.js +97 -86
  21. package/package.json +6 -5
  22. package/src/BaseApplication.tsx +14 -8
  23. package/src/PluginManager.ts +1 -0
  24. package/src/__tests__/app.test.tsx +28 -1
  25. package/src/__tests__/remotePlugins.test.ts +29 -18
  26. package/src/components/form/DrawerFormLayout.tsx +103 -0
  27. package/src/components/form/EnvVariableInput.tsx +126 -0
  28. package/src/components/form/FileSizeInput.tsx +105 -0
  29. package/src/components/form/createFormRegistry.ts +60 -0
  30. package/src/components/form/index.tsx +14 -0
  31. package/src/components/index.ts +1 -1
  32. package/src/flow/actions/__tests__/dataScopeFilter.test.ts +92 -13
  33. package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +476 -1
  34. package/src/flow/actions/linkageRules.tsx +240 -258
  35. package/src/flow/actions/setTargetDataScope.tsx +32 -3
  36. package/src/flow/components/FieldAssignRulesEditor.tsx +2 -0
  37. package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +81 -4
  38. package/src/flow/components/filter/LinkageFilterItem.tsx +9 -2
  39. package/src/flow/components/filter/VariableFilterItem.tsx +2 -6
  40. package/src/flow/components/filter/__tests__/LinkageFilterItem.test.tsx +71 -0
  41. package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +48 -0
  42. package/src/flow/internal/utils/__tests__/enumOptionsUtils.test.ts +10 -1
  43. package/src/flow/internal/utils/enumOptionsUtils.ts +29 -0
  44. package/src/flow/models/actions/AssociateActionModel.tsx +2 -2
  45. package/src/flow/models/actions/AssociationActionUtils.ts +14 -0
  46. package/src/flow/models/actions/__tests__/AssociationActionModel.test.ts +63 -0
  47. package/src/flow/models/base/CollectionBlockModel.tsx +7 -0
  48. package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +33 -9
  49. package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +53 -13
  50. package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +63 -3
  51. package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +33 -1
  52. package/src/flow/models/blocks/filter-manager/FilterManager.ts +66 -2
  53. package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +270 -0
  54. package/src/flow/models/blocks/form/FormBlockModel.tsx +8 -5
  55. package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +30 -0
  56. package/src/flow/models/blocks/form/value-runtime/rules.ts +6 -1
  57. package/src/flow/models/blocks/form/value-runtime/runtime.ts +6 -1
  58. package/src/flow/models/blocks/table/TableBlockModel.tsx +11 -6
  59. package/src/flow/models/blocks/table/TableColumnModel.tsx +3 -0
  60. package/src/flow/models/blocks/table/TableSelectModel.tsx +36 -26
  61. package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowClick.test.ts +69 -0
  62. package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +96 -1
  63. package/src/flow/models/blocks/table/__tests__/TableSelectModel.test.ts +41 -0
  64. package/src/flow/models/fields/ClickableFieldModel.tsx +9 -4
  65. package/src/flow/models/fields/DisplayTitleFieldModel.tsx +12 -4
  66. package/src/flow/models/fields/SelectFieldModel.tsx +31 -1
  67. package/src/flow/models/fields/__tests__/ClickableFieldModel.test.ts +23 -0
  68. package/src/flow/models/fields/mobile-components/MobileSelect.tsx +2 -1
  69. package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +7 -0
  70. package/src/flow/models/utils/displayValueUtils.ts +57 -0
  71. package/src/utils/globalDeps.ts +2 -0
  72. package/src/utils/remotePlugins.ts +7 -27
@@ -8,10 +8,31 @@
8
8
  */
9
9
 
10
10
  import { describe, expect, it, vi } from 'vitest';
11
+ import { FlowEngine, MultiRecordResource } from '@nocobase/flow-engine';
11
12
  import { dataScope } from '../dataScope';
12
13
  import { normalizeDataScopeFilter } from '../dataScopeFilter';
13
14
  import { setTargetDataScope } from '../setTargetDataScope';
14
15
 
16
+ function createSetTargetDataScopeContext(resource: any, options: { selected?: boolean; resolvedValue?: any } = {}) {
17
+ const targetModel = { resource };
18
+ const inputArgs = options.selected === undefined ? undefined : { selected: options.selected };
19
+
20
+ return {
21
+ ...(inputArgs ? { inputArgs } : {}),
22
+ model: {
23
+ uid: 'action-1',
24
+ scheduleModelOperation: vi.fn((_uid, callback) => callback(targetModel)),
25
+ },
26
+ resolveJsonTemplate: vi.fn(async (template) => ({
27
+ ...template,
28
+ filter: {
29
+ ...template.filter,
30
+ items: [{ ...template.filter.items[0], value: options.resolvedValue }],
31
+ },
32
+ })),
33
+ };
34
+ }
35
+
15
36
  describe('normalizeDataScopeFilter', () => {
16
37
  it('keeps null when a right-side variable resolves to empty', () => {
17
38
  const rawFilter = {
@@ -125,34 +146,92 @@ describe('normalizeDataScopeFilter', () => {
125
146
  hasData: vi.fn(() => false),
126
147
  refresh: vi.fn(),
127
148
  };
128
- const targetModel = { resource };
129
- const ctx = {
130
- model: {
131
- uid: 'action-1',
132
- scheduleModelOperation: vi.fn((_uid, callback) => callback(targetModel)),
149
+ const ctx = createSetTargetDataScopeContext(resource);
150
+ const params = {
151
+ targetBlockUid: 'target-1',
152
+ filter: {
153
+ logic: '$and',
154
+ items: [{ path: 'departmentId', operator: '$eq', value: '{{ ctx.formValues.department.id }}' }],
133
155
  },
134
- resolveJsonTemplate: vi.fn(async (template) => ({
135
- ...template,
136
- filter: {
137
- ...template.filter,
138
- items: [{ ...template.filter.items[0], value: undefined }],
139
- },
140
- })),
141
156
  };
157
+
158
+ await (setTargetDataScope as any).handler(ctx, params);
159
+
160
+ expect(ctx.model.scheduleModelOperation).toHaveBeenCalledWith('target-1', expect.any(Function));
161
+ expect(resource.addFilterGroup).toHaveBeenCalledWith('setTargetDataScope_action-1', {
162
+ $and: [{ departmentId: { $eq: null } }],
163
+ });
164
+ expect(resource.removeFilterGroup).not.toHaveBeenCalled();
165
+ });
166
+
167
+ it('setTargetDataScope handler removes clicked-row data scope when row click deselects', async () => {
168
+ const resource = {
169
+ addFilterGroup: vi.fn(),
170
+ removeFilterGroup: vi.fn(),
171
+ hasData: vi.fn(() => true),
172
+ refresh: vi.fn(),
173
+ };
174
+ const ctx = createSetTargetDataScopeContext(resource, { selected: false, resolvedValue: null });
142
175
  const params = {
143
176
  targetBlockUid: 'target-1',
144
177
  filter: {
145
178
  logic: '$and',
146
- items: [{ path: 'departmentId', operator: '$eq', value: '{{ ctx.formValues.department.id }}' }],
179
+ items: [{ path: 'id', operator: '$eq', value: '{{ ctx.clickedRowRecord.id }}' }],
147
180
  },
148
181
  };
149
182
 
150
183
  await (setTargetDataScope as any).handler(ctx, params);
151
184
 
152
185
  expect(ctx.model.scheduleModelOperation).toHaveBeenCalledWith('target-1', expect.any(Function));
186
+ expect(resource.removeFilterGroup).toHaveBeenCalledWith('setTargetDataScope_action-1');
187
+ expect(resource.addFilterGroup).not.toHaveBeenCalled();
188
+ expect(resource.refresh).toHaveBeenCalledTimes(1);
189
+ });
190
+
191
+ it('setTargetDataScope handler keeps null clicked-row field values while row is selected', async () => {
192
+ const resource = {
193
+ addFilterGroup: vi.fn(),
194
+ removeFilterGroup: vi.fn(),
195
+ hasData: vi.fn(() => false),
196
+ refresh: vi.fn(),
197
+ };
198
+ const ctx = createSetTargetDataScopeContext(resource, { selected: true, resolvedValue: null });
199
+ const params = {
200
+ targetBlockUid: 'target-1',
201
+ filter: {
202
+ logic: '$and',
203
+ items: [{ path: 'departmentId', operator: '$eq', value: '{{ ctx.clickedRowRecord.departmentId }}' }],
204
+ },
205
+ };
206
+
207
+ await (setTargetDataScope as any).handler(ctx, params);
208
+
153
209
  expect(resource.addFilterGroup).toHaveBeenCalledWith('setTargetDataScope_action-1', {
154
210
  $and: [{ departmentId: { $eq: null } }],
155
211
  });
156
212
  expect(resource.removeFilterGroup).not.toHaveBeenCalled();
157
213
  });
214
+
215
+ it('setTargetDataScope handler refreshes empty loaded target while preserving its own data scope', async () => {
216
+ const engine = new FlowEngine();
217
+ const resource = engine.createResource(MultiRecordResource);
218
+ resource.addFilterGroup('target-table', { status: { $eq: 'active' } });
219
+ resource.addFilterGroup('setTargetDataScope_action-1', { id: { $eq: 1 } });
220
+ resource.setData([]);
221
+ resource.setMeta({ count: 0, hasNext: false });
222
+ const refresh = vi.spyOn(resource, 'refresh').mockResolvedValue(undefined);
223
+ const ctx = createSetTargetDataScopeContext(resource, { selected: false, resolvedValue: null });
224
+ const params = {
225
+ targetBlockUid: 'target-1',
226
+ filter: {
227
+ logic: '$and',
228
+ items: [{ path: 'id', operator: '$eq', value: '{{ ctx.clickedRowRecord.id }}' }],
229
+ },
230
+ };
231
+
232
+ await (setTargetDataScope as any).handler(ctx, params);
233
+
234
+ expect(resource.getRequestParameter('filter')).toBe(JSON.stringify({ $and: [{ status: { $eq: 'active' } }] }));
235
+ expect(refresh).toHaveBeenCalledTimes(1);
236
+ });
158
237
  });
@@ -7,8 +7,11 @@
7
7
  * For more information, please refer to: https://www.nocobase.com/agreement.
8
8
  */
9
9
 
10
+ import React from 'react';
11
+ import { fireEvent, render, screen } from '@testing-library/react';
12
+ import { FlowSettingsContextProvider } from '@nocobase/flow-engine';
10
13
  import { describe, expect, it, vi } from 'vitest';
11
- import { subFormLinkageSetFieldProps } from '../linkageRules';
14
+ import { fieldLinkageRules, linkageSetFieldProps, subFormLinkageSetFieldProps } from '../linkageRules';
12
15
 
13
16
  describe('subFormLinkageSetFieldProps action', () => {
14
17
  it('should not throw when engine.getModel returns undefined', () => {
@@ -106,4 +109,476 @@ describe('subFormLinkageSetFieldProps action', () => {
106
109
  expect(getFork).toHaveBeenCalledWith('roles:0:field-2');
107
110
  expect(setProps).toHaveBeenCalledWith(formItemModel, { disabled: false });
108
111
  });
112
+
113
+ it('should limit options on the fork model', () => {
114
+ const setProps = vi.fn();
115
+ const selectedOptions = [{ label: 'Open', value: 'open' }];
116
+ const forkModel: any = {
117
+ uid: 'field-3',
118
+ isFork: true,
119
+ };
120
+ const formItemModel: any = {
121
+ uid: 'field-3',
122
+ getFork: vi.fn(() => forkModel),
123
+ };
124
+
125
+ const ctx: any = {
126
+ model: {
127
+ context: {
128
+ fieldKey: ['roles:0'],
129
+ },
130
+ },
131
+ engine: {
132
+ getModel: vi.fn(() => formItemModel),
133
+ },
134
+ };
135
+
136
+ subFormLinkageSetFieldProps.handler(ctx, {
137
+ value: {
138
+ fields: ['field-3'],
139
+ state: 'limitOptions',
140
+ selectedOptions,
141
+ },
142
+ setProps,
143
+ });
144
+
145
+ expect(setProps).toHaveBeenCalledWith(forkModel, { options: selectedOptions });
146
+ });
147
+ });
148
+
149
+ describe('linkageSetFieldProps action', () => {
150
+ it('should limit field options', () => {
151
+ const setProps = vi.fn();
152
+ const selectedOptions = [{ label: 'Draft', value: 'draft' }];
153
+ const fieldComponentModel: any = {
154
+ uid: 'status-field-component',
155
+ };
156
+ const fieldModel: any = {
157
+ uid: 'status-field',
158
+ subModels: {
159
+ field: fieldComponentModel,
160
+ },
161
+ };
162
+ const ctx: any = {
163
+ model: {
164
+ subModels: {
165
+ grid: {
166
+ subModels: {
167
+ items: [fieldModel],
168
+ },
169
+ },
170
+ },
171
+ },
172
+ };
173
+
174
+ linkageSetFieldProps.handler(ctx, {
175
+ value: {
176
+ fields: ['status-field'],
177
+ state: 'limitOptions',
178
+ selectedOptions,
179
+ },
180
+ setProps,
181
+ });
182
+
183
+ expect(setProps).toHaveBeenCalledWith(fieldComponentModel, { options: selectedOptions });
184
+ });
185
+
186
+ it('should sync limited options to existing field forks immediately', async () => {
187
+ const selectedOptions = [{ label: 'Draft', value: 'draft' }];
188
+ const forkModel: any = {
189
+ uid: 'status-field-component',
190
+ isFork: true,
191
+ setProps: vi.fn(),
192
+ };
193
+ const fieldComponentModel: any = {
194
+ uid: 'status-field-component',
195
+ props: {
196
+ options: [
197
+ { label: 'Draft', value: 'draft' },
198
+ { label: 'Published', value: 'published' },
199
+ ],
200
+ },
201
+ forks: new Set([forkModel]),
202
+ setProps(key: any, value?: any) {
203
+ if (typeof key === 'string') {
204
+ this.props[key] = value;
205
+ } else {
206
+ this.props = { ...this.props, ...key };
207
+ }
208
+ },
209
+ };
210
+ const staleFieldComponentModel: any = {
211
+ uid: 'status-field-component',
212
+ props: {
213
+ options: [
214
+ { label: 'Draft', value: 'draft' },
215
+ { label: 'Published', value: 'published' },
216
+ ],
217
+ },
218
+ forks: new Set(),
219
+ setProps: vi.fn(),
220
+ };
221
+ const fieldModel: any = {
222
+ uid: 'status-field',
223
+ subModels: {
224
+ field: fieldComponentModel,
225
+ },
226
+ };
227
+ const ctx: any = {
228
+ app: {
229
+ jsonLogic: {
230
+ apply: vi.fn(() => true),
231
+ },
232
+ },
233
+ model: {
234
+ __allModels: [staleFieldComponentModel],
235
+ subModels: {
236
+ grid: {
237
+ subModels: {
238
+ items: [fieldModel],
239
+ },
240
+ },
241
+ },
242
+ },
243
+ getAction: (name: string) => (name === 'linkageSetFieldProps' ? linkageSetFieldProps : null),
244
+ resolveJsonTemplate: vi.fn(async (value) => value),
245
+ };
246
+
247
+ await fieldLinkageRules.handler(ctx, {
248
+ value: [
249
+ {
250
+ key: 'rule-1',
251
+ enable: true,
252
+ condition: { logic: '$and', items: [] },
253
+ actions: [
254
+ {
255
+ key: 'action-1',
256
+ name: 'linkageSetFieldProps',
257
+ params: {
258
+ value: {
259
+ fields: ['status-field'],
260
+ state: 'limitOptions',
261
+ selectedOptions,
262
+ },
263
+ },
264
+ },
265
+ ],
266
+ },
267
+ ],
268
+ });
269
+
270
+ expect(fieldComponentModel.props.options).toEqual(selectedOptions);
271
+ expect(staleFieldComponentModel.setProps).not.toHaveBeenCalledWith({ options: selectedOptions });
272
+ expect(forkModel.setProps).toHaveBeenCalledWith({ options: selectedOptions });
273
+ });
274
+
275
+ it('should not clear form value when limit options reruns after selection', async () => {
276
+ const selectedOptions = [{ label: 'Draft', value: 'draft' }];
277
+ const form = {
278
+ getFieldValue: vi.fn(() => 'draft'),
279
+ setFieldValue: vi.fn(),
280
+ };
281
+ const fieldComponentModel: any = {
282
+ uid: 'status-field-component',
283
+ context: { form },
284
+ props: {
285
+ name: 'status',
286
+ value: undefined,
287
+ options: [
288
+ { label: 'Draft', value: 'draft' },
289
+ { label: 'Published', value: 'published' },
290
+ ],
291
+ },
292
+ setProps(key: any, value?: any) {
293
+ if (typeof key === 'string') {
294
+ this.props[key] = value;
295
+ } else {
296
+ this.props = { ...this.props, ...key };
297
+ }
298
+ },
299
+ };
300
+ const fieldModel: any = {
301
+ uid: 'status-field',
302
+ subModels: {
303
+ field: fieldComponentModel,
304
+ },
305
+ };
306
+ const ctx: any = {
307
+ app: {
308
+ jsonLogic: {
309
+ apply: vi.fn(() => true),
310
+ },
311
+ },
312
+ model: {
313
+ context: { form },
314
+ subModels: {
315
+ grid: {
316
+ subModels: {
317
+ items: [fieldModel],
318
+ },
319
+ },
320
+ },
321
+ },
322
+ getAction: (name: string) => (name === 'linkageSetFieldProps' ? linkageSetFieldProps : null),
323
+ resolveJsonTemplate: vi.fn(async (value) => value),
324
+ };
325
+
326
+ await fieldLinkageRules.handler(ctx, {
327
+ value: [
328
+ {
329
+ key: 'rule-1',
330
+ enable: true,
331
+ condition: { logic: '$and', items: [] },
332
+ actions: [
333
+ {
334
+ key: 'action-1',
335
+ name: 'linkageSetFieldProps',
336
+ params: {
337
+ value: {
338
+ fields: ['status-field'],
339
+ state: 'limitOptions',
340
+ selectedOptions,
341
+ },
342
+ },
343
+ },
344
+ ],
345
+ },
346
+ ],
347
+ });
348
+
349
+ expect(fieldComponentModel.props.options).toEqual(selectedOptions);
350
+ expect(form.setFieldValue).not.toHaveBeenCalled();
351
+ });
352
+
353
+ it('should keep all options selectable after options were limited once', async () => {
354
+ const fieldComponentModel: any = {
355
+ uid: 'status-field-component',
356
+ props: {
357
+ options: [{ label: 'Draft', value: 'draft' }],
358
+ },
359
+ };
360
+ const fieldModel: any = {
361
+ uid: 'status-field',
362
+ props: {
363
+ label: 'Status',
364
+ },
365
+ collectionField: {
366
+ interface: 'select',
367
+ uiSchema: {
368
+ enum: [
369
+ { label: 'Draft', value: 'draft' },
370
+ { label: 'Published', value: 'published' },
371
+ ],
372
+ },
373
+ },
374
+ subModels: {
375
+ field: fieldComponentModel,
376
+ },
377
+ };
378
+ const ctx: any = {
379
+ model: {
380
+ translate: (text: string) => text,
381
+ subModels: {
382
+ grid: {
383
+ subModels: {
384
+ items: [fieldModel],
385
+ },
386
+ },
387
+ },
388
+ },
389
+ };
390
+ const Comp: any = linkageSetFieldProps.uiSchema.value['x-component'];
391
+ const value = {
392
+ fields: ['status-field'],
393
+ state: 'limitOptions',
394
+ selectedOptions: [{ label: 'Draft', value: 'draft' }],
395
+ };
396
+ const view = render(
397
+ React.createElement(
398
+ FlowSettingsContextProvider,
399
+ { value: ctx },
400
+ React.createElement(Comp, { value, onChange: () => {} }),
401
+ ),
402
+ );
403
+
404
+ const selectors = view.container.querySelectorAll('.ant-select-selector');
405
+ fireEvent.mouseDown(selectors[2] as Element);
406
+ expect(await screen.findByText('Published')).toBeTruthy();
407
+ });
408
+
409
+ it('should only show options state for supported single field selection', () => {
410
+ const supportedField: any = {
411
+ uid: 'status-field',
412
+ props: {
413
+ label: 'Status',
414
+ },
415
+ collectionField: {
416
+ interface: 'select',
417
+ uiSchema: {
418
+ enum: [{ label: 'Draft', value: 'draft' }],
419
+ },
420
+ },
421
+ subModels: {
422
+ field: {
423
+ uid: 'status-field-component',
424
+ props: {
425
+ options: [{ label: 'Draft', value: 'draft' }],
426
+ },
427
+ },
428
+ },
429
+ };
430
+ const unsupportedField: any = {
431
+ uid: 'note-field',
432
+ props: {
433
+ label: 'Note',
434
+ },
435
+ collectionField: {
436
+ interface: 'input',
437
+ },
438
+ subModels: {
439
+ field: {
440
+ uid: 'note-field-component',
441
+ props: {},
442
+ },
443
+ },
444
+ };
445
+ const ctx: any = {
446
+ model: {
447
+ translate: (text: string) => text,
448
+ subModels: {
449
+ grid: {
450
+ subModels: {
451
+ items: [supportedField, unsupportedField],
452
+ },
453
+ },
454
+ },
455
+ },
456
+ };
457
+ const Comp: any = linkageSetFieldProps.uiSchema.value['x-component'];
458
+
459
+ const { rerender } = render(
460
+ React.createElement(
461
+ FlowSettingsContextProvider,
462
+ { value: ctx },
463
+ React.createElement(Comp, {
464
+ value: {
465
+ fields: ['status-field'],
466
+ state: 'limitOptions',
467
+ },
468
+ onChange: () => {},
469
+ }),
470
+ ),
471
+ );
472
+
473
+ expect(screen.getAllByText('Options')).toHaveLength(2);
474
+
475
+ rerender(
476
+ React.createElement(
477
+ FlowSettingsContextProvider,
478
+ { value: ctx },
479
+ React.createElement(Comp, {
480
+ value: {
481
+ fields: ['status-field', 'note-field'],
482
+ state: 'limitOptions',
483
+ },
484
+ onChange: () => {},
485
+ }),
486
+ ),
487
+ );
488
+
489
+ expect(screen.queryAllByText('Options')).toHaveLength(1);
490
+ });
491
+
492
+ it('should clear selected options when changing fields under limit options', () => {
493
+ const statusField: any = {
494
+ uid: 'status-field',
495
+ props: {
496
+ label: 'Status',
497
+ },
498
+ collectionField: {
499
+ interface: 'select',
500
+ uiSchema: {
501
+ enum: [
502
+ { label: 'Draft', value: 'draft' },
503
+ { label: 'Published', value: 'published' },
504
+ ],
505
+ },
506
+ },
507
+ subModels: {
508
+ field: {
509
+ uid: 'status-field-component',
510
+ props: {
511
+ options: [
512
+ { label: 'Draft', value: 'draft' },
513
+ { label: 'Published', value: 'published' },
514
+ ],
515
+ },
516
+ },
517
+ },
518
+ };
519
+ const categoryField: any = {
520
+ uid: 'category-field',
521
+ props: {
522
+ label: 'Category',
523
+ },
524
+ collectionField: {
525
+ interface: 'select',
526
+ uiSchema: {
527
+ enum: [
528
+ { label: 'A', value: 'a' },
529
+ { label: 'B', value: 'b' },
530
+ ],
531
+ },
532
+ },
533
+ subModels: {
534
+ field: {
535
+ uid: 'category-field-component',
536
+ props: {
537
+ options: [
538
+ { label: 'A', value: 'a' },
539
+ { label: 'B', value: 'b' },
540
+ ],
541
+ },
542
+ },
543
+ },
544
+ };
545
+ const ctx: any = {
546
+ model: {
547
+ translate: (text: string) => text,
548
+ subModels: {
549
+ grid: {
550
+ subModels: {
551
+ items: [statusField, categoryField],
552
+ },
553
+ },
554
+ },
555
+ },
556
+ };
557
+ const onChange = vi.fn();
558
+ const Comp: any = linkageSetFieldProps.uiSchema.value['x-component'];
559
+ const view = render(
560
+ React.createElement(
561
+ FlowSettingsContextProvider,
562
+ { value: ctx },
563
+ React.createElement(Comp, {
564
+ value: {
565
+ fields: ['status-field'],
566
+ state: 'limitOptions',
567
+ selectedOptions: [{ label: 'Draft', value: 'draft' }],
568
+ },
569
+ onChange,
570
+ }),
571
+ ),
572
+ );
573
+
574
+ const selectors = view.container.querySelectorAll('.ant-select-selector');
575
+ fireEvent.mouseDown(selectors[0] as Element);
576
+ fireEvent.click(screen.getByText('Category'));
577
+
578
+ expect(onChange).toHaveBeenLastCalledWith({
579
+ fields: ['status-field', 'category-field'],
580
+ state: undefined,
581
+ selectedOptions: [],
582
+ });
583
+ });
109
584
  });