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

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 (45) 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 +1 -0
  5. package/es/flow/models/base/PageModel/PageModel.d.ts +4 -0
  6. package/es/flow/models/base/PageModel/RootPageModel.d.ts +9 -0
  7. package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +7 -0
  8. package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +2 -0
  9. package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +20 -0
  10. package/es/flow/models/fields/JSEditableFieldModel.d.ts +4 -0
  11. package/es/index.mjs +72 -65
  12. package/lib/index.js +61 -54
  13. package/package.json +6 -5
  14. package/src/components/form/JsonTextArea.tsx +129 -0
  15. package/src/components/index.ts +1 -0
  16. package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +478 -0
  17. package/src/flow/actions/__tests__/pattern.test.ts +190 -0
  18. package/src/flow/actions/dateRangeLimit.tsx +66 -0
  19. package/src/flow/actions/index.ts +1 -0
  20. package/src/flow/actions/linkageRules.tsx +117 -19
  21. package/src/flow/actions/openView.tsx +2 -1
  22. package/src/flow/actions/pattern.tsx +25 -2
  23. package/src/flow/admin-shell/AdminLayoutRouteCoordinator.ts +7 -1
  24. package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +117 -0
  25. package/src/flow/models/base/PageModel/PageModel.tsx +15 -3
  26. package/src/flow/models/base/PageModel/RootPageModel.tsx +37 -2
  27. package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +73 -0
  28. package/src/flow/models/base/PageModel/__tests__/RootPageModel.test.ts +116 -0
  29. package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +167 -1
  30. package/src/flow/models/blocks/form/value-runtime/runtime.ts +103 -11
  31. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +27 -3
  32. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +47 -0
  33. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +42 -0
  34. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableField.refresh.test.tsx +122 -0
  35. package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +2 -0
  36. package/src/flow/models/fields/ClickableFieldModel.tsx +21 -9
  37. package/src/flow/models/fields/DateTimeFieldModel/DateOnlyFieldModel.tsx +9 -0
  38. package/src/flow/models/fields/DateTimeFieldModel/DateTimeFieldModel.tsx +4 -0
  39. package/src/flow/models/fields/DateTimeFieldModel/DateTimeNoTzFieldModel.tsx +9 -0
  40. package/src/flow/models/fields/DateTimeFieldModel/DateTimeTzFieldModel.tsx +9 -0
  41. package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +242 -0
  42. package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +152 -0
  43. package/src/flow/models/fields/JSEditableFieldModel.tsx +110 -14
  44. package/src/flow/models/fields/__tests__/ClickableFieldModel.test.ts +87 -0
  45. package/src/flow/models/fields/__tests__/JSEditableFieldModel.test.tsx +210 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nocobase/client-v2",
3
- "version": "2.1.0-beta.27",
3
+ "version": "2.1.0-beta.29",
4
4
  "license": "Apache-2.0",
5
5
  "main": "lib/index.js",
6
6
  "module": "es/index.mjs",
@@ -24,17 +24,18 @@
24
24
  "@formily/antd-v5": "1.2.3",
25
25
  "@formily/react": "^2.2.27",
26
26
  "@formily/shared": "^2.2.27",
27
- "@nocobase/flow-engine": "2.1.0-beta.27",
28
- "@nocobase/sdk": "2.1.0-beta.27",
29
- "@nocobase/shared": "2.1.0-beta.27",
27
+ "@nocobase/flow-engine": "2.1.0-beta.29",
28
+ "@nocobase/sdk": "2.1.0-beta.29",
29
+ "@nocobase/shared": "2.1.0-beta.29",
30
30
  "ahooks": "^3.7.2",
31
31
  "antd": "5.24.2",
32
32
  "classnames": "^2.3.1",
33
33
  "dayjs": "^1.11.10",
34
34
  "i18next": "^22.4.9",
35
+ "json5": "^2.2.3",
35
36
  "lodash": "4.17.21",
36
37
  "react-i18next": "^11.15.1",
37
38
  "react-router-dom": "^6.30.1"
38
39
  },
39
- "gitHead": "6d9e2d4ac3c6bf40951c8eb252860e47aa3a3c37"
40
+ "gitHead": "86c41be29dcbcac6fd6aa46b4a137ef07a27c1d0"
40
41
  }
@@ -0,0 +1,129 @@
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 { css, cx } from '@emotion/css';
11
+ import { Input, Typography } from 'antd';
12
+ import type { TextAreaProps } from 'antd/es/input';
13
+ import JSON5 from 'json5';
14
+ import React, { useCallback, useEffect, useMemo, useState } from 'react';
15
+
16
+ export interface JsonTextAreaProps extends Omit<TextAreaProps, 'value' | 'onChange'> {
17
+ value?: unknown;
18
+ onChange?: (value: unknown) => void;
19
+ space?: number;
20
+ json5?: boolean;
21
+ showError?: boolean;
22
+ }
23
+
24
+ const jsonTextAreaClassName = css`
25
+ font-size: 80%;
26
+ font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
27
+ `;
28
+
29
+ function stringifyJsonValue(value: unknown, json: typeof JSON | typeof JSON5, space: number) {
30
+ if (value == null) {
31
+ return '';
32
+ }
33
+
34
+ if (typeof value === 'string') {
35
+ try {
36
+ json.parse(value);
37
+ return value;
38
+ } catch {
39
+ return json.stringify(value, undefined, space);
40
+ }
41
+ }
42
+
43
+ return json.stringify(value, undefined, space);
44
+ }
45
+
46
+ export const JsonTextArea = React.memo((props: JsonTextAreaProps) => {
47
+ const {
48
+ value,
49
+ onChange,
50
+ space = 2,
51
+ json5 = false,
52
+ showError = true,
53
+ className,
54
+ status,
55
+ onBlur,
56
+ ...textAreaProps
57
+ } = props;
58
+ const json = json5 ? JSON5 : JSON;
59
+ const [text, setText] = useState(() => stringifyJsonValue(value, json, space));
60
+ const [error, setError] = useState<string>();
61
+
62
+ useEffect(() => {
63
+ setText(stringifyJsonValue(value, json, space));
64
+ }, [json, space, value]);
65
+
66
+ const parseText = useCallback(
67
+ (nextText: string) => {
68
+ if (nextText.trim() === '') {
69
+ return null;
70
+ }
71
+
72
+ return json.parse(nextText);
73
+ },
74
+ [json],
75
+ );
76
+
77
+ const handleChange = useCallback(
78
+ (event: React.ChangeEvent<HTMLTextAreaElement>) => {
79
+ const nextText = event.target.value;
80
+ setText(nextText);
81
+
82
+ try {
83
+ parseText(nextText);
84
+ setError(undefined);
85
+ } catch (err) {
86
+ setError(err instanceof Error ? err.message : String(err));
87
+ }
88
+ },
89
+ [parseText],
90
+ );
91
+
92
+ const handleBlur = useCallback(
93
+ (event: React.FocusEvent<HTMLTextAreaElement>) => {
94
+ try {
95
+ const parsed = parseText(event.target.value);
96
+ setError(undefined);
97
+ setText(parsed == null ? '' : json.stringify(parsed, undefined, space));
98
+ onChange?.(parsed);
99
+ } catch (err) {
100
+ setError(err instanceof Error ? err.message : String(err));
101
+ }
102
+
103
+ onBlur?.(event);
104
+ },
105
+ [json, onBlur, onChange, parseText, space],
106
+ );
107
+
108
+ const mergedStatus = useMemo(() => status || (error ? 'error' : undefined), [error, status]);
109
+
110
+ return (
111
+ <>
112
+ <Input.TextArea
113
+ {...textAreaProps}
114
+ value={text}
115
+ onChange={handleChange}
116
+ onBlur={handleBlur}
117
+ status={mergedStatus}
118
+ className={cx(jsonTextAreaClassName, className)}
119
+ />
120
+ {showError && error ? (
121
+ <Typography.Text type="danger" style={{ display: 'block', marginTop: 4 }}>
122
+ {error}
123
+ </Typography.Text>
124
+ ) : null}
125
+ </>
126
+ );
127
+ });
128
+
129
+ JsonTextArea.displayName = 'JsonTextArea';
@@ -10,4 +10,5 @@
10
10
  export * from './AppComponents';
11
11
  export * from './BlankComponent';
12
12
  export * from './Icon';
13
+ export * from './form/JsonTextArea';
13
14
  export * from './RouterContextCleaner';
@@ -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
  });