@nocobase/flow-engine 2.0.0-alpha.9 → 2.0.0-beta.2
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/BlockScopedFlowEngine.d.ts +23 -0
- package/lib/BlockScopedFlowEngine.js +92 -0
- package/lib/FlowDefinition.d.ts +6 -4
- package/lib/JSRunner.js +3 -0
- package/lib/ViewScopedFlowEngine.js +15 -1
- package/lib/acl/Acl.d.ts +12 -12
- package/lib/acl/Acl.js +78 -30
- package/lib/components/DynamicFlowsEditor.js +2 -4
- package/lib/components/FieldModelRenderer.js +10 -8
- package/lib/components/FieldSkeleton.d.ts +10 -0
- package/lib/components/FieldSkeleton.js +64 -0
- package/lib/components/FlowContextSelector.js +19 -3
- package/lib/components/FlowModelRenderer.d.ts +2 -1
- package/lib/components/FlowModelRenderer.js +34 -12
- package/lib/components/FormItem.js +5 -1
- package/lib/components/MobilePopup.d.ts +20 -0
- package/lib/components/MobilePopup.js +102 -0
- package/lib/components/MobilePopup.style.d.ts +17 -0
- package/lib/components/MobilePopup.style.js +186 -0
- package/lib/components/common/withFlowDesignMode.d.ts +1 -1
- package/lib/components/common/withFlowDesignMode.js +5 -5
- package/lib/components/index.d.ts +1 -0
- package/lib/components/index.js +3 -1
- package/lib/components/settings/independents/dropdown/FlowsDropdownButton.js +71 -53
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +19 -0
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +136 -0
- package/lib/components/settings/wrappers/component/SwitchWithTitle.d.ts +10 -0
- package/lib/components/settings/wrappers/component/SwitchWithTitle.js +110 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +221 -93
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +71 -54
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +2 -2
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +63 -23
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +11 -6
- package/lib/components/settings/wrappers/embedded/FlowSettings.js +42 -28
- package/lib/components/settings/wrappers/embedded/FlowsSettings.js +3 -3
- package/lib/components/settings/wrappers/embedded/FlowsSettingsContent.js +52 -32
- package/lib/components/subModel/AddSubModelButton.d.ts +7 -0
- package/lib/components/subModel/AddSubModelButton.js +78 -8
- package/lib/components/subModel/LazyDropdown.js +14 -15
- package/lib/components/subModel/utils.d.ts +1 -1
- package/lib/components/subModel/utils.js +21 -11
- package/lib/components/variables/VariableInput.js +5 -3
- package/lib/components/variables/types.d.ts +2 -0
- package/lib/components/variables/utils.js +4 -2
- package/lib/data-source/index.d.ts +43 -4
- package/lib/data-source/index.js +104 -11
- package/lib/data-source/jioToJoiSchema.js +1 -0
- package/lib/emitter.d.ts +6 -0
- package/lib/emitter.js +12 -0
- package/lib/executor/FlowExecutor.js +48 -7
- package/lib/flow-registry/GlobalFlowRegistry.d.ts +1 -0
- package/lib/flow-registry/GlobalFlowRegistry.js +3 -0
- package/lib/flow-registry/InstanceFlowRegistry.d.ts +1 -0
- package/lib/flow-registry/InstanceFlowRegistry.js +3 -0
- package/lib/flowContext.d.ts +6 -0
- package/lib/flowContext.js +111 -30
- package/lib/flowEngine.d.ts +49 -0
- package/lib/flowEngine.js +265 -10
- package/lib/flowSettings.d.ts +4 -3
- package/lib/flowSettings.js +33 -11
- package/lib/hooks/useApplyAutoFlows.d.ts +1 -0
- package/lib/hooks/useApplyAutoFlows.js +2 -2
- package/lib/index.d.ts +4 -2
- package/lib/index.js +11 -5
- package/lib/locale/de-DE.json +62 -0
- package/lib/locale/en-US.json +57 -45
- package/lib/locale/es-ES.json +62 -0
- package/lib/locale/fr-FR.json +62 -0
- package/lib/locale/hu-HU.json +62 -0
- package/lib/locale/id-ID.json +62 -0
- package/lib/locale/index.d.ts +114 -90
- package/lib/locale/it-IT.json +62 -0
- package/lib/locale/ja-JP.json +62 -0
- package/lib/locale/ko-KR.json +62 -0
- package/lib/locale/nl-NL.json +62 -0
- package/lib/locale/pt-BR.json +62 -0
- package/lib/locale/ru-RU.json +62 -0
- package/lib/locale/tr-TR.json +62 -0
- package/lib/locale/uk-UA.json +62 -0
- package/lib/locale/vi-VN.json +62 -0
- package/lib/locale/zh-CN.json +58 -46
- package/lib/locale/zh-TW.json +62 -0
- package/lib/models/CollectionFieldModel.d.ts +6 -2
- package/lib/models/CollectionFieldModel.js +60 -14
- package/lib/models/flowModel.d.ts +43 -4
- package/lib/models/flowModel.js +128 -26
- package/lib/models/forkFlowModel.d.ts +6 -2
- package/lib/models/forkFlowModel.js +9 -2
- package/lib/provider.d.ts +3 -1
- package/lib/provider.js +4 -3
- package/lib/reactive/index.d.ts +10 -0
- package/lib/reactive/index.js +41 -0
- package/lib/reactive/observer.d.ts +19 -0
- package/lib/reactive/observer.js +109 -0
- package/lib/resources/baseRecordResource.d.ts +1 -0
- package/lib/resources/baseRecordResource.js +14 -3
- package/lib/resources/multiRecordResource.d.ts +4 -2
- package/lib/resources/multiRecordResource.js +15 -6
- package/lib/resources/singleRecordResource.js +6 -3
- package/lib/resources/sqlResource.d.ts +1 -0
- package/lib/resources/sqlResource.js +22 -25
- package/lib/runjs-context/contexts/base.js +42 -6
- package/lib/runjs-context/snippets/global/clipboard-copy-text.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/global/clipboard-copy-text.snippet.js +61 -0
- package/lib/runjs-context/snippets/index.js +3 -0
- package/lib/runjs-context/snippets/scene/block/render-antd-icons.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/render-antd-icons.snippet.js +65 -0
- package/lib/runjs-context/snippets/scene/block/render-button-handler.snippet.js +6 -4
- package/lib/runjs-context/snippets/scene/block/render-info-card.snippet.js +15 -16
- package/lib/runjs-context/snippets/scene/block/render-react-jsx.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/block/render-react-jsx.snippet.js +58 -0
- package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +7 -7
- package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +24 -29
- package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +20 -21
- package/lib/scheduler/ModelOperationScheduler.d.ts +51 -0
- package/lib/scheduler/ModelOperationScheduler.js +262 -0
- package/lib/types.d.ts +42 -7
- package/lib/types.js +4 -3
- package/lib/utils/associationObjectVariable.d.ts +32 -0
- package/lib/utils/associationObjectVariable.js +157 -0
- package/lib/utils/createCollectionContextMeta.d.ts +1 -1
- package/lib/utils/createCollectionContextMeta.js +8 -4
- package/lib/utils/createEphemeralContext.d.ts +13 -0
- package/lib/utils/createEphemeralContext.js +140 -0
- package/lib/utils/flows.d.ts +10 -0
- package/lib/utils/flows.js +48 -0
- package/lib/utils/index.d.ts +7 -3
- package/lib/utils/index.js +20 -0
- package/lib/utils/jsxTransform.d.ts +15 -0
- package/lib/utils/jsxTransform.js +68 -0
- package/lib/utils/params-resolvers.js +3 -3
- package/lib/utils/parsePathnameToViewParams.d.ts +1 -1
- package/lib/utils/parsePathnameToViewParams.js +41 -5
- package/lib/utils/pruneFilter.d.ts +21 -0
- package/lib/utils/pruneFilter.js +52 -0
- package/lib/utils/safeGlobals.d.ts +5 -3
- package/lib/utils/safeGlobals.js +42 -1
- package/lib/utils/schema-utils.d.ts +6 -0
- package/lib/utils/schema-utils.js +71 -6
- package/lib/utils/serverContextParams.d.ts +3 -0
- package/lib/utils/serverContextParams.js +2 -0
- package/lib/utils/translation.d.ts +4 -1
- package/lib/utils/translation.js +6 -2
- package/lib/utils/variablesParams.d.ts +21 -5
- package/lib/utils/variablesParams.js +103 -34
- package/lib/views/DialogComponent.js +1 -5
- package/lib/views/DrawerComponent.js +18 -9
- package/lib/views/PageComponent.js +3 -4
- package/lib/views/ViewNavigation.d.ts +11 -15
- package/lib/views/ViewNavigation.js +37 -19
- package/lib/views/createViewMeta.d.ts +3 -2
- package/lib/views/createViewMeta.js +164 -53
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +36 -30
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +33 -26
- package/lib/views/usePage.d.ts +2 -1
- package/lib/views/usePage.js +40 -29
- package/package.json +6 -3
- package/src/BlockScopedFlowEngine.ts +88 -0
- package/src/JSRunner.ts +3 -0
- package/src/ViewScopedFlowEngine.ts +16 -0
- package/src/__tests__/JSRunner.test.ts +62 -53
- package/src/__tests__/blockScopedFlowEngine.test.ts +154 -0
- package/src/__tests__/createViewMeta.popup.test.ts +142 -0
- package/src/__tests__/flow-engine.test.ts +3 -0
- package/src/__tests__/flowContext.test.ts +70 -0
- package/src/__tests__/flowEngine.destroyModel.test.ts +74 -0
- package/src/__tests__/flowEngine.moveModel.test.ts +43 -0
- package/src/__tests__/flowEngine.removeModel.test.ts +72 -0
- package/src/__tests__/flowEngine.saveModel.test.ts +4 -0
- package/src/__tests__/flowModel.openView.navigation.test.ts +3 -2
- package/src/__tests__/flowSettings.open.test.tsx +2 -0
- package/src/__tests__/flowSettings.test.ts +2 -0
- package/src/__tests__/globalFlowRegistry.test.ts +1 -1
- package/src/__tests__/modelOperationScheduler.test.ts +346 -0
- package/src/__tests__/objectVariable.test.ts +464 -0
- package/src/__tests__/runjsRuntimeFeatures.test.ts +12 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +98 -0
- package/src/acl/Acl.tsx +85 -31
- package/src/acl/__tests__/Acl.test.tsx +43 -1
- package/src/components/DynamicFlowsEditor.tsx +0 -10
- package/src/components/FieldModelRenderer.tsx +15 -8
- package/src/components/FieldSkeleton.tsx +27 -0
- package/src/components/FlowContextSelector.tsx +20 -2
- package/src/components/FlowModelRenderer.tsx +46 -12
- package/src/components/FormItem.tsx +8 -1
- package/src/components/MobilePopup.style.ts +220 -0
- package/src/components/MobilePopup.tsx +86 -0
- package/src/components/__tests__/FlowModelRenderer.test.tsx +89 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +1 -1
- package/src/components/common/withFlowDesignMode.tsx +5 -5
- package/src/components/index.ts +1 -0
- package/src/components/settings/independents/dropdown/FlowsDropdownButton.tsx +34 -17
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +110 -0
- package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +82 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +260 -121
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +34 -18
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +56 -18
- package/src/components/settings/wrappers/contextual/StepSettings.tsx +1 -2
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +12 -6
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +565 -0
- package/src/components/settings/wrappers/embedded/FlowSettings.tsx +47 -35
- package/src/components/settings/wrappers/embedded/FlowsSettings.tsx +1 -1
- package/src/components/settings/wrappers/embedded/FlowsSettingsContent.tsx +64 -42
- package/src/components/subModel/AddSubModelButton.tsx +104 -9
- package/src/components/subModel/LazyDropdown.tsx +14 -14
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +168 -7
- package/src/components/subModel/__tests__/utils.test.ts +12 -12
- package/src/components/subModel/utils.ts +25 -6
- package/src/components/variables/VariableInput.tsx +5 -3
- package/src/components/variables/types.ts +2 -0
- package/src/components/variables/utils.ts +7 -3
- package/src/data-source/index.ts +169 -11
- package/src/data-source/jioToJoiSchema.ts +1 -0
- package/src/emitter.ts +14 -0
- package/src/executor/FlowExecutor.ts +56 -8
- package/src/executor/__tests__/ctx-defs-injection.test.ts +197 -0
- package/src/flow-registry/GlobalFlowRegistry.ts +1 -0
- package/src/flow-registry/InstanceFlowRegistry.ts +1 -0
- package/src/flow-registry/__tests__/globalFlowRegistry.test.ts +54 -0
- package/src/flowContext.ts +144 -29
- package/src/flowEngine.ts +328 -8
- package/src/flowSettings.ts +47 -19
- package/src/hooks/useApplyAutoFlows.ts +3 -3
- package/src/index.ts +4 -2
- package/src/locale/de-DE.json +62 -0
- package/src/locale/en-US.json +57 -45
- package/src/locale/es-ES.json +62 -0
- package/src/locale/fr-FR.json +62 -0
- package/src/locale/hu-HU.json +62 -0
- package/src/locale/id-ID.json +62 -0
- package/src/locale/it-IT.json +62 -0
- package/src/locale/ja-JP.json +62 -0
- package/src/locale/ko-KR.json +62 -0
- package/src/locale/nl-NL.json +62 -0
- package/src/locale/pt-BR.json +62 -0
- package/src/locale/ru-RU.json +62 -0
- package/src/locale/tr-TR.json +62 -0
- package/src/locale/uk-UA.json +62 -0
- package/src/locale/vi-VN.json +62 -0
- package/src/locale/zh-CN.json +58 -46
- package/src/locale/zh-TW.json +62 -0
- package/src/models/CollectionFieldModel.tsx +79 -17
- package/src/models/__tests__/dispatchEvent.behavior.test.ts +169 -0
- package/src/models/__tests__/flowEngine.resolveUse.test.ts +170 -0
- package/src/models/__tests__/flowModel.getFlows.sort.test.ts +29 -5
- package/src/models/__tests__/flowModel.scheduleModelOperation.test.tsx +129 -0
- package/src/models/__tests__/flowModel.test.ts +65 -27
- package/src/models/__tests__/forkFlowModel.test.ts +40 -7
- package/src/models/flowModel.tsx +192 -30
- package/src/models/forkFlowModel.ts +11 -3
- package/src/provider.tsx +5 -5
- package/src/reactive/__tests__/observer.test.tsx +211 -0
- package/src/reactive/index.ts +11 -0
- package/src/reactive/observer.tsx +101 -0
- package/src/resources/baseRecordResource.ts +15 -3
- package/src/resources/multiRecordResource.ts +17 -8
- package/src/resources/singleRecordResource.ts +6 -3
- package/src/resources/sqlResource.ts +22 -26
- package/src/runjs-context/contexts/base.ts +47 -6
- package/src/runjs-context/snippets/global/clipboard-copy-text.snippet.ts +42 -0
- package/src/runjs-context/snippets/index.ts +3 -0
- package/src/runjs-context/snippets/scene/block/render-antd-icons.snippet.ts +46 -0
- package/src/runjs-context/snippets/scene/block/render-button-handler.snippet.ts +6 -4
- package/src/runjs-context/snippets/scene/block/render-info-card.snippet.ts +15 -16
- package/src/runjs-context/snippets/scene/block/render-react-jsx.snippet.ts +39 -0
- package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +7 -7
- package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +24 -29
- package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +20 -21
- package/src/scheduler/ModelOperationScheduler.ts +304 -0
- package/src/types.ts +50 -4
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +51 -0
- package/src/utils/__tests__/flows.test.ts +65 -0
- package/src/utils/__tests__/jsxTransform.test.ts +38 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +25 -0
- package/src/utils/__tests__/pruneFilter.test.ts +38 -0
- package/src/utils/__tests__/safeGlobals.test.ts +23 -1
- package/src/utils/__tests__/utils.test.ts +114 -15
- package/src/utils/__tests__/variablesParams.test.ts +120 -0
- package/src/utils/associationObjectVariable.ts +180 -0
- package/src/utils/createCollectionContextMeta.ts +8 -3
- package/src/utils/createEphemeralContext.ts +142 -0
- package/src/utils/flows.ts +23 -0
- package/src/utils/index.ts +11 -2
- package/src/utils/jsxTransform.ts +39 -0
- package/src/utils/params-resolvers.ts +2 -2
- package/src/utils/parsePathnameToViewParams.ts +50 -6
- package/src/utils/pruneFilter.ts +41 -0
- package/src/utils/safeGlobals.ts +51 -4
- package/src/utils/schema-utils.ts +81 -3
- package/src/utils/serverContextParams.ts +5 -0
- package/src/utils/translation.ts +7 -2
- package/src/utils/variablesParams.ts +125 -42
- package/src/views/DialogComponent.tsx +1 -4
- package/src/views/DrawerComponent.tsx +19 -7
- package/src/views/PageComponent.tsx +2 -4
- package/src/views/ViewNavigation.ts +49 -43
- package/src/views/__tests__/FlowView.usePage.test.tsx +133 -0
- package/src/views/__tests__/ViewNavigation.test.ts +54 -34
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +132 -0
- package/src/views/createViewMeta.ts +179 -42
- package/src/views/useDialog.tsx +36 -24
- package/src/views/useDrawer.tsx +37 -24
- package/src/views/usePage.tsx +46 -27
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
|
|
12
|
+
import { render, cleanup, waitFor, act } from '@testing-library/react';
|
|
13
|
+
import { App, ConfigProvider } from 'antd';
|
|
14
|
+
|
|
15
|
+
import { FlowEngine } from '../../../../../flowEngine';
|
|
16
|
+
import { FlowModel } from '../../../../../models/flowModel';
|
|
17
|
+
import { DefaultSettingsIcon } from '../DefaultSettingsIcon';
|
|
18
|
+
|
|
19
|
+
// ---- Mock antd to capture Dropdown menu props ----
|
|
20
|
+
const dropdownMenus: any[] = [];
|
|
21
|
+
vi.mock('antd', async (importOriginal) => {
|
|
22
|
+
const messageApi = {
|
|
23
|
+
success: vi.fn(),
|
|
24
|
+
error: vi.fn(),
|
|
25
|
+
info: vi.fn(),
|
|
26
|
+
};
|
|
27
|
+
const modalApi = {
|
|
28
|
+
confirm: (opts: any) => {
|
|
29
|
+
if (opts && typeof opts.onOk === 'function') return opts.onOk();
|
|
30
|
+
},
|
|
31
|
+
error: vi.fn(),
|
|
32
|
+
};
|
|
33
|
+
const appApi = { message: messageApi, modal: modalApi };
|
|
34
|
+
|
|
35
|
+
const Dropdown = (props: any) => {
|
|
36
|
+
(globalThis as any).__lastDropdownMenu = props.menu;
|
|
37
|
+
(globalThis as any).__lastDropdownOnOpenChange = props.onOpenChange;
|
|
38
|
+
dropdownMenus.push(props.menu);
|
|
39
|
+
return React.createElement('span', { 'data-testid': 'dropdown' }, props.children);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const App = Object.assign(({ children }: any) => React.createElement(React.Fragment, null, children), {
|
|
43
|
+
useApp: () => appApi,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
const ConfigProvider = ({ children }: any) => React.createElement(React.Fragment, null, children);
|
|
47
|
+
const Modal = {
|
|
48
|
+
confirm: (opts: any) => {
|
|
49
|
+
if (opts && typeof opts.onOk === 'function') return opts.onOk();
|
|
50
|
+
},
|
|
51
|
+
error: vi.fn(),
|
|
52
|
+
};
|
|
53
|
+
const Typography = {
|
|
54
|
+
Paragraph: ({ children }: any) => React.createElement('p', null, children ?? 'Paragraph'),
|
|
55
|
+
Text: ({ children }: any) => React.createElement('span', null, children ?? 'Text'),
|
|
56
|
+
};
|
|
57
|
+
const Collapse = Object.assign((props: any) => React.createElement('div', null, props.children ?? 'Collapse'), {
|
|
58
|
+
Panel: (props: any) => React.createElement('div', null, props.children ?? 'Panel'),
|
|
59
|
+
});
|
|
60
|
+
const Space = ({ children }: any) => React.createElement('div', null, children);
|
|
61
|
+
const FormItem = (props: any) => React.createElement('div', null, props.children ?? 'FormItem');
|
|
62
|
+
const Form = Object.assign((props: any) => React.createElement('form', null, props.children ?? 'Form'), {
|
|
63
|
+
Item: FormItem,
|
|
64
|
+
useForm: () => [{ setFieldsValue: (_: any) => {} }],
|
|
65
|
+
});
|
|
66
|
+
const Input: any = (props: any) => React.createElement('input', props);
|
|
67
|
+
Input.TextArea = (props: any) => React.createElement('textarea', props);
|
|
68
|
+
const InputNumber = (props: any) => React.createElement('input', { ...props, type: 'number' });
|
|
69
|
+
const Select = (props: any) => React.createElement('select', props);
|
|
70
|
+
const Switch = (props: any) => React.createElement('input', { ...props, type: 'checkbox' });
|
|
71
|
+
const Alert = (props: any) => React.createElement('div', { role: 'alert' }, props.message ?? 'Alert');
|
|
72
|
+
const Button = (props: any) => React.createElement('button', props, props.children ?? 'Button');
|
|
73
|
+
const Result = (props: any) => React.createElement('div', null, props.children ?? 'Result');
|
|
74
|
+
|
|
75
|
+
// Keep other components from original mock/default
|
|
76
|
+
return {
|
|
77
|
+
Dropdown,
|
|
78
|
+
App,
|
|
79
|
+
ConfigProvider,
|
|
80
|
+
Modal,
|
|
81
|
+
Typography,
|
|
82
|
+
Collapse,
|
|
83
|
+
Space,
|
|
84
|
+
Form,
|
|
85
|
+
Input,
|
|
86
|
+
InputNumber,
|
|
87
|
+
Select,
|
|
88
|
+
Switch,
|
|
89
|
+
Alert,
|
|
90
|
+
Button,
|
|
91
|
+
Result,
|
|
92
|
+
theme: { useToken: () => ({}) },
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('DefaultSettingsIcon - only static flows are shown', () => {
|
|
97
|
+
beforeEach(() => {
|
|
98
|
+
dropdownMenus.length = 0;
|
|
99
|
+
(globalThis as any).__lastDropdownMenu = undefined;
|
|
100
|
+
(globalThis as any).__lastDropdownOnOpenChange = undefined;
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
afterEach(() => {
|
|
104
|
+
cleanup();
|
|
105
|
+
vi.clearAllMocks();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('excludes instance (dynamic) flows from the settings menu', async () => {
|
|
109
|
+
class TestFlowModel extends FlowModel {}
|
|
110
|
+
|
|
111
|
+
const engine = new FlowEngine();
|
|
112
|
+
const model = new TestFlowModel({ uid: 'model-static-only', flowEngine: engine });
|
|
113
|
+
|
|
114
|
+
// register one static flow with a visible step
|
|
115
|
+
TestFlowModel.registerFlow({
|
|
116
|
+
key: 'static1',
|
|
117
|
+
title: 'Static Flow',
|
|
118
|
+
steps: {
|
|
119
|
+
general: {
|
|
120
|
+
title: 'General',
|
|
121
|
+
uiSchema: {
|
|
122
|
+
field: { type: 'string', 'x-component': 'Input' },
|
|
123
|
+
},
|
|
124
|
+
},
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// add a dynamic (instance) flow which should NOT appear in menu
|
|
129
|
+
model.flowRegistry.addFlow('dyn1', {
|
|
130
|
+
title: 'Dynamic Flow',
|
|
131
|
+
steps: {
|
|
132
|
+
general: {
|
|
133
|
+
title: 'General (Dyn)',
|
|
134
|
+
uiSchema: {
|
|
135
|
+
field: { type: 'string', 'x-component': 'Input' },
|
|
136
|
+
},
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
render(
|
|
142
|
+
React.createElement(
|
|
143
|
+
ConfigProvider as any,
|
|
144
|
+
null,
|
|
145
|
+
React.createElement(
|
|
146
|
+
App as any,
|
|
147
|
+
null,
|
|
148
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
149
|
+
model,
|
|
150
|
+
// 关闭常用操作,避免干扰断言
|
|
151
|
+
showDeleteButton: false,
|
|
152
|
+
showCopyUidButton: false,
|
|
153
|
+
}),
|
|
154
|
+
),
|
|
155
|
+
),
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// 等待菜单渲染完成,并且只包含静态流的步骤
|
|
159
|
+
await waitFor(() => {
|
|
160
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
161
|
+
expect(menu).toBeTruthy();
|
|
162
|
+
const items = (menu?.items || []) as any[];
|
|
163
|
+
const keys = items.map((it) => String(it.key || ''));
|
|
164
|
+
expect(keys.some((k) => k.startsWith('static1:'))).toBe(true);
|
|
165
|
+
expect(keys.some((k) => k.startsWith('dyn1:'))).toBe(false);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
169
|
+
const items = (menu?.items || []) as any[];
|
|
170
|
+
|
|
171
|
+
// 静态流的 step 存在(key: `${flowKey}:${stepKey}`),动态流 step 不存在
|
|
172
|
+
expect(items.some((it) => String(it.key || '').startsWith('static1:'))).toBe(true);
|
|
173
|
+
expect(items.some((it) => String(it.key || '').startsWith('dyn1:'))).toBe(false);
|
|
174
|
+
});
|
|
175
|
+
|
|
176
|
+
it('filters out steps with hideInSettings and keeps visible ones', async () => {
|
|
177
|
+
class TestFlowModel extends FlowModel {}
|
|
178
|
+
const engine = new FlowEngine();
|
|
179
|
+
const model = new TestFlowModel({ uid: 'm-hide', flowEngine: engine });
|
|
180
|
+
|
|
181
|
+
TestFlowModel.registerFlow({
|
|
182
|
+
key: 'flowA',
|
|
183
|
+
title: 'Flow A',
|
|
184
|
+
steps: {
|
|
185
|
+
hidden: { title: 'Hidden', hideInSettings: true, uiSchema: { a: { type: 'string', 'x-component': 'Input' } } },
|
|
186
|
+
visible: { title: 'Visible', uiSchema: { b: { type: 'string', 'x-component': 'Input' } } },
|
|
187
|
+
},
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
render(
|
|
191
|
+
React.createElement(
|
|
192
|
+
ConfigProvider as any,
|
|
193
|
+
null,
|
|
194
|
+
React.createElement(App as any, null, React.createElement(DefaultSettingsIcon as any, { model })),
|
|
195
|
+
),
|
|
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 || '') === 'flowA:visible')).toBe(true);
|
|
202
|
+
expect(items.some((it) => String(it.key || '') === 'flowA:hidden')).toBe(false);
|
|
203
|
+
});
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
it('includes step when uiSchema provided by action (step.use)', async () => {
|
|
207
|
+
class TestFlowModel extends FlowModel {}
|
|
208
|
+
const engine = new FlowEngine();
|
|
209
|
+
const model = new TestFlowModel({ uid: 'm-action', flowEngine: engine });
|
|
210
|
+
|
|
211
|
+
// Step has no uiSchema but uses an action that provides uiSchema
|
|
212
|
+
TestFlowModel.registerFlow({
|
|
213
|
+
key: 'flowB',
|
|
214
|
+
title: 'Flow B',
|
|
215
|
+
steps: {
|
|
216
|
+
useAction: { title: 'Use Action', use: 'act' },
|
|
217
|
+
},
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// Stub getAction to provide uiSchema
|
|
221
|
+
(model as any).getAction = vi.fn().mockReturnValue({
|
|
222
|
+
name: 'act',
|
|
223
|
+
title: 'Action Title',
|
|
224
|
+
uiSchema: { x: { type: 'string', 'x-component': 'Input' } },
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
render(
|
|
228
|
+
React.createElement(
|
|
229
|
+
ConfigProvider as any,
|
|
230
|
+
null,
|
|
231
|
+
React.createElement(App as any, null, React.createElement(DefaultSettingsIcon as any, { model })),
|
|
232
|
+
),
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
await waitFor(() => {
|
|
236
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
237
|
+
const items = (menu?.items || []) as any[];
|
|
238
|
+
expect(items.some((it) => String(it.key || '') === 'flowB:useAction')).toBe(true);
|
|
239
|
+
});
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it('clicking a step item opens flow settings with correct args', async () => {
|
|
243
|
+
class TestFlowModel extends FlowModel {}
|
|
244
|
+
const engine = new FlowEngine();
|
|
245
|
+
const model = new TestFlowModel({ uid: 'm-open', flowEngine: engine });
|
|
246
|
+
const openSpy = vi.spyOn(model, 'openFlowSettings').mockResolvedValue(undefined as any);
|
|
247
|
+
|
|
248
|
+
TestFlowModel.registerFlow({
|
|
249
|
+
key: 'flowC',
|
|
250
|
+
title: 'Flow C',
|
|
251
|
+
steps: {
|
|
252
|
+
general: { title: 'General', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } },
|
|
253
|
+
},
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
render(
|
|
257
|
+
React.createElement(
|
|
258
|
+
ConfigProvider as any,
|
|
259
|
+
null,
|
|
260
|
+
React.createElement(App as any, null, React.createElement(DefaultSettingsIcon as any, { model })),
|
|
261
|
+
),
|
|
262
|
+
);
|
|
263
|
+
|
|
264
|
+
await waitFor(() => {
|
|
265
|
+
expect((globalThis as any).__lastDropdownMenu).toBeTruthy();
|
|
266
|
+
});
|
|
267
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
268
|
+
menu.onClick?.({ key: 'flowC:general' });
|
|
269
|
+
expect(openSpy).toHaveBeenCalledWith({ flowKey: 'flowC', stepKey: 'general' });
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it('copy UID action writes model uid to clipboard', async () => {
|
|
273
|
+
class TestFlowModel extends FlowModel {}
|
|
274
|
+
const engine = new FlowEngine();
|
|
275
|
+
const model = new TestFlowModel({ uid: 'm-copy', flowEngine: engine });
|
|
276
|
+
|
|
277
|
+
TestFlowModel.registerFlow({
|
|
278
|
+
key: 'flowD',
|
|
279
|
+
title: 'Flow D',
|
|
280
|
+
steps: { s: { title: 'S', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } } },
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
// mock clipboard
|
|
284
|
+
Object.defineProperty(window.navigator, 'clipboard', {
|
|
285
|
+
value: { writeText: vi.fn().mockResolvedValue(undefined) },
|
|
286
|
+
configurable: true,
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
render(
|
|
290
|
+
React.createElement(
|
|
291
|
+
ConfigProvider as any,
|
|
292
|
+
null,
|
|
293
|
+
React.createElement(App as any, null, React.createElement(DefaultSettingsIcon as any, { model })),
|
|
294
|
+
),
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
await waitFor(() => {
|
|
298
|
+
expect((globalThis as any).__lastDropdownMenu).toBeTruthy();
|
|
299
|
+
});
|
|
300
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
301
|
+
menu.onClick?.({ key: 'copy-uid' });
|
|
302
|
+
expect((navigator as any).clipboard.writeText).toHaveBeenCalledWith('m-copy');
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
it('delete action calls model.destroy()', async () => {
|
|
306
|
+
class TestFlowModel extends FlowModel {}
|
|
307
|
+
const engine = new FlowEngine();
|
|
308
|
+
const model = new TestFlowModel({ uid: 'm-del', flowEngine: engine });
|
|
309
|
+
const destroySpy = vi.spyOn(model, 'destroy').mockResolvedValue(undefined as any);
|
|
310
|
+
|
|
311
|
+
TestFlowModel.registerFlow({
|
|
312
|
+
key: 'flowE',
|
|
313
|
+
title: 'Flow E',
|
|
314
|
+
steps: { s: { title: 'S', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } } },
|
|
315
|
+
});
|
|
316
|
+
|
|
317
|
+
render(
|
|
318
|
+
React.createElement(
|
|
319
|
+
ConfigProvider as any,
|
|
320
|
+
null,
|
|
321
|
+
React.createElement(App as any, null, React.createElement(DefaultSettingsIcon as any, { model })),
|
|
322
|
+
),
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
await waitFor(() => {
|
|
326
|
+
expect((globalThis as any).__lastDropdownMenu).toBeTruthy();
|
|
327
|
+
});
|
|
328
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
329
|
+
menu.onClick?.({ key: 'delete' });
|
|
330
|
+
expect(destroySpy).toHaveBeenCalled();
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
it('shows sub-model steps with modelKey when flattenSubMenus=false and menuLevels=2', async () => {
|
|
334
|
+
class Parent extends FlowModel {}
|
|
335
|
+
class Child extends FlowModel {}
|
|
336
|
+
const engine = new FlowEngine();
|
|
337
|
+
const parent = new Parent({ uid: 'parent-1', flowEngine: engine });
|
|
338
|
+
const child = new Child({ uid: 'child-1', flowEngine: engine });
|
|
339
|
+
|
|
340
|
+
// child static flow
|
|
341
|
+
Child.registerFlow({
|
|
342
|
+
key: 'childFlow',
|
|
343
|
+
title: 'Child Flow',
|
|
344
|
+
steps: { cstep: { title: 'C', uiSchema: { x: { type: 'string', 'x-component': 'Input' } } } },
|
|
345
|
+
});
|
|
346
|
+
|
|
347
|
+
parent.addSubModel('items', child);
|
|
348
|
+
|
|
349
|
+
render(
|
|
350
|
+
React.createElement(
|
|
351
|
+
ConfigProvider as any,
|
|
352
|
+
null,
|
|
353
|
+
React.createElement(
|
|
354
|
+
App as any,
|
|
355
|
+
null,
|
|
356
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
357
|
+
model: parent,
|
|
358
|
+
menuLevels: 2,
|
|
359
|
+
flattenSubMenus: false,
|
|
360
|
+
}),
|
|
361
|
+
),
|
|
362
|
+
),
|
|
363
|
+
);
|
|
364
|
+
|
|
365
|
+
await waitFor(() => {
|
|
366
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
367
|
+
expect(menu).toBeTruthy();
|
|
368
|
+
const items = (menu?.items || []) as any[];
|
|
369
|
+
const subMenu = items.find((it) => Array.isArray(it?.children));
|
|
370
|
+
expect(subMenu).toBeTruthy();
|
|
371
|
+
expect(subMenu!.children.some((it: any) => String(it.key).startsWith('items[0]:childFlow:cstep'))).toBe(true);
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it('refreshes menu when current model step params change', async () => {
|
|
376
|
+
class TestFlowModel extends FlowModel {}
|
|
377
|
+
const engine = new FlowEngine();
|
|
378
|
+
const model = new TestFlowModel({
|
|
379
|
+
uid: 'step-params-current',
|
|
380
|
+
flowEngine: engine,
|
|
381
|
+
stepParams: {
|
|
382
|
+
dynamicFlow: { dynamicStep: { hidden: true } },
|
|
383
|
+
},
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
TestFlowModel.registerFlow({
|
|
387
|
+
key: 'dynamicFlow',
|
|
388
|
+
title: 'Dynamic Flow',
|
|
389
|
+
steps: {
|
|
390
|
+
dynamicStep: {
|
|
391
|
+
title: 'Dynamic Step',
|
|
392
|
+
hideInSettings: (ctx) => !!ctx.getStepParams('dynamicStep')?.hidden,
|
|
393
|
+
uiSchema: { f: { type: 'string', 'x-component': 'Input' } },
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
render(
|
|
399
|
+
React.createElement(
|
|
400
|
+
ConfigProvider as any,
|
|
401
|
+
null,
|
|
402
|
+
React.createElement(
|
|
403
|
+
App as any,
|
|
404
|
+
null,
|
|
405
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
406
|
+
model,
|
|
407
|
+
showCopyUidButton: true,
|
|
408
|
+
showDeleteButton: false,
|
|
409
|
+
}),
|
|
410
|
+
),
|
|
411
|
+
),
|
|
412
|
+
);
|
|
413
|
+
|
|
414
|
+
await waitFor(() => {
|
|
415
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
416
|
+
expect(menu).toBeTruthy();
|
|
417
|
+
const items = (menu?.items || []) as any[];
|
|
418
|
+
expect(items.some((it) => String(it.key || '') === 'dynamicFlow:dynamicStep')).toBe(false);
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
await act(async () => {
|
|
422
|
+
model.setStepParams('dynamicFlow', 'dynamicStep', { hidden: false });
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
await waitFor(() => {
|
|
426
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
427
|
+
const items = (menu?.items || []) as any[];
|
|
428
|
+
expect(items.some((it) => String(it.key || '') === 'dynamicFlow:dynamicStep')).toBe(true);
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
it('refreshes submenu when child model step params change', async () => {
|
|
433
|
+
class ParentModel extends FlowModel {}
|
|
434
|
+
class ChildModel extends FlowModel {}
|
|
435
|
+
const engine = new FlowEngine();
|
|
436
|
+
const child = new ChildModel({
|
|
437
|
+
uid: 'child-step-params',
|
|
438
|
+
flowEngine: engine,
|
|
439
|
+
stepParams: { childFlow: { childStep: { hidden: true } } },
|
|
440
|
+
});
|
|
441
|
+
const parent = new ParentModel({ uid: 'parent-step-params', flowEngine: engine });
|
|
442
|
+
|
|
443
|
+
ChildModel.registerFlow({
|
|
444
|
+
key: 'childFlow',
|
|
445
|
+
title: 'Child Flow',
|
|
446
|
+
steps: {
|
|
447
|
+
childStep: {
|
|
448
|
+
title: 'Child Step',
|
|
449
|
+
hideInSettings: (ctx) => !!ctx.getStepParams('childStep')?.hidden,
|
|
450
|
+
uiSchema: { x: { type: 'string', 'x-component': 'Input' } },
|
|
451
|
+
},
|
|
452
|
+
},
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
parent.addSubModel('items', child);
|
|
456
|
+
|
|
457
|
+
render(
|
|
458
|
+
React.createElement(
|
|
459
|
+
ConfigProvider as any,
|
|
460
|
+
null,
|
|
461
|
+
React.createElement(
|
|
462
|
+
App as any,
|
|
463
|
+
null,
|
|
464
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
465
|
+
model: parent,
|
|
466
|
+
menuLevels: 2,
|
|
467
|
+
flattenSubMenus: true,
|
|
468
|
+
showCopyUidButton: true,
|
|
469
|
+
showDeleteButton: false,
|
|
470
|
+
}),
|
|
471
|
+
),
|
|
472
|
+
),
|
|
473
|
+
);
|
|
474
|
+
|
|
475
|
+
await waitFor(() => {
|
|
476
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
477
|
+
expect(menu).toBeTruthy();
|
|
478
|
+
const items = (menu?.items || []) as any[];
|
|
479
|
+
expect(items.some((it) => String(it.key || '').startsWith('items[0]:childFlow:childStep'))).toBe(false);
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
await act(async () => {
|
|
483
|
+
child.setStepParams('childFlow', 'childStep', { hidden: false });
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
await waitFor(() => {
|
|
487
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
488
|
+
const items = (menu?.items || []) as any[];
|
|
489
|
+
expect(items.some((it) => String(it.key || '').startsWith('items[0]:childFlow:childStep'))).toBe(true);
|
|
490
|
+
});
|
|
491
|
+
});
|
|
492
|
+
});
|
|
493
|
+
|
|
494
|
+
describe('DefaultSettingsIcon - extra menu items', () => {
|
|
495
|
+
beforeEach(() => {
|
|
496
|
+
dropdownMenus.length = 0;
|
|
497
|
+
(globalThis as any).__lastDropdownMenu = undefined;
|
|
498
|
+
(globalThis as any).__lastDropdownOnOpenChange = undefined;
|
|
499
|
+
});
|
|
500
|
+
|
|
501
|
+
afterEach(() => {
|
|
502
|
+
cleanup();
|
|
503
|
+
vi.clearAllMocks();
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('renders and triggers extra menu items registered on FlowModel class', async () => {
|
|
507
|
+
const onClick = vi.fn();
|
|
508
|
+
|
|
509
|
+
class TestFlowModel extends FlowModel {}
|
|
510
|
+
const dispose = TestFlowModel.registerExtraMenuItems({
|
|
511
|
+
group: 'common-actions',
|
|
512
|
+
sort: 10,
|
|
513
|
+
items: [{ key: 'extra-action', label: 'Extra Action', onClick }],
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
const engine = new FlowEngine();
|
|
517
|
+
const model = new TestFlowModel({ uid: 'm-extra', flowEngine: engine });
|
|
518
|
+
|
|
519
|
+
TestFlowModel.registerFlow({
|
|
520
|
+
key: 'flow',
|
|
521
|
+
title: 'Flow',
|
|
522
|
+
steps: { s: { title: 'S', uiSchema: { f: { type: 'string', 'x-component': 'Input' } } } },
|
|
523
|
+
});
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
render(
|
|
527
|
+
React.createElement(
|
|
528
|
+
ConfigProvider as any,
|
|
529
|
+
null,
|
|
530
|
+
React.createElement(
|
|
531
|
+
App as any,
|
|
532
|
+
null,
|
|
533
|
+
React.createElement(DefaultSettingsIcon as any, {
|
|
534
|
+
model,
|
|
535
|
+
showCopyUidButton: false,
|
|
536
|
+
showDeleteButton: false,
|
|
537
|
+
}),
|
|
538
|
+
),
|
|
539
|
+
),
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
await waitFor(() => {
|
|
543
|
+
expect((globalThis as any).__lastDropdownMenu).toBeTruthy();
|
|
544
|
+
expect((globalThis as any).__lastDropdownOnOpenChange).toBeTruthy();
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
// extra menu items are loaded when dropdown becomes visible
|
|
548
|
+
await act(async () => {
|
|
549
|
+
(globalThis as any).__lastDropdownOnOpenChange?.(true, { source: 'trigger' });
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
await waitFor(() => {
|
|
553
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
554
|
+
const items = (menu?.items || []) as any[];
|
|
555
|
+
expect(items.some((it) => String(it.key || '') === 'extra-action')).toBe(true);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
const menu = (globalThis as any).__lastDropdownMenu;
|
|
559
|
+
menu.onClick?.({ key: 'extra-action' });
|
|
560
|
+
expect(onClick).toHaveBeenCalled();
|
|
561
|
+
} finally {
|
|
562
|
+
dispose?.();
|
|
563
|
+
}
|
|
564
|
+
});
|
|
565
|
+
});
|
|
@@ -8,13 +8,12 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { Alert, Form, Input, InputNumber, Select, Switch } from 'antd';
|
|
11
|
-
import React, { useCallback, useEffect } from 'react';
|
|
12
|
-
// TODO: ISchema may need to be imported from a different package or refactored.
|
|
13
|
-
import { observer } from '@formily/react';
|
|
11
|
+
import React, { useCallback, useEffect, useState } from 'react';
|
|
14
12
|
import { FlowRuntimeContext } from '../../../../flowContext';
|
|
15
13
|
import { useFlowModelById } from '../../../../hooks';
|
|
16
14
|
import { FlowModel } from '../../../../models';
|
|
17
|
-
import { resolveDefaultParams, setupRuntimeContextSteps } from '../../../../utils';
|
|
15
|
+
import { resolveDefaultParams, setupRuntimeContextSteps, shouldHideStepInSettings } from '../../../../utils';
|
|
16
|
+
import { observer } from '../../../../reactive';
|
|
18
17
|
|
|
19
18
|
const { Item: FormItem } = Form;
|
|
20
19
|
|
|
@@ -89,46 +88,59 @@ const FlowSettingsContent: React.FC<FlowSettingsContentProps> = observer(({ mode
|
|
|
89
88
|
const flows = model.getFlows();
|
|
90
89
|
const flow = flows.get(flowKey);
|
|
91
90
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
91
|
+
const [configurableSteps, setConfigurableSteps] = useState<{ stepKey: string; step: any; uiSchema: any }[]>([]);
|
|
92
|
+
|
|
93
|
+
// 获取可配置的步骤(支持动态 hideInSettings)
|
|
94
|
+
useEffect(() => {
|
|
95
|
+
let mounted = true;
|
|
96
|
+
const buildConfigurableSteps = async () => {
|
|
97
|
+
const steps: { stepKey: string; step: any; uiSchema: any }[] = [];
|
|
98
|
+
for (const [stepKey, actionStep] of Object.entries(flow?.steps || {})) {
|
|
99
|
+
if (await shouldHideStepInSettings(model, flow, actionStep as any)) {
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
99
102
|
|
|
100
|
-
|
|
101
|
-
|
|
103
|
+
// 从step获取uiSchema(如果存在)
|
|
104
|
+
const stepUiSchema = actionStep.uiSchema || {};
|
|
102
105
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
106
|
+
// 如果step使用了action,也获取action的uiSchema
|
|
107
|
+
let actionUiSchema = {};
|
|
108
|
+
if (actionStep.use) {
|
|
109
|
+
const action = model.flowEngine?.getAction?.(actionStep.use);
|
|
110
|
+
if (action && action.uiSchema) {
|
|
111
|
+
actionUiSchema = action.uiSchema;
|
|
112
|
+
}
|
|
109
113
|
}
|
|
110
|
-
}
|
|
111
114
|
|
|
112
|
-
|
|
113
|
-
|
|
115
|
+
// 合并uiSchema,确保step的uiSchema优先级更高
|
|
116
|
+
const mergedUiSchema = { ...actionUiSchema };
|
|
114
117
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
118
|
+
// 将stepUiSchema中的字段合并到mergedUiSchema
|
|
119
|
+
Object.entries(stepUiSchema).forEach(([fieldKey, schema]) => {
|
|
120
|
+
if (mergedUiSchema[fieldKey]) {
|
|
121
|
+
mergedUiSchema[fieldKey] = { ...mergedUiSchema[fieldKey], ...schema };
|
|
122
|
+
} else {
|
|
123
|
+
mergedUiSchema[fieldKey] = schema;
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
// 如果没有可配置的UI Schema,跳过
|
|
128
|
+
if (Object.keys(mergedUiSchema).length === 0) {
|
|
129
|
+
continue;
|
|
121
130
|
}
|
|
122
|
-
});
|
|
123
131
|
|
|
124
|
-
|
|
125
|
-
if (Object.keys(mergedUiSchema).length === 0) {
|
|
126
|
-
return null;
|
|
132
|
+
steps.push({ stepKey, step: actionStep, uiSchema: mergedUiSchema });
|
|
127
133
|
}
|
|
134
|
+
if (mounted) {
|
|
135
|
+
setConfigurableSteps(steps);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
128
138
|
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
139
|
+
buildConfigurableSteps();
|
|
140
|
+
return () => {
|
|
141
|
+
mounted = false;
|
|
142
|
+
};
|
|
143
|
+
}, [model, flow]);
|
|
132
144
|
|
|
133
145
|
// 获取当前流程的参数 - 从model中获取实际参数
|
|
134
146
|
const getCurrentParams = useCallback(async () => {
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { Alert } from 'antd';
|
|
12
12
|
import { useFlowModelById } from '../../../../hooks';
|
|
13
|
-
import { observer } from '@formily/react';
|
|
14
13
|
import FlowsSettingsContent from './FlowsSettingsContent';
|
|
14
|
+
import { observer } from '../../../../reactive';
|
|
15
15
|
|
|
16
16
|
// 简单的流程设置组件接口
|
|
17
17
|
interface ModelProvidedProps {
|