@nocobase/client-v2 2.1.0-alpha.45 → 2.1.0-alpha.47
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/authRedirect.d.ts +12 -0
- package/es/collection-manager/filter-operators/index.d.ts +1 -0
- package/es/components/form/TypedVariableInput.d.ts +75 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +5 -1
- package/es/components/form/filter/FilterValueInput.d.ts +5 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
- package/es/flow/components/ConditionBuilder.d.ts +1 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/components/FieldAssignValueInput.d.ts +1 -0
- package/es/flow/components/fieldAssignOptions.d.ts +10 -0
- package/es/flow/components/filter/LinkageFilterItem.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +1 -0
- package/es/flow/components/filter/metaTreeAssociationDepth.d.ts +13 -0
- package/es/flow/internal/utils/operatorSchemaHelper.d.ts +26 -3
- package/es/flow/models/base/AssociationFieldGroupModel.d.ts +2 -33
- package/es/flow/models/blocks/assign-form/assignFieldValuesFlow.d.ts +8 -0
- package/es/flow/models/blocks/filter-manager/utils.d.ts +23 -1
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +2 -0
- package/es/index.mjs +106 -106
- package/lib/index.js +106 -106
- package/package.json +7 -7
- package/src/__tests__/browserChecker.test.ts +103 -0
- package/src/__tests__/dataSourceRuntime.test.tsx +22 -1
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +5 -0
- package/src/authRedirect.ts +43 -0
- package/src/collection-field-interface/CollectionFieldInterface.ts +14 -7
- package/src/collection-manager/filter-operators/index.ts +29 -2
- package/src/components/README.md +48 -0
- package/src/components/README.zh-CN.md +48 -0
- package/src/components/form/TypedVariableInput.tsx +441 -0
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +152 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +10 -2
- package/src/components/form/filter/FilterValueInput.tsx +27 -2
- package/src/components/form/filter/__tests__/FilterValueInput.test.tsx +39 -0
- package/src/components/form/index.tsx +1 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +8 -1
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +100 -0
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +369 -2
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +23 -1
- package/src/flow/actions/dataScope.tsx +8 -1
- package/src/flow/actions/linkageRules.tsx +61 -5
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -3
- package/src/flow/actions/setTargetDataScope.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +15 -1
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +75 -1
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +11 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +2 -4
- package/src/flow/common/Markdown/Edit.tsx +32 -4
- package/src/flow/components/ConditionBuilder.tsx +12 -2
- package/src/flow/components/DefaultValue.tsx +14 -8
- package/src/flow/components/FieldAssignRulesEditor.tsx +37 -46
- package/src/flow/components/FieldAssignValueInput.tsx +14 -14
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +112 -1
- package/src/flow/components/__tests__/fieldAssignOptions.test.ts +71 -1
- package/src/flow/components/code-editor/__tests__/runjsCompletions.test.ts +272 -27
- package/src/flow/components/code-editor/runjsCompletions.ts +490 -9
- package/src/flow/components/fieldAssignOptions.ts +23 -6
- package/src/flow/components/filter/LinkageFilterItem.tsx +26 -12
- package/src/flow/components/filter/VariableFilterItem.tsx +32 -13
- package/src/flow/components/filter/__tests__/fieldsToOptions.test.ts +48 -0
- package/src/flow/components/filter/__tests__/metaTreeAssociationDepth.test.ts +60 -0
- package/src/flow/components/filter/fieldsToOptions.ts +9 -4
- package/src/flow/components/filter/metaTreeAssociationDepth.ts +69 -0
- package/src/flow/internal/utils/operatorSchemaHelper.ts +112 -5
- package/src/flow/models/actions/FilterActionModel.tsx +15 -3
- package/src/flow/models/actions/__tests__/AssignFormRefill.test.ts +90 -0
- package/src/flow/models/base/AssociationFieldGroupModel.tsx +20 -3
- package/src/flow/models/base/GridModel.tsx +1 -1
- package/src/flow/models/base/PageModel/ChildPageModel.tsx +5 -1
- package/src/flow/models/base/PageModel/__tests__/ChildPageModel.test.ts +12 -0
- package/src/flow/models/base/__tests__/AssociationFieldGroupModel.test.ts +86 -0
- package/src/flow/models/base/__tests__/GridModel.render.test.tsx +53 -0
- package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +12 -3
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +14 -4
- package/src/flow/models/blocks/assign-form/__tests__/assignFieldValuesFlow.editor.test.tsx +216 -0
- package/src/flow/models/blocks/assign-form/assignFieldValuesFlow.tsx +142 -40
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +14 -12
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +6 -1
- package/src/flow/models/blocks/filter-manager/__tests__/getDefaultOperator.test.ts +35 -1
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +148 -0
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/defaultOperator.test.tsx +85 -0
- package/src/flow/models/blocks/filter-manager/flow-actions/connectFields.tsx +48 -6
- package/src/flow/models/blocks/filter-manager/flow-actions/customizeFilterRender.tsx +14 -44
- package/src/flow/models/blocks/filter-manager/flow-actions/defaultOperator.tsx +3 -5
- package/src/flow/models/blocks/filter-manager/flow-actions/operatorComponentProps.tsx +2 -12
- package/src/flow/models/blocks/filter-manager/utils.ts +143 -4
- package/src/flow/models/blocks/form/FormAssociationItemModel.tsx +9 -0
- package/src/flow/models/blocks/form/FormItemModel.tsx +9 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/__tests__/FormItemModel.defineChildren.test.ts +27 -2
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +29 -0
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +5 -1
- package/src/flow/models/blocks/table/TableColumnModel.tsx +15 -5
- package/src/flow/models/blocks/table/TableCustomColumnModel.tsx +2 -2
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +18 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +6 -7
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +5 -3
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +70 -0
|
@@ -11,13 +11,20 @@ import { Collection, DisplayItemModel, FlowModel, FlowModelContext } from '@noco
|
|
|
11
11
|
|
|
12
12
|
export class AssociationFieldGroupModel extends FlowModel {
|
|
13
13
|
static itemModelName = 'DetailsItemModel';
|
|
14
|
+
static maxAssociationFieldDepth = 2;
|
|
14
15
|
static defineChildren(ctx: FlowModelContext) {
|
|
15
16
|
const itemModel = this.itemModelName;
|
|
16
|
-
const
|
|
17
|
+
const maxAssociationFieldDepth = this.maxAssociationFieldDepth ?? 2;
|
|
18
|
+
const displayAssociationFields = (targetCollection: Collection, fieldPath = '', associationDepth = 0) => {
|
|
19
|
+
if (associationDepth >= maxAssociationFieldDepth) {
|
|
20
|
+
return [];
|
|
21
|
+
}
|
|
22
|
+
|
|
17
23
|
return targetCollection
|
|
18
24
|
.getToOneAssociationFields()
|
|
19
25
|
.map((field) => {
|
|
20
26
|
const fPath = fieldPath ? `${fieldPath}.${field.name}` : field.name;
|
|
27
|
+
const nextAssociationDepth = associationDepth + 1;
|
|
21
28
|
if (!field.targetCollection) {
|
|
22
29
|
console.error(
|
|
23
30
|
`AssociationFieldGroupModel: target collection ${field.target} not found for field ${field.name}`,
|
|
@@ -37,6 +44,12 @@ export class AssociationFieldGroupModel extends FlowModel {
|
|
|
37
44
|
children: field.targetCollection
|
|
38
45
|
.getFields()
|
|
39
46
|
.map((f) => {
|
|
47
|
+
if (
|
|
48
|
+
nextAssociationDepth >= maxAssociationFieldDepth &&
|
|
49
|
+
(f.isAssociationField?.() || f.target || f.targetCollection)
|
|
50
|
+
) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
40
53
|
const fp = `${fPath}.${f.name}`;
|
|
41
54
|
const binding = DisplayItemModel.getDefaultBindingByField(ctx, f, {
|
|
42
55
|
fallbackToTargetTitleField: true,
|
|
@@ -81,9 +94,13 @@ export class AssociationFieldGroupModel extends FlowModel {
|
|
|
81
94
|
key: `${fPath}-children-associationField`,
|
|
82
95
|
label: 'Display association fields',
|
|
83
96
|
type: 'group',
|
|
84
|
-
children: (
|
|
97
|
+
children: (
|
|
98
|
+
displayAssociationFields(field.targetCollection, fPath, nextAssociationDepth) || []
|
|
99
|
+
).filter(Boolean),
|
|
85
100
|
},
|
|
86
|
-
]
|
|
101
|
+
].filter((item) => {
|
|
102
|
+
return item.key !== `${fPath}-children-associationField` || (item.children as any[])?.length > 0;
|
|
103
|
+
});
|
|
87
104
|
},
|
|
88
105
|
};
|
|
89
106
|
})
|
|
@@ -1006,7 +1006,7 @@ export class GridModel<T extends { subModels: { items: FlowModel[] } } = Default
|
|
|
1006
1006
|
<Space
|
|
1007
1007
|
ref={this.gridContainerRef}
|
|
1008
1008
|
direction={'vertical'}
|
|
1009
|
-
style={{ width: '100%', marginBottom: this.props.rowGap }}
|
|
1009
|
+
style={{ width: '100%', marginBottom: this.props.rowGap, display: 'flex' }}
|
|
1010
1010
|
size={this.props.rowGap}
|
|
1011
1011
|
>
|
|
1012
1012
|
<DndProvider
|
|
@@ -49,7 +49,11 @@ export class ChildPageModel extends PageModel {
|
|
|
49
49
|
throw new Error('Invalid drag event');
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
if (event.active.id === event.over.id) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await this.flowEngine.moveModel(event.active.id, event.over.id);
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
render() {
|
|
@@ -107,6 +107,18 @@ describe('ChildPageModel', () => {
|
|
|
107
107
|
expect(mockFlowEngine.moveModel).toHaveBeenCalledWith('active-model-id', 'over-model-id');
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
it('should ignore self-drop', async () => {
|
|
111
|
+
await childPageModel.handleDragEnd({
|
|
112
|
+
...mockDragEndEvent,
|
|
113
|
+
over: {
|
|
114
|
+
...(mockDragEndEvent.over as NonNullable<DragEndEvent['over']>),
|
|
115
|
+
id: 'active-model-id',
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(mockFlowEngine.moveModel).not.toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
|
|
110
122
|
it('should handle case when over is null', async () => {
|
|
111
123
|
const eventWithNullOver = {
|
|
112
124
|
...mockDragEndEvent,
|
|
@@ -0,0 +1,86 @@
|
|
|
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 { afterEach, describe, expect, it, vi } from 'vitest';
|
|
11
|
+
import { DisplayItemModel } from '@nocobase/flow-engine';
|
|
12
|
+
import { AssociationFieldGroupModel } from '../AssociationFieldGroupModel';
|
|
13
|
+
|
|
14
|
+
function createCollection(name: string, fields: any[]) {
|
|
15
|
+
return {
|
|
16
|
+
name,
|
|
17
|
+
dataSourceKey: 'main',
|
|
18
|
+
getFields: () => fields,
|
|
19
|
+
getToOneAssociationFields: () => fields.filter((field) => field?.type === 'belongsTo' || field?.type === 'hasOne'),
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe('AssociationFieldGroupModel', () => {
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
vi.restoreAllMocks();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('limits display association fields to relation / relation / field', () => {
|
|
29
|
+
vi.spyOn(DisplayItemModel, 'getDefaultBindingByField').mockReturnValue({ modelName: 'InputFieldModel' } as any);
|
|
30
|
+
|
|
31
|
+
const companies = createCollection('companies', [
|
|
32
|
+
{ name: 'title', title: 'Company title', interface: 'input', type: 'string' },
|
|
33
|
+
]);
|
|
34
|
+
const departments = createCollection('departments', [
|
|
35
|
+
{ name: 'name', title: 'Department name', interface: 'input', type: 'string' },
|
|
36
|
+
{
|
|
37
|
+
name: 'company',
|
|
38
|
+
title: 'Company',
|
|
39
|
+
interface: 'm2o',
|
|
40
|
+
type: 'belongsTo',
|
|
41
|
+
target: 'companies',
|
|
42
|
+
targetCollection: companies,
|
|
43
|
+
isAssociationField: () => true,
|
|
44
|
+
},
|
|
45
|
+
]);
|
|
46
|
+
const users = createCollection('users', [
|
|
47
|
+
{
|
|
48
|
+
name: 'department',
|
|
49
|
+
title: 'Department',
|
|
50
|
+
interface: 'm2o',
|
|
51
|
+
type: 'belongsTo',
|
|
52
|
+
target: 'departments',
|
|
53
|
+
targetCollection: departments,
|
|
54
|
+
isAssociationField: () => true,
|
|
55
|
+
},
|
|
56
|
+
]);
|
|
57
|
+
const posts = createCollection('posts', [
|
|
58
|
+
{
|
|
59
|
+
name: 'user',
|
|
60
|
+
title: 'User',
|
|
61
|
+
interface: 'm2o',
|
|
62
|
+
type: 'belongsTo',
|
|
63
|
+
target: 'users',
|
|
64
|
+
targetCollection: users,
|
|
65
|
+
isAssociationField: () => true,
|
|
66
|
+
},
|
|
67
|
+
]);
|
|
68
|
+
|
|
69
|
+
const ctx = {
|
|
70
|
+
collection: posts,
|
|
71
|
+
t: (key: string) => key,
|
|
72
|
+
} as any;
|
|
73
|
+
|
|
74
|
+
const firstLevel = AssociationFieldGroupModel.defineChildren(ctx) as any[];
|
|
75
|
+
const firstChildren = firstLevel[0].children();
|
|
76
|
+
const firstAssociationGroup = firstChildren.find((item) => item.key === 'user-children-associationField');
|
|
77
|
+
const secondLevel = firstAssociationGroup.children;
|
|
78
|
+
const secondChildren = secondLevel[0].children();
|
|
79
|
+
const secondFieldsGroup = secondChildren.find((item) => item.key === 'user.department-children-collectionField');
|
|
80
|
+
|
|
81
|
+
expect(secondChildren.map((item) => item.key)).toContain('user.department-children-collectionField');
|
|
82
|
+
expect(secondChildren.map((item) => item.key)).not.toContain('user.department-children-associationField');
|
|
83
|
+
expect(secondFieldsGroup.children.map((item) => item.key)).toContain('c-user.department.name');
|
|
84
|
+
expect(secondFieldsGroup.children.map((item) => item.key)).not.toContain('c-user.department.company');
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
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, FlowEngineProvider } from '@nocobase/flow-engine';
|
|
11
|
+
import { render } from '@testing-library/react';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
14
|
+
import { GridModel } from '../GridModel';
|
|
15
|
+
|
|
16
|
+
describe('GridModel.render', () => {
|
|
17
|
+
let engine: FlowEngine;
|
|
18
|
+
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
engine = new FlowEngine();
|
|
21
|
+
engine.registerModels({ GridModel });
|
|
22
|
+
await engine.flowSettings.disable();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders the Space wrapper as block-level flex to avoid inline baseline spacing', () => {
|
|
26
|
+
const item = engine.createModel({ use: 'FlowModel', uid: 'item-1' });
|
|
27
|
+
const model = engine.createModel<GridModel>({
|
|
28
|
+
use: 'GridModel',
|
|
29
|
+
uid: 'grid-render',
|
|
30
|
+
props: {
|
|
31
|
+
rows: {
|
|
32
|
+
row1: [['item-1']],
|
|
33
|
+
},
|
|
34
|
+
sizes: {
|
|
35
|
+
row1: [24],
|
|
36
|
+
},
|
|
37
|
+
rowGap: 16,
|
|
38
|
+
},
|
|
39
|
+
structure: {} as any,
|
|
40
|
+
});
|
|
41
|
+
(model as any).subModels = { items: [item] };
|
|
42
|
+
|
|
43
|
+
expect(Object.keys((model as any).getVisibleLayout().rows)).toEqual(['row1']);
|
|
44
|
+
|
|
45
|
+
const { container } = render(<FlowEngineProvider engine={engine}>{model.render()}</FlowEngineProvider>);
|
|
46
|
+
const space = container.querySelector('.ant-space') as HTMLElement;
|
|
47
|
+
|
|
48
|
+
expect(space).toBeTruthy();
|
|
49
|
+
expect(space.style.width).toBe('100%');
|
|
50
|
+
expect(space.style.marginBottom).toBe('16px');
|
|
51
|
+
expect(space.style.display).toBe('flex');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -12,11 +12,16 @@ import { AddSubModelButton, FlowSettingsButton, tExpr, EditableItemModel } from
|
|
|
12
12
|
import { SettingOutlined } from '@ant-design/icons';
|
|
13
13
|
import { FormGridModel } from '../form/FormGridModel';
|
|
14
14
|
|
|
15
|
+
const MAX_ASSIGN_FORM_ASSOCIATION_FIELD_DEPTH = 2;
|
|
16
|
+
|
|
15
17
|
// 使用范型准确标注 subModels.items 的类型
|
|
16
18
|
export class AssignFormGridModel extends FormGridModel {
|
|
17
19
|
renderAddSubModelButton() {
|
|
18
20
|
const collection = (this.context as any)?.collection;
|
|
19
21
|
const fields = collection?.getFields?.() || [];
|
|
22
|
+
const associationDepth = this.context.prefixFieldPath
|
|
23
|
+
? this.context.prefixFieldPath.split('.').filter(Boolean).length
|
|
24
|
+
: 0;
|
|
20
25
|
// 过滤主键/过滤键字段,避免产生“更新主键”的无效配置
|
|
21
26
|
const pk = typeof collection?.getPrimaryKey === 'function' ? collection.getPrimaryKey() : undefined;
|
|
22
27
|
const filterKey = collection?.filterTargetKey;
|
|
@@ -29,6 +34,12 @@ export class AssignFormGridModel extends FormGridModel {
|
|
|
29
34
|
};
|
|
30
35
|
const items = fields
|
|
31
36
|
.filter((field: any) => !isForbidden(field?.name))
|
|
37
|
+
.filter((field: any) => {
|
|
38
|
+
if (associationDepth < MAX_ASSIGN_FORM_ASSOCIATION_FIELD_DEPTH) {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
return !(field?.isAssociationField?.() || field?.target || field?.targetCollection);
|
|
42
|
+
})
|
|
32
43
|
.map((field: any) => {
|
|
33
44
|
const fullName = field.name as string;
|
|
34
45
|
const label = field.title || field.name;
|
|
@@ -111,7 +122,7 @@ export class AssignFormGridModel extends FormGridModel {
|
|
|
111
122
|
return;
|
|
112
123
|
}
|
|
113
124
|
const fieldModel = binding.modelName;
|
|
114
|
-
const created = this.
|
|
125
|
+
const created = this.addSubModel('items', {
|
|
115
126
|
use: 'AssignFormItemModel',
|
|
116
127
|
stepParams: {
|
|
117
128
|
fieldSettings: {
|
|
@@ -137,8 +148,6 @@ export class AssignFormGridModel extends FormGridModel {
|
|
|
137
148
|
},
|
|
138
149
|
},
|
|
139
150
|
},
|
|
140
|
-
parentId: this.uid,
|
|
141
|
-
subKey: 'items',
|
|
142
151
|
});
|
|
143
152
|
created['assignValue'] = value;
|
|
144
153
|
}
|
|
@@ -86,6 +86,14 @@ function getAssignFormTempFieldRefreshKey(originField: AssignFormTempOriginField
|
|
|
86
86
|
].join('|');
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
function normalizeAssignFormInputValue(value: unknown) {
|
|
90
|
+
if (!value || typeof value !== 'object' || !('target' in value)) {
|
|
91
|
+
return value;
|
|
92
|
+
}
|
|
93
|
+
const target = (value as { target?: { value?: unknown } }).target;
|
|
94
|
+
return target && 'value' in target ? target.value : value;
|
|
95
|
+
}
|
|
96
|
+
|
|
89
97
|
/**
|
|
90
98
|
* 使用 FormItemModel 的“表单项”包装,内部渲染 VariableInput,并将“常量”映射到临时字段模型。
|
|
91
99
|
*/
|
|
@@ -230,7 +238,6 @@ export class AssignFormItemModel extends FormItemModel {
|
|
|
230
238
|
fm?.setProps?.({
|
|
231
239
|
disabled: false,
|
|
232
240
|
readPretty: false,
|
|
233
|
-
pattern: 'editable',
|
|
234
241
|
updateAssociation: false,
|
|
235
242
|
multiple: multi,
|
|
236
243
|
});
|
|
@@ -260,9 +267,12 @@ export class AssignFormItemModel extends FormItemModel {
|
|
|
260
267
|
if (fm) {
|
|
261
268
|
fm.setProps?.({
|
|
262
269
|
value: inputProps?.value,
|
|
263
|
-
onChange: (...args:
|
|
264
|
-
const
|
|
265
|
-
|
|
270
|
+
onChange: (...args: unknown[]) => {
|
|
271
|
+
const nextArg = args && args.length ? args[0] : undefined;
|
|
272
|
+
const nextValue = normalizeAssignFormInputValue(nextArg);
|
|
273
|
+
fm.setProps?.({ value: nextValue });
|
|
274
|
+
this.assignValue = nextValue;
|
|
275
|
+
inputProps?.onChange?.(nextValue);
|
|
266
276
|
},
|
|
267
277
|
});
|
|
268
278
|
}
|
|
@@ -0,0 +1,216 @@
|
|
|
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 React from 'react';
|
|
11
|
+
import { App, ConfigProvider } from 'antd';
|
|
12
|
+
import { describe, expect, it } from 'vitest';
|
|
13
|
+
import { render, screen, userEvent, waitFor } from '@nocobase/test/client';
|
|
14
|
+
import {
|
|
15
|
+
FlowEngine,
|
|
16
|
+
FlowEngineProvider,
|
|
17
|
+
FlowRuntimeContext,
|
|
18
|
+
FlowSettingsContextProvider,
|
|
19
|
+
type FlowModel,
|
|
20
|
+
type IFlowModelRepository,
|
|
21
|
+
} from '@nocobase/flow-engine';
|
|
22
|
+
import { AssignFormGridModel } from '../AssignFormGridModel';
|
|
23
|
+
import { AssignFormItemModel } from '../AssignFormItemModel';
|
|
24
|
+
import { AssignFormModel } from '../AssignFormModel';
|
|
25
|
+
import { createAssignFieldValuesStep } from '../assignFieldValuesFlow';
|
|
26
|
+
import { InputFieldModel } from '../../../fields/InputFieldModel';
|
|
27
|
+
import { VariableFieldFormModel } from '../../../fields/VariableFieldFormModel';
|
|
28
|
+
|
|
29
|
+
class MockFlowModelRepository implements IFlowModelRepository {
|
|
30
|
+
async findOne(): Promise<Record<string, any> | null> {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async save(model: FlowModel): Promise<Record<string, any>> {
|
|
35
|
+
return { uid: model.uid };
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async destroy(): Promise<boolean> {
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async move(): Promise<void> {}
|
|
43
|
+
|
|
44
|
+
async duplicate(): Promise<Record<string, any> | null> {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
describe('assignFieldValuesFlow (editor)', () => {
|
|
50
|
+
it('repairs AssignFormModel resource init and clears cached collection', 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
|
+
|
|
63
|
+
const dsm = engine.context.dataSourceManager;
|
|
64
|
+
const main = dsm.getDataSource('main');
|
|
65
|
+
main.addCollection({
|
|
66
|
+
name: 'users',
|
|
67
|
+
fields: [{ name: 'nickname', type: 'string', interface: 'input' }],
|
|
68
|
+
});
|
|
69
|
+
const users = dsm.getCollection('main', 'users');
|
|
70
|
+
expect(users?.name).toBe('users');
|
|
71
|
+
|
|
72
|
+
const action = engine.createModel({
|
|
73
|
+
use: 'FlowModel',
|
|
74
|
+
uid: 'act-assign',
|
|
75
|
+
});
|
|
76
|
+
action.setStepParams('assignSettings', 'assignFieldValues', { assignedValues: { nickname: '1111' } });
|
|
77
|
+
|
|
78
|
+
const existing = engine.createModel<AssignFormModel>({
|
|
79
|
+
use: 'AssignFormModel',
|
|
80
|
+
uid: 'form-assign',
|
|
81
|
+
parentId: action.uid,
|
|
82
|
+
subKey: 'assignForm',
|
|
83
|
+
stepParams: {
|
|
84
|
+
resourceSettings: {
|
|
85
|
+
init: {
|
|
86
|
+
dataSourceKey: 'main',
|
|
87
|
+
collectionName: 'missing',
|
|
88
|
+
},
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
expect(existing.context.collection).toBeUndefined();
|
|
93
|
+
|
|
94
|
+
action.context.defineProperty('blockModel', { value: { collection: users } });
|
|
95
|
+
|
|
96
|
+
const step = createAssignFieldValuesStep({ settingsFlowKey: 'assignSettings' });
|
|
97
|
+
const schema = step.uiSchema();
|
|
98
|
+
const Editor = schema.editor?.['x-component'] as React.ComponentType;
|
|
99
|
+
expect(Editor).toBeTypeOf('function');
|
|
100
|
+
|
|
101
|
+
const flowSettingsCtx = new FlowRuntimeContext(action as any, 'assignSettings', 'settings');
|
|
102
|
+
|
|
103
|
+
render(
|
|
104
|
+
<FlowEngineProvider engine={engine}>
|
|
105
|
+
<ConfigProvider>
|
|
106
|
+
<App>
|
|
107
|
+
<FlowSettingsContextProvider value={flowSettingsCtx}>
|
|
108
|
+
<Editor />
|
|
109
|
+
</FlowSettingsContextProvider>
|
|
110
|
+
</App>
|
|
111
|
+
</ConfigProvider>
|
|
112
|
+
</FlowEngineProvider>,
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
await waitFor(() => {
|
|
116
|
+
const form = engine.findModelByParentId<AssignFormModel>(action.uid, 'assignForm');
|
|
117
|
+
expect(form?.getStepParams('resourceSettings', 'init')).toEqual({
|
|
118
|
+
dataSourceKey: 'main',
|
|
119
|
+
collectionName: 'users',
|
|
120
|
+
});
|
|
121
|
+
expect(form?.context.collection?.name).toBe('users');
|
|
122
|
+
expect(form?.context.collection?.getFields?.().length).toBeGreaterThan(0);
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(screen.getByText('nickname')).toBeInTheDocument();
|
|
127
|
+
const form = engine.findModelByParentId<AssignFormModel>(action.uid, 'assignForm');
|
|
128
|
+
expect(form?.subModels.grid.subModels.items.length).toBe(1);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
await waitFor(() => {
|
|
132
|
+
expect(screen.getByDisplayValue('1111')).toBeInTheDocument();
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
const input = screen.getByDisplayValue('1111') as HTMLInputElement;
|
|
136
|
+
await userEvent.clear(input);
|
|
137
|
+
await userEvent.type(input, '2222');
|
|
138
|
+
|
|
139
|
+
await waitFor(() => {
|
|
140
|
+
const form = engine.findModelByParentId<AssignFormModel>(action.uid, 'assignForm');
|
|
141
|
+
expect(form?.getAssignedValues()).toEqual({ nickname: '2222' });
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('does not clear delegated parent resource cache when repairing AssignFormModel init', async () => {
|
|
146
|
+
const engine = new FlowEngine();
|
|
147
|
+
engine.setModelRepository(new MockFlowModelRepository());
|
|
148
|
+
engine.registerModels({ AssignFormModel, AssignFormGridModel, AssignFormItemModel, InputFieldModel });
|
|
149
|
+
engine.context.defineProperty('location', { value: { search: '' } });
|
|
150
|
+
engine.context.defineProperty('themeToken', { value: { marginLG: 24 } });
|
|
151
|
+
|
|
152
|
+
const dsm = engine.context.dataSourceManager;
|
|
153
|
+
const main = dsm.getDataSource('main');
|
|
154
|
+
main.addCollection({
|
|
155
|
+
name: 'users',
|
|
156
|
+
fields: [{ name: 'nickname', type: 'string', interface: 'input' }],
|
|
157
|
+
});
|
|
158
|
+
const users = dsm.getCollection('main', 'users');
|
|
159
|
+
|
|
160
|
+
const action = engine.createModel({
|
|
161
|
+
use: 'FlowModel',
|
|
162
|
+
uid: 'act-assign-parent-resource',
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
let resourceCreateCount = 0;
|
|
166
|
+
action.context.defineProperty('resource', {
|
|
167
|
+
get: () => ({ id: ++resourceCreateCount }),
|
|
168
|
+
});
|
|
169
|
+
const cachedResource = action.context.resource;
|
|
170
|
+
|
|
171
|
+
engine.createModel<AssignFormModel>({
|
|
172
|
+
use: 'AssignFormModel',
|
|
173
|
+
uid: 'form-assign-parent-resource',
|
|
174
|
+
parentId: action.uid,
|
|
175
|
+
subKey: 'assignForm',
|
|
176
|
+
stepParams: {
|
|
177
|
+
resourceSettings: {
|
|
178
|
+
init: {
|
|
179
|
+
dataSourceKey: 'main',
|
|
180
|
+
collectionName: 'missing',
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
},
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
action.context.defineProperty('blockModel', { value: { collection: users } });
|
|
187
|
+
|
|
188
|
+
const step = createAssignFieldValuesStep({ settingsFlowKey: 'assignSettings' });
|
|
189
|
+
const schema = step.uiSchema();
|
|
190
|
+
const Editor = schema.editor?.['x-component'] as React.ComponentType;
|
|
191
|
+
const flowSettingsCtx = new FlowRuntimeContext(action as any, 'assignSettings', 'settings');
|
|
192
|
+
|
|
193
|
+
render(
|
|
194
|
+
<FlowEngineProvider engine={engine}>
|
|
195
|
+
<ConfigProvider>
|
|
196
|
+
<App>
|
|
197
|
+
<FlowSettingsContextProvider value={flowSettingsCtx}>
|
|
198
|
+
<Editor />
|
|
199
|
+
</FlowSettingsContextProvider>
|
|
200
|
+
</App>
|
|
201
|
+
</ConfigProvider>
|
|
202
|
+
</FlowEngineProvider>,
|
|
203
|
+
);
|
|
204
|
+
|
|
205
|
+
await waitFor(() => {
|
|
206
|
+
const form = engine.findModelByParentId<AssignFormModel>(action.uid, 'assignForm');
|
|
207
|
+
expect(form?.getStepParams('resourceSettings', 'init')).toEqual({
|
|
208
|
+
dataSourceKey: 'main',
|
|
209
|
+
collectionName: 'users',
|
|
210
|
+
});
|
|
211
|
+
});
|
|
212
|
+
|
|
213
|
+
expect(action.context.resource).toBe(cachedResource);
|
|
214
|
+
expect(resourceCreateCount).toBe(1);
|
|
215
|
+
});
|
|
216
|
+
});
|