@nocobase/client-v2 2.1.0-alpha.24 → 2.1.0-alpha.26
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/flow/models/base/BlockGridModel.d.ts +2 -1
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +7 -1
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +5 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +12 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/rowIdentity.d.ts +18 -0
- package/es/index.mjs +84 -84
- package/lib/index.js +84 -84
- package/package.json +5 -5
- package/src/flow/models/base/BlockGridModel.tsx +48 -2
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneAddBlock.test.ts +83 -0
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +39 -16
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +293 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/subtable-nested.test.ts +3 -2
- package/src/flow/models/blocks/form/value-runtime/rules.ts +66 -14
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +285 -12
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +10 -2
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +46 -22
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/rowIdentity.ts +70 -0
- package/src/flow/models/fields/AssociationFieldModel/__tests__/SubTableRowIdentity.test.ts +45 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "2.1.0-alpha.
|
|
3
|
+
"version": "2.1.0-alpha.26",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.mjs",
|
|
@@ -24,9 +24,9 @@
|
|
|
24
24
|
"@formily/antd-v5": "1.2.3",
|
|
25
25
|
"@formily/react": "^2.2.27",
|
|
26
26
|
"@formily/shared": "^2.2.27",
|
|
27
|
-
"@nocobase/flow-engine": "2.1.0-alpha.
|
|
28
|
-
"@nocobase/sdk": "2.1.0-alpha.
|
|
29
|
-
"@nocobase/shared": "2.1.0-alpha.
|
|
27
|
+
"@nocobase/flow-engine": "2.1.0-alpha.26",
|
|
28
|
+
"@nocobase/sdk": "2.1.0-alpha.26",
|
|
29
|
+
"@nocobase/shared": "2.1.0-alpha.26",
|
|
30
30
|
"antd": "5.24.2",
|
|
31
31
|
"classnames": "^2.3.1",
|
|
32
32
|
"dayjs": "^1.11.10",
|
|
@@ -35,5 +35,5 @@
|
|
|
35
35
|
"react-i18next": "^11.15.1",
|
|
36
36
|
"react-router-dom": "^6.30.1"
|
|
37
37
|
},
|
|
38
|
-
"gitHead": "
|
|
38
|
+
"gitHead": "e6e6518030175b58080218bb4357642398bcb54a"
|
|
39
39
|
}
|
|
@@ -8,11 +8,21 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { PlusOutlined } from '@ant-design/icons';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
AddSubModelButton,
|
|
13
|
+
DragOverlayConfig,
|
|
14
|
+
FlowSettingsButton,
|
|
15
|
+
buildSubModelGroups,
|
|
16
|
+
buildSubModelItems,
|
|
17
|
+
type SubModelItem,
|
|
18
|
+
type SubModelItemsType,
|
|
19
|
+
} from '@nocobase/flow-engine';
|
|
12
20
|
import React from 'react';
|
|
13
21
|
import { FilterManager } from '../blocks/filter-manager/FilterManager';
|
|
14
22
|
import { GridModel } from './GridModel';
|
|
15
23
|
|
|
24
|
+
const SELECT_SCENE_ALLOWED_OTHER_BLOCK_MODELS = ['JSBlockModel', 'IframeBlockModel', 'MarkdownBlockModel'] as const;
|
|
25
|
+
|
|
16
26
|
export class BlockGridModel extends GridModel {
|
|
17
27
|
dragOverlayConfig: DragOverlayConfig = {
|
|
18
28
|
// 列内插入
|
|
@@ -53,6 +63,36 @@ export class BlockGridModel extends GridModel {
|
|
|
53
63
|
return this.context.filterManager;
|
|
54
64
|
}
|
|
55
65
|
|
|
66
|
+
get addBlockItems(): SubModelItemsType | undefined {
|
|
67
|
+
if (this.context.view?.inputArgs?.scene !== 'select') {
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return async (ctx) => {
|
|
72
|
+
const items = await buildSubModelGroups(['DataBlockModel', 'FilterBlockModel'])(ctx);
|
|
73
|
+
const allowedOtherBlockModels = SELECT_SCENE_ALLOWED_OTHER_BLOCK_MODELS.filter((modelName) =>
|
|
74
|
+
Boolean(ctx.engine.getModelClass(modelName)),
|
|
75
|
+
);
|
|
76
|
+
const otherBlockItems = (
|
|
77
|
+
await Promise.all(allowedOtherBlockModels.map((modelName) => buildSubModelItems(modelName)(ctx)))
|
|
78
|
+
)
|
|
79
|
+
.flat()
|
|
80
|
+
.sort((a, b) => (a.sort ?? 1000) - (b.sort ?? 1000));
|
|
81
|
+
|
|
82
|
+
if (otherBlockItems.length > 0) {
|
|
83
|
+
items.push({
|
|
84
|
+
key: 'select-scene-other-blocks',
|
|
85
|
+
type: 'group',
|
|
86
|
+
label: ctx.t('Other blocks'),
|
|
87
|
+
sort: 1000,
|
|
88
|
+
children: otherBlockItems,
|
|
89
|
+
} satisfies SubModelItem);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return items;
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
56
96
|
serialize() {
|
|
57
97
|
const data = super.serialize();
|
|
58
98
|
data['filterManager'] = this.filterManager.getFilterConfigs();
|
|
@@ -60,8 +100,14 @@ export class BlockGridModel extends GridModel {
|
|
|
60
100
|
}
|
|
61
101
|
|
|
62
102
|
renderAddSubModelButton() {
|
|
103
|
+
const items = this.addBlockItems;
|
|
63
104
|
return (
|
|
64
|
-
<AddSubModelButton
|
|
105
|
+
<AddSubModelButton
|
|
106
|
+
model={this}
|
|
107
|
+
subModelKey="items"
|
|
108
|
+
items={items}
|
|
109
|
+
subModelBaseClasses={items ? undefined : this.subModelBaseClasses}
|
|
110
|
+
>
|
|
65
111
|
<FlowSettingsButton icon={<PlusOutlined />} data-flow-add-block>
|
|
66
112
|
{this.context.t('Add block')}
|
|
67
113
|
</FlowSettingsButton>
|
|
@@ -0,0 +1,83 @@
|
|
|
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, FlowModel } from '@nocobase/flow-engine';
|
|
11
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
12
|
+
import { BlockGridModel } from '../BlockGridModel';
|
|
13
|
+
|
|
14
|
+
class BlockModel extends FlowModel {}
|
|
15
|
+
BlockModel.define({ label: 'Other blocks' });
|
|
16
|
+
|
|
17
|
+
class DataBlockModel extends BlockModel {}
|
|
18
|
+
DataBlockModel.define({ label: 'Data blocks' });
|
|
19
|
+
|
|
20
|
+
class FilterBlockModel extends BlockModel {}
|
|
21
|
+
FilterBlockModel.define({ label: 'Filter blocks' });
|
|
22
|
+
|
|
23
|
+
class SelectTableBlockModel extends DataBlockModel {}
|
|
24
|
+
SelectTableBlockModel.define({ label: 'Table', children: false });
|
|
25
|
+
|
|
26
|
+
class SelectFilterBlockModel extends FilterBlockModel {}
|
|
27
|
+
SelectFilterBlockModel.define({ label: 'Filter', children: false });
|
|
28
|
+
|
|
29
|
+
class JSBlockModel extends BlockModel {}
|
|
30
|
+
JSBlockModel.define({ label: 'JS block' });
|
|
31
|
+
|
|
32
|
+
class IframeBlockModel extends BlockModel {}
|
|
33
|
+
IframeBlockModel.define({ label: 'Iframe' });
|
|
34
|
+
|
|
35
|
+
class MarkdownBlockModel extends BlockModel {}
|
|
36
|
+
MarkdownBlockModel.define({ label: 'Markdown' });
|
|
37
|
+
|
|
38
|
+
class ActionPanelBlockModel extends BlockModel {}
|
|
39
|
+
ActionPanelBlockModel.define({ label: 'Action panel' });
|
|
40
|
+
|
|
41
|
+
class ReferenceBlockModel extends BlockModel {}
|
|
42
|
+
ReferenceBlockModel.define({ label: 'Block template' });
|
|
43
|
+
|
|
44
|
+
describe('BlockGridModel - select scene add block menu', () => {
|
|
45
|
+
let engine: FlowEngine;
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
engine = new FlowEngine();
|
|
49
|
+
engine.registerModels({
|
|
50
|
+
BlockModel,
|
|
51
|
+
DataBlockModel,
|
|
52
|
+
FilterBlockModel,
|
|
53
|
+
BlockGridModel,
|
|
54
|
+
SelectTableBlockModel,
|
|
55
|
+
SelectFilterBlockModel,
|
|
56
|
+
JSBlockModel,
|
|
57
|
+
IframeBlockModel,
|
|
58
|
+
MarkdownBlockModel,
|
|
59
|
+
ActionPanelBlockModel,
|
|
60
|
+
ReferenceBlockModel,
|
|
61
|
+
});
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('keeps only JS, iframe and markdown under other blocks in select scene', async () => {
|
|
65
|
+
engine.context.defineProperty('view', { value: { inputArgs: { scene: 'select' } } });
|
|
66
|
+
const model = engine.createModel<BlockGridModel>({ use: 'BlockGridModel' });
|
|
67
|
+
|
|
68
|
+
const itemsSource = model.addBlockItems;
|
|
69
|
+
expect(typeof itemsSource).toBe('function');
|
|
70
|
+
|
|
71
|
+
const items = await itemsSource!(model.context);
|
|
72
|
+
const otherBlocks = items.find((item) => item.key === 'select-scene-other-blocks');
|
|
73
|
+
|
|
74
|
+
expect(otherBlocks).toBeTruthy();
|
|
75
|
+
expect(otherBlocks?.type).toBe('group');
|
|
76
|
+
expect(Array.isArray(otherBlocks?.children)).toBe(true);
|
|
77
|
+
|
|
78
|
+
const childKeys = (otherBlocks?.children as any[]).map((item) => item.key);
|
|
79
|
+
expect(childKeys).toEqual(['JSBlockModel', 'IframeBlockModel', 'MarkdownBlockModel']);
|
|
80
|
+
expect(childKeys).not.toContain('ActionPanelBlockModel');
|
|
81
|
+
expect(childKeys).not.toContain('ReferenceBlockModel');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
@@ -28,6 +28,18 @@ import { FieldModel } from '../../base/FieldModel';
|
|
|
28
28
|
import { FormComponent } from './FormBlockModel';
|
|
29
29
|
import { FormItemModel } from './FormItemModel';
|
|
30
30
|
|
|
31
|
+
export const QUICK_EDIT_POPOVER_MAX_HEIGHT = 'calc(100vh - 96px)';
|
|
32
|
+
export const QUICK_EDIT_FORM_MAX_HEIGHT = 'calc(100vh - 160px)';
|
|
33
|
+
export const QUICK_EDIT_MARKDOWN_HEIGHT = 'min(480px, calc(100vh - 320px))';
|
|
34
|
+
|
|
35
|
+
export function getQuickEditFieldProps(collectionField: CollectionField, fieldProps?: Record<string, any>) {
|
|
36
|
+
const nextProps = { ...collectionField.getComponentProps(), ...(fieldProps || {}) };
|
|
37
|
+
if (['markdown', 'vditor'].includes(collectionField.interface) && nextProps.height == null) {
|
|
38
|
+
nextProps.height = QUICK_EDIT_MARKDOWN_HEIGHT;
|
|
39
|
+
}
|
|
40
|
+
return nextProps;
|
|
41
|
+
}
|
|
42
|
+
|
|
31
43
|
export class QuickEditFormModel extends FlowModel {
|
|
32
44
|
fieldPath: string;
|
|
33
45
|
|
|
@@ -99,7 +111,10 @@ export class QuickEditFormModel extends FlowModel {
|
|
|
99
111
|
placement: 'rightTop',
|
|
100
112
|
styles: {
|
|
101
113
|
body: {
|
|
102
|
-
width:
|
|
114
|
+
width: 420,
|
|
115
|
+
maxHeight: QUICK_EDIT_POPOVER_MAX_HEIGHT,
|
|
116
|
+
overflowY: 'auto',
|
|
117
|
+
overscrollBehavior: 'contain',
|
|
103
118
|
},
|
|
104
119
|
},
|
|
105
120
|
content: (popover) => {
|
|
@@ -160,20 +175,28 @@ export class QuickEditFormModel extends FlowModel {
|
|
|
160
175
|
|
|
161
176
|
return (
|
|
162
177
|
<FormComponent model={this}>
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
178
|
+
<div
|
|
179
|
+
style={{
|
|
180
|
+
minHeight: 0,
|
|
181
|
+
overflowY: 'auto',
|
|
182
|
+
maxHeight: QUICK_EDIT_FORM_MAX_HEIGHT,
|
|
183
|
+
}}
|
|
184
|
+
>
|
|
185
|
+
{this.mapSubModels('fields', (field) => {
|
|
186
|
+
return (
|
|
187
|
+
<FormItem
|
|
188
|
+
showLabel={false}
|
|
189
|
+
name={this.fieldPath}
|
|
190
|
+
key={field.uid}
|
|
191
|
+
initialValue={this.context.record?.[this.fieldPath]}
|
|
192
|
+
{...this.props}
|
|
193
|
+
>
|
|
194
|
+
<FieldModelRenderer model={field} fallback={<Skeleton.Input size="small" />} />
|
|
195
|
+
</FormItem>
|
|
196
|
+
);
|
|
197
|
+
})}
|
|
198
|
+
</div>
|
|
199
|
+
<Space style={{ display: 'flex', justifyContent: 'flex-end', flexShrink: 0 }}>
|
|
177
200
|
<Button
|
|
178
201
|
onClick={() => {
|
|
179
202
|
this.viewContainer.close();
|
|
@@ -257,7 +280,7 @@ QuickEditFormModel.registerFlow({
|
|
|
257
280
|
},
|
|
258
281
|
},
|
|
259
282
|
});
|
|
260
|
-
fieldModel.setProps(
|
|
283
|
+
fieldModel.setProps(getQuickEditFieldProps(collectionField, ctx.model._fieldProps));
|
|
261
284
|
fieldModel.setProps({ sourceFieldModelUid: ctx.inputArgs.sourceFieldModelUid });
|
|
262
285
|
ctx.model.context.defineProperty('collectionField', {
|
|
263
286
|
get: () => collectionField,
|
|
@@ -1706,6 +1706,299 @@ describe('FormValueRuntime (form assign rules)', () => {
|
|
|
1706
1706
|
expect(formStub.getFieldValue(['users', 0, 'name'])).toBe('');
|
|
1707
1707
|
});
|
|
1708
1708
|
|
|
1709
|
+
it('clears explicit state for deleted to-many rows even when allValues is stale', () => {
|
|
1710
|
+
const engineEmitter = new EventEmitter();
|
|
1711
|
+
const blockEmitter = new EventEmitter();
|
|
1712
|
+
const formStub = createFormStub({
|
|
1713
|
+
roles: [{ title: 'Z', __is_new__: true }],
|
|
1714
|
+
});
|
|
1715
|
+
|
|
1716
|
+
const blockModel: any = {
|
|
1717
|
+
uid: 'form-assign-default-create-readd-row',
|
|
1718
|
+
flowEngine: { emitter: engineEmitter },
|
|
1719
|
+
emitter: blockEmitter,
|
|
1720
|
+
dispatchEvent: vi.fn(),
|
|
1721
|
+
getAclActionName: () => 'create',
|
|
1722
|
+
};
|
|
1723
|
+
|
|
1724
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1725
|
+
runtime.mount({ sync: true });
|
|
1726
|
+
|
|
1727
|
+
lodashSet((formStub as any).__store, ['roles', 0, 'title'], 'custom');
|
|
1728
|
+
runtime.handleFormFieldsChange([{ name: ['roles', 0, 'title'], touched: true } as any]);
|
|
1729
|
+
expect((runtime as any).findExplicitHit('roles[0].title')).toBe('roles[0].title');
|
|
1730
|
+
|
|
1731
|
+
const staleAllValues = { roles: [{ title: 'custom', __is_new__: true }] };
|
|
1732
|
+
lodashSet((formStub as any).__store, ['roles'], []);
|
|
1733
|
+
runtime.handleFormValuesChange({ roles: [] }, staleAllValues);
|
|
1734
|
+
|
|
1735
|
+
expect((runtime as any).findExplicitHit('roles[0].title')).toBeNull();
|
|
1736
|
+
});
|
|
1737
|
+
|
|
1738
|
+
it('moves explicit state with filterTargetKey identified to-many rows after deleting a preceding row', () => {
|
|
1739
|
+
const engineEmitter = new EventEmitter();
|
|
1740
|
+
const blockEmitter = new EventEmitter();
|
|
1741
|
+
const formStub = createFormStub({
|
|
1742
|
+
roles: [
|
|
1743
|
+
{ code: 'admin', title: 'same-title' },
|
|
1744
|
+
{ code: 'editor', title: 'custom' },
|
|
1745
|
+
],
|
|
1746
|
+
});
|
|
1747
|
+
|
|
1748
|
+
const blockModel: any = {
|
|
1749
|
+
uid: 'form-assign-default-create-shift-row',
|
|
1750
|
+
flowEngine: { emitter: engineEmitter },
|
|
1751
|
+
emitter: blockEmitter,
|
|
1752
|
+
dispatchEvent: vi.fn(),
|
|
1753
|
+
getAclActionName: () => 'create',
|
|
1754
|
+
};
|
|
1755
|
+
|
|
1756
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1757
|
+
runtime.mount({ sync: true });
|
|
1758
|
+
|
|
1759
|
+
const blockCtx = createFieldContext(runtime);
|
|
1760
|
+
const roleRowCollection: any = { getField: () => null, filterTargetKey: 'code' };
|
|
1761
|
+
const rolesField: any = { type: 'hasMany', isAssociationField: () => true, targetCollection: roleRowCollection };
|
|
1762
|
+
const rootCollection: any = { getField: (name: string) => (name === 'roles' ? rolesField : null) };
|
|
1763
|
+
blockCtx.defineProperty('collection', { value: rootCollection });
|
|
1764
|
+
blockModel.context = blockCtx;
|
|
1765
|
+
|
|
1766
|
+
lodashSet((formStub as any).__store, ['roles', 1, 'title'], 'same-title');
|
|
1767
|
+
runtime.handleFormFieldsChange([{ name: ['roles', 1, 'title'], touched: true } as any]);
|
|
1768
|
+
expect((runtime as any).findExplicitHit('roles[1].title')).toBe('roles[1].title');
|
|
1769
|
+
|
|
1770
|
+
const shiftedRow = formStub.getFieldValue(['roles', 1]);
|
|
1771
|
+
lodashSet((formStub as any).__store, ['roles'], [shiftedRow]);
|
|
1772
|
+
runtime.handleFormValuesChange({ roles: [shiftedRow] }, formStub.getFieldsValue());
|
|
1773
|
+
|
|
1774
|
+
expect((runtime as any).findExplicitHit('roles[1].title')).toBeNull();
|
|
1775
|
+
expect((runtime as any).findExplicitHit('roles[0].title')).toBe('roles[0].title');
|
|
1776
|
+
});
|
|
1777
|
+
|
|
1778
|
+
it('moves observable binding writes with filterTargetKey identified rows after deleting a preceding row', async () => {
|
|
1779
|
+
const engineEmitter = new EventEmitter();
|
|
1780
|
+
const blockEmitter = new EventEmitter();
|
|
1781
|
+
const formStub = createFormStub({
|
|
1782
|
+
roles: [{ code: 'admin' }, { code: 'editor' }],
|
|
1783
|
+
});
|
|
1784
|
+
|
|
1785
|
+
const blockModel: any = {
|
|
1786
|
+
uid: 'form-assign-default-observable-shift-row',
|
|
1787
|
+
flowEngine: { emitter: engineEmitter },
|
|
1788
|
+
emitter: blockEmitter,
|
|
1789
|
+
dispatchEvent: vi.fn(),
|
|
1790
|
+
getAclActionName: () => 'create',
|
|
1791
|
+
};
|
|
1792
|
+
|
|
1793
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1794
|
+
runtime.mount({ sync: true });
|
|
1795
|
+
|
|
1796
|
+
const blockCtx = createFieldContext(runtime);
|
|
1797
|
+
const roleRowCollection: any = { getField: () => null, filterTargetKey: 'code' };
|
|
1798
|
+
const rolesField: any = { type: 'hasMany', isAssociationField: () => true, targetCollection: roleRowCollection };
|
|
1799
|
+
const rootCollection: any = { getField: (name: string) => (name === 'roles' ? rolesField : null) };
|
|
1800
|
+
blockCtx.defineProperty('collection', { value: rootCollection });
|
|
1801
|
+
blockCtx.defineProperty('fieldIndex', { value: ['roles:1'] });
|
|
1802
|
+
blockModel.context = blockCtx;
|
|
1803
|
+
|
|
1804
|
+
const defaultMeta = observable({ label: 'Editor' });
|
|
1805
|
+
await runtime.setFormValues(blockCtx, [{ path: 'roles.meta', value: defaultMeta }], {
|
|
1806
|
+
source: 'linkage',
|
|
1807
|
+
markExplicit: false,
|
|
1808
|
+
});
|
|
1809
|
+
|
|
1810
|
+
expect(formStub.getFieldValue(['roles', 1, 'meta'])).toEqual({ label: 'Editor' });
|
|
1811
|
+
|
|
1812
|
+
const shiftedRow = formStub.getFieldValue(['roles', 1]);
|
|
1813
|
+
lodashSet((formStub as any).__store, ['roles'], [shiftedRow]);
|
|
1814
|
+
runtime.handleFormValuesChange({ roles: [shiftedRow] }, formStub.getFieldsValue());
|
|
1815
|
+
|
|
1816
|
+
defaultMeta.label = 'Updated';
|
|
1817
|
+
|
|
1818
|
+
await waitFor(() => expect(formStub.getFieldValue(['roles', 0, 'meta'])).toEqual({ label: 'Updated' }));
|
|
1819
|
+
expect(formStub.getFieldValue(['roles', 1])).toBeUndefined();
|
|
1820
|
+
});
|
|
1821
|
+
|
|
1822
|
+
it('skips stale to-many row rules after the row index is out of bounds', async () => {
|
|
1823
|
+
const engineEmitter = new EventEmitter();
|
|
1824
|
+
const blockEmitter = new EventEmitter();
|
|
1825
|
+
const formStub = createFormStub({
|
|
1826
|
+
roles: [{ title: 'Z', __is_new__: true, __index__: 'row-1' }],
|
|
1827
|
+
});
|
|
1828
|
+
|
|
1829
|
+
const blockModel: any = {
|
|
1830
|
+
uid: 'form-assign-default-stale-row',
|
|
1831
|
+
flowEngine: { emitter: engineEmitter },
|
|
1832
|
+
emitter: blockEmitter,
|
|
1833
|
+
dispatchEvent: vi.fn(),
|
|
1834
|
+
getAclActionName: () => 'create',
|
|
1835
|
+
};
|
|
1836
|
+
|
|
1837
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1838
|
+
runtime.mount({ sync: true });
|
|
1839
|
+
|
|
1840
|
+
const blockCtx = createFieldContext(runtime);
|
|
1841
|
+
const roleRowCollection: any = { getField: () => null };
|
|
1842
|
+
const rolesField: any = { type: 'hasMany', isAssociationField: () => true, targetCollection: roleRowCollection };
|
|
1843
|
+
const rootCollection: any = { getField: (name: string) => (name === 'roles' ? rolesField : null) };
|
|
1844
|
+
blockCtx.defineProperty('collection', { value: rootCollection });
|
|
1845
|
+
blockModel.context = blockCtx;
|
|
1846
|
+
|
|
1847
|
+
const masterModel: any = {
|
|
1848
|
+
uid: 'roles.title',
|
|
1849
|
+
subModels: { field: {} },
|
|
1850
|
+
getStepParams(flowKey: string, stepKey: string) {
|
|
1851
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') {
|
|
1852
|
+
return { fieldPath: 'roles.title' };
|
|
1853
|
+
}
|
|
1854
|
+
return undefined;
|
|
1855
|
+
},
|
|
1856
|
+
};
|
|
1857
|
+
const masterCtx = createFieldContext(runtime);
|
|
1858
|
+
masterCtx.defineProperty('blockModel', { value: blockModel });
|
|
1859
|
+
masterCtx.defineProperty('model', { value: masterModel });
|
|
1860
|
+
masterModel.context = masterCtx;
|
|
1861
|
+
|
|
1862
|
+
const staleRow: any = {
|
|
1863
|
+
uid: 'roles.title',
|
|
1864
|
+
isFork: true,
|
|
1865
|
+
forkId: 'roles:stale',
|
|
1866
|
+
subModels: { field: {} },
|
|
1867
|
+
getStepParams(flowKey: string, stepKey: string) {
|
|
1868
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') {
|
|
1869
|
+
return { fieldPath: 'roles.title' };
|
|
1870
|
+
}
|
|
1871
|
+
return undefined;
|
|
1872
|
+
},
|
|
1873
|
+
};
|
|
1874
|
+
const staleCtx = createFieldContext(runtime);
|
|
1875
|
+
staleCtx.defineProperty('blockModel', { value: blockModel });
|
|
1876
|
+
staleCtx.defineProperty('fieldIndex', { value: ['roles:1'] });
|
|
1877
|
+
staleCtx.defineProperty('item', {
|
|
1878
|
+
value: {
|
|
1879
|
+
index: 1,
|
|
1880
|
+
length: 1,
|
|
1881
|
+
__is_new__: true,
|
|
1882
|
+
value: { title: 'Z', __is_new__: true, __index__: 'row-1' },
|
|
1883
|
+
},
|
|
1884
|
+
});
|
|
1885
|
+
staleCtx.defineProperty('model', { value: staleRow });
|
|
1886
|
+
staleRow.context = staleCtx;
|
|
1887
|
+
masterModel.forks = new Set([staleRow]);
|
|
1888
|
+
|
|
1889
|
+
blockCtx.defineProperty('engine', {
|
|
1890
|
+
value: {
|
|
1891
|
+
forEachModel: (cb: any) => {
|
|
1892
|
+
cb(masterModel);
|
|
1893
|
+
},
|
|
1894
|
+
},
|
|
1895
|
+
});
|
|
1896
|
+
|
|
1897
|
+
runtime.syncAssignRules([
|
|
1898
|
+
{
|
|
1899
|
+
key: 'r1',
|
|
1900
|
+
enable: true,
|
|
1901
|
+
targetPath: 'roles.title',
|
|
1902
|
+
mode: 'default',
|
|
1903
|
+
condition: { logic: '$and', items: [] },
|
|
1904
|
+
value: 'Z',
|
|
1905
|
+
},
|
|
1906
|
+
]);
|
|
1907
|
+
|
|
1908
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1909
|
+
expect(formStub.getFieldValue(['roles'])).toHaveLength(1);
|
|
1910
|
+
expect(formStub.getFieldValue(['roles', 1])).toBeUndefined();
|
|
1911
|
+
});
|
|
1912
|
+
|
|
1913
|
+
it('skips to-many row rules when filterTargetKey identity does not match the current row at that index', async () => {
|
|
1914
|
+
const engineEmitter = new EventEmitter();
|
|
1915
|
+
const blockEmitter = new EventEmitter();
|
|
1916
|
+
const formStub = createFormStub({
|
|
1917
|
+
roles: [{ code: 'admin', title: '' }],
|
|
1918
|
+
});
|
|
1919
|
+
|
|
1920
|
+
const blockModel: any = {
|
|
1921
|
+
uid: 'form-assign-default-mismatched-row',
|
|
1922
|
+
flowEngine: { emitter: engineEmitter },
|
|
1923
|
+
emitter: blockEmitter,
|
|
1924
|
+
dispatchEvent: vi.fn(),
|
|
1925
|
+
getAclActionName: () => 'create',
|
|
1926
|
+
};
|
|
1927
|
+
|
|
1928
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as any });
|
|
1929
|
+
runtime.mount({ sync: true });
|
|
1930
|
+
|
|
1931
|
+
const blockCtx = createFieldContext(runtime);
|
|
1932
|
+
const roleRowCollection: any = { getField: () => null, filterTargetKey: 'code' };
|
|
1933
|
+
const rolesField: any = { type: 'hasMany', isAssociationField: () => true, targetCollection: roleRowCollection };
|
|
1934
|
+
const rootCollection: any = { getField: (name: string) => (name === 'roles' ? rolesField : null) };
|
|
1935
|
+
blockCtx.defineProperty('collection', { value: rootCollection });
|
|
1936
|
+
blockModel.context = blockCtx;
|
|
1937
|
+
|
|
1938
|
+
const masterModel: any = {
|
|
1939
|
+
uid: 'roles.title',
|
|
1940
|
+
subModels: { field: {} },
|
|
1941
|
+
getStepParams(flowKey: string, stepKey: string) {
|
|
1942
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') {
|
|
1943
|
+
return { fieldPath: 'roles.title' };
|
|
1944
|
+
}
|
|
1945
|
+
return undefined;
|
|
1946
|
+
},
|
|
1947
|
+
};
|
|
1948
|
+
const masterCtx = createFieldContext(runtime);
|
|
1949
|
+
masterCtx.defineProperty('blockModel', { value: blockModel });
|
|
1950
|
+
masterCtx.defineProperty('model', { value: masterModel });
|
|
1951
|
+
masterModel.context = masterCtx;
|
|
1952
|
+
|
|
1953
|
+
const mismatchedRow: any = {
|
|
1954
|
+
uid: 'roles.title',
|
|
1955
|
+
isFork: true,
|
|
1956
|
+
forkId: 'roles:mismatched',
|
|
1957
|
+
subModels: { field: {} },
|
|
1958
|
+
getStepParams(flowKey: string, stepKey: string) {
|
|
1959
|
+
if (flowKey === 'fieldSettings' && stepKey === 'init') {
|
|
1960
|
+
return { fieldPath: 'roles.title' };
|
|
1961
|
+
}
|
|
1962
|
+
return undefined;
|
|
1963
|
+
},
|
|
1964
|
+
};
|
|
1965
|
+
const rowCtx = createFieldContext(runtime);
|
|
1966
|
+
rowCtx.defineProperty('blockModel', { value: blockModel });
|
|
1967
|
+
rowCtx.defineProperty('fieldIndex', { value: ['roles:0'] });
|
|
1968
|
+
rowCtx.defineProperty('item', {
|
|
1969
|
+
value: {
|
|
1970
|
+
index: 0,
|
|
1971
|
+
length: 1,
|
|
1972
|
+
value: { code: 'editor', title: 'custom' },
|
|
1973
|
+
},
|
|
1974
|
+
});
|
|
1975
|
+
rowCtx.defineProperty('model', { value: mismatchedRow });
|
|
1976
|
+
mismatchedRow.context = rowCtx;
|
|
1977
|
+
masterModel.forks = new Set([mismatchedRow]);
|
|
1978
|
+
|
|
1979
|
+
blockCtx.defineProperty('engine', {
|
|
1980
|
+
value: {
|
|
1981
|
+
forEachModel: (cb: any) => {
|
|
1982
|
+
cb(masterModel);
|
|
1983
|
+
},
|
|
1984
|
+
},
|
|
1985
|
+
});
|
|
1986
|
+
|
|
1987
|
+
runtime.syncAssignRules([
|
|
1988
|
+
{
|
|
1989
|
+
key: 'r1',
|
|
1990
|
+
enable: true,
|
|
1991
|
+
targetPath: 'roles.title',
|
|
1992
|
+
mode: 'default',
|
|
1993
|
+
condition: { logic: '$and', items: [] },
|
|
1994
|
+
value: 'Z',
|
|
1995
|
+
},
|
|
1996
|
+
]);
|
|
1997
|
+
|
|
1998
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
1999
|
+
expect(formStub.getFieldValue(['roles', 0, 'title'])).toBe('');
|
|
2000
|
+
});
|
|
2001
|
+
|
|
1709
2002
|
it('keeps default-disabled for edited to-many leaf when onValuesChange only provides top-level path', async () => {
|
|
1710
2003
|
const engineEmitter = new EventEmitter();
|
|
1711
2004
|
const blockEmitter = new EventEmitter();
|
|
@@ -82,8 +82,9 @@ describe('SubTableColumnModel (nested subform)', () => {
|
|
|
82
82
|
parentFieldIndex: ['users:0'],
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
const rowFork = column.
|
|
86
|
-
expect(rowFork
|
|
85
|
+
const [rowFork] = Array.from(column.forks ?? []);
|
|
86
|
+
expect(rowFork).toBeDefined();
|
|
87
|
+
expect((rowFork as any)?.context?.fieldIndex).toEqual(['users:0', 'roles:1']);
|
|
87
88
|
|
|
88
89
|
runtime.syncAssignRules([
|
|
89
90
|
{
|