@nocobase/client-v2 2.2.0-beta.9 → 3.0.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/es/APIClient.d.ts +1 -3
- package/es/BaseApplication.d.ts +1 -0
- package/es/acl/ACLProvider.d.ts +10 -0
- package/es/authRedirect.d.ts +5 -0
- package/es/collection-field-interface/CollectionFieldInterface.d.ts +1 -0
- package/es/collection-field-interface/CollectionFieldInterfaceManager.d.ts +1 -0
- package/es/components/AttachmentUpload.d.ts +51 -0
- package/es/components/category-tabs/SortableCategoryTabs.d.ts +55 -0
- package/es/components/category-tabs/index.d.ts +9 -0
- package/es/components/form/ScanInput/useCodeScanner.d.ts +3 -2
- package/es/components/form/TypedVariableInput.d.ts +31 -5
- package/es/components/form/filter/CollectionFilter.d.ts +2 -0
- package/es/components/form/filter/CollectionFilterItem.d.ts +11 -1
- package/es/components/form/filter/CollectionFilterPanel.d.ts +2 -0
- package/es/components/form/filter/index.d.ts +2 -0
- package/es/components/form/filter/useFilterActionProps.d.ts +2 -0
- package/es/components/index.d.ts +2 -0
- package/es/flow/actions/index.d.ts +1 -1
- package/es/flow/actions/linkageRules.d.ts +3 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/NocoBaseLogo.d.ts +10 -0
- package/es/flow/components/FieldAssignExactDatePicker.d.ts +1 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +9 -0
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/RunJSValueEditor.d.ts +1 -0
- package/es/flow/components/filter/FilterGroup.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +6 -1
- package/es/flow/index.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/models/base/ActionModelCore.d.ts +4 -2
- package/es/flow/models/base/BlockGridModel.d.ts +3 -0
- package/es/flow/models/base/PageModel/PageModel.d.ts +12 -0
- package/es/flow/models/base/PageModel/PageModelTabBar.d.ts +22 -0
- package/es/flow/models/base/PageModel/PageTabModel.d.ts +12 -0
- package/es/flow/models/blocks/form/QuickEditFormModel.d.ts +17 -2
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +1 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow-compat/fieldValidationConstants.d.ts +1 -1
- package/es/flow-compat/routeTypes.d.ts +1 -0
- package/es/index.d.ts +8 -1
- package/es/index.mjs +190 -141
- package/es/nocobase-buildin-plugin/currentUserAuthStatus.d.ts +11 -0
- package/es/nocobase-buildin-plugin/index.d.ts +9 -0
- package/es/settings-app/SettingsApplication.d.ts +18 -0
- package/es/settings-app/SettingsBrand.d.ts +16 -0
- package/es/settings-app/SettingsBuildInPlugin.d.ts +9 -0
- package/es/settings-app/SettingsDocumentRedirect.d.ts +10 -0
- package/es/settings-app/SettingsGroupNav.d.ts +17 -0
- package/es/settings-app/SettingsPluginSettingsManager.d.ts +14 -0
- package/es/settings-app/SettingsRouterManager.d.ts +15 -0
- package/es/settings-app/SettingsSearch.d.ts +17 -0
- package/es/settings-app/SettingsShell.d.ts +10 -0
- package/es/settings-app/isSettingsApp.d.ts +18 -0
- package/es/settings-app/runtimePaths.d.ts +18 -0
- package/es/settings-app/settingsDocumentPath.d.ts +14 -0
- package/es/settings-app/settingsTheme.d.ts +87 -0
- package/es/settings-app/useSettingsThemeConfig.d.ts +22 -0
- package/es/settings-center/AdminSettingsLayout.d.ts +2 -2
- package/es/settings-center/groups.d.ts +66 -0
- package/es/settings-center/useSettingsGroups.d.ts +36 -0
- package/es/settings-center/useSettingsSearch.d.ts +35 -0
- package/es/settings-center/utils.d.ts +24 -0
- package/es/theme/index.d.ts +1 -0
- package/es/theme/itemActive.d.ts +24 -0
- package/es/theme/type.d.ts +9 -0
- package/es/utils/getRouteRuntimeVersion.d.ts +23 -0
- package/es/utils/index.d.ts +1 -0
- package/lib/index.js +195 -146
- package/lib/locale/languageCodes.js +3 -1
- package/package.json +9 -7
- package/src/APIClient.ts +1 -10
- package/src/Application.tsx +4 -0
- package/src/BaseApplication.tsx +11 -6
- package/src/__tests__/app.test.tsx +73 -0
- package/src/__tests__/authRedirect.test.ts +137 -0
- package/src/__tests__/browserChecker.test.ts +185 -6
- package/src/__tests__/getRouteRuntimeVersion.test.ts +68 -0
- package/src/__tests__/mockSettingsApplication.ts +29 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +79 -10
- package/src/__tests__/plugin-manager.test.tsx +7 -6
- package/src/__tests__/settings-application.test.ts +164 -0
- package/src/__tests__/settings-auth-routes.test.ts +71 -0
- package/src/__tests__/settings-center.test.tsx +38 -37
- package/src/__tests__/settings-layout-root.test.tsx +260 -0
- package/src/__tests__/settings-runtime-paths.test.ts +99 -0
- package/src/__tests__/settings-shell.test.tsx +200 -0
- package/src/acl/ACLProvider.tsx +21 -0
- package/src/authRedirect.ts +72 -3
- package/src/collection-field-interface/CollectionFieldInterface.ts +1 -0
- package/src/collection-field-interface/CollectionFieldInterfaceManager.ts +1 -0
- package/src/collection-manager/field-validation.ts +1 -1
- package/src/components/AttachmentUpload.tsx +275 -0
- package/src/components/README.md +7 -1
- package/src/components/README.zh-CN.md +6 -1
- package/src/components/category-tabs/SortableCategoryTabs.tsx +210 -0
- package/src/components/category-tabs/index.ts +10 -0
- package/src/components/form/JsonTextArea.tsx +4 -0
- package/src/components/form/ScanInput/CodeScanner.tsx +79 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +101 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +284 -11
- package/src/components/form/ScanInput/useCodeScanner.ts +332 -24
- package/src/components/form/TypedVariableInput.tsx +452 -101
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +364 -12
- package/src/components/form/filter/CollectionFilter.tsx +4 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +32 -7
- package/src/components/form/filter/CollectionFilterPanel.tsx +4 -0
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +38 -0
- package/src/components/form/filter/__tests__/useFilterActionProps.test.tsx +95 -0
- package/src/components/form/filter/index.ts +2 -0
- package/src/components/form/filter/useFilterActionProps.ts +37 -6
- package/src/components/index.ts +2 -0
- package/src/flow/FlowPage.tsx +9 -1
- package/src/flow/__tests__/FlowPage.test.tsx +50 -3
- package/src/flow/__tests__/FlowRoute.test.tsx +128 -10
- package/src/flow/__tests__/PluginFlowEngine.test.ts +58 -0
- package/src/flow/actions/__tests__/actionLinkageRules.forkProps.test.ts +314 -0
- package/src/flow/actions/__tests__/dataScopeFormValueClear.test.ts +102 -0
- package/src/flow/actions/__tests__/linkageRules.tab.test.ts +171 -0
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
- package/src/flow/actions/index.ts +2 -0
- package/src/flow/actions/linkageRules.tsx +111 -16
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +1 -57
- package/src/flow/admin-shell/admin-layout/NocoBaseLogo.tsx +77 -0
- package/src/flow/admin-shell/admin-layout/TopbarActionsBar.tsx +37 -14
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +253 -6
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +77 -13
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FieldAssignExactDatePicker.tsx +25 -11
- package/src/flow/components/FieldAssignValueInput.tsx +91 -20
- package/src/flow/components/FlowRoute.tsx +78 -32
- package/src/flow/components/RunJSValueEditor.tsx +9 -1
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +216 -0
- package/src/flow/components/filter/FilterGroup.tsx +33 -5
- package/src/flow/components/filter/VariableFilterItem.tsx +158 -10
- package/src/flow/components/filter/__tests__/FilterGroup.test.tsx +45 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.leftMetaTree.test.tsx +9 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +70 -1
- package/src/flow/index.ts +3 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/models/base/ActionModelCore.tsx +11 -2
- package/src/flow/models/base/BlockGridModel.tsx +26 -0
- package/src/flow/models/base/CollectionBlockModel.tsx +6 -2
- package/src/flow/models/base/PageModel/PageModel.tsx +387 -53
- package/src/flow/models/base/PageModel/PageModelTabBar.tsx +114 -0
- package/src/flow/models/base/PageModel/PageTabModel.tsx +249 -18
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +790 -10
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.module-isolation.test.tsx +130 -0
- package/src/flow/models/base/PageModel/__tests__/PageModelTabBar.test.tsx +118 -0
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +1075 -7
- package/src/flow/models/base/__tests__/ActionModelCore.render.test.tsx +49 -0
- package/src/flow/models/base/__tests__/BlockGridModel.selectSceneActivation.test.ts +124 -0
- package/src/flow/models/base/__tests__/CollectionBlockModel.addAppends.test.ts +41 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +28 -3
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +27 -12
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +36 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +66 -4
- package/src/flow/models/blocks/form/QuickEditFormModel.tsx +189 -36
- package/src/flow/models/blocks/form/__tests__/QuickEditFormModel.quickEdit.test.ts +350 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +4 -2
- package/src/flow/models/blocks/table/TableColumnModel.tsx +30 -1
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.quickEditRefresh.test.ts +12 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +33 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +2 -1
- package/src/flow/models/fields/DateTimeFieldModel/__tests__/DateTimeNoTzFieldModel.dateLimit.test.tsx +149 -0
- package/src/flow/models/fields/DateTimeFieldModel/dateLimit.ts +149 -113
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
- package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +20 -10
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +24 -12
- package/src/flow/models/topbar/TopbarActionModel.tsx +52 -180
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/flow-compat/fieldValidationConstants.ts +1 -1
- package/src/flow-compat/routeTypes.ts +1 -0
- package/src/index.ts +14 -1
- package/src/locale/languageCodes.ts +3 -1
- package/src/nocobase-buildin-plugin/currentUserAuthStatus.ts +58 -0
- package/src/nocobase-buildin-plugin/index.tsx +110 -70
- package/src/settings-app/SettingsApplication.ts +31 -0
- package/src/settings-app/SettingsBrand.tsx +108 -0
- package/src/settings-app/SettingsBuildInPlugin.ts +10 -0
- package/src/settings-app/SettingsDocumentRedirect.tsx +26 -0
- package/src/settings-app/SettingsGroupNav.tsx +124 -0
- package/src/settings-app/SettingsPluginSettingsManager.ts +38 -0
- package/src/settings-app/SettingsRouterManager.ts +92 -0
- package/src/settings-app/SettingsSearch.tsx +238 -0
- package/src/settings-app/SettingsShell.tsx +167 -0
- package/src/settings-app/isSettingsApp.ts +21 -0
- package/src/settings-app/runtimePaths.ts +104 -0
- package/src/settings-app/settingsDocumentPath.ts +66 -0
- package/src/settings-app/settingsTheme.ts +301 -0
- package/src/settings-app/useSettingsThemeConfig.ts +100 -0
- package/src/settings-center/AdminSettingsLayout.tsx +122 -141
- package/src/settings-center/SystemSettingsPage.tsx +4 -170
- package/src/settings-center/groups.ts +108 -0
- package/src/settings-center/useSettingsGroups.ts +105 -0
- package/src/settings-center/useSettingsSearch.ts +173 -0
- package/src/settings-center/utils.tsx +50 -1
- package/src/theme/index.tsx +1 -0
- package/src/theme/itemActive.ts +31 -0
- package/src/theme/type.ts +10 -0
- package/src/utils/getRouteRuntimeVersion.ts +185 -0
- package/src/utils/index.tsx +1 -0
|
@@ -1,3 +1,12 @@
|
|
|
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
|
+
|
|
1
10
|
import { render, screen } from '@nocobase/test/client';
|
|
2
11
|
import { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
|
|
3
12
|
import { App, ConfigProvider } from 'antd';
|
|
@@ -35,3 +44,43 @@ describe('ActionModel rendering', () => {
|
|
|
35
44
|
expect(screen.getByRole('button', { name: 'Open details' })).toBeInTheDocument();
|
|
36
45
|
});
|
|
37
46
|
});
|
|
47
|
+
|
|
48
|
+
describe('ActionModel disabled state persistence', () => {
|
|
49
|
+
it('drops disabled from persisted props during initialization', () => {
|
|
50
|
+
const engine = new FlowEngine();
|
|
51
|
+
engine.registerModels({ NoIconActionModel });
|
|
52
|
+
const model = engine.createModel<NoIconActionModel>({
|
|
53
|
+
use: 'NoIconActionModel',
|
|
54
|
+
props: { title: 'Open details', disabled: true },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
expect(model.getProps()).not.toHaveProperty('disabled');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it('keeps runtime disabled state without serializing it', () => {
|
|
61
|
+
const engine = new FlowEngine();
|
|
62
|
+
engine.registerModels({ NoIconActionModel });
|
|
63
|
+
const model = engine.createModel<NoIconActionModel>({
|
|
64
|
+
use: 'NoIconActionModel',
|
|
65
|
+
props: { title: 'Open details' },
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
model.setProps('disabled', true);
|
|
69
|
+
|
|
70
|
+
expect(model.getProps().disabled).toBe(true);
|
|
71
|
+
expect(model.serialize().props).toEqual({ title: 'Open details' });
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it('does not serialize fork-local disabled state', () => {
|
|
75
|
+
const engine = new FlowEngine();
|
|
76
|
+
engine.registerModels({ NoIconActionModel });
|
|
77
|
+
const model = engine.createModel<NoIconActionModel>({
|
|
78
|
+
use: 'NoIconActionModel',
|
|
79
|
+
props: { title: 'Open details' },
|
|
80
|
+
});
|
|
81
|
+
const fork = model.createFork({ disabled: true });
|
|
82
|
+
|
|
83
|
+
expect(fork.getProps().disabled).toBe(true);
|
|
84
|
+
expect(fork.serialize().props).toEqual({ title: 'Open details' });
|
|
85
|
+
});
|
|
86
|
+
});
|
|
@@ -0,0 +1,124 @@
|
|
|
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 { FlowEngine, type FlowModelContext, MultiRecordResource, VIEW_ACTIVATED_EVENT } from '@nocobase/flow-engine';
|
|
11
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
12
|
+
import { BlockGridModel } from '../BlockGridModel';
|
|
13
|
+
import { CollectionBlockModel } from '../CollectionBlockModel';
|
|
14
|
+
|
|
15
|
+
class TestBlockGridModel extends BlockGridModel {
|
|
16
|
+
mountForTest() {
|
|
17
|
+
super.onMount();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
unmountForTest() {
|
|
21
|
+
super.onUnmount();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
class TestMultiRecordResource extends MultiRecordResource<Record<string, unknown>> {
|
|
26
|
+
refreshCalls = 0;
|
|
27
|
+
|
|
28
|
+
override async refresh(): Promise<void> {
|
|
29
|
+
this.refreshCalls += 1;
|
|
30
|
+
this.emit('refresh');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
class TestCollectionBlockModel extends CollectionBlockModel {
|
|
35
|
+
createResource(ctx: FlowModelContext) {
|
|
36
|
+
return ctx.createResource(TestMultiRecordResource);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
mountForTest() {
|
|
40
|
+
super.onMount();
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
describe('BlockGridModel - select scene activation', () => {
|
|
45
|
+
let engine: FlowEngine;
|
|
46
|
+
|
|
47
|
+
beforeEach(() => {
|
|
48
|
+
engine = new FlowEngine();
|
|
49
|
+
engine.context.defineProperty('location', { value: { search: '' } });
|
|
50
|
+
engine.registerModels({ TestBlockGridModel, TestCollectionBlockModel });
|
|
51
|
+
|
|
52
|
+
engine.dataSourceManager.getDataSource('main').addCollection({
|
|
53
|
+
name: 'roles',
|
|
54
|
+
filterTargetKey: 'id',
|
|
55
|
+
fields: [
|
|
56
|
+
{ name: 'id', type: 'integer', interface: 'number' },
|
|
57
|
+
{ name: 'name', type: 'string', interface: 'input' },
|
|
58
|
+
],
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
function createModels(scene = 'select') {
|
|
63
|
+
engine.context.defineProperty('view', { value: { inputArgs: { scene } } });
|
|
64
|
+
const grid = engine.createModel<TestBlockGridModel>({ use: 'TestBlockGridModel' });
|
|
65
|
+
const block = grid.addSubModel<TestCollectionBlockModel>('items', {
|
|
66
|
+
use: 'TestCollectionBlockModel',
|
|
67
|
+
stepParams: {
|
|
68
|
+
resourceSettings: {
|
|
69
|
+
init: {
|
|
70
|
+
dataSourceKey: 'main',
|
|
71
|
+
collectionName: 'roles',
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
const resource = block.context.resource as TestMultiRecordResource;
|
|
77
|
+
|
|
78
|
+
block.mountForTest();
|
|
79
|
+
grid.mountForTest();
|
|
80
|
+
return { grid, resource };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
it('refreshes dirty collection blocks once when a nested view closes', async () => {
|
|
84
|
+
const { grid, resource } = createModels();
|
|
85
|
+
|
|
86
|
+
engine.markDataSourceDirty('main', 'roles');
|
|
87
|
+
engine.emitter.emit(VIEW_ACTIVATED_EVENT);
|
|
88
|
+
await Promise.resolve();
|
|
89
|
+
|
|
90
|
+
expect(resource.refreshCalls).toBe(1);
|
|
91
|
+
|
|
92
|
+
engine.emitter.emit(VIEW_ACTIVATED_EVENT);
|
|
93
|
+
await Promise.resolve();
|
|
94
|
+
|
|
95
|
+
expect(resource.refreshCalls).toBe(1);
|
|
96
|
+
grid.unmountForTest();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('does not refresh without dirty data or after the grid unmounts', async () => {
|
|
100
|
+
const { grid, resource } = createModels();
|
|
101
|
+
|
|
102
|
+
engine.emitter.emit(VIEW_ACTIVATED_EVENT);
|
|
103
|
+
await Promise.resolve();
|
|
104
|
+
expect(resource.refreshCalls).toBe(0);
|
|
105
|
+
|
|
106
|
+
grid.unmountForTest();
|
|
107
|
+
engine.markDataSourceDirty('main', 'roles');
|
|
108
|
+
engine.emitter.emit(VIEW_ACTIVATED_EVENT);
|
|
109
|
+
await Promise.resolve();
|
|
110
|
+
|
|
111
|
+
expect(resource.refreshCalls).toBe(0);
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('does not handle activation outside the select scene', async () => {
|
|
115
|
+
const { grid, resource } = createModels('list');
|
|
116
|
+
|
|
117
|
+
engine.markDataSourceDirty('main', 'roles');
|
|
118
|
+
engine.emitter.emit(VIEW_ACTIVATED_EVENT);
|
|
119
|
+
await Promise.resolve();
|
|
120
|
+
|
|
121
|
+
expect(resource.refreshCalls).toBe(0);
|
|
122
|
+
grid.unmountForTest();
|
|
123
|
+
});
|
|
124
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
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 { FlowEngine, FlowModelContext, MultiRecordResource } from '@nocobase/flow-engine';
|
|
11
|
+
import { describe, expect, it } from 'vitest';
|
|
12
|
+
import { CollectionBlockModel } from '../CollectionBlockModel';
|
|
13
|
+
|
|
14
|
+
class AddAppendsCollectionBlockModel extends CollectionBlockModel {
|
|
15
|
+
createResource(ctx: FlowModelContext) {
|
|
16
|
+
return ctx.createResource(MultiRecordResource);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
describe('CollectionBlockModel addAppends', () => {
|
|
21
|
+
it('does not throw for a nested field path when the collection is unavailable', () => {
|
|
22
|
+
const engine = new FlowEngine();
|
|
23
|
+
engine.registerModels({ AddAppendsCollectionBlockModel });
|
|
24
|
+
|
|
25
|
+
const model = engine.createModel<AddAppendsCollectionBlockModel>({
|
|
26
|
+
uid: 'missing-collection-add-appends-block',
|
|
27
|
+
use: 'AddAppendsCollectionBlockModel',
|
|
28
|
+
stepParams: {
|
|
29
|
+
resourceSettings: {
|
|
30
|
+
init: {
|
|
31
|
+
dataSourceKey: 'main',
|
|
32
|
+
collectionName: 'missing_collection',
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
expect(model.collection).toBeUndefined();
|
|
39
|
+
expect(() => model.addAppends('createdBy.departments')).not.toThrow();
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -15,8 +15,10 @@ import {
|
|
|
15
15
|
DndProvider,
|
|
16
16
|
DragHandler,
|
|
17
17
|
Droppable,
|
|
18
|
+
isCtxDateExpression,
|
|
18
19
|
isRunJSValue,
|
|
19
20
|
normalizeRunJSValue,
|
|
21
|
+
parseCtxDateExpression,
|
|
20
22
|
tExpr,
|
|
21
23
|
FlowModelRenderer,
|
|
22
24
|
FlowSettingsButton,
|
|
@@ -44,6 +46,15 @@ import { normalizeFilterValueByOperator } from './valueNormalization';
|
|
|
44
46
|
|
|
45
47
|
const RELATION_FIELD_TYPES = ['belongsTo', 'hasOne', 'hasMany', 'belongsToMany', 'belongsToArray'];
|
|
46
48
|
const NUMERIC_FIELD_TYPES = ['integer', 'float', 'double', 'decimal'];
|
|
49
|
+
const DATE_FILTER_OPERATORS = new Set([
|
|
50
|
+
'$dateOn',
|
|
51
|
+
'$dateNotOn',
|
|
52
|
+
'$dateBefore',
|
|
53
|
+
'$dateAfter',
|
|
54
|
+
'$dateNotBefore',
|
|
55
|
+
'$dateNotAfter',
|
|
56
|
+
'$dateBetween',
|
|
57
|
+
]);
|
|
47
58
|
|
|
48
59
|
function getFilterFormFieldMetaType(field: CollectionField) {
|
|
49
60
|
if (RELATION_FIELD_TYPES.includes(field.type)) {
|
|
@@ -66,6 +77,14 @@ function getFilterFormFieldMetaType(field: CollectionField) {
|
|
|
66
77
|
}
|
|
67
78
|
}
|
|
68
79
|
|
|
80
|
+
function parseDateFilterDefaultValue(operator: string | undefined, rawValue: unknown) {
|
|
81
|
+
if (!operator || !DATE_FILTER_OPERATORS.has(operator) || !isCtxDateExpression(rawValue)) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return parseCtxDateExpression(rawValue);
|
|
86
|
+
}
|
|
87
|
+
|
|
69
88
|
function shouldShowFilterFormFieldMeta(field: CollectionField) {
|
|
70
89
|
return Boolean(field?.interface);
|
|
71
90
|
}
|
|
@@ -431,6 +450,7 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
431
450
|
|
|
432
451
|
private canApplyFormDefaultValue(name: string, current: any, force?: boolean) {
|
|
433
452
|
if (force) return true;
|
|
453
|
+
if (this.userEditedFieldNames.has(name)) return false;
|
|
434
454
|
if (isEmptyValue(current)) return true;
|
|
435
455
|
if (!this.lastDefaultValueByFieldName.has(name)) return false;
|
|
436
456
|
return isEqual(current, this.lastDefaultValueByFieldName.get(name));
|
|
@@ -488,7 +508,7 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
488
508
|
const rules = (params?.value || []) as any[];
|
|
489
509
|
if (!Array.isArray(rules) || rules.length === 0) return appliedValues;
|
|
490
510
|
|
|
491
|
-
const resolveValue = async (raw: any) => {
|
|
511
|
+
const resolveValue = async (raw: any, operator?: string) => {
|
|
492
512
|
// RunJS support
|
|
493
513
|
if (isRunJSValue(raw)) {
|
|
494
514
|
const { code, version } = normalizeRunJSValue(raw);
|
|
@@ -496,6 +516,11 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
496
516
|
return ret?.success ? ret.value : undefined;
|
|
497
517
|
}
|
|
498
518
|
|
|
519
|
+
const parsedDateFilterValue = parseDateFilterDefaultValue(operator, raw);
|
|
520
|
+
if (typeof parsedDateFilterValue !== 'undefined') {
|
|
521
|
+
return parsedDateFilterValue;
|
|
522
|
+
}
|
|
523
|
+
|
|
499
524
|
return await (this.context as any).resolveJsonTemplate?.(raw);
|
|
500
525
|
};
|
|
501
526
|
|
|
@@ -519,11 +544,11 @@ export class FilterFormBlockModel extends FilterBlockModel<{
|
|
|
519
544
|
|
|
520
545
|
const current = (form as any).getFieldValue?.(name);
|
|
521
546
|
|
|
522
|
-
const
|
|
547
|
+
const operator = getDefaultOperator(itemModel as any);
|
|
548
|
+
const resolved = await resolveValue(rule.value, operator);
|
|
523
549
|
if (options?.refreshSeq && options.refreshSeq !== this.defaultValuesRefreshSeq) return appliedValues;
|
|
524
550
|
if (typeof resolved === 'undefined') continue;
|
|
525
551
|
|
|
526
|
-
const operator = getDefaultOperator(itemModel as any);
|
|
527
552
|
const normalized = normalizeFilterValueByOperator(operator, resolved);
|
|
528
553
|
const mode = this.normalizeFieldValueMode(rule.mode);
|
|
529
554
|
if (mode === 'default' && !this.canApplyFormDefaultValue(String(name), current, force)) continue;
|
|
@@ -62,9 +62,25 @@ function isRecord(value: unknown): value is Record<string, unknown> {
|
|
|
62
62
|
return !!value && typeof value === 'object' && !Array.isArray(value);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
interface CollectionFieldWithAssociationMarker {
|
|
66
|
+
isAssociationField?: () => boolean;
|
|
67
|
+
target?: unknown;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function hasAssociationMarker(value: unknown): value is CollectionFieldWithAssociationMarker {
|
|
71
|
+
return isRecord(value) && (typeof value.isAssociationField === 'function' || 'target' in value);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const isAssociationCollectionField = (collectionField: unknown) => {
|
|
75
|
+
if (!hasAssociationMarker(collectionField)) {
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
return collectionField.isAssociationField ? collectionField.isAssociationField() : Boolean(collectionField.target);
|
|
79
|
+
};
|
|
80
|
+
|
|
65
81
|
const normalizeAssociationDefaultFilterValue = (value: any, fieldModel: any) => {
|
|
66
82
|
const collectionField = fieldModel?.context?.collectionField;
|
|
67
|
-
if (!collectionField
|
|
83
|
+
if (!isAssociationCollectionField(collectionField)) {
|
|
68
84
|
return value;
|
|
69
85
|
}
|
|
70
86
|
|
|
@@ -171,8 +187,7 @@ const buildFilterFormFieldItem = ({
|
|
|
171
187
|
if (!binding) {
|
|
172
188
|
return;
|
|
173
189
|
}
|
|
174
|
-
const isAssociation =
|
|
175
|
-
typeof field?.isAssociationField === 'function' ? field.isAssociationField() : Boolean(field?.target);
|
|
190
|
+
const isAssociation = isAssociationCollectionField(field);
|
|
176
191
|
const fieldModel =
|
|
177
192
|
isAssociation && ctxWithFlags.engine?.getModelClass?.('FilterFormRecordSelectFieldModel')
|
|
178
193
|
? 'FilterFormRecordSelectFieldModel'
|
|
@@ -507,10 +522,7 @@ export class FilterFormItemModel extends FilterableItemModel<{
|
|
|
507
522
|
const formValue = this.context.form?.getFieldValue(this.props.name);
|
|
508
523
|
const modelValue = fieldModel.getFilterValue ? fieldModel.getFilterValue() : formValue;
|
|
509
524
|
const collectionField = (fieldModel as any)?.context?.collectionField;
|
|
510
|
-
const isAssociationField =
|
|
511
|
-
typeof collectionField?.isAssociationField === 'function'
|
|
512
|
-
? collectionField.isAssociationField()
|
|
513
|
-
: !!collectionField?.target;
|
|
525
|
+
const isAssociationField = isAssociationCollectionField(collectionField);
|
|
514
526
|
const shouldUseFormValue =
|
|
515
527
|
isAssociationField &&
|
|
516
528
|
!this.mounted &&
|
|
@@ -547,11 +559,7 @@ export class FilterFormItemModel extends FilterableItemModel<{
|
|
|
547
559
|
return value;
|
|
548
560
|
}
|
|
549
561
|
const collectionField = (fieldModel as any)?.context?.collectionField;
|
|
550
|
-
|
|
551
|
-
typeof collectionField?.isAssociationField === 'function'
|
|
552
|
-
? collectionField.isAssociationField()
|
|
553
|
-
: !!collectionField?.target;
|
|
554
|
-
if (!isAssociation) {
|
|
562
|
+
if (!isAssociationCollectionField(collectionField)) {
|
|
555
563
|
return value;
|
|
556
564
|
}
|
|
557
565
|
const normalizedFieldNames = normalizeAssociationFieldNames(
|
|
@@ -798,6 +806,13 @@ FilterFormItemModel.registerFlow({
|
|
|
798
806
|
},
|
|
799
807
|
defaultOperator: {
|
|
800
808
|
use: 'defaultOperator',
|
|
809
|
+
hideInSettings(ctx) {
|
|
810
|
+
const collectionField =
|
|
811
|
+
ctx.collectionField ||
|
|
812
|
+
ctx.model?.context?.collectionField ||
|
|
813
|
+
ctx.model?.subModels?.field?.context?.collectionField;
|
|
814
|
+
return isAssociationCollectionField(collectionField);
|
|
815
|
+
},
|
|
801
816
|
},
|
|
802
817
|
operatorComponentProps: {
|
|
803
818
|
use: 'operatorComponentProps',
|
package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts
CHANGED
|
@@ -33,6 +33,42 @@ class DummyCollectionBlockModel extends CollectionBlockModel {
|
|
|
33
33
|
}
|
|
34
34
|
|
|
35
35
|
describe('FilterFormItemModel defineChildren association fields', () => {
|
|
36
|
+
it('hides default operator setting for association filter fields', () => {
|
|
37
|
+
const engine = new FlowEngine();
|
|
38
|
+
engine.registerModels({
|
|
39
|
+
FilterFormItemModel,
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
const filterItem = engine.createModel<FilterFormItemModel>({
|
|
43
|
+
uid: 'association-filter-item-settings',
|
|
44
|
+
use: 'FilterFormItemModel',
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
const defaultOperatorStep = filterItem.getFlow('filterFormItemSettings')?.steps?.defaultOperator as {
|
|
48
|
+
hideInSettings?: (ctx: {
|
|
49
|
+
collectionField?: unknown;
|
|
50
|
+
model?: {
|
|
51
|
+
subModels?: {
|
|
52
|
+
field?: {
|
|
53
|
+
context?: {
|
|
54
|
+
collectionField?: unknown;
|
|
55
|
+
};
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
}) => boolean;
|
|
60
|
+
};
|
|
61
|
+
expect(defaultOperatorStep?.hideInSettings?.({ collectionField: { isAssociationField: () => true } })).toBe(true);
|
|
62
|
+
expect(
|
|
63
|
+
defaultOperatorStep?.hideInSettings?.({
|
|
64
|
+
model: { subModels: { field: { context: { collectionField: { target: 'departments' } } } } },
|
|
65
|
+
}),
|
|
66
|
+
).toBe(true);
|
|
67
|
+
expect(defaultOperatorStep?.hideInSettings?.({ collectionField: { interface: 'input', type: 'string' } })).toBe(
|
|
68
|
+
false,
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
36
72
|
it('groups association target fields and supports recursive paths', async () => {
|
|
37
73
|
const engine = new FlowEngine();
|
|
38
74
|
engine.registerModels({
|
|
@@ -17,7 +17,15 @@ import { FilterFormBlockModel } from '../FilterFormBlockModel';
|
|
|
17
17
|
function resolveTemplateValue(raw: any, values: Record<string, any>): any {
|
|
18
18
|
if (typeof raw === 'string') {
|
|
19
19
|
const matched = raw.match(/^\{\{\s*ctx\.formValues\.([^}]+?)\s*\}\}$/);
|
|
20
|
-
|
|
20
|
+
if (matched) {
|
|
21
|
+
return values[matched[1]];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (/^\{\{\s*ctx\.date\.relative\.past\.day\.n7\s*\}\}$/.test(raw)) {
|
|
25
|
+
return '2026-06-15';
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return raw;
|
|
21
29
|
}
|
|
22
30
|
if (Array.isArray(raw)) {
|
|
23
31
|
return raw.map((item) => resolveTemplateValue(item, values));
|
|
@@ -30,7 +38,7 @@ function resolveTemplateValue(raw: any, values: Record<string, any>): any {
|
|
|
30
38
|
|
|
31
39
|
function createFilterFormDefaultValuesModel(rules: any[], initialValues: Record<string, any> = {}) {
|
|
32
40
|
const values = { ...initialValues };
|
|
33
|
-
const createItem = (fieldPath: string, uid: string) => ({
|
|
41
|
+
const createItem = (fieldPath: string, uid: string, operator?: string) => ({
|
|
34
42
|
uid,
|
|
35
43
|
fieldPath,
|
|
36
44
|
props: { name: `${fieldPath}_${uid}` },
|
|
@@ -44,7 +52,7 @@ function createFilterFormDefaultValuesModel(rules: any[], initialValues: Record<
|
|
|
44
52
|
return undefined;
|
|
45
53
|
},
|
|
46
54
|
subModels: {
|
|
47
|
-
field: {},
|
|
55
|
+
field: operator ? { operator } : {},
|
|
48
56
|
},
|
|
49
57
|
});
|
|
50
58
|
const model = {
|
|
@@ -76,7 +84,11 @@ function createFilterFormDefaultValuesModel(rules: any[], initialValues: Record<
|
|
|
76
84
|
subModels: {
|
|
77
85
|
grid: {
|
|
78
86
|
subModels: {
|
|
79
|
-
items: [
|
|
87
|
+
items: [
|
|
88
|
+
createItem('nickname', 'nick'),
|
|
89
|
+
createItem('username', 'user'),
|
|
90
|
+
createItem('birthdate_tz', 'birthdate', '$dateOn'),
|
|
91
|
+
],
|
|
80
92
|
},
|
|
81
93
|
},
|
|
82
94
|
},
|
|
@@ -301,6 +313,40 @@ describe('filter-form defaultValues wiring', () => {
|
|
|
301
313
|
expect(values.username_user).toBe('Manual');
|
|
302
314
|
});
|
|
303
315
|
|
|
316
|
+
it('does not reapply a filter form default value after user clears the field', async () => {
|
|
317
|
+
const { model, values } = createFilterFormDefaultValuesModel([
|
|
318
|
+
{
|
|
319
|
+
key: 'username-default',
|
|
320
|
+
enable: true,
|
|
321
|
+
targetPath: 'username',
|
|
322
|
+
mode: 'default',
|
|
323
|
+
value: 'admin',
|
|
324
|
+
},
|
|
325
|
+
]);
|
|
326
|
+
|
|
327
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
|
|
328
|
+
expect(values.username_user).toBe('admin');
|
|
329
|
+
|
|
330
|
+
values.username_user = undefined;
|
|
331
|
+
(model as any).handleFilterFormValuesChange({ username_user: undefined }, { username_user: undefined });
|
|
332
|
+
|
|
333
|
+
await waitFor(() => {
|
|
334
|
+
expect(model.dispatchEvent).toHaveBeenCalledWith(
|
|
335
|
+
'formValuesChange',
|
|
336
|
+
{
|
|
337
|
+
changedValues: {
|
|
338
|
+
username_user: undefined,
|
|
339
|
+
},
|
|
340
|
+
allValues: {
|
|
341
|
+
username_user: undefined,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
{ debounce: true },
|
|
345
|
+
);
|
|
346
|
+
});
|
|
347
|
+
expect(values.username_user).toBeUndefined();
|
|
348
|
+
});
|
|
349
|
+
|
|
304
350
|
it('applies fixed values even when the target filter field already has a value', async () => {
|
|
305
351
|
const { model, values } = createFilterFormDefaultValuesModel(
|
|
306
352
|
[
|
|
@@ -320,6 +366,22 @@ describe('filter-form defaultValues wiring', () => {
|
|
|
320
366
|
expect(values.username_user).toBe('Bob');
|
|
321
367
|
});
|
|
322
368
|
|
|
369
|
+
it('preserves relative date descriptors for date filter default values', async () => {
|
|
370
|
+
const { model, values } = createFilterFormDefaultValuesModel([
|
|
371
|
+
{
|
|
372
|
+
key: 'birthdate-default',
|
|
373
|
+
enable: true,
|
|
374
|
+
targetPath: 'birthdate_tz',
|
|
375
|
+
mode: 'assign',
|
|
376
|
+
value: '{{ ctx.date.relative.past.day.n7 }}',
|
|
377
|
+
},
|
|
378
|
+
]);
|
|
379
|
+
|
|
380
|
+
await FilterFormBlockModel.prototype.applyFormDefaultValues.call(model as any);
|
|
381
|
+
|
|
382
|
+
expect(values.birthdate_tz_birthdate).toEqual({ type: 'past', unit: 'day', number: 7 });
|
|
383
|
+
});
|
|
384
|
+
|
|
323
385
|
it('applies override values until the target filter field is changed by user', async () => {
|
|
324
386
|
const { model, values } = createFilterFormDefaultValuesModel(
|
|
325
387
|
[
|