@nocobase/client-v2 2.3.0-alpha.1 → 2.3.0-beta.10
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/APIClient.d.ts +12 -0
- package/es/components/form/ScanInput/useCodeScanner.d.ts +12 -2
- package/es/components/form/ScanInput/zxingWasmDecoder.d.ts +9 -0
- package/es/flow/actions/linkageRules.d.ts +25 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +6 -30
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
- package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +32 -0
- package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
- package/es/flow/components/field-value-variable/index.d.ts +11 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +7 -0
- package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/internal/utils/operatorSchemaHelper.d.ts +2 -2
- package/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
- package/es/flow/models/base/ActionModelCore.d.ts +4 -2
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/base/CollectionBlockModel.d.ts +3 -0
- package/es/flow/models/blocks/details/DetailsBlockModel.d.ts +3 -0
- package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
- package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +7 -0
- package/es/flow/models/blocks/form/FormGridModel.d.ts +6 -0
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +1 -0
- package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +12 -0
- package/es/flow/models/blocks/table/utils.d.ts +1 -0
- package/es/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.d.ts +2 -1
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +0 -17
- package/es/flow/models/fields/AssociationFieldModel/recordSelectShared.d.ts +41 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/index.d.ts +1 -0
- package/es/index.mjs +148 -145
- package/es/ui-operation/index.d.ts +15 -0
- package/es/ui-operation/ui-operation-codec.d.ts +10 -0
- package/lib/index.js +170 -167
- package/lib/locale/languageCodes.js +1 -0
- package/package.json +10 -8
- package/src/APIClient.ts +92 -0
- package/src/Application.tsx +3 -1
- package/src/__tests__/APIClient.test.tsx +58 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +7 -2
- package/src/__tests__/settings-center.test.tsx +270 -6
- package/src/acl/ACLProvider.tsx +2 -0
- package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
- package/src/collection-manager/__tests__/field-configure.test.ts +52 -0
- package/src/collection-manager/field-configure.ts +2 -2
- package/src/components/form/ScanInput/CodeScanner.tsx +64 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +107 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +294 -24
- package/src/components/form/ScanInput/__tests__/zxingWasmDecoder.test.ts +78 -0
- package/src/components/form/ScanInput/useCodeScanner.ts +281 -23
- package/src/components/form/ScanInput/zxingWasmDecoder.ts +64 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +4 -2
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +17 -0
- package/src/components/form/table/Table.tsx +81 -6
- package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +126 -8
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
- package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -0
- package/src/flow/actions/linkageRules.tsx +59 -16
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +78 -59
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +228 -0
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +42 -23
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FieldAssignValueInput.tsx +42 -621
- package/src/flow/components/FlowRoute.tsx +71 -29
- package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
- package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +335 -0
- package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +389 -0
- package/src/flow/components/field-value-variable/dateValue.ts +223 -0
- package/src/flow/components/field-value-variable/index.ts +12 -0
- package/src/flow/components/filter/VariableFilterItem.tsx +23 -6
- package/src/flow/components/filter/__tests__/VariableFilterItem.rightMetaTree.test.tsx +158 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +125 -0
- package/src/flow/components/placeholders/BlockPlaceholder.tsx +70 -23
- package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +57 -7
- package/src/flow/index.ts +2 -0
- package/src/flow/internal/components/Markdown/DisplayMarkdown.tsx +14 -9
- package/src/flow/internal/components/Markdown/__tests__/DisplayMarkdown.test.tsx +46 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/internal/utils/operatorSchemaHelper.ts +3 -2
- package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
- package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
- package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
- package/src/flow/models/base/ActionModelCore.tsx +11 -2
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +38 -3
- package/src/flow/models/base/GridModel.tsx +0 -1
- package/src/flow/models/base/PageModel/PageTabModel.tsx +67 -17
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +509 -12
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
- package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +22 -1
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +36 -50
- package/src/flow/models/blocks/assign-form/__tests__/assignFieldValuesFlow.editor.test.tsx +140 -0
- package/src/flow/models/blocks/details/DetailsBlockModel.tsx +8 -3
- package/src/flow/models/blocks/details/__tests__/DetailsBlockModel.initialPaginationChangeRefresh.test.ts +32 -1
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +27 -3
- package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +129 -14
- package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.toggleFormFieldsCollapse.test.ts +29 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +359 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +32 -4
- package/src/flow/models/blocks/filter-form/fields/FieldComponentProps.tsx +1 -1
- package/src/flow/models/blocks/filter-form/fields/__tests__/FieldComponentProps.options.test.tsx +61 -0
- package/src/flow/models/blocks/filter-manager/FilterManager.ts +5 -0
- package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +40 -0
- package/src/flow/models/blocks/form/FormBlockModel.tsx +73 -5
- package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +153 -2
- package/src/flow/models/blocks/form/__tests__/popupLinkage.test.tsx +175 -0
- package/src/flow/models/blocks/form/__tests__/runJsFormSubmit.test.ts +131 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +435 -0
- package/src/flow/models/blocks/form/value-runtime/rules.ts +67 -14
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +43 -19
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +28 -2
- package/src/flow/models/blocks/table/TableColumnModel.tsx +48 -9
- package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.filterReset.test.ts +78 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
- package/src/flow/models/blocks/table/utils.ts +7 -0
- package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +8 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
- package/src/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.tsx +29 -3
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +9 -136
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +191 -3
- package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -0
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/DisplayAssociationField/DisplaySubTableFieldModel.tsx +42 -7
- package/src/flow/models/fields/DisplayAssociationField/__tests__/DisplaySubTableFieldModel.test.tsx +351 -0
- package/src/flow/models/fields/DisplayHtmlFieldModel.tsx +2 -1
- package/src/flow/models/fields/DisplayNumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/RichTextFieldModel/__tests__/RichTextFieldModel.test.tsx +74 -0
- package/src/flow/models/fields/RichTextFieldModel/index.tsx +27 -6
- package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
- package/src/flow/models/fields/__tests__/NumberFieldModel.test.tsx +47 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +6 -0
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +27 -2
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +139 -13
- package/src/flow/models/topbar/TopbarActionModel.tsx +5 -5
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/index.ts +1 -0
- package/src/layout-manager/LayoutContentRoute.tsx +2 -1
- package/src/layout-manager/LayoutRoute.tsx +2 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +87 -1
- package/src/locale/languageCodes.ts +1 -0
- package/src/settings-center/AdminSettingsLayout.tsx +23 -2
- package/src/ui-operation/index.ts +33 -0
- package/src/ui-operation/ui-operation-codec.ts +54 -0
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FlowEngine, type FlowModelContext, MultiRecordResource, VIEW_ACTIVATED_EVENT } from '@nocobase/flow-engine';
|
|
11
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
12
|
+
import { BlockGridModel } from '../BlockGridModel';
|
|
13
|
+
import { CollectionBlockModel } from '../CollectionBlockModel';
|
|
14
|
+
|
|
15
|
+
class TestBlockGridModel extends BlockGridModel {
|
|
16
|
+
mountForTest() {
|
|
17
|
+
super.onMount();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
unmountForTest() {
|
|
21
|
+
super.onUnmount();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class TestMultiRecordResource extends MultiRecordResource<Record<string, unknown>> {
|
|
26
|
+
refreshCalls = 0;
|
|
27
|
+
|
|
28
|
+
override async refresh(): Promise<void> {
|
|
29
|
+
this.refreshCalls += 1;
|
|
30
|
+
this.emit('refresh');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class TestCollectionBlockModel extends CollectionBlockModel {
|
|
35
|
+
createResource(ctx: FlowModelContext) {
|
|
36
|
+
return ctx.createResource(TestMultiRecordResource);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
mountForTest() {
|
|
40
|
+
super.onMount();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('BlockGridModel - select scene activation', () => {
|
|
45
|
+
let engine: FlowEngine;
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
engine = new FlowEngine();
|
|
49
|
+
engine.context.defineProperty('location', { value: { search: '' } });
|
|
50
|
+
engine.registerModels({ TestBlockGridModel, TestCollectionBlockModel });
|
|
51
|
+
|
|
52
|
+
engine.dataSourceManager.getDataSource('main').addCollection({
|
|
53
|
+
name: 'roles',
|
|
54
|
+
filterTargetKey: 'id',
|
|
55
|
+
fields: [
|
|
56
|
+
{ name: 'id', type: 'integer', interface: 'number' },
|
|
57
|
+
{ name: 'name', type: 'string', interface: 'input' },
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
function createModels(scene = 'select') {
|
|
63
|
+
engine.context.defineProperty('view', { value: { inputArgs: { scene } } });
|
|
64
|
+
const grid = engine.createModel<TestBlockGridModel>({ use: 'TestBlockGridModel' });
|
|
65
|
+
const block = grid.addSubModel<TestCollectionBlockModel>('items', {
|
|
66
|
+
use: 'TestCollectionBlockModel',
|
|
67
|
+
stepParams: {
|
|
68
|
+
resourceSettings: {
|
|
69
|
+
init: {
|
|
70
|
+
dataSourceKey: 'main',
|
|
71
|
+
collectionName: 'roles',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const resource = block.context.resource as TestMultiRecordResource;
|
|
77
|
+
|
|
78
|
+
block.mountForTest();
|
|
79
|
+
grid.mountForTest();
|
|
80
|
+
return { grid, resource };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
it('refreshes dirty collection blocks once when a nested view closes', async () => {
|
|
84
|
+
const { grid, resource } = createModels();
|
|
85
|
+
|
|
86
|
+
engine.markDataSourceDirty('main', 'roles');
|
|
87
|
+
engine.emitter.emit(VIEW_ACTIVATED_EVENT);
|
|
88
|
+
await Promise.resolve();
|
|
89
|
+
|
|
90
|
+
expect(resource.refreshCalls).toBe(1);
|
|
91
|
+
|
|
92
|
+
engine.emitter.emit(VIEW_ACTIVATED_EVENT);
|
|
93
|
+
await Promise.resolve();
|
|
94
|
+
|
|
95
|
+
expect(resource.refreshCalls).toBe(1);
|
|
96
|
+
grid.unmountForTest();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('does not refresh without dirty data or after the grid unmounts', async () => {
|
|
100
|
+
const { grid, resource } = createModels();
|
|
101
|
+
|
|
102
|
+
engine.emitter.emit(VIEW_ACTIVATED_EVENT);
|
|
103
|
+
await Promise.resolve();
|
|
104
|
+
expect(resource.refreshCalls).toBe(0);
|
|
105
|
+
|
|
106
|
+
grid.unmountForTest();
|
|
107
|
+
engine.markDataSourceDirty('main', 'roles');
|
|
108
|
+
engine.emitter.emit(VIEW_ACTIVATED_EVENT);
|
|
109
|
+
await Promise.resolve();
|
|
110
|
+
|
|
111
|
+
expect(resource.refreshCalls).toBe(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('does not handle activation outside the select scene', async () => {
|
|
115
|
+
const { grid, resource } = createModels('list');
|
|
116
|
+
|
|
117
|
+
engine.markDataSourceDirty('main', 'roles');
|
|
118
|
+
engine.emitter.emit(VIEW_ACTIVATED_EVENT);
|
|
119
|
+
await Promise.resolve();
|
|
120
|
+
|
|
121
|
+
expect(resource.refreshCalls).toBe(0);
|
|
122
|
+
grid.unmountForTest();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FlowEngine, FlowModelContext, MultiRecordResource } from '@nocobase/flow-engine';
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import { CollectionBlockModel } from '../CollectionBlockModel';
|
|
13
|
+
|
|
14
|
+
class AddAppendsCollectionBlockModel extends CollectionBlockModel {
|
|
15
|
+
createResource(ctx: FlowModelContext) {
|
|
16
|
+
return ctx.createResource(MultiRecordResource);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('CollectionBlockModel addAppends', () => {
|
|
21
|
+
it('does not throw for a nested field path when the collection is unavailable', () => {
|
|
22
|
+
const engine = new FlowEngine();
|
|
23
|
+
engine.registerModels({ AddAppendsCollectionBlockModel });
|
|
24
|
+
|
|
25
|
+
const model = engine.createModel<AddAppendsCollectionBlockModel>({
|
|
26
|
+
uid: 'missing-collection-add-appends-block',
|
|
27
|
+
use: 'AddAppendsCollectionBlockModel',
|
|
28
|
+
stepParams: {
|
|
29
|
+
resourceSettings: {
|
|
30
|
+
init: {
|
|
31
|
+
dataSourceKey: 'main',
|
|
32
|
+
collectionName: 'missing_collection',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(model.collection).toBeUndefined();
|
|
39
|
+
expect(() => model.addAppends('createdBy.departments')).not.toThrow();
|
|
40
|
+
});
|
|
41
|
+
});
|
package/src/flow/models/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts
CHANGED
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { afterEach,
|
|
11
|
-
import { FlowEngine, MultiRecordResource } from '@nocobase/flow-engine';
|
|
10
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
11
|
+
import { FlowEngine, FlowRuntimeContext, MultiRecordResource, ResourceError } from '@nocobase/flow-engine';
|
|
12
12
|
import '../../../index';
|
|
13
|
+
import { BlockResourceErrorPlaceholder } from '../../../components/placeholders/BlockPlaceholder';
|
|
13
14
|
import { CollectionBlockModel } from '../CollectionBlockModel';
|
|
14
15
|
import { dataLoadingMode } from '../../../actions/dataLoadingMode';
|
|
15
16
|
|
|
@@ -19,13 +20,16 @@ class InitialBeforeRenderTestBlockModel extends CollectionBlockModel {
|
|
|
19
20
|
}
|
|
20
21
|
}
|
|
21
22
|
|
|
22
|
-
function setupModel() {
|
|
23
|
+
function setupModel(dataSourceKey = 'main') {
|
|
23
24
|
const engine = new FlowEngine();
|
|
24
25
|
engine.context.defineProperty('location', { value: { search: '' } as any });
|
|
25
26
|
engine.registerActions({ dataLoadingMode });
|
|
26
27
|
engine.registerModels({ InitialBeforeRenderTestBlockModel });
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
if (!engine.dataSourceManager.getDataSource(dataSourceKey)) {
|
|
30
|
+
engine.dataSourceManager.addDataSource({ key: dataSourceKey });
|
|
31
|
+
}
|
|
32
|
+
const ds = engine.dataSourceManager.getDataSource(dataSourceKey);
|
|
29
33
|
ds.addCollection({
|
|
30
34
|
name: 'users',
|
|
31
35
|
filterTargetKey: 'id',
|
|
@@ -41,7 +45,7 @@ function setupModel() {
|
|
|
41
45
|
stepParams: {
|
|
42
46
|
resourceSettings: {
|
|
43
47
|
init: {
|
|
44
|
-
dataSourceKey
|
|
48
|
+
dataSourceKey,
|
|
45
49
|
collectionName: 'users',
|
|
46
50
|
},
|
|
47
51
|
},
|
|
@@ -88,16 +92,21 @@ function setupModelWithManualMode() {
|
|
|
88
92
|
return { model, resource: model.resource as MultiRecordResource };
|
|
89
93
|
}
|
|
90
94
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
async function runRefreshStep(model: InitialBeforeRenderTestBlockModel) {
|
|
96
|
+
const handler = model.getFlow('refreshSettings')?.getStep('refresh')?.serialize().handler;
|
|
97
|
+
if (!handler) {
|
|
98
|
+
throw new Error('refreshSettings.refresh handler is not registered');
|
|
99
|
+
}
|
|
100
|
+
await handler(new FlowRuntimeContext(model, 'refreshSettings'), {});
|
|
101
|
+
}
|
|
95
102
|
|
|
103
|
+
describe('CollectionBlockModel initial beforeRender refresh', () => {
|
|
96
104
|
afterEach(() => {
|
|
97
105
|
vi.useRealTimers();
|
|
98
106
|
});
|
|
99
107
|
|
|
100
108
|
it('only performs one actual data request on first beforeRender', async () => {
|
|
109
|
+
vi.useFakeTimers();
|
|
101
110
|
const { model, resource } = setupModel();
|
|
102
111
|
const runActionSpy = vi.spyOn(resource, 'runAction').mockResolvedValue({
|
|
103
112
|
data: [{ id: 1, name: 'Alice' }],
|
|
@@ -112,7 +121,54 @@ describe('CollectionBlockModel initial beforeRender refresh', () => {
|
|
|
112
121
|
expect(runActionSpy).toHaveBeenCalledTimes(1);
|
|
113
122
|
});
|
|
114
123
|
|
|
124
|
+
it('keeps resource request failures in the block error state', async () => {
|
|
125
|
+
const { model, resource } = setupModel('external');
|
|
126
|
+
const resourceError = new ResourceError({
|
|
127
|
+
response: {
|
|
128
|
+
data: {
|
|
129
|
+
errors: [{ code: 'DATA_SOURCE_UNAVAILABLE', message: 'Data source unavailable' }],
|
|
130
|
+
},
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
vi.spyOn(resource, 'refresh').mockImplementation(async () => {
|
|
134
|
+
resource.setError(resourceError);
|
|
135
|
+
throw resourceError;
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
await runRefreshStep(model);
|
|
139
|
+
|
|
140
|
+
expect(resource.getError()).toBe(resourceError);
|
|
141
|
+
expect(CollectionBlockModel.prototype.render.call(model)).toMatchObject({
|
|
142
|
+
type: BlockResourceErrorPlaceholder,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it('continues to throw resource request failures from the main data source', async () => {
|
|
147
|
+
const { model, resource } = setupModel();
|
|
148
|
+
const resourceError = new ResourceError({
|
|
149
|
+
response: {
|
|
150
|
+
data: {
|
|
151
|
+
errors: [{ code: 'UNKNOWN_ERROR', message: 'Main data source failed' }],
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
});
|
|
155
|
+
vi.spyOn(resource, 'refresh').mockImplementation(async () => {
|
|
156
|
+
resource.setError(resourceError);
|
|
157
|
+
throw resourceError;
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
await expect(runRefreshStep(model)).rejects.toBe(resourceError);
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it('continues to throw non-resource errors from beforeRender', async () => {
|
|
164
|
+
const { model, resource } = setupModel('external');
|
|
165
|
+
vi.spyOn(resource, 'refresh').mockRejectedValue(new Error('beforeRender bug'));
|
|
166
|
+
|
|
167
|
+
await expect(runRefreshStep(model)).rejects.toThrow('beforeRender bug');
|
|
168
|
+
});
|
|
169
|
+
|
|
115
170
|
it('respects saved manual mode and skips initial request when filters are empty', async () => {
|
|
171
|
+
vi.useFakeTimers();
|
|
116
172
|
const { model, resource } = setupModelWithManualMode();
|
|
117
173
|
const runActionSpy = vi.spyOn(resource, 'runAction').mockResolvedValue({
|
|
118
174
|
data: [{ id: 1, name: 'Alice' }],
|
|
@@ -128,4 +184,64 @@ describe('CollectionBlockModel initial beforeRender refresh', () => {
|
|
|
128
184
|
expect(resource.getData()).toEqual([]);
|
|
129
185
|
expect(resource.getMeta('count')).toBe(0);
|
|
130
186
|
});
|
|
187
|
+
|
|
188
|
+
it('skips forced active refresh in manual mode when filters are empty', async () => {
|
|
189
|
+
const { model, resource } = setupModelWithManualMode();
|
|
190
|
+
const refreshSpy = vi.spyOn(resource, 'refresh');
|
|
191
|
+
|
|
192
|
+
resource.setData([{ id: 2, name: 'Stale' }]);
|
|
193
|
+
resource.setMeta({ count: 1, hasNext: true });
|
|
194
|
+
resource.setPage(2);
|
|
195
|
+
resource.loading = true;
|
|
196
|
+
|
|
197
|
+
model.onActive(true);
|
|
198
|
+
await Promise.resolve();
|
|
199
|
+
await Promise.resolve();
|
|
200
|
+
|
|
201
|
+
expect(refreshSpy).not.toHaveBeenCalled();
|
|
202
|
+
expect(resource.getData()).toEqual([]);
|
|
203
|
+
expect(resource.getMeta('count')).toBe(0);
|
|
204
|
+
expect(resource.getMeta('hasNext')).toBe(false);
|
|
205
|
+
expect(resource.getMeta('page')).toBe(1);
|
|
206
|
+
expect(resource.getRequestParameter('page')).toBe(1);
|
|
207
|
+
expect(resource.loading).toBe(false);
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it('keeps forced active refresh in manual mode when filters are active', async () => {
|
|
211
|
+
const { model, resource } = setupModelWithManualMode();
|
|
212
|
+
const refreshSpy = vi.spyOn(resource, 'refresh').mockResolvedValue();
|
|
213
|
+
|
|
214
|
+
resource.setData([{ id: 2, name: 'Stale' }]);
|
|
215
|
+
resource.setMeta({ count: 1, hasNext: true, page: 2 });
|
|
216
|
+
model.setFilterActive('filter-form-item', true);
|
|
217
|
+
|
|
218
|
+
model.onActive(true);
|
|
219
|
+
await Promise.resolve();
|
|
220
|
+
|
|
221
|
+
expect(refreshSpy).toHaveBeenCalledTimes(1);
|
|
222
|
+
expect(resource.getData()).toEqual([{ id: 2, name: 'Stale' }]);
|
|
223
|
+
expect(resource.getMeta('count')).toBe(1);
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it('skips forced active refresh in manual mode when only resource filters are active', async () => {
|
|
227
|
+
const { model, resource } = setupModelWithManualMode();
|
|
228
|
+
const refreshSpy = vi.spyOn(resource, 'refresh');
|
|
229
|
+
|
|
230
|
+
resource.setData([{ id: 2, name: 'Stale' }]);
|
|
231
|
+
resource.setMeta({ count: 1, hasNext: true });
|
|
232
|
+
resource.setPage(2);
|
|
233
|
+
resource.loading = true;
|
|
234
|
+
resource.addFilterGroup('data-scope', { status: { $eq: 'active' } });
|
|
235
|
+
|
|
236
|
+
model.onActive(true);
|
|
237
|
+
await Promise.resolve();
|
|
238
|
+
|
|
239
|
+
expect(refreshSpy).not.toHaveBeenCalled();
|
|
240
|
+
expect(resource.getData()).toEqual([]);
|
|
241
|
+
expect(resource.getMeta('count')).toBe(0);
|
|
242
|
+
expect(resource.getMeta('hasNext')).toBe(false);
|
|
243
|
+
expect(resource.getMeta('page')).toBe(1);
|
|
244
|
+
expect(resource.getRequestParameter('page')).toBe(1);
|
|
245
|
+
expect(resource.loading).toBe(false);
|
|
246
|
+
});
|
|
131
247
|
});
|
|
@@ -115,7 +115,28 @@ export class AssignFormGridModel extends FormGridModel {
|
|
|
115
115
|
(existing as any).assignValue = value;
|
|
116
116
|
return;
|
|
117
117
|
}
|
|
118
|
-
|
|
118
|
+
if (!collection) {
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
const field = (collection.getFields?.() || []).find((f: any) => f.name === fieldName);
|
|
122
|
+
|
|
123
|
+
if (!field) {
|
|
124
|
+
const created = this.addSubModel('items', {
|
|
125
|
+
use: 'AssignFormItemModel',
|
|
126
|
+
stepParams: {
|
|
127
|
+
fieldSettings: {
|
|
128
|
+
init: {
|
|
129
|
+
dataSourceKey: collection?.dataSourceKey,
|
|
130
|
+
collectionName: collection?.name,
|
|
131
|
+
fieldPath: fieldName,
|
|
132
|
+
},
|
|
133
|
+
assignValue: { value },
|
|
134
|
+
},
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
created['assignValue'] = value;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
119
140
|
|
|
120
141
|
const binding = EditableItemModel.getDefaultBindingByField(this.context, field);
|
|
121
142
|
if (!binding) {
|
|
@@ -11,15 +11,12 @@ import React from 'react';
|
|
|
11
11
|
import { Input } from 'antd';
|
|
12
12
|
import { define, observable } from '@formily/reactive';
|
|
13
13
|
import {
|
|
14
|
+
EditableItemModel,
|
|
15
|
+
FieldDeletePlaceholder,
|
|
14
16
|
FlowModelRenderer,
|
|
15
17
|
FormItem,
|
|
16
|
-
VariableInput,
|
|
17
|
-
tExpr,
|
|
18
|
-
isVariableExpression,
|
|
19
|
-
parseValueToPath,
|
|
20
|
-
isRunJSValue,
|
|
21
|
-
EditableItemModel,
|
|
22
18
|
jioToJoiSchema,
|
|
19
|
+
tExpr,
|
|
23
20
|
} from '@nocobase/flow-engine';
|
|
24
21
|
// 无需类型导入(避免未使用的类型)
|
|
25
22
|
import { FormItemModel } from '../form/FormItemModel';
|
|
@@ -27,6 +24,13 @@ import { EditFormModel } from '../form/EditFormModel';
|
|
|
27
24
|
import { customAlphabet as Alphabet } from 'nanoid';
|
|
28
25
|
import { ensureOptionsFromUiSchemaEnumIfAbsent } from '../../../internal/utils/enumOptionsUtils';
|
|
29
26
|
import { RunJSValueEditor } from '../../../components/RunJSValueEditor';
|
|
27
|
+
import {
|
|
28
|
+
DEFAULT_DATE_VARIABLE_COMPONENT_PROPS,
|
|
29
|
+
FieldValueVariableInput,
|
|
30
|
+
getFieldInterface,
|
|
31
|
+
isDateLikeField,
|
|
32
|
+
resolveDateVariableComponentProps,
|
|
33
|
+
} from '../../../components/field-value-variable';
|
|
30
34
|
|
|
31
35
|
type AssignFormTempOriginField = {
|
|
32
36
|
uid?: string;
|
|
@@ -163,11 +167,15 @@ export class AssignFormItemModel extends FormItemModel {
|
|
|
163
167
|
|
|
164
168
|
getAssignedEntry(): [string, any] | null {
|
|
165
169
|
const name = this.fieldPath;
|
|
166
|
-
if (!name) return null;
|
|
170
|
+
if (!name || !this.collectionField) return null;
|
|
167
171
|
return [name, this.assignValue];
|
|
168
172
|
}
|
|
169
173
|
|
|
170
174
|
render() {
|
|
175
|
+
if (!this.collectionField) {
|
|
176
|
+
return <FieldDeletePlaceholder />;
|
|
177
|
+
}
|
|
178
|
+
|
|
171
179
|
// 与 FormItemModel.render 结构保持一致,仅替换内部渲染为 VariableInput + 常量编辑器
|
|
172
180
|
const ctx: any = this.context;
|
|
173
181
|
const collection = ctx.collection;
|
|
@@ -285,7 +293,9 @@ export class AssignFormItemModel extends FormItemModel {
|
|
|
285
293
|
) : null;
|
|
286
294
|
};
|
|
287
295
|
|
|
288
|
-
const NullComponent: React.FC = () =>
|
|
296
|
+
const NullComponent: React.FC = () => (
|
|
297
|
+
<Input placeholder={`<${this.context.t?.('Null') ?? 'Null'}>`} readOnly style={{ width: '100%' }} />
|
|
298
|
+
);
|
|
289
299
|
|
|
290
300
|
const RunJSComponent: React.FC<any> = (inputProps: any) => {
|
|
291
301
|
return (
|
|
@@ -298,46 +308,18 @@ export class AssignFormItemModel extends FormItemModel {
|
|
|
298
308
|
);
|
|
299
309
|
};
|
|
300
310
|
|
|
301
|
-
const
|
|
302
|
-
renderInputComponent: (meta: any) => {
|
|
303
|
-
const firstPath = meta?.paths?.[0];
|
|
304
|
-
if (firstPath === 'constant') return ConstantValueEditor as any;
|
|
305
|
-
if (firstPath === 'null') return NullComponent as any;
|
|
306
|
-
if (firstPath === 'runjs') return RunJSComponent as any;
|
|
307
|
-
return undefined;
|
|
308
|
-
},
|
|
309
|
-
resolveValueFromPath: (item: any) => {
|
|
310
|
-
const firstPath = item?.paths?.[0];
|
|
311
|
-
if (firstPath === 'constant') return '';
|
|
312
|
-
if (firstPath === 'null') return null;
|
|
313
|
-
if (firstPath === 'runjs') return { code: '', version: 'v2' };
|
|
314
|
-
return undefined;
|
|
315
|
-
},
|
|
316
|
-
resolvePathFromValue: (currentValue: any) => {
|
|
317
|
-
if (currentValue === null) return ['null'];
|
|
318
|
-
if (isRunJSValue(currentValue)) return ['runjs'];
|
|
319
|
-
return isVariableExpression(currentValue) ? parseValueToPath(currentValue) : ['constant'];
|
|
320
|
-
},
|
|
321
|
-
} as any;
|
|
322
|
-
|
|
323
|
-
// 合并变量树:在最前面追加“常量/空值”两个选项
|
|
324
|
-
const mergedMetaTree = async () => {
|
|
311
|
+
const baseMetaTree = async () => {
|
|
325
312
|
const getTree = (this.context as any)?.getPropertyMetaTree;
|
|
326
|
-
|
|
327
|
-
return [
|
|
328
|
-
{
|
|
329
|
-
title: tExpr('Constant'),
|
|
330
|
-
name: 'constant',
|
|
331
|
-
type: 'string',
|
|
332
|
-
paths: ['constant'],
|
|
333
|
-
render: ConstantValueEditor,
|
|
334
|
-
},
|
|
335
|
-
{ title: tExpr('Null'), name: 'null', type: 'object', paths: ['null'], render: NullComponent },
|
|
336
|
-
{ title: tExpr('RunJS'), name: 'runjs', type: 'object', paths: ['runjs'], render: RunJSComponent },
|
|
337
|
-
...base,
|
|
338
|
-
];
|
|
313
|
+
return typeof getTree === 'function' ? await getTree() : [];
|
|
339
314
|
};
|
|
340
315
|
|
|
316
|
+
const targetCollectionField = collection?.getField?.(this.fieldPath);
|
|
317
|
+
const targetFieldInterface = getFieldInterface(targetCollectionField);
|
|
318
|
+
const dateLike = isDateLikeField(targetCollectionField, this.fieldPath?.split('.').slice(-1)[0]);
|
|
319
|
+
const dateComponentProps = dateLike
|
|
320
|
+
? resolveDateVariableComponentProps(targetCollectionField, targetFieldInterface)
|
|
321
|
+
: DEFAULT_DATE_VARIABLE_COMPONENT_PROPS;
|
|
322
|
+
|
|
341
323
|
// 计算 label:优先使用配置中的 label,其次集合字段标题,最后回退字段路径
|
|
342
324
|
let labelText = this.props?.label ?? this.fieldPath ?? '';
|
|
343
325
|
const cf = collection?.getField?.(this.fieldPath);
|
|
@@ -357,14 +339,18 @@ export class AssignFormItemModel extends FormItemModel {
|
|
|
357
339
|
const formValue = formBindingProps?.__assign_value__;
|
|
358
340
|
const mergedValue = typeof formValue === 'undefined' ? this.assignValue : formValue;
|
|
359
341
|
return (
|
|
360
|
-
<
|
|
342
|
+
<FieldValueVariableInput
|
|
361
343
|
value={mergedValue}
|
|
362
|
-
onChange={(v
|
|
344
|
+
onChange={(v) => {
|
|
363
345
|
this.assignValue = v;
|
|
364
346
|
formBindingProps?.__assign_trigger__?.(v);
|
|
365
347
|
}}
|
|
366
|
-
|
|
367
|
-
|
|
348
|
+
baseMetaTree={baseMetaTree}
|
|
349
|
+
constantComponent={ConstantValueEditor}
|
|
350
|
+
nullComponent={NullComponent}
|
|
351
|
+
runJSComponent={RunJSComponent}
|
|
352
|
+
isDateLikeField={dateLike}
|
|
353
|
+
dateComponentProps={dateComponentProps}
|
|
368
354
|
clearValue={''}
|
|
369
355
|
/>
|
|
370
356
|
);
|
|
@@ -417,7 +403,7 @@ AssignFormItemModel.registerFlow({
|
|
|
417
403
|
},
|
|
418
404
|
defaultParams: (ctx) => {
|
|
419
405
|
return {
|
|
420
|
-
label: (ctx.model as any).collectionField.
|
|
406
|
+
label: (ctx.model as any).collectionField?.title || (ctx.model as any).fieldPath,
|
|
421
407
|
};
|
|
422
408
|
},
|
|
423
409
|
handler(ctx, params) {
|
|
@@ -47,6 +47,146 @@ class MockFlowModelRepository implements IFlowModelRepository {
|
|
|
47
47
|
}
|
|
48
48
|
|
|
49
49
|
describe('assignFieldValuesFlow (editor)', () => {
|
|
50
|
+
it('shows the deleted-field placeholder for a stale assigned value', async () => {
|
|
51
|
+
const engine = new FlowEngine();
|
|
52
|
+
engine.setModelRepository(new MockFlowModelRepository());
|
|
53
|
+
engine.registerModels({
|
|
54
|
+
AssignFormModel,
|
|
55
|
+
AssignFormGridModel,
|
|
56
|
+
AssignFormItemModel,
|
|
57
|
+
InputFieldModel,
|
|
58
|
+
VariableFieldFormModel,
|
|
59
|
+
});
|
|
60
|
+
engine.context.defineProperty('location', { value: { search: '' } });
|
|
61
|
+
engine.context.defineProperty('themeToken', { value: { marginLG: 24 } });
|
|
62
|
+
engine.context.defineProperty('flowSettingsEnabled', { value: true });
|
|
63
|
+
|
|
64
|
+
const main = engine.context.dataSourceManager.getDataSource('main');
|
|
65
|
+
main.addCollection({
|
|
66
|
+
name: 'users',
|
|
67
|
+
fields: [{ name: 'nickname', type: 'string', interface: 'input' }],
|
|
68
|
+
});
|
|
69
|
+
const users = engine.context.dataSourceManager.getCollection('main', 'users');
|
|
70
|
+
|
|
71
|
+
const action = engine.createModel({
|
|
72
|
+
use: 'FlowModel',
|
|
73
|
+
uid: 'act-assign-deleted-field',
|
|
74
|
+
});
|
|
75
|
+
action.setStepParams('assignSettings', 'assignFieldValues', {
|
|
76
|
+
assignedValues: { deletedField: 'stale value' },
|
|
77
|
+
});
|
|
78
|
+
action.context.defineProperty('blockModel', { value: { collection: users } });
|
|
79
|
+
|
|
80
|
+
const step = createAssignFieldValuesStep({ settingsFlowKey: 'assignSettings' });
|
|
81
|
+
const Editor = step.uiSchema().editor?.['x-component'] as React.ComponentType;
|
|
82
|
+
const flowSettingsCtx = new FlowRuntimeContext(action, 'assignSettings', 'settings');
|
|
83
|
+
|
|
84
|
+
render(
|
|
85
|
+
<FlowEngineProvider engine={engine}>
|
|
86
|
+
<ConfigProvider>
|
|
87
|
+
<App>
|
|
88
|
+
<FlowSettingsContextProvider value={flowSettingsCtx}>
|
|
89
|
+
<Editor />
|
|
90
|
+
</FlowSettingsContextProvider>
|
|
91
|
+
</App>
|
|
92
|
+
</ConfigProvider>
|
|
93
|
+
</FlowEngineProvider>,
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
await waitFor(() => {
|
|
97
|
+
expect(screen.getByRole('button', { name: /Fields/ })).toBeInTheDocument();
|
|
98
|
+
expect(screen.getByText(/deletedField.*may have been deleted/)).toBeInTheDocument();
|
|
99
|
+
const form = engine.findModelByParentId<AssignFormModel>(action.uid, 'assignForm');
|
|
100
|
+
expect(form).toBeDefined();
|
|
101
|
+
expect(form?.subModels.grid.subModels.items || []).toHaveLength(1);
|
|
102
|
+
expect(form?.getAssignedValues()).toEqual({});
|
|
103
|
+
});
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it('shows the standard deleted-field placeholder for a persisted assigned field model', async () => {
|
|
107
|
+
const engine = new FlowEngine();
|
|
108
|
+
engine.setModelRepository(new MockFlowModelRepository());
|
|
109
|
+
engine.registerModels({
|
|
110
|
+
AssignFormModel,
|
|
111
|
+
AssignFormGridModel,
|
|
112
|
+
AssignFormItemModel,
|
|
113
|
+
InputFieldModel,
|
|
114
|
+
VariableFieldFormModel,
|
|
115
|
+
});
|
|
116
|
+
engine.context.defineProperty('location', { value: { search: '' } });
|
|
117
|
+
engine.context.defineProperty('themeToken', { value: { marginLG: 24 } });
|
|
118
|
+
engine.context.defineProperty('flowSettingsEnabled', { value: true });
|
|
119
|
+
|
|
120
|
+
const main = engine.context.dataSourceManager.getDataSource('main');
|
|
121
|
+
main.addCollection({
|
|
122
|
+
name: 'users',
|
|
123
|
+
fields: [{ name: 'nickname', type: 'string', interface: 'input' }],
|
|
124
|
+
});
|
|
125
|
+
const users = engine.context.dataSourceManager.getCollection('main', 'users');
|
|
126
|
+
|
|
127
|
+
const action = engine.createModel({
|
|
128
|
+
use: 'FlowModel',
|
|
129
|
+
uid: 'act-assign-persisted-deleted-field',
|
|
130
|
+
});
|
|
131
|
+
action.setStepParams('assignSettings', 'assignFieldValues', {
|
|
132
|
+
assignedValues: { deletedField: 'stale value' },
|
|
133
|
+
});
|
|
134
|
+
action.context.defineProperty('blockModel', { value: { collection: users } });
|
|
135
|
+
|
|
136
|
+
const form = engine.createModel<AssignFormModel>({
|
|
137
|
+
use: 'AssignFormModel',
|
|
138
|
+
uid: 'form-assign-persisted-deleted-field',
|
|
139
|
+
parentId: action.uid,
|
|
140
|
+
subKey: 'assignForm',
|
|
141
|
+
stepParams: {
|
|
142
|
+
resourceSettings: {
|
|
143
|
+
init: {
|
|
144
|
+
dataSourceKey: 'main',
|
|
145
|
+
collectionName: 'users',
|
|
146
|
+
},
|
|
147
|
+
},
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
action.setSubModel('assignForm', form);
|
|
151
|
+
form.subModels.grid.addSubModel('items', {
|
|
152
|
+
use: 'AssignFormItemModel',
|
|
153
|
+
uid: 'item-assign-persisted-deleted-field',
|
|
154
|
+
stepParams: {
|
|
155
|
+
fieldSettings: {
|
|
156
|
+
init: {
|
|
157
|
+
dataSourceKey: 'main',
|
|
158
|
+
collectionName: 'users',
|
|
159
|
+
fieldPath: 'deletedField',
|
|
160
|
+
},
|
|
161
|
+
assignValue: { value: 'stale value' },
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
form.subModels.grid.resetRows(true);
|
|
166
|
+
|
|
167
|
+
const step = createAssignFieldValuesStep({ settingsFlowKey: 'assignSettings' });
|
|
168
|
+
const Editor = step.uiSchema().editor?.['x-component'] as React.ComponentType;
|
|
169
|
+
const flowSettingsCtx = new FlowRuntimeContext(action, 'assignSettings', 'settings');
|
|
170
|
+
|
|
171
|
+
render(
|
|
172
|
+
<FlowEngineProvider engine={engine}>
|
|
173
|
+
<ConfigProvider>
|
|
174
|
+
<App>
|
|
175
|
+
<FlowSettingsContextProvider value={flowSettingsCtx}>
|
|
176
|
+
<Editor />
|
|
177
|
+
</FlowSettingsContextProvider>
|
|
178
|
+
</App>
|
|
179
|
+
</ConfigProvider>
|
|
180
|
+
</FlowEngineProvider>,
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
await waitFor(() => {
|
|
184
|
+
expect(screen.getByText(/deletedField.*may have been deleted/)).toBeInTheDocument();
|
|
185
|
+
expect(form.subModels.grid.subModels.items).toHaveLength(1);
|
|
186
|
+
expect(form.getAssignedValues()).toEqual({});
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
50
190
|
it('repairs AssignFormModel resource init and clears cached collection', async () => {
|
|
51
191
|
const engine = new FlowEngine();
|
|
52
192
|
engine.setModelRepository(new MockFlowModelRepository());
|