@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
|
@@ -134,6 +134,17 @@ const openStepSettingsDialog = async ({
|
|
|
134
134
|
};
|
|
135
135
|
|
|
136
136
|
const openView = model.context.viewer[mode].bind(model.context.viewer);
|
|
137
|
+
const resolvedUiModeProps = toJS(uiModeProps) || {};
|
|
138
|
+
const { zIndex: uiModeZIndex, ...restUiModeProps } = resolvedUiModeProps;
|
|
139
|
+
const resolveDialogZIndex = (rawZIndex?: number) => {
|
|
140
|
+
const nextZIndex =
|
|
141
|
+
typeof model.context.viewer?.getNextZIndex === 'function'
|
|
142
|
+
? model.context.viewer.getNextZIndex()
|
|
143
|
+
: (model.context.themeToken?.zIndexPopupBase || 1000) + 1;
|
|
144
|
+
const inputZIndex = Number(rawZIndex) || 0;
|
|
145
|
+
return Math.max(nextZIndex, inputZIndex);
|
|
146
|
+
};
|
|
147
|
+
const mergedZIndex = resolveDialogZIndex(uiModeZIndex);
|
|
137
148
|
|
|
138
149
|
const form = createForm({
|
|
139
150
|
initialValues: compileUiSchema(scopes, initialValues),
|
|
@@ -152,7 +163,8 @@ const openStepSettingsDialog = async ({
|
|
|
152
163
|
title: dialogTitle || t(title),
|
|
153
164
|
width: dialogWidth,
|
|
154
165
|
destroyOnClose: true,
|
|
155
|
-
...
|
|
166
|
+
...restUiModeProps,
|
|
167
|
+
zIndex: mergedZIndex,
|
|
156
168
|
// 透传 navigation,便于变量元信息根据真实视图栈推断父级弹窗
|
|
157
169
|
inputArgs,
|
|
158
170
|
onClose: () => {
|
|
@@ -165,7 +177,11 @@ const openStepSettingsDialog = async ({
|
|
|
165
177
|
useEffect(() => {
|
|
166
178
|
return autorun(() => {
|
|
167
179
|
const dynamicProps = toJS(uiModeProps);
|
|
168
|
-
|
|
180
|
+
const { zIndex, ...restDynamicProps } = dynamicProps || {};
|
|
181
|
+
currentDialog.update({
|
|
182
|
+
...restDynamicProps,
|
|
183
|
+
zIndex: resolveDialogZIndex(zIndex),
|
|
184
|
+
});
|
|
169
185
|
});
|
|
170
186
|
}, []);
|
|
171
187
|
|
|
@@ -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(() => {
|
|
@@ -322,7 +324,9 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
322
324
|
);
|
|
323
325
|
expect(tooltipElement).toBeTruthy();
|
|
324
326
|
|
|
325
|
-
const iconElement = React.isValidElement(tooltipElement)
|
|
327
|
+
const iconElement = React.isValidElement(tooltipElement)
|
|
328
|
+
? (tooltipElement as React.ReactElement<any>).props.children
|
|
329
|
+
: null;
|
|
326
330
|
expect(React.isValidElement(iconElement)).toBe(true);
|
|
327
331
|
expect((iconElement as any).props?.style?.color).toBe(mockColorTextTertiary);
|
|
328
332
|
|
|
@@ -414,6 +418,99 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
414
418
|
});
|
|
415
419
|
});
|
|
416
420
|
|
|
421
|
+
it('prefers the local toolbar container as popup host inside contextual toolbars', async () => {
|
|
422
|
+
class TestFlowModel extends FlowModel {}
|
|
423
|
+
const engine = new FlowEngine();
|
|
424
|
+
const model = new TestFlowModel({ uid: 'm-toolbar-popup-host', flowEngine: engine });
|
|
425
|
+
const externalPopupRoot = document.createElement('div');
|
|
426
|
+
externalPopupRoot.id = 'external-popup-root';
|
|
427
|
+
document.body.appendChild(externalPopupRoot);
|
|
428
|
+
|
|
429
|
+
TestFlowModel.registerFlow({
|
|
430
|
+
key: 'flowPopupHost',
|
|
431
|
+
title: 'Flow Popup Host',
|
|
432
|
+
steps: {
|
|
433
|
+
general: { title: 'General', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } },
|
|
434
|
+
},
|
|
435
|
+
});
|
|
436
|
+
|
|
437
|
+
const { getByTestId, unmount } = render(
|
|
438
|
+
React.createElement(
|
|
439
|
+
ConfigProvider as any,
|
|
440
|
+
null,
|
|
441
|
+
React.createElement(
|
|
442
|
+
App as any,
|
|
443
|
+
null,
|
|
444
|
+
React.createElement(
|
|
445
|
+
'div',
|
|
446
|
+
{ className: 'nb-toolbar-container' },
|
|
447
|
+
React.createElement(
|
|
448
|
+
'div',
|
|
449
|
+
{ className: 'nb-toolbar-container-icons' },
|
|
450
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
451
|
+
model,
|
|
452
|
+
getPopupContainer: () => externalPopupRoot,
|
|
453
|
+
}),
|
|
454
|
+
),
|
|
455
|
+
),
|
|
456
|
+
),
|
|
457
|
+
),
|
|
458
|
+
);
|
|
459
|
+
|
|
460
|
+
await waitFor(() => {
|
|
461
|
+
expect((globalThis as any).__lastDropdownGetPopupContainer).toBeTruthy();
|
|
462
|
+
});
|
|
463
|
+
|
|
464
|
+
const popupContainer = (globalThis as any).__lastDropdownGetPopupContainer?.(getByTestId('dropdown'));
|
|
465
|
+
expect(popupContainer).toBeTruthy();
|
|
466
|
+
expect(popupContainer?.className).toContain('nb-toolbar-container-icons');
|
|
467
|
+
|
|
468
|
+
unmount();
|
|
469
|
+
externalPopupRoot.remove();
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it('falls back to the provided popup host outside contextual toolbars', async () => {
|
|
473
|
+
class TestFlowModel extends FlowModel {}
|
|
474
|
+
const engine = new FlowEngine();
|
|
475
|
+
const model = new TestFlowModel({ uid: 'm-external-popup-host', flowEngine: engine });
|
|
476
|
+
const externalPopupRoot = document.createElement('div');
|
|
477
|
+
externalPopupRoot.id = 'external-popup-root';
|
|
478
|
+
document.body.appendChild(externalPopupRoot);
|
|
479
|
+
|
|
480
|
+
TestFlowModel.registerFlow({
|
|
481
|
+
key: 'flowExternalPopupHost',
|
|
482
|
+
title: 'Flow External Popup Host',
|
|
483
|
+
steps: {
|
|
484
|
+
general: { title: 'General', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } },
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
const { getByTestId, unmount } = render(
|
|
489
|
+
React.createElement(
|
|
490
|
+
ConfigProvider as any,
|
|
491
|
+
null,
|
|
492
|
+
React.createElement(
|
|
493
|
+
App as any,
|
|
494
|
+
null,
|
|
495
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
496
|
+
model,
|
|
497
|
+
getPopupContainer: () => externalPopupRoot,
|
|
498
|
+
}),
|
|
499
|
+
),
|
|
500
|
+
),
|
|
501
|
+
);
|
|
502
|
+
|
|
503
|
+
await waitFor(() => {
|
|
504
|
+
expect((globalThis as any).__lastDropdownGetPopupContainer).toBeTruthy();
|
|
505
|
+
});
|
|
506
|
+
|
|
507
|
+
const popupContainer = (globalThis as any).__lastDropdownGetPopupContainer?.(getByTestId('dropdown'));
|
|
508
|
+
expect(popupContainer).toBe(externalPopupRoot);
|
|
509
|
+
|
|
510
|
+
unmount();
|
|
511
|
+
externalPopupRoot.remove();
|
|
512
|
+
});
|
|
513
|
+
|
|
417
514
|
it('copy UID action writes model uid to clipboard', async () => {
|
|
418
515
|
class TestFlowModel extends FlowModel {}
|
|
419
516
|
const engine = new FlowEngine();
|
|
@@ -517,7 +614,9 @@ describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
|
517
614
|
const items = (menu?.items || []) as any[];
|
|
518
615
|
const subMenu = items.find((it) => Array.isArray(it?.children));
|
|
519
616
|
expect(subMenu).toBeTruthy();
|
|
520
|
-
expect(subMenu
|
|
617
|
+
expect((subMenu?.children || []).some((it: any) => String(it.key).startsWith('items[0]:childFlow:cstep'))).toBe(
|
|
618
|
+
true,
|
|
619
|
+
);
|
|
521
620
|
});
|
|
522
621
|
});
|
|
523
622
|
|
|
@@ -714,4 +813,91 @@ describe('DefaultSettingsIcon - extra menu items', () => {
|
|
|
714
813
|
dispose?.();
|
|
715
814
|
}
|
|
716
815
|
});
|
|
816
|
+
|
|
817
|
+
it('supports nested extra menu items with sorting and disabled states', async () => {
|
|
818
|
+
const onInsertBefore = vi.fn();
|
|
819
|
+
const onInsertAfter = vi.fn();
|
|
820
|
+
|
|
821
|
+
class TestFlowModel extends FlowModel {}
|
|
822
|
+
const dispose = TestFlowModel.registerExtraMenuItems({
|
|
823
|
+
group: 'common-actions',
|
|
824
|
+
sort: 10,
|
|
825
|
+
items: [
|
|
826
|
+
{
|
|
827
|
+
key: 'insert-actions',
|
|
828
|
+
label: 'Insert actions',
|
|
829
|
+
children: [
|
|
830
|
+
{ key: 'insert-after', label: 'Insert after', sort: 20, onClick: onInsertAfter },
|
|
831
|
+
{ key: 'insert-before', label: 'Insert before', sort: 10, onClick: onInsertBefore },
|
|
832
|
+
{ key: 'insert-inner', label: 'Insert inner', sort: 30, disabled: true, onClick: vi.fn() },
|
|
833
|
+
],
|
|
834
|
+
},
|
|
835
|
+
],
|
|
836
|
+
});
|
|
837
|
+
|
|
838
|
+
const engine = new FlowEngine();
|
|
839
|
+
const model = new TestFlowModel({ uid: 'm-extra-nested', flowEngine: engine });
|
|
840
|
+
|
|
841
|
+
TestFlowModel.registerFlow({
|
|
842
|
+
key: 'flow',
|
|
843
|
+
title: 'Flow',
|
|
844
|
+
steps: { s: { title: 'S', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } } },
|
|
845
|
+
});
|
|
846
|
+
|
|
847
|
+
try {
|
|
848
|
+
render(
|
|
849
|
+
React.createElement(
|
|
850
|
+
ConfigProvider as any,
|
|
851
|
+
null,
|
|
852
|
+
React.createElement(
|
|
853
|
+
App as any,
|
|
854
|
+
null,
|
|
855
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
856
|
+
model,
|
|
857
|
+
showCopyUidButton: false,
|
|
858
|
+
showDeleteButton: false,
|
|
859
|
+
}),
|
|
860
|
+
),
|
|
861
|
+
),
|
|
862
|
+
);
|
|
863
|
+
|
|
864
|
+
await waitFor(() => {
|
|
865
|
+
expect((globalThis as any).__lastDropdownMenu).toBeTruthy();
|
|
866
|
+
expect((globalThis as any).__lastDropdownOnOpenChange).toBeTruthy();
|
|
867
|
+
});
|
|
868
|
+
|
|
869
|
+
await act(async () => {
|
|
870
|
+
(globalThis as any).__lastDropdownOnOpenChange?.(true, { source: 'trigger' });
|
|
871
|
+
});
|
|
872
|
+
|
|
873
|
+
await waitFor(() => {
|
|
874
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
875
|
+
const items = (menu?.items || []) as any[];
|
|
876
|
+
const nested = items.find((it) => String(it.key || '') === 'insert-actions');
|
|
877
|
+
expect(nested).toBeTruthy();
|
|
878
|
+
expect((nested.children || []).map((it) => String(it.key || ''))).toEqual([
|
|
879
|
+
'insert-before',
|
|
880
|
+
'insert-after',
|
|
881
|
+
'insert-inner',
|
|
882
|
+
]);
|
|
883
|
+
expect((nested.children || []).find((it) => String(it.key || '') === 'insert-inner')?.disabled).toBe(true);
|
|
884
|
+
});
|
|
885
|
+
|
|
886
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
887
|
+
await act(async () => {
|
|
888
|
+
menu.onClick?.({ key: 'insert-inner' });
|
|
889
|
+
});
|
|
890
|
+
expect(onInsertBefore).not.toHaveBeenCalled();
|
|
891
|
+
expect(onInsertAfter).not.toHaveBeenCalled();
|
|
892
|
+
expect((globalThis as any).__lastDropdownOpen).toBe(true);
|
|
893
|
+
|
|
894
|
+
await act(async () => {
|
|
895
|
+
menu.onClick?.({ key: 'insert-before' });
|
|
896
|
+
});
|
|
897
|
+
expect(onInsertBefore).toHaveBeenCalledTimes(1);
|
|
898
|
+
expect((globalThis as any).__lastDropdownOpen).toBe(false);
|
|
899
|
+
} finally {
|
|
900
|
+
dispose?.();
|
|
901
|
+
}
|
|
902
|
+
});
|
|
717
903
|
});
|