@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
|
@@ -256,11 +256,11 @@ describe('ViewScopedFlowEngine', () => {
|
|
|
256
256
|
|
|
257
257
|
// Both children should return null from hydration because parent has flowSettingsEnabled
|
|
258
258
|
// This is the bug fix: previously only children with their own flowSettingsEnabled would return null
|
|
259
|
-
const result1 = (scoped as any).hydrateModelFromPreviousEngines({
|
|
259
|
+
const result1 = await (scoped as any).hydrateModelFromPreviousEngines({
|
|
260
260
|
parentId: 'parent-with-settings',
|
|
261
261
|
subKey: 'popup',
|
|
262
262
|
});
|
|
263
|
-
const result2 = (scoped as any).hydrateModelFromPreviousEngines({
|
|
263
|
+
const result2 = await (scoped as any).hydrateModelFromPreviousEngines({
|
|
264
264
|
parentId: 'parent-with-settings',
|
|
265
265
|
subKey: 'items',
|
|
266
266
|
});
|
|
@@ -298,7 +298,7 @@ describe('ViewScopedFlowEngine', () => {
|
|
|
298
298
|
const scoped = createViewScopedEngine(root);
|
|
299
299
|
|
|
300
300
|
// Call the private method hydrateModelFromPreviousEngines directly
|
|
301
|
-
const result = (scoped as any).hydrateModelFromPreviousEngines({
|
|
301
|
+
const result = await (scoped as any).hydrateModelFromPreviousEngines({
|
|
302
302
|
parentId: 'parent-normal',
|
|
303
303
|
subKey: 'content',
|
|
304
304
|
});
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
10
|
+
import type { FlowModelRendererProps } from './FlowModelRenderer';
|
|
11
|
+
import { FlowModelRenderer } from './FlowModelRenderer';
|
|
11
12
|
import _ from 'lodash';
|
|
12
13
|
import React, { useEffect, useMemo, useRef } from 'react';
|
|
13
14
|
|
|
@@ -69,7 +69,9 @@ export interface FlowModelRendererProps {
|
|
|
69
69
|
showBackground?: boolean;
|
|
70
70
|
showBorder?: boolean;
|
|
71
71
|
showDragHandle?: boolean;
|
|
72
|
-
/**
|
|
72
|
+
/** 是否显示事件流入口,默认 true */
|
|
73
|
+
showDynamicFlowsEditor?: boolean;
|
|
74
|
+
/** 自定义工具栏样式,`top/left/right/bottom` 会作为 portal overlay 的 inset 使用 */
|
|
73
75
|
style?: React.CSSProperties;
|
|
74
76
|
/**
|
|
75
77
|
* @default 'inside'
|
|
@@ -112,6 +114,8 @@ const FlowModelRendererWithAutoFlows: React.FC<{
|
|
|
112
114
|
showBackground?: boolean;
|
|
113
115
|
showBorder?: boolean;
|
|
114
116
|
showDragHandle?: boolean;
|
|
117
|
+
showDynamicFlowsEditor?: boolean;
|
|
118
|
+
/** `top/left/right/bottom` 会作为 portal overlay 的 inset 使用 */
|
|
115
119
|
style?: React.CSSProperties;
|
|
116
120
|
/**
|
|
117
121
|
* @default 'inside'
|
|
@@ -126,6 +130,7 @@ const FlowModelRendererWithAutoFlows: React.FC<{
|
|
|
126
130
|
settingsMenuLevel?: number;
|
|
127
131
|
extraToolbarItems?: ToolbarItemConfig[];
|
|
128
132
|
fallback?: React.ReactNode;
|
|
133
|
+
useCache?: boolean;
|
|
129
134
|
}> = observer(
|
|
130
135
|
({
|
|
131
136
|
model,
|
|
@@ -138,12 +143,12 @@ const FlowModelRendererWithAutoFlows: React.FC<{
|
|
|
138
143
|
settingsMenuLevel,
|
|
139
144
|
extraToolbarItems,
|
|
140
145
|
fallback,
|
|
146
|
+
useCache,
|
|
141
147
|
}) => {
|
|
142
148
|
// hidden 占位由模型自身处理;无需在此注入
|
|
143
|
-
|
|
144
149
|
const { loading: pending, error: autoFlowsError } = useApplyAutoFlows(model, inputArgs, {
|
|
145
150
|
throwOnError: false,
|
|
146
|
-
useCache
|
|
151
|
+
useCache,
|
|
147
152
|
});
|
|
148
153
|
// 将错误下沉到 model 实例上,供内容层读取(类型安全的 WeakMap 存储)
|
|
149
154
|
setAutoFlowError(model, autoFlowsError || null);
|
|
@@ -182,6 +187,8 @@ const FlowModelRendererCore: React.FC<{
|
|
|
182
187
|
showBackground?: boolean;
|
|
183
188
|
showBorder?: boolean;
|
|
184
189
|
showDragHandle?: boolean;
|
|
190
|
+
showDynamicFlowsEditor?: boolean;
|
|
191
|
+
/** `top/left/right/bottom` 会作为 portal overlay 的 inset 使用 */
|
|
185
192
|
style?: React.CSSProperties;
|
|
186
193
|
/**
|
|
187
194
|
* @default 'inside'
|
|
@@ -251,6 +258,7 @@ const FlowModelRendererCore: React.FC<{
|
|
|
251
258
|
showBackground={_.isObject(showFlowSettings) ? showFlowSettings.showBackground : undefined}
|
|
252
259
|
showBorder={_.isObject(showFlowSettings) ? showFlowSettings.showBorder : undefined}
|
|
253
260
|
showDragHandle={_.isObject(showFlowSettings) ? showFlowSettings.showDragHandle : undefined}
|
|
261
|
+
showDynamicFlowsEditor={_.isObject(showFlowSettings) ? showFlowSettings.showDynamicFlowsEditor : undefined}
|
|
254
262
|
settingsMenuLevel={settingsMenuLevel}
|
|
255
263
|
extraToolbarItems={extraToolbarItems}
|
|
256
264
|
toolbarStyle={_.isObject(showFlowSettings) ? showFlowSettings.style : undefined}
|
|
@@ -297,6 +305,7 @@ const FlowModelRendererCore: React.FC<{
|
|
|
297
305
|
showBackground={_.isObject(showFlowSettings) ? showFlowSettings.showBackground : undefined}
|
|
298
306
|
showBorder={_.isObject(showFlowSettings) ? showFlowSettings.showBorder : undefined}
|
|
299
307
|
showDragHandle={_.isObject(showFlowSettings) ? showFlowSettings.showDragHandle : undefined}
|
|
308
|
+
showDynamicFlowsEditor={_.isObject(showFlowSettings) ? showFlowSettings.showDynamicFlowsEditor : undefined}
|
|
300
309
|
settingsMenuLevel={settingsMenuLevel}
|
|
301
310
|
extraToolbarItems={extraToolbarItems}
|
|
302
311
|
toolbarStyle={_.isObject(showFlowSettings) ? showFlowSettings.style : undefined}
|
|
@@ -346,13 +355,15 @@ export const FlowModelRenderer: React.FC<FlowModelRendererProps> = observer(
|
|
|
346
355
|
extraToolbarItems,
|
|
347
356
|
useCache,
|
|
348
357
|
}) => {
|
|
358
|
+
const resolvedUseCache = typeof useCache === 'boolean' ? useCache : model?.context?.useCache;
|
|
359
|
+
|
|
349
360
|
useEffect(() => {
|
|
350
|
-
if (model?.context) {
|
|
361
|
+
if (model?.context && typeof resolvedUseCache !== 'undefined') {
|
|
351
362
|
model.context.defineProperty('useCache', {
|
|
352
|
-
value:
|
|
363
|
+
value: resolvedUseCache,
|
|
353
364
|
});
|
|
354
365
|
}
|
|
355
|
-
}, [model?.context,
|
|
366
|
+
}, [model?.context, resolvedUseCache]);
|
|
356
367
|
|
|
357
368
|
if (!model || typeof model.render !== 'function') {
|
|
358
369
|
// 可以选择渲染 null 或者一个错误/提示信息
|
|
@@ -373,6 +384,7 @@ export const FlowModelRenderer: React.FC<FlowModelRendererProps> = observer(
|
|
|
373
384
|
settingsMenuLevel={settingsMenuLevel}
|
|
374
385
|
extraToolbarItems={extraToolbarItems}
|
|
375
386
|
fallback={fallback}
|
|
387
|
+
useCache={resolvedUseCache}
|
|
376
388
|
/>
|
|
377
389
|
);
|
|
378
390
|
|
|
@@ -8,11 +8,13 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { ConfigProvider } from 'antd';
|
|
11
|
-
import { Popup } from 'antd-mobile';
|
|
12
11
|
import React, { FC, ReactNode, useMemo } from 'react';
|
|
13
|
-
import { CloseOutline } from 'antd-mobile-icons';
|
|
14
12
|
import { useMobileActionDrawerStyle } from './MobilePopup.style';
|
|
15
13
|
import { useTranslation } from 'react-i18next';
|
|
14
|
+
import { lazy } from '../lazy-helper';
|
|
15
|
+
|
|
16
|
+
const { Popup } = lazy(() => import('antd-mobile'), 'Popup');
|
|
17
|
+
const { CloseOutline } = lazy(() => import('antd-mobile-icons'), 'CloseOutline');
|
|
16
18
|
|
|
17
19
|
interface MobilePopupProps {
|
|
18
20
|
title?: string;
|
|
@@ -38,9 +38,8 @@ describe('FlowModelRenderer', () => {
|
|
|
38
38
|
test('should pass useCache to useApplyAutoFlows and set it on context', async () => {
|
|
39
39
|
const { unmount } = renderWithProvider(<FlowModelRenderer model={model} useCache={true} />);
|
|
40
40
|
|
|
41
|
-
// Check if dispatchEvent was called with useCache: true
|
|
42
|
-
// useApplyAutoFlows calls dispatchEvent('beforeRender', inputArgs, { useCache })
|
|
43
41
|
await waitFor(() => {
|
|
42
|
+
expect(model.dispatchEvent).toHaveBeenCalledTimes(1);
|
|
44
43
|
expect(model.dispatchEvent).toHaveBeenCalledWith(
|
|
45
44
|
'beforeRender',
|
|
46
45
|
undefined,
|
|
@@ -58,6 +57,7 @@ describe('FlowModelRenderer', () => {
|
|
|
58
57
|
const { unmount } = renderWithProvider(<FlowModelRenderer model={model} useCache={false} />);
|
|
59
58
|
|
|
60
59
|
await waitFor(() => {
|
|
60
|
+
expect(model.dispatchEvent).toHaveBeenCalledTimes(1);
|
|
61
61
|
expect(model.dispatchEvent).toHaveBeenCalledWith(
|
|
62
62
|
'beforeRender',
|
|
63
63
|
undefined,
|
|
@@ -74,6 +74,7 @@ describe('FlowModelRenderer', () => {
|
|
|
74
74
|
const { unmount } = renderWithProvider(<FlowModelRenderer model={model} />);
|
|
75
75
|
|
|
76
76
|
await waitFor(() => {
|
|
77
|
+
expect(model.dispatchEvent).toHaveBeenCalledTimes(1);
|
|
77
78
|
expect(model.dispatchEvent).toHaveBeenCalledWith(
|
|
78
79
|
'beforeRender',
|
|
79
80
|
undefined,
|
|
@@ -86,4 +87,66 @@ describe('FlowModelRenderer', () => {
|
|
|
86
87
|
|
|
87
88
|
unmount();
|
|
88
89
|
});
|
|
90
|
+
|
|
91
|
+
test('should clear stale beforeRender state after unmount when reusing the same model', async () => {
|
|
92
|
+
const statefulEngine = new FlowEngine();
|
|
93
|
+
const onMountSpy = vi.fn();
|
|
94
|
+
const onUnmountSpy = vi.fn();
|
|
95
|
+
|
|
96
|
+
class StatefulModel extends FlowModel {
|
|
97
|
+
render(): any {
|
|
98
|
+
return <div>Stateful Content</div>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
protected onMount(): void {
|
|
102
|
+
onMountSpy();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
protected onUnmount(): void {
|
|
106
|
+
onUnmountSpy();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const statefulModel = new StatefulModel({
|
|
111
|
+
uid: 'stateful-model',
|
|
112
|
+
flowEngine: statefulEngine,
|
|
113
|
+
});
|
|
114
|
+
const executorSpy = vi.spyOn((statefulEngine as any).executor, 'dispatchEvent').mockResolvedValue([]);
|
|
115
|
+
|
|
116
|
+
const firstRender = renderWithProvider(<FlowModelRenderer model={statefulModel} />);
|
|
117
|
+
await waitFor(() => {
|
|
118
|
+
expect(executorSpy).toHaveBeenCalledTimes(1);
|
|
119
|
+
});
|
|
120
|
+
await waitFor(() => {
|
|
121
|
+
expect(onMountSpy).toHaveBeenCalledTimes(1);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
firstRender.unmount();
|
|
125
|
+
await waitFor(() => {
|
|
126
|
+
expect(onUnmountSpy).toHaveBeenCalledTimes(1);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
executorSpy.mockClear();
|
|
130
|
+
statefulModel.setStepParams('anyFlow', 'anyStep', { x: 1 });
|
|
131
|
+
await new Promise((resolve) => setTimeout(resolve, 150));
|
|
132
|
+
expect(executorSpy.mock.calls.length).toBe(0);
|
|
133
|
+
|
|
134
|
+
const secondRender = renderWithProvider(<FlowModelRenderer model={statefulModel} />);
|
|
135
|
+
await waitFor(() => {
|
|
136
|
+
expect(executorSpy).toHaveBeenCalledTimes(1);
|
|
137
|
+
});
|
|
138
|
+
await waitFor(() => {
|
|
139
|
+
expect(onMountSpy).toHaveBeenCalledTimes(2);
|
|
140
|
+
});
|
|
141
|
+
const [target, eventName, inputArgs, options] = executorSpy.mock.calls[0];
|
|
142
|
+
expect(target).toBe(statefulModel);
|
|
143
|
+
expect(eventName).toBe('beforeRender');
|
|
144
|
+
expect(inputArgs).toBeUndefined();
|
|
145
|
+
expect(options).toMatchObject({ useCache: true });
|
|
146
|
+
|
|
147
|
+
secondRender.unmount();
|
|
148
|
+
await waitFor(() => {
|
|
149
|
+
expect(onUnmountSpy).toHaveBeenCalledTimes(2);
|
|
150
|
+
});
|
|
151
|
+
});
|
|
89
152
|
});
|
|
@@ -0,0 +1,44 @@
|
|
|
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 { describe, expect, it } from 'vitest';
|
|
11
|
+
import { resolveOverlayAnchorTransform } from '../dnd';
|
|
12
|
+
|
|
13
|
+
describe('resolveOverlayAnchorTransform', () => {
|
|
14
|
+
it('should keep the original transform when anchor point is missing', () => {
|
|
15
|
+
const transform = { x: 24, y: 36, scaleX: 1, scaleY: 1 };
|
|
16
|
+
|
|
17
|
+
expect(
|
|
18
|
+
resolveOverlayAnchorTransform({
|
|
19
|
+
activeId: 'menu-item-1',
|
|
20
|
+
active: { id: 'menu-item-1' },
|
|
21
|
+
transform,
|
|
22
|
+
activeNodeRect: { top: 80, left: 120 },
|
|
23
|
+
dragAnchorPoint: null,
|
|
24
|
+
}),
|
|
25
|
+
).toEqual(transform);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('should align the overlay origin to the pointer position when dragging from toolbar handle', () => {
|
|
29
|
+
expect(
|
|
30
|
+
resolveOverlayAnchorTransform({
|
|
31
|
+
activeId: 'menu-item-1',
|
|
32
|
+
active: { id: 'menu-item-1' },
|
|
33
|
+
transform: { x: 20, y: 30, scaleX: 1, scaleY: 1 },
|
|
34
|
+
activeNodeRect: { top: 200, left: 100 },
|
|
35
|
+
dragAnchorPoint: { x: 180, y: 260 },
|
|
36
|
+
}),
|
|
37
|
+
).toEqual({
|
|
38
|
+
x: 100,
|
|
39
|
+
y: 90,
|
|
40
|
+
scaleX: 1,
|
|
41
|
+
scaleY: 1,
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
12
|
-
import { render, cleanup, waitFor } from '@testing-library/react';
|
|
12
|
+
import { render, cleanup, fireEvent, waitFor } from '@testing-library/react';
|
|
13
13
|
import { App, ConfigProvider } from 'antd';
|
|
14
14
|
import { FlowEngine } from '../../flowEngine';
|
|
15
15
|
import { FlowModel, ModelRenderMode } from '../../models/flowModel';
|
|
@@ -94,6 +94,16 @@ const clickDeleteFromLastDropdown = async () => {
|
|
|
94
94
|
menu.onClick?.({ key: 'delete' });
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
+
const getHost = (element: HTMLElement) => element.closest('[data-has-float-menu="true"]') as HTMLDivElement;
|
|
98
|
+
|
|
99
|
+
const hoverHostAndClickDelete = async (element: HTMLElement) => {
|
|
100
|
+
const host = getHost(element);
|
|
101
|
+
if (host) {
|
|
102
|
+
fireEvent.mouseEnter(host);
|
|
103
|
+
}
|
|
104
|
+
await clickDeleteFromLastDropdown();
|
|
105
|
+
};
|
|
106
|
+
|
|
97
107
|
// ---------------- Tests ----------------
|
|
98
108
|
describe('Delete problematic model via FlowSettings menu', () => {
|
|
99
109
|
beforeEach(() => {
|
|
@@ -114,13 +124,13 @@ describe('Delete problematic model via FlowSettings menu', () => {
|
|
|
114
124
|
}
|
|
115
125
|
|
|
116
126
|
const engine = new FlowEngine();
|
|
117
|
-
engine.flowSettings.forceEnable();
|
|
127
|
+
await engine.flowSettings.forceEnable();
|
|
118
128
|
engine.registerModels({ BrokenModel });
|
|
119
129
|
const model = engine.createModel({ use: 'BrokenModel', uid: 'broken-top-2' }) as BrokenModel;
|
|
120
130
|
// satisfy FlowsFloatContextMenu styles
|
|
121
131
|
model.context.defineProperty('themeToken', { value: { borderRadiusLG: 8 } });
|
|
122
132
|
|
|
123
|
-
render(
|
|
133
|
+
const { findByTestId } = render(
|
|
124
134
|
<ConfigProvider>
|
|
125
135
|
<App>
|
|
126
136
|
<FlowEngineProvider engine={engine}>
|
|
@@ -130,7 +140,7 @@ describe('Delete problematic model via FlowSettings menu', () => {
|
|
|
130
140
|
</ConfigProvider>,
|
|
131
141
|
);
|
|
132
142
|
|
|
133
|
-
await
|
|
143
|
+
await hoverHostAndClickDelete(await findByTestId('result'));
|
|
134
144
|
expect(engine.getModel(model.uid)).toBeUndefined();
|
|
135
145
|
});
|
|
136
146
|
|
|
@@ -154,7 +164,7 @@ describe('Delete problematic model via FlowSettings menu', () => {
|
|
|
154
164
|
}
|
|
155
165
|
|
|
156
166
|
const engine = new FlowEngine();
|
|
157
|
-
engine.flowSettings.forceEnable();
|
|
167
|
+
await engine.flowSettings.forceEnable();
|
|
158
168
|
engine.registerModels({ ParentModel, BrokenChild });
|
|
159
169
|
const parent = engine.createModel({ use: 'ParentModel', uid: 'parent-3' }) as ParentModel;
|
|
160
170
|
const child = engine.createModel({ use: 'BrokenChild', uid: 'child-3' }) as BrokenChild;
|
|
@@ -163,7 +173,7 @@ describe('Delete problematic model via FlowSettings menu', () => {
|
|
|
163
173
|
parent.context.defineProperty('themeToken', { value: { borderRadiusLG: 8 } });
|
|
164
174
|
child.context.defineProperty('themeToken', { value: { borderRadiusLG: 8 } });
|
|
165
175
|
|
|
166
|
-
render(
|
|
176
|
+
const { findByTestId } = render(
|
|
167
177
|
<ConfigProvider>
|
|
168
178
|
<App>
|
|
169
179
|
<FlowEngineProvider engine={engine}>
|
|
@@ -173,7 +183,7 @@ describe('Delete problematic model via FlowSettings menu', () => {
|
|
|
173
183
|
</ConfigProvider>,
|
|
174
184
|
);
|
|
175
185
|
|
|
176
|
-
await
|
|
186
|
+
await hoverHostAndClickDelete(await findByTestId('result'));
|
|
177
187
|
expect(engine.getModel(child.uid)).toBeUndefined();
|
|
178
188
|
const remain = (parent.subModels as any).items || [];
|
|
179
189
|
expect(remain.find((m: FlowModel) => m.uid === child.uid)).toBeUndefined();
|
|
@@ -200,7 +210,7 @@ describe('Delete problematic model via FlowSettings menu', () => {
|
|
|
200
210
|
}
|
|
201
211
|
|
|
202
212
|
const engine = new FlowEngine();
|
|
203
|
-
engine.flowSettings.forceEnable();
|
|
213
|
+
await engine.flowSettings.forceEnable();
|
|
204
214
|
engine.registerModels({ ParentModel, RenderFnChild });
|
|
205
215
|
const parent = engine.createModel({ use: 'ParentModel', uid: 'parent-4' }) as ParentModel;
|
|
206
216
|
const child = engine.createModel({ use: 'RenderFnChild', uid: 'cell-4' }) as RenderFnChild;
|
|
@@ -208,7 +218,7 @@ describe('Delete problematic model via FlowSettings menu', () => {
|
|
|
208
218
|
parent.context.defineProperty('themeToken', { value: { borderRadiusLG: 8 } });
|
|
209
219
|
child.context.defineProperty('themeToken', { value: { borderRadiusLG: 8 } });
|
|
210
220
|
|
|
211
|
-
render(
|
|
221
|
+
const { findByTestId } = render(
|
|
212
222
|
<ConfigProvider>
|
|
213
223
|
<App>
|
|
214
224
|
<FlowEngineProvider engine={engine}>
|
|
@@ -218,7 +228,7 @@ describe('Delete problematic model via FlowSettings menu', () => {
|
|
|
218
228
|
</ConfigProvider>,
|
|
219
229
|
);
|
|
220
230
|
|
|
221
|
-
await
|
|
231
|
+
await hoverHostAndClickDelete(await findByTestId('result'));
|
|
222
232
|
expect(engine.getModel(child.uid)).toBeUndefined();
|
|
223
233
|
const remain = (parent.subModels as any).cells || [];
|
|
224
234
|
expect(remain.find((m: FlowModel) => m.uid === child.uid)).toBeUndefined();
|