@nocobase/flow-engine 2.1.0-beta.8 → 2.2.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/FlowContextProvider.d.ts +5 -1
- package/lib/FlowContextProvider.js +9 -2
- package/lib/components/FieldModelRenderer.js +2 -2
- package/lib/components/FlowModelRenderer.d.ts +3 -1
- package/lib/components/FlowModelRenderer.js +12 -6
- package/lib/components/FormItem.d.ts +6 -0
- package/lib/components/FormItem.js +11 -3
- package/lib/components/MobilePopup.js +6 -5
- package/lib/components/dnd/gridDragPlanner.d.ts +59 -2
- package/lib/components/dnd/gridDragPlanner.js +607 -19
- package/lib/components/dnd/index.d.ts +31 -2
- package/lib/components/dnd/index.js +244 -23
- package/lib/components/settings/wrappers/component/SelectWithTitle.d.ts +2 -1
- package/lib/components/settings/wrappers/component/SelectWithTitle.js +14 -12
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.d.ts +3 -0
- package/lib/components/settings/wrappers/contextual/DefaultSettingsIcon.js +152 -42
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
- package/lib/components/subModel/AddSubModelButton.js +12 -1
- package/lib/components/subModel/LazyDropdown.js +301 -52
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +2 -1
- package/lib/components/subModel/utils.js +15 -5
- package/lib/components/variables/VariableHybridInput.d.ts +27 -0
- package/lib/components/variables/VariableHybridInput.js +499 -0
- package/lib/components/variables/index.d.ts +2 -0
- package/lib/components/variables/index.js +3 -0
- package/lib/data-source/index.d.ts +84 -0
- package/lib/data-source/index.js +269 -7
- package/lib/executor/FlowExecutor.js +6 -3
- package/lib/flow-registry/DetachedFlowRegistry.d.ts +21 -0
- package/lib/flow-registry/DetachedFlowRegistry.js +80 -0
- package/lib/flow-registry/index.d.ts +1 -0
- package/lib/flow-registry/index.js +3 -1
- package/lib/flowContext.d.ts +9 -1
- package/lib/flowContext.js +77 -6
- package/lib/flowEngine.d.ts +136 -4
- package/lib/flowEngine.js +429 -51
- package/lib/flowI18n.js +2 -1
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +34 -6
- package/lib/index.d.ts +2 -0
- package/lib/index.js +7 -0
- package/lib/lazy-helper.d.ts +14 -0
- package/lib/lazy-helper.js +71 -0
- package/lib/locale/en-US.json +1 -0
- package/lib/locale/index.d.ts +2 -0
- package/lib/locale/zh-CN.json +1 -0
- package/lib/models/DisplayItemModel.d.ts +1 -1
- package/lib/models/EditableItemModel.d.ts +1 -1
- package/lib/models/FilterableItemModel.d.ts +1 -1
- package/lib/models/flowModel.d.ts +13 -10
- package/lib/models/flowModel.js +126 -34
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/contexts/FormJSFieldItemRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/JSBlockRunJSContext.js +4 -15
- package/lib/runjs-context/contexts/JSColumnRunJSContext.js +5 -2
- package/lib/runjs-context/contexts/JSEditableFieldRunJSContext.js +5 -8
- package/lib/runjs-context/contexts/JSFieldRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/JSItemRunJSContext.js +4 -3
- package/lib/runjs-context/contexts/base.js +464 -29
- package/lib/runjs-context/contexts/elementDoc.d.ts +11 -0
- package/lib/runjs-context/contexts/elementDoc.js +152 -0
- package/lib/runjs-context/setup.js +1 -0
- package/lib/runjs-context/snippets/index.js +13 -2
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
- package/lib/types.d.ts +50 -2
- package/lib/types.js +1 -0
- package/lib/utils/createCollectionContextMeta.js +6 -2
- package/lib/utils/index.d.ts +3 -2
- package/lib/utils/index.js +7 -0
- package/lib/utils/loadedPageCache.d.ts +24 -0
- package/lib/utils/loadedPageCache.js +139 -0
- package/lib/utils/parsePathnameToViewParams.d.ts +5 -1
- package/lib/utils/parsePathnameToViewParams.js +28 -4
- package/lib/utils/randomId.d.ts +39 -0
- package/lib/utils/randomId.js +45 -0
- package/lib/utils/runjsTemplateCompat.js +1 -1
- package/lib/utils/runjsValue.js +41 -11
- package/lib/utils/schema-utils.d.ts +7 -1
- package/lib/utils/schema-utils.js +19 -0
- package/lib/views/FlowView.d.ts +7 -1
- package/lib/views/FlowView.js +11 -1
- package/lib/views/PageComponent.js +8 -6
- package/lib/views/ViewNavigation.d.ts +12 -2
- package/lib/views/ViewNavigation.js +28 -9
- package/lib/views/createViewMeta.js +114 -50
- package/lib/views/inheritLayoutContext.d.ts +10 -0
- package/lib/views/inheritLayoutContext.js +50 -0
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +12 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +12 -3
- package/lib/views/usePage.d.ts +5 -11
- package/lib/views/usePage.js +304 -144
- package/package.json +5 -4
- package/src/FlowContextProvider.tsx +9 -1
- package/src/__tests__/createViewMeta.popup.test.ts +115 -1
- package/src/__tests__/flow-engine.test.ts +166 -0
- package/src/__tests__/flowContext.test.ts +105 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowEngine.moveModel.test.ts +81 -1
- package/src/__tests__/flowEngine.removeModel.test.ts +47 -3
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/objectVariable.test.ts +24 -0
- package/src/__tests__/provider.test.tsx +24 -2
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +21 -0
- package/src/__tests__/runjsContextImplementations.test.ts +9 -2
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsLocales.test.ts +6 -5
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +136 -3
- package/src/components/FieldModelRenderer.tsx +2 -1
- package/src/components/FlowModelRenderer.tsx +18 -6
- package/src/components/FormItem.tsx +7 -1
- package/src/components/MobilePopup.tsx +4 -2
- package/src/components/__tests__/FlowModelRenderer.test.tsx +65 -2
- package/src/components/__tests__/FormItem.test.tsx +25 -0
- package/src/components/__tests__/dnd.test.ts +44 -0
- package/src/components/__tests__/flow-model-render-error-fallback.test.tsx +20 -10
- package/src/components/__tests__/gridDragPlanner.test.ts +472 -5
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +750 -17
- package/src/components/dnd/index.tsx +305 -28
- package/src/components/settings/wrappers/component/SelectWithTitle.tsx +21 -9
- package/src/components/settings/wrappers/contextual/DefaultSettingsIcon.tsx +178 -48
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +344 -8
- package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
- package/src/components/subModel/AddSubModelButton.tsx +16 -2
- package/src/components/subModel/LazyDropdown.tsx +341 -56
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +524 -38
- package/src/components/subModel/__tests__/utils.test.ts +24 -0
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +13 -2
- package/src/components/variables/VariableHybridInput.tsx +531 -0
- package/src/components/variables/index.ts +2 -0
- package/src/data-source/__tests__/collection.test.ts +41 -2
- package/src/data-source/__tests__/index.test.ts +69 -2
- package/src/data-source/index.ts +332 -8
- package/src/executor/FlowExecutor.ts +6 -3
- package/src/executor/__tests__/flowExecutor.test.ts +57 -0
- package/src/flow-registry/DetachedFlowRegistry.ts +46 -0
- package/src/flow-registry/__tests__/detachedFlowRegistry.test.ts +47 -0
- package/src/flow-registry/index.ts +1 -0
- package/src/flowContext.ts +85 -6
- package/src/flowEngine.ts +484 -45
- package/src/flowI18n.ts +2 -1
- package/src/flowSettings.ts +40 -6
- package/src/index.ts +2 -0
- package/src/lazy-helper.tsx +57 -0
- package/src/locale/en-US.json +1 -0
- package/src/locale/zh-CN.json +1 -0
- package/src/models/DisplayItemModel.tsx +1 -1
- package/src/models/EditableItemModel.tsx +1 -1
- package/src/models/FilterableItemModel.tsx +1 -1
- package/src/models/__tests__/flowEngine.resolveUse.test.ts +0 -15
- package/src/models/__tests__/flowModel.test.ts +65 -37
- package/src/models/flowModel.tsx +184 -65
- package/src/provider.tsx +41 -25
- package/src/reactive/__tests__/observer.test.tsx +82 -0
- package/src/reactive/observer.tsx +87 -25
- package/src/runjs-context/contexts/FormJSFieldItemRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/JSBlockRunJSContext.ts +4 -15
- package/src/runjs-context/contexts/JSColumnRunJSContext.ts +4 -2
- package/src/runjs-context/contexts/JSEditableFieldRunJSContext.ts +5 -9
- package/src/runjs-context/contexts/JSFieldRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/JSItemRunJSContext.ts +4 -3
- package/src/runjs-context/contexts/base.ts +467 -31
- package/src/runjs-context/contexts/elementDoc.ts +130 -0
- package/src/runjs-context/setup.ts +1 -0
- package/src/runjs-context/snippets/index.ts +12 -1
- package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
- package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
- package/src/types.ts +62 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +21 -0
- package/src/utils/__tests__/runjsValue.test.ts +11 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/createCollectionContextMeta.ts +6 -2
- package/src/utils/index.ts +5 -1
- package/src/utils/loadedPageCache.ts +147 -0
- package/src/utils/parsePathnameToViewParams.ts +45 -5
- package/src/utils/randomId.ts +48 -0
- package/src/utils/runjsTemplateCompat.ts +1 -1
- package/src/utils/runjsValue.ts +50 -11
- package/src/utils/schema-utils.ts +30 -1
- package/src/views/FlowView.tsx +22 -2
- package/src/views/PageComponent.tsx +7 -4
- package/src/views/ViewNavigation.ts +46 -9
- package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
- package/src/views/__tests__/ViewNavigation.test.ts +52 -0
- package/src/views/__tests__/inheritLayoutContext.test.ts +53 -0
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +12 -12
- package/src/views/createViewMeta.ts +106 -34
- package/src/views/inheritLayoutContext.ts +26 -0
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +13 -3
- package/src/views/useDrawer.tsx +13 -3
- package/src/views/usePage.tsx +367 -180
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { beforeEach, describe, expect, it } from 'vitest';
|
|
10
|
+
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
11
11
|
import { FlowEngine } from '../flowEngine';
|
|
12
12
|
import { FlowModel } from '../models';
|
|
13
13
|
|
|
@@ -20,6 +20,7 @@ describe('FlowEngine removeModel', () => {
|
|
|
20
20
|
});
|
|
21
21
|
|
|
22
22
|
it('removeModel should remove model but keep sub-models in cache (current behavior)', () => {
|
|
23
|
+
const loggerSpy = vi.spyOn(engine.logger, 'debug').mockImplementation(() => {});
|
|
23
24
|
const parent = engine.createModel({ uid: 'parent', use: 'FlowModel' });
|
|
24
25
|
const child = engine.createModel({
|
|
25
26
|
uid: 'child',
|
|
@@ -32,14 +33,53 @@ describe('FlowEngine removeModel', () => {
|
|
|
32
33
|
expect(engine.getModel('parent')).toBe(parent);
|
|
33
34
|
expect(engine.getModel('child')).toBe(child);
|
|
34
35
|
|
|
35
|
-
|
|
36
|
+
try {
|
|
37
|
+
engine.removeModel('parent');
|
|
38
|
+
} finally {
|
|
39
|
+
loggerSpy.mockRestore();
|
|
40
|
+
}
|
|
36
41
|
|
|
37
42
|
expect(engine.getModel('parent')).toBeUndefined();
|
|
38
43
|
// Current behavior: child is still in cache
|
|
39
44
|
expect(engine.getModel('child')).toBeDefined();
|
|
40
45
|
});
|
|
41
46
|
|
|
47
|
+
it('removeModel should log missing models at debug level', () => {
|
|
48
|
+
const consoleWarnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
49
|
+
const loggerSpy = vi.spyOn(engine.logger, 'debug').mockImplementation(() => {});
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
expect(engine.removeModel('missing')).toBe(false);
|
|
53
|
+
expect(consoleWarnSpy).not.toHaveBeenCalled();
|
|
54
|
+
expect(loggerSpy).toHaveBeenCalledWith("FlowEngine: Model with UID 'missing' does not exist.");
|
|
55
|
+
} finally {
|
|
56
|
+
consoleWarnSpy.mockRestore();
|
|
57
|
+
loggerSpy.mockRestore();
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('should reduce default logger verbosity in production', () => {
|
|
62
|
+
const originalNodeEnv = process.env.NODE_ENV;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
process.env.NODE_ENV = 'production';
|
|
66
|
+
|
|
67
|
+
const productionEngine = new FlowEngine();
|
|
68
|
+
|
|
69
|
+
expect(productionEngine.logger.level).toBe('warn');
|
|
70
|
+
expect(productionEngine.logger.isLevelEnabled('debug')).toBe(false);
|
|
71
|
+
expect(productionEngine.logger.isLevelEnabled('warn')).toBe(true);
|
|
72
|
+
} finally {
|
|
73
|
+
if (originalNodeEnv === undefined) {
|
|
74
|
+
delete process.env.NODE_ENV;
|
|
75
|
+
} else {
|
|
76
|
+
process.env.NODE_ENV = originalNodeEnv;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
|
|
42
81
|
it('removeModelWithSubModels should remove model and all sub-models from cache', () => {
|
|
82
|
+
const loggerSpy = vi.spyOn(engine.logger, 'debug').mockImplementation(() => {});
|
|
43
83
|
const parent = engine.createModel({ uid: 'parent', use: 'FlowModel' });
|
|
44
84
|
const child = engine.createModel({
|
|
45
85
|
uid: 'child',
|
|
@@ -63,7 +103,11 @@ describe('FlowEngine removeModel', () => {
|
|
|
63
103
|
expect(engine.getModel('child')).toBe(child);
|
|
64
104
|
expect(engine.getModel('grandChild')).toBe(grandChild);
|
|
65
105
|
|
|
66
|
-
|
|
106
|
+
try {
|
|
107
|
+
engine.removeModelWithSubModels('parent');
|
|
108
|
+
} finally {
|
|
109
|
+
loggerSpy.mockRestore();
|
|
110
|
+
}
|
|
67
111
|
|
|
68
112
|
expect(engine.getModel('parent')).toBeUndefined();
|
|
69
113
|
expect(engine.getModel('child')).toBeUndefined();
|
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, test, expect, beforeEach, afterEach, vi } from 'vitest';
|
|
11
|
+
import React from 'react';
|
|
12
|
+
import { createForm } from '@formily/core';
|
|
13
|
+
import { createSchemaField, FormProvider } from '@formily/react';
|
|
14
|
+
import { render, screen } from '@testing-library/react';
|
|
11
15
|
import { FlowSettings } from '../flowSettings';
|
|
12
16
|
import { DefaultSettingsIcon } from '../components/settings/wrappers/contextual/DefaultSettingsIcon';
|
|
13
17
|
import { FlowModel } from '../models';
|
|
@@ -142,10 +146,10 @@ describe('FlowSettings', () => {
|
|
|
142
146
|
expect(settingsItem?.sort).toBe(0);
|
|
143
147
|
});
|
|
144
148
|
|
|
145
|
-
test('should set up observable properties', () => {
|
|
149
|
+
test('should set up observable properties', async () => {
|
|
146
150
|
// Test that enabled property is reactive
|
|
147
151
|
const initialEnabled = flowSettings.enabled;
|
|
148
|
-
flowSettings.enable();
|
|
152
|
+
await flowSettings.enable();
|
|
149
153
|
expect(flowSettings.enabled).not.toBe(initialEnabled);
|
|
150
154
|
expect(flowSettings.enabled).toBe(true);
|
|
151
155
|
});
|
|
@@ -186,6 +190,43 @@ describe('FlowSettings', () => {
|
|
|
186
190
|
flowSettings.registerComponents({});
|
|
187
191
|
expect(Object.keys(flowSettings.components)).toHaveLength(0);
|
|
188
192
|
});
|
|
193
|
+
|
|
194
|
+
test('should register component loaders and load component on render', async () => {
|
|
195
|
+
const loader = vi.fn(async () => ({
|
|
196
|
+
default: () => React.createElement('div', null, 'Lazy Flow Settings Component'),
|
|
197
|
+
}));
|
|
198
|
+
|
|
199
|
+
flowSettings.registerComponentLoaders({
|
|
200
|
+
DemoFlowSettingsLazyField: loader,
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
expect(loader).not.toHaveBeenCalled();
|
|
204
|
+
|
|
205
|
+
const SchemaField = createSchemaField();
|
|
206
|
+
const form = createForm();
|
|
207
|
+
|
|
208
|
+
render(
|
|
209
|
+
React.createElement(
|
|
210
|
+
FormProvider,
|
|
211
|
+
{ form },
|
|
212
|
+
React.createElement(SchemaField, {
|
|
213
|
+
schema: {
|
|
214
|
+
type: 'object',
|
|
215
|
+
properties: {
|
|
216
|
+
demo: {
|
|
217
|
+
type: 'void',
|
|
218
|
+
'x-component': 'DemoFlowSettingsLazyField',
|
|
219
|
+
},
|
|
220
|
+
},
|
|
221
|
+
},
|
|
222
|
+
components: flowSettings.components,
|
|
223
|
+
}),
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
|
|
227
|
+
expect(await screen.findByText('Lazy Flow Settings Component')).toBeInTheDocument();
|
|
228
|
+
expect(loader).toHaveBeenCalledTimes(1);
|
|
229
|
+
});
|
|
189
230
|
});
|
|
190
231
|
|
|
191
232
|
describe('Scope Registration', () => {
|
|
@@ -228,30 +269,68 @@ describe('FlowSettings', () => {
|
|
|
228
269
|
});
|
|
229
270
|
|
|
230
271
|
describe('Enable/Disable Functionality', () => {
|
|
231
|
-
test('should enable flow settings', () => {
|
|
272
|
+
test('should enable flow settings', async () => {
|
|
232
273
|
expect(flowSettings.enabled).toBe(false);
|
|
233
274
|
|
|
234
|
-
flowSettings.enable();
|
|
275
|
+
await flowSettings.enable();
|
|
235
276
|
|
|
236
277
|
expect(flowSettings.enabled).toBe(true);
|
|
237
278
|
});
|
|
238
279
|
|
|
239
|
-
test('should
|
|
240
|
-
|
|
280
|
+
test('should preload model loaders before enabling flow settings', async () => {
|
|
281
|
+
const preloadSpy = vi.spyOn(engine, 'preloadModelLoaders').mockResolvedValue({
|
|
282
|
+
requested: [],
|
|
283
|
+
loaded: [],
|
|
284
|
+
failed: [],
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
await flowSettings.enable();
|
|
288
|
+
|
|
289
|
+
expect(preloadSpy).toHaveBeenCalledTimes(1);
|
|
241
290
|
expect(flowSettings.enabled).toBe(true);
|
|
291
|
+
});
|
|
242
292
|
|
|
243
|
-
|
|
293
|
+
test('should preload model loaders before force enabling flow settings', async () => {
|
|
294
|
+
const preloadSpy = vi.spyOn(engine, 'preloadModelLoaders').mockResolvedValue({
|
|
295
|
+
requested: [],
|
|
296
|
+
loaded: [],
|
|
297
|
+
failed: [],
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
await flowSettings.forceEnable();
|
|
301
|
+
|
|
302
|
+
expect(preloadSpy).toHaveBeenCalledTimes(1);
|
|
303
|
+
expect(flowSettings.enabled).toBe(true);
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
test('should disable flow settings', async () => {
|
|
307
|
+
await flowSettings.enable();
|
|
308
|
+
expect(flowSettings.enabled).toBe(true);
|
|
309
|
+
|
|
310
|
+
await flowSettings.disable();
|
|
244
311
|
|
|
245
312
|
expect(flowSettings.enabled).toBe(false);
|
|
246
313
|
});
|
|
247
314
|
|
|
248
|
-
test('should handle multiple enable/disable calls', () => {
|
|
249
|
-
flowSettings.enable();
|
|
250
|
-
flowSettings.enable();
|
|
315
|
+
test('should handle multiple enable/disable calls', async () => {
|
|
316
|
+
await flowSettings.enable();
|
|
317
|
+
await flowSettings.enable();
|
|
251
318
|
expect(flowSettings.enabled).toBe(true);
|
|
252
319
|
|
|
253
|
-
flowSettings.disable();
|
|
254
|
-
flowSettings.disable();
|
|
320
|
+
await flowSettings.disable();
|
|
321
|
+
await flowSettings.disable();
|
|
322
|
+
expect(flowSettings.enabled).toBe(false);
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
test('forceDisable should clear force-enabled state and disable flow settings', async () => {
|
|
326
|
+
await flowSettings.forceEnable();
|
|
327
|
+
expect(flowSettings.enabled).toBe(true);
|
|
328
|
+
|
|
329
|
+
await flowSettings.forceDisable();
|
|
330
|
+
|
|
331
|
+
expect(flowSettings.enabled).toBe(false);
|
|
332
|
+
|
|
333
|
+
await flowSettings.disable();
|
|
255
334
|
expect(flowSettings.enabled).toBe(false);
|
|
256
335
|
});
|
|
257
336
|
});
|
|
@@ -512,7 +591,7 @@ describe('FlowSettings', () => {
|
|
|
512
591
|
});
|
|
513
592
|
|
|
514
593
|
describe('Complex Integration Scenarios', () => {
|
|
515
|
-
test('should maintain state consistency during multiple operations', () => {
|
|
594
|
+
test('should maintain state consistency during multiple operations', async () => {
|
|
516
595
|
// Initialize with components and scopes
|
|
517
596
|
const TestComponent = () => 'TestComponent';
|
|
518
597
|
const testScope = () => 'testScope';
|
|
@@ -528,7 +607,7 @@ describe('FlowSettings', () => {
|
|
|
528
607
|
});
|
|
529
608
|
|
|
530
609
|
// Enable/disable
|
|
531
|
-
flowSettings.enable();
|
|
610
|
+
await flowSettings.enable();
|
|
532
611
|
expect(flowSettings.enabled).toBe(true);
|
|
533
612
|
|
|
534
613
|
// Verify all state is maintained
|
|
@@ -536,7 +615,7 @@ describe('FlowSettings', () => {
|
|
|
536
615
|
expect(flowSettings.scopes.testScope).toBe(testScope);
|
|
537
616
|
expect(flowSettings.getToolbarItems().find((item) => item.key === 'integration-test')).toBeDefined();
|
|
538
617
|
|
|
539
|
-
flowSettings.disable();
|
|
618
|
+
await flowSettings.disable();
|
|
540
619
|
expect(flowSettings.enabled).toBe(false);
|
|
541
620
|
|
|
542
621
|
// State should still be maintained after disable
|
|
@@ -26,6 +26,7 @@ function setupEngineWithCollections() {
|
|
|
26
26
|
fields: [
|
|
27
27
|
{ name: 'id', type: 'integer', interface: 'number' },
|
|
28
28
|
{ name: 'name', type: 'string', interface: 'text' },
|
|
29
|
+
{ name: 'rawUserPayload', type: 'json', filterable: true },
|
|
29
30
|
],
|
|
30
31
|
});
|
|
31
32
|
ds.addCollection({
|
|
@@ -41,6 +42,8 @@ function setupEngineWithCollections() {
|
|
|
41
42
|
filterTargetKey: 'id',
|
|
42
43
|
fields: [
|
|
43
44
|
{ name: 'title', type: 'string', interface: 'text' },
|
|
45
|
+
{ name: 'internalName', type: 'string', interface: 'text' },
|
|
46
|
+
{ name: 'rawPostPayload', type: 'json', filterable: true },
|
|
44
47
|
{ name: 'author', type: 'belongsTo', target: 'users', interface: 'm2o' },
|
|
45
48
|
{ name: 'tags', type: 'belongsToMany', target: 'tags', interface: 'm2m' },
|
|
46
49
|
],
|
|
@@ -91,6 +94,27 @@ describe('objectVariable utilities', () => {
|
|
|
91
94
|
});
|
|
92
95
|
});
|
|
93
96
|
|
|
97
|
+
it('createAssociationAwareObjectMetaFactory should hide fields without interface from object variable meta', async () => {
|
|
98
|
+
const { collection } = setupEngineWithCollections();
|
|
99
|
+
const obj = { title: 'hello', internalName: 'internal', rawPostPayload: { secret: true }, author: 1 };
|
|
100
|
+
const metaFactory = createAssociationAwareObjectMetaFactory(
|
|
101
|
+
() => collection,
|
|
102
|
+
'Current object',
|
|
103
|
+
() => obj,
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const meta = await metaFactory();
|
|
107
|
+
const props = await (meta?.properties as any)?.();
|
|
108
|
+
const authorFields = await props?.author?.properties?.();
|
|
109
|
+
|
|
110
|
+
expect(props).toHaveProperty('title');
|
|
111
|
+
expect(props).toHaveProperty('internalName');
|
|
112
|
+
expect(props).toHaveProperty('author');
|
|
113
|
+
expect(props).not.toHaveProperty('rawPostPayload');
|
|
114
|
+
expect(authorFields).toHaveProperty('name');
|
|
115
|
+
expect(authorFields).not.toHaveProperty('rawUserPayload');
|
|
116
|
+
});
|
|
117
|
+
|
|
94
118
|
it('integrates with FlowContext.resolveJsonTemplate to call variables:resolve with flattened contextParams', async () => {
|
|
95
119
|
const { engine, collection } = setupEngineWithCollections();
|
|
96
120
|
const obj = { author: 1 };
|
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { describe, expect, it } from 'vitest';
|
|
12
|
-
import { renderHook } from '@testing-library/react';
|
|
12
|
+
import { render, renderHook } from '@testing-library/react';
|
|
13
|
+
import { App, ConfigProvider, theme } from 'antd';
|
|
13
14
|
import { FlowEngine } from '../flowEngine';
|
|
14
|
-
import { FlowEngineProvider, useFlowEngine } from '../provider';
|
|
15
|
+
import { FlowEngineGlobalsContextProvider, FlowEngineProvider, useFlowEngine } from '../provider';
|
|
15
16
|
|
|
16
17
|
describe('FlowEngineProvider/useFlowEngine', () => {
|
|
17
18
|
it('returns engine within provider', () => {
|
|
@@ -20,4 +21,25 @@ describe('FlowEngineProvider/useFlowEngine', () => {
|
|
|
20
21
|
const { result } = renderHook(() => useFlowEngine(), { wrapper });
|
|
21
22
|
expect(result.current).toBe(engine);
|
|
22
23
|
});
|
|
24
|
+
|
|
25
|
+
it('registers isDarkTheme within globals provider before first child render', () => {
|
|
26
|
+
const engine = new FlowEngine();
|
|
27
|
+
const reads: boolean[] = [];
|
|
28
|
+
const Reader = () => {
|
|
29
|
+
reads.push(engine.context.isDarkTheme);
|
|
30
|
+
return null;
|
|
31
|
+
};
|
|
32
|
+
const wrapper = ({ children }: any) => (
|
|
33
|
+
<ConfigProvider theme={{ algorithm: theme.darkAlgorithm }}>
|
|
34
|
+
<App>
|
|
35
|
+
<FlowEngineProvider engine={engine}>
|
|
36
|
+
<FlowEngineGlobalsContextProvider>{children}</FlowEngineGlobalsContextProvider>
|
|
37
|
+
</FlowEngineProvider>
|
|
38
|
+
</App>
|
|
39
|
+
</ConfigProvider>
|
|
40
|
+
);
|
|
41
|
+
render(<Reader />, { wrapper });
|
|
42
|
+
expect(reads[0]).toBe(true);
|
|
43
|
+
expect(engine.context.isDarkTheme).toBe(true);
|
|
44
|
+
});
|
|
23
45
|
});
|
|
@@ -14,7 +14,7 @@ import { FlowEngine } from '../flowEngine';
|
|
|
14
14
|
import { FlowModel, ModelRenderMode } from '../models/flowModel';
|
|
15
15
|
|
|
16
16
|
describe('FlowModel.renderHiddenInConfig', () => {
|
|
17
|
-
it('renders via renderHiddenInConfig when hidden and config enabled (React element mode, mounted)', () => {
|
|
17
|
+
it('renders via renderHiddenInConfig when hidden and config enabled (React element mode, mounted)', async () => {
|
|
18
18
|
class ElemModel extends FlowModel {
|
|
19
19
|
render() {
|
|
20
20
|
return <div data-testid="content">Content</div>;
|
|
@@ -28,14 +28,14 @@ describe('FlowModel.renderHiddenInConfig', () => {
|
|
|
28
28
|
const model = new ElemModel({ uid: 'elem-1', flowEngine: engine });
|
|
29
29
|
|
|
30
30
|
// runtime hidden => mounted result should be empty (no content/hidden)
|
|
31
|
-
engine.flowSettings.disable();
|
|
31
|
+
await engine.flowSettings.disable();
|
|
32
32
|
model.setHidden(true);
|
|
33
33
|
const { container, unmount, rerender } = render(model.render() as React.ReactElement);
|
|
34
34
|
expect(screen.queryByTestId('content')).toBeNull();
|
|
35
35
|
expect(screen.queryByTestId('hidden')).toBeNull();
|
|
36
36
|
|
|
37
37
|
// config enabled + hidden => should show renderHiddenInConfig result
|
|
38
|
-
engine.flowSettings.enable();
|
|
38
|
+
await engine.flowSettings.enable();
|
|
39
39
|
rerender(model.render() as React.ReactElement);
|
|
40
40
|
expect(screen.getByTestId('hidden').textContent).toBe('HiddenViaAPI');
|
|
41
41
|
|
|
@@ -46,7 +46,7 @@ describe('FlowModel.renderHiddenInConfig', () => {
|
|
|
46
46
|
unmount();
|
|
47
47
|
cleanup();
|
|
48
48
|
});
|
|
49
|
-
it('returns a render function when hidden and config enabled (RenderFunction mode)', () => {
|
|
49
|
+
it('returns a render function when hidden and config enabled (RenderFunction mode)', async () => {
|
|
50
50
|
class FuncModel extends FlowModel {
|
|
51
51
|
static override renderMode = ModelRenderMode.RenderFunction;
|
|
52
52
|
render() {
|
|
@@ -63,13 +63,13 @@ describe('FlowModel.renderHiddenInConfig', () => {
|
|
|
63
63
|
const model = engine.createModel({ use: 'FuncModel' }) as FuncModel;
|
|
64
64
|
|
|
65
65
|
// runtime hidden => null
|
|
66
|
-
engine.flowSettings.disable();
|
|
66
|
+
await engine.flowSettings.disable();
|
|
67
67
|
model.setHidden(true);
|
|
68
68
|
const runtimeHidden = model.render();
|
|
69
69
|
expect(runtimeHidden).toBeNull();
|
|
70
70
|
|
|
71
71
|
// config enabled + hidden => renderHiddenInConfig (function)
|
|
72
|
-
engine.flowSettings.enable();
|
|
72
|
+
await engine.flowSettings.enable();
|
|
73
73
|
const cfgHidden = model.render();
|
|
74
74
|
expect(typeof cfgHidden).toBe('function');
|
|
75
75
|
const cellNode = (cfgHidden as any)();
|
|
@@ -38,6 +38,7 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
38
38
|
'JSBlockModel',
|
|
39
39
|
'JSFieldModel',
|
|
40
40
|
'JSItemModel',
|
|
41
|
+
'JSItemActionModel',
|
|
41
42
|
'JSColumnModel',
|
|
42
43
|
'FormJSFieldItemModel',
|
|
43
44
|
'JSRecordActionModel',
|
|
@@ -58,8 +59,10 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
58
59
|
it('should expose scene metadata for contexts', () => {
|
|
59
60
|
expect(getRunJSScenesForModel('JSBlockModel', 'v1')).toEqual(['block']);
|
|
60
61
|
expect(getRunJSScenesForModel('JSFieldModel', 'v1')).toEqual(['detail']);
|
|
62
|
+
expect(getRunJSScenesForModel('JSItemActionModel', 'v1')).toEqual(['table']);
|
|
61
63
|
expect(getRunJSScenesForModel('JSBlockModel', 'v2')).toEqual(['block']);
|
|
62
64
|
expect(getRunJSScenesForModel('JSFieldModel', 'v2')).toEqual(['detail']);
|
|
65
|
+
expect(getRunJSScenesForModel('JSItemActionModel', 'v2')).toEqual(['table']);
|
|
63
66
|
expect(getRunJSScenesForModel('UnknownModel', 'v1')).toEqual([]);
|
|
64
67
|
expect(getRunJSScenesForModel('UnknownModel', 'v2')).toEqual([]);
|
|
65
68
|
});
|
|
@@ -85,6 +88,19 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
85
88
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
86
89
|
expect(doc).toBeTruthy();
|
|
87
90
|
expect(doc?.label).toMatch(/RunJS base/);
|
|
91
|
+
expect(doc?.properties?.element).toBeUndefined();
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should mark element-dependent base completions with element requirement', () => {
|
|
95
|
+
const ctx: any = { model: { constructor: { name: 'UnknownModel' } } };
|
|
96
|
+
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
97
|
+
|
|
98
|
+
expect((doc?.methods?.render as any)?.completion?.requires).toContain('element');
|
|
99
|
+
expect((doc?.properties?.viewer as any)?.properties?.popover?.completion?.requires).toContain('element');
|
|
100
|
+
expect((doc?.properties?.viewer as any)?.properties?.embed?.completion?.requires).toContain('element');
|
|
101
|
+
expect(
|
|
102
|
+
(doc?.properties?.libs as any)?.properties?.ReactDOM?.properties?.createRoot?.completion?.requires,
|
|
103
|
+
).toContain('element');
|
|
88
104
|
});
|
|
89
105
|
|
|
90
106
|
it('should support locale-specific doc', () => {
|
|
@@ -96,6 +112,11 @@ describe('flowRunJSContext registry and doc', () => {
|
|
|
96
112
|
const messageText =
|
|
97
113
|
typeof message === 'string' ? message : (message as any)?.description ?? (message as any)?.detail ?? '';
|
|
98
114
|
expect(String(messageText)).toMatch(/Ant Design 全局消息/);
|
|
115
|
+
expect((doc?.methods?.render as any)?.completion?.requires).toContain('element');
|
|
116
|
+
expect((doc?.properties?.viewer as any)?.properties?.popover?.completion?.requires).toContain('element');
|
|
117
|
+
expect(
|
|
118
|
+
(doc?.properties?.libs as any)?.properties?.ReactDOM?.properties?.createRoot?.completion?.requires,
|
|
119
|
+
).toContain('element');
|
|
99
120
|
});
|
|
100
121
|
|
|
101
122
|
it('should fallback to English when locale is not found', () => {
|
|
@@ -27,7 +27,10 @@ describe('Specific RunJSContext implementations', () => {
|
|
|
27
27
|
const ctx: any = { model: { constructor: { name: 'JSColumnModel' } } };
|
|
28
28
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
29
29
|
expect(doc?.properties?.element).toBeTruthy();
|
|
30
|
-
|
|
30
|
+
const elementDoc: any = doc?.properties?.element;
|
|
31
|
+
expect(elementDoc?.detail).toContain('ElementProxy');
|
|
32
|
+
expect(elementDoc?.properties?.setAttribute).toBeTruthy();
|
|
33
|
+
expect(elementDoc?.properties?.querySelector).toBeTruthy();
|
|
31
34
|
});
|
|
32
35
|
|
|
33
36
|
it('should have record property in doc', () => {
|
|
@@ -68,7 +71,9 @@ describe('Specific RunJSContext implementations', () => {
|
|
|
68
71
|
(ctx as any).defineProperty('api', { value: { auth: { locale: 'zh-CN' } } });
|
|
69
72
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
70
73
|
expect(doc?.label).toMatch(/JS 列/);
|
|
71
|
-
|
|
74
|
+
const elementDoc: any = doc?.properties?.element;
|
|
75
|
+
expect(elementDoc?.description).toContain('表格单元格');
|
|
76
|
+
expect(elementDoc?.properties?.addEventListener).toBeTruthy();
|
|
72
77
|
});
|
|
73
78
|
|
|
74
79
|
it('should create instance successfully', () => {
|
|
@@ -162,6 +167,7 @@ describe('Specific RunJSContext implementations', () => {
|
|
|
162
167
|
const ctx: any = { model: { constructor: { name: 'JSRecordActionModel' } } };
|
|
163
168
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
164
169
|
expect(doc?.properties?.record).toBeTruthy();
|
|
170
|
+
expect(doc?.properties?.element).toBeUndefined();
|
|
165
171
|
});
|
|
166
172
|
|
|
167
173
|
it('should have filterByTk property', () => {
|
|
@@ -184,6 +190,7 @@ describe('Specific RunJSContext implementations', () => {
|
|
|
184
190
|
const ctx: any = { model: { constructor: { name: 'JSCollectionActionModel' } } };
|
|
185
191
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
186
192
|
expect(doc?.properties?.resource).toBeTruthy();
|
|
193
|
+
expect(doc?.properties?.element).toBeUndefined();
|
|
187
194
|
});
|
|
188
195
|
|
|
189
196
|
it('should support zh-CN locale', () => {
|
|
@@ -186,6 +186,7 @@ describe('RunJS Context Runtime Behavior', () => {
|
|
|
186
186
|
'JSBlockModel',
|
|
187
187
|
'JSFieldModel',
|
|
188
188
|
'JSItemModel',
|
|
189
|
+
'JSItemActionModel',
|
|
189
190
|
'JSColumnModel',
|
|
190
191
|
'FormJSFieldItemModel',
|
|
191
192
|
'JSRecordActionModel',
|
|
@@ -237,6 +238,7 @@ describe('RunJS Context Runtime Behavior', () => {
|
|
|
237
238
|
'JSBlockModel',
|
|
238
239
|
'JSFieldModel',
|
|
239
240
|
'JSItemModel',
|
|
241
|
+
'JSItemActionModel',
|
|
240
242
|
'JSColumnModel',
|
|
241
243
|
'FormJSFieldItemModel',
|
|
242
244
|
'JSRecordActionModel',
|
|
@@ -12,6 +12,10 @@ import { getRunJSDocFor } from '..';
|
|
|
12
12
|
import { setupRunJSContexts } from '../runjs-context/setup';
|
|
13
13
|
import { FlowContext } from '../flowContext';
|
|
14
14
|
|
|
15
|
+
function getRunJSDocText(doc: unknown) {
|
|
16
|
+
return typeof doc === 'string' ? doc : (doc as any)?.description ?? (doc as any)?.detail ?? '';
|
|
17
|
+
}
|
|
18
|
+
|
|
15
19
|
describe('RunJS locales patch (engine doc)', () => {
|
|
16
20
|
beforeAll(async () => {
|
|
17
21
|
await setupRunJSContexts();
|
|
@@ -30,10 +34,7 @@ describe('RunJS locales patch (engine doc)', () => {
|
|
|
30
34
|
(ctx as any).defineProperty('model', { value: { constructor: { name: 'JSBlockModel' } } });
|
|
31
35
|
(ctx as any).defineProperty('api', { value: { auth: { locale: 'zh-CN' } } });
|
|
32
36
|
const doc = getRunJSDocFor(ctx as any, { version: 'v1' });
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
typeof message === 'string' ? message : (message as any)?.description ?? (message as any)?.detail ?? '';
|
|
36
|
-
expect(String(messageText)).toMatch(/Ant Design 全局消息 API/);
|
|
37
|
-
expect(String(doc?.methods?.t || '')).toMatch(/国际化函数/);
|
|
37
|
+
expect(String(getRunJSDocText(doc?.properties?.message))).toMatch(/Ant Design 全局消息 API/);
|
|
38
|
+
expect(String(getRunJSDocText(doc?.methods?.t))).toMatch(/国际化函数/);
|
|
38
39
|
});
|
|
39
40
|
});
|
|
@@ -112,6 +112,27 @@ describe('RunJS Snippets', () => {
|
|
|
112
112
|
expect(multiScene?.scenes).toEqual(expect.arrayContaining(['detail', 'table']));
|
|
113
113
|
expect(multiScene?.groups).toEqual(expect.arrayContaining(['scene/detail', 'scene/table']));
|
|
114
114
|
});
|
|
115
|
+
|
|
116
|
+
it('should expose new style snippets for matching contexts', async () => {
|
|
117
|
+
const tableSnippets = await listSnippetsForContext('JSColumnRunJSContext', 'v1', 'zh-CN');
|
|
118
|
+
const fieldSnippets = await listSnippetsForContext('FormJSFieldItemRunJSContext', 'v1', 'zh-CN');
|
|
119
|
+
const detailEventSnippets = await listSnippetsForContext('DetailsItemModel', 'v1', 'zh-CN');
|
|
120
|
+
const tableEventSnippets = await listSnippetsForContext('TableColumnModel', 'v1', 'zh-CN');
|
|
121
|
+
|
|
122
|
+
const tableStyle = tableSnippets.find((s) => s.ref === 'scene/table/set-cell-style');
|
|
123
|
+
expect(tableStyle?.name).toBe('表格字段样式设置');
|
|
124
|
+
expect(tableStyle?.body).toContain('ctx.model.props.onCell');
|
|
125
|
+
expect(tableStyle?.scenes).toEqual(['tableFieldEvent']);
|
|
126
|
+
|
|
127
|
+
const fieldStyle = fieldSnippets.find((s) => s.ref === 'scene/detail/set-field-style');
|
|
128
|
+
expect(fieldStyle?.name).toBe('设置表单项/详情项样式');
|
|
129
|
+
expect(fieldStyle?.body).toContain('ctx.model.props.style');
|
|
130
|
+
expect(fieldStyle?.scenes).toEqual(expect.arrayContaining(['detailFieldEvent', 'formFieldEvent']));
|
|
131
|
+
expect(fieldStyle?.groups).toEqual(expect.arrayContaining(['scene/detail', 'scene/form']));
|
|
132
|
+
|
|
133
|
+
expect(detailEventSnippets.some((s) => s.ref === 'scene/detail/set-field-style')).toBe(true);
|
|
134
|
+
expect(tableEventSnippets.some((s) => s.ref === 'scene/table/set-cell-style')).toBe(true);
|
|
135
|
+
});
|
|
115
136
|
});
|
|
116
137
|
|
|
117
138
|
describe('New snippets', () => {
|