@nocobase/client-v2 2.1.0-alpha.35 → 2.1.0-alpha.36
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/components/code-editor/index.d.ts +1 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +4 -0
- package/es/flow/models/fields/ClickableFieldModel.d.ts +3 -0
- package/es/flow/models/fields/DisplayTitleFieldModel.d.ts +2 -2
- package/es/index.mjs +49 -49
- package/lib/index.js +37 -37
- package/package.json +6 -5
- package/src/__tests__/globalDeps.test.ts +1 -0
- package/src/flow/actions/__tests__/pattern.test.ts +134 -0
- package/src/flow/actions/__tests__/titleField.test.ts +45 -0
- package/src/flow/actions/pattern.tsx +41 -6
- package/src/flow/actions/titleField.tsx +3 -1
- package/src/flow/components/DynamicFlowsIcon.tsx +87 -13
- package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +195 -8
- package/src/flow/components/code-editor/index.tsx +12 -8
- package/src/flow/models/blocks/form/FormActionModel.tsx +2 -8
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +144 -3
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +170 -1
- package/src/flow/models/fields/ClickableFieldModel.tsx +46 -2
- package/src/flow/models/fields/DisplayTitleFieldModel.tsx +40 -15
- package/src/flow/models/fields/__tests__/ClickableFieldModel.test.ts +180 -2
- package/src/utils/globalDeps.ts +3 -0
|
@@ -8,7 +8,18 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, expect, it, vi } from 'vitest';
|
|
11
|
-
import {
|
|
11
|
+
import { FlowEngine } from '@nocobase/flow-engine';
|
|
12
|
+
import { DisplayTitleFieldModel } from '../../../DisplayTitleFieldModel';
|
|
13
|
+
import { titleField } from '../../../../../actions/titleField';
|
|
14
|
+
import {
|
|
15
|
+
SubTableColumnModel,
|
|
16
|
+
getLatestSubTableRowRecord,
|
|
17
|
+
buildRowPathFromFieldIndex,
|
|
18
|
+
isSubTableColumnConfiguredReadPretty,
|
|
19
|
+
getSubTableColumnTitleField,
|
|
20
|
+
getSubTableColumnReadPrettyFieldProps,
|
|
21
|
+
isSubTableColumnReadPretty,
|
|
22
|
+
} from '../SubTableColumnModel';
|
|
12
23
|
|
|
13
24
|
describe('SubTableColumnModel row record helpers', () => {
|
|
14
25
|
it('builds the row path from fieldIndex entries', () => {
|
|
@@ -39,4 +50,162 @@ describe('SubTableColumnModel row record helpers', () => {
|
|
|
39
50
|
|
|
40
51
|
expect(getLatestSubTableRowRecord(form, ['roles:0'], fallback)).toBe(fallback);
|
|
41
52
|
});
|
|
53
|
+
|
|
54
|
+
it('treats a display-only column pattern as read-pretty mode', () => {
|
|
55
|
+
expect(isSubTableColumnReadPretty({ props: { pattern: 'readPretty' } })).toBe(true);
|
|
56
|
+
expect(isSubTableColumnReadPretty({ props: { readPretty: true } })).toBe(true);
|
|
57
|
+
expect(isSubTableColumnReadPretty({ props: { pattern: 'editable' } })).toBe(false);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('treats a saved display-only column pattern as read-pretty during beforeRender restore', () => {
|
|
61
|
+
expect(
|
|
62
|
+
isSubTableColumnConfiguredReadPretty({
|
|
63
|
+
props: {},
|
|
64
|
+
getStepParams: vi.fn(() => ({ pattern: 'readPretty' })),
|
|
65
|
+
}),
|
|
66
|
+
).toBe(true);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('passes the association title field to read-pretty cell field models', () => {
|
|
70
|
+
const relationValue = { id: 1, name: 'Alice' };
|
|
71
|
+
expect(
|
|
72
|
+
getSubTableColumnReadPrettyFieldProps(
|
|
73
|
+
{
|
|
74
|
+
props: {},
|
|
75
|
+
collectionField: {
|
|
76
|
+
targetCollectionTitleFieldName: 'name',
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
relationValue,
|
|
80
|
+
),
|
|
81
|
+
).toEqual({
|
|
82
|
+
value: relationValue,
|
|
83
|
+
titleField: 'name',
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('resolves the saved title field before the target collection default', () => {
|
|
88
|
+
expect(
|
|
89
|
+
getSubTableColumnTitleField({
|
|
90
|
+
props: { titleField: 'nickname' },
|
|
91
|
+
subModels: {
|
|
92
|
+
field: {
|
|
93
|
+
props: {
|
|
94
|
+
titleField: 'name',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
collectionField: {
|
|
99
|
+
targetCollectionTitleFieldName: 'title',
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
).toBe('nickname');
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('applies the configured title field to a display-only association column', async () => {
|
|
106
|
+
const engine = new FlowEngine();
|
|
107
|
+
engine.registerModels({ SubTableColumnModel, DisplayTitleFieldModel });
|
|
108
|
+
engine.registerActions({ titleField });
|
|
109
|
+
|
|
110
|
+
const rolesField = {
|
|
111
|
+
name: 'roles',
|
|
112
|
+
title: 'Roles',
|
|
113
|
+
collection: { name: 'users' },
|
|
114
|
+
targetCollectionTitleFieldName: 'name',
|
|
115
|
+
targetCollection: {
|
|
116
|
+
name: 'roles',
|
|
117
|
+
getField: vi.fn((name: string) => ({
|
|
118
|
+
name,
|
|
119
|
+
getComponentProps: () => ({ componentField: name }),
|
|
120
|
+
})),
|
|
121
|
+
},
|
|
122
|
+
isAssociationField: () => true,
|
|
123
|
+
getComponentProps: () => ({}),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const column = engine.createModel<SubTableColumnModel>({
|
|
127
|
+
use: SubTableColumnModel,
|
|
128
|
+
uid: 'roles-display-column-title-field',
|
|
129
|
+
stepParams: {
|
|
130
|
+
fieldSettings: {
|
|
131
|
+
init: {
|
|
132
|
+
dataSourceKey: 'main',
|
|
133
|
+
collectionName: 'users',
|
|
134
|
+
fieldPath: 'roles',
|
|
135
|
+
},
|
|
136
|
+
},
|
|
137
|
+
subTableColumnSettings: {
|
|
138
|
+
pattern: {
|
|
139
|
+
pattern: 'readPretty',
|
|
140
|
+
},
|
|
141
|
+
fieldNames: {
|
|
142
|
+
label: 'nickname',
|
|
143
|
+
},
|
|
144
|
+
},
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
column.context.defineProperty('collectionField', { value: rolesField });
|
|
148
|
+
column.context.defineProperty('blockModel', { value: { addAppends: vi.fn() } });
|
|
149
|
+
column.setSubModel('field', {
|
|
150
|
+
use: DisplayTitleFieldModel,
|
|
151
|
+
uid: 'roles-display-field-title-field',
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
await column.dispatchEvent('beforeRender');
|
|
155
|
+
|
|
156
|
+
expect(column.props.titleField).toBe('nickname');
|
|
157
|
+
expect(column.props.componentField).toBe('nickname');
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('applies saved display field settings to the inner field during column beforeRender', async () => {
|
|
161
|
+
const engine = new FlowEngine();
|
|
162
|
+
engine.registerModels({ SubTableColumnModel, DisplayTitleFieldModel });
|
|
163
|
+
|
|
164
|
+
const rolesCollection = {
|
|
165
|
+
name: 'roles',
|
|
166
|
+
filterTargetKey: 'id',
|
|
167
|
+
};
|
|
168
|
+
const rolesField = {
|
|
169
|
+
name: 'roles',
|
|
170
|
+
title: 'Roles',
|
|
171
|
+
collection: { name: 'users' },
|
|
172
|
+
targetCollection: rolesCollection,
|
|
173
|
+
isAssociationField: () => true,
|
|
174
|
+
getComponentProps: () => ({ titleField: 'name' }),
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
const column = engine.createModel<SubTableColumnModel>({
|
|
178
|
+
use: SubTableColumnModel,
|
|
179
|
+
uid: 'roles-title-column',
|
|
180
|
+
stepParams: {
|
|
181
|
+
fieldSettings: {
|
|
182
|
+
init: {
|
|
183
|
+
dataSourceKey: 'main',
|
|
184
|
+
collectionName: 'users',
|
|
185
|
+
fieldPath: 'roles',
|
|
186
|
+
},
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
});
|
|
190
|
+
column.context.defineProperty('collectionField', { value: rolesField });
|
|
191
|
+
column.context.defineProperty('blockModel', { value: { addAppends: vi.fn() } });
|
|
192
|
+
|
|
193
|
+
const field = column.setSubModel('field', {
|
|
194
|
+
use: DisplayTitleFieldModel,
|
|
195
|
+
uid: 'roles-title-display',
|
|
196
|
+
stepParams: {
|
|
197
|
+
displayFieldSettings: {
|
|
198
|
+
clickToOpen: {
|
|
199
|
+
clickToOpen: true,
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
},
|
|
203
|
+
}) as DisplayTitleFieldModel;
|
|
204
|
+
|
|
205
|
+
expect(field.props.clickToOpen).toBeUndefined();
|
|
206
|
+
|
|
207
|
+
await column.dispatchEvent('beforeRender');
|
|
208
|
+
|
|
209
|
+
expect(field.props.clickToOpen).toBe(true);
|
|
210
|
+
});
|
|
42
211
|
});
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { CollectionField, tExpr } from '@nocobase/flow-engine';
|
|
11
|
+
import { uid } from '@formily/shared';
|
|
11
12
|
import { Tag } from 'antd';
|
|
12
13
|
import { castArray, get } from 'lodash';
|
|
13
14
|
import React from 'react';
|
|
@@ -38,9 +39,49 @@ const hasAssociationPathName = (parent: unknown): parent is { associationPathNam
|
|
|
38
39
|
|
|
39
40
|
const hasUsableSourceId = (sourceId: unknown) => sourceId !== undefined && sourceId !== null && String(sourceId) !== '';
|
|
40
41
|
|
|
42
|
+
function getParentAssociationField(model: FieldModel): CollectionField | null {
|
|
43
|
+
const parentCollectionField =
|
|
44
|
+
(model.parent as any)?.context?.collectionField || (model.parent as any)?.collectionField;
|
|
45
|
+
return parentCollectionField?.isAssociationField?.() ? parentCollectionField : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function applyClickToOpenProps(ctx: any, params: any) {
|
|
49
|
+
const collectionField = ctx.collectionField?.isAssociationField?.()
|
|
50
|
+
? ctx.collectionField
|
|
51
|
+
: ctx.model?.parent?.context?.collectionField || ctx.collectionField;
|
|
52
|
+
ctx.model.setProps({
|
|
53
|
+
clickToOpen: params.clickToOpen,
|
|
54
|
+
...(collectionField?.getComponentProps?.() || {}),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export async function refreshClickToOpenRuntime(ctx: any) {
|
|
59
|
+
ctx.model.invalidateFlowCache?.('beforeRender', true);
|
|
60
|
+
await ctx.model.rerender?.();
|
|
61
|
+
|
|
62
|
+
const parent = ctx.model.parent;
|
|
63
|
+
if (!parent) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
parent.invalidateFlowCache?.('beforeRender', true);
|
|
67
|
+
parent.setProps?.({
|
|
68
|
+
__displayFieldRefreshKey: uid(),
|
|
69
|
+
});
|
|
70
|
+
await parent.rerender?.();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export async function applyClickToOpenSetting(ctx: any, params: any) {
|
|
74
|
+
applyClickToOpenProps(ctx, params);
|
|
75
|
+
await refreshClickToOpenRuntime(ctx);
|
|
76
|
+
}
|
|
77
|
+
|
|
41
78
|
export class ClickableFieldModel extends FieldModel {
|
|
42
79
|
get collectionField(): CollectionField {
|
|
43
|
-
|
|
80
|
+
const collectionField = this.context.collectionField;
|
|
81
|
+
if (collectionField?.isAssociationField?.()) {
|
|
82
|
+
return collectionField;
|
|
83
|
+
}
|
|
84
|
+
return getParentAssociationField(this) || collectionField;
|
|
44
85
|
}
|
|
45
86
|
|
|
46
87
|
/**
|
|
@@ -294,8 +335,11 @@ ClickableFieldModel.registerFlow({
|
|
|
294
335
|
hideInSettings(ctx) {
|
|
295
336
|
return ctx.disableFieldClickToOpen;
|
|
296
337
|
},
|
|
338
|
+
async afterParamsSave(ctx, params) {
|
|
339
|
+
await applyClickToOpenSetting(ctx, params);
|
|
340
|
+
},
|
|
297
341
|
handler(ctx, params) {
|
|
298
|
-
ctx
|
|
342
|
+
applyClickToOpenProps(ctx, params);
|
|
299
343
|
},
|
|
300
344
|
},
|
|
301
345
|
},
|
|
@@ -12,12 +12,20 @@ import { Typography } from 'antd';
|
|
|
12
12
|
import { castArray } from 'lodash';
|
|
13
13
|
import { css } from '@emotion/css';
|
|
14
14
|
import React from 'react';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
15
|
+
import { openViewFlow } from '../../flows/openViewFlow';
|
|
16
|
+
import { applyClickToOpenProps, applyClickToOpenSetting, ClickableFieldModel } from './ClickableFieldModel';
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
function isParentAssociationField(ctx: any) {
|
|
19
|
+
return !!ctx.model?.parent?.context?.collectionField?.isAssociationField?.();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export class DisplayTitleFieldModel extends ClickableFieldModel {
|
|
19
23
|
get collectionField(): CollectionField {
|
|
20
|
-
|
|
24
|
+
const collectionField = this.context.collectionField;
|
|
25
|
+
if (collectionField?.isAssociationField?.()) {
|
|
26
|
+
return collectionField;
|
|
27
|
+
}
|
|
28
|
+
return (this.parent as any)?.context?.collectionField || collectionField;
|
|
21
29
|
}
|
|
22
30
|
|
|
23
31
|
renderComponent(value) {
|
|
@@ -56,20 +64,13 @@ export class DisplayTitleFieldModel extends FieldModel {
|
|
|
56
64
|
};
|
|
57
65
|
if (titleField) {
|
|
58
66
|
const result = castArray(value).flatMap((v, idx) => {
|
|
59
|
-
const
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
const result = this.renderComponent(displayValue);
|
|
63
|
-
const node = hasDisplayValue(displayValue) ? result : 'N/A';
|
|
64
|
-
return idx === 0 ? [node] : [<span key={`sep-${idx}`}>, </span>, node];
|
|
67
|
+
const node = this.renderInDisplayStyle(v?.[titleField], v, Array.isArray(value));
|
|
68
|
+
const keyedNode = React.isValidElement(node) ? React.cloneElement(node, { key: `item-${idx}` }) : node;
|
|
69
|
+
return idx === 0 ? [keyedNode] : [<span key={`sep-${idx}`}>, </span>, keyedNode];
|
|
65
70
|
});
|
|
66
71
|
return <Typography.Text {...typographyProps}>{result}</Typography.Text>;
|
|
67
72
|
} else {
|
|
68
|
-
const textContent = (
|
|
69
|
-
<Typography.Text {...typographyProps}>
|
|
70
|
-
{this.renderComponent(normalizeDisplayValue(value, { collectionField: this.context.collectionField }))}
|
|
71
|
-
</Typography.Text>
|
|
72
|
-
);
|
|
73
|
+
const textContent = <Typography.Text {...typographyProps}>{this.renderInDisplayStyle(value)}</Typography.Text>;
|
|
73
74
|
return textContent;
|
|
74
75
|
}
|
|
75
76
|
}
|
|
@@ -83,5 +84,29 @@ DisplayTitleFieldModel.registerFlow({
|
|
|
83
84
|
overflowMode: {
|
|
84
85
|
use: 'overflowMode',
|
|
85
86
|
},
|
|
87
|
+
clickToOpen: {
|
|
88
|
+
title: tExpr('Enable click-to-open'),
|
|
89
|
+
uiMode: { type: 'switch', key: 'clickToOpen' },
|
|
90
|
+
defaultParams: (ctx) => {
|
|
91
|
+
if (ctx.disableFieldClickToOpen) {
|
|
92
|
+
return {
|
|
93
|
+
clickToOpen: false,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
clickToOpen: ctx.collectionField?.isAssociationField?.() || isParentAssociationField(ctx),
|
|
98
|
+
};
|
|
99
|
+
},
|
|
100
|
+
hideInSettings(ctx) {
|
|
101
|
+
return ctx.disableFieldClickToOpen;
|
|
102
|
+
},
|
|
103
|
+
async afterParamsSave(ctx, params) {
|
|
104
|
+
await applyClickToOpenSetting(ctx, params);
|
|
105
|
+
},
|
|
106
|
+
handler(ctx, params) {
|
|
107
|
+
applyClickToOpenProps(ctx, params);
|
|
108
|
+
},
|
|
109
|
+
},
|
|
86
110
|
},
|
|
87
111
|
});
|
|
112
|
+
DisplayTitleFieldModel.registerFlow(openViewFlow);
|
|
@@ -8,9 +8,11 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, expect, it, vi } from 'vitest';
|
|
11
|
-
import { FlowEngine } from '@nocobase/flow-engine';
|
|
12
|
-
import
|
|
11
|
+
import { FlowEngine, FlowModel } from '@nocobase/flow-engine';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { fireEvent, render, screen } from '@testing-library/react';
|
|
13
14
|
import { ClickableFieldModel } from '../ClickableFieldModel';
|
|
15
|
+
import { DisplayTitleFieldModel } from '../DisplayTitleFieldModel';
|
|
14
16
|
import { DisplayTextFieldModel } from '../DisplayTextFieldModel';
|
|
15
17
|
|
|
16
18
|
function createRolesFieldModel(sourceRecord: Record<string, any>) {
|
|
@@ -87,6 +89,133 @@ describe('ClickableFieldModel', () => {
|
|
|
87
89
|
);
|
|
88
90
|
});
|
|
89
91
|
|
|
92
|
+
it('uses the parent association field when the display model is bound to the title field', () => {
|
|
93
|
+
const engine = new FlowEngine();
|
|
94
|
+
engine.registerModels({ ClickableFieldModel });
|
|
95
|
+
|
|
96
|
+
const usersCollection = {
|
|
97
|
+
name: 'users',
|
|
98
|
+
filterTargetKey: 'id',
|
|
99
|
+
};
|
|
100
|
+
const rolesCollection = {
|
|
101
|
+
name: 'roles',
|
|
102
|
+
filterTargetKey: 'name',
|
|
103
|
+
};
|
|
104
|
+
const rolesField = {
|
|
105
|
+
name: 'roles',
|
|
106
|
+
target: 'roles',
|
|
107
|
+
targetKey: 'name',
|
|
108
|
+
type: 'belongsToMany',
|
|
109
|
+
interface: 'm2m',
|
|
110
|
+
collection: usersCollection,
|
|
111
|
+
targetCollection: rolesCollection,
|
|
112
|
+
isAssociationField: () => true,
|
|
113
|
+
};
|
|
114
|
+
const titleField = {
|
|
115
|
+
name: 'title',
|
|
116
|
+
collection: rolesCollection,
|
|
117
|
+
isAssociationField: () => false,
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const parent = engine.createModel<FlowModel>({
|
|
121
|
+
use: FlowModel,
|
|
122
|
+
uid: 'roles-column',
|
|
123
|
+
});
|
|
124
|
+
parent.context.defineProperty('collectionField', { value: rolesField });
|
|
125
|
+
|
|
126
|
+
const model = engine.createModel<ClickableFieldModel>({
|
|
127
|
+
use: ClickableFieldModel,
|
|
128
|
+
uid: 'roles-title-display',
|
|
129
|
+
});
|
|
130
|
+
model.setParent(parent);
|
|
131
|
+
model.context.defineProperty('collectionField', { value: titleField });
|
|
132
|
+
model.context.defineProperty('blockModel', { value: { collection: usersCollection } });
|
|
133
|
+
model.context.defineProperty('record', { value: { id: 1 } });
|
|
134
|
+
const dispatchEvent = vi.spyOn(model, 'dispatchEvent').mockResolvedValue([]);
|
|
135
|
+
const event = { type: 'click' };
|
|
136
|
+
|
|
137
|
+
model.onClick(event, { name: 'admin', title: 'Admin' });
|
|
138
|
+
|
|
139
|
+
expect(dispatchEvent).toHaveBeenCalledWith(
|
|
140
|
+
'click',
|
|
141
|
+
{
|
|
142
|
+
event,
|
|
143
|
+
filterByTk: 'admin',
|
|
144
|
+
collectionName: 'users',
|
|
145
|
+
associationName: 'users.roles',
|
|
146
|
+
sourceId: 1,
|
|
147
|
+
},
|
|
148
|
+
{ debounce: true },
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('renders title display values as links when click-to-open is enabled', () => {
|
|
153
|
+
const engine = new FlowEngine();
|
|
154
|
+
engine.registerModels({ DisplayTitleFieldModel });
|
|
155
|
+
|
|
156
|
+
const usersCollection = {
|
|
157
|
+
name: 'users',
|
|
158
|
+
filterTargetKey: 'id',
|
|
159
|
+
};
|
|
160
|
+
const rolesCollection = {
|
|
161
|
+
name: 'roles',
|
|
162
|
+
filterTargetKey: 'name',
|
|
163
|
+
};
|
|
164
|
+
const rolesField = {
|
|
165
|
+
name: 'roles',
|
|
166
|
+
target: 'roles',
|
|
167
|
+
targetKey: 'name',
|
|
168
|
+
type: 'belongsToMany',
|
|
169
|
+
interface: 'm2m',
|
|
170
|
+
collection: usersCollection,
|
|
171
|
+
targetCollection: rolesCollection,
|
|
172
|
+
isAssociationField: () => true,
|
|
173
|
+
};
|
|
174
|
+
const titleField = {
|
|
175
|
+
name: 'title',
|
|
176
|
+
collection: rolesCollection,
|
|
177
|
+
isAssociationField: () => false,
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
const parent = engine.createModel<FlowModel>({
|
|
181
|
+
use: FlowModel,
|
|
182
|
+
uid: 'roles-title-column',
|
|
183
|
+
});
|
|
184
|
+
parent.context.defineProperty('collectionField', { value: rolesField });
|
|
185
|
+
|
|
186
|
+
const model = engine.createModel<DisplayTitleFieldModel>({
|
|
187
|
+
use: DisplayTitleFieldModel,
|
|
188
|
+
uid: 'roles-title-display-link',
|
|
189
|
+
props: {
|
|
190
|
+
clickToOpen: true,
|
|
191
|
+
titleField: 'name',
|
|
192
|
+
value: { name: 'admin', title: 'Admin' },
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
model.setParent(parent);
|
|
196
|
+
model.context.defineProperty('collectionField', { value: titleField });
|
|
197
|
+
model.context.defineProperty('blockModel', { value: { collection: usersCollection } });
|
|
198
|
+
model.context.defineProperty('record', { value: { id: 1 } });
|
|
199
|
+
const dispatchEvent = vi.spyOn(model, 'dispatchEvent').mockResolvedValue([]);
|
|
200
|
+
|
|
201
|
+
render(React.createElement(React.Fragment, null, model.render()));
|
|
202
|
+
const link = screen.getByText('admin').closest('a');
|
|
203
|
+
expect(link).toBeTruthy();
|
|
204
|
+
|
|
205
|
+
fireEvent.click(link);
|
|
206
|
+
|
|
207
|
+
expect(dispatchEvent).toHaveBeenCalledWith(
|
|
208
|
+
'click',
|
|
209
|
+
expect.objectContaining({
|
|
210
|
+
filterByTk: 'admin',
|
|
211
|
+
collectionName: 'users',
|
|
212
|
+
associationName: 'users.roles',
|
|
213
|
+
sourceId: 1,
|
|
214
|
+
}),
|
|
215
|
+
{ debounce: true },
|
|
216
|
+
);
|
|
217
|
+
});
|
|
218
|
+
|
|
90
219
|
it('renders object title field values by configured target title field', () => {
|
|
91
220
|
const engine = new FlowEngine();
|
|
92
221
|
engine.registerModels({ DisplayTextFieldModel });
|
|
@@ -107,4 +236,53 @@ describe('ClickableFieldModel', () => {
|
|
|
107
236
|
expect(screen.getByText('S-001')).toBeInTheDocument();
|
|
108
237
|
expect(screen.queryByText('Sales')).not.toBeInTheDocument();
|
|
109
238
|
});
|
|
239
|
+
|
|
240
|
+
it('refreshes the parent column when title display click-to-open changes', async () => {
|
|
241
|
+
const engine = new FlowEngine();
|
|
242
|
+
engine.registerModels({ DisplayTitleFieldModel });
|
|
243
|
+
|
|
244
|
+
const rolesField = {
|
|
245
|
+
name: 'roles',
|
|
246
|
+
target: 'roles',
|
|
247
|
+
collection: { name: 'users' },
|
|
248
|
+
targetCollection: { name: 'roles' },
|
|
249
|
+
isAssociationField: () => true,
|
|
250
|
+
getComponentProps: () => ({ titleField: 'name' }),
|
|
251
|
+
};
|
|
252
|
+
const titleField = {
|
|
253
|
+
name: 'title',
|
|
254
|
+
collection: { name: 'roles' },
|
|
255
|
+
isAssociationField: () => false,
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
const parent = engine.createModel<FlowModel>({
|
|
259
|
+
use: FlowModel,
|
|
260
|
+
uid: 'roles-title-column-refresh',
|
|
261
|
+
});
|
|
262
|
+
parent.context.defineProperty('collectionField', { value: rolesField });
|
|
263
|
+
const parentSetProps = vi.spyOn(parent, 'setProps');
|
|
264
|
+
const parentRerender = vi.spyOn(parent, 'rerender').mockResolvedValue(undefined);
|
|
265
|
+
|
|
266
|
+
const model = engine.createModel<DisplayTitleFieldModel>({
|
|
267
|
+
use: DisplayTitleFieldModel,
|
|
268
|
+
uid: 'roles-title-display-refresh',
|
|
269
|
+
});
|
|
270
|
+
model.setParent(parent);
|
|
271
|
+
model.context.defineProperty('collectionField', { value: titleField });
|
|
272
|
+
const modelRerender = vi.spyOn(model, 'rerender').mockResolvedValue(undefined);
|
|
273
|
+
|
|
274
|
+
const clickToOpenStep = model.getFlow('displayFieldSettings').steps.clickToOpen;
|
|
275
|
+
|
|
276
|
+
await clickToOpenStep.afterParamsSave(model.context as any, { clickToOpen: true }, { clickToOpen: false });
|
|
277
|
+
|
|
278
|
+
expect(model.props).toMatchObject({
|
|
279
|
+
clickToOpen: true,
|
|
280
|
+
titleField: 'name',
|
|
281
|
+
});
|
|
282
|
+
expect(modelRerender).toHaveBeenCalled();
|
|
283
|
+
expect(parentSetProps).toHaveBeenCalledWith({
|
|
284
|
+
__displayFieldRefreshKey: expect.any(String),
|
|
285
|
+
});
|
|
286
|
+
expect(parentRerender).toHaveBeenCalled();
|
|
287
|
+
});
|
|
110
288
|
});
|
package/src/utils/globalDeps.ts
CHANGED
|
@@ -16,6 +16,7 @@ import * as formilyCore from '@formily/core';
|
|
|
16
16
|
import * as formilyReact from '@formily/react';
|
|
17
17
|
import * as formilyReactive from '@formily/reactive';
|
|
18
18
|
import * as formilyShared from '@formily/shared';
|
|
19
|
+
import * as nocobaseEvaluators from '@nocobase/evaluators/client';
|
|
19
20
|
import * as nocobaseClientUtils from '@nocobase/utils/client';
|
|
20
21
|
import { dayjs } from '@nocobase/utils/client';
|
|
21
22
|
import * as nocobaseFlowEngine from '@nocobase/flow-engine';
|
|
@@ -71,6 +72,8 @@ export function defineGlobalDeps(requirejs: RequireJS) {
|
|
|
71
72
|
requirejs.define('@nocobase/client-v2', () => nocobaseClientV2);
|
|
72
73
|
requirejs.define('@nocobase/client-v2/client-v2', () => nocobaseClientV2);
|
|
73
74
|
requirejs.define('@nocobase/flow-engine', () => nocobaseFlowEngine);
|
|
75
|
+
requirejs.define('@nocobase/evaluators', () => nocobaseEvaluators);
|
|
76
|
+
requirejs.define('@nocobase/evaluators/client', () => nocobaseEvaluators);
|
|
74
77
|
|
|
75
78
|
// utils
|
|
76
79
|
requirejs.define('ahooks', () => ahooks);
|