@nocobase/client-v2 2.2.0-beta.6 → 2.2.0-beta.7
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/es/PluginSettingsManager.d.ts +33 -0
- package/es/index.mjs +68 -68
- package/lib/index.js +72 -72
- package/package.json +7 -7
- package/src/PluginSettingsManager.ts +53 -0
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +7 -1
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +58 -0
- package/src/flow/actions/dataScopeFilter.ts +10 -0
- package/src/flow/components/FieldAssignRulesEditor.tsx +66 -9
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +427 -0
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
- package/src/flow/components/fieldAssignOptions.ts +155 -27
- package/src/nocobase-buildin-plugin/index.tsx +0 -2
- package/src/settings-center/SystemSettingsPage.tsx +0 -1
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/utils.tsx +0 -6
|
@@ -285,6 +285,433 @@ describe('FieldAssignRulesEditor', () => {
|
|
|
285
285
|
});
|
|
286
286
|
});
|
|
287
287
|
|
|
288
|
+
it('preloads full association children when configured options already contain partial children', async () => {
|
|
289
|
+
const nameField = { name: 'name', title: 'Name', interface: 'input' };
|
|
290
|
+
const nicknameField = { name: 'nickname', title: 'Nickname', interface: 'input' };
|
|
291
|
+
const profileCollection = {
|
|
292
|
+
getField: (name: string) => {
|
|
293
|
+
if (name === 'name') return nameField;
|
|
294
|
+
if (name === 'nickname') return nicknameField;
|
|
295
|
+
return null;
|
|
296
|
+
},
|
|
297
|
+
getFields: vi.fn(() => [nameField, nicknameField]),
|
|
298
|
+
};
|
|
299
|
+
const profileField = {
|
|
300
|
+
name: 'profile',
|
|
301
|
+
title: 'Profile',
|
|
302
|
+
type: 'belongsTo',
|
|
303
|
+
interface: 'm2o',
|
|
304
|
+
target: 'profiles',
|
|
305
|
+
targetCollection: profileCollection,
|
|
306
|
+
};
|
|
307
|
+
const rootCollection = {
|
|
308
|
+
getField: (name: string) => (name === 'profile' ? profileField : null),
|
|
309
|
+
getFields: () => [profileField],
|
|
310
|
+
};
|
|
311
|
+
const fieldOptions = [
|
|
312
|
+
{
|
|
313
|
+
label: 'Profile',
|
|
314
|
+
value: 'profile',
|
|
315
|
+
isLeaf: false,
|
|
316
|
+
children: [{ label: 'Name', value: 'name', isLeaf: true }],
|
|
317
|
+
},
|
|
318
|
+
];
|
|
319
|
+
const value: FieldAssignRuleItem[] = [
|
|
320
|
+
{
|
|
321
|
+
key: 'rule-profile-nickname',
|
|
322
|
+
enable: true,
|
|
323
|
+
targetPath: 'profile.nickname',
|
|
324
|
+
mode: 'assign',
|
|
325
|
+
},
|
|
326
|
+
];
|
|
327
|
+
|
|
328
|
+
render(
|
|
329
|
+
wrap(
|
|
330
|
+
<FieldAssignRulesEditor
|
|
331
|
+
t={t}
|
|
332
|
+
fieldOptions={fieldOptions}
|
|
333
|
+
rootCollection={rootCollection}
|
|
334
|
+
value={value}
|
|
335
|
+
showCondition={false}
|
|
336
|
+
/>,
|
|
337
|
+
),
|
|
338
|
+
);
|
|
339
|
+
|
|
340
|
+
await waitFor(() => {
|
|
341
|
+
expect(profileCollection.getFields).toHaveBeenCalled();
|
|
342
|
+
});
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
it('keeps configured child paths expandable when target collection cannot be resolved', async () => {
|
|
346
|
+
const configuredParent = {
|
|
347
|
+
label: 'Legacy profile',
|
|
348
|
+
value: 'legacyProfile',
|
|
349
|
+
isLeaf: false,
|
|
350
|
+
children: [{ label: 'Nickname', value: 'nickname', isLeaf: true }],
|
|
351
|
+
};
|
|
352
|
+
const rootCollection = {
|
|
353
|
+
getField: vi.fn(() => null),
|
|
354
|
+
getFields: () => [],
|
|
355
|
+
};
|
|
356
|
+
const value: FieldAssignRuleItem[] = [
|
|
357
|
+
{
|
|
358
|
+
key: 'rule-legacy-profile-nickname',
|
|
359
|
+
enable: true,
|
|
360
|
+
targetPath: 'legacyProfile.nickname',
|
|
361
|
+
mode: 'assign',
|
|
362
|
+
},
|
|
363
|
+
];
|
|
364
|
+
|
|
365
|
+
render(
|
|
366
|
+
wrap(
|
|
367
|
+
<FieldAssignRulesEditor
|
|
368
|
+
t={t}
|
|
369
|
+
fieldOptions={[configuredParent]}
|
|
370
|
+
rootCollection={rootCollection}
|
|
371
|
+
value={value}
|
|
372
|
+
showCondition={false}
|
|
373
|
+
/>,
|
|
374
|
+
),
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
await waitFor(() => {
|
|
378
|
+
expect(rootCollection.getField).toHaveBeenCalled();
|
|
379
|
+
expect(configuredParent.isLeaf).toBe(false);
|
|
380
|
+
});
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
it('preloads the latest fieldOptions after props switch', async () => {
|
|
384
|
+
const nameField = { name: 'name', title: 'Name', interface: 'input' };
|
|
385
|
+
const nicknameField = { name: 'nickname', title: 'Nickname', interface: 'input' };
|
|
386
|
+
const profileCollection = {
|
|
387
|
+
getField: (name: string) => {
|
|
388
|
+
if (name === 'name') return nameField;
|
|
389
|
+
if (name === 'nickname') return nicknameField;
|
|
390
|
+
return null;
|
|
391
|
+
},
|
|
392
|
+
getFields: vi.fn(() => [nameField, nicknameField]),
|
|
393
|
+
};
|
|
394
|
+
const profileField = {
|
|
395
|
+
name: 'profile',
|
|
396
|
+
title: 'Profile',
|
|
397
|
+
type: 'belongsTo',
|
|
398
|
+
interface: 'm2o',
|
|
399
|
+
target: 'profiles',
|
|
400
|
+
targetCollection: profileCollection,
|
|
401
|
+
};
|
|
402
|
+
const rootCollection = {
|
|
403
|
+
getField: (name: string) => (name === 'profile' ? profileField : null),
|
|
404
|
+
getFields: () => [profileField],
|
|
405
|
+
};
|
|
406
|
+
const oldConfiguredParent = {
|
|
407
|
+
label: 'Profile',
|
|
408
|
+
value: 'profile',
|
|
409
|
+
isLeaf: false,
|
|
410
|
+
children: [{ label: 'Name', value: 'name', isLeaf: true }],
|
|
411
|
+
};
|
|
412
|
+
const latestConfiguredParent = {
|
|
413
|
+
label: 'Profile',
|
|
414
|
+
value: 'profile',
|
|
415
|
+
isLeaf: false,
|
|
416
|
+
children: [{ label: 'Name', value: 'name', isLeaf: true }],
|
|
417
|
+
};
|
|
418
|
+
const value: FieldAssignRuleItem[] = [
|
|
419
|
+
{
|
|
420
|
+
key: 'rule-profile-nickname',
|
|
421
|
+
enable: true,
|
|
422
|
+
targetPath: 'profile.nickname',
|
|
423
|
+
mode: 'assign',
|
|
424
|
+
},
|
|
425
|
+
];
|
|
426
|
+
|
|
427
|
+
const { container, rerender } = render(
|
|
428
|
+
wrap(
|
|
429
|
+
<FieldAssignRulesEditor
|
|
430
|
+
t={t}
|
|
431
|
+
fieldOptions={[oldConfiguredParent]}
|
|
432
|
+
rootCollection={rootCollection}
|
|
433
|
+
value={[]}
|
|
434
|
+
showCondition={false}
|
|
435
|
+
/>,
|
|
436
|
+
),
|
|
437
|
+
);
|
|
438
|
+
|
|
439
|
+
rerender(
|
|
440
|
+
wrap(
|
|
441
|
+
<FieldAssignRulesEditor
|
|
442
|
+
t={t}
|
|
443
|
+
fieldOptions={[latestConfiguredParent]}
|
|
444
|
+
rootCollection={rootCollection}
|
|
445
|
+
value={value}
|
|
446
|
+
showCondition={false}
|
|
447
|
+
/>,
|
|
448
|
+
),
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
await waitFor(() => {
|
|
452
|
+
expect(profileCollection.getFields).toHaveBeenCalled();
|
|
453
|
+
});
|
|
454
|
+
const selector = container.querySelector('.ant-select-selector') as HTMLElement | null;
|
|
455
|
+
expect(selector).not.toBeNull();
|
|
456
|
+
await userEvent.click(selector as HTMLElement);
|
|
457
|
+
expect(await screen.findByText('Nickname')).toBeInTheDocument();
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
it('does not mutate fieldOptions props while preloading children', async () => {
|
|
461
|
+
const nameField = { name: 'name', title: 'Name', interface: 'input' };
|
|
462
|
+
const nicknameField = { name: 'nickname', title: 'Nickname', interface: 'input' };
|
|
463
|
+
const profileCollection = {
|
|
464
|
+
getField: (name: string) => {
|
|
465
|
+
if (name === 'name') return nameField;
|
|
466
|
+
if (name === 'nickname') return nicknameField;
|
|
467
|
+
return null;
|
|
468
|
+
},
|
|
469
|
+
getFields: vi.fn(() => [nameField, nicknameField]),
|
|
470
|
+
};
|
|
471
|
+
const profileField = {
|
|
472
|
+
name: 'profile',
|
|
473
|
+
title: 'Profile',
|
|
474
|
+
type: 'belongsTo',
|
|
475
|
+
interface: 'm2o',
|
|
476
|
+
target: 'profiles',
|
|
477
|
+
targetCollection: profileCollection,
|
|
478
|
+
};
|
|
479
|
+
const rootCollection = {
|
|
480
|
+
getField: (name: string) => (name === 'profile' ? profileField : null),
|
|
481
|
+
getFields: () => [profileField],
|
|
482
|
+
};
|
|
483
|
+
const configuredParent = {
|
|
484
|
+
label: 'Profile',
|
|
485
|
+
value: 'profile',
|
|
486
|
+
isLeaf: false,
|
|
487
|
+
children: [{ label: 'Name', value: 'name', isLeaf: true }],
|
|
488
|
+
};
|
|
489
|
+
const value: FieldAssignRuleItem[] = [
|
|
490
|
+
{
|
|
491
|
+
key: 'rule-profile-nickname',
|
|
492
|
+
enable: true,
|
|
493
|
+
targetPath: 'profile.nickname',
|
|
494
|
+
mode: 'assign',
|
|
495
|
+
},
|
|
496
|
+
];
|
|
497
|
+
|
|
498
|
+
render(
|
|
499
|
+
wrap(
|
|
500
|
+
<FieldAssignRulesEditor
|
|
501
|
+
t={t}
|
|
502
|
+
fieldOptions={[configuredParent]}
|
|
503
|
+
rootCollection={rootCollection}
|
|
504
|
+
value={value}
|
|
505
|
+
showCondition={false}
|
|
506
|
+
/>,
|
|
507
|
+
),
|
|
508
|
+
);
|
|
509
|
+
|
|
510
|
+
await waitFor(() => {
|
|
511
|
+
expect(profileCollection.getFields).toHaveBeenCalled();
|
|
512
|
+
});
|
|
513
|
+
expect(configuredParent.children.some((child) => child.value === 'nickname')).toBe(false);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it('retries preload when rootCollection becomes resolvable with same fieldOptions', async () => {
|
|
517
|
+
const nicknameField = { name: 'nickname', title: 'Nickname', interface: 'input' };
|
|
518
|
+
const profileCollection = {
|
|
519
|
+
getField: (name: string) => (name === 'nickname' ? nicknameField : null),
|
|
520
|
+
getFields: vi.fn(() => [nicknameField]),
|
|
521
|
+
};
|
|
522
|
+
const profileField = {
|
|
523
|
+
name: 'profile',
|
|
524
|
+
title: 'Profile',
|
|
525
|
+
type: 'belongsTo',
|
|
526
|
+
interface: 'm2o',
|
|
527
|
+
target: 'profiles',
|
|
528
|
+
targetCollection: profileCollection,
|
|
529
|
+
};
|
|
530
|
+
const unresolvedCollection = {
|
|
531
|
+
getField: () => null,
|
|
532
|
+
getFields: () => [],
|
|
533
|
+
};
|
|
534
|
+
const resolvedCollection = {
|
|
535
|
+
getField: (name: string) => (name === 'profile' ? profileField : null),
|
|
536
|
+
getFields: () => [profileField],
|
|
537
|
+
};
|
|
538
|
+
const configuredParent = {
|
|
539
|
+
label: 'Profile',
|
|
540
|
+
value: 'profile',
|
|
541
|
+
isLeaf: false,
|
|
542
|
+
children: [{ label: 'Name', value: 'name', isLeaf: true }],
|
|
543
|
+
};
|
|
544
|
+
const value: FieldAssignRuleItem[] = [
|
|
545
|
+
{
|
|
546
|
+
key: 'rule-profile-nickname',
|
|
547
|
+
enable: true,
|
|
548
|
+
targetPath: 'profile.nickname',
|
|
549
|
+
mode: 'assign',
|
|
550
|
+
},
|
|
551
|
+
];
|
|
552
|
+
|
|
553
|
+
const { rerender } = render(
|
|
554
|
+
wrap(
|
|
555
|
+
<FieldAssignRulesEditor
|
|
556
|
+
t={t}
|
|
557
|
+
fieldOptions={[configuredParent]}
|
|
558
|
+
rootCollection={unresolvedCollection}
|
|
559
|
+
value={value}
|
|
560
|
+
showCondition={false}
|
|
561
|
+
/>,
|
|
562
|
+
),
|
|
563
|
+
);
|
|
564
|
+
|
|
565
|
+
rerender(
|
|
566
|
+
wrap(
|
|
567
|
+
<FieldAssignRulesEditor
|
|
568
|
+
t={t}
|
|
569
|
+
fieldOptions={[configuredParent]}
|
|
570
|
+
rootCollection={resolvedCollection}
|
|
571
|
+
value={value}
|
|
572
|
+
showCondition={false}
|
|
573
|
+
/>,
|
|
574
|
+
),
|
|
575
|
+
);
|
|
576
|
+
|
|
577
|
+
await waitFor(() => {
|
|
578
|
+
expect(profileCollection.getFields).toHaveBeenCalled();
|
|
579
|
+
});
|
|
580
|
+
});
|
|
581
|
+
|
|
582
|
+
it('handles preload errors without leaving unhandled rejections', async () => {
|
|
583
|
+
const loadError = new Error('failed to load fields');
|
|
584
|
+
const profileCollection = {
|
|
585
|
+
getFields: vi.fn(() => {
|
|
586
|
+
throw loadError;
|
|
587
|
+
}),
|
|
588
|
+
};
|
|
589
|
+
const profileField = {
|
|
590
|
+
name: 'profile',
|
|
591
|
+
title: 'Profile',
|
|
592
|
+
type: 'belongsTo',
|
|
593
|
+
interface: 'm2o',
|
|
594
|
+
target: 'profiles',
|
|
595
|
+
targetCollection: profileCollection,
|
|
596
|
+
};
|
|
597
|
+
const rootCollection = {
|
|
598
|
+
getField: (name: string) => (name === 'profile' ? profileField : null),
|
|
599
|
+
getFields: () => [profileField],
|
|
600
|
+
};
|
|
601
|
+
const configuredParent = {
|
|
602
|
+
label: 'Profile',
|
|
603
|
+
value: 'profile',
|
|
604
|
+
isLeaf: false,
|
|
605
|
+
};
|
|
606
|
+
const value: FieldAssignRuleItem[] = [
|
|
607
|
+
{
|
|
608
|
+
key: 'rule-profile-nickname',
|
|
609
|
+
enable: true,
|
|
610
|
+
targetPath: 'profile.nickname',
|
|
611
|
+
mode: 'assign',
|
|
612
|
+
},
|
|
613
|
+
];
|
|
614
|
+
const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
615
|
+
|
|
616
|
+
render(
|
|
617
|
+
wrap(
|
|
618
|
+
<FieldAssignRulesEditor
|
|
619
|
+
t={t}
|
|
620
|
+
fieldOptions={[configuredParent]}
|
|
621
|
+
rootCollection={rootCollection}
|
|
622
|
+
value={value}
|
|
623
|
+
showCondition={false}
|
|
624
|
+
/>,
|
|
625
|
+
),
|
|
626
|
+
);
|
|
627
|
+
|
|
628
|
+
try {
|
|
629
|
+
await waitFor(() => {
|
|
630
|
+
expect(warnSpy).toHaveBeenCalledWith('[FieldAssignRulesEditor] Failed to preload cascader path', loadError);
|
|
631
|
+
});
|
|
632
|
+
} finally {
|
|
633
|
+
warnSpy.mockRestore();
|
|
634
|
+
}
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
it('keeps loaded cascader children when translation function identity changes', async () => {
|
|
638
|
+
const nameField = { name: 'name', title: 'Name', interface: 'input' };
|
|
639
|
+
const nicknameField = { name: 'nickname', title: 'Nickname', interface: 'input' };
|
|
640
|
+
const profileCollection = {
|
|
641
|
+
getField: (name: string) => {
|
|
642
|
+
if (name === 'name') return nameField;
|
|
643
|
+
if (name === 'nickname') return nicknameField;
|
|
644
|
+
return null;
|
|
645
|
+
},
|
|
646
|
+
getFields: vi.fn(() => [nameField, nicknameField]),
|
|
647
|
+
};
|
|
648
|
+
const profileField = {
|
|
649
|
+
name: 'profile',
|
|
650
|
+
title: 'Profile',
|
|
651
|
+
type: 'belongsTo',
|
|
652
|
+
interface: 'm2o',
|
|
653
|
+
target: 'profiles',
|
|
654
|
+
targetCollection: profileCollection,
|
|
655
|
+
};
|
|
656
|
+
const rootCollection = {
|
|
657
|
+
getField: (name: string) => (name === 'profile' ? profileField : null),
|
|
658
|
+
getFields: () => [profileField],
|
|
659
|
+
};
|
|
660
|
+
const fieldOptions = [
|
|
661
|
+
{
|
|
662
|
+
label: 'Profile',
|
|
663
|
+
value: 'profile',
|
|
664
|
+
isLeaf: false,
|
|
665
|
+
},
|
|
666
|
+
];
|
|
667
|
+
const value: FieldAssignRuleItem[] = [
|
|
668
|
+
{
|
|
669
|
+
key: 'rule-profile',
|
|
670
|
+
enable: true,
|
|
671
|
+
targetPath: 'profile',
|
|
672
|
+
mode: 'assign',
|
|
673
|
+
},
|
|
674
|
+
];
|
|
675
|
+
|
|
676
|
+
const { container, rerender } = render(
|
|
677
|
+
wrap(
|
|
678
|
+
<FieldAssignRulesEditor
|
|
679
|
+
t={(key) => key}
|
|
680
|
+
fieldOptions={fieldOptions}
|
|
681
|
+
rootCollection={rootCollection}
|
|
682
|
+
value={value}
|
|
683
|
+
showCondition={false}
|
|
684
|
+
/>,
|
|
685
|
+
),
|
|
686
|
+
);
|
|
687
|
+
|
|
688
|
+
const selector = container.querySelector('.ant-select-selector') as HTMLElement | null;
|
|
689
|
+
expect(selector).not.toBeNull();
|
|
690
|
+
await userEvent.click(selector as HTMLElement);
|
|
691
|
+
const profileOption = (await screen.findAllByText('Profile')).find((item) =>
|
|
692
|
+
item.closest('.ant-cascader-menu-item'),
|
|
693
|
+
);
|
|
694
|
+
expect(profileOption).toBeTruthy();
|
|
695
|
+
await userEvent.click(profileOption as HTMLElement);
|
|
696
|
+
expect(await screen.findByText('Nickname')).toBeInTheDocument();
|
|
697
|
+
await userEvent.keyboard('{Escape}');
|
|
698
|
+
|
|
699
|
+
rerender(
|
|
700
|
+
wrap(
|
|
701
|
+
<FieldAssignRulesEditor
|
|
702
|
+
t={(key) => key}
|
|
703
|
+
fieldOptions={fieldOptions}
|
|
704
|
+
rootCollection={rootCollection}
|
|
705
|
+
value={value}
|
|
706
|
+
showCondition={false}
|
|
707
|
+
/>,
|
|
708
|
+
),
|
|
709
|
+
);
|
|
710
|
+
|
|
711
|
+
await userEvent.click(selector as HTMLElement);
|
|
712
|
+
expect(await screen.findByText('Nickname')).toBeInTheDocument();
|
|
713
|
+
});
|
|
714
|
+
|
|
288
715
|
it('saves selected title field into current assign rule without syncing', async () => {
|
|
289
716
|
const { rootCollection, value } = createAssociationFixture();
|
|
290
717
|
const onChange = vi.fn();
|
|
@@ -14,6 +14,15 @@ import {
|
|
|
14
14
|
collectFieldAssignCascaderOptions,
|
|
15
15
|
} from '../fieldAssignOptions';
|
|
16
16
|
|
|
17
|
+
type TestCollectionField = {
|
|
18
|
+
name?: string;
|
|
19
|
+
title?: string;
|
|
20
|
+
interface?: string;
|
|
21
|
+
target?: string;
|
|
22
|
+
targetCollection?: unknown;
|
|
23
|
+
isAssociationField?: () => boolean;
|
|
24
|
+
};
|
|
25
|
+
|
|
17
26
|
describe('fieldAssignOptions', () => {
|
|
18
27
|
it('marks subform node as non-leaf so Cascader can lazy-load target collection fields', () => {
|
|
19
28
|
const usersCollection = {
|
|
@@ -98,8 +107,58 @@ describe('fieldAssignOptions', () => {
|
|
|
98
107
|
).toBe(true);
|
|
99
108
|
});
|
|
100
109
|
|
|
110
|
+
it('preserves configured display association field paths as nested cascader options', () => {
|
|
111
|
+
const orgNameField = {
|
|
112
|
+
name: 'orgname',
|
|
113
|
+
title: '公司名称(单行文本)',
|
|
114
|
+
interface: 'input',
|
|
115
|
+
type: 'string',
|
|
116
|
+
isAssociationField: () => false,
|
|
117
|
+
};
|
|
118
|
+
const orgCollection = {
|
|
119
|
+
getFields: () => [orgNameField],
|
|
120
|
+
getField: (name: string) => (name === 'orgname' ? orgNameField : null),
|
|
121
|
+
};
|
|
122
|
+
const orgAssociationField = {
|
|
123
|
+
name: 'org_m2o',
|
|
124
|
+
title: 'org_m2o',
|
|
125
|
+
interface: 'm2o',
|
|
126
|
+
type: 'belongsTo',
|
|
127
|
+
target: 'org',
|
|
128
|
+
isAssociationField: () => true,
|
|
129
|
+
targetCollection: orgCollection,
|
|
130
|
+
};
|
|
131
|
+
const rootCollection = {
|
|
132
|
+
getField: (name: string) => (name === 'org_m2o' ? orgAssociationField : null),
|
|
133
|
+
getFields: () => [orgAssociationField],
|
|
134
|
+
};
|
|
135
|
+
const displayAssociationItemModel = {
|
|
136
|
+
props: { label: '公司名称(单行文本)' },
|
|
137
|
+
getStepParams: (flowKey: string, stepKey: string) => {
|
|
138
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') return { fieldPath: 'org_m2o.orgname' };
|
|
139
|
+
return {};
|
|
140
|
+
},
|
|
141
|
+
subModels: {
|
|
142
|
+
field: {
|
|
143
|
+
context: { collectionField: orgNameField },
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
const formBlockModel = {
|
|
148
|
+
context: { collection: rootCollection },
|
|
149
|
+
subModels: { grid: { subModels: { items: [displayAssociationItemModel] } } },
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const options = collectFieldAssignCascaderOptions({ formBlockModel, t: (s) => s });
|
|
153
|
+
const orgOption = options.find((option) => option.value === 'org_m2o');
|
|
154
|
+
|
|
155
|
+
expect(orgOption?.isLeaf).toBe(false);
|
|
156
|
+
expect(orgOption?.children?.some((child) => child.value === 'orgname' && child.isLeaf === true)).toBe(true);
|
|
157
|
+
expect(options.some((option) => option.value === 'orgname')).toBe(false);
|
|
158
|
+
});
|
|
159
|
+
|
|
101
160
|
it('hides configured association fields deeper than relation / relation / field', () => {
|
|
102
|
-
const makeItem = (fieldPath: string, field:
|
|
161
|
+
const makeItem = (fieldPath: string, field: TestCollectionField, label: string) => ({
|
|
103
162
|
props: { label },
|
|
104
163
|
getStepParams: (flowKey: string, stepKey: string) => {
|
|
105
164
|
if (flowKey === 'fieldSettings' && stepKey === 'init') return { fieldPath };
|
|
@@ -141,9 +200,98 @@ describe('fieldAssignOptions', () => {
|
|
|
141
200
|
};
|
|
142
201
|
|
|
143
202
|
const options = collectFieldAssignCascaderOptions({ formBlockModel, t: (s) => s });
|
|
203
|
+
const usersOption = options.find((item) => item.value === 'users');
|
|
204
|
+
const departmentOption = usersOption?.children?.find((item) => item.value === 'department');
|
|
205
|
+
|
|
206
|
+
expect(departmentOption?.children?.map((item) => item.value)).toContain('name');
|
|
207
|
+
expect(departmentOption?.children?.map((item) => item.value)).not.toContain('company');
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('hides configured non-association fields under a third association level', () => {
|
|
211
|
+
const codeField = {
|
|
212
|
+
name: 'code',
|
|
213
|
+
title: 'Code',
|
|
214
|
+
interface: 'input',
|
|
215
|
+
isAssociationField: () => false,
|
|
216
|
+
};
|
|
217
|
+
const companyCollection = {
|
|
218
|
+
getField: (name: string) => (name === 'code' ? codeField : null),
|
|
219
|
+
getFields: () => [codeField],
|
|
220
|
+
};
|
|
221
|
+
const companyField = {
|
|
222
|
+
name: 'company',
|
|
223
|
+
title: 'Company',
|
|
224
|
+
interface: 'm2o',
|
|
225
|
+
target: 'companies',
|
|
226
|
+
isAssociationField: () => true,
|
|
227
|
+
targetCollection: companyCollection,
|
|
228
|
+
};
|
|
229
|
+
const nameField = {
|
|
230
|
+
name: 'name',
|
|
231
|
+
title: 'Name',
|
|
232
|
+
interface: 'input',
|
|
233
|
+
isAssociationField: () => false,
|
|
234
|
+
};
|
|
235
|
+
const departmentCollection = {
|
|
236
|
+
getField: (name: string) => (name === 'name' ? nameField : name === 'company' ? companyField : null),
|
|
237
|
+
getFields: () => [nameField, companyField],
|
|
238
|
+
};
|
|
239
|
+
const departmentField = {
|
|
240
|
+
name: 'department',
|
|
241
|
+
title: 'Department',
|
|
242
|
+
interface: 'm2o',
|
|
243
|
+
target: 'departments',
|
|
244
|
+
isAssociationField: () => true,
|
|
245
|
+
targetCollection: departmentCollection,
|
|
246
|
+
};
|
|
247
|
+
const userCollection = {
|
|
248
|
+
getField: (name: string) => (name === 'department' ? departmentField : null),
|
|
249
|
+
getFields: () => [departmentField],
|
|
250
|
+
};
|
|
251
|
+
const usersField = {
|
|
252
|
+
name: 'users',
|
|
253
|
+
title: 'Users',
|
|
254
|
+
interface: 'm2m',
|
|
255
|
+
target: 'users',
|
|
256
|
+
isAssociationField: () => true,
|
|
257
|
+
targetCollection: userCollection,
|
|
258
|
+
};
|
|
259
|
+
const rootCollection = {
|
|
260
|
+
getField: (name: string) => (name === 'users' ? usersField : null),
|
|
261
|
+
getFields: () => [usersField],
|
|
262
|
+
};
|
|
263
|
+
const makeItem = (fieldPath: string, field: TestCollectionField, label: string) => ({
|
|
264
|
+
props: { label },
|
|
265
|
+
getStepParams: (flowKey: string, stepKey: string) => {
|
|
266
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') return { fieldPath };
|
|
267
|
+
return {};
|
|
268
|
+
},
|
|
269
|
+
subModels: {
|
|
270
|
+
field: {
|
|
271
|
+
context: { collectionField: field },
|
|
272
|
+
},
|
|
273
|
+
},
|
|
274
|
+
});
|
|
275
|
+
const formBlockModel = {
|
|
276
|
+
context: { collection: rootCollection },
|
|
277
|
+
subModels: {
|
|
278
|
+
grid: {
|
|
279
|
+
subModels: {
|
|
280
|
+
items: [
|
|
281
|
+
makeItem('users.department.name', nameField, 'Department name'),
|
|
282
|
+
makeItem('users.department.company.code', codeField, 'Company code'),
|
|
283
|
+
],
|
|
284
|
+
},
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const options = collectFieldAssignCascaderOptions({ formBlockModel, t: (s) => s });
|
|
290
|
+
const usersOption = options.find((item) => item.value === 'users');
|
|
291
|
+
const departmentOption = usersOption?.children?.find((item) => item.value === 'department');
|
|
144
292
|
|
|
145
|
-
expect(
|
|
146
|
-
expect(
|
|
293
|
+
expect(departmentOption?.children?.map((item) => item.value)).toContain('name');
|
|
294
|
+
expect(departmentOption?.children?.map((item) => item.value)).not.toContain('company');
|
|
147
295
|
});
|
|
148
296
|
|
|
149
297
|
it('hides lazy-loaded association selector options after two association levels', () => {
|