@nocobase/client-v2 2.2.0-alpha.1 → 2.2.0-alpha.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/PluginSettingsManager.d.ts +33 -0
- package/es/RouteRepository.d.ts +21 -6
- package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/TypedVariableInput.d.ts +22 -4
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +2 -0
- package/es/flow/actions/afterSuccess.d.ts +8 -1
- package/es/flow/actions/index.d.ts +1 -1
- 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 +5 -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/components/FieldAssignValueInput.d.ts +2 -0
- package/es/flow/index.d.ts +1 -0
- 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/submitHandler.d.ts +1 -1
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +30 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow/models/blocks/js-block/JSBlock.d.ts +1 -0
- package/es/flow/models/blocks/table/TableActionsColumnModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/dragSort/dragSortHooks.d.ts +1 -1
- package/es/flow/models/blocks/table/dragSort/dragSortSettings.d.ts +2 -1
- package/es/flow/models/blocks/table/dragSort/dragSortUtils.d.ts +6 -4
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +6 -0
- package/es/flow/resolveViewParamsToViewList.d.ts +1 -1
- package/es/flow/utils/dataScopeRowSnapshot.d.ts +59 -0
- package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
- package/es/flow/utils/formValueDeps.d.ts +32 -0
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/index.d.ts +2 -1
- package/es/index.mjs +233 -137
- package/lib/index.js +234 -138
- package/package.json +7 -7
- package/src/PluginSettingsManager.ts +53 -0
- package/src/RouteRepository.ts +126 -24
- package/src/__tests__/PluginSettingsManager.test.ts +13 -0
- package/src/__tests__/RouteRepository.test.ts +216 -0
- package/src/__tests__/browserChecker.test.ts +61 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +199 -0
- package/src/__tests__/plugin-manager.test.tsx +44 -2
- package/src/__tests__/settings-center.test.tsx +40 -1
- package/src/collection-manager/field-configure.ts +1 -1
- package/src/collection-manager/interfaces/id.ts +1 -1
- package/src/collection-manager/interfaces/m2m.tsx +2 -2
- package/src/collection-manager/interfaces/m2o.tsx +2 -2
- package/src/collection-manager/interfaces/nanoid.ts +1 -1
- package/src/collection-manager/interfaces/o2m.tsx +2 -2
- package/src/collection-manager/interfaces/obo.tsx +2 -2
- package/src/collection-manager/interfaces/oho.tsx +2 -2
- package/src/collection-manager/interfaces/properties/index.ts +2 -2
- package/src/collection-manager/interfaces/uuid.ts +1 -1
- package/src/components/AppComponents.tsx +19 -3
- package/src/components/README.md +7 -1
- package/src/components/README.zh-CN.md +6 -1
- package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
- package/src/components/category-tabs/index.ts +10 -0
- package/src/components/form/JsonTextArea.tsx +21 -11
- package/src/components/form/TypedVariableInput.tsx +416 -93
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +268 -9
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +2 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +443 -5
- package/src/flow/__tests__/getKey.test.ts +7 -0
- package/src/flow/__tests__/resolveViewParamsToViewList.test.ts +34 -0
- package/src/flow/actions/__tests__/afterSuccess.test.ts +73 -0
- package/src/flow/actions/__tests__/dataScopeFilter.test.ts +58 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +1022 -0
- 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/__tests__/openView.defineProps.route.test.tsx +104 -0
- package/src/flow/actions/__tests__/subFormFieldLinkageRules.inputArgs.test.ts +2 -2
- package/src/flow/actions/afterSuccess.tsx +142 -3
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/dataScopeFilter.ts +10 -0
- package/src/flow/actions/dateTimeFormat.tsx +42 -28
- package/src/flow/actions/index.ts +1 -1
- package/src/flow/actions/linkageRules.tsx +123 -35
- package/src/flow/actions/linkageRulesFormValueRefresh.ts +22 -130
- package/src/flow/actions/openView.tsx +38 -4
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/admin-shell/BaseLayoutModel.tsx +25 -3
- package/src/flow/admin-shell/BaseLayoutRouteCoordinator.ts +5 -2
- package/src/flow/admin-shell/__tests__/AdminLayoutRouteCoordinator.test.ts +13 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +34 -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 +23 -9
- package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
- 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__/AdminLayoutModel.test.tsx +82 -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/DynamicFlowsIcon.tsx +447 -126
- package/src/flow/components/FieldAssignRulesEditor.tsx +128 -21
- package/src/flow/components/FieldAssignValueInput.tsx +10 -5
- package/src/flow/components/FlowRoute.tsx +56 -11
- package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +508 -4
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +151 -3
- 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/components/fieldAssignOptions.ts +155 -27
- package/src/flow/flows/editMarkdownFlow.tsx +1 -1
- package/src/flow/getViewDiffAndUpdateHidden.tsx +1 -0
- package/src/flow/index.ts +2 -1
- package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
- 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/ActionModelCore.tsx +6 -4
- 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__/ActionModelCore.render.test.tsx +37 -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/filter-form/fields/FilterFormCustomFieldModel.tsx +1 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormActionModel.tsx +1 -1
- 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/__tests__/submitHandler.test.ts +54 -1
- package/src/flow/models/blocks/form/submitHandler.ts +17 -3
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +940 -75
- package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +348 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +225 -9
- package/src/flow/models/blocks/js-block/__tests__/JSBlockModel.test.tsx +150 -0
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +36 -13
- package/src/flow/models/blocks/table/TableBlockModel.tsx +38 -17
- package/src/flow/models/blocks/table/TableColumnModel.tsx +36 -3
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +111 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.dragSort.test.ts +127 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.rowSelection.test.tsx +1 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +159 -1
- package/src/flow/models/blocks/table/dragSort/dragSortHooks.tsx +28 -17
- package/src/flow/models/blocks/table/dragSort/dragSortSettings.ts +13 -6
- package/src/flow/models/blocks/table/dragSort/dragSortUtils.ts +15 -2
- 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/RecordSelectFieldModel.tsx +16 -4
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +141 -19
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +197 -0
- 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/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
- package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
- package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
- package/src/flow/models/fields/InputFieldModel.tsx +40 -2
- 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/SelectFieldModel.tsx +6 -4
- package/src/flow/models/fields/TextareaFieldModel.tsx +49 -3
- package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +117 -1
- package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
- package/src/flow/models/fields/__tests__/TextareaFieldModel.test.tsx +100 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +93 -10
- package/src/flow/resolveViewParamsToViewList.tsx +11 -3
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +258 -0
- package/src/flow/utils/dataScopeFormValueClear.ts +218 -81
- package/src/flow/utils/dataScopeRowSnapshot.ts +616 -0
- package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
- package/src/flow/utils/formValueDeps.ts +170 -0
- 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/nocobase-buildin-plugin/index.tsx +44 -14
- package/src/settings-center/AdminSettingsLayout.tsx +10 -2
- package/src/settings-center/SystemSettingsPage.tsx +1 -2
- package/src/settings-center/plugin-manager/PluginCard.tsx +2 -2
- package/src/settings-center/plugin-manager/index.tsx +3 -0
- package/src/settings-center/utils.tsx +0 -6
|
@@ -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
|
}
|
|
@@ -122,6 +127,93 @@ function createFieldContext(runtime: FormValueRuntime) {
|
|
|
122
127
|
}
|
|
123
128
|
|
|
124
129
|
describe('FormValueRuntime (default rules)', () => {
|
|
130
|
+
it('builds draft snapshots from current user-edited values only', async () => {
|
|
131
|
+
const engineEmitter = new EventEmitter();
|
|
132
|
+
const blockEmitter = new EventEmitter();
|
|
133
|
+
const formStub = createFormStub({});
|
|
134
|
+
const dispatchEvent = vi.fn();
|
|
135
|
+
|
|
136
|
+
const blockModel = {
|
|
137
|
+
uid: 'form-user-edited-draft-snapshot',
|
|
138
|
+
flowEngine: { emitter: engineEmitter },
|
|
139
|
+
emitter: blockEmitter,
|
|
140
|
+
dispatchEvent,
|
|
141
|
+
getAclActionName: () => 'create',
|
|
142
|
+
} as ConstructorParameters<typeof FormValueRuntime>[0]['model'];
|
|
143
|
+
|
|
144
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as unknown as FormInstance });
|
|
145
|
+
runtime.mount({ sync: true });
|
|
146
|
+
|
|
147
|
+
const blockCtx = createFieldContext(runtime);
|
|
148
|
+
await runtime.setFormValues(blockCtx, [{ path: ['fixed'], value: 'Fixed auto value' }], { source: 'system' });
|
|
149
|
+
await runtime.setFormValues(blockCtx, [{ path: ['defaultTitle'], value: 'Default auto value' }], {
|
|
150
|
+
source: 'default',
|
|
151
|
+
});
|
|
152
|
+
await runtime.setFormValues(blockCtx, [{ path: ['overrideTitle'], value: 'Override auto value' }], {
|
|
153
|
+
source: 'override',
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
expect(runtime.getUserEditedValuePatches()).toEqual([]);
|
|
157
|
+
expect(runtime.getUserEditedValuesSnapshot()).toEqual({});
|
|
158
|
+
|
|
159
|
+
await runtime.setFormValues(
|
|
160
|
+
blockCtx,
|
|
161
|
+
[
|
|
162
|
+
{ path: ['title'], value: 'Manual title' },
|
|
163
|
+
{ path: ['roles', 0, 'roleName'], value: 'Manual role' },
|
|
164
|
+
],
|
|
165
|
+
{ source: 'user' },
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
expect(runtime.getUserEditedValuePatches()).toEqual([
|
|
169
|
+
{ path: ['title'], value: 'Manual title' },
|
|
170
|
+
{ path: ['roles', 0, 'roleName'], value: 'Manual role' },
|
|
171
|
+
]);
|
|
172
|
+
expect(runtime.getUserEditedValuesSnapshot()).toEqual({
|
|
173
|
+
title: 'Manual title',
|
|
174
|
+
roles: [{ roleName: 'Manual role' }],
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
await runtime.setFormValues(blockCtx, [{ path: ['title'], value: 'Fixed title' }], { source: 'system' });
|
|
178
|
+
|
|
179
|
+
expect(runtime.getUserEditedValuesSnapshot()).toEqual({
|
|
180
|
+
roles: [{ roleName: 'Manual role' }],
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it('omits non-user descendant values from parent user-edited draft patches', async () => {
|
|
185
|
+
const engineEmitter = new EventEmitter();
|
|
186
|
+
const blockEmitter = new EventEmitter();
|
|
187
|
+
const formStub = createFormStub({});
|
|
188
|
+
const dispatchEvent = vi.fn();
|
|
189
|
+
|
|
190
|
+
const blockModel = {
|
|
191
|
+
uid: 'form-user-edited-parent-draft-patch',
|
|
192
|
+
flowEngine: { emitter: engineEmitter },
|
|
193
|
+
emitter: blockEmitter,
|
|
194
|
+
dispatchEvent,
|
|
195
|
+
getAclActionName: () => 'create',
|
|
196
|
+
} as ConstructorParameters<typeof FormValueRuntime>[0]['model'];
|
|
197
|
+
|
|
198
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as unknown as FormInstance });
|
|
199
|
+
runtime.mount({ sync: true });
|
|
200
|
+
|
|
201
|
+
const blockCtx = createFieldContext(runtime);
|
|
202
|
+
await runtime.setFormValues(
|
|
203
|
+
blockCtx,
|
|
204
|
+
[{ path: ['roles'], value: [{ roleName: 'Manual role', fixedRoleName: 'Initial fixed role' }] }],
|
|
205
|
+
{ source: 'user' },
|
|
206
|
+
);
|
|
207
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles', 0, 'fixedRoleName'], value: 'System fixed role' }], {
|
|
208
|
+
source: 'system',
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
expect(runtime.getUserEditedValuePatches()).toEqual([{ path: ['roles'], value: [{ roleName: 'Manual role' }] }]);
|
|
212
|
+
expect(runtime.getUserEditedValuesSnapshot()).toEqual({
|
|
213
|
+
roles: [{ roleName: 'Manual role' }],
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
125
217
|
it('skips object patches when values are unchanged', async () => {
|
|
126
218
|
const engineEmitter = new EventEmitter();
|
|
127
219
|
const blockEmitter = new EventEmitter();
|
|
@@ -747,13 +839,13 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
747
839
|
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('Y'));
|
|
748
840
|
});
|
|
749
841
|
|
|
750
|
-
it('mode=
|
|
842
|
+
it('mode=assign follows to-many association title changes without clearing on transient empty arrays', async () => {
|
|
751
843
|
const engineEmitter = new EventEmitter();
|
|
752
844
|
const blockEmitter = new EventEmitter();
|
|
753
|
-
const formStub = createFormStub({
|
|
845
|
+
const formStub = createFormStub({ roles: [] });
|
|
754
846
|
|
|
755
847
|
const blockModel: any = {
|
|
756
|
-
uid: 'form-assign-
|
|
848
|
+
uid: 'form-assign-to-many-source-title',
|
|
757
849
|
flowEngine: { emitter: engineEmitter },
|
|
758
850
|
emitter: blockEmitter,
|
|
759
851
|
dispatchEvent: vi.fn(),
|
|
@@ -764,46 +856,66 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
764
856
|
runtime.mount({ sync: true });
|
|
765
857
|
|
|
766
858
|
const blockCtx = createFieldContext(runtime);
|
|
767
|
-
const
|
|
768
|
-
|
|
769
|
-
|
|
859
|
+
const usernameField: any = { isAssociationField: () => false, interface: 'input' };
|
|
860
|
+
const rolesCollection: any = { getField: () => null, filterTargetKey: 'name' };
|
|
861
|
+
const rolesField: any = {
|
|
862
|
+
isAssociationField: () => true,
|
|
863
|
+
type: 'belongsToMany',
|
|
864
|
+
targetCollection: rolesCollection,
|
|
770
865
|
};
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
866
|
+
const collection: any = {
|
|
867
|
+
getField: (name: string) => {
|
|
868
|
+
if (name === 'username') return usernameField;
|
|
869
|
+
if (name === 'roles') return rolesField;
|
|
870
|
+
return null;
|
|
774
871
|
},
|
|
775
|
-
}
|
|
872
|
+
};
|
|
873
|
+
blockCtx.defineProperty('collection', { value: collection });
|
|
776
874
|
blockModel.context = blockCtx;
|
|
777
875
|
|
|
778
876
|
runtime.syncAssignRules([
|
|
779
877
|
{
|
|
780
|
-
key: '
|
|
878
|
+
key: 'username-from-role-title',
|
|
781
879
|
enable: true,
|
|
782
|
-
targetPath: '
|
|
783
|
-
mode: '
|
|
880
|
+
targetPath: 'username',
|
|
881
|
+
mode: 'assign',
|
|
784
882
|
condition: { logic: '$and', items: [] },
|
|
785
|
-
value: '
|
|
883
|
+
value: '{{ ctx.formValues.roles.title }}',
|
|
786
884
|
},
|
|
787
885
|
]);
|
|
788
886
|
|
|
789
|
-
await
|
|
887
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
888
|
+
expect(formStub.getFieldValue(['username'])).toBeUndefined();
|
|
790
889
|
|
|
791
|
-
|
|
792
|
-
await
|
|
793
|
-
expect(formStub.getFieldValue(['a'])).toBe('user');
|
|
890
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: '经理' }] }], { source: 'user' });
|
|
891
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('经理'));
|
|
794
892
|
|
|
795
|
-
await runtime.setFormValues(blockCtx, [{ path: ['
|
|
796
|
-
|
|
797
|
-
|
|
893
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: '经理' }, { title: 'Admin' }] }], {
|
|
894
|
+
source: 'user',
|
|
895
|
+
});
|
|
896
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('经理, Admin'));
|
|
897
|
+
|
|
898
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: null }] }], { source: 'system' });
|
|
899
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
900
|
+
expect(formStub.getFieldValue(['username'])).toBe('经理, Admin');
|
|
901
|
+
|
|
902
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ __index__: 'row-1', __is_new__: true }] }], {
|
|
903
|
+
source: 'system',
|
|
904
|
+
});
|
|
905
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
906
|
+
expect(formStub.getFieldValue(['username'])).toBe('经理, Admin');
|
|
907
|
+
|
|
908
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: '销售' }] }], { source: 'user' });
|
|
909
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('销售'));
|
|
798
910
|
});
|
|
799
911
|
|
|
800
|
-
it('
|
|
912
|
+
it('mode=assign clears scalar target when to-many association source is really cleared by user', async () => {
|
|
801
913
|
const engineEmitter = new EventEmitter();
|
|
802
914
|
const blockEmitter = new EventEmitter();
|
|
803
|
-
const formStub = createFormStub({});
|
|
915
|
+
const formStub = createFormStub({ roles: [] });
|
|
804
916
|
|
|
805
917
|
const blockModel: any = {
|
|
806
|
-
uid: 'form-assign-
|
|
918
|
+
uid: 'form-assign-to-many-source-user-clear',
|
|
807
919
|
flowEngine: { emitter: engineEmitter },
|
|
808
920
|
emitter: blockEmitter,
|
|
809
921
|
dispatchEvent: vi.fn(),
|
|
@@ -814,47 +926,103 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
814
926
|
runtime.mount({ sync: true });
|
|
815
927
|
|
|
816
928
|
const blockCtx = createFieldContext(runtime);
|
|
817
|
-
const
|
|
818
|
-
|
|
819
|
-
|
|
929
|
+
const usernameField: any = { isAssociationField: () => false, interface: 'input' };
|
|
930
|
+
const rolesCollection: any = { getField: () => null, filterTargetKey: 'name' };
|
|
931
|
+
const rolesField: any = {
|
|
932
|
+
isAssociationField: () => true,
|
|
933
|
+
type: 'belongsToMany',
|
|
934
|
+
targetCollection: rolesCollection,
|
|
820
935
|
};
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
936
|
+
const collection: any = {
|
|
937
|
+
getField: (name: string) => {
|
|
938
|
+
if (name === 'username') return usernameField;
|
|
939
|
+
if (name === 'roles') return rolesField;
|
|
940
|
+
return null;
|
|
824
941
|
},
|
|
825
|
-
}
|
|
942
|
+
};
|
|
943
|
+
blockCtx.defineProperty('collection', { value: collection });
|
|
826
944
|
blockModel.context = blockCtx;
|
|
827
945
|
|
|
828
946
|
runtime.syncAssignRules([
|
|
829
947
|
{
|
|
830
|
-
key: '
|
|
948
|
+
key: 'username-from-role-title-user-clear',
|
|
831
949
|
enable: true,
|
|
832
|
-
targetPath: '
|
|
833
|
-
mode: '
|
|
950
|
+
targetPath: 'username',
|
|
951
|
+
mode: 'assign',
|
|
834
952
|
condition: { logic: '$and', items: [] },
|
|
835
|
-
value: '
|
|
953
|
+
value: '{{ ctx.formValues.roles.title }}',
|
|
836
954
|
},
|
|
837
955
|
]);
|
|
838
956
|
|
|
839
|
-
await
|
|
957
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: '经理' }] }], { source: 'user' });
|
|
958
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('经理'));
|
|
840
959
|
|
|
841
|
-
await runtime.setFormValues(blockCtx, [{ path: ['
|
|
842
|
-
expect(formStub.getFieldValue(['
|
|
960
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [] }], { source: 'user' });
|
|
961
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe(''));
|
|
962
|
+
});
|
|
843
963
|
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
964
|
+
it('mode=assign does not stringify multi-value aggregate for non-text scalar targets', async () => {
|
|
965
|
+
const engineEmitter = new EventEmitter();
|
|
966
|
+
const blockEmitter = new EventEmitter();
|
|
967
|
+
const formStub = createFormStub({ roles: [] });
|
|
847
968
|
|
|
848
|
-
|
|
969
|
+
const blockModel: any = {
|
|
970
|
+
uid: 'form-assign-to-many-number-target',
|
|
971
|
+
flowEngine: { emitter: engineEmitter },
|
|
972
|
+
emitter: blockEmitter,
|
|
973
|
+
dispatchEvent: vi.fn(),
|
|
974
|
+
getAclActionName: () => 'create',
|
|
975
|
+
};
|
|
976
|
+
|
|
977
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
978
|
+
runtime.mount({ sync: true });
|
|
979
|
+
|
|
980
|
+
const blockCtx = createFieldContext(runtime);
|
|
981
|
+
const scoreField: any = { isAssociationField: () => false, interface: 'number' };
|
|
982
|
+
const rolesCollection: any = { getField: () => null, filterTargetKey: 'name' };
|
|
983
|
+
const rolesField: any = {
|
|
984
|
+
isAssociationField: () => true,
|
|
985
|
+
type: 'belongsToMany',
|
|
986
|
+
targetCollection: rolesCollection,
|
|
987
|
+
};
|
|
988
|
+
const collection: any = {
|
|
989
|
+
getField: (name: string) => {
|
|
990
|
+
if (name === 'score') return scoreField;
|
|
991
|
+
if (name === 'roles') return rolesField;
|
|
992
|
+
return null;
|
|
993
|
+
},
|
|
994
|
+
};
|
|
995
|
+
blockCtx.defineProperty('collection', { value: collection });
|
|
996
|
+
blockModel.context = blockCtx;
|
|
997
|
+
|
|
998
|
+
runtime.syncAssignRules([
|
|
999
|
+
{
|
|
1000
|
+
key: 'score-from-role-level',
|
|
1001
|
+
enable: true,
|
|
1002
|
+
targetPath: 'score',
|
|
1003
|
+
mode: 'assign',
|
|
1004
|
+
condition: { logic: '$and', items: [] },
|
|
1005
|
+
value: '{{ ctx.formValues.roles.level }}',
|
|
1006
|
+
},
|
|
1007
|
+
]);
|
|
1008
|
+
|
|
1009
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ level: 3 }] }], { source: 'user' });
|
|
1010
|
+
await waitFor(() => expect(formStub.getFieldValue(['score'])).toBe(3));
|
|
1011
|
+
|
|
1012
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ level: 1 }, { level: 2 }] }], {
|
|
1013
|
+
source: 'user',
|
|
1014
|
+
});
|
|
1015
|
+
await new Promise((resolve) => setTimeout(resolve, 50));
|
|
1016
|
+
expect(formStub.getFieldValue(['score'])).toBe(3);
|
|
849
1017
|
});
|
|
850
1018
|
|
|
851
|
-
it('
|
|
1019
|
+
it('tracks dynamic ctx.resolveJsonTemplate formValues dependencies from RunJS values', async () => {
|
|
852
1020
|
const engineEmitter = new EventEmitter();
|
|
853
1021
|
const blockEmitter = new EventEmitter();
|
|
854
1022
|
const formStub = createFormStub({ selector: 'x', x: 'AAA', y: 'BBB' });
|
|
855
1023
|
|
|
856
1024
|
const blockModel: any = {
|
|
857
|
-
uid: 'form-assign-
|
|
1025
|
+
uid: 'form-assign-runjs-dynamic-template-deps',
|
|
858
1026
|
flowEngine: { emitter: engineEmitter },
|
|
859
1027
|
emitter: blockEmitter,
|
|
860
1028
|
dispatchEvent: vi.fn(),
|
|
@@ -866,56 +1034,104 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
866
1034
|
|
|
867
1035
|
const blockCtx = createFieldContext(runtime);
|
|
868
1036
|
const fieldModel: any = {
|
|
869
|
-
uid: 'field-a-
|
|
1037
|
+
uid: 'field-a-runjs-dynamic-template-deps',
|
|
870
1038
|
context: { fieldPathArray: ['a'] },
|
|
871
1039
|
};
|
|
872
1040
|
blockCtx.defineProperty('engine', {
|
|
873
1041
|
value: {
|
|
874
|
-
getModel: (id: string) => (id === 'field-a-
|
|
1042
|
+
getModel: (id: string) => (id === 'field-a-runjs-dynamic-template-deps' ? fieldModel : null),
|
|
875
1043
|
},
|
|
876
1044
|
});
|
|
877
1045
|
blockModel.context = blockCtx;
|
|
878
1046
|
|
|
879
1047
|
runtime.syncAssignRules([
|
|
880
1048
|
{
|
|
881
|
-
key: '
|
|
1049
|
+
key: 'runjs-dynamic-template-deps',
|
|
882
1050
|
enable: true,
|
|
883
1051
|
targetPath: 'a',
|
|
884
|
-
mode: '
|
|
1052
|
+
mode: 'assign',
|
|
885
1053
|
condition: { logic: '$and', items: [] },
|
|
886
|
-
value:
|
|
1054
|
+
value: {
|
|
1055
|
+
code: "const key = ctx.formValues.selector; return await ctx.resolveJsonTemplate('{{ ctx.formValues.' + key + ' }}');",
|
|
1056
|
+
version: 'v1',
|
|
1057
|
+
},
|
|
887
1058
|
},
|
|
888
1059
|
]);
|
|
889
1060
|
|
|
890
1061
|
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('AAA'));
|
|
891
1062
|
|
|
892
|
-
await runtime.setFormValues(blockCtx, [{ path: ['
|
|
893
|
-
expect(formStub.getFieldValue(['a'])).toBe('
|
|
1063
|
+
await runtime.setFormValues(blockCtx, [{ path: ['x'], value: 'AAA2' }], { source: 'user' });
|
|
1064
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('AAA2'));
|
|
894
1065
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
runtime.resetAfterFormReset();
|
|
1066
|
+
await runtime.setFormValues(blockCtx, [{ path: ['selector'], value: 'y' }], { source: 'user' });
|
|
1067
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('BBB'));
|
|
1068
|
+
});
|
|
899
1069
|
|
|
900
|
-
|
|
901
|
-
|
|
1070
|
+
it('mode=assign keeps local formValues replacement text literal', async () => {
|
|
1071
|
+
const engineEmitter = new EventEmitter();
|
|
1072
|
+
const blockEmitter = new EventEmitter();
|
|
1073
|
+
const formStub = createFormStub({ roles: [] });
|
|
902
1074
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
1075
|
+
const blockModel: any = {
|
|
1076
|
+
uid: 'form-assign-local-template-literal',
|
|
1077
|
+
flowEngine: { emitter: engineEmitter },
|
|
1078
|
+
emitter: blockEmitter,
|
|
1079
|
+
dispatchEvent: vi.fn(),
|
|
1080
|
+
getAclActionName: () => 'create',
|
|
1081
|
+
};
|
|
1082
|
+
|
|
1083
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1084
|
+
runtime.mount({ sync: true });
|
|
1085
|
+
|
|
1086
|
+
const blockCtx = createFieldContext(runtime);
|
|
1087
|
+
blockCtx.defineProperty('locale', { value: 'zh-CN' });
|
|
1088
|
+
|
|
1089
|
+
const usernameField: any = { isAssociationField: () => false, interface: 'input' };
|
|
1090
|
+
const rolesCollection: any = { getField: () => null, filterTargetKey: 'name' };
|
|
1091
|
+
const rolesField: any = {
|
|
1092
|
+
isAssociationField: () => true,
|
|
1093
|
+
type: 'belongsToMany',
|
|
1094
|
+
targetCollection: rolesCollection,
|
|
1095
|
+
};
|
|
1096
|
+
const collection: any = {
|
|
1097
|
+
getField: (name: string) => {
|
|
1098
|
+
if (name === 'username') return usernameField;
|
|
1099
|
+
if (name === 'roles') return rolesField;
|
|
1100
|
+
return null;
|
|
1101
|
+
},
|
|
1102
|
+
};
|
|
1103
|
+
blockCtx.defineProperty('collection', { value: collection });
|
|
1104
|
+
blockModel.context = blockCtx;
|
|
1105
|
+
|
|
1106
|
+
runtime.syncAssignRules([
|
|
1107
|
+
{
|
|
1108
|
+
key: 'username-from-role-template-literal',
|
|
1109
|
+
enable: true,
|
|
1110
|
+
targetPath: 'username',
|
|
1111
|
+
mode: 'assign',
|
|
1112
|
+
condition: { logic: '$and', items: [] },
|
|
1113
|
+
value: '{{ ctx.formValues.roles.title }}',
|
|
1114
|
+
},
|
|
1115
|
+
]);
|
|
1116
|
+
|
|
1117
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ title: '{{ ctx.locale }}' }] }], {
|
|
1118
|
+
source: 'user',
|
|
1119
|
+
});
|
|
1120
|
+
|
|
1121
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('{{ ctx.locale }}'));
|
|
906
1122
|
});
|
|
907
1123
|
|
|
908
|
-
it('
|
|
1124
|
+
it('mode=default follows explicit semantics and stops after user change', async () => {
|
|
909
1125
|
const engineEmitter = new EventEmitter();
|
|
910
1126
|
const blockEmitter = new EventEmitter();
|
|
911
1127
|
const formStub = createFormStub({ b: 'X' });
|
|
912
1128
|
|
|
913
1129
|
const blockModel: any = {
|
|
914
|
-
uid: 'form-assign-
|
|
1130
|
+
uid: 'form-assign-2',
|
|
915
1131
|
flowEngine: { emitter: engineEmitter },
|
|
916
1132
|
emitter: blockEmitter,
|
|
917
1133
|
dispatchEvent: vi.fn(),
|
|
918
|
-
getAclActionName: () => '
|
|
1134
|
+
getAclActionName: () => 'create',
|
|
919
1135
|
};
|
|
920
1136
|
|
|
921
1137
|
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
@@ -923,12 +1139,12 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
923
1139
|
|
|
924
1140
|
const blockCtx = createFieldContext(runtime);
|
|
925
1141
|
const fieldModel: any = {
|
|
926
|
-
uid: 'field-
|
|
1142
|
+
uid: 'field-a2',
|
|
927
1143
|
context: { fieldPathArray: ['a'] },
|
|
928
1144
|
};
|
|
929
1145
|
blockCtx.defineProperty('engine', {
|
|
930
1146
|
value: {
|
|
931
|
-
getModel: (id: string) => (id === 'field-
|
|
1147
|
+
getModel: (id: string) => (id === 'field-a2' ? fieldModel : null),
|
|
932
1148
|
},
|
|
933
1149
|
});
|
|
934
1150
|
blockModel.context = blockCtx;
|
|
@@ -944,21 +1160,24 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
944
1160
|
},
|
|
945
1161
|
]);
|
|
946
1162
|
|
|
947
|
-
await
|
|
948
|
-
|
|
1163
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('X'));
|
|
1164
|
+
|
|
1165
|
+
// user writes to target; default mode MUST stop permanently
|
|
1166
|
+
await runtime.setFormValues(blockCtx, [{ path: ['a'], value: 'user' }], { source: 'user' });
|
|
1167
|
+
expect(formStub.getFieldValue(['a'])).toBe('user');
|
|
949
1168
|
|
|
950
1169
|
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'Y' }], { source: 'user' });
|
|
951
1170
|
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
952
|
-
expect(formStub.getFieldValue(['a'])).
|
|
1171
|
+
expect(formStub.getFieldValue(['a'])).toBe('user');
|
|
953
1172
|
});
|
|
954
1173
|
|
|
955
|
-
it('
|
|
1174
|
+
it('mode=default follows to-many association subpath changes without clearing on transient empty arrays', async () => {
|
|
956
1175
|
const engineEmitter = new EventEmitter();
|
|
957
1176
|
const blockEmitter = new EventEmitter();
|
|
958
|
-
const formStub = createFormStub();
|
|
1177
|
+
const formStub = createFormStub({ roles: [] });
|
|
959
1178
|
|
|
960
1179
|
const blockModel: any = {
|
|
961
|
-
uid: 'form-assign-default-
|
|
1180
|
+
uid: 'form-assign-default-to-many-source',
|
|
962
1181
|
flowEngine: { emitter: engineEmitter },
|
|
963
1182
|
emitter: blockEmitter,
|
|
964
1183
|
dispatchEvent: vi.fn(),
|
|
@@ -969,10 +1188,542 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
969
1188
|
runtime.mount({ sync: true });
|
|
970
1189
|
|
|
971
1190
|
const blockCtx = createFieldContext(runtime);
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
const
|
|
975
|
-
|
|
1191
|
+
const usernameField: any = { isAssociationField: () => false, interface: 'input' };
|
|
1192
|
+
const rolesCollection: any = { getField: () => null, filterTargetKey: 'name' };
|
|
1193
|
+
const rolesField: any = {
|
|
1194
|
+
isAssociationField: () => true,
|
|
1195
|
+
type: 'belongsToMany',
|
|
1196
|
+
targetCollection: rolesCollection,
|
|
1197
|
+
};
|
|
1198
|
+
const collection: any = {
|
|
1199
|
+
getField: (name: string) => {
|
|
1200
|
+
if (name === 'username') return usernameField;
|
|
1201
|
+
if (name === 'roles') return rolesField;
|
|
1202
|
+
return null;
|
|
1203
|
+
},
|
|
1204
|
+
};
|
|
1205
|
+
blockCtx.defineProperty('collection', { value: collection });
|
|
1206
|
+
blockModel.context = blockCtx;
|
|
1207
|
+
|
|
1208
|
+
runtime.syncAssignRules([
|
|
1209
|
+
{
|
|
1210
|
+
key: 'username-from-role-name',
|
|
1211
|
+
enable: true,
|
|
1212
|
+
targetPath: 'username',
|
|
1213
|
+
mode: 'default',
|
|
1214
|
+
condition: { logic: '$and', items: [] },
|
|
1215
|
+
value: '{{ ctx.formValues.roles.name }}',
|
|
1216
|
+
},
|
|
1217
|
+
]);
|
|
1218
|
+
|
|
1219
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1220
|
+
expect(formStub.getFieldValue(['username'])).toBeUndefined();
|
|
1221
|
+
|
|
1222
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ name: 'manager' }] }], { source: 'user' });
|
|
1223
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('manager'));
|
|
1224
|
+
|
|
1225
|
+
// Association selects can briefly carry an incomplete display record while option data is refreshing.
|
|
1226
|
+
// For scalar targets, default mode should not treat that empty aggregate as a real default value.
|
|
1227
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ name: null }] }], { source: 'system' });
|
|
1228
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
1229
|
+
expect(formStub.getFieldValue(['username'])).toBe('manager');
|
|
1230
|
+
|
|
1231
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ name: 'member' }] }], { source: 'user' });
|
|
1232
|
+
await waitFor(() => expect(formStub.getFieldValue(['username'])).toBe('member'));
|
|
1233
|
+
|
|
1234
|
+
await runtime.setFormValues(blockCtx, [{ path: ['username'], value: 'manual' }], { source: 'user' });
|
|
1235
|
+
await runtime.setFormValues(blockCtx, [{ path: ['roles'], value: [{ name: 'admin' }] }], { source: 'user' });
|
|
1236
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1237
|
+
expect(formStub.getFieldValue(['username'])).toBe('manual');
|
|
1238
|
+
});
|
|
1239
|
+
|
|
1240
|
+
it('mode=override overwrites existing update values until the user changes the target', async () => {
|
|
1241
|
+
const engineEmitter = new EventEmitter();
|
|
1242
|
+
const blockEmitter = new EventEmitter();
|
|
1243
|
+
const formStub = createFormStub({ a: 'Existing', b: 'X' });
|
|
1244
|
+
|
|
1245
|
+
const blockModel: any = {
|
|
1246
|
+
uid: 'form-assign-override-update',
|
|
1247
|
+
flowEngine: { emitter: engineEmitter },
|
|
1248
|
+
emitter: blockEmitter,
|
|
1249
|
+
dispatchEvent: vi.fn(),
|
|
1250
|
+
getAclActionName: () => 'update',
|
|
1251
|
+
};
|
|
1252
|
+
|
|
1253
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1254
|
+
runtime.mount({ sync: true });
|
|
1255
|
+
|
|
1256
|
+
const blockCtx = createFieldContext(runtime);
|
|
1257
|
+
const fieldModel: any = {
|
|
1258
|
+
uid: 'field-a-override-update',
|
|
1259
|
+
context: { fieldPathArray: ['a'] },
|
|
1260
|
+
};
|
|
1261
|
+
blockCtx.defineProperty('engine', {
|
|
1262
|
+
value: {
|
|
1263
|
+
getModel: (id: string) => (id === 'field-a-override-update' ? fieldModel : null),
|
|
1264
|
+
},
|
|
1265
|
+
});
|
|
1266
|
+
blockModel.context = blockCtx;
|
|
1267
|
+
|
|
1268
|
+
runtime.syncAssignRules([
|
|
1269
|
+
{
|
|
1270
|
+
key: 'r1',
|
|
1271
|
+
enable: true,
|
|
1272
|
+
targetPath: 'a',
|
|
1273
|
+
mode: 'override',
|
|
1274
|
+
condition: { logic: '$and', items: [] },
|
|
1275
|
+
value: '__B__',
|
|
1276
|
+
},
|
|
1277
|
+
]);
|
|
1278
|
+
|
|
1279
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('X'));
|
|
1280
|
+
expect(runtime.canApplyOverrideValuePatch(['a'])).toBe(true);
|
|
1281
|
+
|
|
1282
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'Y' }], { source: 'user' });
|
|
1283
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('Y'));
|
|
1284
|
+
|
|
1285
|
+
await runtime.setFormValues(blockCtx, [{ path: ['a'], value: 'Manual' }], { source: 'user' });
|
|
1286
|
+
expect(formStub.getFieldValue(['a'])).toBe('Manual');
|
|
1287
|
+
expect(runtime.canApplyOverrideValuePatch(['a'])).toBe(false);
|
|
1288
|
+
|
|
1289
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'Z' }], { source: 'user' });
|
|
1290
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1291
|
+
expect(formStub.getFieldValue(['a'])).toBe('Manual');
|
|
1292
|
+
});
|
|
1293
|
+
|
|
1294
|
+
it('stops mode=override for an association root when the user edits descendant fields', async () => {
|
|
1295
|
+
const engineEmitter = new EventEmitter();
|
|
1296
|
+
const blockEmitter = new EventEmitter();
|
|
1297
|
+
const formStub = createFormStub({
|
|
1298
|
+
b: { id: 2, name: 'Auto' },
|
|
1299
|
+
user: { id: 1, name: 'Existing' },
|
|
1300
|
+
});
|
|
1301
|
+
|
|
1302
|
+
const blockModel: any = {
|
|
1303
|
+
uid: 'form-assign-override-association-root',
|
|
1304
|
+
flowEngine: { emitter: engineEmitter },
|
|
1305
|
+
emitter: blockEmitter,
|
|
1306
|
+
dispatchEvent: vi.fn(),
|
|
1307
|
+
getAclActionName: () => 'update',
|
|
1308
|
+
};
|
|
1309
|
+
|
|
1310
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1311
|
+
runtime.mount({ sync: true });
|
|
1312
|
+
|
|
1313
|
+
const blockCtx = createFieldContext(runtime);
|
|
1314
|
+
blockModel.context = blockCtx;
|
|
1315
|
+
|
|
1316
|
+
runtime.syncAssignRules([
|
|
1317
|
+
{
|
|
1318
|
+
key: 'r1',
|
|
1319
|
+
enable: true,
|
|
1320
|
+
targetPath: 'user',
|
|
1321
|
+
mode: 'override',
|
|
1322
|
+
condition: { logic: '$and', items: [] },
|
|
1323
|
+
value: '__B__',
|
|
1324
|
+
},
|
|
1325
|
+
]);
|
|
1326
|
+
|
|
1327
|
+
await waitFor(() => expect(formStub.getFieldValue(['user'])).toEqual({ id: 2, name: 'Auto' }));
|
|
1328
|
+
expect(runtime.canApplyOverrideValuePatch(['user'])).toBe(true);
|
|
1329
|
+
|
|
1330
|
+
lodashSet((formStub as any).__store, ['user'], { id: 3, name: 'Manual' });
|
|
1331
|
+
runtime.handleFormValuesChange({ user: formStub.getFieldValue(['user']) }, formStub.getFieldsValue());
|
|
1332
|
+
expect(runtime.canApplyOverrideValuePatch(['user'])).toBe(false);
|
|
1333
|
+
|
|
1334
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: { id: 4, name: 'Next' } }], { source: 'user' });
|
|
1335
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1336
|
+
|
|
1337
|
+
expect(formStub.getFieldValue(['user'])).toEqual({ id: 3, name: 'Manual' });
|
|
1338
|
+
});
|
|
1339
|
+
|
|
1340
|
+
it('stops mode=override for a subtable relation array after a user edits a row cell', async () => {
|
|
1341
|
+
const engineEmitter = new EventEmitter();
|
|
1342
|
+
const blockEmitter = new EventEmitter();
|
|
1343
|
+
const formStub = createFormStub({
|
|
1344
|
+
roles: [
|
|
1345
|
+
{ id: 1, name: 'Existing A' },
|
|
1346
|
+
{ id: 2, name: 'Existing B' },
|
|
1347
|
+
],
|
|
1348
|
+
});
|
|
1349
|
+
|
|
1350
|
+
const blockModel: any = {
|
|
1351
|
+
uid: 'form-assign-override-subtable-array',
|
|
1352
|
+
flowEngine: { emitter: engineEmitter },
|
|
1353
|
+
emitter: blockEmitter,
|
|
1354
|
+
dispatchEvent: vi.fn(),
|
|
1355
|
+
getAclActionName: () => 'update',
|
|
1356
|
+
};
|
|
1357
|
+
|
|
1358
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1359
|
+
runtime.mount({ sync: true });
|
|
1360
|
+
|
|
1361
|
+
const blockCtx = createFieldContext(runtime);
|
|
1362
|
+
blockModel.context = blockCtx;
|
|
1363
|
+
|
|
1364
|
+
runtime.syncAssignRules([
|
|
1365
|
+
{
|
|
1366
|
+
key: 'r1',
|
|
1367
|
+
enable: true,
|
|
1368
|
+
targetPath: 'roles',
|
|
1369
|
+
mode: 'override',
|
|
1370
|
+
condition: { logic: '$and', items: [] },
|
|
1371
|
+
value: [
|
|
1372
|
+
{ id: 1, name: 'Auto A' },
|
|
1373
|
+
{ id: 2, name: 'Auto B' },
|
|
1374
|
+
],
|
|
1375
|
+
},
|
|
1376
|
+
]);
|
|
1377
|
+
|
|
1378
|
+
await waitFor(() =>
|
|
1379
|
+
expect(formStub.getFieldValue(['roles'])).toEqual([
|
|
1380
|
+
{ id: 1, name: 'Auto A' },
|
|
1381
|
+
{ id: 2, name: 'Auto B' },
|
|
1382
|
+
]),
|
|
1383
|
+
);
|
|
1384
|
+
|
|
1385
|
+
lodashSet((formStub as any).__store, ['roles', 0, 'name'], 'Manual A');
|
|
1386
|
+
runtime.handleFormFieldsChange([{ name: ['roles', 0, 'name'], touched: true } as any]);
|
|
1387
|
+
runtime.handleFormValuesChange({ roles: formStub.getFieldValue(['roles']) }, formStub.getFieldsValue());
|
|
1388
|
+
expect(runtime.canApplyOverrideValuePatch(['roles'])).toBe(false);
|
|
1389
|
+
|
|
1390
|
+
runtime.syncAssignRules([
|
|
1391
|
+
{
|
|
1392
|
+
key: 'r1',
|
|
1393
|
+
enable: true,
|
|
1394
|
+
targetPath: 'roles',
|
|
1395
|
+
mode: 'override',
|
|
1396
|
+
condition: { logic: '$and', items: [] },
|
|
1397
|
+
value: [
|
|
1398
|
+
{ id: 1, name: 'Next A' },
|
|
1399
|
+
{ id: 2, name: 'Next B' },
|
|
1400
|
+
{ id: 3, name: 'Unexpected extra row' },
|
|
1401
|
+
],
|
|
1402
|
+
},
|
|
1403
|
+
]);
|
|
1404
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1405
|
+
|
|
1406
|
+
expect(formStub.getFieldValue(['roles'])).toEqual([
|
|
1407
|
+
{ id: 1, name: 'Manual A' },
|
|
1408
|
+
{ id: 2, name: 'Auto B' },
|
|
1409
|
+
]);
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
it('stops mode=override after a user clears a multiple value array', async () => {
|
|
1413
|
+
const engineEmitter = new EventEmitter();
|
|
1414
|
+
const blockEmitter = new EventEmitter();
|
|
1415
|
+
const formStub = createFormStub({ b: 'ready', tags: ['Existing'] });
|
|
1416
|
+
|
|
1417
|
+
const blockModel: any = {
|
|
1418
|
+
uid: 'form-assign-override-multiple-clear',
|
|
1419
|
+
flowEngine: { emitter: engineEmitter },
|
|
1420
|
+
emitter: blockEmitter,
|
|
1421
|
+
dispatchEvent: vi.fn(),
|
|
1422
|
+
getAclActionName: () => 'update',
|
|
1423
|
+
};
|
|
1424
|
+
|
|
1425
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1426
|
+
runtime.mount({ sync: true });
|
|
1427
|
+
|
|
1428
|
+
const blockCtx = createFieldContext(runtime);
|
|
1429
|
+
blockModel.context = blockCtx;
|
|
1430
|
+
|
|
1431
|
+
runtime.syncAssignRules([
|
|
1432
|
+
{
|
|
1433
|
+
key: 'r1',
|
|
1434
|
+
enable: true,
|
|
1435
|
+
targetPath: 'tags',
|
|
1436
|
+
mode: 'override',
|
|
1437
|
+
condition: { logic: '$and', items: [{ path: '{{ ctx.formValues.b }}', operator: '$notEmpty', value: '' }] },
|
|
1438
|
+
value: ['A', 'B'],
|
|
1439
|
+
},
|
|
1440
|
+
]);
|
|
1441
|
+
|
|
1442
|
+
await waitFor(() => expect(formStub.getFieldValue(['tags'])).toEqual(['A', 'B']));
|
|
1443
|
+
expect(runtime.canApplyOverrideValuePatch(['tags'])).toBe(true);
|
|
1444
|
+
|
|
1445
|
+
lodashSet((formStub as any).__store, ['tags'], []);
|
|
1446
|
+
runtime.handleFormValuesChange({ tags: [] }, formStub.getFieldsValue());
|
|
1447
|
+
expect(runtime.canApplyOverrideValuePatch(['tags'])).toBe(false);
|
|
1448
|
+
|
|
1449
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'changed' }], { source: 'user' });
|
|
1450
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1451
|
+
|
|
1452
|
+
expect(formStub.getFieldValue(['tags'])).toEqual([]);
|
|
1453
|
+
});
|
|
1454
|
+
|
|
1455
|
+
it('stops mode=override after a user removes an item from a multiple value array', async () => {
|
|
1456
|
+
const engineEmitter = new EventEmitter();
|
|
1457
|
+
const blockEmitter = new EventEmitter();
|
|
1458
|
+
const formStub = createFormStub({ b: 'ready', tags: ['Existing'] });
|
|
1459
|
+
|
|
1460
|
+
const blockModel: any = {
|
|
1461
|
+
uid: 'form-assign-override-multiple-remove-item',
|
|
1462
|
+
flowEngine: { emitter: engineEmitter },
|
|
1463
|
+
emitter: blockEmitter,
|
|
1464
|
+
dispatchEvent: vi.fn(),
|
|
1465
|
+
getAclActionName: () => 'update',
|
|
1466
|
+
};
|
|
1467
|
+
|
|
1468
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1469
|
+
runtime.mount({ sync: true });
|
|
1470
|
+
|
|
1471
|
+
const blockCtx = createFieldContext(runtime);
|
|
1472
|
+
blockModel.context = blockCtx;
|
|
1473
|
+
|
|
1474
|
+
runtime.syncAssignRules([
|
|
1475
|
+
{
|
|
1476
|
+
key: 'r1',
|
|
1477
|
+
enable: true,
|
|
1478
|
+
targetPath: 'tags',
|
|
1479
|
+
mode: 'override',
|
|
1480
|
+
condition: { logic: '$and', items: [{ path: '{{ ctx.formValues.b }}', operator: '$notEmpty', value: '' }] },
|
|
1481
|
+
value: ['A', 'B'],
|
|
1482
|
+
},
|
|
1483
|
+
]);
|
|
1484
|
+
|
|
1485
|
+
await waitFor(() => expect(formStub.getFieldValue(['tags'])).toEqual(['A', 'B']));
|
|
1486
|
+
expect(runtime.canApplyOverrideValuePatch(['tags'])).toBe(true);
|
|
1487
|
+
|
|
1488
|
+
lodashSet((formStub as any).__store, ['tags'], ['A']);
|
|
1489
|
+
runtime.handleFormValuesChange({ tags: ['A'] }, formStub.getFieldsValue());
|
|
1490
|
+
expect(runtime.canApplyOverrideValuePatch(['tags'])).toBe(false);
|
|
1491
|
+
|
|
1492
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'changed' }], { source: 'user' });
|
|
1493
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1494
|
+
|
|
1495
|
+
expect(formStub.getFieldValue(['tags'])).toEqual(['A']);
|
|
1496
|
+
});
|
|
1497
|
+
|
|
1498
|
+
it('reapplies mode=override after user edited state is reset', async () => {
|
|
1499
|
+
const engineEmitter = new EventEmitter();
|
|
1500
|
+
const blockEmitter = new EventEmitter();
|
|
1501
|
+
const formStub = createFormStub({ a: 'Existing', b: 'X' });
|
|
1502
|
+
|
|
1503
|
+
const blockModel: any = {
|
|
1504
|
+
uid: 'form-assign-override-reset-user-edited',
|
|
1505
|
+
flowEngine: { emitter: engineEmitter },
|
|
1506
|
+
emitter: blockEmitter,
|
|
1507
|
+
dispatchEvent: vi.fn(),
|
|
1508
|
+
getAclActionName: () => 'update',
|
|
1509
|
+
};
|
|
1510
|
+
|
|
1511
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1512
|
+
runtime.mount({ sync: true });
|
|
1513
|
+
|
|
1514
|
+
const blockCtx = createFieldContext(runtime);
|
|
1515
|
+
const fieldModel: any = {
|
|
1516
|
+
uid: 'field-a-override-reset-user-edited',
|
|
1517
|
+
context: { fieldPathArray: ['a'] },
|
|
1518
|
+
};
|
|
1519
|
+
blockCtx.defineProperty('engine', {
|
|
1520
|
+
value: {
|
|
1521
|
+
getModel: (id: string) => (id === 'field-a-override-reset-user-edited' ? fieldModel : null),
|
|
1522
|
+
},
|
|
1523
|
+
});
|
|
1524
|
+
blockModel.context = blockCtx;
|
|
1525
|
+
|
|
1526
|
+
runtime.syncAssignRules([
|
|
1527
|
+
{
|
|
1528
|
+
key: 'r1',
|
|
1529
|
+
enable: true,
|
|
1530
|
+
targetPath: 'a',
|
|
1531
|
+
mode: 'override',
|
|
1532
|
+
condition: { logic: '$and', items: [] },
|
|
1533
|
+
value: '__B__',
|
|
1534
|
+
},
|
|
1535
|
+
]);
|
|
1536
|
+
|
|
1537
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('X'));
|
|
1538
|
+
|
|
1539
|
+
await runtime.setFormValues(blockCtx, [{ path: ['a'], value: 'Manual' }], { source: 'user' });
|
|
1540
|
+
expect(formStub.getFieldValue(['a'])).toBe('Manual');
|
|
1541
|
+
|
|
1542
|
+
formStub.__store.a = 'Next record';
|
|
1543
|
+
formStub.__store.b = 'Y';
|
|
1544
|
+
runtime.handleFormValuesChange({ a: 'Next record', b: 'Y' }, formStub.getFieldsValue());
|
|
1545
|
+
runtime.resetUserEditedState();
|
|
1546
|
+
expect(runtime.canApplyOverrideValuePatch(['a'])).toBe(true);
|
|
1547
|
+
|
|
1548
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('Y'));
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
it('reapplies mode=default value after form reset starts a new create session', async () => {
|
|
1552
|
+
const engineEmitter = new EventEmitter();
|
|
1553
|
+
const blockEmitter = new EventEmitter();
|
|
1554
|
+
const formStub = createFormStub({});
|
|
1555
|
+
|
|
1556
|
+
const blockModel: any = {
|
|
1557
|
+
uid: 'form-assign-reset-reapply-default',
|
|
1558
|
+
flowEngine: { emitter: engineEmitter },
|
|
1559
|
+
emitter: blockEmitter,
|
|
1560
|
+
dispatchEvent: vi.fn(),
|
|
1561
|
+
getAclActionName: () => 'create',
|
|
1562
|
+
};
|
|
1563
|
+
|
|
1564
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1565
|
+
runtime.mount({ sync: true });
|
|
1566
|
+
|
|
1567
|
+
const blockCtx = createFieldContext(runtime);
|
|
1568
|
+
const fieldModel: any = {
|
|
1569
|
+
uid: 'field-a-reset-reapply-default',
|
|
1570
|
+
context: { fieldPathArray: ['a'] },
|
|
1571
|
+
};
|
|
1572
|
+
blockCtx.defineProperty('engine', {
|
|
1573
|
+
value: {
|
|
1574
|
+
getModel: (id: string) => (id === 'field-a-reset-reapply-default' ? fieldModel : null),
|
|
1575
|
+
},
|
|
1576
|
+
});
|
|
1577
|
+
blockModel.context = blockCtx;
|
|
1578
|
+
|
|
1579
|
+
runtime.syncAssignRules([
|
|
1580
|
+
{
|
|
1581
|
+
key: 'r1',
|
|
1582
|
+
enable: true,
|
|
1583
|
+
targetPath: 'a',
|
|
1584
|
+
mode: 'default',
|
|
1585
|
+
condition: { logic: '$and', items: [] },
|
|
1586
|
+
value: 'AAA',
|
|
1587
|
+
},
|
|
1588
|
+
]);
|
|
1589
|
+
|
|
1590
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('AAA'));
|
|
1591
|
+
|
|
1592
|
+
await runtime.setFormValues(blockCtx, [{ path: ['a'], value: 'AAA1' }], { source: 'user' });
|
|
1593
|
+
expect(formStub.getFieldValue(['a'])).toBe('AAA1');
|
|
1594
|
+
|
|
1595
|
+
formStub.__store.a = undefined;
|
|
1596
|
+
delete formStub.__store.a;
|
|
1597
|
+
runtime.resetAfterFormReset();
|
|
1598
|
+
|
|
1599
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('AAA'));
|
|
1600
|
+
});
|
|
1601
|
+
|
|
1602
|
+
it('resets valuesMirror before rescheduling default rules after form reset', async () => {
|
|
1603
|
+
const engineEmitter = new EventEmitter();
|
|
1604
|
+
const blockEmitter = new EventEmitter();
|
|
1605
|
+
const formStub = createFormStub({ selector: 'x', x: 'AAA', y: 'BBB' });
|
|
1606
|
+
|
|
1607
|
+
const blockModel: any = {
|
|
1608
|
+
uid: 'form-assign-reset-values-mirror',
|
|
1609
|
+
flowEngine: { emitter: engineEmitter },
|
|
1610
|
+
emitter: blockEmitter,
|
|
1611
|
+
dispatchEvent: vi.fn(),
|
|
1612
|
+
getAclActionName: () => 'create',
|
|
1613
|
+
};
|
|
1614
|
+
|
|
1615
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1616
|
+
runtime.mount({ sync: true });
|
|
1617
|
+
|
|
1618
|
+
const blockCtx = createFieldContext(runtime);
|
|
1619
|
+
const fieldModel: any = {
|
|
1620
|
+
uid: 'field-a-reset-values-mirror',
|
|
1621
|
+
context: { fieldPathArray: ['a'] },
|
|
1622
|
+
};
|
|
1623
|
+
blockCtx.defineProperty('engine', {
|
|
1624
|
+
value: {
|
|
1625
|
+
getModel: (id: string) => (id === 'field-a-reset-values-mirror' ? fieldModel : null),
|
|
1626
|
+
},
|
|
1627
|
+
});
|
|
1628
|
+
blockModel.context = blockCtx;
|
|
1629
|
+
|
|
1630
|
+
runtime.syncAssignRules([
|
|
1631
|
+
{
|
|
1632
|
+
key: 'r1',
|
|
1633
|
+
enable: true,
|
|
1634
|
+
targetPath: 'a',
|
|
1635
|
+
mode: 'default',
|
|
1636
|
+
condition: { logic: '$and', items: [] },
|
|
1637
|
+
value: '__DYNAMIC__',
|
|
1638
|
+
},
|
|
1639
|
+
]);
|
|
1640
|
+
|
|
1641
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('AAA'));
|
|
1642
|
+
|
|
1643
|
+
await runtime.setFormValues(blockCtx, [{ path: ['a'], value: 'AAA1' }], { source: 'user' });
|
|
1644
|
+
expect(formStub.getFieldValue(['a'])).toBe('AAA1');
|
|
1645
|
+
|
|
1646
|
+
for (const key of Object.keys(formStub.__store)) {
|
|
1647
|
+
delete formStub.__store[key];
|
|
1648
|
+
}
|
|
1649
|
+
runtime.resetAfterFormReset();
|
|
1650
|
+
|
|
1651
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1652
|
+
expect(formStub.getFieldValue(['a'])).toBeUndefined();
|
|
1653
|
+
|
|
1654
|
+
await runtime.setFormValues(blockCtx, [{ path: ['selector'], value: 'y' }], { source: 'user' });
|
|
1655
|
+
await runtime.setFormValues(blockCtx, [{ path: ['y'], value: 'BBB2' }], { source: 'user' });
|
|
1656
|
+
await waitFor(() => expect(formStub.getFieldValue(['a'])).toBe('BBB2'));
|
|
1657
|
+
});
|
|
1658
|
+
|
|
1659
|
+
it('skips mode=default form assignment in update form', async () => {
|
|
1660
|
+
const engineEmitter = new EventEmitter();
|
|
1661
|
+
const blockEmitter = new EventEmitter();
|
|
1662
|
+
const formStub = createFormStub({ b: 'X' });
|
|
1663
|
+
|
|
1664
|
+
const blockModel: any = {
|
|
1665
|
+
uid: 'form-assign-update-skip-default',
|
|
1666
|
+
flowEngine: { emitter: engineEmitter },
|
|
1667
|
+
emitter: blockEmitter,
|
|
1668
|
+
dispatchEvent: vi.fn(),
|
|
1669
|
+
getAclActionName: () => 'update',
|
|
1670
|
+
};
|
|
1671
|
+
|
|
1672
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1673
|
+
runtime.mount({ sync: true });
|
|
1674
|
+
|
|
1675
|
+
const blockCtx = createFieldContext(runtime);
|
|
1676
|
+
const fieldModel: any = {
|
|
1677
|
+
uid: 'field-a-update-skip-default',
|
|
1678
|
+
context: { fieldPathArray: ['a'] },
|
|
1679
|
+
};
|
|
1680
|
+
blockCtx.defineProperty('engine', {
|
|
1681
|
+
value: {
|
|
1682
|
+
getModel: (id: string) => (id === 'field-a-update-skip-default' ? fieldModel : null),
|
|
1683
|
+
},
|
|
1684
|
+
});
|
|
1685
|
+
blockModel.context = blockCtx;
|
|
1686
|
+
|
|
1687
|
+
runtime.syncAssignRules([
|
|
1688
|
+
{
|
|
1689
|
+
key: 'r1',
|
|
1690
|
+
enable: true,
|
|
1691
|
+
targetPath: 'a',
|
|
1692
|
+
mode: 'default',
|
|
1693
|
+
condition: { logic: '$and', items: [] },
|
|
1694
|
+
value: '__B__',
|
|
1695
|
+
},
|
|
1696
|
+
]);
|
|
1697
|
+
|
|
1698
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1699
|
+
expect(formStub.getFieldValue(['a'])).toBeUndefined();
|
|
1700
|
+
|
|
1701
|
+
await runtime.setFormValues(blockCtx, [{ path: ['b'], value: 'Y' }], { source: 'user' });
|
|
1702
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1703
|
+
expect(formStub.getFieldValue(['a'])).toBeUndefined();
|
|
1704
|
+
});
|
|
1705
|
+
|
|
1706
|
+
it('syncs UI props for mode=default form assignment on configured fields', async () => {
|
|
1707
|
+
const engineEmitter = new EventEmitter();
|
|
1708
|
+
const blockEmitter = new EventEmitter();
|
|
1709
|
+
const formStub = createFormStub();
|
|
1710
|
+
|
|
1711
|
+
const blockModel: any = {
|
|
1712
|
+
uid: 'form-assign-default-ui-sync',
|
|
1713
|
+
flowEngine: { emitter: engineEmitter },
|
|
1714
|
+
emitter: blockEmitter,
|
|
1715
|
+
dispatchEvent: vi.fn(),
|
|
1716
|
+
getAclActionName: () => 'create',
|
|
1717
|
+
};
|
|
1718
|
+
|
|
1719
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1720
|
+
runtime.mount({ sync: true });
|
|
1721
|
+
|
|
1722
|
+
const blockCtx = createFieldContext(runtime);
|
|
1723
|
+
blockModel.context = blockCtx;
|
|
1724
|
+
|
|
1725
|
+
const fieldCtx = createFieldContext(runtime);
|
|
1726
|
+
fieldCtx.defineProperty('blockModel', { value: blockModel });
|
|
976
1727
|
fieldCtx.defineProperty('fieldPathArray', { value: ['color'] });
|
|
977
1728
|
|
|
978
1729
|
const colorFieldModel: any = {
|
|
@@ -1014,6 +1765,116 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
1014
1765
|
expect(colorFieldModel.setProps).toHaveBeenCalledWith({ value: '#1677FF' });
|
|
1015
1766
|
});
|
|
1016
1767
|
|
|
1768
|
+
it('syncs mounted field UI props after programmatic form writes', async () => {
|
|
1769
|
+
const engineEmitter = new EventEmitter();
|
|
1770
|
+
const blockEmitter = new EventEmitter();
|
|
1771
|
+
const formStub = createFormStub();
|
|
1772
|
+
|
|
1773
|
+
const blockModel: any = {
|
|
1774
|
+
uid: 'form-runtime-mounted-field-ui-sync',
|
|
1775
|
+
flowEngine: { emitter: engineEmitter },
|
|
1776
|
+
emitter: blockEmitter,
|
|
1777
|
+
dispatchEvent: vi.fn(),
|
|
1778
|
+
getAclActionName: () => 'create',
|
|
1779
|
+
};
|
|
1780
|
+
|
|
1781
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1782
|
+
|
|
1783
|
+
const blockCtx = createFieldContext(runtime);
|
|
1784
|
+
blockModel.context = blockCtx;
|
|
1785
|
+
|
|
1786
|
+
const fieldCtx = createFieldContext(runtime);
|
|
1787
|
+
fieldCtx.defineProperty('blockModel', { value: blockModel });
|
|
1788
|
+
fieldCtx.defineProperty('fieldPathArray', { value: ['username'] });
|
|
1789
|
+
|
|
1790
|
+
const inputFieldModel: any = {
|
|
1791
|
+
setProps: vi.fn(),
|
|
1792
|
+
};
|
|
1793
|
+
const formItemModel: any = {
|
|
1794
|
+
uid: 'form-item-username-runtime-ui-sync',
|
|
1795
|
+
context: fieldCtx,
|
|
1796
|
+
subModels: { field: inputFieldModel },
|
|
1797
|
+
getStepParams: (flowKey: string, stepKey: string) => {
|
|
1798
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') {
|
|
1799
|
+
return { fieldPath: 'username' };
|
|
1800
|
+
}
|
|
1801
|
+
return undefined;
|
|
1802
|
+
},
|
|
1803
|
+
setProps: vi.fn(),
|
|
1804
|
+
};
|
|
1805
|
+
|
|
1806
|
+
blockCtx.defineProperty('engine', {
|
|
1807
|
+
value: {
|
|
1808
|
+
forEachModel: (visit: (model: any) => void) => visit(formItemModel),
|
|
1809
|
+
},
|
|
1810
|
+
});
|
|
1811
|
+
|
|
1812
|
+
runtime.mount({ sync: true });
|
|
1813
|
+
|
|
1814
|
+
await runtime.setFormValues(blockCtx, [{ path: ['username'], value: 'Admin' }], { source: 'system' });
|
|
1815
|
+
|
|
1816
|
+
expect(formStub.getFieldValue(['username'])).toBe('Admin');
|
|
1817
|
+
expect(formItemModel.setProps).toHaveBeenCalledWith({ value: 'Admin' });
|
|
1818
|
+
expect(inputFieldModel.setProps).toHaveBeenCalledWith({ value: 'Admin' });
|
|
1819
|
+
});
|
|
1820
|
+
|
|
1821
|
+
it('syncs mounted field UI props from model index without scanning all models on every write', async () => {
|
|
1822
|
+
const engineEmitter = new EventEmitter();
|
|
1823
|
+
const blockEmitter = new EventEmitter();
|
|
1824
|
+
const formStub = createFormStub();
|
|
1825
|
+
|
|
1826
|
+
const blockModel: any = {
|
|
1827
|
+
uid: 'form-runtime-mounted-field-ui-index-sync',
|
|
1828
|
+
flowEngine: { emitter: engineEmitter },
|
|
1829
|
+
emitter: blockEmitter,
|
|
1830
|
+
dispatchEvent: vi.fn(),
|
|
1831
|
+
getAclActionName: () => 'create',
|
|
1832
|
+
};
|
|
1833
|
+
|
|
1834
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1835
|
+
|
|
1836
|
+
const blockCtx = createFieldContext(runtime);
|
|
1837
|
+
blockModel.context = blockCtx;
|
|
1838
|
+
|
|
1839
|
+
const fieldCtx = createFieldContext(runtime);
|
|
1840
|
+
fieldCtx.defineProperty('blockModel', { value: blockModel });
|
|
1841
|
+
fieldCtx.defineProperty('fieldPathArray', { value: ['username'] });
|
|
1842
|
+
|
|
1843
|
+
const inputFieldModel: any = {
|
|
1844
|
+
setProps: vi.fn(),
|
|
1845
|
+
};
|
|
1846
|
+
const formItemModel: any = {
|
|
1847
|
+
uid: 'form-item-username-runtime-ui-index-sync',
|
|
1848
|
+
context: fieldCtx,
|
|
1849
|
+
subModels: { field: inputFieldModel },
|
|
1850
|
+
getStepParams: (flowKey: string, stepKey: string) => {
|
|
1851
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') {
|
|
1852
|
+
return { fieldPath: 'username' };
|
|
1853
|
+
}
|
|
1854
|
+
return undefined;
|
|
1855
|
+
},
|
|
1856
|
+
setProps: vi.fn(),
|
|
1857
|
+
};
|
|
1858
|
+
|
|
1859
|
+
const forEachModel = vi.fn((visit: (model: any) => void) => visit(formItemModel));
|
|
1860
|
+
blockCtx.defineProperty('engine', {
|
|
1861
|
+
value: {
|
|
1862
|
+
forEachModel,
|
|
1863
|
+
},
|
|
1864
|
+
});
|
|
1865
|
+
|
|
1866
|
+
runtime.mount({ sync: true });
|
|
1867
|
+
engineEmitter.emit('model:mounted', { model: formItemModel });
|
|
1868
|
+
forEachModel.mockClear();
|
|
1869
|
+
|
|
1870
|
+
await runtime.setFormValues(blockCtx, [{ path: ['username'], value: 'Admin' }], { source: 'system' });
|
|
1871
|
+
await runtime.setFormValues(blockCtx, [{ path: ['username'], value: 'Owner' }], { source: 'system' });
|
|
1872
|
+
|
|
1873
|
+
expect(forEachModel).not.toHaveBeenCalled();
|
|
1874
|
+
expect(formItemModel.setProps).toHaveBeenLastCalledWith({ value: 'Owner' });
|
|
1875
|
+
expect(inputFieldModel.setProps).toHaveBeenLastCalledWith({ value: 'Owner' });
|
|
1876
|
+
});
|
|
1877
|
+
|
|
1017
1878
|
it('linkage assignment takes precedence over mode=assign form assignment', async () => {
|
|
1018
1879
|
const engineEmitter = new EventEmitter();
|
|
1019
1880
|
const blockEmitter = new EventEmitter();
|
|
@@ -2396,6 +3257,10 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
2396
3257
|
|
|
2397
3258
|
lodashSet((formStub as any).__store, ['users'], [{ __is_new__: true, name: '' }]);
|
|
2398
3259
|
runtime.handleFormValuesChange({ users: formStub.getFieldValue(['users']) }, formStub.getFieldsValue());
|
|
3260
|
+
expect((runtime as any).findExplicitHit('users')).toBe('users');
|
|
3261
|
+
expect((runtime as any).findExplicitHit('users[0].name')).toBeNull();
|
|
3262
|
+
expect((runtime as any).findUserEditedHit('users')).toBe('users');
|
|
3263
|
+
expect((runtime as any).findUserEditedHit('users[0].name')).toBeNull();
|
|
2399
3264
|
|
|
2400
3265
|
const row0: any = {
|
|
2401
3266
|
uid: 'users.name',
|