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

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 (68) hide show
  1. package/es/components/form/JsonTextArea.d.ts +18 -0
  2. package/es/components/index.d.ts +1 -0
  3. package/es/flow/actions/dateRangeLimit.d.ts +9 -0
  4. package/es/flow/actions/index.d.ts +2 -1
  5. package/es/flow/actions/linkageRules.d.ts +2 -0
  6. package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +4 -0
  7. package/es/flow/admin-shell/admin-layout/AdminLayoutModel.d.ts +7 -0
  8. package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +5 -0
  9. package/es/flow/models/base/PageModel/PageModel.d.ts +4 -0
  10. package/es/flow/models/base/PageModel/RootPageModel.d.ts +9 -0
  11. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +7 -0
  12. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +5 -0
  13. package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +20 -0
  14. package/es/flow/models/fields/JSEditableFieldModel.d.ts +4 -0
  15. package/es/index.mjs +79 -67
  16. package/lib/index.js +80 -68
  17. package/package.json +6 -5
  18. package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +67 -46
  19. package/src/__tests__/settings-center.test.tsx +30 -0
  20. package/src/components/form/JsonTextArea.tsx +129 -0
  21. package/src/components/index.ts +1 -0
  22. package/src/flow/__tests__/FlowRoute.test.tsx +4 -5
  23. package/src/flow/actions/__tests__/actionLinkageRules.race.repro.test.ts +199 -0
  24. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +478 -0
  25. package/src/flow/actions/__tests__/linkageRules.formValueDrivenRefresh.test.ts +6 -1
  26. package/src/flow/actions/__tests__/linkageRules.menu.test.ts +90 -0
  27. package/src/flow/actions/__tests__/pattern.test.ts +190 -0
  28. package/src/flow/actions/dateRangeLimit.tsx +66 -0
  29. package/src/flow/actions/index.ts +3 -0
  30. package/src/flow/actions/linkageRules.tsx +194 -42
  31. package/src/flow/actions/linkageRulesFormValueRefresh.ts +2 -8
  32. package/src/flow/actions/openView.tsx +2 -1
  33. package/src/flow/actions/pattern.tsx +25 -2
  34. package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +7 -1
  35. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +117 -0
  36. package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +8 -1
  37. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +70 -12
  38. package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +26 -87
  39. package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +11 -0
  40. package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +5 -1
  41. package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +292 -31
  42. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +50 -12
  43. package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +77 -56
  44. package/src/flow/components/AdminLayout.tsx +2 -2
  45. package/src/flow/components/FlowRoute.tsx +17 -4
  46. package/src/flow/models/base/PageModel/PageModel.tsx +15 -3
  47. package/src/flow/models/base/PageModel/RootPageModel.tsx +37 -2
  48. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +73 -0
  49. package/src/flow/models/base/PageModel/__tests__/RootPageModel.test.ts +116 -0
  50. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +167 -1
  51. package/src/flow/models/blocks/form/value-runtime/runtime.ts +103 -11
  52. package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +4 -0
  53. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +34 -3
  54. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +47 -0
  55. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +42 -0
  56. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableField.refresh.test.tsx +122 -0
  57. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -1
  58. package/src/flow/models/fields/ClickableFieldModel.tsx +21 -9
  59. package/src/flow/models/fields/DateTimeFieldModel/DateOnlyFieldModel.tsx +9 -0
  60. package/src/flow/models/fields/DateTimeFieldModel/DateTimeFieldModel.tsx +4 -0
  61. package/src/flow/models/fields/DateTimeFieldModel/DateTimeNoTzFieldModel.tsx +9 -0
  62. package/src/flow/models/fields/DateTimeFieldModel/DateTimeTzFieldModel.tsx +9 -0
  63. package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +242 -0
  64. package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +152 -0
  65. package/src/flow/models/fields/JSEditableFieldModel.tsx +110 -14
  66. package/src/flow/models/fields/__tests__/ClickableFieldModel.test.ts +87 -0
  67. package/src/flow/models/fields/__tests__/JSEditableFieldModel.test.tsx +210 -0
  68. package/src/flow/system-settings/useSystemSettings.tsx +36 -1
@@ -535,4 +535,482 @@ describe('fieldLinkageRules action - linkage scope metadata', () => {
535
535
  linkageScopeDepth: 0,
536
536
  });
537
537
  });
538
+
539
+ it('runs row-scoped linkage rules for subtable row forks without subModels.items and deduplicates the same row', async () => {
540
+ const setFormValues = vi.fn(async () => undefined);
541
+ const linkageAssignHandler = vi.fn((actionCtx: any, { value, addFormValuePatch }: any) => {
542
+ expect(actionCtx.linkageScopeDepth).toBe(0);
543
+ expect(actionCtx.item?.value?.uid).toBe('role-uid-1');
544
+ expect(value[0]?.value).toBe('role-uid-1');
545
+ addFormValuePatch({ path: 'roles.name', value: value[0].value });
546
+ });
547
+
548
+ const engine = new FlowEngine();
549
+ const makeRowFork = (uid: string) => {
550
+ const rowFork = new FlowModel({ uid, flowEngine: engine }) as any;
551
+ rowFork.context.defineMethod('subTableRowFork', () => true);
552
+ rowFork.context.defineProperty('fieldIndex', {
553
+ value: ['roles:0'],
554
+ });
555
+ rowFork.context.defineProperty('item', {
556
+ value: {
557
+ index: 0,
558
+ __is_new__: true,
559
+ value: {
560
+ uid: 'role-uid-1',
561
+ },
562
+ },
563
+ });
564
+ rowFork.context.defineProperty('setFormValues', {
565
+ value: setFormValues,
566
+ });
567
+ rowFork.context.defineProperty('app', {
568
+ value: {
569
+ jsonLogic: {
570
+ apply: () => true,
571
+ },
572
+ },
573
+ });
574
+ rowFork.getAction = vi.fn((name: string) => {
575
+ if (name === 'linkageAssignField') {
576
+ return {
577
+ handler: linkageAssignHandler,
578
+ };
579
+ }
580
+ });
581
+ return rowFork;
582
+ };
583
+
584
+ const rowFork1 = makeRowFork('role-row-fork-1');
585
+ const rowFork2 = makeRowFork('role-row-fork-2');
586
+
587
+ const masterModel1: any = {
588
+ uid: 'master-role-grid-1',
589
+ forks: new Set([rowFork1]),
590
+ };
591
+ const masterModel2: any = {
592
+ uid: 'master-role-grid-2',
593
+ forks: new Set([rowFork2]),
594
+ };
595
+
596
+ const rolesField: any = {
597
+ type: 'hasMany',
598
+ isAssociationField: () => true,
599
+ targetCollection: {
600
+ getField: (name: string) => ({ name, isAssociationField: () => false }),
601
+ },
602
+ };
603
+
604
+ const gridModel: any = {
605
+ uid: 'grid-model-role-row',
606
+ context: {
607
+ blockModel: {
608
+ collection: {
609
+ getField: (name: string) => (name === 'roles' ? rolesField : null),
610
+ },
611
+ },
612
+ },
613
+ getAction: vi.fn((name: string) => {
614
+ if (name === 'linkageAssignField') {
615
+ return {
616
+ handler: linkageAssignHandler,
617
+ };
618
+ }
619
+ }),
620
+ __allModels: [],
621
+ };
622
+
623
+ const ctx: any = {
624
+ model: gridModel,
625
+ engine: {
626
+ forEachModel: (cb: (m: any) => void) => {
627
+ cb(masterModel1);
628
+ cb(masterModel2);
629
+ },
630
+ },
631
+ flowKey: 'eventSettings',
632
+ inputArgs: {
633
+ source: 'user',
634
+ txId: 'tx-role-row',
635
+ changedPaths: [['roles', 0, 'uid']],
636
+ },
637
+ app: {
638
+ jsonLogic: {
639
+ apply: () => true,
640
+ },
641
+ },
642
+ resolveJsonTemplate: async (v: any) => {
643
+ if (v === '{{ ctx.item.value.uid }}') {
644
+ return 'role-uid-1';
645
+ }
646
+ return v;
647
+ },
648
+ };
649
+
650
+ await fieldLinkageRules.handler(ctx, {
651
+ value: [
652
+ {
653
+ key: 'rule-role-row',
654
+ title: 'rule-role-row',
655
+ enable: true,
656
+ condition: { logic: '$and', items: [] },
657
+ actions: [
658
+ {
659
+ name: 'linkageAssignField',
660
+ params: {
661
+ value: [
662
+ {
663
+ key: 'r1',
664
+ enable: true,
665
+ targetPath: 'roles.name',
666
+ mode: 'assign',
667
+ value: '{{ ctx.item.value.uid }}',
668
+ condition: { logic: '$and', items: [] },
669
+ },
670
+ ],
671
+ },
672
+ },
673
+ ],
674
+ },
675
+ ],
676
+ });
677
+
678
+ expect(linkageAssignHandler).toHaveBeenCalledTimes(1);
679
+ expect(setFormValues).toHaveBeenCalledTimes(1);
680
+ expect(setFormValues).toHaveBeenCalledWith(
681
+ [
682
+ {
683
+ path: ['roles', 0, 'name'],
684
+ value: 'role-uid-1',
685
+ },
686
+ ],
687
+ expect.objectContaining({
688
+ source: 'linkage',
689
+ linkageTxId: 'tx-role-row',
690
+ linkageScopeDepth: 0,
691
+ }),
692
+ );
693
+ });
694
+
695
+ it('keeps row-scoped default patches following while current value is still the last default', async () => {
696
+ const store = {
697
+ roles: [{ uid: 'role-uid-1', name: '' }],
698
+ };
699
+ const getAt = (path: Array<string | number>) => path.reduce((acc: any, seg) => acc?.[seg], store as any);
700
+ const setAt = (path: Array<string | number>, value: any) => {
701
+ let cur: any = store;
702
+ for (const seg of path.slice(0, -1)) {
703
+ cur = cur[seg];
704
+ }
705
+ cur[path[path.length - 1]] = value;
706
+ };
707
+ const pathKey = (path: Array<string | number>) => JSON.stringify(path);
708
+ const lastDefaults = new Map<string, any>();
709
+ const form = {
710
+ getFieldValue: (path: Array<string | number>) => getAt(path),
711
+ };
712
+ const formValueRuntime = {
713
+ canApplyDefaultValuePatch: vi.fn((path: Array<string | number>, value: any) => {
714
+ const current = getAt(path);
715
+ const last = lastDefaults.get(pathKey(path));
716
+ if (current === undefined || current === null || current === '') return true;
717
+ if (typeof last !== 'undefined' && current === last) return true;
718
+ if (current === value) {
719
+ lastDefaults.set(pathKey(path), value);
720
+ }
721
+ return false;
722
+ }),
723
+ recordDefaultValuePatch: vi.fn((path: Array<string | number>, value: any) => {
724
+ lastDefaults.set(pathKey(path), value);
725
+ }),
726
+ };
727
+ const setFormValues = vi.fn(async (patches: Array<{ path: Array<string | number>; value: any }>) => {
728
+ for (const patch of patches) {
729
+ setAt(patch.path, patch.value);
730
+ }
731
+ });
732
+ const defaultHandler = vi.fn((actionCtx: any, { value, addFormValuePatch }: any) => {
733
+ addFormValuePatch({ path: value[0].targetPath, value: value[0].value, whenEmpty: true });
734
+ });
735
+
736
+ const engine = new FlowEngine();
737
+ const rowFork = new FlowModel({ uid: 'role-default-row-fork', flowEngine: engine }) as any;
738
+ rowFork.context.defineProperty('subTableRowFork', {
739
+ value: true,
740
+ });
741
+ rowFork.context.defineProperty('fieldIndex', {
742
+ value: ['roles:0'],
743
+ });
744
+ rowFork.context.defineProperty('form', {
745
+ value: form,
746
+ });
747
+ rowFork.context.defineProperty('item', {
748
+ get: () => ({
749
+ index: 0,
750
+ __is_new__: true,
751
+ value: store.roles[0],
752
+ }),
753
+ });
754
+ rowFork.context.defineProperty('setFormValues', {
755
+ value: setFormValues,
756
+ });
757
+ rowFork.context.defineProperty('app', {
758
+ value: {
759
+ jsonLogic: {
760
+ apply: () => true,
761
+ },
762
+ },
763
+ });
764
+ rowFork.getAction = vi.fn((name: string) => {
765
+ if (name === 'setFieldsDefaultValue') {
766
+ return {
767
+ handler: defaultHandler,
768
+ };
769
+ }
770
+ });
771
+
772
+ const masterModel: any = {
773
+ uid: 'master-role-default-grid',
774
+ forks: new Set([rowFork]),
775
+ };
776
+ const rolesField: any = {
777
+ type: 'hasMany',
778
+ isAssociationField: () => true,
779
+ targetCollection: {
780
+ getField: (name: string) => ({ name, isAssociationField: () => false }),
781
+ },
782
+ };
783
+ const blockModel: any = {
784
+ collection: {
785
+ getField: (name: string) => (name === 'roles' ? rolesField : null),
786
+ },
787
+ formValueRuntime,
788
+ };
789
+ rowFork.context.defineProperty('blockModel', {
790
+ value: blockModel,
791
+ });
792
+ const gridModel: any = {
793
+ uid: 'grid-model-role-default-row',
794
+ context: {
795
+ blockModel,
796
+ },
797
+ getAction: vi.fn((name: string) => {
798
+ if (name === 'setFieldsDefaultValue') {
799
+ return {
800
+ handler: defaultHandler,
801
+ };
802
+ }
803
+ }),
804
+ __allModels: [],
805
+ };
806
+ const ctx: any = {
807
+ model: gridModel,
808
+ engine: {
809
+ forEachModel: (cb: (m: any) => void) => {
810
+ cb(masterModel);
811
+ },
812
+ },
813
+ flowKey: 'eventSettings',
814
+ inputArgs: {
815
+ source: 'user',
816
+ txId: 'tx-role-default-row',
817
+ changedPaths: [['roles', 0, 'uid']],
818
+ },
819
+ app: {
820
+ jsonLogic: {
821
+ apply: () => true,
822
+ },
823
+ },
824
+ resolveJsonTemplate: async (v: any) => v,
825
+ };
826
+ const makeParams = (value: string) => ({
827
+ value: [
828
+ {
829
+ key: `rule-${value}`,
830
+ title: `rule-${value}`,
831
+ enable: true,
832
+ condition: { logic: '$and', items: [] },
833
+ actions: [
834
+ {
835
+ name: 'setFieldsDefaultValue',
836
+ params: {
837
+ value: [
838
+ {
839
+ key: `r-${value}`,
840
+ enable: true,
841
+ targetPath: 'roles.name',
842
+ mode: 'default',
843
+ value,
844
+ condition: { logic: '$and', items: [] },
845
+ },
846
+ ],
847
+ },
848
+ },
849
+ ],
850
+ },
851
+ ],
852
+ });
853
+
854
+ await fieldLinkageRules.handler(ctx, makeParams('role-uid-1'));
855
+ expect(store.roles[0].name).toBe('role-uid-1');
856
+
857
+ store.roles[0].uid = 'role-uid-2';
858
+ await fieldLinkageRules.handler(ctx, makeParams('role-uid-2'));
859
+ expect(store.roles[0].name).toBe('role-uid-2');
860
+ expect(setFormValues).toHaveBeenCalledTimes(2);
861
+ expect(formValueRuntime.canApplyDefaultValuePatch).toHaveBeenCalledTimes(2);
862
+ expect(formValueRuntime.recordDefaultValuePatch).toHaveBeenCalledTimes(2);
863
+
864
+ store.roles[0].name = 'manual';
865
+ store.roles[0].uid = 'role-uid-3';
866
+ await fieldLinkageRules.handler(ctx, makeParams('role-uid-3'));
867
+ expect(store.roles[0].name).toBe('manual');
868
+ expect(setFormValues).toHaveBeenCalledTimes(2);
869
+ });
870
+
871
+ it('skips stale subtable row forks after the row has been removed', async () => {
872
+ const setFormValues = vi.fn(async () => undefined);
873
+ const defaultHandler = vi.fn((actionCtx: any, { addFormValuePatch }: any) => {
874
+ addFormValuePatch({ path: 'roles.title', value: 'stale-title', whenEmpty: true });
875
+ });
876
+ const form = {
877
+ getFieldValue: vi.fn((path: Array<string | number>) => {
878
+ if (JSON.stringify(path) === JSON.stringify(['roles', 2])) {
879
+ return undefined;
880
+ }
881
+ if (JSON.stringify(path) === JSON.stringify(['roles'])) {
882
+ return [
883
+ { name: '1', title: '1' },
884
+ { name: '3', title: '3' },
885
+ ];
886
+ }
887
+ }),
888
+ };
889
+
890
+ const engine = new FlowEngine();
891
+ const staleRowFork = new FlowModel({ uid: 'stale-role-row-fork', flowEngine: engine }) as any;
892
+ staleRowFork.context.defineProperty('subTableRowFork', {
893
+ value: true,
894
+ });
895
+ staleRowFork.context.defineProperty('fieldIndex', {
896
+ value: ['roles:2'],
897
+ });
898
+ staleRowFork.context.defineProperty('form', {
899
+ value: form,
900
+ });
901
+ staleRowFork.context.defineProperty('item', {
902
+ value: {
903
+ index: 2,
904
+ __is_new__: true,
905
+ value: { name: '3', title: '3' },
906
+ },
907
+ });
908
+ staleRowFork.context.defineProperty('setFormValues', {
909
+ value: setFormValues,
910
+ });
911
+ staleRowFork.context.defineProperty('app', {
912
+ value: {
913
+ jsonLogic: {
914
+ apply: () => true,
915
+ },
916
+ },
917
+ });
918
+ staleRowFork.getAction = vi.fn((name: string) => {
919
+ if (name === 'setFieldsDefaultValue') {
920
+ return {
921
+ handler: defaultHandler,
922
+ };
923
+ }
924
+ });
925
+
926
+ const masterModel: any = {
927
+ uid: 'master-stale-role-grid',
928
+ forks: new Set([staleRowFork]),
929
+ };
930
+ const rolesField: any = {
931
+ type: 'hasMany',
932
+ isAssociationField: () => true,
933
+ targetCollection: {
934
+ getField: (name: string) => ({ name, isAssociationField: () => false }),
935
+ },
936
+ };
937
+ const blockModel: any = {
938
+ collection: {
939
+ getField: (name: string) => (name === 'roles' ? rolesField : null),
940
+ },
941
+ formValueRuntime: {
942
+ canApplyDefaultValuePatch: vi.fn(() => true),
943
+ recordDefaultValuePatch: vi.fn(),
944
+ },
945
+ };
946
+ staleRowFork.context.defineProperty('blockModel', {
947
+ value: blockModel,
948
+ });
949
+
950
+ const gridModel: any = {
951
+ uid: 'grid-model-stale-role-row',
952
+ context: {
953
+ blockModel,
954
+ },
955
+ getAction: vi.fn((name: string) => {
956
+ if (name === 'setFieldsDefaultValue') {
957
+ return {
958
+ handler: defaultHandler,
959
+ };
960
+ }
961
+ }),
962
+ __allModels: [],
963
+ };
964
+ const ctx: any = {
965
+ model: gridModel,
966
+ engine: {
967
+ forEachModel: (cb: (m: any) => void) => {
968
+ cb(masterModel);
969
+ },
970
+ },
971
+ flowKey: 'eventSettings',
972
+ inputArgs: {
973
+ source: 'user',
974
+ txId: 'tx-stale-row',
975
+ changedPaths: [['roles']],
976
+ },
977
+ app: {
978
+ jsonLogic: {
979
+ apply: () => true,
980
+ },
981
+ },
982
+ resolveJsonTemplate: async (v: any) => v,
983
+ };
984
+
985
+ await fieldLinkageRules.handler(ctx, {
986
+ value: [
987
+ {
988
+ key: 'rule-stale-row',
989
+ title: 'rule-stale-row',
990
+ enable: true,
991
+ condition: { logic: '$and', items: [] },
992
+ actions: [
993
+ {
994
+ name: 'setFieldsDefaultValue',
995
+ params: {
996
+ value: [
997
+ {
998
+ key: 'r-stale-row',
999
+ enable: true,
1000
+ targetPath: 'roles.title',
1001
+ mode: 'default',
1002
+ value: 'stale-title',
1003
+ condition: { logic: '$and', items: [] },
1004
+ },
1005
+ ],
1006
+ },
1007
+ },
1008
+ ],
1009
+ },
1010
+ ],
1011
+ });
1012
+
1013
+ expect(defaultHandler).not.toHaveBeenCalled();
1014
+ expect(setFormValues).not.toHaveBeenCalled();
1015
+ });
538
1016
  });
@@ -26,7 +26,12 @@ function createRule(overrides: any = {}) {
26
26
 
27
27
  function createRuntime(
28
28
  params: any,
29
- options: { fieldIndex?: string[]; fieldIndexRef?: { current: string[] }; actionHandler?: any; engineEmitter?: any } = {},
29
+ options: {
30
+ fieldIndex?: string[];
31
+ fieldIndexRef?: { current: string[] };
32
+ actionHandler?: any;
33
+ engineEmitter?: any;
34
+ } = {},
30
35
  ) {
31
36
  const formEmitter = new EventEmitter();
32
37
  const formBlock: any = {
@@ -0,0 +1,90 @@
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 { ActionScene } from '@nocobase/flow-engine';
11
+ import { describe, expect, it, vi } from 'vitest';
12
+
13
+ import * as registeredActions from '../index';
14
+ import { linkageRunjs, linkageSetMenuItemProps, menuLinkageRules } from '../linkageRules';
15
+
16
+ describe('menu linkage rules', () => {
17
+ it('should register menu linkage actions from the actions entry', () => {
18
+ expect(registeredActions.menuLinkageRules).toBe(menuLinkageRules);
19
+ expect(registeredActions.linkageSetMenuItemProps).toBe(linkageSetMenuItemProps);
20
+ });
21
+
22
+ it('should expose menu state and runjs actions in ui schema', () => {
23
+ const schema = menuLinkageRules.uiSchema?.({
24
+ getActions: () =>
25
+ new Map([
26
+ ['linkageSetMenuItemProps', linkageSetMenuItemProps],
27
+ ['linkageRunjs', linkageRunjs],
28
+ [
29
+ 'actionOnly',
30
+ {
31
+ name: 'actionOnly',
32
+ scene: ActionScene.ACTION_LINKAGE_RULES,
33
+ },
34
+ ],
35
+ ]),
36
+ } as any) as any;
37
+
38
+ expect(schema?.value?.['x-component-props']?.supportedActions).toEqual(['linkageSetMenuItemProps', 'linkageRunjs']);
39
+ });
40
+
41
+ it('should set menu model hidden through common linkage handler', async () => {
42
+ const setProps = vi.fn(function (this: any, props: any) {
43
+ this.props = {
44
+ ...this.props,
45
+ ...props,
46
+ };
47
+ });
48
+ const setHidden = vi.fn(function (this: any, value: boolean) {
49
+ this.hidden = value;
50
+ });
51
+ const model: any = {
52
+ uid: 'menu-item-1',
53
+ props: {},
54
+ hidden: false,
55
+ __allModels: [],
56
+ setProps,
57
+ setHidden,
58
+ };
59
+ const ctx: any = {
60
+ app: { jsonLogic: { apply: () => true } },
61
+ model,
62
+ t: (text: string) => text,
63
+ getAction: (name: string) => (name === 'linkageSetMenuItemProps' ? linkageSetMenuItemProps : undefined),
64
+ resolveJsonTemplate: vi.fn(async (value: any) => value),
65
+ };
66
+
67
+ await menuLinkageRules.handler(ctx, {
68
+ value: [
69
+ {
70
+ key: 'r1',
71
+ title: 'Hide menu item',
72
+ enable: true,
73
+ condition: { logic: '$and', items: [] },
74
+ actions: [
75
+ {
76
+ key: 'a1',
77
+ name: 'linkageSetMenuItemProps',
78
+ params: {
79
+ value: 'hidden',
80
+ },
81
+ },
82
+ ],
83
+ },
84
+ ],
85
+ });
86
+
87
+ expect(setHidden).toHaveBeenCalledWith(true);
88
+ expect(model.hidden).toBe(true);
89
+ });
90
+ });