@nocobase/flow-engine 2.1.0-alpha.4 → 2.1.0-alpha.40
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/FormItem.d.ts +6 -0
- package/lib/components/FormItem.js +11 -3
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
- package/lib/components/dnd/gridDragPlanner.js +613 -21
- package/lib/components/dnd/index.d.ts +31 -2
- package/lib/components/dnd/index.js +244 -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/LazyDropdown.js +96 -39
- 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 +9 -3
- package/lib/components/variables/VariableHybridInput.d.ts +27 -0
- package/lib/components/variables/VariableHybridInput.js +499 -0
- package/lib/components/variables/index.d.ts +2 -0
- package/lib/components/variables/index.js +3 -0
- package/lib/data-source/index.d.ts +75 -0
- package/lib/data-source/index.js +247 -5
- package/lib/executor/FlowExecutor.js +32 -9
- package/lib/flow-registry/DetachedFlowRegistry.d.ts +21 -0
- package/lib/flow-registry/DetachedFlowRegistry.js +80 -0
- package/lib/flow-registry/index.d.ts +1 -0
- package/lib/flow-registry/index.js +3 -1
- package/lib/flowContext.d.ts +3 -0
- package/lib/flowContext.js +43 -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/index.d.ts +2 -0
- package/lib/index.js +7 -0
- 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 +50 -2
- package/lib/types.js +1 -0
- package/lib/utils/createCollectionContextMeta.js +6 -2
- package/lib/utils/index.d.ts +3 -2
- package/lib/utils/index.js +7 -0
- package/lib/utils/parsePathnameToViewParams.js +1 -1
- package/lib/utils/randomId.d.ts +39 -0
- package/lib/utils/randomId.js +45 -0
- 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/FlowView.js +11 -1
- package/lib/views/PageComponent.js +8 -6
- package/lib/views/ViewNavigation.js +6 -2
- 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 +5 -11
- package/lib/views/usePage.js +302 -144
- 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 +82 -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/FormItem.tsx +7 -1
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
- package/src/components/__tests__/FormItem.test.tsx +25 -0
- 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 +558 -3
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +758 -19
- package/src/components/dnd/index.tsx +305 -28
- 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/LazyDropdown.tsx +107 -43
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +319 -36
- package/src/components/subModel/__tests__/utils.test.ts +24 -0
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +7 -1
- package/src/components/variables/VariableHybridInput.tsx +531 -0
- package/src/components/variables/index.ts +2 -0
- package/src/data-source/__tests__/collection.test.ts +41 -2
- package/src/data-source/__tests__/index.test.ts +68 -1
- package/src/data-source/index.ts +304 -6
- package/src/executor/FlowExecutor.ts +35 -10
- package/src/executor/__tests__/flowExecutor.test.ts +57 -0
- package/src/flow-registry/DetachedFlowRegistry.ts +46 -0
- package/src/flow-registry/__tests__/detachedFlowRegistry.test.ts +47 -0
- package/src/flow-registry/index.ts +1 -0
- package/src/flowContext.ts +47 -3
- package/src/flowEngine.ts +445 -11
- package/src/flowI18n.ts +2 -1
- package/src/flowSettings.ts +40 -6
- package/src/index.ts +2 -0
- 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 +47 -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 +62 -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 +5 -1
- package/src/utils/parsePathnameToViewParams.ts +2 -2
- package/src/utils/randomId.ts +48 -0
- 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 +22 -2
- package/src/views/PageComponent.tsx +7 -4
- package/src/views/ViewNavigation.ts +6 -2
- package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
- 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 +365 -179
|
@@ -0,0 +1,57 @@
|
|
|
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, { lazy as reactLazy } from 'react';
|
|
11
|
+
|
|
12
|
+
type LazyComponentType<M extends Record<string, any>, K extends keyof M> = {
|
|
13
|
+
[P in K]: M[P];
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
export function lazy<M extends Record<'default', any>>(factory: () => Promise<M>): M['default'];
|
|
17
|
+
|
|
18
|
+
export function lazy<M extends Record<string, any>, K extends keyof M = keyof M>(
|
|
19
|
+
factory: () => Promise<M>,
|
|
20
|
+
...componentNames: K[]
|
|
21
|
+
): LazyComponentType<M, K>;
|
|
22
|
+
|
|
23
|
+
export function lazy<M extends Record<string, any>, K extends keyof M>(
|
|
24
|
+
factory: () => Promise<M>,
|
|
25
|
+
...componentNames: K[]
|
|
26
|
+
) {
|
|
27
|
+
if (componentNames.length === 0) {
|
|
28
|
+
const LazyComponent = reactLazy(() =>
|
|
29
|
+
factory().then((module) => ({
|
|
30
|
+
default: module.default,
|
|
31
|
+
})),
|
|
32
|
+
);
|
|
33
|
+
const Component = (props) => (
|
|
34
|
+
<React.Suspense fallback={null}>
|
|
35
|
+
<LazyComponent {...props} />
|
|
36
|
+
</React.Suspense>
|
|
37
|
+
);
|
|
38
|
+
return Component;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return componentNames.reduce(
|
|
42
|
+
(acc, name) => {
|
|
43
|
+
const LazyComponent = reactLazy(() =>
|
|
44
|
+
factory().then((module) => ({
|
|
45
|
+
default: module[name],
|
|
46
|
+
})),
|
|
47
|
+
);
|
|
48
|
+
acc[name] = ((props) => (
|
|
49
|
+
<React.Suspense fallback={null}>
|
|
50
|
+
<LazyComponent {...props} />
|
|
51
|
+
</React.Suspense>
|
|
52
|
+
)) as M[K];
|
|
53
|
+
return acc;
|
|
54
|
+
},
|
|
55
|
+
{} as LazyComponentType<M, K>,
|
|
56
|
+
);
|
|
57
|
+
}
|
package/src/locale/en-US.json
CHANGED
|
@@ -34,6 +34,7 @@
|
|
|
34
34
|
"Failed to destroy model after creation error": "Failed to destroy model after creation error",
|
|
35
35
|
"Failed to get action {{action}}": "Failed to get action {{action}}",
|
|
36
36
|
"Failed to get configurable flows for model {{model}}": "Failed to get configurable flows for model {{model}}",
|
|
37
|
+
"Attributes are unavailable before selecting a record": "Attributes are unavailable before selecting a record",
|
|
37
38
|
"Failed to import FormDialog": "Failed to import FormDialog",
|
|
38
39
|
"Failed to import FormDialog or FormStep": "Failed to import FormDialog or FormStep",
|
|
39
40
|
"Failed to import Formily components": "Failed to import Formily components",
|
package/src/locale/zh-CN.json
CHANGED
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"Failed to destroy model after creation error": "创建错误后销毁模型失败",
|
|
32
32
|
"Failed to get action {{action}}": "获取 action '{{action}}' 失败",
|
|
33
33
|
"Failed to get configurable flows for model {{model}}": "获取模型 '{{model}}' 的可配置 flows 失败",
|
|
34
|
+
"Attributes are unavailable before selecting a record": "选择记录之前,当前项属性不可用",
|
|
34
35
|
"Failed to import FormDialog": "导入 FormDialog 失败",
|
|
35
36
|
"Failed to import FormDialog or FormStep": "导入 FormDialog 或 FormStep 失败",
|
|
36
37
|
"Failed to import Formily components": "导入 Formily 组件失败",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { DefaultStructure } from '
|
|
10
|
+
import type { DefaultStructure } from '../types';
|
|
11
11
|
import { CollectionFieldModel } from './CollectionFieldModel';
|
|
12
12
|
|
|
13
13
|
export class DisplayItemModel<T extends DefaultStructure = DefaultStructure> extends CollectionFieldModel<T> {}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { DefaultStructure } from '
|
|
10
|
+
import type { DefaultStructure } from '../types';
|
|
11
11
|
import { CollectionFieldModel } from './CollectionFieldModel';
|
|
12
12
|
|
|
13
13
|
export class EditableItemModel<T extends DefaultStructure = DefaultStructure> extends CollectionFieldModel<T> {}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { DefaultStructure } from '
|
|
10
|
+
import type { DefaultStructure } from '../types';
|
|
11
11
|
import { CollectionFieldModel } from './CollectionFieldModel';
|
|
12
12
|
|
|
13
13
|
export class FilterableItemModel<T extends DefaultStructure = DefaultStructure> extends CollectionFieldModel<T> {}
|
|
@@ -105,6 +105,45 @@ describe('dispatchEvent dynamic event flow phase (scheduleModelOperation integra
|
|
|
105
105
|
expect(calls).toEqual(['static-a', 'dynamic']);
|
|
106
106
|
});
|
|
107
107
|
|
|
108
|
+
test("phase='afterAllFlows': skips when event aborted by ctx.exitAll()", async () => {
|
|
109
|
+
const engine = new FlowEngine();
|
|
110
|
+
class M extends FlowModel {}
|
|
111
|
+
engine.registerModels({ M });
|
|
112
|
+
|
|
113
|
+
const calls: string[] = [];
|
|
114
|
+
|
|
115
|
+
M.registerFlow({
|
|
116
|
+
key: 'S',
|
|
117
|
+
on: { eventName: 'go' },
|
|
118
|
+
steps: {
|
|
119
|
+
a: { handler: async () => void calls.push('static-a') } as any,
|
|
120
|
+
},
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
const model = engine.createModel({ use: 'M' });
|
|
124
|
+
model.registerFlow('Abort', {
|
|
125
|
+
on: { eventName: 'go' },
|
|
126
|
+
sort: -10,
|
|
127
|
+
steps: {
|
|
128
|
+
d: {
|
|
129
|
+
handler: async (ctx: any) => {
|
|
130
|
+
calls.push('abort');
|
|
131
|
+
ctx.exitAll();
|
|
132
|
+
},
|
|
133
|
+
} as any,
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
model.registerFlow('AfterAll', {
|
|
137
|
+
on: { eventName: 'go', phase: 'afterAllFlows' },
|
|
138
|
+
steps: {
|
|
139
|
+
d: { handler: async () => void calls.push('after-all') } as any,
|
|
140
|
+
},
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
await model.dispatchEvent('go');
|
|
144
|
+
expect(calls).toEqual(['abort']);
|
|
145
|
+
});
|
|
146
|
+
|
|
108
147
|
test("phase='beforeFlow': instance flow runs before the target static flow", async () => {
|
|
109
148
|
const engine = new FlowEngine();
|
|
110
149
|
class M extends FlowModel {}
|
|
@@ -161,6 +200,39 @@ describe('dispatchEvent dynamic event flow phase (scheduleModelOperation integra
|
|
|
161
200
|
expect(calls).toEqual(['static-a', 'static-b', 'dynamic']);
|
|
162
201
|
});
|
|
163
202
|
|
|
203
|
+
test("phase='afterFlow': skips when anchor flow is aborted by ctx.exitAll()", async () => {
|
|
204
|
+
const engine = new FlowEngine();
|
|
205
|
+
class M extends FlowModel {}
|
|
206
|
+
engine.registerModels({ M });
|
|
207
|
+
|
|
208
|
+
const calls: string[] = [];
|
|
209
|
+
|
|
210
|
+
M.registerFlow({
|
|
211
|
+
key: 'S',
|
|
212
|
+
on: { eventName: 'go' },
|
|
213
|
+
steps: {
|
|
214
|
+
a: {
|
|
215
|
+
handler: async (ctx: any) => {
|
|
216
|
+
calls.push('static-a');
|
|
217
|
+
ctx.exitAll();
|
|
218
|
+
},
|
|
219
|
+
} as any,
|
|
220
|
+
b: { handler: async () => void calls.push('static-b') } as any,
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const model = engine.createModel({ use: 'M' });
|
|
225
|
+
model.registerFlow('D', {
|
|
226
|
+
on: { eventName: 'go', phase: 'afterFlow', flowKey: 'S' },
|
|
227
|
+
steps: {
|
|
228
|
+
d: { handler: async () => void calls.push('dynamic') } as any,
|
|
229
|
+
},
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
await model.dispatchEvent('go');
|
|
233
|
+
expect(calls).toEqual(['static-a']);
|
|
234
|
+
});
|
|
235
|
+
|
|
164
236
|
test("phase='beforeStep': instance flow runs before the target static step", async () => {
|
|
165
237
|
const engine = new FlowEngine();
|
|
166
238
|
class M extends FlowModel {}
|
|
@@ -217,6 +289,39 @@ describe('dispatchEvent dynamic event flow phase (scheduleModelOperation integra
|
|
|
217
289
|
expect(calls).toEqual(['static-a', 'dynamic', 'static-b']);
|
|
218
290
|
});
|
|
219
291
|
|
|
292
|
+
test("phase='afterStep': skips when anchor step is aborted by ctx.exitAll()", async () => {
|
|
293
|
+
const engine = new FlowEngine();
|
|
294
|
+
class M extends FlowModel {}
|
|
295
|
+
engine.registerModels({ M });
|
|
296
|
+
|
|
297
|
+
const calls: string[] = [];
|
|
298
|
+
|
|
299
|
+
M.registerFlow({
|
|
300
|
+
key: 'S',
|
|
301
|
+
on: { eventName: 'go' },
|
|
302
|
+
steps: {
|
|
303
|
+
a: {
|
|
304
|
+
handler: async (ctx: any) => {
|
|
305
|
+
calls.push('static-a');
|
|
306
|
+
ctx.exitAll();
|
|
307
|
+
},
|
|
308
|
+
} as any,
|
|
309
|
+
b: { handler: async () => void calls.push('static-b') } as any,
|
|
310
|
+
},
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
const model = engine.createModel({ use: 'M' });
|
|
314
|
+
model.registerFlow('D', {
|
|
315
|
+
on: { eventName: 'go', phase: 'afterStep', flowKey: 'S', stepKey: 'a' },
|
|
316
|
+
steps: {
|
|
317
|
+
d: { handler: async () => void calls.push('dynamic') } as any,
|
|
318
|
+
},
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
await model.dispatchEvent('go');
|
|
322
|
+
expect(calls).toEqual(['static-a']);
|
|
323
|
+
});
|
|
324
|
+
|
|
220
325
|
test("phase='beforeFlow': ctx.exitAll() stops anchor flow and subsequent flows", async () => {
|
|
221
326
|
const engine = new FlowEngine();
|
|
222
327
|
class M extends FlowModel {}
|
|
@@ -430,6 +535,115 @@ describe('dispatchEvent dynamic event flow phase (scheduleModelOperation integra
|
|
|
430
535
|
expect(calls).toEqual(['static-a', 'dynamic']);
|
|
431
536
|
});
|
|
432
537
|
|
|
538
|
+
test('fallback to event:end (missing anchor) skips when event aborted by ctx.exitAll()', async () => {
|
|
539
|
+
const engine = new FlowEngine();
|
|
540
|
+
class M extends FlowModel {}
|
|
541
|
+
engine.registerModels({ M });
|
|
542
|
+
|
|
543
|
+
const calls: string[] = [];
|
|
544
|
+
|
|
545
|
+
M.registerFlow({
|
|
546
|
+
key: 'S',
|
|
547
|
+
on: { eventName: 'go' },
|
|
548
|
+
steps: {
|
|
549
|
+
a: { handler: async () => void calls.push('static-a') } as any,
|
|
550
|
+
},
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
const model = engine.createModel({ use: 'M' });
|
|
554
|
+
model.registerFlow('Abort', {
|
|
555
|
+
on: { eventName: 'go' },
|
|
556
|
+
sort: -10,
|
|
557
|
+
steps: {
|
|
558
|
+
d: {
|
|
559
|
+
handler: async (ctx: any) => {
|
|
560
|
+
calls.push('abort');
|
|
561
|
+
ctx.exitAll();
|
|
562
|
+
},
|
|
563
|
+
} as any,
|
|
564
|
+
},
|
|
565
|
+
});
|
|
566
|
+
model.registerFlow('FallbackToEnd', {
|
|
567
|
+
on: { eventName: 'go', phase: 'beforeFlow', flowKey: 'missing' },
|
|
568
|
+
steps: {
|
|
569
|
+
d: { handler: async () => void calls.push('fallback-end') } as any,
|
|
570
|
+
},
|
|
571
|
+
});
|
|
572
|
+
|
|
573
|
+
await model.dispatchEvent('go');
|
|
574
|
+
expect(calls).toEqual(['abort']);
|
|
575
|
+
});
|
|
576
|
+
|
|
577
|
+
test('event:end is still emitted with aborted=true when exitAll happens', async () => {
|
|
578
|
+
const engine = new FlowEngine();
|
|
579
|
+
class M extends FlowModel {}
|
|
580
|
+
engine.registerModels({ M });
|
|
581
|
+
|
|
582
|
+
const endEvents: any[] = [];
|
|
583
|
+
const onEnd = (payload: any) => {
|
|
584
|
+
endEvents.push(payload);
|
|
585
|
+
};
|
|
586
|
+
engine.emitter.on('model:event:go:end', onEnd);
|
|
587
|
+
|
|
588
|
+
const model = engine.createModel({ use: 'M' });
|
|
589
|
+
model.registerFlow('Abort', {
|
|
590
|
+
on: { eventName: 'go' },
|
|
591
|
+
steps: {
|
|
592
|
+
d: {
|
|
593
|
+
handler: async (ctx: any) => {
|
|
594
|
+
ctx.exitAll();
|
|
595
|
+
},
|
|
596
|
+
} as any,
|
|
597
|
+
},
|
|
598
|
+
});
|
|
599
|
+
|
|
600
|
+
await model.dispatchEvent('go');
|
|
601
|
+
engine.emitter.off('model:event:go:end', onEnd);
|
|
602
|
+
|
|
603
|
+
expect(endEvents).toHaveLength(1);
|
|
604
|
+
expect(endEvents[0]?.aborted).toBe(true);
|
|
605
|
+
});
|
|
606
|
+
|
|
607
|
+
test('flow:end/step:end are emitted with aborted=true when exitAll happens', async () => {
|
|
608
|
+
const engine = new FlowEngine();
|
|
609
|
+
class M extends FlowModel {}
|
|
610
|
+
engine.registerModels({ M });
|
|
611
|
+
|
|
612
|
+
const flowEndEvents: any[] = [];
|
|
613
|
+
const stepEndEvents: any[] = [];
|
|
614
|
+
const onFlowEnd = (payload: any) => {
|
|
615
|
+
flowEndEvents.push(payload);
|
|
616
|
+
};
|
|
617
|
+
const onStepEnd = (payload: any) => {
|
|
618
|
+
stepEndEvents.push(payload);
|
|
619
|
+
};
|
|
620
|
+
engine.emitter.on('model:event:go:flow:S:end', onFlowEnd);
|
|
621
|
+
engine.emitter.on('model:event:go:flow:S:step:a:end', onStepEnd);
|
|
622
|
+
|
|
623
|
+
M.registerFlow({
|
|
624
|
+
key: 'S',
|
|
625
|
+
on: { eventName: 'go' },
|
|
626
|
+
steps: {
|
|
627
|
+
a: {
|
|
628
|
+
handler: async (ctx: any) => {
|
|
629
|
+
ctx.exitAll();
|
|
630
|
+
},
|
|
631
|
+
} as any,
|
|
632
|
+
},
|
|
633
|
+
});
|
|
634
|
+
|
|
635
|
+
const model = engine.createModel({ use: 'M' });
|
|
636
|
+
await model.dispatchEvent('go');
|
|
637
|
+
|
|
638
|
+
engine.emitter.off('model:event:go:flow:S:end', onFlowEnd);
|
|
639
|
+
engine.emitter.off('model:event:go:flow:S:step:a:end', onStepEnd);
|
|
640
|
+
|
|
641
|
+
expect(flowEndEvents).toHaveLength(1);
|
|
642
|
+
expect(stepEndEvents).toHaveLength(1);
|
|
643
|
+
expect(flowEndEvents[0]?.aborted).toBe(true);
|
|
644
|
+
expect(stepEndEvents[0]?.aborted).toBe(true);
|
|
645
|
+
});
|
|
646
|
+
|
|
433
647
|
test('multiple flows on same anchor: executes by flow.sort asc (stable)', async () => {
|
|
434
648
|
const engine = new FlowEngine();
|
|
435
649
|
class M extends FlowModel {}
|
|
@@ -546,6 +546,34 @@ describe('FlowModel', () => {
|
|
|
546
546
|
|
|
547
547
|
loggerSpy.mockRestore();
|
|
548
548
|
});
|
|
549
|
+
|
|
550
|
+
test('should warn and skip step when use and handler are both missing', async () => {
|
|
551
|
+
const warnSpy = vi.spyOn(model.context.logger, 'warn').mockImplementation(() => {});
|
|
552
|
+
const errorSpy = vi.spyOn(model.context.logger, 'error').mockImplementation(() => {});
|
|
553
|
+
|
|
554
|
+
TestFlowModel.registerFlow({
|
|
555
|
+
key: 'settingsOnlyFlow',
|
|
556
|
+
steps: {
|
|
557
|
+
edit: {
|
|
558
|
+
title: 'Edit',
|
|
559
|
+
uiSchema: {},
|
|
560
|
+
},
|
|
561
|
+
},
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const result = await model.applyFlow('settingsOnlyFlow');
|
|
565
|
+
|
|
566
|
+
expect(result).toEqual({});
|
|
567
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
568
|
+
expect.stringContaining("Step 'edit' in flow 'settingsOnlyFlow' has neither 'use' nor 'handler'"),
|
|
569
|
+
);
|
|
570
|
+
expect(errorSpy).not.toHaveBeenCalledWith(
|
|
571
|
+
expect.stringContaining("Step 'edit' in flow 'settingsOnlyFlow' has neither 'use' nor 'handler'"),
|
|
572
|
+
);
|
|
573
|
+
|
|
574
|
+
warnSpy.mockRestore();
|
|
575
|
+
errorSpy.mockRestore();
|
|
576
|
+
});
|
|
549
577
|
});
|
|
550
578
|
|
|
551
579
|
describe('beforeRender flows', () => {
|
|
@@ -1855,7 +1883,7 @@ describe('FlowModel', () => {
|
|
|
1855
1883
|
});
|
|
1856
1884
|
|
|
1857
1885
|
describe('serialization', () => {
|
|
1858
|
-
test('should serialize basic model data
|
|
1886
|
+
test('should serialize basic model data with the latest props, excluding flowEngine', () => {
|
|
1859
1887
|
model.sortIndex = 5;
|
|
1860
1888
|
model.setProps({ name: 'Test Model', value: 42 });
|
|
1861
1889
|
model.setStepParams({
|
|
@@ -1867,13 +1895,12 @@ describe('FlowModel', () => {
|
|
|
1867
1895
|
expect(serialized).toEqual(
|
|
1868
1896
|
expect.objectContaining({
|
|
1869
1897
|
uid: model.uid,
|
|
1898
|
+
props: expect.objectContaining({ name: 'Test Model', value: 42 }),
|
|
1870
1899
|
stepParams: expect.objectContaining({ flow1: { step1: { param1: 'value1' } } }),
|
|
1871
1900
|
sortIndex: 5,
|
|
1872
1901
|
subModels: expect.any(Object),
|
|
1873
1902
|
}),
|
|
1874
1903
|
);
|
|
1875
|
-
// props should be excluded from serialization
|
|
1876
|
-
expect(serialized.props).toBeUndefined();
|
|
1877
1904
|
expect(serialized.flowEngine).toBeUndefined();
|
|
1878
1905
|
});
|
|
1879
1906
|
|
|
@@ -1892,6 +1919,7 @@ describe('FlowModel', () => {
|
|
|
1892
1919
|
expect(serialized).toEqual(
|
|
1893
1920
|
expect.objectContaining({
|
|
1894
1921
|
uid: 'empty-model',
|
|
1922
|
+
props: expect.objectContaining({ foo: 'bar' }),
|
|
1895
1923
|
stepParams: expect.any(Object),
|
|
1896
1924
|
sortIndex: expect.any(Number),
|
|
1897
1925
|
subModels: expect.any(Object),
|
|
@@ -1899,6 +1927,22 @@ describe('FlowModel', () => {
|
|
|
1899
1927
|
);
|
|
1900
1928
|
expect(serialized.flowEngine).toBeUndefined();
|
|
1901
1929
|
});
|
|
1930
|
+
|
|
1931
|
+
test('should serialize the latest props after multiple updates', () => {
|
|
1932
|
+
model.setProps({ fieldNames: { title: 'name' }, searchable: true });
|
|
1933
|
+
model.setProps({ fieldNames: { title: 'age' } });
|
|
1934
|
+
model.setProps('defaultExpandAll', false);
|
|
1935
|
+
|
|
1936
|
+
const serialized = model.serialize();
|
|
1937
|
+
|
|
1938
|
+
expect(serialized.props).toEqual(
|
|
1939
|
+
expect.objectContaining({
|
|
1940
|
+
fieldNames: { title: 'age' },
|
|
1941
|
+
searchable: true,
|
|
1942
|
+
defaultExpandAll: false,
|
|
1943
|
+
}),
|
|
1944
|
+
);
|
|
1945
|
+
});
|
|
1902
1946
|
});
|
|
1903
1947
|
});
|
|
1904
1948
|
|