@nocobase/client-v2 2.2.0-beta.5 → 2.2.0-beta.6
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/RouteRepository.d.ts +21 -6
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +4 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/index.d.ts +2 -1
- package/es/index.mjs +143 -117
- package/lib/index.js +148 -122
- package/package.json +7 -7
- package/src/RouteRepository.ts +126 -24
- package/src/__tests__/RouteRepository.test.ts +216 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/settings-center.test.tsx +33 -0
- package/src/components/form/JsonTextArea.tsx +21 -11
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +1 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +443 -5
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
- package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/linkageRules.tsx +122 -34
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +33 -10
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +310 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +44 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +21 -8
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +166 -2
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/FieldAssignRulesEditor.tsx +62 -12
- package/src/flow/components/FlowRoute.tsx +56 -11
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +81 -4
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
- package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
- package/src/flow/models/actions/JSActionModel.tsx +2 -7
- package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
- package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
- package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
- package/src/flow/models/base/ActionModel.tsx +12 -1
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
- package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +31 -3
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +77 -0
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +880 -102
- package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +257 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +2 -7
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +1 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +13 -3
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +54 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +29 -6
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
- package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
- package/src/flow/models/fields/InputFieldModel.tsx +14 -22
- package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
- package/src/flow/models/fields/JSFieldModel.tsx +2 -7
- package/src/flow/models/fields/JSItemModel.tsx +2 -14
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +17 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +93 -10
- package/src/flow-compat/FieldValidation.tsx +122 -60
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/index.ts +8 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/settings-center/AdminSettingsLayout.tsx +3 -0
- package/src/settings-center/plugin-manager/index.tsx +3 -0
|
@@ -14,6 +14,7 @@ import dayjs from 'dayjs';
|
|
|
14
14
|
import { observable } from '@formily/reactive';
|
|
15
15
|
import { get as lodashGet, merge as lodashMerge, set as lodashSet } from 'lodash';
|
|
16
16
|
import { FlowContext, JSRunner } from '@nocobase/flow-engine';
|
|
17
|
+
import { getValuesByPath } from '@nocobase/shared';
|
|
17
18
|
import { FormValueRuntime } from '..';
|
|
18
19
|
import type { FormInstance } from 'antd';
|
|
19
20
|
|
|
@@ -95,6 +96,10 @@ function createFieldContext(runtime: FormValueRuntime) {
|
|
|
95
96
|
if (match) {
|
|
96
97
|
const pathString = match[1];
|
|
97
98
|
if (!pathString) return this;
|
|
99
|
+
if (pathString === 'formValues') return this.formValues;
|
|
100
|
+
if (pathString.startsWith('formValues.')) {
|
|
101
|
+
return getValuesByPath(runtime.getFormValuesSnapshot(), pathString.slice('formValues.'.length));
|
|
102
|
+
}
|
|
98
103
|
const segs = pathString.split('.').filter(Boolean);
|
|
99
104
|
return lodashGet(this, segs);
|
|
100
105
|
}
|
|
@@ -747,13 +752,13 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
747
752
|
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('Y'));
|
|
748
753
|
});
|
|
749
754
|
|
|
750
|
-
it('mode=
|
|
755
|
+
it('mode=assign follows to-many association title changes without clearing on transient empty arrays', async () => {
|
|
751
756
|
const engineEmitter = new EventEmitter();
|
|
752
757
|
const blockEmitter = new EventEmitter();
|
|
753
|
-
const formStub = createFormStub({
|
|
758
|
+
const formStub = createFormStub({ roles: [] });
|
|
754
759
|
|
|
755
760
|
const blockModel: any = {
|
|
756
|
-
uid: 'form-assign-
|
|
761
|
+
uid: 'form-assign-to-many-source-title',
|
|
757
762
|
flowEngine: { emitter: engineEmitter },
|
|
758
763
|
emitter: blockEmitter,
|
|
759
764
|
dispatchEvent: vi.fn(),
|
|
@@ -764,46 +769,66 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
764
769
|
runtime.mount({ sync: true });
|
|
765
770
|
|
|
766
771
|
const blockCtx = createFieldContext(runtime);
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
772
|
+
const usernameField: any = { isAssociationField: () => false, interface: 'input' };
|
|
773
|
+
const rolesCollection: any = { getField: () => null, filterTargetKey: 'name' };
|
|
774
|
+
const rolesField: any = {
|
|
775
|
+
isAssociationField: () => true,
|
|
776
|
+
type: 'belongsToMany',
|
|
777
|
+
targetCollection: rolesCollection,
|
|
770
778
|
};
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
779
|
+
const collection: any = {
|
|
780
|
+
getField: (name: string) => {
|
|
781
|
+
if (name === 'username') return usernameField;
|
|
782
|
+
if (name === 'roles') return rolesField;
|
|
783
|
+
return null;
|
|
774
784
|
},
|
|
775
|
-
}
|
|
785
|
+
};
|
|
786
|
+
blockCtx.defineProperty('collection', { value: collection });
|
|
776
787
|
blockModel.context = blockCtx;
|
|
777
788
|
|
|
778
789
|
runtime.syncAssignRules([
|
|
779
790
|
{
|
|
780
|
-
key: '
|
|
791
|
+
key: 'username-from-role-title',
|
|
781
792
|
enable: true,
|
|
782
|
-
targetPath: '
|
|
783
|
-
mode: '
|
|
793
|
+
targetPath: 'username',
|
|
794
|
+
mode: 'assign',
|
|
784
795
|
condition: { logic: '$and', items: [] },
|
|
785
|
-
value: '
|
|
796
|
+
value: '{{ ctx.formValues.roles.title }}',
|
|
786
797
|
},
|
|
787
798
|
]);
|
|
788
799
|
|
|
789
|
-
await
|
|
800
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
801
|
+
expect(formStub.getFieldValue(['username'])).toBeUndefined();
|
|
790
802
|
|
|
791
|
-
|
|
792
|
-
await
|
|
793
|
-
expect(formStub.getFieldValue(['a'])).toBe('user');
|
|
803
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: '经理' }] }], { source: 'user' });
|
|
804
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('经理'));
|
|
794
805
|
|
|
795
|
-
await runtime.setFormValues(blockCtx, [{ path: ['
|
|
796
|
-
|
|
797
|
-
|
|
806
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: '经理' }, { title: 'Admin' }] }], {
|
|
807
|
+
source: 'user',
|
|
808
|
+
});
|
|
809
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('经理, Admin'));
|
|
810
|
+
|
|
811
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: null }] }], { source: 'system' });
|
|
812
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
813
|
+
expect(formStub.getFieldValue(['username'])).toBe('经理, Admin');
|
|
814
|
+
|
|
815
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ __index__: 'row-1', __is_new__: true }] }], {
|
|
816
|
+
source: 'system',
|
|
817
|
+
});
|
|
818
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
819
|
+
expect(formStub.getFieldValue(['username'])).toBe('经理, Admin');
|
|
820
|
+
|
|
821
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: '销售' }] }], { source: 'user' });
|
|
822
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('销售'));
|
|
798
823
|
});
|
|
799
824
|
|
|
800
|
-
it('
|
|
825
|
+
it('mode=assign clears scalar target when to-many association source is really cleared by user', async () => {
|
|
801
826
|
const engineEmitter = new EventEmitter();
|
|
802
827
|
const blockEmitter = new EventEmitter();
|
|
803
|
-
const formStub = createFormStub({});
|
|
828
|
+
const formStub = createFormStub({ roles: [] });
|
|
804
829
|
|
|
805
830
|
const blockModel: any = {
|
|
806
|
-
uid: 'form-assign-
|
|
831
|
+
uid: 'form-assign-to-many-source-user-clear',
|
|
807
832
|
flowEngine: { emitter: engineEmitter },
|
|
808
833
|
emitter: blockEmitter,
|
|
809
834
|
dispatchEvent: vi.fn(),
|
|
@@ -814,47 +839,103 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
814
839
|
runtime.mount({ sync: true });
|
|
815
840
|
|
|
816
841
|
const blockCtx = createFieldContext(runtime);
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
842
|
+
const usernameField: any = { isAssociationField: () => false, interface: 'input' };
|
|
843
|
+
const rolesCollection: any = { getField: () => null, filterTargetKey: 'name' };
|
|
844
|
+
const rolesField: any = {
|
|
845
|
+
isAssociationField: () => true,
|
|
846
|
+
type: 'belongsToMany',
|
|
847
|
+
targetCollection: rolesCollection,
|
|
820
848
|
};
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
849
|
+
const collection: any = {
|
|
850
|
+
getField: (name: string) => {
|
|
851
|
+
if (name === 'username') return usernameField;
|
|
852
|
+
if (name === 'roles') return rolesField;
|
|
853
|
+
return null;
|
|
824
854
|
},
|
|
825
|
-
}
|
|
855
|
+
};
|
|
856
|
+
blockCtx.defineProperty('collection', { value: collection });
|
|
826
857
|
blockModel.context = blockCtx;
|
|
827
858
|
|
|
828
859
|
runtime.syncAssignRules([
|
|
829
860
|
{
|
|
830
|
-
key: '
|
|
861
|
+
key: 'username-from-role-title-user-clear',
|
|
831
862
|
enable: true,
|
|
832
|
-
targetPath: '
|
|
833
|
-
mode: '
|
|
863
|
+
targetPath: 'username',
|
|
864
|
+
mode: 'assign',
|
|
834
865
|
condition: { logic: '$and', items: [] },
|
|
835
|
-
value: '
|
|
866
|
+
value: '{{ ctx.formValues.roles.title }}',
|
|
836
867
|
},
|
|
837
868
|
]);
|
|
838
869
|
|
|
839
|
-
await
|
|
870
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: '经理' }] }], { source: 'user' });
|
|
871
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('经理'));
|
|
840
872
|
|
|
841
|
-
await runtime.setFormValues(blockCtx, [{ path: ['
|
|
842
|
-
expect(formStub.getFieldValue(['
|
|
873
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [] }], { source: 'user' });
|
|
874
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe(''));
|
|
875
|
+
});
|
|
843
876
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
877
|
+
it('mode=assign does not stringify multi-value aggregate for non-text scalar targets', async () => {
|
|
878
|
+
const engineEmitter = new EventEmitter();
|
|
879
|
+
const blockEmitter = new EventEmitter();
|
|
880
|
+
const formStub = createFormStub({ roles: [] });
|
|
847
881
|
|
|
848
|
-
|
|
882
|
+
const blockModel: any = {
|
|
883
|
+
uid: 'form-assign-to-many-number-target',
|
|
884
|
+
flowEngine: { emitter: engineEmitter },
|
|
885
|
+
emitter: blockEmitter,
|
|
886
|
+
dispatchEvent: vi.fn(),
|
|
887
|
+
getAclActionName: () => 'create',
|
|
888
|
+
};
|
|
889
|
+
|
|
890
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
891
|
+
runtime.mount({ sync: true });
|
|
892
|
+
|
|
893
|
+
const blockCtx = createFieldContext(runtime);
|
|
894
|
+
const scoreField: any = { isAssociationField: () => false, interface: 'number' };
|
|
895
|
+
const rolesCollection: any = { getField: () => null, filterTargetKey: 'name' };
|
|
896
|
+
const rolesField: any = {
|
|
897
|
+
isAssociationField: () => true,
|
|
898
|
+
type: 'belongsToMany',
|
|
899
|
+
targetCollection: rolesCollection,
|
|
900
|
+
};
|
|
901
|
+
const collection: any = {
|
|
902
|
+
getField: (name: string) => {
|
|
903
|
+
if (name === 'score') return scoreField;
|
|
904
|
+
if (name === 'roles') return rolesField;
|
|
905
|
+
return null;
|
|
906
|
+
},
|
|
907
|
+
};
|
|
908
|
+
blockCtx.defineProperty('collection', { value: collection });
|
|
909
|
+
blockModel.context = blockCtx;
|
|
910
|
+
|
|
911
|
+
runtime.syncAssignRules([
|
|
912
|
+
{
|
|
913
|
+
key: 'score-from-role-level',
|
|
914
|
+
enable: true,
|
|
915
|
+
targetPath: 'score',
|
|
916
|
+
mode: 'assign',
|
|
917
|
+
condition: { logic: '$and', items: [] },
|
|
918
|
+
value: '{{ ctx.formValues.roles.level }}',
|
|
919
|
+
},
|
|
920
|
+
]);
|
|
921
|
+
|
|
922
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ level: 3 }] }], { source: 'user' });
|
|
923
|
+
await waitFor(() => expect(formStub.getFieldValue(['score'])).toBe(3));
|
|
924
|
+
|
|
925
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ level: 1 }, { level: 2 }] }], {
|
|
926
|
+
source: 'user',
|
|
927
|
+
});
|
|
928
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
929
|
+
expect(formStub.getFieldValue(['score'])).toBe(3);
|
|
849
930
|
});
|
|
850
931
|
|
|
851
|
-
it('
|
|
932
|
+
it('tracks dynamic ctx.resolveJsonTemplate formValues dependencies from RunJS values', async () => {
|
|
852
933
|
const engineEmitter = new EventEmitter();
|
|
853
934
|
const blockEmitter = new EventEmitter();
|
|
854
935
|
const formStub = createFormStub({ selector: 'x', x: 'AAA', y: 'BBB' });
|
|
855
936
|
|
|
856
937
|
const blockModel: any = {
|
|
857
|
-
uid: 'form-assign-
|
|
938
|
+
uid: 'form-assign-runjs-dynamic-template-deps',
|
|
858
939
|
flowEngine: { emitter: engineEmitter },
|
|
859
940
|
emitter: blockEmitter,
|
|
860
941
|
dispatchEvent: vi.fn(),
|
|
@@ -866,56 +947,104 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
866
947
|
|
|
867
948
|
const blockCtx = createFieldContext(runtime);
|
|
868
949
|
const fieldModel: any = {
|
|
869
|
-
uid: 'field-a-
|
|
950
|
+
uid: 'field-a-runjs-dynamic-template-deps',
|
|
870
951
|
context: { fieldPathArray: ['a'] },
|
|
871
952
|
};
|
|
872
953
|
blockCtx.defineProperty('engine', {
|
|
873
954
|
value: {
|
|
874
|
-
getModel: (id: string) => (id === 'field-a-
|
|
955
|
+
getModel: (id: string) => (id === 'field-a-runjs-dynamic-template-deps' ? fieldModel : null),
|
|
875
956
|
},
|
|
876
957
|
});
|
|
877
958
|
blockModel.context = blockCtx;
|
|
878
959
|
|
|
879
960
|
runtime.syncAssignRules([
|
|
880
961
|
{
|
|
881
|
-
key: '
|
|
962
|
+
key: 'runjs-dynamic-template-deps',
|
|
882
963
|
enable: true,
|
|
883
964
|
targetPath: 'a',
|
|
884
|
-
mode: '
|
|
965
|
+
mode: 'assign',
|
|
885
966
|
condition: { logic: '$and', items: [] },
|
|
886
|
-
value:
|
|
967
|
+
value: {
|
|
968
|
+
code: "const key = ctx.formValues.selector; return await ctx.resolveJsonTemplate('{{ ctx.formValues.' + key + ' }}');",
|
|
969
|
+
version: 'v1',
|
|
970
|
+
},
|
|
887
971
|
},
|
|
888
972
|
]);
|
|
889
973
|
|
|
890
974
|
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('AAA'));
|
|
891
975
|
|
|
892
|
-
await runtime.setFormValues(blockCtx, [{ path: ['
|
|
893
|
-
expect(formStub.getFieldValue(['a'])).toBe('
|
|
976
|
+
await runtime.setFormValues(blockCtx, [{ path: ['x'], value: 'AAA2' }], { source: 'user' });
|
|
977
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('AAA2'));
|
|
894
978
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
runtime.resetAfterFormReset();
|
|
979
|
+
await runtime.setFormValues(blockCtx, [{ path: ['selector'], value: 'y' }], { source: 'user' });
|
|
980
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('BBB'));
|
|
981
|
+
});
|
|
899
982
|
|
|
900
|
-
|
|
901
|
-
|
|
983
|
+
it('mode=assign keeps local formValues replacement text literal', async () => {
|
|
984
|
+
const engineEmitter = new EventEmitter();
|
|
985
|
+
const blockEmitter = new EventEmitter();
|
|
986
|
+
const formStub = createFormStub({ roles: [] });
|
|
902
987
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
988
|
+
const blockModel: any = {
|
|
989
|
+
uid: 'form-assign-local-template-literal',
|
|
990
|
+
flowEngine: { emitter: engineEmitter },
|
|
991
|
+
emitter: blockEmitter,
|
|
992
|
+
dispatchEvent: vi.fn(),
|
|
993
|
+
getAclActionName: () => 'create',
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
997
|
+
runtime.mount({ sync: true });
|
|
998
|
+
|
|
999
|
+
const blockCtx = createFieldContext(runtime);
|
|
1000
|
+
blockCtx.defineProperty('locale', { value: 'zh-CN' });
|
|
1001
|
+
|
|
1002
|
+
const usernameField: any = { isAssociationField: () => false, interface: 'input' };
|
|
1003
|
+
const rolesCollection: any = { getField: () => null, filterTargetKey: 'name' };
|
|
1004
|
+
const rolesField: any = {
|
|
1005
|
+
isAssociationField: () => true,
|
|
1006
|
+
type: 'belongsToMany',
|
|
1007
|
+
targetCollection: rolesCollection,
|
|
1008
|
+
};
|
|
1009
|
+
const collection: any = {
|
|
1010
|
+
getField: (name: string) => {
|
|
1011
|
+
if (name === 'username') return usernameField;
|
|
1012
|
+
if (name === 'roles') return rolesField;
|
|
1013
|
+
return null;
|
|
1014
|
+
},
|
|
1015
|
+
};
|
|
1016
|
+
blockCtx.defineProperty('collection', { value: collection });
|
|
1017
|
+
blockModel.context = blockCtx;
|
|
1018
|
+
|
|
1019
|
+
runtime.syncAssignRules([
|
|
1020
|
+
{
|
|
1021
|
+
key: 'username-from-role-template-literal',
|
|
1022
|
+
enable: true,
|
|
1023
|
+
targetPath: 'username',
|
|
1024
|
+
mode: 'assign',
|
|
1025
|
+
condition: { logic: '$and', items: [] },
|
|
1026
|
+
value: '{{ ctx.formValues.roles.title }}',
|
|
1027
|
+
},
|
|
1028
|
+
]);
|
|
1029
|
+
|
|
1030
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: '{{ ctx.locale }}' }] }], {
|
|
1031
|
+
source: 'user',
|
|
1032
|
+
});
|
|
1033
|
+
|
|
1034
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('{{ ctx.locale }}'));
|
|
906
1035
|
});
|
|
907
1036
|
|
|
908
|
-
it('
|
|
1037
|
+
it('mode=default follows explicit semantics and stops after user change', async () => {
|
|
909
1038
|
const engineEmitter = new EventEmitter();
|
|
910
1039
|
const blockEmitter = new EventEmitter();
|
|
911
1040
|
const formStub = createFormStub({ b: 'X' });
|
|
912
1041
|
|
|
913
1042
|
const blockModel: any = {
|
|
914
|
-
uid: 'form-assign-
|
|
1043
|
+
uid: 'form-assign-2',
|
|
915
1044
|
flowEngine: { emitter: engineEmitter },
|
|
916
1045
|
emitter: blockEmitter,
|
|
917
1046
|
dispatchEvent: vi.fn(),
|
|
918
|
-
getAclActionName: () => '
|
|
1047
|
+
getAclActionName: () => 'create',
|
|
919
1048
|
};
|
|
920
1049
|
|
|
921
1050
|
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
@@ -923,12 +1052,12 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
923
1052
|
|
|
924
1053
|
const blockCtx = createFieldContext(runtime);
|
|
925
1054
|
const fieldModel: any = {
|
|
926
|
-
uid: 'field-
|
|
1055
|
+
uid: 'field-a2',
|
|
927
1056
|
context: { fieldPathArray: ['a'] },
|
|
928
1057
|
};
|
|
929
1058
|
blockCtx.defineProperty('engine', {
|
|
930
1059
|
value: {
|
|
931
|
-
getModel: (id: string) => (id === 'field-
|
|
1060
|
+
getModel: (id: string) => (id === 'field-a2' ? fieldModel : null),
|
|
932
1061
|
},
|
|
933
1062
|
});
|
|
934
1063
|
blockModel.context = blockCtx;
|
|
@@ -944,21 +1073,24 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
944
1073
|
},
|
|
945
1074
|
]);
|
|
946
1075
|
|
|
947
|
-
await
|
|
948
|
-
|
|
1076
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('X'));
|
|
1077
|
+
|
|
1078
|
+
// user writes to target; default mode MUST stop permanently
|
|
1079
|
+
await runtime.setFormValues(blockCtx, [{ path: ['a'], value: 'user' }], { source: 'user' });
|
|
1080
|
+
expect(formStub.getFieldValue(['a'])).toBe('user');
|
|
949
1081
|
|
|
950
1082
|
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'Y' }], { source: 'user' });
|
|
951
1083
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
952
|
-
expect(formStub.getFieldValue(['a'])).
|
|
1084
|
+
expect(formStub.getFieldValue(['a'])).toBe('user');
|
|
953
1085
|
});
|
|
954
1086
|
|
|
955
|
-
it('
|
|
1087
|
+
it('mode=default follows to-many association subpath changes without clearing on transient empty arrays', async () => {
|
|
956
1088
|
const engineEmitter = new EventEmitter();
|
|
957
1089
|
const blockEmitter = new EventEmitter();
|
|
958
|
-
const formStub = createFormStub();
|
|
1090
|
+
const formStub = createFormStub({ roles: [] });
|
|
959
1091
|
|
|
960
1092
|
const blockModel: any = {
|
|
961
|
-
uid: 'form-assign-default-
|
|
1093
|
+
uid: 'form-assign-default-to-many-source',
|
|
962
1094
|
flowEngine: { emitter: engineEmitter },
|
|
963
1095
|
emitter: blockEmitter,
|
|
964
1096
|
dispatchEvent: vi.fn(),
|
|
@@ -969,49 +1101,691 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
969
1101
|
runtime.mount({ sync: true });
|
|
970
1102
|
|
|
971
1103
|
const blockCtx = createFieldContext(runtime);
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
const colorFieldModel: any = {
|
|
979
|
-
setProps: vi.fn(),
|
|
1104
|
+
const usernameField: any = { isAssociationField: () => false, interface: 'input' };
|
|
1105
|
+
const rolesCollection: any = { getField: () => null, filterTargetKey: 'name' };
|
|
1106
|
+
const rolesField: any = {
|
|
1107
|
+
isAssociationField: () => true,
|
|
1108
|
+
type: 'belongsToMany',
|
|
1109
|
+
targetCollection: rolesCollection,
|
|
980
1110
|
};
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
getStepParams: (flowKey: string, stepKey: string) => {
|
|
987
|
-
if (flowKey === 'fieldSettings' && stepKey === 'init') {
|
|
988
|
-
return { fieldPath: 'color' };
|
|
989
|
-
}
|
|
990
|
-
return undefined;
|
|
1111
|
+
const collection: any = {
|
|
1112
|
+
getField: (name: string) => {
|
|
1113
|
+
if (name === 'username') return usernameField;
|
|
1114
|
+
if (name === 'roles') return rolesField;
|
|
1115
|
+
return null;
|
|
991
1116
|
},
|
|
992
|
-
setProps: vi.fn(),
|
|
993
1117
|
};
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
blockCtx.defineProperty('engine', {
|
|
997
|
-
value: {
|
|
998
|
-
forEachModel: (visit: (model: any) => void) => visit(formItemModel),
|
|
999
|
-
},
|
|
1000
|
-
});
|
|
1118
|
+
blockCtx.defineProperty('collection', { value: collection });
|
|
1119
|
+
blockModel.context = blockCtx;
|
|
1001
1120
|
|
|
1002
1121
|
runtime.syncAssignRules([
|
|
1003
1122
|
{
|
|
1004
|
-
key: '
|
|
1123
|
+
key: 'username-from-role-name',
|
|
1005
1124
|
enable: true,
|
|
1006
|
-
targetPath: '
|
|
1125
|
+
targetPath: 'username',
|
|
1007
1126
|
mode: 'default',
|
|
1008
1127
|
condition: { logic: '$and', items: [] },
|
|
1009
|
-
value: '
|
|
1128
|
+
value: '{{ ctx.formValues.roles.name }}',
|
|
1010
1129
|
},
|
|
1011
1130
|
]);
|
|
1012
1131
|
|
|
1013
|
-
await
|
|
1014
|
-
expect(
|
|
1132
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1133
|
+
expect(formStub.getFieldValue(['username'])).toBeUndefined();
|
|
1134
|
+
|
|
1135
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ name: 'manager' }] }], { source: 'user' });
|
|
1136
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('manager'));
|
|
1137
|
+
|
|
1138
|
+
// Association selects can briefly carry an incomplete display record while option data is refreshing.
|
|
1139
|
+
// For scalar targets, default mode should not treat that empty aggregate as a real default value.
|
|
1140
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ name: null }] }], { source: 'system' });
|
|
1141
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1142
|
+
expect(formStub.getFieldValue(['username'])).toBe('manager');
|
|
1143
|
+
|
|
1144
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ name: 'member' }] }], { source: 'user' });
|
|
1145
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('member'));
|
|
1146
|
+
|
|
1147
|
+
await runtime.setFormValues(blockCtx, [{ path: ['username'], value: 'manual' }], { source: 'user' });
|
|
1148
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ name: 'admin' }] }], { source: 'user' });
|
|
1149
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1150
|
+
expect(formStub.getFieldValue(['username'])).toBe('manual');
|
|
1151
|
+
});
|
|
1152
|
+
|
|
1153
|
+
it('mode=override overwrites existing update values until the user changes the target', async () => {
|
|
1154
|
+
const engineEmitter = new EventEmitter();
|
|
1155
|
+
const blockEmitter = new EventEmitter();
|
|
1156
|
+
const formStub = createFormStub({ a: 'Existing', b: 'X' });
|
|
1157
|
+
|
|
1158
|
+
const blockModel: any = {
|
|
1159
|
+
uid: 'form-assign-override-update',
|
|
1160
|
+
flowEngine: { emitter: engineEmitter },
|
|
1161
|
+
emitter: blockEmitter,
|
|
1162
|
+
dispatchEvent: vi.fn(),
|
|
1163
|
+
getAclActionName: () => 'update',
|
|
1164
|
+
};
|
|
1165
|
+
|
|
1166
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1167
|
+
runtime.mount({ sync: true });
|
|
1168
|
+
|
|
1169
|
+
const blockCtx = createFieldContext(runtime);
|
|
1170
|
+
const fieldModel: any = {
|
|
1171
|
+
uid: 'field-a-override-update',
|
|
1172
|
+
context: { fieldPathArray: ['a'] },
|
|
1173
|
+
};
|
|
1174
|
+
blockCtx.defineProperty('engine', {
|
|
1175
|
+
value: {
|
|
1176
|
+
getModel: (id: string) => (id === 'field-a-override-update' ? fieldModel : null),
|
|
1177
|
+
},
|
|
1178
|
+
});
|
|
1179
|
+
blockModel.context = blockCtx;
|
|
1180
|
+
|
|
1181
|
+
runtime.syncAssignRules([
|
|
1182
|
+
{
|
|
1183
|
+
key: 'r1',
|
|
1184
|
+
enable: true,
|
|
1185
|
+
targetPath: 'a',
|
|
1186
|
+
mode: 'override',
|
|
1187
|
+
condition: { logic: '$and', items: [] },
|
|
1188
|
+
value: '__B__',
|
|
1189
|
+
},
|
|
1190
|
+
]);
|
|
1191
|
+
|
|
1192
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('X'));
|
|
1193
|
+
expect(runtime.canApplyOverrideValuePatch(['a'])).toBe(true);
|
|
1194
|
+
|
|
1195
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'Y' }], { source: 'user' });
|
|
1196
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('Y'));
|
|
1197
|
+
|
|
1198
|
+
await runtime.setFormValues(blockCtx, [{ path: ['a'], value: 'Manual' }], { source: 'user' });
|
|
1199
|
+
expect(formStub.getFieldValue(['a'])).toBe('Manual');
|
|
1200
|
+
expect(runtime.canApplyOverrideValuePatch(['a'])).toBe(false);
|
|
1201
|
+
|
|
1202
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'Z' }], { source: 'user' });
|
|
1203
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1204
|
+
expect(formStub.getFieldValue(['a'])).toBe('Manual');
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
it('stops mode=override for an association root when the user edits descendant fields', async () => {
|
|
1208
|
+
const engineEmitter = new EventEmitter();
|
|
1209
|
+
const blockEmitter = new EventEmitter();
|
|
1210
|
+
const formStub = createFormStub({
|
|
1211
|
+
b: { id: 2, name: 'Auto' },
|
|
1212
|
+
user: { id: 1, name: 'Existing' },
|
|
1213
|
+
});
|
|
1214
|
+
|
|
1215
|
+
const blockModel: any = {
|
|
1216
|
+
uid: 'form-assign-override-association-root',
|
|
1217
|
+
flowEngine: { emitter: engineEmitter },
|
|
1218
|
+
emitter: blockEmitter,
|
|
1219
|
+
dispatchEvent: vi.fn(),
|
|
1220
|
+
getAclActionName: () => 'update',
|
|
1221
|
+
};
|
|
1222
|
+
|
|
1223
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1224
|
+
runtime.mount({ sync: true });
|
|
1225
|
+
|
|
1226
|
+
const blockCtx = createFieldContext(runtime);
|
|
1227
|
+
blockModel.context = blockCtx;
|
|
1228
|
+
|
|
1229
|
+
runtime.syncAssignRules([
|
|
1230
|
+
{
|
|
1231
|
+
key: 'r1',
|
|
1232
|
+
enable: true,
|
|
1233
|
+
targetPath: 'user',
|
|
1234
|
+
mode: 'override',
|
|
1235
|
+
condition: { logic: '$and', items: [] },
|
|
1236
|
+
value: '__B__',
|
|
1237
|
+
},
|
|
1238
|
+
]);
|
|
1239
|
+
|
|
1240
|
+
await waitFor(() => expect(formStub.getFieldValue(['user'])).toEqual({ id: 2, name: 'Auto' }));
|
|
1241
|
+
expect(runtime.canApplyOverrideValuePatch(['user'])).toBe(true);
|
|
1242
|
+
|
|
1243
|
+
lodashSet((formStub as any).__store, ['user'], { id: 3, name: 'Manual' });
|
|
1244
|
+
runtime.handleFormValuesChange({ user: formStub.getFieldValue(['user']) }, formStub.getFieldsValue());
|
|
1245
|
+
expect(runtime.canApplyOverrideValuePatch(['user'])).toBe(false);
|
|
1246
|
+
|
|
1247
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: { id: 4, name: 'Next' } }], { source: 'user' });
|
|
1248
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1249
|
+
|
|
1250
|
+
expect(formStub.getFieldValue(['user'])).toEqual({ id: 3, name: 'Manual' });
|
|
1251
|
+
});
|
|
1252
|
+
|
|
1253
|
+
it('stops mode=override for a subtable relation array after a user edits a row cell', async () => {
|
|
1254
|
+
const engineEmitter = new EventEmitter();
|
|
1255
|
+
const blockEmitter = new EventEmitter();
|
|
1256
|
+
const formStub = createFormStub({
|
|
1257
|
+
roles: [
|
|
1258
|
+
{ id: 1, name: 'Existing A' },
|
|
1259
|
+
{ id: 2, name: 'Existing B' },
|
|
1260
|
+
],
|
|
1261
|
+
});
|
|
1262
|
+
|
|
1263
|
+
const blockModel: any = {
|
|
1264
|
+
uid: 'form-assign-override-subtable-array',
|
|
1265
|
+
flowEngine: { emitter: engineEmitter },
|
|
1266
|
+
emitter: blockEmitter,
|
|
1267
|
+
dispatchEvent: vi.fn(),
|
|
1268
|
+
getAclActionName: () => 'update',
|
|
1269
|
+
};
|
|
1270
|
+
|
|
1271
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1272
|
+
runtime.mount({ sync: true });
|
|
1273
|
+
|
|
1274
|
+
const blockCtx = createFieldContext(runtime);
|
|
1275
|
+
blockModel.context = blockCtx;
|
|
1276
|
+
|
|
1277
|
+
runtime.syncAssignRules([
|
|
1278
|
+
{
|
|
1279
|
+
key: 'r1',
|
|
1280
|
+
enable: true,
|
|
1281
|
+
targetPath: 'roles',
|
|
1282
|
+
mode: 'override',
|
|
1283
|
+
condition: { logic: '$and', items: [] },
|
|
1284
|
+
value: [
|
|
1285
|
+
{ id: 1, name: 'Auto A' },
|
|
1286
|
+
{ id: 2, name: 'Auto B' },
|
|
1287
|
+
],
|
|
1288
|
+
},
|
|
1289
|
+
]);
|
|
1290
|
+
|
|
1291
|
+
await waitFor(() =>
|
|
1292
|
+
expect(formStub.getFieldValue(['roles'])).toEqual([
|
|
1293
|
+
{ id: 1, name: 'Auto A' },
|
|
1294
|
+
{ id: 2, name: 'Auto B' },
|
|
1295
|
+
]),
|
|
1296
|
+
);
|
|
1297
|
+
|
|
1298
|
+
lodashSet((formStub as any).__store, ['roles', 0, 'name'], 'Manual A');
|
|
1299
|
+
runtime.handleFormFieldsChange([{ name: ['roles', 0, 'name'], touched: true } as any]);
|
|
1300
|
+
runtime.handleFormValuesChange({ roles: formStub.getFieldValue(['roles']) }, formStub.getFieldsValue());
|
|
1301
|
+
expect(runtime.canApplyOverrideValuePatch(['roles'])).toBe(false);
|
|
1302
|
+
|
|
1303
|
+
runtime.syncAssignRules([
|
|
1304
|
+
{
|
|
1305
|
+
key: 'r1',
|
|
1306
|
+
enable: true,
|
|
1307
|
+
targetPath: 'roles',
|
|
1308
|
+
mode: 'override',
|
|
1309
|
+
condition: { logic: '$and', items: [] },
|
|
1310
|
+
value: [
|
|
1311
|
+
{ id: 1, name: 'Next A' },
|
|
1312
|
+
{ id: 2, name: 'Next B' },
|
|
1313
|
+
{ id: 3, name: 'Unexpected extra row' },
|
|
1314
|
+
],
|
|
1315
|
+
},
|
|
1316
|
+
]);
|
|
1317
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1318
|
+
|
|
1319
|
+
expect(formStub.getFieldValue(['roles'])).toEqual([
|
|
1320
|
+
{ id: 1, name: 'Manual A' },
|
|
1321
|
+
{ id: 2, name: 'Auto B' },
|
|
1322
|
+
]);
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
it('stops mode=override after a user clears a multiple value array', async () => {
|
|
1326
|
+
const engineEmitter = new EventEmitter();
|
|
1327
|
+
const blockEmitter = new EventEmitter();
|
|
1328
|
+
const formStub = createFormStub({ b: 'ready', tags: ['Existing'] });
|
|
1329
|
+
|
|
1330
|
+
const blockModel: any = {
|
|
1331
|
+
uid: 'form-assign-override-multiple-clear',
|
|
1332
|
+
flowEngine: { emitter: engineEmitter },
|
|
1333
|
+
emitter: blockEmitter,
|
|
1334
|
+
dispatchEvent: vi.fn(),
|
|
1335
|
+
getAclActionName: () => 'update',
|
|
1336
|
+
};
|
|
1337
|
+
|
|
1338
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1339
|
+
runtime.mount({ sync: true });
|
|
1340
|
+
|
|
1341
|
+
const blockCtx = createFieldContext(runtime);
|
|
1342
|
+
blockModel.context = blockCtx;
|
|
1343
|
+
|
|
1344
|
+
runtime.syncAssignRules([
|
|
1345
|
+
{
|
|
1346
|
+
key: 'r1',
|
|
1347
|
+
enable: true,
|
|
1348
|
+
targetPath: 'tags',
|
|
1349
|
+
mode: 'override',
|
|
1350
|
+
condition: { logic: '$and', items: [{ path: '{{ ctx.formValues.b }}', operator: '$notEmpty', value: '' }] },
|
|
1351
|
+
value: ['A', 'B'],
|
|
1352
|
+
},
|
|
1353
|
+
]);
|
|
1354
|
+
|
|
1355
|
+
await waitFor(() => expect(formStub.getFieldValue(['tags'])).toEqual(['A', 'B']));
|
|
1356
|
+
expect(runtime.canApplyOverrideValuePatch(['tags'])).toBe(true);
|
|
1357
|
+
|
|
1358
|
+
lodashSet((formStub as any).__store, ['tags'], []);
|
|
1359
|
+
runtime.handleFormValuesChange({ tags: [] }, formStub.getFieldsValue());
|
|
1360
|
+
expect(runtime.canApplyOverrideValuePatch(['tags'])).toBe(false);
|
|
1361
|
+
|
|
1362
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'changed' }], { source: 'user' });
|
|
1363
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1364
|
+
|
|
1365
|
+
expect(formStub.getFieldValue(['tags'])).toEqual([]);
|
|
1366
|
+
});
|
|
1367
|
+
|
|
1368
|
+
it('stops mode=override after a user removes an item from a multiple value array', async () => {
|
|
1369
|
+
const engineEmitter = new EventEmitter();
|
|
1370
|
+
const blockEmitter = new EventEmitter();
|
|
1371
|
+
const formStub = createFormStub({ b: 'ready', tags: ['Existing'] });
|
|
1372
|
+
|
|
1373
|
+
const blockModel: any = {
|
|
1374
|
+
uid: 'form-assign-override-multiple-remove-item',
|
|
1375
|
+
flowEngine: { emitter: engineEmitter },
|
|
1376
|
+
emitter: blockEmitter,
|
|
1377
|
+
dispatchEvent: vi.fn(),
|
|
1378
|
+
getAclActionName: () => 'update',
|
|
1379
|
+
};
|
|
1380
|
+
|
|
1381
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1382
|
+
runtime.mount({ sync: true });
|
|
1383
|
+
|
|
1384
|
+
const blockCtx = createFieldContext(runtime);
|
|
1385
|
+
blockModel.context = blockCtx;
|
|
1386
|
+
|
|
1387
|
+
runtime.syncAssignRules([
|
|
1388
|
+
{
|
|
1389
|
+
key: 'r1',
|
|
1390
|
+
enable: true,
|
|
1391
|
+
targetPath: 'tags',
|
|
1392
|
+
mode: 'override',
|
|
1393
|
+
condition: { logic: '$and', items: [{ path: '{{ ctx.formValues.b }}', operator: '$notEmpty', value: '' }] },
|
|
1394
|
+
value: ['A', 'B'],
|
|
1395
|
+
},
|
|
1396
|
+
]);
|
|
1397
|
+
|
|
1398
|
+
await waitFor(() => expect(formStub.getFieldValue(['tags'])).toEqual(['A', 'B']));
|
|
1399
|
+
expect(runtime.canApplyOverrideValuePatch(['tags'])).toBe(true);
|
|
1400
|
+
|
|
1401
|
+
lodashSet((formStub as any).__store, ['tags'], ['A']);
|
|
1402
|
+
runtime.handleFormValuesChange({ tags: ['A'] }, formStub.getFieldsValue());
|
|
1403
|
+
expect(runtime.canApplyOverrideValuePatch(['tags'])).toBe(false);
|
|
1404
|
+
|
|
1405
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'changed' }], { source: 'user' });
|
|
1406
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1407
|
+
|
|
1408
|
+
expect(formStub.getFieldValue(['tags'])).toEqual(['A']);
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
it('reapplies mode=override after user edited state is reset', async () => {
|
|
1412
|
+
const engineEmitter = new EventEmitter();
|
|
1413
|
+
const blockEmitter = new EventEmitter();
|
|
1414
|
+
const formStub = createFormStub({ a: 'Existing', b: 'X' });
|
|
1415
|
+
|
|
1416
|
+
const blockModel: any = {
|
|
1417
|
+
uid: 'form-assign-override-reset-user-edited',
|
|
1418
|
+
flowEngine: { emitter: engineEmitter },
|
|
1419
|
+
emitter: blockEmitter,
|
|
1420
|
+
dispatchEvent: vi.fn(),
|
|
1421
|
+
getAclActionName: () => 'update',
|
|
1422
|
+
};
|
|
1423
|
+
|
|
1424
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1425
|
+
runtime.mount({ sync: true });
|
|
1426
|
+
|
|
1427
|
+
const blockCtx = createFieldContext(runtime);
|
|
1428
|
+
const fieldModel: any = {
|
|
1429
|
+
uid: 'field-a-override-reset-user-edited',
|
|
1430
|
+
context: { fieldPathArray: ['a'] },
|
|
1431
|
+
};
|
|
1432
|
+
blockCtx.defineProperty('engine', {
|
|
1433
|
+
value: {
|
|
1434
|
+
getModel: (id: string) => (id === 'field-a-override-reset-user-edited' ? fieldModel : null),
|
|
1435
|
+
},
|
|
1436
|
+
});
|
|
1437
|
+
blockModel.context = blockCtx;
|
|
1438
|
+
|
|
1439
|
+
runtime.syncAssignRules([
|
|
1440
|
+
{
|
|
1441
|
+
key: 'r1',
|
|
1442
|
+
enable: true,
|
|
1443
|
+
targetPath: 'a',
|
|
1444
|
+
mode: 'override',
|
|
1445
|
+
condition: { logic: '$and', items: [] },
|
|
1446
|
+
value: '__B__',
|
|
1447
|
+
},
|
|
1448
|
+
]);
|
|
1449
|
+
|
|
1450
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('X'));
|
|
1451
|
+
|
|
1452
|
+
await runtime.setFormValues(blockCtx, [{ path: ['a'], value: 'Manual' }], { source: 'user' });
|
|
1453
|
+
expect(formStub.getFieldValue(['a'])).toBe('Manual');
|
|
1454
|
+
|
|
1455
|
+
formStub.__store.a = 'Next record';
|
|
1456
|
+
formStub.__store.b = 'Y';
|
|
1457
|
+
runtime.handleFormValuesChange({ a: 'Next record', b: 'Y' }, formStub.getFieldsValue());
|
|
1458
|
+
runtime.resetUserEditedState();
|
|
1459
|
+
expect(runtime.canApplyOverrideValuePatch(['a'])).toBe(true);
|
|
1460
|
+
|
|
1461
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('Y'));
|
|
1462
|
+
});
|
|
1463
|
+
|
|
1464
|
+
it('reapplies mode=default value after form reset starts a new create session', async () => {
|
|
1465
|
+
const engineEmitter = new EventEmitter();
|
|
1466
|
+
const blockEmitter = new EventEmitter();
|
|
1467
|
+
const formStub = createFormStub({});
|
|
1468
|
+
|
|
1469
|
+
const blockModel: any = {
|
|
1470
|
+
uid: 'form-assign-reset-reapply-default',
|
|
1471
|
+
flowEngine: { emitter: engineEmitter },
|
|
1472
|
+
emitter: blockEmitter,
|
|
1473
|
+
dispatchEvent: vi.fn(),
|
|
1474
|
+
getAclActionName: () => 'create',
|
|
1475
|
+
};
|
|
1476
|
+
|
|
1477
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1478
|
+
runtime.mount({ sync: true });
|
|
1479
|
+
|
|
1480
|
+
const blockCtx = createFieldContext(runtime);
|
|
1481
|
+
const fieldModel: any = {
|
|
1482
|
+
uid: 'field-a-reset-reapply-default',
|
|
1483
|
+
context: { fieldPathArray: ['a'] },
|
|
1484
|
+
};
|
|
1485
|
+
blockCtx.defineProperty('engine', {
|
|
1486
|
+
value: {
|
|
1487
|
+
getModel: (id: string) => (id === 'field-a-reset-reapply-default' ? fieldModel : null),
|
|
1488
|
+
},
|
|
1489
|
+
});
|
|
1490
|
+
blockModel.context = blockCtx;
|
|
1491
|
+
|
|
1492
|
+
runtime.syncAssignRules([
|
|
1493
|
+
{
|
|
1494
|
+
key: 'r1',
|
|
1495
|
+
enable: true,
|
|
1496
|
+
targetPath: 'a',
|
|
1497
|
+
mode: 'default',
|
|
1498
|
+
condition: { logic: '$and', items: [] },
|
|
1499
|
+
value: 'AAA',
|
|
1500
|
+
},
|
|
1501
|
+
]);
|
|
1502
|
+
|
|
1503
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('AAA'));
|
|
1504
|
+
|
|
1505
|
+
await runtime.setFormValues(blockCtx, [{ path: ['a'], value: 'AAA1' }], { source: 'user' });
|
|
1506
|
+
expect(formStub.getFieldValue(['a'])).toBe('AAA1');
|
|
1507
|
+
|
|
1508
|
+
formStub.__store.a = undefined;
|
|
1509
|
+
delete formStub.__store.a;
|
|
1510
|
+
runtime.resetAfterFormReset();
|
|
1511
|
+
|
|
1512
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('AAA'));
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
it('resets valuesMirror before rescheduling default rules after form reset', async () => {
|
|
1516
|
+
const engineEmitter = new EventEmitter();
|
|
1517
|
+
const blockEmitter = new EventEmitter();
|
|
1518
|
+
const formStub = createFormStub({ selector: 'x', x: 'AAA', y: 'BBB' });
|
|
1519
|
+
|
|
1520
|
+
const blockModel: any = {
|
|
1521
|
+
uid: 'form-assign-reset-values-mirror',
|
|
1522
|
+
flowEngine: { emitter: engineEmitter },
|
|
1523
|
+
emitter: blockEmitter,
|
|
1524
|
+
dispatchEvent: vi.fn(),
|
|
1525
|
+
getAclActionName: () => 'create',
|
|
1526
|
+
};
|
|
1527
|
+
|
|
1528
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1529
|
+
runtime.mount({ sync: true });
|
|
1530
|
+
|
|
1531
|
+
const blockCtx = createFieldContext(runtime);
|
|
1532
|
+
const fieldModel: any = {
|
|
1533
|
+
uid: 'field-a-reset-values-mirror',
|
|
1534
|
+
context: { fieldPathArray: ['a'] },
|
|
1535
|
+
};
|
|
1536
|
+
blockCtx.defineProperty('engine', {
|
|
1537
|
+
value: {
|
|
1538
|
+
getModel: (id: string) => (id === 'field-a-reset-values-mirror' ? fieldModel : null),
|
|
1539
|
+
},
|
|
1540
|
+
});
|
|
1541
|
+
blockModel.context = blockCtx;
|
|
1542
|
+
|
|
1543
|
+
runtime.syncAssignRules([
|
|
1544
|
+
{
|
|
1545
|
+
key: 'r1',
|
|
1546
|
+
enable: true,
|
|
1547
|
+
targetPath: 'a',
|
|
1548
|
+
mode: 'default',
|
|
1549
|
+
condition: { logic: '$and', items: [] },
|
|
1550
|
+
value: '__DYNAMIC__',
|
|
1551
|
+
},
|
|
1552
|
+
]);
|
|
1553
|
+
|
|
1554
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('AAA'));
|
|
1555
|
+
|
|
1556
|
+
await runtime.setFormValues(blockCtx, [{ path: ['a'], value: 'AAA1' }], { source: 'user' });
|
|
1557
|
+
expect(formStub.getFieldValue(['a'])).toBe('AAA1');
|
|
1558
|
+
|
|
1559
|
+
for (const key of Object.keys(formStub.__store)) {
|
|
1560
|
+
delete formStub.__store[key];
|
|
1561
|
+
}
|
|
1562
|
+
runtime.resetAfterFormReset();
|
|
1563
|
+
|
|
1564
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1565
|
+
expect(formStub.getFieldValue(['a'])).toBeUndefined();
|
|
1566
|
+
|
|
1567
|
+
await runtime.setFormValues(blockCtx, [{ path: ['selector'], value: 'y' }], { source: 'user' });
|
|
1568
|
+
await runtime.setFormValues(blockCtx, [{ path: ['y'], value: 'BBB2' }], { source: 'user' });
|
|
1569
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('BBB2'));
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
it('skips mode=default form assignment in update form', async () => {
|
|
1573
|
+
const engineEmitter = new EventEmitter();
|
|
1574
|
+
const blockEmitter = new EventEmitter();
|
|
1575
|
+
const formStub = createFormStub({ b: 'X' });
|
|
1576
|
+
|
|
1577
|
+
const blockModel: any = {
|
|
1578
|
+
uid: 'form-assign-update-skip-default',
|
|
1579
|
+
flowEngine: { emitter: engineEmitter },
|
|
1580
|
+
emitter: blockEmitter,
|
|
1581
|
+
dispatchEvent: vi.fn(),
|
|
1582
|
+
getAclActionName: () => 'update',
|
|
1583
|
+
};
|
|
1584
|
+
|
|
1585
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1586
|
+
runtime.mount({ sync: true });
|
|
1587
|
+
|
|
1588
|
+
const blockCtx = createFieldContext(runtime);
|
|
1589
|
+
const fieldModel: any = {
|
|
1590
|
+
uid: 'field-a-update-skip-default',
|
|
1591
|
+
context: { fieldPathArray: ['a'] },
|
|
1592
|
+
};
|
|
1593
|
+
blockCtx.defineProperty('engine', {
|
|
1594
|
+
value: {
|
|
1595
|
+
getModel: (id: string) => (id === 'field-a-update-skip-default' ? fieldModel : null),
|
|
1596
|
+
},
|
|
1597
|
+
});
|
|
1598
|
+
blockModel.context = blockCtx;
|
|
1599
|
+
|
|
1600
|
+
runtime.syncAssignRules([
|
|
1601
|
+
{
|
|
1602
|
+
key: 'r1',
|
|
1603
|
+
enable: true,
|
|
1604
|
+
targetPath: 'a',
|
|
1605
|
+
mode: 'default',
|
|
1606
|
+
condition: { logic: '$and', items: [] },
|
|
1607
|
+
value: '__B__',
|
|
1608
|
+
},
|
|
1609
|
+
]);
|
|
1610
|
+
|
|
1611
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1612
|
+
expect(formStub.getFieldValue(['a'])).toBeUndefined();
|
|
1613
|
+
|
|
1614
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'Y' }], { source: 'user' });
|
|
1615
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1616
|
+
expect(formStub.getFieldValue(['a'])).toBeUndefined();
|
|
1617
|
+
});
|
|
1618
|
+
|
|
1619
|
+
it('syncs UI props for mode=default form assignment on configured fields', async () => {
|
|
1620
|
+
const engineEmitter = new EventEmitter();
|
|
1621
|
+
const blockEmitter = new EventEmitter();
|
|
1622
|
+
const formStub = createFormStub();
|
|
1623
|
+
|
|
1624
|
+
const blockModel: any = {
|
|
1625
|
+
uid: 'form-assign-default-ui-sync',
|
|
1626
|
+
flowEngine: { emitter: engineEmitter },
|
|
1627
|
+
emitter: blockEmitter,
|
|
1628
|
+
dispatchEvent: vi.fn(),
|
|
1629
|
+
getAclActionName: () => 'create',
|
|
1630
|
+
};
|
|
1631
|
+
|
|
1632
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1633
|
+
runtime.mount({ sync: true });
|
|
1634
|
+
|
|
1635
|
+
const blockCtx = createFieldContext(runtime);
|
|
1636
|
+
blockModel.context = blockCtx;
|
|
1637
|
+
|
|
1638
|
+
const fieldCtx = createFieldContext(runtime);
|
|
1639
|
+
fieldCtx.defineProperty('blockModel', { value: blockModel });
|
|
1640
|
+
fieldCtx.defineProperty('fieldPathArray', { value: ['color'] });
|
|
1641
|
+
|
|
1642
|
+
const colorFieldModel: any = {
|
|
1643
|
+
setProps: vi.fn(),
|
|
1644
|
+
};
|
|
1645
|
+
|
|
1646
|
+
const formItemModel: any = {
|
|
1647
|
+
uid: 'form-item-color-default-ui-sync',
|
|
1648
|
+
context: fieldCtx,
|
|
1649
|
+
subModels: { field: colorFieldModel },
|
|
1650
|
+
getStepParams: (flowKey: string, stepKey: string) => {
|
|
1651
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') {
|
|
1652
|
+
return { fieldPath: 'color' };
|
|
1653
|
+
}
|
|
1654
|
+
return undefined;
|
|
1655
|
+
},
|
|
1656
|
+
setProps: vi.fn(),
|
|
1657
|
+
};
|
|
1658
|
+
fieldCtx.defineProperty('model', { value: formItemModel });
|
|
1659
|
+
|
|
1660
|
+
blockCtx.defineProperty('engine', {
|
|
1661
|
+
value: {
|
|
1662
|
+
forEachModel: (visit: (model: any) => void) => visit(formItemModel),
|
|
1663
|
+
},
|
|
1664
|
+
});
|
|
1665
|
+
|
|
1666
|
+
runtime.syncAssignRules([
|
|
1667
|
+
{
|
|
1668
|
+
key: 'color-default',
|
|
1669
|
+
enable: true,
|
|
1670
|
+
targetPath: 'color',
|
|
1671
|
+
mode: 'default',
|
|
1672
|
+
condition: { logic: '$and', items: [] },
|
|
1673
|
+
value: '#1677FF',
|
|
1674
|
+
},
|
|
1675
|
+
]);
|
|
1676
|
+
|
|
1677
|
+
await waitFor(() => expect(formStub.getFieldValue(['color'])).toBe('#1677FF'));
|
|
1678
|
+
expect(colorFieldModel.setProps).toHaveBeenCalledWith({ value: '#1677FF' });
|
|
1679
|
+
});
|
|
1680
|
+
|
|
1681
|
+
it('syncs mounted field UI props after programmatic form writes', async () => {
|
|
1682
|
+
const engineEmitter = new EventEmitter();
|
|
1683
|
+
const blockEmitter = new EventEmitter();
|
|
1684
|
+
const formStub = createFormStub();
|
|
1685
|
+
|
|
1686
|
+
const blockModel: any = {
|
|
1687
|
+
uid: 'form-runtime-mounted-field-ui-sync',
|
|
1688
|
+
flowEngine: { emitter: engineEmitter },
|
|
1689
|
+
emitter: blockEmitter,
|
|
1690
|
+
dispatchEvent: vi.fn(),
|
|
1691
|
+
getAclActionName: () => 'create',
|
|
1692
|
+
};
|
|
1693
|
+
|
|
1694
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1695
|
+
|
|
1696
|
+
const blockCtx = createFieldContext(runtime);
|
|
1697
|
+
blockModel.context = blockCtx;
|
|
1698
|
+
|
|
1699
|
+
const fieldCtx = createFieldContext(runtime);
|
|
1700
|
+
fieldCtx.defineProperty('blockModel', { value: blockModel });
|
|
1701
|
+
fieldCtx.defineProperty('fieldPathArray', { value: ['username'] });
|
|
1702
|
+
|
|
1703
|
+
const inputFieldModel: any = {
|
|
1704
|
+
setProps: vi.fn(),
|
|
1705
|
+
};
|
|
1706
|
+
const formItemModel: any = {
|
|
1707
|
+
uid: 'form-item-username-runtime-ui-sync',
|
|
1708
|
+
context: fieldCtx,
|
|
1709
|
+
subModels: { field: inputFieldModel },
|
|
1710
|
+
getStepParams: (flowKey: string, stepKey: string) => {
|
|
1711
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') {
|
|
1712
|
+
return { fieldPath: 'username' };
|
|
1713
|
+
}
|
|
1714
|
+
return undefined;
|
|
1715
|
+
},
|
|
1716
|
+
setProps: vi.fn(),
|
|
1717
|
+
};
|
|
1718
|
+
|
|
1719
|
+
blockCtx.defineProperty('engine', {
|
|
1720
|
+
value: {
|
|
1721
|
+
forEachModel: (visit: (model: any) => void) => visit(formItemModel),
|
|
1722
|
+
},
|
|
1723
|
+
});
|
|
1724
|
+
|
|
1725
|
+
runtime.mount({ sync: true });
|
|
1726
|
+
|
|
1727
|
+
await runtime.setFormValues(blockCtx, [{ path: ['username'], value: 'Admin' }], { source: 'system' });
|
|
1728
|
+
|
|
1729
|
+
expect(formStub.getFieldValue(['username'])).toBe('Admin');
|
|
1730
|
+
expect(formItemModel.setProps).toHaveBeenCalledWith({ value: 'Admin' });
|
|
1731
|
+
expect(inputFieldModel.setProps).toHaveBeenCalledWith({ value: 'Admin' });
|
|
1732
|
+
});
|
|
1733
|
+
|
|
1734
|
+
it('syncs mounted field UI props from model index without scanning all models on every write', async () => {
|
|
1735
|
+
const engineEmitter = new EventEmitter();
|
|
1736
|
+
const blockEmitter = new EventEmitter();
|
|
1737
|
+
const formStub = createFormStub();
|
|
1738
|
+
|
|
1739
|
+
const blockModel: any = {
|
|
1740
|
+
uid: 'form-runtime-mounted-field-ui-index-sync',
|
|
1741
|
+
flowEngine: { emitter: engineEmitter },
|
|
1742
|
+
emitter: blockEmitter,
|
|
1743
|
+
dispatchEvent: vi.fn(),
|
|
1744
|
+
getAclActionName: () => 'create',
|
|
1745
|
+
};
|
|
1746
|
+
|
|
1747
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1748
|
+
|
|
1749
|
+
const blockCtx = createFieldContext(runtime);
|
|
1750
|
+
blockModel.context = blockCtx;
|
|
1751
|
+
|
|
1752
|
+
const fieldCtx = createFieldContext(runtime);
|
|
1753
|
+
fieldCtx.defineProperty('blockModel', { value: blockModel });
|
|
1754
|
+
fieldCtx.defineProperty('fieldPathArray', { value: ['username'] });
|
|
1755
|
+
|
|
1756
|
+
const inputFieldModel: any = {
|
|
1757
|
+
setProps: vi.fn(),
|
|
1758
|
+
};
|
|
1759
|
+
const formItemModel: any = {
|
|
1760
|
+
uid: 'form-item-username-runtime-ui-index-sync',
|
|
1761
|
+
context: fieldCtx,
|
|
1762
|
+
subModels: { field: inputFieldModel },
|
|
1763
|
+
getStepParams: (flowKey: string, stepKey: string) => {
|
|
1764
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') {
|
|
1765
|
+
return { fieldPath: 'username' };
|
|
1766
|
+
}
|
|
1767
|
+
return undefined;
|
|
1768
|
+
},
|
|
1769
|
+
setProps: vi.fn(),
|
|
1770
|
+
};
|
|
1771
|
+
|
|
1772
|
+
const forEachModel = vi.fn((visit: (model: any) => void) => visit(formItemModel));
|
|
1773
|
+
blockCtx.defineProperty('engine', {
|
|
1774
|
+
value: {
|
|
1775
|
+
forEachModel,
|
|
1776
|
+
},
|
|
1777
|
+
});
|
|
1778
|
+
|
|
1779
|
+
runtime.mount({ sync: true });
|
|
1780
|
+
engineEmitter.emit('model:mounted', { model: formItemModel });
|
|
1781
|
+
forEachModel.mockClear();
|
|
1782
|
+
|
|
1783
|
+
await runtime.setFormValues(blockCtx, [{ path: ['username'], value: 'Admin' }], { source: 'system' });
|
|
1784
|
+
await runtime.setFormValues(blockCtx, [{ path: ['username'], value: 'Owner' }], { source: 'system' });
|
|
1785
|
+
|
|
1786
|
+
expect(forEachModel).not.toHaveBeenCalled();
|
|
1787
|
+
expect(formItemModel.setProps).toHaveBeenLastCalledWith({ value: 'Owner' });
|
|
1788
|
+
expect(inputFieldModel.setProps).toHaveBeenLastCalledWith({ value: 'Owner' });
|
|
1015
1789
|
});
|
|
1016
1790
|
|
|
1017
1791
|
it('linkage assignment takes precedence over mode=assign form assignment', async () => {
|
|
@@ -2396,6 +3170,10 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
2396
3170
|
|
|
2397
3171
|
lodashSet((formStub as any).__store, ['users'], [{ __is_new__: true, name: '' }]);
|
|
2398
3172
|
runtime.handleFormValuesChange({ users: formStub.getFieldValue(['users']) }, formStub.getFieldsValue());
|
|
3173
|
+
expect((runtime as any).findExplicitHit('users')).toBe('users');
|
|
3174
|
+
expect((runtime as any).findExplicitHit('users[0].name')).toBeNull();
|
|
3175
|
+
expect((runtime as any).findUserEditedHit('users')).toBe('users');
|
|
3176
|
+
expect((runtime as any).findUserEditedHit('users[0].name')).toBeNull();
|
|
2399
3177
|
|
|
2400
3178
|
const row0: any = {
|
|
2401
3179
|
uid: 'users.name',
|