@nocobase/flow-engine 2.1.0-beta.9 → 2.1.1
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/lib/FlowContextProvider.d.ts +5 -1
- package/lib/FlowContextProvider.js +9 -2
- 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 +607 -19
- 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 +152 -42
- 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/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 +12 -1
- package/lib/components/subModel/LazyDropdown.js +301 -52
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +2 -1
- package/lib/components/subModel/utils.js +15 -5
- 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 +84 -0
- package/lib/data-source/index.js +269 -7
- package/lib/executor/FlowExecutor.js +6 -3
- 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 +9 -1
- package/lib/flowContext.js +77 -6
- package/lib/flowEngine.d.ts +136 -4
- package/lib/flowEngine.js +429 -51
- 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 +126 -34
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +4 -15
- package/lib/runjs-context/contexts/JSColumnRunJSContext.js +5 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +5 -8
- package/lib/runjs-context/contexts/JSFieldRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/base.js +464 -29
- package/lib/runjs-context/contexts/elementDoc.d.ts +11 -0
- package/lib/runjs-context/contexts/elementDoc.js +152 -0
- package/lib/runjs-context/setup.js +1 -0
- 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/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/loadedPageCache.d.ts +24 -0
- package/lib/utils/loadedPageCache.js +139 -0
- package/lib/utils/parsePathnameToViewParams.d.ts +5 -1
- package/lib/utils/parsePathnameToViewParams.js +28 -4
- 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.d.ts +12 -2
- package/lib/views/ViewNavigation.js +28 -9
- package/lib/views/createViewMeta.js +114 -50
- package/lib/views/inheritLayoutContext.d.ts +10 -0
- package/lib/views/inheritLayoutContext.js +50 -0
- 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 +12 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +12 -3
- package/lib/views/usePage.d.ts +5 -11
- package/lib/views/usePage.js +304 -144
- package/package.json +5 -4
- package/src/FlowContextProvider.tsx +9 -1
- package/src/__tests__/createViewMeta.popup.test.ts +115 -1
- package/src/__tests__/flow-engine.test.ts +166 -0
- package/src/__tests__/flowContext.test.ts +105 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowEngine.moveModel.test.ts +81 -1
- package/src/__tests__/flowEngine.removeModel.test.ts +47 -3
- 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 +21 -0
- package/src/__tests__/runjsContextImplementations.test.ts +9 -2
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsLocales.test.ts +6 -5
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +136 -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 +472 -5
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +750 -17
- 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 +178 -48
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +344 -8
- 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 +16 -2
- package/src/components/subModel/LazyDropdown.tsx +341 -56
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +524 -38
- package/src/components/subModel/__tests__/utils.test.ts +24 -0
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +13 -2
- 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 +69 -2
- package/src/data-source/index.ts +332 -8
- package/src/executor/FlowExecutor.ts +6 -3
- 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 +85 -6
- package/src/flowEngine.ts +484 -45
- 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__/flowEngine.resolveUse.test.ts +0 -15
- package/src/models/__tests__/flowModel.test.ts +65 -37
- package/src/models/flowModel.tsx +184 -65
- 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/contexts/FormJSFieldItemRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +4 -15
- package/src/runjs-context/contexts/JSColumnRunJSContext.ts +4 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +5 -9
- package/src/runjs-context/contexts/JSFieldRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/base.ts +467 -31
- package/src/runjs-context/contexts/elementDoc.ts +130 -0
- package/src/runjs-context/setup.ts +1 -0
- 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/types.ts +62 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +21 -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/loadedPageCache.ts +147 -0
- package/src/utils/parsePathnameToViewParams.ts +45 -5
- 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 +46 -9
- package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
- package/src/views/__tests__/ViewNavigation.test.ts +52 -0
- package/src/views/__tests__/inheritLayoutContext.test.ts +53 -0
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +12 -12
- package/src/views/createViewMeta.ts +106 -34
- package/src/views/inheritLayoutContext.ts +26 -0
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +13 -3
- package/src/views/useDrawer.tsx +13 -3
- package/src/views/usePage.tsx +367 -180
|
@@ -7,10 +7,10 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
11
|
-
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
12
|
-
import { render, cleanup, waitFor, act } from '@testing-library/react';
|
|
10
|
+
import { act, cleanup, render, waitFor } from '@testing-library/react';
|
|
13
11
|
import { App, ConfigProvider } from 'antd';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
14
14
|
|
|
15
15
|
import { FlowEngine } from '../../../../../flowEngine';
|
|
16
16
|
import { FlowModel } from '../../../../../models/flowModel';
|
|
@@ -37,6 +37,7 @@ vi.mock('antd', async (importOriginal) => {
|
|
|
37
37
|
(globalThis as any).__lastDropdownMenu = props.menu;
|
|
38
38
|
(globalThis as any).__lastDropdownOnOpenChange = props.onOpenChange;
|
|
39
39
|
(globalThis as any).__lastDropdownOpen = props.open;
|
|
40
|
+
(globalThis as any).__lastDropdownGetPopupContainer = props.getPopupContainer;
|
|
40
41
|
dropdownMenus.push(props.menu);
|
|
41
42
|
return React.createElement('span', { 'data-testid': 'dropdown' }, props.children);
|
|
42
43
|
};
|
|
@@ -105,7 +106,7 @@ const findElement = (node: any, predicate: (element: React.ReactElement) => bool
|
|
|
105
106
|
return node;
|
|
106
107
|
}
|
|
107
108
|
|
|
108
|
-
const children = React.Children.toArray(node.props?.children);
|
|
109
|
+
const children = React.Children.toArray((node as React.ReactElement<any>).props?.children);
|
|
109
110
|
for (const child of children) {
|
|
110
111
|
const matched = findElement(child, predicate);
|
|
111
112
|
if (matched) {
|
|
@@ -132,6 +133,7 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
132
133
|
(globalThis as any).__lastDropdownMenu = undefined;
|
|
133
134
|
(globalThis as any).__lastDropdownOnOpenChange = undefined;
|
|
134
135
|
(globalThis as any).__lastDropdownOpen = undefined;
|
|
136
|
+
(globalThis as any).__lastDropdownGetPopupContainer = undefined;
|
|
135
137
|
});
|
|
136
138
|
|
|
137
139
|
afterEach(() => {
|
|
@@ -139,6 +141,86 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
139
141
|
vi.clearAllMocks();
|
|
140
142
|
});
|
|
141
143
|
|
|
144
|
+
it('defers nested configurable step resolution and clears stale config while closed', async () => {
|
|
145
|
+
class TestFlowModel extends FlowModel {}
|
|
146
|
+
|
|
147
|
+
const engine = new FlowEngine();
|
|
148
|
+
const model = new TestFlowModel({ uid: 'model-lazy-settings', flowEngine: engine });
|
|
149
|
+
const hideInSettings = vi.fn((ctx) => !!ctx.getStepParams('general')?.hidden);
|
|
150
|
+
const uiSchema = vi.fn(() => ({
|
|
151
|
+
field: { type: 'string', 'x-component': 'Input' },
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
TestFlowModel.registerFlow({
|
|
155
|
+
key: 'lazyFlow',
|
|
156
|
+
title: 'Lazy Flow',
|
|
157
|
+
steps: {
|
|
158
|
+
general: {
|
|
159
|
+
title: 'General',
|
|
160
|
+
hideInSettings,
|
|
161
|
+
uiSchema,
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const { getByLabelText } = render(
|
|
167
|
+
React.createElement(
|
|
168
|
+
ConfigProvider as any,
|
|
169
|
+
null,
|
|
170
|
+
React.createElement(
|
|
171
|
+
App as any,
|
|
172
|
+
null,
|
|
173
|
+
React.createElement(DefaultSettingsIcon as any, { model, menuLevels: 2 }),
|
|
174
|
+
),
|
|
175
|
+
),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
expect(getByLabelText('flows-settings')).toBeTruthy();
|
|
179
|
+
expect(hideInSettings).not.toHaveBeenCalled();
|
|
180
|
+
expect(uiSchema).not.toHaveBeenCalled();
|
|
181
|
+
|
|
182
|
+
await act(async () => {
|
|
183
|
+
(globalThis as any).__lastDropdownOnOpenChange?.(true, { source: 'trigger' });
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
await waitFor(() => {
|
|
187
|
+
expect(hideInSettings).toHaveBeenCalledTimes(1);
|
|
188
|
+
expect(uiSchema).toHaveBeenCalledTimes(1);
|
|
189
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
190
|
+
const items = (menu?.items || []) as any[];
|
|
191
|
+
expect(items.some((it) => String(it.key || '') === 'lazyFlow:general')).toBe(true);
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
await act(async () => {
|
|
195
|
+
(globalThis as any).__lastDropdownOnOpenChange?.(false, { source: 'trigger' });
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
await waitFor(() => {
|
|
199
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
200
|
+
const items = (menu?.items || []) as any[];
|
|
201
|
+
expect(items.some((it) => String(it.key || '') === 'lazyFlow:general')).toBe(false);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
await act(async () => {
|
|
205
|
+
model.setStepParams('lazyFlow', 'general', { hidden: true });
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
expect(hideInSettings).toHaveBeenCalledTimes(1);
|
|
209
|
+
expect(uiSchema).toHaveBeenCalledTimes(1);
|
|
210
|
+
|
|
211
|
+
await act(async () => {
|
|
212
|
+
(globalThis as any).__lastDropdownOnOpenChange?.(true, { source: 'trigger' });
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
await waitFor(() => {
|
|
216
|
+
expect(hideInSettings).toHaveBeenCalledTimes(2);
|
|
217
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
218
|
+
const items = (menu?.items || []) as any[];
|
|
219
|
+
expect(items.some((it) => String(it.key || '') === 'lazyFlow:general')).toBe(false);
|
|
220
|
+
});
|
|
221
|
+
expect(uiSchema).toHaveBeenCalledTimes(1);
|
|
222
|
+
});
|
|
223
|
+
|
|
142
224
|
it('excludes instance (dynamic) flows from the settings menu', async () => {
|
|
143
225
|
class TestFlowModel extends FlowModel {}
|
|
144
226
|
|
|
@@ -322,7 +404,9 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
322
404
|
);
|
|
323
405
|
expect(tooltipElement).toBeTruthy();
|
|
324
406
|
|
|
325
|
-
const iconElement = React.isValidElement(tooltipElement)
|
|
407
|
+
const iconElement = React.isValidElement(tooltipElement)
|
|
408
|
+
? (tooltipElement as React.ReactElement<any>).props.children
|
|
409
|
+
: null;
|
|
326
410
|
expect(React.isValidElement(iconElement)).toBe(true);
|
|
327
411
|
expect((iconElement as any).props?.style?.color).toBe(mockColorTextTertiary);
|
|
328
412
|
|
|
@@ -414,6 +498,99 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
414
498
|
});
|
|
415
499
|
});
|
|
416
500
|
|
|
501
|
+
it('prefers the local toolbar container as popup host inside contextual toolbars', async () => {
|
|
502
|
+
class TestFlowModel extends FlowModel {}
|
|
503
|
+
const engine = new FlowEngine();
|
|
504
|
+
const model = new TestFlowModel({ uid: 'm-toolbar-popup-host', flowEngine: engine });
|
|
505
|
+
const externalPopupRoot = document.createElement('div');
|
|
506
|
+
externalPopupRoot.id = 'external-popup-root';
|
|
507
|
+
document.body.appendChild(externalPopupRoot);
|
|
508
|
+
|
|
509
|
+
TestFlowModel.registerFlow({
|
|
510
|
+
key: 'flowPopupHost',
|
|
511
|
+
title: 'Flow Popup Host',
|
|
512
|
+
steps: {
|
|
513
|
+
general: { title: 'General', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } },
|
|
514
|
+
},
|
|
515
|
+
});
|
|
516
|
+
|
|
517
|
+
const { getByTestId, unmount } = render(
|
|
518
|
+
React.createElement(
|
|
519
|
+
ConfigProvider as any,
|
|
520
|
+
null,
|
|
521
|
+
React.createElement(
|
|
522
|
+
App as any,
|
|
523
|
+
null,
|
|
524
|
+
React.createElement(
|
|
525
|
+
'div',
|
|
526
|
+
{ className: 'nb-toolbar-container' },
|
|
527
|
+
React.createElement(
|
|
528
|
+
'div',
|
|
529
|
+
{ className: 'nb-toolbar-container-icons' },
|
|
530
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
531
|
+
model,
|
|
532
|
+
getPopupContainer: () => externalPopupRoot,
|
|
533
|
+
}),
|
|
534
|
+
),
|
|
535
|
+
),
|
|
536
|
+
),
|
|
537
|
+
),
|
|
538
|
+
);
|
|
539
|
+
|
|
540
|
+
await waitFor(() => {
|
|
541
|
+
expect((globalThis as any).__lastDropdownGetPopupContainer).toBeTruthy();
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
const popupContainer = (globalThis as any).__lastDropdownGetPopupContainer?.(getByTestId('dropdown'));
|
|
545
|
+
expect(popupContainer).toBeTruthy();
|
|
546
|
+
expect(popupContainer?.className).toContain('nb-toolbar-container-icons');
|
|
547
|
+
|
|
548
|
+
unmount();
|
|
549
|
+
externalPopupRoot.remove();
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
it('falls back to the provided popup host outside contextual toolbars', async () => {
|
|
553
|
+
class TestFlowModel extends FlowModel {}
|
|
554
|
+
const engine = new FlowEngine();
|
|
555
|
+
const model = new TestFlowModel({ uid: 'm-external-popup-host', flowEngine: engine });
|
|
556
|
+
const externalPopupRoot = document.createElement('div');
|
|
557
|
+
externalPopupRoot.id = 'external-popup-root';
|
|
558
|
+
document.body.appendChild(externalPopupRoot);
|
|
559
|
+
|
|
560
|
+
TestFlowModel.registerFlow({
|
|
561
|
+
key: 'flowExternalPopupHost',
|
|
562
|
+
title: 'Flow External Popup Host',
|
|
563
|
+
steps: {
|
|
564
|
+
general: { title: 'General', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } },
|
|
565
|
+
},
|
|
566
|
+
});
|
|
567
|
+
|
|
568
|
+
const { getByTestId, unmount } = render(
|
|
569
|
+
React.createElement(
|
|
570
|
+
ConfigProvider as any,
|
|
571
|
+
null,
|
|
572
|
+
React.createElement(
|
|
573
|
+
App as any,
|
|
574
|
+
null,
|
|
575
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
576
|
+
model,
|
|
577
|
+
getPopupContainer: () => externalPopupRoot,
|
|
578
|
+
}),
|
|
579
|
+
),
|
|
580
|
+
),
|
|
581
|
+
);
|
|
582
|
+
|
|
583
|
+
await waitFor(() => {
|
|
584
|
+
expect((globalThis as any).__lastDropdownGetPopupContainer).toBeTruthy();
|
|
585
|
+
});
|
|
586
|
+
|
|
587
|
+
const popupContainer = (globalThis as any).__lastDropdownGetPopupContainer?.(getByTestId('dropdown'));
|
|
588
|
+
expect(popupContainer).toBe(externalPopupRoot);
|
|
589
|
+
|
|
590
|
+
unmount();
|
|
591
|
+
externalPopupRoot.remove();
|
|
592
|
+
});
|
|
593
|
+
|
|
417
594
|
it('copy UID action writes model uid to clipboard', async () => {
|
|
418
595
|
class TestFlowModel extends FlowModel {}
|
|
419
596
|
const engine = new FlowEngine();
|
|
@@ -517,7 +694,9 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
517
694
|
const items = (menu?.items || []) as any[];
|
|
518
695
|
const subMenu = items.find((it) => Array.isArray(it?.children));
|
|
519
696
|
expect(subMenu).toBeTruthy();
|
|
520
|
-
expect(subMenu
|
|
697
|
+
expect((subMenu?.children || []).some((it: any) => String(it.key).startsWith('items[0]:childFlow:cstep'))).toBe(
|
|
698
|
+
true,
|
|
699
|
+
);
|
|
521
700
|
});
|
|
522
701
|
});
|
|
523
702
|
|
|
@@ -621,6 +800,10 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
621
800
|
),
|
|
622
801
|
);
|
|
623
802
|
|
|
803
|
+
await act(async () => {
|
|
804
|
+
(globalThis as any).__lastDropdownOnOpenChange?.(true, { source: 'trigger' });
|
|
805
|
+
});
|
|
806
|
+
|
|
624
807
|
await waitFor(() => {
|
|
625
808
|
const menu = (globalThis as any).__lastDropdownMenu;
|
|
626
809
|
expect(menu).toBeTruthy();
|
|
@@ -701,17 +884,170 @@ describe('DefaultSettingsIcon - extra menu items', () => {
|
|
|
701
884
|
await waitFor(() => {
|
|
702
885
|
const menu = (globalThis as any).__lastDropdownMenu;
|
|
703
886
|
const items = (menu?.items || []) as any[];
|
|
704
|
-
|
|
887
|
+
const extraActionItem = items.find((it) => String(it.key || '') === 'extra-action');
|
|
888
|
+
expect(extraActionItem).toBeTruthy();
|
|
889
|
+
expect(extraActionItem.onClick).toBeUndefined();
|
|
705
890
|
});
|
|
706
891
|
|
|
707
892
|
const menu = (globalThis as any).__lastDropdownMenu;
|
|
708
893
|
await act(async () => {
|
|
709
894
|
menu.onClick?.({ key: 'extra-action' });
|
|
710
895
|
});
|
|
711
|
-
expect(onClick).
|
|
896
|
+
expect(onClick).toHaveBeenCalledTimes(1);
|
|
897
|
+
expect((globalThis as any).__lastDropdownOpen).toBe(false);
|
|
898
|
+
} finally {
|
|
899
|
+
dispose?.();
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
it('supports nested extra menu items with sorting and disabled states', async () => {
|
|
904
|
+
const onInsertBefore = vi.fn();
|
|
905
|
+
const onInsertAfter = vi.fn();
|
|
906
|
+
|
|
907
|
+
class TestFlowModel extends FlowModel {}
|
|
908
|
+
const dispose = TestFlowModel.registerExtraMenuItems({
|
|
909
|
+
group: 'common-actions',
|
|
910
|
+
sort: 10,
|
|
911
|
+
items: [
|
|
912
|
+
{
|
|
913
|
+
key: 'insert-actions',
|
|
914
|
+
label: 'Insert actions',
|
|
915
|
+
children: [
|
|
916
|
+
{ key: 'insert-after', label: 'Insert after', sort: 20, onClick: onInsertAfter },
|
|
917
|
+
{ key: 'insert-before', label: 'Insert before', sort: 10, onClick: onInsertBefore },
|
|
918
|
+
{ key: 'insert-inner', label: 'Insert inner', sort: 30, disabled: true, onClick: vi.fn() },
|
|
919
|
+
],
|
|
920
|
+
},
|
|
921
|
+
],
|
|
922
|
+
});
|
|
923
|
+
|
|
924
|
+
const engine = new FlowEngine();
|
|
925
|
+
const model = new TestFlowModel({ uid: 'm-extra-nested', flowEngine: engine });
|
|
926
|
+
|
|
927
|
+
TestFlowModel.registerFlow({
|
|
928
|
+
key: 'flow',
|
|
929
|
+
title: 'Flow',
|
|
930
|
+
steps: { s: { title: 'S', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } } },
|
|
931
|
+
});
|
|
932
|
+
|
|
933
|
+
try {
|
|
934
|
+
render(
|
|
935
|
+
React.createElement(
|
|
936
|
+
ConfigProvider as any,
|
|
937
|
+
null,
|
|
938
|
+
React.createElement(
|
|
939
|
+
App as any,
|
|
940
|
+
null,
|
|
941
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
942
|
+
model,
|
|
943
|
+
showCopyUidButton: false,
|
|
944
|
+
showDeleteButton: false,
|
|
945
|
+
}),
|
|
946
|
+
),
|
|
947
|
+
),
|
|
948
|
+
);
|
|
949
|
+
|
|
950
|
+
await waitFor(() => {
|
|
951
|
+
expect((globalThis as any).__lastDropdownMenu).toBeTruthy();
|
|
952
|
+
expect((globalThis as any).__lastDropdownOnOpenChange).toBeTruthy();
|
|
953
|
+
});
|
|
954
|
+
|
|
955
|
+
await act(async () => {
|
|
956
|
+
(globalThis as any).__lastDropdownOnOpenChange?.(true, { source: 'trigger' });
|
|
957
|
+
});
|
|
958
|
+
|
|
959
|
+
await waitFor(() => {
|
|
960
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
961
|
+
const items = (menu?.items || []) as any[];
|
|
962
|
+
const nested = items.find((it) => String(it.key || '') === 'insert-actions');
|
|
963
|
+
expect(nested).toBeTruthy();
|
|
964
|
+
expect((nested.children || []).map((it) => String(it.key || ''))).toEqual([
|
|
965
|
+
'insert-before',
|
|
966
|
+
'insert-after',
|
|
967
|
+
'insert-inner',
|
|
968
|
+
]);
|
|
969
|
+
expect((nested.children || []).find((it) => String(it.key || '') === 'insert-before')?.onClick).toBeUndefined();
|
|
970
|
+
expect((nested.children || []).find((it) => String(it.key || '') === 'insert-inner')?.disabled).toBe(true);
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
974
|
+
await act(async () => {
|
|
975
|
+
menu.onClick?.({ key: 'insert-inner' });
|
|
976
|
+
});
|
|
977
|
+
expect(onInsertBefore).not.toHaveBeenCalled();
|
|
978
|
+
expect(onInsertAfter).not.toHaveBeenCalled();
|
|
979
|
+
expect((globalThis as any).__lastDropdownOpen).toBe(true);
|
|
980
|
+
|
|
981
|
+
await act(async () => {
|
|
982
|
+
menu.onClick?.({ key: 'insert-before' });
|
|
983
|
+
});
|
|
984
|
+
expect(onInsertBefore).toHaveBeenCalledTimes(1);
|
|
712
985
|
expect((globalThis as any).__lastDropdownOpen).toBe(false);
|
|
713
986
|
} finally {
|
|
714
987
|
dispose?.();
|
|
715
988
|
}
|
|
716
989
|
});
|
|
990
|
+
|
|
991
|
+
it('uses common extra actions to defer nested configurable step resolution', async () => {
|
|
992
|
+
const onClick = vi.fn();
|
|
993
|
+
|
|
994
|
+
class TestFlowModel extends FlowModel {}
|
|
995
|
+
const dispose = TestFlowModel.registerExtraMenuItems({
|
|
996
|
+
group: 'common-actions',
|
|
997
|
+
sort: 10,
|
|
998
|
+
items: [{ key: 'extra-action', label: 'Extra Action', onClick }],
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
const engine = new FlowEngine();
|
|
1002
|
+
const model = new TestFlowModel({ uid: 'm-extra-lazy', flowEngine: engine });
|
|
1003
|
+
const uiSchema = vi.fn(() => ({
|
|
1004
|
+
f: { type: 'string', 'x-component': 'Input' },
|
|
1005
|
+
}));
|
|
1006
|
+
|
|
1007
|
+
TestFlowModel.registerFlow({
|
|
1008
|
+
key: 'flow',
|
|
1009
|
+
title: 'Flow',
|
|
1010
|
+
steps: { s: { title: 'S', uiSchema } },
|
|
1011
|
+
});
|
|
1012
|
+
|
|
1013
|
+
try {
|
|
1014
|
+
const { getByLabelText } = render(
|
|
1015
|
+
React.createElement(
|
|
1016
|
+
ConfigProvider as any,
|
|
1017
|
+
null,
|
|
1018
|
+
React.createElement(
|
|
1019
|
+
App as any,
|
|
1020
|
+
null,
|
|
1021
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
1022
|
+
model,
|
|
1023
|
+
menuLevels: 2,
|
|
1024
|
+
showCopyUidButton: false,
|
|
1025
|
+
showDeleteButton: false,
|
|
1026
|
+
}),
|
|
1027
|
+
),
|
|
1028
|
+
),
|
|
1029
|
+
);
|
|
1030
|
+
|
|
1031
|
+
await waitFor(() => {
|
|
1032
|
+
expect(getByLabelText('flows-settings')).toBeTruthy();
|
|
1033
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
1034
|
+
const items = (menu?.items || []) as any[];
|
|
1035
|
+
expect(items.some((it) => String(it.key || '') === 'extra-action')).toBe(true);
|
|
1036
|
+
});
|
|
1037
|
+
expect(uiSchema).not.toHaveBeenCalled();
|
|
1038
|
+
|
|
1039
|
+
await act(async () => {
|
|
1040
|
+
(globalThis as any).__lastDropdownOnOpenChange?.(true, { source: 'trigger' });
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
await waitFor(() => {
|
|
1044
|
+
expect(uiSchema).toHaveBeenCalledTimes(1);
|
|
1045
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
1046
|
+
const items = (menu?.items || []) as any[];
|
|
1047
|
+
expect(items.some((it) => String(it.key || '') === 'flow:s')).toBe(true);
|
|
1048
|
+
});
|
|
1049
|
+
} finally {
|
|
1050
|
+
dispose?.();
|
|
1051
|
+
}
|
|
1052
|
+
});
|
|
717
1053
|
});
|