@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.
Files changed (160) hide show
  1. package/LICENSE +201 -661
  2. package/README.md +79 -10
  3. package/lib/JSRunner.d.ts +10 -1
  4. package/lib/JSRunner.js +50 -5
  5. package/lib/ViewScopedFlowEngine.js +5 -1
  6. package/lib/components/FieldModelRenderer.js +2 -2
  7. package/lib/components/FlowModelRenderer.d.ts +3 -1
  8. package/lib/components/FlowModelRenderer.js +12 -6
  9. package/lib/components/MobilePopup.js +6 -5
  10. package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
  11. package/lib/components/dnd/gridDragPlanner.js +601 -21
  12. package/lib/components/dnd/index.d.ts +19 -1
  13. package/lib/components/dnd/index.js +243 -23
  14. package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
  15. package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
  16. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
  17. package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +68 -10
  18. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
  19. package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
  20. package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
  21. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
  22. package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
  23. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
  24. package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
  25. package/lib/components/subModel/AddSubModelButton.js +27 -1
  26. package/lib/components/subModel/index.d.ts +1 -0
  27. package/lib/components/subModel/index.js +19 -0
  28. package/lib/components/subModel/utils.d.ts +1 -1
  29. package/lib/components/subModel/utils.js +2 -2
  30. package/lib/data-source/index.d.ts +73 -0
  31. package/lib/data-source/index.js +211 -1
  32. package/lib/executor/FlowExecutor.js +31 -8
  33. package/lib/flowContext.d.ts +2 -0
  34. package/lib/flowContext.js +31 -1
  35. package/lib/flowEngine.d.ts +151 -1
  36. package/lib/flowEngine.js +389 -15
  37. package/lib/flowI18n.js +2 -1
  38. package/lib/flowSettings.d.ts +14 -6
  39. package/lib/flowSettings.js +34 -6
  40. package/lib/lazy-helper.d.ts +14 -0
  41. package/lib/lazy-helper.js +71 -0
  42. package/lib/locale/en-US.json +1 -0
  43. package/lib/locale/index.d.ts +2 -0
  44. package/lib/locale/zh-CN.json +1 -0
  45. package/lib/models/DisplayItemModel.d.ts +1 -1
  46. package/lib/models/EditableItemModel.d.ts +1 -1
  47. package/lib/models/FilterableItemModel.d.ts +1 -1
  48. package/lib/models/flowModel.d.ts +13 -10
  49. package/lib/models/flowModel.js +78 -18
  50. package/lib/provider.js +38 -23
  51. package/lib/reactive/observer.js +46 -16
  52. package/lib/runjs-context/registry.d.ts +1 -1
  53. package/lib/runjs-context/setup.js +20 -12
  54. package/lib/runjs-context/snippets/index.js +13 -2
  55. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
  56. package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
  57. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
  58. package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
  59. package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
  60. package/lib/scheduler/ModelOperationScheduler.js +3 -2
  61. package/lib/types.d.ts +47 -1
  62. package/lib/utils/createCollectionContextMeta.js +6 -2
  63. package/lib/utils/index.d.ts +2 -2
  64. package/lib/utils/index.js +4 -0
  65. package/lib/utils/parsePathnameToViewParams.js +1 -1
  66. package/lib/utils/runjsTemplateCompat.js +1 -1
  67. package/lib/utils/runjsValue.js +41 -11
  68. package/lib/utils/schema-utils.d.ts +7 -1
  69. package/lib/utils/schema-utils.js +19 -0
  70. package/lib/views/FlowView.d.ts +7 -1
  71. package/lib/views/runViewBeforeClose.d.ts +10 -0
  72. package/lib/views/runViewBeforeClose.js +45 -0
  73. package/lib/views/useDialog.d.ts +2 -1
  74. package/lib/views/useDialog.js +20 -3
  75. package/lib/views/useDrawer.d.ts +2 -1
  76. package/lib/views/useDrawer.js +20 -3
  77. package/lib/views/usePage.d.ts +2 -1
  78. package/lib/views/usePage.js +10 -3
  79. package/package.json +6 -5
  80. package/src/JSRunner.ts +68 -4
  81. package/src/ViewScopedFlowEngine.ts +4 -0
  82. package/src/__tests__/JSRunner.test.ts +27 -1
  83. package/src/__tests__/flow-engine.test.ts +166 -0
  84. package/src/__tests__/flowContext.test.ts +65 -1
  85. package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
  86. package/src/__tests__/flowSettings.test.ts +94 -15
  87. package/src/__tests__/objectVariable.test.ts +24 -0
  88. package/src/__tests__/provider.test.tsx +24 -2
  89. package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
  90. package/src/__tests__/runjsContext.test.ts +16 -0
  91. package/src/__tests__/runjsContextRuntime.test.ts +2 -0
  92. package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
  93. package/src/__tests__/runjsSnippets.test.ts +21 -0
  94. package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
  95. package/src/components/FieldModelRenderer.tsx +2 -1
  96. package/src/components/FlowModelRenderer.tsx +18 -6
  97. package/src/components/MobilePopup.tsx +4 -2
  98. package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
  99. package/src/components/__tests__/dnd.test.ts +44 -0
  100. package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
  101. package/src/components/__tests__/gridDragPlanner.test.ts +512 -3
  102. package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
  103. package/src/components/dnd/gridDragPlanner.ts +743 -19
  104. package/src/components/dnd/index.tsx +291 -27
  105. package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
  106. package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +88 -10
  107. package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
  108. package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
  109. package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +189 -3
  110. package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
  111. package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
  112. package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
  113. package/src/components/subModel/AddSubModelButton.tsx +32 -2
  114. package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +142 -32
  115. package/src/components/subModel/index.ts +1 -0
  116. package/src/components/subModel/utils.ts +1 -1
  117. package/src/data-source/__tests__/index.test.ts +34 -1
  118. package/src/data-source/index.ts +258 -2
  119. package/src/executor/FlowExecutor.ts +34 -9
  120. package/src/executor/__tests__/flowExecutor.test.ts +57 -0
  121. package/src/flowContext.ts +37 -3
  122. package/src/flowEngine.ts +445 -11
  123. package/src/flowI18n.ts +2 -1
  124. package/src/flowSettings.ts +40 -6
  125. package/src/lazy-helper.tsx +57 -0
  126. package/src/locale/en-US.json +1 -0
  127. package/src/locale/zh-CN.json +1 -0
  128. package/src/models/DisplayItemModel.tsx +1 -1
  129. package/src/models/EditableItemModel.tsx +1 -1
  130. package/src/models/FilterableItemModel.tsx +1 -1
  131. package/src/models/__tests__/dispatchEvent.when.test.ts +214 -0
  132. package/src/models/__tests__/flowModel.test.ts +19 -3
  133. package/src/models/flowModel.tsx +119 -33
  134. package/src/provider.tsx +41 -25
  135. package/src/reactive/__tests__/observer.test.tsx +82 -0
  136. package/src/reactive/observer.tsx +87 -25
  137. package/src/runjs-context/registry.ts +1 -1
  138. package/src/runjs-context/setup.ts +22 -12
  139. package/src/runjs-context/snippets/index.ts +12 -1
  140. package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
  141. package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
  142. package/src/scheduler/ModelOperationScheduler.ts +14 -3
  143. package/src/types.ts +60 -0
  144. package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
  145. package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
  146. package/src/utils/__tests__/runjsValue.test.ts +11 -0
  147. package/src/utils/__tests__/utils.test.ts +62 -0
  148. package/src/utils/createCollectionContextMeta.ts +6 -2
  149. package/src/utils/index.ts +2 -1
  150. package/src/utils/parsePathnameToViewParams.ts +2 -2
  151. package/src/utils/runjsTemplateCompat.ts +1 -1
  152. package/src/utils/runjsValue.ts +50 -11
  153. package/src/utils/schema-utils.ts +30 -1
  154. package/src/views/FlowView.tsx +11 -1
  155. package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
  156. package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
  157. package/src/views/runViewBeforeClose.ts +19 -0
  158. package/src/views/useDialog.tsx +25 -3
  159. package/src/views/useDrawer.tsx +25 -3
  160. 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 { FlowModelRenderer, FlowModelRendererProps } from '@nocobase/flow-engine';
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: model.context.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: typeof useCache === 'boolean' ? useCache : model.context.useCache,
363
+ value: resolvedUseCache,
353
364
  });
354
365
  }
355
- }, [model?.context, useCache]);
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 clickDeleteFromLastDropdown();
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 clickDeleteFromLastDropdown();
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 clickDeleteFromLastDropdown();
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();