@nocobase/flow-engine 2.1.0-alpha.4 → 2.1.0-alpha.40
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -661
- package/README.md +79 -10
- package/lib/JSRunner.d.ts +10 -1
- package/lib/JSRunner.js +50 -5
- package/lib/ViewScopedFlowEngine.js +5 -1
- package/lib/components/FieldModelRenderer.js +2 -2
- package/lib/components/FlowModelRenderer.d.ts +3 -1
- package/lib/components/FlowModelRenderer.js +12 -6
- package/lib/components/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 +613 -21
- 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 +68 -10
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.d.ts +23 -43
- package/lib/components/settings/wrappers/contextual/FlowsFloatContextMenu.js +352 -295
- package/lib/components/settings/wrappers/contextual/StepSettingsDialog.js +16 -2
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.d.ts +36 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarPortal.js +274 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.d.ts +30 -0
- package/lib/components/settings/wrappers/contextual/useFloatToolbarVisibility.js +315 -0
- package/lib/components/subModel/AddSubModelButton.js +27 -1
- package/lib/components/subModel/LazyDropdown.js +96 -39
- package/lib/components/subModel/index.d.ts +1 -0
- package/lib/components/subModel/index.js +19 -0
- package/lib/components/subModel/utils.d.ts +1 -1
- package/lib/components/subModel/utils.js +9 -3
- 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 +75 -0
- package/lib/data-source/index.js +247 -5
- package/lib/executor/FlowExecutor.js +32 -9
- 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 +3 -0
- package/lib/flowContext.js +43 -1
- package/lib/flowEngine.d.ts +151 -1
- package/lib/flowEngine.js +389 -15
- package/lib/flowI18n.js +2 -1
- package/lib/flowSettings.d.ts +14 -6
- package/lib/flowSettings.js +34 -6
- package/lib/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 +78 -18
- package/lib/provider.js +38 -23
- package/lib/reactive/observer.js +46 -16
- package/lib/runjs-context/registry.d.ts +1 -1
- package/lib/runjs-context/setup.js +20 -12
- package/lib/runjs-context/snippets/index.js +13 -2
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/detail/set-field-style.snippet.js +50 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.d.ts +11 -0
- package/lib/runjs-context/snippets/scene/table/set-cell-style.snippet.js +54 -0
- package/lib/scheduler/ModelOperationScheduler.d.ts +5 -1
- package/lib/scheduler/ModelOperationScheduler.js +3 -2
- package/lib/types.d.ts +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/parsePathnameToViewParams.js +1 -1
- 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.js +6 -2
- package/lib/views/runViewBeforeClose.d.ts +10 -0
- package/lib/views/runViewBeforeClose.js +45 -0
- package/lib/views/useDialog.d.ts +2 -1
- package/lib/views/useDialog.js +20 -3
- package/lib/views/useDrawer.d.ts +2 -1
- package/lib/views/useDrawer.js +20 -3
- package/lib/views/usePage.d.ts +5 -11
- package/lib/views/usePage.js +302 -144
- package/package.json +6 -5
- package/src/JSRunner.ts +68 -4
- package/src/ViewScopedFlowEngine.ts +4 -0
- package/src/__tests__/JSRunner.test.ts +27 -1
- package/src/__tests__/flow-engine.test.ts +166 -0
- package/src/__tests__/flowContext.test.ts +82 -1
- package/src/__tests__/flowEngine.modelLoaders.test.ts +245 -0
- package/src/__tests__/flowSettings.test.ts +94 -15
- package/src/__tests__/objectVariable.test.ts +24 -0
- package/src/__tests__/provider.test.tsx +24 -2
- package/src/__tests__/renderHiddenInConfig.test.tsx +6 -6
- package/src/__tests__/runjsContext.test.ts +16 -0
- package/src/__tests__/runjsContextRuntime.test.ts +2 -0
- package/src/__tests__/runjsPreprocessDefault.test.ts +23 -0
- package/src/__tests__/runjsSnippets.test.ts +21 -0
- package/src/__tests__/viewScopedFlowEngine.test.ts +3 -3
- package/src/components/FieldModelRenderer.tsx +2 -1
- package/src/components/FlowModelRenderer.tsx +18 -6
- package/src/components/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 +558 -3
- package/src/components/dnd/__tests__/DndProvider.test.tsx +98 -0
- package/src/components/dnd/gridDragPlanner.ts +758 -19
- 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 +88 -10
- package/src/components/settings/wrappers/contextual/FlowsFloatContextMenu.tsx +487 -440
- package/src/components/settings/wrappers/contextual/StepSettingsDialog.tsx +18 -2
- package/src/components/settings/wrappers/contextual/__tests__/DefaultSettingsIcon.test.tsx +189 -3
- package/src/components/settings/wrappers/contextual/__tests__/FlowsFloatContextMenu.test.tsx +778 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarPortal.ts +360 -0
- package/src/components/settings/wrappers/contextual/useFloatToolbarVisibility.ts +361 -0
- package/src/components/subModel/AddSubModelButton.tsx +32 -2
- package/src/components/subModel/LazyDropdown.tsx +107 -43
- package/src/components/subModel/__tests__/AddSubModelButton.test.tsx +319 -36
- package/src/components/subModel/__tests__/utils.test.ts +24 -0
- package/src/components/subModel/index.ts +1 -0
- package/src/components/subModel/utils.ts +7 -1
- 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 +68 -1
- package/src/data-source/index.ts +304 -6
- package/src/executor/FlowExecutor.ts +35 -10
- 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 +47 -3
- package/src/flowEngine.ts +445 -11
- 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__/dispatchEvent.when.test.ts +214 -0
- package/src/models/__tests__/flowModel.test.ts +47 -3
- package/src/models/flowModel.tsx +119 -33
- package/src/provider.tsx +41 -25
- package/src/reactive/__tests__/observer.test.tsx +82 -0
- package/src/reactive/observer.tsx +87 -25
- package/src/runjs-context/registry.ts +1 -1
- package/src/runjs-context/setup.ts +22 -12
- package/src/runjs-context/snippets/index.ts +12 -1
- package/src/runjs-context/snippets/scene/detail/set-field-style.snippet.ts +30 -0
- package/src/runjs-context/snippets/scene/table/set-cell-style.snippet.ts +34 -0
- package/src/scheduler/ModelOperationScheduler.ts +14 -3
- package/src/types.ts +62 -0
- package/src/utils/__tests__/createCollectionContextMeta.test.ts +48 -0
- package/src/utils/__tests__/parsePathnameToViewParams.test.ts +7 -0
- package/src/utils/__tests__/runjsValue.test.ts +11 -0
- package/src/utils/__tests__/utils.test.ts +62 -0
- package/src/utils/createCollectionContextMeta.ts +6 -2
- package/src/utils/index.ts +5 -1
- package/src/utils/parsePathnameToViewParams.ts +2 -2
- 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 +6 -2
- package/src/views/__tests__/FlowView.usePage.test.tsx +243 -3
- package/src/views/__tests__/runViewBeforeClose.test.ts +30 -0
- package/src/views/__tests__/useDialog.closeDestroy.test.tsx +13 -12
- package/src/views/runViewBeforeClose.ts +19 -0
- package/src/views/useDialog.tsx +25 -3
- package/src/views/useDrawer.tsx +25 -3
- package/src/views/usePage.tsx +365 -179
|
@@ -21,11 +21,13 @@ import {
|
|
|
21
21
|
import { SubModelItem, mergeSubModelItems, transformItems } from '../AddSubModelButton';
|
|
22
22
|
import { App, ConfigProvider } from 'antd';
|
|
23
23
|
|
|
24
|
+
const getSubmenuTitle = (label: string) => screen.getByText(label).closest('.ant-dropdown-menu-submenu-title');
|
|
25
|
+
|
|
24
26
|
describe('AddSubModelButton - preset settings open on add', () => {
|
|
25
27
|
test('calls openFlowSettings with preset=true for subModel with preset steps', async () => {
|
|
26
28
|
// Arrange: set up engine and models
|
|
27
29
|
const engine = new FlowEngine();
|
|
28
|
-
engine.flowSettings.forceEnable();
|
|
30
|
+
await engine.flowSettings.forceEnable();
|
|
29
31
|
|
|
30
32
|
class ParentModel extends FlowModel {}
|
|
31
33
|
|
|
@@ -99,12 +101,70 @@ describe('AddSubModelButton - preset settings open on add', () => {
|
|
|
99
101
|
});
|
|
100
102
|
});
|
|
101
103
|
|
|
104
|
+
describe('AddSubModelButton - model loader integration', () => {
|
|
105
|
+
test('resolves model loaders before creating sub models', async () => {
|
|
106
|
+
const engine = new FlowEngine();
|
|
107
|
+
await engine.flowSettings.forceEnable();
|
|
108
|
+
|
|
109
|
+
class ParentModel extends FlowModel {}
|
|
110
|
+
class ChildModel extends FlowModel {}
|
|
111
|
+
|
|
112
|
+
const childLoader = vi.fn(async () => ({ ChildModel }));
|
|
113
|
+
|
|
114
|
+
engine.registerModels({ ParentModel });
|
|
115
|
+
engine.registerModelLoaders({
|
|
116
|
+
ChildModel: {
|
|
117
|
+
loader: childLoader,
|
|
118
|
+
},
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
const parent = engine.createModel<ParentModel>({ use: 'ParentModel', uid: 'parent-loader' });
|
|
122
|
+
|
|
123
|
+
render(
|
|
124
|
+
<FlowEngineProvider engine={engine}>
|
|
125
|
+
<ConfigProvider>
|
|
126
|
+
<App>
|
|
127
|
+
<AddSubModelButton
|
|
128
|
+
model={parent}
|
|
129
|
+
subModelKey="items"
|
|
130
|
+
items={[
|
|
131
|
+
{
|
|
132
|
+
key: 'child',
|
|
133
|
+
label: 'Add Child',
|
|
134
|
+
createModelOptions: { use: 'ChildModel' },
|
|
135
|
+
},
|
|
136
|
+
]}
|
|
137
|
+
>
|
|
138
|
+
Add SubModel
|
|
139
|
+
</AddSubModelButton>
|
|
140
|
+
</App>
|
|
141
|
+
</ConfigProvider>
|
|
142
|
+
</FlowEngineProvider>,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
await act(async () => {
|
|
146
|
+
await userEvent.click(screen.getByText('Add SubModel'));
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await waitFor(() => expect(screen.getByText('Add Child')).toBeInTheDocument());
|
|
150
|
+
|
|
151
|
+
await act(async () => {
|
|
152
|
+
await userEvent.click(screen.getByText('Add Child'));
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
await waitFor(() => expect(childLoader).toHaveBeenCalledTimes(1));
|
|
156
|
+
const items = parent.subModels.items as FlowModel[];
|
|
157
|
+
expect(Array.isArray(items)).toBe(true);
|
|
158
|
+
expect(items[0]).toBeInstanceOf(ChildModel);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
102
162
|
describe('AddSubModelButton - async group children (nested)', () => {
|
|
103
163
|
const sleep = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
104
164
|
|
|
105
165
|
it('renders group and nested async group leaf items', async () => {
|
|
106
166
|
const engine = new FlowEngine();
|
|
107
|
-
engine.flowSettings.forceEnable();
|
|
167
|
+
await engine.flowSettings.forceEnable();
|
|
108
168
|
class Parent extends FlowModel {}
|
|
109
169
|
engine.registerModels({ Parent });
|
|
110
170
|
const parent = engine.createModel<FlowModel>({ use: 'Parent', uid: 'p1' });
|
|
@@ -157,12 +217,74 @@ describe('AddSubModelButton - async group children (nested)', () => {
|
|
|
157
217
|
await waitFor(() => expect(screen.getByText('Nested-Leaf-1')).toBeInTheDocument());
|
|
158
218
|
await waitFor(() => expect(screen.getByText('Nested-Leaf-2')).toBeInTheDocument());
|
|
159
219
|
});
|
|
220
|
+
|
|
221
|
+
it('keeps root dropdown open while only the current nested group stays expanded', async () => {
|
|
222
|
+
const engine = new FlowEngine();
|
|
223
|
+
await engine.flowSettings.forceEnable();
|
|
224
|
+
class Parent extends FlowModel {}
|
|
225
|
+
engine.registerModels({ Parent });
|
|
226
|
+
const parent = engine.createModel<FlowModel>({ use: 'Parent', uid: 'p-multi-open' });
|
|
227
|
+
|
|
228
|
+
const items = [
|
|
229
|
+
{
|
|
230
|
+
key: 'group-a',
|
|
231
|
+
label: 'Group A',
|
|
232
|
+
children: [
|
|
233
|
+
{ key: 'a-1', label: 'A-1', createModelOptions: { use: 'Parent' } },
|
|
234
|
+
{ key: 'a-2', label: 'A-2', createModelOptions: { use: 'Parent' } },
|
|
235
|
+
],
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
key: 'group-b',
|
|
239
|
+
label: 'Group B',
|
|
240
|
+
children: [
|
|
241
|
+
{ key: 'b-1', label: 'B-1', createModelOptions: { use: 'Parent' } },
|
|
242
|
+
{ key: 'b-2', label: 'B-2', createModelOptions: { use: 'Parent' } },
|
|
243
|
+
],
|
|
244
|
+
},
|
|
245
|
+
];
|
|
246
|
+
|
|
247
|
+
render(
|
|
248
|
+
<FlowEngineProvider engine={engine}>
|
|
249
|
+
<ConfigProvider>
|
|
250
|
+
<App>
|
|
251
|
+
<AddSubModelButton model={parent} subModelKey="items" items={items as any}>
|
|
252
|
+
Open Menu
|
|
253
|
+
</AddSubModelButton>
|
|
254
|
+
</App>
|
|
255
|
+
</ConfigProvider>
|
|
256
|
+
</FlowEngineProvider>,
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
await act(async () => {
|
|
260
|
+
await userEvent.click(screen.getByText('Open Menu'));
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
await waitFor(() => expect(screen.getByText('Group A')).toBeInTheDocument());
|
|
264
|
+
await waitFor(() => expect(screen.getByText('Group B')).toBeInTheDocument());
|
|
265
|
+
|
|
266
|
+
await act(async () => {
|
|
267
|
+
await userEvent.hover(screen.getByText('Group A'));
|
|
268
|
+
});
|
|
269
|
+
await waitFor(() => expect(screen.getByText('A-1')).toBeInTheDocument());
|
|
270
|
+
await waitFor(() => expect(getSubmenuTitle('Group A')).toHaveAttribute('aria-expanded', 'true'));
|
|
271
|
+
expect(getSubmenuTitle('Group B')).toHaveAttribute('aria-expanded', 'false');
|
|
272
|
+
|
|
273
|
+
await act(async () => {
|
|
274
|
+
await userEvent.hover(screen.getByText('Group B'));
|
|
275
|
+
});
|
|
276
|
+
await waitFor(() => expect(screen.getByText('B-1')).toBeInTheDocument());
|
|
277
|
+
await waitFor(() => expect(getSubmenuTitle('Group B')).toHaveAttribute('aria-expanded', 'true'));
|
|
278
|
+
expect(getSubmenuTitle('Group A')).toHaveAttribute('aria-expanded', 'false');
|
|
279
|
+
expect(screen.getByText('Group A')).toBeInTheDocument();
|
|
280
|
+
expect(screen.getByText('Group B')).toBeInTheDocument();
|
|
281
|
+
});
|
|
160
282
|
});
|
|
161
283
|
|
|
162
284
|
describe('transformItems - searchable flags', () => {
|
|
163
285
|
it('preserves searchable + placeholder on non-group submenu items', async () => {
|
|
164
286
|
const engine = new FlowEngine();
|
|
165
|
-
engine.flowSettings.forceEnable();
|
|
287
|
+
await engine.flowSettings.forceEnable();
|
|
166
288
|
class Parent extends FlowModel {}
|
|
167
289
|
engine.registerModels({ Parent });
|
|
168
290
|
const parent = engine.createModel<FlowModel>({ use: 'Parent' });
|
|
@@ -188,12 +310,56 @@ describe('transformItems - searchable flags', () => {
|
|
|
188
310
|
expect(submenu.searchPlaceholder).toBe('Search blocks');
|
|
189
311
|
expect(Array.isArray(submenu.children)).toBe(true);
|
|
190
312
|
});
|
|
313
|
+
|
|
314
|
+
it('filters searchable field menus by display label instead of item key', async () => {
|
|
315
|
+
const engine = new FlowEngine();
|
|
316
|
+
await engine.flowSettings.forceEnable();
|
|
317
|
+
const parent = engine.createModel<FlowModel>({ use: FlowModel });
|
|
318
|
+
const user = userEvent.setup();
|
|
319
|
+
|
|
320
|
+
const items = [
|
|
321
|
+
{
|
|
322
|
+
key: 'fields',
|
|
323
|
+
label: '',
|
|
324
|
+
type: 'group' as const,
|
|
325
|
+
searchable: true,
|
|
326
|
+
searchPlaceholder: 'Search fields',
|
|
327
|
+
children: [
|
|
328
|
+
{ key: 'field_name', label: 'Field display name' },
|
|
329
|
+
{ key: 'other_field', label: 'Other field' },
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
render(
|
|
335
|
+
<FlowEngineProvider engine={engine}>
|
|
336
|
+
<ConfigProvider>
|
|
337
|
+
<App>
|
|
338
|
+
<AddSubModelButton model={parent} items={items as any} subModelKey="items">
|
|
339
|
+
Open
|
|
340
|
+
</AddSubModelButton>
|
|
341
|
+
</App>
|
|
342
|
+
</ConfigProvider>
|
|
343
|
+
</FlowEngineProvider>,
|
|
344
|
+
);
|
|
345
|
+
|
|
346
|
+
await user.click(screen.getByText('Open'));
|
|
347
|
+
const searchInput = await screen.findByPlaceholderText('Search fields');
|
|
348
|
+
expect(screen.getByText('Field display name')).toBeInTheDocument();
|
|
349
|
+
|
|
350
|
+
await user.type(searchInput, 'field_name');
|
|
351
|
+
await waitFor(() => expect(screen.queryByText('Field display name')).not.toBeInTheDocument());
|
|
352
|
+
|
|
353
|
+
await user.clear(searchInput);
|
|
354
|
+
await user.type(searchInput, 'display');
|
|
355
|
+
await waitFor(() => expect(screen.getByText('Field display name')).toBeInTheDocument());
|
|
356
|
+
});
|
|
191
357
|
});
|
|
192
358
|
|
|
193
359
|
describe('transformItems - hide', () => {
|
|
194
360
|
it('filters items by hide flag/function recursively', async () => {
|
|
195
361
|
const engine = new FlowEngine();
|
|
196
|
-
engine.flowSettings.forceEnable();
|
|
362
|
+
await engine.flowSettings.forceEnable();
|
|
197
363
|
class Parent extends FlowModel {}
|
|
198
364
|
engine.registerModels({ Parent });
|
|
199
365
|
const parent = engine.createModel<FlowModel>({ use: 'Parent', uid: 'p-hide' });
|
|
@@ -239,7 +405,7 @@ describe('transformItems - hide', () => {
|
|
|
239
405
|
|
|
240
406
|
it('removes group when all children are hidden (even with async hide)', async () => {
|
|
241
407
|
const engine = new FlowEngine();
|
|
242
|
-
engine.flowSettings.forceEnable();
|
|
408
|
+
await engine.flowSettings.forceEnable();
|
|
243
409
|
class Parent extends FlowModel {}
|
|
244
410
|
engine.registerModels({ Parent });
|
|
245
411
|
const parent = engine.createModel<FlowModel>({ use: 'Parent', uid: 'p-empty-group' });
|
|
@@ -272,7 +438,7 @@ describe('transformItems - hide', () => {
|
|
|
272
438
|
|
|
273
439
|
it('supports async hide functions and disables cache', async () => {
|
|
274
440
|
const engine = new FlowEngine();
|
|
275
|
-
engine.flowSettings.forceEnable();
|
|
441
|
+
await engine.flowSettings.forceEnable();
|
|
276
442
|
class Parent extends FlowModel {}
|
|
277
443
|
engine.registerModels({ Parent });
|
|
278
444
|
const parent = engine.createModel<FlowModel>({ use: 'Parent', uid: 'p-async-hide' });
|
|
@@ -300,7 +466,7 @@ describe('transformItems - hide', () => {
|
|
|
300
466
|
|
|
301
467
|
it('shows items when hide function throws (conservative fallback)', async () => {
|
|
302
468
|
const engine = new FlowEngine();
|
|
303
|
-
engine.flowSettings.forceEnable();
|
|
469
|
+
await engine.flowSettings.forceEnable();
|
|
304
470
|
class Parent extends FlowModel {}
|
|
305
471
|
engine.registerModels({ Parent });
|
|
306
472
|
const parent = engine.createModel<FlowModel>({ use: 'Parent', uid: 'p-hide-throws' });
|
|
@@ -331,15 +497,15 @@ describe('transformItems - toggleable items', () => {
|
|
|
331
497
|
class ToggleParent extends FlowModel {}
|
|
332
498
|
class ToggleChild extends FlowModel {}
|
|
333
499
|
|
|
334
|
-
const setupEngine = () => {
|
|
500
|
+
const setupEngine = async () => {
|
|
335
501
|
const engine = new FlowEngine();
|
|
336
|
-
engine.flowSettings.forceEnable();
|
|
502
|
+
await engine.flowSettings.forceEnable();
|
|
337
503
|
engine.registerModels({ ToggleParent, ToggleChild });
|
|
338
504
|
return engine;
|
|
339
505
|
};
|
|
340
506
|
|
|
341
507
|
it('marks toggleable item as active when matching sub model exists', async () => {
|
|
342
|
-
const engine = setupEngine();
|
|
508
|
+
const engine = await setupEngine();
|
|
343
509
|
const parent = engine.createModel<ToggleParent>({ use: 'ToggleParent', uid: 'toggle-parent-on' });
|
|
344
510
|
const child = engine.createModel<ToggleChild>({ use: 'ToggleChild', uid: 'toggle-child-on' });
|
|
345
511
|
parent.addSubModel('items', child);
|
|
@@ -371,7 +537,7 @@ describe('transformItems - toggleable items', () => {
|
|
|
371
537
|
});
|
|
372
538
|
|
|
373
539
|
it('infers useModel from createModelOptions when toggleable is enabled', async () => {
|
|
374
|
-
const engine = setupEngine();
|
|
540
|
+
const engine = await setupEngine();
|
|
375
541
|
const parent = engine.createModel<ToggleParent>({ use: 'ToggleParent', uid: 'toggle-parent-infer' });
|
|
376
542
|
const child = engine.createModel<ToggleChild>({ use: 'ToggleChild', uid: 'toggle-child-infer' });
|
|
377
543
|
parent.addSubModel('items', child);
|
|
@@ -397,7 +563,7 @@ describe('transformItems - toggleable items', () => {
|
|
|
397
563
|
});
|
|
398
564
|
|
|
399
565
|
it('keeps toggleable item off when sub model missing', async () => {
|
|
400
|
-
const engine = setupEngine();
|
|
566
|
+
const engine = await setupEngine();
|
|
401
567
|
const parent = engine.createModel<ToggleParent>({ use: 'ToggleParent', uid: 'toggle-parent-off' });
|
|
402
568
|
|
|
403
569
|
const definition: SubModelItem[] = [
|
|
@@ -420,7 +586,7 @@ describe('transformItems - toggleable items', () => {
|
|
|
420
586
|
});
|
|
421
587
|
|
|
422
588
|
it('respects keepDropdownOpen override on toggleable items', async () => {
|
|
423
|
-
const engine = setupEngine();
|
|
589
|
+
const engine = await setupEngine();
|
|
424
590
|
const parent = engine.createModel<ToggleParent>({ use: 'ToggleParent', uid: 'toggle-parent-keep' });
|
|
425
591
|
|
|
426
592
|
const definition: SubModelItem[] = [
|
|
@@ -443,7 +609,7 @@ describe('transformItems - toggleable items', () => {
|
|
|
443
609
|
|
|
444
610
|
it('removes object sub model via default remove handler when toggleDetector provided', async () => {
|
|
445
611
|
const engine = new FlowEngine();
|
|
446
|
-
engine.flowSettings.forceEnable();
|
|
612
|
+
await engine.flowSettings.forceEnable();
|
|
447
613
|
|
|
448
614
|
class ObjectParent extends FlowModel {}
|
|
449
615
|
class ObjectChild extends FlowModel {}
|
|
@@ -481,6 +647,8 @@ describe('transformItems - toggleable items', () => {
|
|
|
481
647
|
</FlowEngineProvider>,
|
|
482
648
|
);
|
|
483
649
|
|
|
650
|
+
await waitFor(() => expect(screen.getByText('Toggle Menu')).toBeInTheDocument());
|
|
651
|
+
|
|
484
652
|
await act(async () => {
|
|
485
653
|
await userEvent.click(screen.getByText('Toggle Menu'));
|
|
486
654
|
});
|
|
@@ -519,16 +687,16 @@ describe('transformItems - caching behaviour', () => {
|
|
|
519
687
|
class CacheParent extends FlowModel {}
|
|
520
688
|
class CacheChild extends FlowModel {}
|
|
521
689
|
|
|
522
|
-
const setupEngine = () => {
|
|
690
|
+
const setupEngine = async () => {
|
|
523
691
|
const engine = new FlowEngine();
|
|
524
|
-
engine.flowSettings.forceEnable();
|
|
692
|
+
await engine.flowSettings.forceEnable();
|
|
525
693
|
engine.registerModels({ CacheParent, CacheChild });
|
|
526
694
|
const parent = engine.createModel<CacheParent>({ use: 'CacheParent', uid: 'cache-parent' });
|
|
527
695
|
return { engine, parent };
|
|
528
696
|
};
|
|
529
697
|
|
|
530
698
|
it('reuses cached result when no toggleable items exist', async () => {
|
|
531
|
-
const { parent } = setupEngine();
|
|
699
|
+
const { parent } = await setupEngine();
|
|
532
700
|
const definition: SubModelItem[] = [{ key: 'basic', label: 'Basic', createModelOptions: { use: 'CacheChild' } }];
|
|
533
701
|
|
|
534
702
|
const factory = transformItems(definition, parent, 'items', 'array');
|
|
@@ -541,7 +709,7 @@ describe('transformItems - caching behaviour', () => {
|
|
|
541
709
|
});
|
|
542
710
|
|
|
543
711
|
it('refreshes toggle state after new sub model is added', async () => {
|
|
544
|
-
const { parent, engine } = setupEngine();
|
|
712
|
+
const { parent, engine } = await setupEngine();
|
|
545
713
|
const createDefinition = (): SubModelItem[] => [
|
|
546
714
|
{
|
|
547
715
|
key: 'toggleable',
|
|
@@ -570,7 +738,7 @@ describe('transformItems - caching behaviour', () => {
|
|
|
570
738
|
describe('AddSubModelButton - refreshTargets linkage', () => {
|
|
571
739
|
it('clicking an item with refreshTargets triggers toggle recomputation on target branch', async () => {
|
|
572
740
|
const engine = new FlowEngine();
|
|
573
|
-
engine.flowSettings.forceEnable();
|
|
741
|
+
await engine.flowSettings.forceEnable();
|
|
574
742
|
|
|
575
743
|
class Parent extends FlowModel {}
|
|
576
744
|
class ToggleModel extends FlowModel {}
|
|
@@ -642,7 +810,7 @@ describe('AddSubModelButton - base class menu groups', () => {
|
|
|
642
810
|
|
|
643
811
|
it('renders async children provided by subModelBaseClasses', async () => {
|
|
644
812
|
const engine = new FlowEngine();
|
|
645
|
-
engine.flowSettings.forceEnable();
|
|
813
|
+
await engine.flowSettings.forceEnable();
|
|
646
814
|
|
|
647
815
|
class Parent extends FlowModel {}
|
|
648
816
|
class AsyncLeaf extends FlowModel {}
|
|
@@ -687,7 +855,7 @@ describe('AddSubModelButton - base class menu groups', () => {
|
|
|
687
855
|
|
|
688
856
|
it('skips base class groups whose children resolve to empty', async () => {
|
|
689
857
|
const engine = new FlowEngine();
|
|
690
|
-
engine.flowSettings.forceEnable();
|
|
858
|
+
await engine.flowSettings.forceEnable();
|
|
691
859
|
|
|
692
860
|
class Parent extends FlowModel {}
|
|
693
861
|
class EmptyLeaf extends FlowModel {}
|
|
@@ -739,7 +907,7 @@ describe('AddSubModelButton - base class menu groups', () => {
|
|
|
739
907
|
|
|
740
908
|
it('renders submenu base class with children and respects meta.sort', async () => {
|
|
741
909
|
const engine = new FlowEngine();
|
|
742
|
-
engine.flowSettings.forceEnable();
|
|
910
|
+
await engine.flowSettings.forceEnable();
|
|
743
911
|
|
|
744
912
|
class Parent extends FlowModel {}
|
|
745
913
|
class Leaf extends FlowModel {}
|
|
@@ -803,7 +971,7 @@ describe('AddSubModelButton - base class menu groups', () => {
|
|
|
803
971
|
|
|
804
972
|
it('merges explicit items with base class and grouped sources', async () => {
|
|
805
973
|
const engine = new FlowEngine();
|
|
806
|
-
engine.flowSettings.forceEnable();
|
|
974
|
+
await engine.flowSettings.forceEnable();
|
|
807
975
|
|
|
808
976
|
class Parent extends FlowModel {}
|
|
809
977
|
class BaseChild extends FlowModel {}
|
|
@@ -862,7 +1030,7 @@ describe('AddSubModelButton - base class menu groups', () => {
|
|
|
862
1030
|
describe('AddSubModelButton - toggle interactions', () => {
|
|
863
1031
|
it('removes existing toggleable sub model and triggers callbacks', async () => {
|
|
864
1032
|
const engine = new FlowEngine();
|
|
865
|
-
engine.flowSettings.forceEnable();
|
|
1033
|
+
await engine.flowSettings.forceEnable();
|
|
866
1034
|
|
|
867
1035
|
class ToggleParent extends FlowModel {}
|
|
868
1036
|
const destroySpy = vi.fn();
|
|
@@ -926,7 +1094,7 @@ describe('AddSubModelButton - toggle interactions', () => {
|
|
|
926
1094
|
|
|
927
1095
|
it('creates toggleable sub model and runs lifecycle callbacks', async () => {
|
|
928
1096
|
const engine = new FlowEngine();
|
|
929
|
-
engine.flowSettings.forceEnable();
|
|
1097
|
+
await engine.flowSettings.forceEnable();
|
|
930
1098
|
|
|
931
1099
|
class ToggleParent extends FlowModel {}
|
|
932
1100
|
const saveSpy = vi.fn();
|
|
@@ -995,6 +1163,56 @@ describe('AddSubModelButton - toggle interactions', () => {
|
|
|
995
1163
|
const subModels = ((parent.subModels as any).items as FlowModel[]) || [];
|
|
996
1164
|
expect(subModels).toHaveLength(1);
|
|
997
1165
|
});
|
|
1166
|
+
|
|
1167
|
+
it('updates toggle state after external sub model removal', async () => {
|
|
1168
|
+
const engine = new FlowEngine();
|
|
1169
|
+
await engine.flowSettings.forceEnable();
|
|
1170
|
+
|
|
1171
|
+
class ToggleParent extends FlowModel {}
|
|
1172
|
+
class ToggleChild extends FlowModel {}
|
|
1173
|
+
|
|
1174
|
+
engine.registerModels({ ToggleParent, ToggleChild });
|
|
1175
|
+
const parent = engine.createModel<ToggleParent>({ use: 'ToggleParent', uid: 'toggle-parent-external-remove' });
|
|
1176
|
+
const existing = engine.createModel<ToggleChild>({ use: 'ToggleChild', uid: 'toggle-child-external-remove' });
|
|
1177
|
+
parent.addSubModel('items', existing);
|
|
1178
|
+
|
|
1179
|
+
render(
|
|
1180
|
+
<FlowEngineProvider engine={engine}>
|
|
1181
|
+
<ConfigProvider>
|
|
1182
|
+
<App>
|
|
1183
|
+
<AddSubModelButton
|
|
1184
|
+
model={parent}
|
|
1185
|
+
subModelKey="items"
|
|
1186
|
+
items={[
|
|
1187
|
+
{
|
|
1188
|
+
key: 'toggle-child',
|
|
1189
|
+
label: 'Toggle Child',
|
|
1190
|
+
toggleable: true,
|
|
1191
|
+
useModel: 'ToggleChild',
|
|
1192
|
+
createModelOptions: { use: 'ToggleChild' },
|
|
1193
|
+
},
|
|
1194
|
+
]}
|
|
1195
|
+
>
|
|
1196
|
+
Toggle Menu
|
|
1197
|
+
</AddSubModelButton>
|
|
1198
|
+
</App>
|
|
1199
|
+
</ConfigProvider>
|
|
1200
|
+
</FlowEngineProvider>,
|
|
1201
|
+
);
|
|
1202
|
+
|
|
1203
|
+
await act(async () => {
|
|
1204
|
+
await userEvent.click(screen.getByText('Toggle Menu'));
|
|
1205
|
+
});
|
|
1206
|
+
|
|
1207
|
+
await waitFor(() => expect(screen.getByText('Toggle Child')).toBeInTheDocument());
|
|
1208
|
+
await waitFor(() => expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'true'));
|
|
1209
|
+
|
|
1210
|
+
await act(async () => {
|
|
1211
|
+
await existing.destroy();
|
|
1212
|
+
});
|
|
1213
|
+
|
|
1214
|
+
await waitFor(() => expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'false'));
|
|
1215
|
+
});
|
|
998
1216
|
});
|
|
999
1217
|
|
|
1000
1218
|
// ========================
|
|
@@ -1013,9 +1231,9 @@ describe('AddSubModelButton toggleable behavior', () => {
|
|
|
1013
1231
|
duplicate = vi.fn().mockResolvedValue(null);
|
|
1014
1232
|
}
|
|
1015
1233
|
|
|
1016
|
-
function setup() {
|
|
1234
|
+
async function setup() {
|
|
1017
1235
|
const engine = new FlowEngine();
|
|
1018
|
-
engine.flowSettings.forceEnable();
|
|
1236
|
+
await engine.flowSettings.forceEnable();
|
|
1019
1237
|
engine.registerModels({ ToggleModel });
|
|
1020
1238
|
engine.setModelRepository(new FakeRepo());
|
|
1021
1239
|
|
|
@@ -1068,7 +1286,7 @@ describe('AddSubModelButton toggleable behavior', () => {
|
|
|
1068
1286
|
});
|
|
1069
1287
|
|
|
1070
1288
|
test('keeps dropdown open and preserves loaded children on toggle add/remove', async () => {
|
|
1071
|
-
const { engine, ui } = setup();
|
|
1289
|
+
const { engine, ui } = await setup();
|
|
1072
1290
|
const user = userEvent.setup();
|
|
1073
1291
|
|
|
1074
1292
|
render(ui);
|
|
@@ -1092,6 +1310,7 @@ describe('AddSubModelButton toggleable behavior', () => {
|
|
|
1092
1310
|
},
|
|
1093
1311
|
{ timeout: 3000 },
|
|
1094
1312
|
);
|
|
1313
|
+
await waitFor(() => expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'true'));
|
|
1095
1314
|
|
|
1096
1315
|
// dropdown should remain open and children should still be visible (no flicker / reload)
|
|
1097
1316
|
expect(screen.getByText('Async Group')).toBeInTheDocument();
|
|
@@ -1108,12 +1327,13 @@ describe('AddSubModelButton toggleable behavior', () => {
|
|
|
1108
1327
|
|
|
1109
1328
|
// ensure destroy has been called (avoid flakiness on exact call counts)
|
|
1110
1329
|
await waitFor(() => {
|
|
1330
|
+
expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'false');
|
|
1111
1331
|
expect(repo.destroy).toHaveBeenCalled();
|
|
1112
1332
|
});
|
|
1113
1333
|
});
|
|
1114
1334
|
|
|
1115
1335
|
test('toggle state updates without menu closing', async () => {
|
|
1116
|
-
const { ui } = setup();
|
|
1336
|
+
const { ui } = await setup();
|
|
1117
1337
|
const user = userEvent.setup();
|
|
1118
1338
|
|
|
1119
1339
|
render(ui);
|
|
@@ -1131,7 +1351,7 @@ describe('AddSubModelButton toggleable behavior', () => {
|
|
|
1131
1351
|
|
|
1132
1352
|
test('nested submenu (static items) toggle keeps menu open and reflects state', async () => {
|
|
1133
1353
|
const engine = new FlowEngine();
|
|
1134
|
-
engine.flowSettings.forceEnable();
|
|
1354
|
+
await engine.flowSettings.forceEnable();
|
|
1135
1355
|
engine.registerModels({ ToggleModel });
|
|
1136
1356
|
const parent = engine.createModel<FlowModel>({ use: FlowModel });
|
|
1137
1357
|
|
|
@@ -1213,7 +1433,7 @@ describe('AddSubModelButton toggleable behavior', () => {
|
|
|
1213
1433
|
|
|
1214
1434
|
test('submenu (second-level) toggleable stays open and updates state', async () => {
|
|
1215
1435
|
const engine = new FlowEngine();
|
|
1216
|
-
engine.flowSettings.forceEnable();
|
|
1436
|
+
await engine.flowSettings.forceEnable();
|
|
1217
1437
|
engine.registerModels({ ToggleModel });
|
|
1218
1438
|
engine.setModelRepository(new FakeRepo());
|
|
1219
1439
|
vi.spyOn(engine.flowSettings, 'open').mockResolvedValue(false as any);
|
|
@@ -1261,18 +1481,81 @@ describe('AddSubModelButton toggleable behavior', () => {
|
|
|
1261
1481
|
// click leaf toggle to add
|
|
1262
1482
|
await user.click(screen.getByText('Leaf Toggle'));
|
|
1263
1483
|
|
|
1264
|
-
// menu should remain visible
|
|
1484
|
+
// menu and submenu should remain visible after toggling a submenu leaf
|
|
1265
1485
|
expect(screen.getByText('Fields')).toBeInTheDocument();
|
|
1266
|
-
|
|
1267
|
-
// 由于点击叶子项后二级子菜单可能被收起,这里先重新展开再断言开关状态
|
|
1268
|
-
await user.hover(screen.getByText('Fields'));
|
|
1269
1486
|
await waitFor(() => expect(screen.getByText('Leaf Toggle')).toBeInTheDocument());
|
|
1270
1487
|
await waitFor(() => expect(screen.getByRole('switch')).toHaveAttribute('aria-checked', 'true'));
|
|
1271
1488
|
});
|
|
1272
1489
|
|
|
1490
|
+
test('keepDropdownOpen keeps root menu visible after clicking a nested relation-style leaf', async () => {
|
|
1491
|
+
const engine = new FlowEngine();
|
|
1492
|
+
await engine.flowSettings.forceEnable();
|
|
1493
|
+
|
|
1494
|
+
class Parent extends FlowModel {}
|
|
1495
|
+
class RelationLeafModel extends FlowModel {}
|
|
1496
|
+
|
|
1497
|
+
engine.registerModels({ Parent, RelationLeafModel });
|
|
1498
|
+
engine.setModelRepository(new FakeRepo());
|
|
1499
|
+
vi.spyOn(engine.flowSettings, 'open').mockResolvedValue(false as any);
|
|
1500
|
+
|
|
1501
|
+
const parent = engine.createModel<FlowModel>({ use: 'Parent' });
|
|
1502
|
+
|
|
1503
|
+
const items = [
|
|
1504
|
+
{
|
|
1505
|
+
key: 'relation-fields',
|
|
1506
|
+
label: 'Display association fields',
|
|
1507
|
+
children: [
|
|
1508
|
+
{
|
|
1509
|
+
key: 'users',
|
|
1510
|
+
label: 'Users',
|
|
1511
|
+
type: 'group' as const,
|
|
1512
|
+
children: [
|
|
1513
|
+
{
|
|
1514
|
+
key: 'user-name',
|
|
1515
|
+
label: 'User name',
|
|
1516
|
+
createModelOptions: { use: 'RelationLeafModel' },
|
|
1517
|
+
},
|
|
1518
|
+
],
|
|
1519
|
+
},
|
|
1520
|
+
],
|
|
1521
|
+
},
|
|
1522
|
+
];
|
|
1523
|
+
|
|
1524
|
+
render(
|
|
1525
|
+
<FlowEngineProvider engine={engine}>
|
|
1526
|
+
<ConfigProvider>
|
|
1527
|
+
<App>
|
|
1528
|
+
<AddSubModelButton
|
|
1529
|
+
model={parent}
|
|
1530
|
+
items={items as any}
|
|
1531
|
+
subModelType="array"
|
|
1532
|
+
subModelKey="subs"
|
|
1533
|
+
keepDropdownOpen
|
|
1534
|
+
>
|
|
1535
|
+
Open
|
|
1536
|
+
</AddSubModelButton>
|
|
1537
|
+
</App>
|
|
1538
|
+
</ConfigProvider>
|
|
1539
|
+
</FlowEngineProvider>,
|
|
1540
|
+
);
|
|
1541
|
+
|
|
1542
|
+
const user = userEvent.setup();
|
|
1543
|
+
await user.click(screen.getByText('Open'));
|
|
1544
|
+
|
|
1545
|
+
await waitFor(() => expect(screen.getByText('Display association fields')).toBeInTheDocument());
|
|
1546
|
+
await user.hover(screen.getByText('Display association fields'));
|
|
1547
|
+
await waitFor(() => expect(screen.getByText('Users')).toBeInTheDocument());
|
|
1548
|
+
await waitFor(() => expect(getSubmenuTitle('Display association fields')).toHaveAttribute('aria-expanded', 'true'));
|
|
1549
|
+
|
|
1550
|
+
await user.click(screen.getByText('User name'));
|
|
1551
|
+
|
|
1552
|
+
await waitFor(() => expect(screen.getByText('Display association fields')).toBeInTheDocument());
|
|
1553
|
+
await waitFor(() => expect(getSubmenuTitle('Display association fields')).toHaveAttribute('aria-expanded', 'true'));
|
|
1554
|
+
});
|
|
1555
|
+
|
|
1273
1556
|
test('top-level toggle updates after opening a second-level branch', async () => {
|
|
1274
1557
|
const engine = new FlowEngine();
|
|
1275
|
-
engine.flowSettings.forceEnable();
|
|
1558
|
+
await engine.flowSettings.forceEnable();
|
|
1276
1559
|
engine.registerModels({ ToggleModel });
|
|
1277
1560
|
engine.setModelRepository(new FakeRepo());
|
|
1278
1561
|
vi.spyOn(engine.flowSettings, 'open').mockResolvedValue(false as any);
|
|
@@ -100,6 +100,30 @@ describe('subModel/utils', () => {
|
|
|
100
100
|
expect(groups[0].children).toBeTruthy();
|
|
101
101
|
});
|
|
102
102
|
|
|
103
|
+
it('preserves searchable meta on generated groups', async () => {
|
|
104
|
+
const engine = new FlowEngine();
|
|
105
|
+
|
|
106
|
+
class Base extends FlowModel {}
|
|
107
|
+
Base.define({
|
|
108
|
+
label: 'Base Group',
|
|
109
|
+
searchable: true,
|
|
110
|
+
searchPlaceholder: 'Search fields',
|
|
111
|
+
});
|
|
112
|
+
const BaseDC = attachDefineChildren(Base, async () => [{ key: 'title', label: 'Title' }]);
|
|
113
|
+
|
|
114
|
+
engine.registerModels({ Base: BaseDC });
|
|
115
|
+
|
|
116
|
+
const model = engine.createModel({ use: 'FlowModel' });
|
|
117
|
+
const ctx = model.context;
|
|
118
|
+
|
|
119
|
+
const groupsFactory = buildSubModelGroups([BaseDC]);
|
|
120
|
+
const groups = await groupsFactory(ctx);
|
|
121
|
+
|
|
122
|
+
expect(groups).toHaveLength(1);
|
|
123
|
+
expect(groups[0].searchable).toBe(true);
|
|
124
|
+
expect(groups[0].searchPlaceholder).toBe('Search fields');
|
|
125
|
+
});
|
|
126
|
+
|
|
103
127
|
it('invokes buildSubModelItems when meta.children is false', async () => {
|
|
104
128
|
const engine = new FlowEngine();
|
|
105
129
|
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import _ from 'lodash';
|
|
11
11
|
import type { Collection } from '../../data-source';
|
|
12
12
|
import { FlowModelContext } from '../../flowContext';
|
|
13
13
|
import { FlowModelMeta, ModelConstructor } from '../../types';
|
|
@@ -196,12 +196,16 @@ export function buildSubModelGroups(subModelBaseClasses: (string | ModelConstruc
|
|
|
196
196
|
const baseKey = typeof subModelBaseClass === 'string' ? subModelBaseClass : BaseClass.name;
|
|
197
197
|
const menuType = BaseClass?.meta?.menuType || 'group';
|
|
198
198
|
const groupSort = BaseClass?.meta?.sort ?? 1000;
|
|
199
|
+
const searchable = !!BaseClass?.meta?.searchable;
|
|
200
|
+
const searchPlaceholder = BaseClass?.meta?.searchPlaceholder;
|
|
199
201
|
if (menuType === 'submenu') {
|
|
200
202
|
// 作为可点击的一级项,展开二级子菜单
|
|
201
203
|
items.push({
|
|
202
204
|
key: baseKey,
|
|
203
205
|
label: groupLabel,
|
|
204
206
|
sort: groupSort,
|
|
207
|
+
searchable,
|
|
208
|
+
searchPlaceholder,
|
|
205
209
|
children,
|
|
206
210
|
});
|
|
207
211
|
} else {
|
|
@@ -211,6 +215,8 @@ export function buildSubModelGroups(subModelBaseClasses: (string | ModelConstruc
|
|
|
211
215
|
type: 'group',
|
|
212
216
|
label: groupLabel,
|
|
213
217
|
sort: groupSort,
|
|
218
|
+
searchable,
|
|
219
|
+
searchPlaceholder,
|
|
214
220
|
children,
|
|
215
221
|
});
|
|
216
222
|
}
|