@nocobase/flow-engine 2.1.0-alpha.3 → 2.1.0-alpha.30
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/LICENSE +201 -661
- package/README.md +79 -10
- package/lib/JSRunner.d.ts +10 -1
- package/lib/JSRunner.js +50 -5
- package/lib/ViewScopedFlowEngine.js +5 -1
- package/lib/components/FieldModelRenderer.js +2 -2
- package/lib/components/FlowModelRenderer.d.ts +3 -1
- package/lib/components/FlowModelRenderer.js +12 -6
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
- package/lib/components/dnd/gridDragPlanner.js +601 -21
- package/lib/components/dnd/index.d.ts +19 -1
- package/lib/components/dnd/index.js +243 -23
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +68 -10
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
- package/lib/components/subModel/AddSubModelButton.js +27 -1
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +1 -1
- package/lib/components/subModel/utils.js +2 -2
- package/lib/data-source/index.d.ts +73 -0
- package/lib/data-source/index.js +211 -1
- package/lib/executor/FlowExecutor.js +31 -8
- package/lib/flowContext.d.ts +2 -0
- package/lib/flowContext.js +31 -1
- package/lib/flowEngine.d.ts +151 -1
- package/lib/flowEngine.js +389 -15
- package/lib/flowI18n.js +2 -1
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +34 -6
- package/lib/lazy-helper.d.ts +14 -0
- package/lib/lazy-helper.js +71 -0
- package/lib/locale/en-US.json +1 -0
- package/lib/locale/index.d.ts +2 -0
- package/lib/locale/zh-CN.json +1 -0
- package/lib/models/DisplayItemModel.d.ts +1 -1
- package/lib/models/EditableItemModel.d.ts +1 -1
- package/lib/models/FilterableItemModel.d.ts +1 -1
- package/lib/models/flowModel.d.ts +13 -10
- package/lib/models/flowModel.js +78 -18
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +20 -12
- package/lib/runjs-context/snippets/index.js +13 -2
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
- package/lib/scheduler/ModelOperationScheduler.js +3 -2
- package/lib/types.d.ts +47 -1
- package/lib/utils/createCollectionContextMeta.js +6 -2
- package/lib/utils/index.d.ts +2 -2
- package/lib/utils/index.js +4 -0
- package/lib/utils/parsePathnameToViewParams.js +1 -1
- package/lib/utils/runjsTemplateCompat.js +1 -1
- package/lib/utils/runjsValue.js +41 -11
- package/lib/utils/schema-utils.d.ts +7 -1
- package/lib/utils/schema-utils.js +19 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +20 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +20 -3
- package/lib/views/usePage.d.ts +2 -1
- package/lib/views/usePage.js +10 -3
- package/package.json +6 -5
- package/src/JSRunner.ts +68 -4
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +27 -1
- package/src/__tests__/flow-engine.test.ts +166 -0
- package/src/__tests__/flowContext.test.ts +65 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/objectVariable.test.ts +24 -0
- package/src/__tests__/provider.test.tsx +24 -2
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +16 -0
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
- package/src/components/FieldModelRenderer.tsx +2 -1
- package/src/components/FlowModelRenderer.tsx +18 -6
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
- package/src/components/__tests__/dnd.test.ts +44 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
- package/src/components/__tests__/gridDragPlanner.test.ts +512 -3
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +743 -19
- package/src/components/dnd/index.tsx +291 -27
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +88 -10
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +189 -3
- package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
- package/src/components/subModel/AddSubModelButton.tsx +32 -2
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +142 -32
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +1 -1
- package/src/data-source/__tests__/index.test.ts +34 -1
- package/src/data-source/index.ts +258 -2
- package/src/executor/FlowExecutor.ts +34 -9
- package/src/executor/__tests__/flowExecutor.test.ts +57 -0
- package/src/flowContext.ts +37 -3
- package/src/flowEngine.ts +445 -11
- package/src/flowI18n.ts +2 -1
- package/src/flowSettings.ts +40 -6
- package/src/lazy-helper.tsx +57 -0
- package/src/locale/en-US.json +1 -0
- package/src/locale/zh-CN.json +1 -0
- package/src/models/DisplayItemModel.tsx +1 -1
- package/src/models/EditableItemModel.tsx +1 -1
- package/src/models/FilterableItemModel.tsx +1 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
- package/src/models/__tests__/flowModel.test.ts +19 -3
- package/src/models/flowModel.tsx +119 -33
- package/src/provider.tsx +41 -25
- package/src/reactive/__tests__/observer.test.tsx +82 -0
- package/src/reactive/observer.tsx +87 -25
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +22 -12
- package/src/runjs-context/snippets/index.ts +12 -1
- package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
- package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
- package/src/scheduler/ModelOperationScheduler.ts +14 -3
- package/src/types.ts +60 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
- package/src/utils/__tests__/runjsValue.test.ts +11 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/createCollectionContextMeta.ts +6 -2
- package/src/utils/index.ts +2 -1
- package/src/utils/parsePathnameToViewParams.ts +2 -2
- package/src/utils/runjsTemplateCompat.ts +1 -1
- package/src/utils/runjsValue.ts +50 -11
- package/src/utils/schema-utils.ts +30 -1
- package/src/views/FlowView.tsx +11 -1
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +25 -3
- package/src/views/useDrawer.tsx +25 -3
- package/src/views/usePage.tsx +12 -3
|
@@ -0,0 +1,245 @@
|
|
|
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 { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
11
|
+
import { FlowEngine } from '../flowEngine';
|
|
12
|
+
import { ErrorFlowModel, FlowModel } from '../models';
|
|
13
|
+
import type { IFlowModelRepository } from '../types';
|
|
14
|
+
|
|
15
|
+
class MockFlowModelRepository implements IFlowModelRepository {
|
|
16
|
+
findOneResult: any = null;
|
|
17
|
+
save = vi.fn(async (model: FlowModel) => ({ success: true, uid: model.uid }));
|
|
18
|
+
|
|
19
|
+
async findOne() {
|
|
20
|
+
return this.findOneResult ? JSON.parse(JSON.stringify(this.findOneResult)) : null;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async destroy() {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async move() {}
|
|
28
|
+
|
|
29
|
+
async duplicate() {
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('FlowEngine model loaders', () => {
|
|
35
|
+
let engine: FlowEngine;
|
|
36
|
+
let repo: MockFlowModelRepository;
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
engine = new FlowEngine();
|
|
40
|
+
repo = new MockFlowModelRepository();
|
|
41
|
+
engine.setModelRepository(repo);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('resolves explicit and meta-default model trees before synchronous creation', async () => {
|
|
45
|
+
class ParentModel extends FlowModel {}
|
|
46
|
+
class ChildModel extends FlowModel {}
|
|
47
|
+
class DefaultChildModel extends FlowModel {}
|
|
48
|
+
|
|
49
|
+
ParentModel.define({
|
|
50
|
+
createModelOptions: {
|
|
51
|
+
subModels: {
|
|
52
|
+
defaultChild: {
|
|
53
|
+
use: 'DefaultChildModel',
|
|
54
|
+
},
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const parentLoader = vi.fn(async () => ({ ParentModel }));
|
|
60
|
+
const childLoader = vi.fn(async () => ({ ChildModel }));
|
|
61
|
+
const defaultChildLoader = vi.fn(async () => ({ DefaultChildModel }));
|
|
62
|
+
|
|
63
|
+
engine.registerModelLoaders({
|
|
64
|
+
ParentModel: { loader: parentLoader },
|
|
65
|
+
ChildModel: { loader: childLoader },
|
|
66
|
+
DefaultChildModel: { loader: defaultChildLoader },
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const model = await engine.loadOrCreateModel({
|
|
70
|
+
uid: 'parent-model',
|
|
71
|
+
use: 'ParentModel',
|
|
72
|
+
subModels: {
|
|
73
|
+
child: {
|
|
74
|
+
use: 'ChildModel',
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
expect(model).toBeInstanceOf(ParentModel);
|
|
80
|
+
expect(model?.subModels.child).toBeInstanceOf(ChildModel);
|
|
81
|
+
expect(model?.subModels.defaultChild).toBeInstanceOf(DefaultChildModel);
|
|
82
|
+
expect(parentLoader).toHaveBeenCalledTimes(1);
|
|
83
|
+
expect(childLoader).toHaveBeenCalledTimes(1);
|
|
84
|
+
expect(defaultChildLoader).toHaveBeenCalledTimes(1);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('resolves repository-loaded model trees before loadModel creates instances', async () => {
|
|
88
|
+
class RepoRootModel extends FlowModel {}
|
|
89
|
+
class RepoChildModel extends FlowModel {}
|
|
90
|
+
|
|
91
|
+
const rootLoader = vi.fn(async () => ({ RepoRootModel }));
|
|
92
|
+
const childLoader = vi.fn(async () => ({ RepoChildModel }));
|
|
93
|
+
|
|
94
|
+
engine.registerModelLoaders({
|
|
95
|
+
RepoRootModel: { loader: rootLoader },
|
|
96
|
+
RepoChildModel: { loader: childLoader },
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
repo.findOneResult = {
|
|
100
|
+
uid: 'repo-root',
|
|
101
|
+
use: 'RepoRootModel',
|
|
102
|
+
subModels: {
|
|
103
|
+
child: {
|
|
104
|
+
use: 'RepoChildModel',
|
|
105
|
+
},
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
const model = await engine.loadModel({ uid: 'repo-root' });
|
|
110
|
+
|
|
111
|
+
expect(model).toBeInstanceOf(RepoRootModel);
|
|
112
|
+
expect(model?.subModels.child).toBeInstanceOf(RepoChildModel);
|
|
113
|
+
expect(rootLoader).toHaveBeenCalledTimes(1);
|
|
114
|
+
expect(childLoader).toHaveBeenCalledTimes(1);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('supports async model creation and async getters', async () => {
|
|
118
|
+
class AsyncRootModel extends FlowModel {}
|
|
119
|
+
class AsyncChildModel extends FlowModel {}
|
|
120
|
+
|
|
121
|
+
const rootLoader = vi.fn(async () => ({ AsyncRootModel }));
|
|
122
|
+
const childLoader = vi.fn(async () => ({ AsyncChildModel }));
|
|
123
|
+
|
|
124
|
+
engine.registerModelLoaders({
|
|
125
|
+
AsyncRootModel: { loader: rootLoader },
|
|
126
|
+
AsyncChildModel: { loader: childLoader },
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
const rootClass = await engine.getModelClassAsync('AsyncRootModel');
|
|
130
|
+
const classes = await engine.getModelClassesAsync();
|
|
131
|
+
const model = await engine.createModelAsync({
|
|
132
|
+
uid: 'async-root',
|
|
133
|
+
use: 'AsyncRootModel',
|
|
134
|
+
subModels: {
|
|
135
|
+
child: {
|
|
136
|
+
use: 'AsyncChildModel',
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
expect(rootClass).toBe(AsyncRootModel);
|
|
142
|
+
expect(classes.get('AsyncRootModel')).toBe(AsyncRootModel);
|
|
143
|
+
expect(classes.get('AsyncChildModel')).toBe(AsyncChildModel);
|
|
144
|
+
expect(model).toBeInstanceOf(AsyncRootModel);
|
|
145
|
+
expect(model.subModels.child).toBeInstanceOf(AsyncChildModel);
|
|
146
|
+
expect(rootLoader).toHaveBeenCalledTimes(1);
|
|
147
|
+
expect(childLoader).toHaveBeenCalledTimes(1);
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
it('createModelAsync degrades unresolved loader failures to ErrorFlowModel', async () => {
|
|
151
|
+
const invalidLoader = vi.fn(async () => ({ notAModel: {} }));
|
|
152
|
+
|
|
153
|
+
engine.registerModelLoaders({
|
|
154
|
+
BrokenRootModel: { loader: invalidLoader as any },
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
const model = await engine.createModelAsync({
|
|
158
|
+
uid: 'broken-root',
|
|
159
|
+
use: 'BrokenRootModel',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(model).toBeInstanceOf(ErrorFlowModel);
|
|
163
|
+
expect(invalidLoader).toHaveBeenCalledTimes(1);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
it('keeps loader resolution idempotent across resolveModelTree and flow settings preload', async () => {
|
|
167
|
+
class RuntimeResolvedModel extends FlowModel {}
|
|
168
|
+
class DesignResolvedModel extends FlowModel {}
|
|
169
|
+
|
|
170
|
+
const runtimeLoader = vi.fn(async () => ({ RuntimeResolvedModel }));
|
|
171
|
+
const designLoader = vi.fn(async () => ({ DesignResolvedModel }));
|
|
172
|
+
|
|
173
|
+
engine.registerModelLoaders({
|
|
174
|
+
RuntimeResolvedModel: { loader: runtimeLoader },
|
|
175
|
+
DesignResolvedModel: { loader: designLoader },
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
await engine.resolveModelTree({
|
|
179
|
+
use: 'RuntimeResolvedModel',
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
const firstPreload = await engine.preloadModelLoaders();
|
|
183
|
+
const secondPreload = await engine.preloadModelLoaders();
|
|
184
|
+
|
|
185
|
+
expect(runtimeLoader).toHaveBeenCalledTimes(1);
|
|
186
|
+
expect(designLoader).toHaveBeenCalledTimes(1);
|
|
187
|
+
expect(firstPreload.loaded).toContain('DesignResolvedModel');
|
|
188
|
+
expect(firstPreload.loaded).not.toContain('RuntimeResolvedModel');
|
|
189
|
+
expect(secondPreload.loaded).toHaveLength(0);
|
|
190
|
+
expect(secondPreload.failed).toHaveLength(0);
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('picks up newly registered loaders after preload has already completed', async () => {
|
|
194
|
+
class FirstModel extends FlowModel {}
|
|
195
|
+
class SecondModel extends FlowModel {}
|
|
196
|
+
|
|
197
|
+
const firstLoader = vi.fn(async () => ({ FirstModel }));
|
|
198
|
+
const secondLoader = vi.fn(async () => ({ SecondModel }));
|
|
199
|
+
|
|
200
|
+
engine.registerModelLoaders({
|
|
201
|
+
FirstModel: { loader: firstLoader },
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await engine.preloadModelLoaders();
|
|
205
|
+
expect(firstLoader).toHaveBeenCalledTimes(1);
|
|
206
|
+
expect(engine.getModelClass('FirstModel')).toBe(FirstModel);
|
|
207
|
+
|
|
208
|
+
engine.registerModelLoaders({
|
|
209
|
+
SecondModel: { loader: secondLoader },
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
const result = await engine.preloadModelLoaders();
|
|
213
|
+
|
|
214
|
+
expect(secondLoader).toHaveBeenCalledTimes(1);
|
|
215
|
+
expect(result.loaded).toContain('SecondModel');
|
|
216
|
+
expect(engine.getModelClass('SecondModel')).toBe(SecondModel);
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
it('degrades unresolved loader failures to ErrorFlowModel instead of crashing runtime creation', async () => {
|
|
220
|
+
class ParentModel extends FlowModel {}
|
|
221
|
+
|
|
222
|
+
const parentLoader = vi.fn(async () => ({ ParentModel }));
|
|
223
|
+
const invalidChildLoader = vi.fn(async () => ({ notAModel: {} }));
|
|
224
|
+
|
|
225
|
+
engine.registerModelLoaders({
|
|
226
|
+
ParentModel: { loader: parentLoader },
|
|
227
|
+
BrokenChildModel: { loader: invalidChildLoader as any },
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const model = await engine.loadOrCreateModel({
|
|
231
|
+
uid: 'parent-with-broken-child',
|
|
232
|
+
use: 'ParentModel',
|
|
233
|
+
subModels: {
|
|
234
|
+
child: {
|
|
235
|
+
use: 'BrokenChildModel',
|
|
236
|
+
},
|
|
237
|
+
},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
expect(model).toBeInstanceOf(ParentModel);
|
|
241
|
+
expect(model?.subModels.child).toBeInstanceOf(ErrorFlowModel);
|
|
242
|
+
expect(parentLoader).toHaveBeenCalledTimes(1);
|
|
243
|
+
expect(invalidChildLoader).toHaveBeenCalledTimes(1);
|
|
244
|
+
});
|
|
245
|
+
});
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { createForm } from '@formily/core';
|
|
13
|
+
import { createSchemaField, FormProvider } from '@formily/react';
|
|
14
|
+
import { render, screen } from '@testing-library/react';
|
|
11
15
|
import { FlowSettings } from '../flowSettings';
|
|
12
16
|
import { DefaultSettingsIcon } from '../components/settings/wrappers/contextual/DefaultSettingsIcon';
|
|
13
17
|
import { FlowModel } from '../models';
|
|
@@ -142,10 +146,10 @@ describe('FlowSettings', () => {
|
|
|
142
146
|
expect(settingsItem?.sort).toBe(0);
|
|
143
147
|
});
|
|
144
148
|
|
|
145
|
-
test('should set up observable properties', () => {
|
|
149
|
+
test('should set up observable properties', async () => {
|
|
146
150
|
// Test that enabled property is reactive
|
|
147
151
|
const initialEnabled = flowSettings.enabled;
|
|
148
|
-
flowSettings.enable();
|
|
152
|
+
await flowSettings.enable();
|
|
149
153
|
expect(flowSettings.enabled).not.toBe(initialEnabled);
|
|
150
154
|
expect(flowSettings.enabled).toBe(true);
|
|
151
155
|
});
|
|
@@ -186,6 +190,43 @@ describe('FlowSettings', () => {
|
|
|
186
190
|
flowSettings.registerComponents({});
|
|
187
191
|
expect(Object.keys(flowSettings.components)).toHaveLength(0);
|
|
188
192
|
});
|
|
193
|
+
|
|
194
|
+
test('should register component loaders and load component on render', async () => {
|
|
195
|
+
const loader = vi.fn(async () => ({
|
|
196
|
+
default: () => React.createElement('div', null, 'Lazy Flow Settings Component'),
|
|
197
|
+
}));
|
|
198
|
+
|
|
199
|
+
flowSettings.registerComponentLoaders({
|
|
200
|
+
DemoFlowSettingsLazyField: loader,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(loader).not.toHaveBeenCalled();
|
|
204
|
+
|
|
205
|
+
const SchemaField = createSchemaField();
|
|
206
|
+
const form = createForm();
|
|
207
|
+
|
|
208
|
+
render(
|
|
209
|
+
React.createElement(
|
|
210
|
+
FormProvider,
|
|
211
|
+
{ form },
|
|
212
|
+
React.createElement(SchemaField, {
|
|
213
|
+
schema: {
|
|
214
|
+
type: 'object',
|
|
215
|
+
properties: {
|
|
216
|
+
demo: {
|
|
217
|
+
type: 'void',
|
|
218
|
+
'x-component': 'DemoFlowSettingsLazyField',
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
components: flowSettings.components,
|
|
223
|
+
}),
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
expect(await screen.findByText('Lazy Flow Settings Component')).toBeInTheDocument();
|
|
228
|
+
expect(loader).toHaveBeenCalledTimes(1);
|
|
229
|
+
});
|
|
189
230
|
});
|
|
190
231
|
|
|
191
232
|
describe('Scope Registration', () => {
|
|
@@ -228,30 +269,68 @@ describe('FlowSettings', () => {
|
|
|
228
269
|
});
|
|
229
270
|
|
|
230
271
|
describe('Enable/Disable Functionality', () => {
|
|
231
|
-
test('should enable flow settings', () => {
|
|
272
|
+
test('should enable flow settings', async () => {
|
|
232
273
|
expect(flowSettings.enabled).toBe(false);
|
|
233
274
|
|
|
234
|
-
flowSettings.enable();
|
|
275
|
+
await flowSettings.enable();
|
|
235
276
|
|
|
236
277
|
expect(flowSettings.enabled).toBe(true);
|
|
237
278
|
});
|
|
238
279
|
|
|
239
|
-
test('should
|
|
240
|
-
|
|
280
|
+
test('should preload model loaders before enabling flow settings', async () => {
|
|
281
|
+
const preloadSpy = vi.spyOn(engine, 'preloadModelLoaders').mockResolvedValue({
|
|
282
|
+
requested: [],
|
|
283
|
+
loaded: [],
|
|
284
|
+
failed: [],
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await flowSettings.enable();
|
|
288
|
+
|
|
289
|
+
expect(preloadSpy).toHaveBeenCalledTimes(1);
|
|
241
290
|
expect(flowSettings.enabled).toBe(true);
|
|
291
|
+
});
|
|
242
292
|
|
|
243
|
-
|
|
293
|
+
test('should preload model loaders before force enabling flow settings', async () => {
|
|
294
|
+
const preloadSpy = vi.spyOn(engine, 'preloadModelLoaders').mockResolvedValue({
|
|
295
|
+
requested: [],
|
|
296
|
+
loaded: [],
|
|
297
|
+
failed: [],
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
await flowSettings.forceEnable();
|
|
301
|
+
|
|
302
|
+
expect(preloadSpy).toHaveBeenCalledTimes(1);
|
|
303
|
+
expect(flowSettings.enabled).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('should disable flow settings', async () => {
|
|
307
|
+
await flowSettings.enable();
|
|
308
|
+
expect(flowSettings.enabled).toBe(true);
|
|
309
|
+
|
|
310
|
+
await flowSettings.disable();
|
|
244
311
|
|
|
245
312
|
expect(flowSettings.enabled).toBe(false);
|
|
246
313
|
});
|
|
247
314
|
|
|
248
|
-
test('should handle multiple enable/disable calls', () => {
|
|
249
|
-
flowSettings.enable();
|
|
250
|
-
flowSettings.enable();
|
|
315
|
+
test('should handle multiple enable/disable calls', async () => {
|
|
316
|
+
await flowSettings.enable();
|
|
317
|
+
await flowSettings.enable();
|
|
251
318
|
expect(flowSettings.enabled).toBe(true);
|
|
252
319
|
|
|
253
|
-
flowSettings.disable();
|
|
254
|
-
flowSettings.disable();
|
|
320
|
+
await flowSettings.disable();
|
|
321
|
+
await flowSettings.disable();
|
|
322
|
+
expect(flowSettings.enabled).toBe(false);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('forceDisable should clear force-enabled state and disable flow settings', async () => {
|
|
326
|
+
await flowSettings.forceEnable();
|
|
327
|
+
expect(flowSettings.enabled).toBe(true);
|
|
328
|
+
|
|
329
|
+
await flowSettings.forceDisable();
|
|
330
|
+
|
|
331
|
+
expect(flowSettings.enabled).toBe(false);
|
|
332
|
+
|
|
333
|
+
await flowSettings.disable();
|
|
255
334
|
expect(flowSettings.enabled).toBe(false);
|
|
256
335
|
});
|
|
257
336
|
});
|
|
@@ -512,7 +591,7 @@ describe('FlowSettings', () => {
|
|
|
512
591
|
});
|
|
513
592
|
|
|
514
593
|
describe('Complex Integration Scenarios', () => {
|
|
515
|
-
test('should maintain state consistency during multiple operations', () => {
|
|
594
|
+
test('should maintain state consistency during multiple operations', async () => {
|
|
516
595
|
// Initialize with components and scopes
|
|
517
596
|
const TestComponent = () => 'TestComponent';
|
|
518
597
|
const testScope = () => 'testScope';
|
|
@@ -528,7 +607,7 @@ describe('FlowSettings', () => {
|
|
|
528
607
|
});
|
|
529
608
|
|
|
530
609
|
// Enable/disable
|
|
531
|
-
flowSettings.enable();
|
|
610
|
+
await flowSettings.enable();
|
|
532
611
|
expect(flowSettings.enabled).toBe(true);
|
|
533
612
|
|
|
534
613
|
// Verify all state is maintained
|
|
@@ -536,7 +615,7 @@ describe('FlowSettings', () => {
|
|
|
536
615
|
expect(flowSettings.scopes.testScope).toBe(testScope);
|
|
537
616
|
expect(flowSettings.getToolbarItems().find((item) => item.key === 'integration-test')).toBeDefined();
|
|
538
617
|
|
|
539
|
-
flowSettings.disable();
|
|
618
|
+
await flowSettings.disable();
|
|
540
619
|
expect(flowSettings.enabled).toBe(false);
|
|
541
620
|
|
|
542
621
|
// State should still be maintained after disable
|
|
@@ -26,6 +26,7 @@ function setupEngineWithCollections() {
|
|
|
26
26
|
fields: [
|
|
27
27
|
{ name: 'id', type: 'integer', interface: 'number' },
|
|
28
28
|
{ name: 'name', type: 'string', interface: 'text' },
|
|
29
|
+
{ name: 'rawUserPayload', type: 'json', filterable: true },
|
|
29
30
|
],
|
|
30
31
|
});
|
|
31
32
|
ds.addCollection({
|
|
@@ -41,6 +42,8 @@ function setupEngineWithCollections() {
|
|
|
41
42
|
filterTargetKey: 'id',
|
|
42
43
|
fields: [
|
|
43
44
|
{ name: 'title', type: 'string', interface: 'text' },
|
|
45
|
+
{ name: 'internalName', type: 'string', interface: 'text' },
|
|
46
|
+
{ name: 'rawPostPayload', type: 'json', filterable: true },
|
|
44
47
|
{ name: 'author', type: 'belongsTo', target: 'users', interface: 'm2o' },
|
|
45
48
|
{ name: 'tags', type: 'belongsToMany', target: 'tags', interface: 'm2m' },
|
|
46
49
|
],
|
|
@@ -91,6 +94,27 @@ describe('objectVariable utilities', () => {
|
|
|
91
94
|
});
|
|
92
95
|
});
|
|
93
96
|
|
|
97
|
+
it('createAssociationAwareObjectMetaFactory should hide fields without interface from object variable meta', async () => {
|
|
98
|
+
const { collection } = setupEngineWithCollections();
|
|
99
|
+
const obj = { title: 'hello', internalName: 'internal', rawPostPayload: { secret: true }, author: 1 };
|
|
100
|
+
const metaFactory = createAssociationAwareObjectMetaFactory(
|
|
101
|
+
() => collection,
|
|
102
|
+
'Current object',
|
|
103
|
+
() => obj,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const meta = await metaFactory();
|
|
107
|
+
const props = await (meta?.properties as any)?.();
|
|
108
|
+
const authorFields = await props?.author?.properties?.();
|
|
109
|
+
|
|
110
|
+
expect(props).toHaveProperty('title');
|
|
111
|
+
expect(props).toHaveProperty('internalName');
|
|
112
|
+
expect(props).toHaveProperty('author');
|
|
113
|
+
expect(props).not.toHaveProperty('rawPostPayload');
|
|
114
|
+
expect(authorFields).toHaveProperty('name');
|
|
115
|
+
expect(authorFields).not.toHaveProperty('rawUserPayload');
|
|
116
|
+
});
|
|
117
|
+
|
|
94
118
|
it('integrates with FlowContext.resolveJsonTemplate to call variables:resolve with flattened contextParams', async () => {
|
|
95
119
|
const { engine, collection } = setupEngineWithCollections();
|
|
96
120
|
const obj = { author: 1 };
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { describe, expect, it } from 'vitest';
|
|
12
|
-
import { renderHook } from '@testing-library/react';
|
|
12
|
+
import { render, renderHook } from '@testing-library/react';
|
|
13
|
+
import { App, ConfigProvider, theme } from 'antd';
|
|
13
14
|
import { FlowEngine } from '../flowEngine';
|
|
14
|
-
import { FlowEngineProvider, useFlowEngine } from '../provider';
|
|
15
|
+
import { FlowEngineGlobalsContextProvider, FlowEngineProvider, useFlowEngine } from '../provider';
|
|
15
16
|
|
|
16
17
|
describe('FlowEngineProvider/useFlowEngine', () => {
|
|
17
18
|
it('returns engine within provider', () => {
|
|
@@ -20,4 +21,25 @@ describe('FlowEngineProvider/useFlowEngine', () => {
|
|
|
20
21
|
const { result } = renderHook(() => useFlowEngine(), { wrapper });
|
|
21
22
|
expect(result.current).toBe(engine);
|
|
22
23
|
});
|
|
24
|
+
|
|
25
|
+
it('registers isDarkTheme within globals provider before first child render', () => {
|
|
26
|
+
const engine = new FlowEngine();
|
|
27
|
+
const reads: boolean[] = [];
|
|
28
|
+
const Reader = () => {
|
|
29
|
+
reads.push(engine.context.isDarkTheme);
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
const wrapper = ({ children }: any) => (
|
|
33
|
+
<ConfigProvider theme={{ algorithm: theme.darkAlgorithm }}>
|
|
34
|
+
<App>
|
|
35
|
+
<FlowEngineProvider engine={engine}>
|
|
36
|
+
<FlowEngineGlobalsContextProvider>{children}</FlowEngineGlobalsContextProvider>
|
|
37
|
+
</FlowEngineProvider>
|
|
38
|
+
</App>
|
|
39
|
+
</ConfigProvider>
|
|
40
|
+
);
|
|
41
|
+
render(<Reader />, { wrapper });
|
|
42
|
+
expect(reads[0]).toBe(true);
|
|
43
|
+
expect(engine.context.isDarkTheme).toBe(true);
|
|
44
|
+
});
|
|
23
45
|
});
|
|
@@ -14,7 +14,7 @@ import { FlowEngine } from '../flowEngine';
|
|
|
14
14
|
import { FlowModel, ModelRenderMode } from '../models/flowModel';
|
|
15
15
|
|
|
16
16
|
describe('FlowModel.renderHiddenInConfig', () => {
|
|
17
|
-
it('renders via renderHiddenInConfig when hidden and config enabled (React element mode, mounted)', () => {
|
|
17
|
+
it('renders via renderHiddenInConfig when hidden and config enabled (React element mode, mounted)', async () => {
|
|
18
18
|
class ElemModel extends FlowModel {
|
|
19
19
|
render() {
|
|
20
20
|
return <div data-testid="content">Content</div>;
|
|
@@ -28,14 +28,14 @@ describe('FlowModel.renderHiddenInConfig', () => {
|
|
|
28
28
|
const model = new ElemModel({ uid: 'elem-1', flowEngine: engine });
|
|
29
29
|
|
|
30
30
|
// runtime hidden => mounted result should be empty (no content/hidden)
|
|
31
|
-
engine.flowSettings.disable();
|
|
31
|
+
await engine.flowSettings.disable();
|
|
32
32
|
model.setHidden(true);
|
|
33
33
|
const { container, unmount, rerender } = render(model.render() as React.ReactElement);
|
|
34
34
|
expect(screen.queryByTestId('content')).toBeNull();
|
|
35
35
|
expect(screen.queryByTestId('hidden')).toBeNull();
|
|
36
36
|
|
|
37
37
|
// config enabled + hidden => should show renderHiddenInConfig result
|
|
38
|
-
engine.flowSettings.enable();
|
|
38
|
+
await engine.flowSettings.enable();
|
|
39
39
|
rerender(model.render() as React.ReactElement);
|
|
40
40
|
expect(screen.getByTestId('hidden').textContent).toBe('HiddenViaAPI');
|
|
41
41
|
|
|
@@ -46,7 +46,7 @@ describe('FlowModel.renderHiddenInConfig', () => {
|
|
|
46
46
|
unmount();
|
|
47
47
|
cleanup();
|
|
48
48
|
});
|
|
49
|
-
it('returns a render function when hidden and config enabled (RenderFunction mode)', () => {
|
|
49
|
+
it('returns a render function when hidden and config enabled (RenderFunction mode)', async () => {
|
|
50
50
|
class FuncModel extends FlowModel {
|
|
51
51
|
static override renderMode = ModelRenderMode.RenderFunction;
|
|
52
52
|
render() {
|
|
@@ -63,13 +63,13 @@ describe('FlowModel.renderHiddenInConfig', () => {
|
|
|
63
63
|
const model = engine.createModel({ use: 'FuncModel' }) as FuncModel;
|
|
64
64
|
|
|
65
65
|
// runtime hidden => null
|
|
66
|
-
engine.flowSettings.disable();
|
|
66
|
+
await engine.flowSettings.disable();
|
|
67
67
|
model.setHidden(true);
|
|
68
68
|
const runtimeHidden = model.render();
|
|
69
69
|
expect(runtimeHidden).toBeNull();
|
|
70
70
|
|
|
71
71
|
// config enabled + hidden => renderHiddenInConfig (function)
|
|
72
|
-
engine.flowSettings.enable();
|
|
72
|
+
await engine.flowSettings.enable();
|
|
73
73
|
const cfgHidden = model.render();
|
|
74
74
|
expect(typeof cfgHidden).toBe('function');
|
|
75
75
|
const cellNode = (cfgHidden as any)();
|
|
@@ -29,11 +29,16 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
29
29
|
expect(RunJSContextRegistry['resolve']('v1' as any, '*')).toBeTruthy();
|
|
30
30
|
});
|
|
31
31
|
|
|
32
|
+
it('should register v2 mapping', () => {
|
|
33
|
+
expect(RunJSContextRegistry['resolve']('v2' as any, '*')).toBeTruthy();
|
|
34
|
+
});
|
|
35
|
+
|
|
32
36
|
it('should register all context types', () => {
|
|
33
37
|
const contextTypes = [
|
|
34
38
|
'JSBlockModel',
|
|
35
39
|
'JSFieldModel',
|
|
36
40
|
'JSItemModel',
|
|
41
|
+
'JSItemActionModel',
|
|
37
42
|
'JSColumnModel',
|
|
38
43
|
'FormJSFieldItemModel',
|
|
39
44
|
'JSRecordActionModel',
|
|
@@ -44,12 +49,22 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
44
49
|
const ctor = RunJSContextRegistry['resolve']('v1' as any, modelClass);
|
|
45
50
|
expect(ctor).toBeTruthy();
|
|
46
51
|
});
|
|
52
|
+
|
|
53
|
+
contextTypes.forEach((modelClass) => {
|
|
54
|
+
const ctor = RunJSContextRegistry['resolve']('v2' as any, modelClass);
|
|
55
|
+
expect(ctor).toBeTruthy();
|
|
56
|
+
});
|
|
47
57
|
});
|
|
48
58
|
|
|
49
59
|
it('should expose scene metadata for contexts', () => {
|
|
50
60
|
expect(getRunJSScenesForModel('JSBlockModel', 'v1')).toEqual(['block']);
|
|
51
61
|
expect(getRunJSScenesForModel('JSFieldModel', 'v1')).toEqual(['detail']);
|
|
62
|
+
expect(getRunJSScenesForModel('JSItemActionModel', 'v1')).toEqual(['table']);
|
|
63
|
+
expect(getRunJSScenesForModel('JSBlockModel', 'v2')).toEqual(['block']);
|
|
64
|
+
expect(getRunJSScenesForModel('JSFieldModel', 'v2')).toEqual(['detail']);
|
|
65
|
+
expect(getRunJSScenesForModel('JSItemActionModel', 'v2')).toEqual(['table']);
|
|
52
66
|
expect(getRunJSScenesForModel('UnknownModel', 'v1')).toEqual([]);
|
|
67
|
+
expect(getRunJSScenesForModel('UnknownModel', 'v2')).toEqual([]);
|
|
53
68
|
});
|
|
54
69
|
|
|
55
70
|
it('should only execute once (idempotent)', async () => {
|
|
@@ -175,6 +190,7 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
175
190
|
const ctx = new FlowContext();
|
|
176
191
|
ctx.defineProperty('model', { value: { constructor: { name: 'JSColumnModel' } } });
|
|
177
192
|
expect(getRunJSScenesForContext(ctx as any, { version: 'v1' })).toEqual(['table']);
|
|
193
|
+
expect(getRunJSScenesForContext(ctx as any, { version: 'v2' })).toEqual(['table']);
|
|
178
194
|
});
|
|
179
195
|
|
|
180
196
|
it('JSBlockModel context should have element property in doc', () => {
|
|
@@ -186,6 +186,7 @@ describe('RunJS Context Runtime Behavior', () => {
|
|
|
186
186
|
'JSBlockModel',
|
|
187
187
|
'JSFieldModel',
|
|
188
188
|
'JSItemModel',
|
|
189
|
+
'JSItemActionModel',
|
|
189
190
|
'JSColumnModel',
|
|
190
191
|
'FormJSFieldItemModel',
|
|
191
192
|
'JSRecordActionModel',
|
|
@@ -237,6 +238,7 @@ describe('RunJS Context Runtime Behavior', () => {
|
|
|
237
238
|
'JSBlockModel',
|
|
238
239
|
'JSFieldModel',
|
|
239
240
|
'JSItemModel',
|
|
241
|
+
'JSItemActionModel',
|
|
240
242
|
'JSColumnModel',
|
|
241
243
|
'FormJSFieldItemModel',
|
|
242
244
|
'JSRecordActionModel',
|
|
@@ -36,6 +36,29 @@ describe('ctx.runjs preprocessTemplates default', () => {
|
|
|
36
36
|
expect(r.value).toBe('{{ctx.user.id}}');
|
|
37
37
|
});
|
|
38
38
|
|
|
39
|
+
it('disables template preprocess by default for version v2', async () => {
|
|
40
|
+
const engine = new FlowEngine();
|
|
41
|
+
const ctx = engine.context as any;
|
|
42
|
+
ctx.defineProperty('user', { value: { id: 123 } });
|
|
43
|
+
|
|
44
|
+
const r = await ctx.runjs('return "{{ctx.user.id}}";', undefined, { version: 'v2' });
|
|
45
|
+
expect(r.success).toBe(true);
|
|
46
|
+
expect(r.value).toBe('{{ctx.user.id}}');
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('keeps explicit preprocessTemplates override higher priority than version', async () => {
|
|
50
|
+
const engine = new FlowEngine();
|
|
51
|
+
const ctx = engine.context as any;
|
|
52
|
+
ctx.defineProperty('user', { value: { id: 123 } });
|
|
53
|
+
|
|
54
|
+
const r = await ctx.runjs('return "{{ctx.user.id}}";', undefined, {
|
|
55
|
+
version: 'v2',
|
|
56
|
+
preprocessTemplates: true,
|
|
57
|
+
});
|
|
58
|
+
expect(r.success).toBe(true);
|
|
59
|
+
expect(r.value).toBe('123');
|
|
60
|
+
});
|
|
61
|
+
|
|
39
62
|
it('does not double-preprocess already prepared code', async () => {
|
|
40
63
|
const engine = new FlowEngine();
|
|
41
64
|
const ctx = engine.context as any;
|
|
@@ -112,6 +112,27 @@ describe('RunJS Snippets', () => {
|
|
|
112
112
|
expect(multiScene?.scenes).toEqual(expect.arrayContaining(['detail', 'table']));
|
|
113
113
|
expect(multiScene?.groups).toEqual(expect.arrayContaining(['scene/detail', 'scene/table']));
|
|
114
114
|
});
|
|
115
|
+
|
|
116
|
+
it('should expose new style snippets for matching contexts', async () => {
|
|
117
|
+
const tableSnippets = await listSnippetsForContext('JSColumnRunJSContext', 'v1', 'zh-CN');
|
|
118
|
+
const fieldSnippets = await listSnippetsForContext('FormJSFieldItemRunJSContext', 'v1', 'zh-CN');
|
|
119
|
+
const detailEventSnippets = await listSnippetsForContext('DetailsItemModel', 'v1', 'zh-CN');
|
|
120
|
+
const tableEventSnippets = await listSnippetsForContext('TableColumnModel', 'v1', 'zh-CN');
|
|
121
|
+
|
|
122
|
+
const tableStyle = tableSnippets.find((s) => s.ref === 'scene/table/set-cell-style');
|
|
123
|
+
expect(tableStyle?.name).toBe('表格字段样式设置');
|
|
124
|
+
expect(tableStyle?.body).toContain('ctx.model.props.onCell');
|
|
125
|
+
expect(tableStyle?.scenes).toEqual(['tableFieldEvent']);
|
|
126
|
+
|
|
127
|
+
const fieldStyle = fieldSnippets.find((s) => s.ref === 'scene/detail/set-field-style');
|
|
128
|
+
expect(fieldStyle?.name).toBe('设置表单项/详情项样式');
|
|
129
|
+
expect(fieldStyle?.body).toContain('ctx.model.props.style');
|
|
130
|
+
expect(fieldStyle?.scenes).toEqual(expect.arrayContaining(['detailFieldEvent', 'formFieldEvent']));
|
|
131
|
+
expect(fieldStyle?.groups).toEqual(expect.arrayContaining(['scene/detail', 'scene/form']));
|
|
132
|
+
|
|
133
|
+
expect(detailEventSnippets.some((s) => s.ref === 'scene/detail/set-field-style')).toBe(true);
|
|
134
|
+
expect(tableEventSnippets.some((s) => s.ref === 'scene/table/set-cell-style')).toBe(true);
|
|
135
|
+
});
|
|
115
136
|
});
|
|
116
137
|
|
|
117
138
|
describe('New snippets', () => {
|