@nocobase/flow-engine 2.0.0-beta.8 → 2.0.0
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.js +0 -1
- package/lib/FlowDefinition.d.ts +2 -0
- package/lib/JSRunner.d.ts +6 -0
- package/lib/JSRunner.js +32 -2
- package/lib/ViewScopedFlowEngine.js +3 -0
- package/lib/acl/Acl.js +13 -3
- package/lib/components/FlowContextSelector.js +155 -10
- package/lib/components/settings/wrappers/component/SwitchWithTitle.js +2 -1
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +76 -15
- package/lib/components/settings/wrappers/contextual/FlowsContextMenu.js +24 -4
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +5 -1
- package/lib/components/variables/VariableInput.js +9 -4
- package/lib/components/variables/VariableTag.js +46 -39
- package/lib/components/variables/utils.d.ts +7 -0
- package/lib/components/variables/utils.js +42 -2
- package/lib/data-source/index.d.ts +7 -27
- package/lib/data-source/index.js +81 -51
- package/lib/executor/FlowExecutor.d.ts +2 -1
- package/lib/executor/FlowExecutor.js +163 -22
- package/lib/flowContext.d.ts +230 -7
- package/lib/flowContext.js +2267 -148
- package/lib/flowEngine.d.ts +21 -0
- package/lib/flowEngine.js +56 -8
- package/lib/flowI18n.js +6 -4
- package/lib/flowSettings.js +17 -11
- package/lib/index.d.ts +7 -1
- package/lib/index.js +21 -0
- package/lib/locale/en-US.json +9 -2
- package/lib/locale/index.d.ts +14 -0
- package/lib/locale/zh-CN.json +8 -1
- package/lib/models/CollectionFieldModel.d.ts +1 -0
- package/lib/models/CollectionFieldModel.js +3 -2
- package/lib/models/flowModel.js +12 -1
- package/lib/provider.js +5 -5
- package/lib/resources/baseRecordResource.d.ts +5 -0
- package/lib/resources/baseRecordResource.js +24 -0
- package/lib/resources/multiRecordResource.d.ts +1 -0
- package/lib/resources/multiRecordResource.js +11 -4
- package/lib/resources/singleRecordResource.js +2 -0
- package/lib/resources/sqlResource.d.ts +4 -3
- package/lib/resources/sqlResource.js +8 -3
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +2 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.d.ts +16 -0
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +125 -0
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +12 -2
- package/lib/runjs-context/contexts/base.js +706 -41
- package/lib/runjs-context/contributions.d.ts +33 -0
- package/lib/runjs-context/contributions.js +88 -0
- package/lib/runjs-context/helpers.js +12 -1
- package/lib/runjs-context/setup.js +6 -0
- package/lib/runjs-context/snippets/global/api-request.snippet.js +3 -3
- package/lib/runjs-context/snippets/global/import-esm.snippet.js +2 -3
- package/lib/runjs-context/snippets/global/query-selector.snippet.js +8 -3
- package/lib/runjs-context/snippets/global/require-amd.snippet.js +1 -1
- package/lib/runjs-context/snippets/index.d.ts +11 -1
- package/lib/runjs-context/snippets/index.js +61 -40
- package/lib/runjs-context/snippets/scene/block/add-event-listener.snippet.js +10 -7
- package/lib/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/block/chartjs-bar.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/echarts-init.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-iframe.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/block/render-react.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-statistics.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/render-timeline.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/block/resource-example.snippet.js +5 -5
- package/lib/runjs-context/snippets/scene/block/three-users-orbit.snippet.js +6 -6
- package/lib/runjs-context/snippets/scene/block/vue-component.snippet.js +3 -4
- package/lib/runjs-context/snippets/scene/detail/color-by-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.js +20 -3
- package/lib/runjs-context/snippets/scene/detail/format-number.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/innerHTML-value.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/detail/percentage-bar.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/relative-time.snippet.js +3 -3
- package/lib/runjs-context/snippets/scene/detail/status-tag.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/form/cascade-select.snippet.js +1 -1
- package/lib/runjs-context/snippets/scene/form/render-basic.snippet.js +2 -2
- package/lib/runjs-context/snippets/scene/table/cell-open-dialog.snippet.js +6 -3
- package/lib/runjs-context/snippets/scene/table/concat-fields.snippet.js +3 -1
- package/lib/runjsLibs.d.ts +28 -0
- package/lib/runjsLibs.js +532 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +2 -0
- package/lib/scheduler/ModelOperationScheduler.js +25 -21
- package/lib/types.d.ts +27 -0
- package/lib/utils/associationObjectVariable.d.ts +2 -2
- package/lib/utils/createCollectionContextMeta.js +1 -0
- package/lib/utils/createEphemeralContext.js +2 -2
- package/lib/utils/dateVariable.d.ts +16 -0
- package/lib/utils/dateVariable.js +380 -0
- package/lib/utils/exceptions.d.ts +7 -0
- package/lib/utils/exceptions.js +10 -0
- package/lib/utils/index.d.ts +8 -3
- package/lib/utils/index.js +45 -0
- package/lib/utils/params-resolvers.js +16 -9
- package/lib/utils/resolveModuleUrl.d.ts +58 -0
- package/lib/utils/resolveModuleUrl.js +65 -0
- package/lib/utils/resolveRunJSObjectValues.d.ts +16 -0
- package/lib/utils/resolveRunJSObjectValues.js +61 -0
- package/lib/utils/runjsModuleLoader.d.ts +58 -0
- package/lib/utils/runjsModuleLoader.js +422 -0
- package/lib/utils/runjsTemplateCompat.d.ts +35 -0
- package/lib/utils/runjsTemplateCompat.js +743 -0
- package/lib/utils/runjsValue.d.ts +29 -0
- package/lib/utils/runjsValue.js +275 -0
- package/lib/utils/safeGlobals.d.ts +18 -8
- package/lib/utils/safeGlobals.js +164 -17
- package/lib/utils/schema-utils.d.ts +10 -0
- package/lib/utils/schema-utils.js +61 -0
- package/lib/views/createViewMeta.d.ts +0 -7
- package/lib/views/createViewMeta.js +19 -70
- package/lib/views/index.d.ts +1 -2
- package/lib/views/index.js +4 -3
- package/lib/views/useDialog.js +7 -2
- package/lib/views/useDrawer.js +7 -2
- package/lib/views/usePage.d.ts +4 -0
- package/lib/views/usePage.js +43 -6
- package/lib/views/usePopover.js +4 -1
- package/lib/views/viewEvents.d.ts +17 -0
- package/lib/views/viewEvents.js +90 -0
- package/package.json +4 -4
- package/src/BlockScopedFlowEngine.ts +2 -5
- package/src/JSRunner.ts +44 -2
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +64 -0
- package/src/__tests__/createViewMeta.popup.test.ts +62 -1
- package/src/__tests__/flowContext.test.ts +693 -1
- package/src/__tests__/flowEngine.dataSourceDirty.test.ts +63 -0
- package/src/__tests__/flowModel.openView.navigation.test.ts +28 -0
- package/src/__tests__/flowRunJSContextDefine.test.ts +63 -0
- package/src/__tests__/flowRuntimeContext.test.ts +2 -1
- package/src/__tests__/flowSettings.open.test.tsx +123 -19
- package/src/__tests__/runjsContext.test.ts +10 -7
- package/src/__tests__/runjsContextImplementations.test.ts +34 -3
- package/src/__tests__/runjsContextRuntime.test.ts +3 -3
- package/src/__tests__/runjsContributions.test.ts +89 -0
- package/src/__tests__/runjsExternalLibs.test.ts +242 -0
- package/src/__tests__/runjsLibsLazyLoading.test.ts +44 -0
- package/src/__tests__/runjsLocales.test.ts +4 -1
- package/src/__tests__/runjsPreprocessDefault.test.ts +49 -0
- package/src/__tests__/runjsRuntimeFeatures.test.ts +166 -0
- package/src/__tests__/runjsSnippets.test.ts +40 -3
- package/src/acl/Acl.tsx +3 -3
- package/src/components/FlowContextSelector.tsx +208 -12
- package/src/components/settings/wrappers/component/SwitchWithTitle.tsx +2 -1
- package/src/components/settings/wrappers/component/__tests__/InlineControls.test.tsx +74 -0
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +109 -16
- package/src/components/settings/wrappers/contextual/FlowsContextMenu.tsx +41 -7
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +13 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +157 -5
- package/src/components/variables/VariableInput.tsx +12 -4
- package/src/components/variables/VariableTag.tsx +54 -45
- package/src/components/variables/__tests__/FlowContextSelector.test.tsx +260 -3
- package/src/components/variables/__tests__/VariableTag.test.tsx +50 -0
- package/src/components/variables/__tests__/utils.test.ts +81 -3
- package/src/components/variables/utils.ts +67 -6
- package/src/data-source/index.ts +85 -110
- package/src/executor/FlowExecutor.ts +200 -23
- package/src/executor/__tests__/flowExecutor.test.ts +66 -0
- package/src/flowContext.ts +2986 -211
- package/src/flowEngine.ts +59 -8
- package/src/flowI18n.ts +7 -5
- package/src/flowSettings.ts +18 -12
- package/src/index.ts +14 -1
- package/src/locale/en-US.json +9 -2
- package/src/locale/zh-CN.json +8 -1
- package/src/models/CollectionFieldModel.tsx +3 -1
- package/src/models/__tests__/dispatchEvent.when.test.ts +554 -0
- package/src/models/__tests__/flowModel.test.ts +20 -4
- package/src/models/flowModel.tsx +13 -1
- package/src/provider.tsx +7 -6
- package/src/resources/__tests__/multiRecordResource.test.ts +44 -0
- package/src/resources/__tests__/sqlResource.test.ts +60 -0
- package/src/resources/baseRecordResource.ts +31 -0
- package/src/resources/multiRecordResource.ts +11 -4
- package/src/resources/singleRecordResource.ts +3 -0
- package/src/resources/sqlResource.ts +11 -6
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +6 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +106 -0
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +10 -0
- package/src/runjs-context/contexts/base.ts +715 -44
- package/src/runjs-context/contributions.ts +88 -0
- package/src/runjs-context/helpers.ts +11 -1
- package/src/runjs-context/setup.ts +6 -0
- package/src/runjs-context/snippets/global/api-request.snippet.ts +3 -3
- package/src/runjs-context/snippets/global/import-esm.snippet.ts +2 -3
- package/src/runjs-context/snippets/global/query-selector.snippet.ts +8 -3
- package/src/runjs-context/snippets/global/require-amd.snippet.ts +1 -1
- package/src/runjs-context/snippets/index.ts +75 -41
- package/src/runjs-context/snippets/scene/block/add-event-listener.snippet.ts +11 -13
- package/src/runjs-context/snippets/scene/block/api-fetch-render-list.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/block/chartjs-bar.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/echarts-init.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-iframe.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/block/render-react.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-statistics.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/render-timeline.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/block/resource-example.snippet.ts +6 -11
- package/src/runjs-context/snippets/scene/block/three-users-orbit.snippet.ts +6 -6
- package/src/runjs-context/snippets/scene/block/vue-component.snippet.ts +3 -4
- package/src/runjs-context/snippets/scene/detail/color-by-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/copy-to-clipboard.snippet.ts +20 -3
- package/src/runjs-context/snippets/scene/detail/format-number.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/innerHTML-value.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/detail/percentage-bar.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/relative-time.snippet.ts +3 -3
- package/src/runjs-context/snippets/scene/detail/status-tag.snippet.ts +2 -2
- package/src/runjs-context/snippets/scene/form/cascade-select.snippet.ts +1 -1
- package/src/runjs-context/snippets/scene/form/render-basic.snippet.ts +3 -8
- package/src/runjs-context/snippets/scene/table/cell-open-dialog.snippet.ts +6 -3
- package/src/runjs-context/snippets/scene/table/concat-fields.snippet.ts +3 -1
- package/src/runjsLibs.ts +622 -0
- package/src/scheduler/ModelOperationScheduler.ts +27 -21
- package/src/types.ts +38 -1
- package/src/utils/__tests__/dateVariable.test.ts +101 -0
- package/src/utils/__tests__/params-resolvers.test.ts +40 -0
- package/src/utils/__tests__/runjsRequireAsyncAutoWhitelist.test.ts +38 -0
- package/src/utils/__tests__/runjsTemplateCompat.test.ts +159 -0
- package/src/utils/__tests__/runjsValue.test.ts +44 -0
- package/src/utils/__tests__/safeGlobals.test.ts +57 -2
- package/src/utils/__tests__/utils.test.ts +95 -0
- package/src/utils/associationObjectVariable.ts +2 -2
- package/src/utils/createCollectionContextMeta.ts +1 -0
- package/src/utils/createEphemeralContext.ts +5 -4
- package/src/utils/dateVariable.ts +397 -0
- package/src/utils/exceptions.ts +11 -0
- package/src/utils/index.ts +37 -3
- package/src/utils/params-resolvers.ts +23 -9
- package/src/utils/resolveModuleUrl.ts +91 -0
- package/src/utils/resolveRunJSObjectValues.ts +46 -0
- package/src/utils/runjsModuleLoader.ts +553 -0
- package/src/utils/runjsTemplateCompat.ts +828 -0
- package/src/utils/runjsValue.ts +287 -0
- package/src/utils/safeGlobals.ts +188 -17
- package/src/utils/schema-utils.ts +79 -0
- package/src/views/__tests__/FlowView.usePage.test.tsx +54 -1
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +35 -8
- package/src/views/__tests__/viewEvents.resolveOpenerEngine.test.ts +28 -0
- package/src/views/createViewMeta.ts +22 -75
- package/src/views/index.tsx +1 -2
- package/src/views/useDialog.tsx +8 -1
- package/src/views/useDrawer.tsx +8 -1
- package/src/views/usePage.tsx +51 -5
- package/src/views/usePopover.tsx +4 -1
- package/src/views/viewEvents.ts +55 -0
|
@@ -0,0 +1,242 @@
|
|
|
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 { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
11
|
+
import { observable } from '@formily/reactive';
|
|
12
|
+
|
|
13
|
+
vi.mock('../utils/runjsModuleLoader', async (importOriginal) => {
|
|
14
|
+
const actual = await importOriginal<typeof import('../utils/runjsModuleLoader')>();
|
|
15
|
+
return {
|
|
16
|
+
...actual,
|
|
17
|
+
runjsImportAsync: vi.fn(),
|
|
18
|
+
runjsRequireAsync: vi.fn(),
|
|
19
|
+
};
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
import { runjsImportAsync } from '../utils/runjsModuleLoader';
|
|
23
|
+
import { FlowEngine, FlowRunJSContext } from '..';
|
|
24
|
+
import { externalReactRender } from '../runjsLibs';
|
|
25
|
+
|
|
26
|
+
function newEngine(): FlowEngine {
|
|
27
|
+
const engine = new FlowEngine();
|
|
28
|
+
// 提供最小 api,避免 ctx.auth getter 在打印对象时抛错
|
|
29
|
+
engine.context.defineProperty('api', { value: { auth: { role: 'guest', locale: 'zh-CN', token: '' } } });
|
|
30
|
+
return engine;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
(globalThis as any).__nocobaseImportAsyncCache = undefined;
|
|
35
|
+
(globalThis as any).__nbRunjsRoots = undefined;
|
|
36
|
+
(runjsImportAsync as any).mockReset();
|
|
37
|
+
|
|
38
|
+
if (typeof window !== 'undefined') {
|
|
39
|
+
(window as any).__esm_cdn_base_url__ = 'https://esm.sh';
|
|
40
|
+
(window as any).__esm_cdn_suffix__ = '';
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
describe('RunJS external libs', () => {
|
|
45
|
+
it('should override ctx.React/ReactDOM when importing external react', async () => {
|
|
46
|
+
const engine = newEngine();
|
|
47
|
+
const ctx = new FlowRunJSContext(engine.context);
|
|
48
|
+
|
|
49
|
+
const fakeReact = { createElement: vi.fn(), Fragment: Symbol('Fragment') };
|
|
50
|
+
const fakeReactDOM = { createRoot: vi.fn(() => ({ render: vi.fn(), unmount: vi.fn() })) };
|
|
51
|
+
|
|
52
|
+
(runjsImportAsync as any).mockImplementation(async (url: string) => {
|
|
53
|
+
if (url === 'https://esm.sh/react@18.2.0') return fakeReact;
|
|
54
|
+
if (url === 'https://esm.sh/react-dom@18.2.0/client') return fakeReactDOM;
|
|
55
|
+
throw new Error(`unexpected import url: ${url}`);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
await ctx.importAsync('react@18.2.0');
|
|
59
|
+
|
|
60
|
+
expect(ctx.React).toBe(fakeReact);
|
|
61
|
+
expect(ctx.libs.React).toBe(fakeReact);
|
|
62
|
+
expect(ctx.ReactDOM).toBe(fakeReactDOM);
|
|
63
|
+
expect(ctx.libs.ReactDOM).toBe(fakeReactDOM);
|
|
64
|
+
|
|
65
|
+
expect(runjsImportAsync).toHaveBeenNthCalledWith(1, 'https://esm.sh/react@18.2.0');
|
|
66
|
+
expect(runjsImportAsync).toHaveBeenNthCalledWith(2, 'https://esm.sh/react-dom@18.2.0/client');
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it('should override ctx.antd and ctx.libs.antd when importing antd', async () => {
|
|
70
|
+
const engine = newEngine();
|
|
71
|
+
const ctx = new FlowRunJSContext(engine.context);
|
|
72
|
+
|
|
73
|
+
const fakeAntd = { Button: 'Button' };
|
|
74
|
+
|
|
75
|
+
(runjsImportAsync as any).mockImplementation(async (url: string) => {
|
|
76
|
+
if (url === 'https://esm.sh/antd@5.29.3?bundle=1') return fakeAntd;
|
|
77
|
+
throw new Error(`unexpected import url: ${url}`);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
await ctx.importAsync('antd@5.29.3');
|
|
81
|
+
|
|
82
|
+
expect(ctx.antd).toBe(fakeAntd);
|
|
83
|
+
expect(ctx.libs.antd).toBe(fakeAntd);
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
it('should isolate roots by rendererKey and unmount on ReactDOM switch', () => {
|
|
87
|
+
const engine = newEngine();
|
|
88
|
+
const ctx = new FlowRunJSContext(engine.context);
|
|
89
|
+
|
|
90
|
+
const root1 = { render: vi.fn(), unmount: vi.fn() };
|
|
91
|
+
const root2 = { render: vi.fn(), unmount: vi.fn() };
|
|
92
|
+
|
|
93
|
+
const renderer1 = { createRoot: vi.fn(() => root1) };
|
|
94
|
+
const renderer2 = { createRoot: vi.fn(() => root2) };
|
|
95
|
+
|
|
96
|
+
ctx.defineProperty('ReactDOM', { value: renderer1 });
|
|
97
|
+
|
|
98
|
+
const container = document.createElement('div');
|
|
99
|
+
|
|
100
|
+
ctx.render({ step: 1 }, container);
|
|
101
|
+
ctx.render({ step: 2 }, container);
|
|
102
|
+
|
|
103
|
+
expect(renderer1.createRoot).toHaveBeenCalledTimes(1);
|
|
104
|
+
expect(root1.render).toHaveBeenCalledTimes(2);
|
|
105
|
+
|
|
106
|
+
ctx.defineProperty('ReactDOM', { value: renderer2 });
|
|
107
|
+
ctx.render({ step: 3 }, container);
|
|
108
|
+
|
|
109
|
+
expect(root1.unmount).toHaveBeenCalledTimes(1);
|
|
110
|
+
expect(renderer2.createRoot).toHaveBeenCalledTimes(1);
|
|
111
|
+
expect(root2.render).toHaveBeenCalledTimes(1);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('should not reuse roots across different ctx owners when rendererKey is the same', () => {
|
|
115
|
+
const engine1 = newEngine();
|
|
116
|
+
const ctx1 = new FlowRunJSContext(engine1.context);
|
|
117
|
+
|
|
118
|
+
const engine2 = newEngine();
|
|
119
|
+
const ctx2 = new FlowRunJSContext(engine2.context);
|
|
120
|
+
|
|
121
|
+
const root1 = { render: vi.fn(), unmount: vi.fn() };
|
|
122
|
+
const root2 = { render: vi.fn(), unmount: vi.fn() };
|
|
123
|
+
|
|
124
|
+
// 两个 ctx 共享同一个 ReactDOM 实例引用(rendererKey 相同),但 owner 不同;
|
|
125
|
+
// 若错误复用 entry,会导致旧 ctx 的 autorun 闭包继续持有并驱动新渲染,进而泄漏。
|
|
126
|
+
const renderer = {
|
|
127
|
+
createRoot: vi.fn(() => root1),
|
|
128
|
+
};
|
|
129
|
+
(renderer.createRoot as any).mockImplementationOnce(() => root1).mockImplementationOnce(() => root2);
|
|
130
|
+
|
|
131
|
+
ctx1.defineProperty('ReactDOM', { value: renderer });
|
|
132
|
+
ctx2.defineProperty('ReactDOM', { value: renderer });
|
|
133
|
+
|
|
134
|
+
const container = document.createElement('div');
|
|
135
|
+
|
|
136
|
+
ctx1.render({ step: 1 } as any, container);
|
|
137
|
+
ctx2.render({ step: 2 } as any, container);
|
|
138
|
+
|
|
139
|
+
expect(renderer.createRoot).toHaveBeenCalledTimes(2);
|
|
140
|
+
expect(root1.unmount).toHaveBeenCalledTimes(1);
|
|
141
|
+
expect(root2.render).toHaveBeenCalledTimes(1);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it('should wrap external antd ConfigProvider and rerender on themeToken change', async () => {
|
|
145
|
+
const engine = newEngine();
|
|
146
|
+
|
|
147
|
+
const themeState = observable.shallow({ token: { colorPrimary: 'red' } });
|
|
148
|
+
engine.context.defineProperty('themeToken', {
|
|
149
|
+
get: () => themeState.token,
|
|
150
|
+
cache: false,
|
|
151
|
+
});
|
|
152
|
+
engine.context.defineProperty('antdConfig', {
|
|
153
|
+
value: {
|
|
154
|
+
theme: { token: { colorPrimary: 'red' } },
|
|
155
|
+
prefixCls: 'ant',
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
const ctx = new FlowRunJSContext(engine.context);
|
|
160
|
+
|
|
161
|
+
const createElement = vi.fn((type: any, props: any, ...children: any[]) => ({ type, props, children }));
|
|
162
|
+
const fakeReact = { createElement };
|
|
163
|
+
const fakeAntdConfigProvider = function ConfigProvider() {};
|
|
164
|
+
const fakeAntdApp = function App() {};
|
|
165
|
+
const fakeAntd = { ConfigProvider: fakeAntdConfigProvider, App: fakeAntdApp };
|
|
166
|
+
|
|
167
|
+
const root = { render: vi.fn(), unmount: vi.fn() };
|
|
168
|
+
const fakeReactDOM = { createRoot: vi.fn(() => root) };
|
|
169
|
+
|
|
170
|
+
ctx.defineProperty('React', { value: fakeReact });
|
|
171
|
+
ctx.defineProperty('ReactDOM', { value: fakeReactDOM });
|
|
172
|
+
ctx.defineProperty('antd', { value: fakeAntd });
|
|
173
|
+
|
|
174
|
+
const container = document.createElement('div');
|
|
175
|
+
const vnode = { v: 1 };
|
|
176
|
+
|
|
177
|
+
ctx.render(vnode as any, container);
|
|
178
|
+
|
|
179
|
+
expect(root.render).toHaveBeenCalledTimes(1);
|
|
180
|
+
// createElement is called twice: App first, then ConfigProvider
|
|
181
|
+
expect(createElement).toHaveBeenNthCalledWith(
|
|
182
|
+
2,
|
|
183
|
+
fakeAntdConfigProvider,
|
|
184
|
+
expect.objectContaining({ prefixCls: 'ant' }),
|
|
185
|
+
expect.anything(),
|
|
186
|
+
);
|
|
187
|
+
|
|
188
|
+
themeState.token = { colorPrimary: 'blue' };
|
|
189
|
+
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
190
|
+
|
|
191
|
+
expect(root.render).toHaveBeenCalledTimes(2);
|
|
192
|
+
|
|
193
|
+
// cleanup (dispose autorun + unmount root)
|
|
194
|
+
ctx.render('', container);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it('should enhance hooks dispatcher-null TypeError with a helpful hint', () => {
|
|
198
|
+
const original = new TypeError(`Cannot read properties of null (reading 'useMemo')`);
|
|
199
|
+
// Mimic a real browser stack from ESM CDN where a dependency brings its own React.
|
|
200
|
+
(original as any).stack = [
|
|
201
|
+
`TypeError: Cannot read properties of null (reading 'useMemo')`,
|
|
202
|
+
` at u.useMemo (https://esm.sh/react@19.2.4/es2022/react.mjs:2:7636)`,
|
|
203
|
+
` at to (https://esm.sh/@dnd-kit/core@6.1.0/es2022/core.mjs:6:1574)`,
|
|
204
|
+
].join('\n');
|
|
205
|
+
|
|
206
|
+
const root = {
|
|
207
|
+
render: vi.fn(() => {
|
|
208
|
+
throw original;
|
|
209
|
+
}),
|
|
210
|
+
unmount: vi.fn(),
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
const internalReact = {};
|
|
214
|
+
const internalAntd = {};
|
|
215
|
+
const ctx: any = {
|
|
216
|
+
React: internalReact,
|
|
217
|
+
ReactDOM: { __nbRunjsInternalShim: true },
|
|
218
|
+
antd: internalAntd,
|
|
219
|
+
};
|
|
220
|
+
|
|
221
|
+
const entry: any = { root };
|
|
222
|
+
const containerEl = document.createElement('div');
|
|
223
|
+
|
|
224
|
+
try {
|
|
225
|
+
externalReactRender({
|
|
226
|
+
ctx,
|
|
227
|
+
entry,
|
|
228
|
+
vnode: { v: 1 },
|
|
229
|
+
containerEl,
|
|
230
|
+
rootMap: new WeakMap(),
|
|
231
|
+
unmountContainerRoot: vi.fn(),
|
|
232
|
+
internalReact,
|
|
233
|
+
internalAntd,
|
|
234
|
+
});
|
|
235
|
+
expect.fail('expected externalReactRender to throw');
|
|
236
|
+
} catch (e: any) {
|
|
237
|
+
expect(String(e?.message || '')).toContain('[RunJS Hint]');
|
|
238
|
+
expect(String(e?.message || '')).toContain('await ctx.importAsync("react@19.2.4")');
|
|
239
|
+
expect(e?.cause).toBe(original);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from 'vitest';
|
|
11
|
+
import { FlowEngine } from '../flowEngine';
|
|
12
|
+
import { registerRunJSLib } from '../runjsLibs';
|
|
13
|
+
|
|
14
|
+
describe('RunJS ctx.libs lazy loading', () => {
|
|
15
|
+
it('preloads member access via prepareRunJsCode injection', async () => {
|
|
16
|
+
registerRunJSLib('testLib', async () => ({ foo: 123 }));
|
|
17
|
+
|
|
18
|
+
const engine = new FlowEngine();
|
|
19
|
+
const ctx: any = engine.context;
|
|
20
|
+
const r = await ctx.runjs(`return ctx.libs.testLib.foo;`);
|
|
21
|
+
expect(r.success).toBe(true);
|
|
22
|
+
expect(r.value).toBe(123);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('preloads bracket access via prepareRunJsCode injection', async () => {
|
|
26
|
+
registerRunJSLib('testLib', async () => ({ foo: 456 }));
|
|
27
|
+
|
|
28
|
+
const engine = new FlowEngine();
|
|
29
|
+
const ctx: any = engine.context;
|
|
30
|
+
const r = await ctx.runjs(`return ctx.libs['testLib'].foo;`);
|
|
31
|
+
expect(r.success).toBe(true);
|
|
32
|
+
expect(r.value).toBe(456);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it('preloads object destructuring via prepareRunJsCode injection', async () => {
|
|
36
|
+
registerRunJSLib('testLib', async () => ({ foo: 789 }));
|
|
37
|
+
|
|
38
|
+
const engine = new FlowEngine();
|
|
39
|
+
const ctx: any = engine.context;
|
|
40
|
+
const r = await ctx.runjs(`const { testLib } = ctx.libs; return testLib.foo;`);
|
|
41
|
+
expect(r.success).toBe(true);
|
|
42
|
+
expect(r.value).toBe(789);
|
|
43
|
+
});
|
|
44
|
+
});
|
|
@@ -30,7 +30,10 @@ describe('RunJS locales patch (engine doc)', () => {
|
|
|
30
30
|
(ctx as any).defineProperty('model', { value: { constructor: { name: 'JSBlockModel' } } });
|
|
31
31
|
(ctx as any).defineProperty('api', { value: { auth: { locale: 'zh-CN' } } });
|
|
32
32
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
33
|
-
|
|
33
|
+
const message = doc?.properties?.message;
|
|
34
|
+
const messageText =
|
|
35
|
+
typeof message === 'string' ? message : (message as any)?.description ?? (message as any)?.detail ?? '';
|
|
36
|
+
expect(String(messageText)).toMatch(/Ant Design 全局消息 API/);
|
|
34
37
|
expect(String(doc?.methods?.t || '')).toMatch(/国际化函数/);
|
|
35
38
|
});
|
|
36
39
|
});
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, it, expect } from 'vitest';
|
|
11
|
+
import { FlowEngine } from '../flowEngine';
|
|
12
|
+
import { prepareRunJsCode } from '../utils/runjsTemplateCompat';
|
|
13
|
+
|
|
14
|
+
describe('ctx.runjs preprocessTemplates default', () => {
|
|
15
|
+
it('enables template preprocess by default', async () => {
|
|
16
|
+
const engine = new FlowEngine();
|
|
17
|
+
const ctx = engine.context as any;
|
|
18
|
+
ctx.defineProperty('user', { value: { id: 123 } });
|
|
19
|
+
|
|
20
|
+
const r1 = await ctx.runjs('return {{ctx.user.id}};');
|
|
21
|
+
expect(r1.success).toBe(true);
|
|
22
|
+
expect(r1.value).toBe(123);
|
|
23
|
+
|
|
24
|
+
const r2 = await ctx.runjs('return "{{ctx.user.id}}";');
|
|
25
|
+
expect(r2.success).toBe(true);
|
|
26
|
+
expect(r2.value).toBe('123');
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
it('can disable template preprocess explicitly', async () => {
|
|
30
|
+
const engine = new FlowEngine();
|
|
31
|
+
const ctx = engine.context as any;
|
|
32
|
+
ctx.defineProperty('user', { value: { id: 123 } });
|
|
33
|
+
|
|
34
|
+
const r = await ctx.runjs('return "{{ctx.user.id}}";', undefined, { preprocessTemplates: false });
|
|
35
|
+
expect(r.success).toBe(true);
|
|
36
|
+
expect(r.value).toBe('{{ctx.user.id}}');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('does not double-preprocess already prepared code', async () => {
|
|
40
|
+
const engine = new FlowEngine();
|
|
41
|
+
const ctx = engine.context as any;
|
|
42
|
+
ctx.defineProperty('user', { value: { id: 123 } });
|
|
43
|
+
|
|
44
|
+
const prepared = await prepareRunJsCode('return "{{ctx.user.id}}";', { preprocessTemplates: true });
|
|
45
|
+
const r = await ctx.runjs(prepared);
|
|
46
|
+
expect(r.success).toBe(true);
|
|
47
|
+
expect(r.value).toBe('123');
|
|
48
|
+
});
|
|
49
|
+
});
|
|
@@ -13,6 +13,7 @@ import { FlowEngine } from '../flowEngine';
|
|
|
13
13
|
import { FlowContext } from '../flowContext';
|
|
14
14
|
import { setupRunJSContexts } from '../runjs-context/setup';
|
|
15
15
|
import { createJSRunnerWithVersion } from '..';
|
|
16
|
+
import { RunJSContextRegistry } from '../runjs-context/registry';
|
|
16
17
|
|
|
17
18
|
describe('RunJS Runtime Features', () => {
|
|
18
19
|
let engine: FlowEngine;
|
|
@@ -458,4 +459,169 @@ describe('RunJS Runtime Features', () => {
|
|
|
458
459
|
expect(result?.error).toBeTruthy();
|
|
459
460
|
});
|
|
460
461
|
});
|
|
462
|
+
|
|
463
|
+
describe('Deprecation warnings', () => {
|
|
464
|
+
const version = 'test-deprecation-warnings';
|
|
465
|
+
const dynamicDefineVersion = 'test-deprecation-dynamic-defineProperty';
|
|
466
|
+
|
|
467
|
+
beforeAll(() => {
|
|
468
|
+
class TestDeprecatedRunJSContext extends FlowRunJSContext {}
|
|
469
|
+
TestDeprecatedRunJSContext.define({
|
|
470
|
+
label: 'RunJS deprecated test',
|
|
471
|
+
properties: {
|
|
472
|
+
resource: {
|
|
473
|
+
properties: {
|
|
474
|
+
getData: {
|
|
475
|
+
type: 'function',
|
|
476
|
+
description: 'Deprecated deep-path method for testing.',
|
|
477
|
+
deprecated: {
|
|
478
|
+
message: 'Use refresh instead.',
|
|
479
|
+
replacedBy: 'ctx.resource.refresh',
|
|
480
|
+
since: '0.0.0-test',
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
});
|
|
487
|
+
RunJSContextRegistry.register(version as any, '*', TestDeprecatedRunJSContext);
|
|
488
|
+
|
|
489
|
+
class TestDynamicDefineRunJSContext extends FlowRunJSContext {}
|
|
490
|
+
TestDynamicDefineRunJSContext.define({
|
|
491
|
+
label: 'RunJS deprecated dynamic defineProperty test',
|
|
492
|
+
});
|
|
493
|
+
RunJSContextRegistry.register(dynamicDefineVersion as any, '*', TestDynamicDefineRunJSContext);
|
|
494
|
+
});
|
|
495
|
+
|
|
496
|
+
it('should warn when calling a deprecated deep-path API', async () => {
|
|
497
|
+
const ctx = new FlowContext();
|
|
498
|
+
const warn = vi.fn();
|
|
499
|
+
ctx.defineProperty('logger', { value: { warn } });
|
|
500
|
+
ctx.defineProperty('model', { value: { constructor: { name: 'JSBlockModel' } } });
|
|
501
|
+
ctx.defineProperty('resource', { value: { getData: () => 123 } });
|
|
502
|
+
|
|
503
|
+
const runner = createJSRunnerWithVersion.call(ctx, { version });
|
|
504
|
+
const result = await runner.run('return ctx.resource.getData()');
|
|
505
|
+
|
|
506
|
+
expect(result?.success).toBe(true);
|
|
507
|
+
expect(result?.value).toBe(123);
|
|
508
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
509
|
+
expect(warn.mock.calls[0]?.[0]).toContain('[RunJS][Deprecated] ctx.resource.getData');
|
|
510
|
+
expect(warn.mock.calls[0]?.[0]).toContain('Use refresh instead.');
|
|
511
|
+
expect(warn.mock.calls[0]?.[0]).toContain('Use ctx.resource.refresh instead');
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('should only warn on invoke (accessing should not warn)', async () => {
|
|
515
|
+
const ctx = new FlowContext();
|
|
516
|
+
const warn = vi.fn();
|
|
517
|
+
ctx.defineProperty('logger', { value: { warn } });
|
|
518
|
+
ctx.defineProperty('model', { value: { constructor: { name: 'JSBlockModel' } } });
|
|
519
|
+
ctx.defineProperty('resource', { value: { getData: () => 1 } });
|
|
520
|
+
|
|
521
|
+
const runner = createJSRunnerWithVersion.call(ctx, { version });
|
|
522
|
+
const result = await runner.run(`
|
|
523
|
+
const fn = ctx.resource.getData;
|
|
524
|
+
return typeof fn === 'function';
|
|
525
|
+
`);
|
|
526
|
+
|
|
527
|
+
expect(result?.success).toBe(true);
|
|
528
|
+
expect(result?.value).toBe(true);
|
|
529
|
+
expect(warn).toHaveBeenCalledTimes(0);
|
|
530
|
+
});
|
|
531
|
+
|
|
532
|
+
it('should warn once per RunJS execution for the same API path', async () => {
|
|
533
|
+
const ctx = new FlowContext();
|
|
534
|
+
const warn = vi.fn();
|
|
535
|
+
ctx.defineProperty('logger', { value: { warn } });
|
|
536
|
+
ctx.defineProperty('model', { value: { constructor: { name: 'JSBlockModel' } } });
|
|
537
|
+
ctx.defineProperty('resource', { value: { getData: () => 1 } });
|
|
538
|
+
|
|
539
|
+
const runner1 = createJSRunnerWithVersion.call(ctx, { version });
|
|
540
|
+
const r1 = await runner1.run(`
|
|
541
|
+
ctx.resource.getData();
|
|
542
|
+
ctx.resource.getData();
|
|
543
|
+
return true;
|
|
544
|
+
`);
|
|
545
|
+
expect(r1?.success).toBe(true);
|
|
546
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
547
|
+
|
|
548
|
+
const runner2 = createJSRunnerWithVersion.call(ctx, { version });
|
|
549
|
+
const r2 = await runner2.run('ctx.resource.getData(); return true;');
|
|
550
|
+
expect(r2?.success).toBe(true);
|
|
551
|
+
expect(warn).toHaveBeenCalledTimes(2);
|
|
552
|
+
});
|
|
553
|
+
|
|
554
|
+
it('should warn when accessing a deprecated property defined via FlowContext info', async () => {
|
|
555
|
+
const ctx = new FlowContext();
|
|
556
|
+
const warn = vi.fn();
|
|
557
|
+
ctx.defineProperty('logger', { value: { warn } });
|
|
558
|
+
ctx.defineProperty('model', { value: { constructor: { name: 'JSBlockModel' } } });
|
|
559
|
+
ctx.defineProperty('element', {
|
|
560
|
+
value: { innerHTML: '<div />' },
|
|
561
|
+
info: { deprecated: { message: 'it is deprecated!' } },
|
|
562
|
+
});
|
|
563
|
+
|
|
564
|
+
const runner = createJSRunnerWithVersion.call(ctx, { version: 'v1' });
|
|
565
|
+
const result = await runner.run('return ctx.element.innerHTML');
|
|
566
|
+
|
|
567
|
+
expect(result?.success).toBe(true);
|
|
568
|
+
expect(result?.value).toBe('<div />');
|
|
569
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
570
|
+
expect(warn.mock.calls[0]?.[0]).toContain('[RunJS][Deprecated] ctx.element it is deprecated!');
|
|
571
|
+
expect(warn.mock.calls[0]?.[0]).not.toContain('已废弃');
|
|
572
|
+
expect(warn.mock.calls[0]?.[0]).toContain('line');
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it('should warn for deprecated APIs defined via ctx.defineProperty during execution', async () => {
|
|
576
|
+
const ctx = new FlowContext();
|
|
577
|
+
const warn = vi.fn();
|
|
578
|
+
ctx.defineProperty('logger', { value: { warn } });
|
|
579
|
+
ctx.defineProperty('model', { value: { constructor: { name: 'JSBlockModel' } } });
|
|
580
|
+
|
|
581
|
+
const runner = createJSRunnerWithVersion.call(ctx, { version: dynamicDefineVersion });
|
|
582
|
+
const result = await runner.run(`
|
|
583
|
+
ctx.defineProperty('element', {
|
|
584
|
+
value: { innerHTML: '<div />' },
|
|
585
|
+
info: { deprecated: { message: 'it is deprecated!' } },
|
|
586
|
+
});
|
|
587
|
+
return ctx.element.innerHTML;
|
|
588
|
+
`);
|
|
589
|
+
|
|
590
|
+
expect(result?.success).toBe(true);
|
|
591
|
+
expect(result?.value).toBe('<div />');
|
|
592
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
593
|
+
expect(warn.mock.calls[0]?.[0]).toContain('[RunJS][Deprecated] ctx.element it is deprecated!');
|
|
594
|
+
expect(warn.mock.calls[0]?.[0]).not.toContain('已废弃');
|
|
595
|
+
});
|
|
596
|
+
|
|
597
|
+
it('should support deprecated deep-path defined via FlowContext info.properties', async () => {
|
|
598
|
+
const ctx = new FlowContext();
|
|
599
|
+
const warn = vi.fn();
|
|
600
|
+
ctx.defineProperty('logger', { value: { warn } });
|
|
601
|
+
ctx.defineProperty('model', { value: { constructor: { name: 'JSBlockModel' } } });
|
|
602
|
+
ctx.defineProperty('element', {
|
|
603
|
+
value: { innerHTML: '<div />' },
|
|
604
|
+
info: {
|
|
605
|
+
properties: {
|
|
606
|
+
innerHTML: {
|
|
607
|
+
deprecated: {
|
|
608
|
+
message: 'Use append instead.',
|
|
609
|
+
replacedBy: 'ctx.element.append',
|
|
610
|
+
},
|
|
611
|
+
},
|
|
612
|
+
},
|
|
613
|
+
},
|
|
614
|
+
});
|
|
615
|
+
|
|
616
|
+
const runner = createJSRunnerWithVersion.call(ctx, { version: 'v1' });
|
|
617
|
+
const result = await runner.run('return ctx.element.innerHTML');
|
|
618
|
+
|
|
619
|
+
expect(result?.success).toBe(true);
|
|
620
|
+
expect(result?.value).toBe('<div />');
|
|
621
|
+
expect(warn).toHaveBeenCalledTimes(1);
|
|
622
|
+
expect(warn.mock.calls[0]?.[0]).toContain('[RunJS][Deprecated] ctx.element.innerHTML');
|
|
623
|
+
expect(warn.mock.calls[0]?.[0]).toContain('Use append instead.');
|
|
624
|
+
expect(warn.mock.calls[0]?.[0]).toContain('Use ctx.element.append instead');
|
|
625
|
+
});
|
|
626
|
+
});
|
|
461
627
|
});
|
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { describe,
|
|
11
|
-
import { getSnippetBody, listSnippetsForContext } from '../runjs-context/snippets';
|
|
10
|
+
import { describe, expect, it } from 'vitest';
|
|
11
|
+
import { getSnippetBody, listSnippetsForContext, registerRunJSSnippet } from '../runjs-context/snippets';
|
|
12
12
|
|
|
13
13
|
describe('RunJS Snippets', () => {
|
|
14
14
|
describe('getSnippetBody', () => {
|
|
@@ -22,7 +22,7 @@ describe('RunJS Snippets', () => {
|
|
|
22
22
|
it('should return snippet body for global/api-request', async () => {
|
|
23
23
|
const body = await getSnippetBody('global/api-request');
|
|
24
24
|
expect(body).toBeTruthy();
|
|
25
|
-
expect(body).toContain('ctx.
|
|
25
|
+
expect(body).toContain('ctx.request');
|
|
26
26
|
});
|
|
27
27
|
|
|
28
28
|
it('should throw error for non-existent snippet', async () => {
|
|
@@ -137,4 +137,41 @@ describe('RunJS Snippets', () => {
|
|
|
137
137
|
await expect(getSnippetBody('global/copy-record-json')).rejects.toThrow();
|
|
138
138
|
});
|
|
139
139
|
});
|
|
140
|
+
|
|
141
|
+
describe('registerRunJSSnippet', () => {
|
|
142
|
+
it('should allow registering and consuming a custom snippet', async () => {
|
|
143
|
+
const ref = 'plugin/test/hello';
|
|
144
|
+
registerRunJSSnippet(ref, async () => ({
|
|
145
|
+
default: {
|
|
146
|
+
content: `console.log('hello');`,
|
|
147
|
+
label: 'Hello',
|
|
148
|
+
versions: ['v1'],
|
|
149
|
+
contexts: ['*'],
|
|
150
|
+
scenes: ['block'],
|
|
151
|
+
},
|
|
152
|
+
}));
|
|
153
|
+
|
|
154
|
+
const body = await getSnippetBody(ref);
|
|
155
|
+
expect(body).toContain('hello');
|
|
156
|
+
|
|
157
|
+
const list = await listSnippetsForContext('*', 'v1', 'en-US');
|
|
158
|
+
expect(list.some((s) => s.ref === ref)).toBe(true);
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it('should not overwrite existing refs by default, but can overwrite with override=true', async () => {
|
|
162
|
+
const ref = 'plugin/test/collision';
|
|
163
|
+
registerRunJSSnippet(ref, async () => ({ default: { content: 'A', label: 'A' } }));
|
|
164
|
+
expect(await getSnippetBody(ref)).toBe('A');
|
|
165
|
+
|
|
166
|
+
const overwritten = registerRunJSSnippet(ref, async () => ({ default: { content: 'B', label: 'B' } }));
|
|
167
|
+
expect(overwritten).toBe(false);
|
|
168
|
+
expect(await getSnippetBody(ref)).toBe('A');
|
|
169
|
+
|
|
170
|
+
const forced = registerRunJSSnippet(ref, async () => ({ default: { content: 'C', label: 'C' } }), {
|
|
171
|
+
override: true,
|
|
172
|
+
});
|
|
173
|
+
expect(forced).toBe(true);
|
|
174
|
+
expect(await getSnippetBody(ref)).toBe('C');
|
|
175
|
+
});
|
|
176
|
+
});
|
|
140
177
|
});
|
package/src/acl/Acl.tsx
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
|
-
import { omit } from 'lodash';
|
|
9
|
+
import _, { omit } from 'lodash';
|
|
10
10
|
import { FlowEngine } from '../flowEngine';
|
|
11
11
|
import { FlowModel } from '../models/flowModel';
|
|
12
12
|
|
|
@@ -31,11 +31,11 @@ export class ACL {
|
|
|
31
31
|
constructor(private flowEngine: FlowEngine) {}
|
|
32
32
|
|
|
33
33
|
setData(data: Record<string, any>) {
|
|
34
|
-
this.data = data;
|
|
34
|
+
this.data = _.cloneDeep(data);
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
setMeta(data: Record<string, any>) {
|
|
38
|
-
this.meta = data;
|
|
38
|
+
this.meta = _.cloneDeep(data);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
async load() {
|