@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
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
12
|
+
import { render, waitFor } from '@nocobase/test/client';
|
|
13
|
+
import { FieldAssignValueInput } from '../FieldAssignValueInput';
|
|
14
|
+
|
|
15
|
+
const { mockUseFlowContext, mockGetDefaultBindingByField, mockVariableInput } = vi.hoisted(() => ({
|
|
16
|
+
mockUseFlowContext: vi.fn(),
|
|
17
|
+
mockGetDefaultBindingByField: vi.fn(),
|
|
18
|
+
mockVariableInput: vi.fn(() => <div data-testid="variable-input" />),
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
vi.mock('@nocobase/flow-engine', async () => {
|
|
22
|
+
const actual = await vi.importActual<typeof import('@nocobase/flow-engine')>('@nocobase/flow-engine');
|
|
23
|
+
class MockEditableItemModel extends actual.EditableItemModel {}
|
|
24
|
+
MockEditableItemModel.getDefaultBindingByField = mockGetDefaultBindingByField;
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
...actual,
|
|
28
|
+
useFlowContext: () => mockUseFlowContext(),
|
|
29
|
+
VariableInput: mockVariableInput,
|
|
30
|
+
FlowModelRenderer: () => <div data-testid="flow-model-renderer" />,
|
|
31
|
+
EditableItemModel: MockEditableItemModel,
|
|
32
|
+
};
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe('FieldAssignValueInput context', () => {
|
|
36
|
+
it('delegates temporary field model context to the source form context', async () => {
|
|
37
|
+
type MockContext = {
|
|
38
|
+
dataSourceManager: { getDataSource: ReturnType<typeof vi.fn> };
|
|
39
|
+
t: (key: string) => string;
|
|
40
|
+
engine?: { createModel: ReturnType<typeof vi.fn> };
|
|
41
|
+
collection?: MockCollection;
|
|
42
|
+
blockModel?: MockFormModel;
|
|
43
|
+
};
|
|
44
|
+
type MockFieldModel = {
|
|
45
|
+
props: Record<string, unknown>;
|
|
46
|
+
setProps: ReturnType<typeof vi.fn>;
|
|
47
|
+
dispatchEvent: ReturnType<typeof vi.fn>;
|
|
48
|
+
remove: ReturnType<typeof vi.fn>;
|
|
49
|
+
};
|
|
50
|
+
type MockCollectionField = {
|
|
51
|
+
name: string;
|
|
52
|
+
interface: string;
|
|
53
|
+
uiSchema: { enum: Array<{ label: string; value: string }> };
|
|
54
|
+
isAssociationField: () => boolean;
|
|
55
|
+
getComponentProps: () => Record<string, unknown>;
|
|
56
|
+
};
|
|
57
|
+
type MockCollection = {
|
|
58
|
+
dataSourceKey: string;
|
|
59
|
+
name: string;
|
|
60
|
+
getField: (name: string) => MockCollectionField | null;
|
|
61
|
+
getFields: () => MockCollectionField[];
|
|
62
|
+
};
|
|
63
|
+
type MockFormModel = {
|
|
64
|
+
context: MockContext;
|
|
65
|
+
collection: MockCollection;
|
|
66
|
+
subModels: Record<string, unknown>;
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
const sourceContext: MockContext = {
|
|
70
|
+
dataSourceManager: {
|
|
71
|
+
getDataSource: vi.fn(() => ({})),
|
|
72
|
+
},
|
|
73
|
+
t: (key: string) => key,
|
|
74
|
+
};
|
|
75
|
+
const fieldModel: MockFieldModel = {
|
|
76
|
+
props: {},
|
|
77
|
+
setProps: vi.fn((props: Record<string, unknown>) => {
|
|
78
|
+
fieldModel.props = { ...fieldModel.props, ...props };
|
|
79
|
+
}),
|
|
80
|
+
dispatchEvent: vi.fn(),
|
|
81
|
+
remove: vi.fn(),
|
|
82
|
+
};
|
|
83
|
+
const tempRoot = {
|
|
84
|
+
context: {
|
|
85
|
+
defineProperty: vi.fn(),
|
|
86
|
+
},
|
|
87
|
+
subModels: {
|
|
88
|
+
fields: [fieldModel],
|
|
89
|
+
},
|
|
90
|
+
setProps: vi.fn(),
|
|
91
|
+
remove: vi.fn(),
|
|
92
|
+
};
|
|
93
|
+
const engine = {
|
|
94
|
+
createModel: vi.fn(() => tempRoot),
|
|
95
|
+
};
|
|
96
|
+
sourceContext.engine = engine;
|
|
97
|
+
|
|
98
|
+
const collectionField: MockCollectionField = {
|
|
99
|
+
name: 'status',
|
|
100
|
+
interface: 'select',
|
|
101
|
+
uiSchema: {
|
|
102
|
+
enum: [{ label: 'Open', value: 'open' }],
|
|
103
|
+
},
|
|
104
|
+
isAssociationField: () => false,
|
|
105
|
+
getComponentProps: () => ({}),
|
|
106
|
+
};
|
|
107
|
+
const collection: MockCollection = {
|
|
108
|
+
dataSourceKey: 'main',
|
|
109
|
+
name: 'tasks',
|
|
110
|
+
getField: (name: string) => (name === 'status' ? collectionField : null),
|
|
111
|
+
getFields: () => [collectionField],
|
|
112
|
+
};
|
|
113
|
+
const formModel: MockFormModel = {
|
|
114
|
+
context: sourceContext,
|
|
115
|
+
collection,
|
|
116
|
+
subModels: {},
|
|
117
|
+
};
|
|
118
|
+
sourceContext.collection = collection;
|
|
119
|
+
sourceContext.blockModel = formModel;
|
|
120
|
+
|
|
121
|
+
mockGetDefaultBindingByField.mockReturnValue({ modelName: 'SelectFieldModel' });
|
|
122
|
+
mockUseFlowContext.mockReturnValue({
|
|
123
|
+
model: formModel,
|
|
124
|
+
t: (key: string) => key,
|
|
125
|
+
getPropertyMetaTree: vi.fn(async () => []),
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
render(<FieldAssignValueInput targetPath="status" value="" onChange={vi.fn()} />);
|
|
129
|
+
|
|
130
|
+
await waitFor(() => {
|
|
131
|
+
expect(engine.createModel).toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
expect(engine.createModel).toHaveBeenCalledWith(expect.any(Object), { delegate: sourceContext });
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
it('lets callers override variable path parsing for domain-specific stored formats', async () => {
|
|
137
|
+
mockVariableInput.mockClear();
|
|
138
|
+
const sourceContext = {
|
|
139
|
+
dataSourceManager: {
|
|
140
|
+
getDataSource: vi.fn(() => ({})),
|
|
141
|
+
},
|
|
142
|
+
t: (key: string) => key,
|
|
143
|
+
};
|
|
144
|
+
const fieldModel = {
|
|
145
|
+
props: {},
|
|
146
|
+
setProps: vi.fn(),
|
|
147
|
+
dispatchEvent: vi.fn(),
|
|
148
|
+
remove: vi.fn(),
|
|
149
|
+
};
|
|
150
|
+
const tempRoot = {
|
|
151
|
+
context: {
|
|
152
|
+
defineProperty: vi.fn(),
|
|
153
|
+
},
|
|
154
|
+
subModels: {
|
|
155
|
+
fields: [fieldModel],
|
|
156
|
+
},
|
|
157
|
+
setProps: vi.fn(),
|
|
158
|
+
remove: vi.fn(),
|
|
159
|
+
};
|
|
160
|
+
const engine = {
|
|
161
|
+
createModel: vi.fn(() => tempRoot),
|
|
162
|
+
};
|
|
163
|
+
const collectionField = {
|
|
164
|
+
name: 'status',
|
|
165
|
+
interface: 'input',
|
|
166
|
+
uiSchema: { 'x-component': 'Input' },
|
|
167
|
+
isAssociationField: () => false,
|
|
168
|
+
getComponentProps: () => ({}),
|
|
169
|
+
};
|
|
170
|
+
const collection = {
|
|
171
|
+
dataSourceKey: 'main',
|
|
172
|
+
name: 'tasks',
|
|
173
|
+
getField: (name: string) => (name === 'status' ? collectionField : null),
|
|
174
|
+
getFields: () => [collectionField],
|
|
175
|
+
};
|
|
176
|
+
const formModel = {
|
|
177
|
+
context: { ...sourceContext, engine, collection, blockModel: null as any },
|
|
178
|
+
collection,
|
|
179
|
+
subModels: {},
|
|
180
|
+
};
|
|
181
|
+
formModel.context.blockModel = formModel;
|
|
182
|
+
|
|
183
|
+
mockGetDefaultBindingByField.mockReturnValue({ modelName: 'InputFieldModel' });
|
|
184
|
+
mockUseFlowContext.mockReturnValue({
|
|
185
|
+
model: formModel,
|
|
186
|
+
t: (key: string) => key,
|
|
187
|
+
getPropertyMetaTree: vi.fn(async () => []),
|
|
188
|
+
});
|
|
189
|
+
|
|
190
|
+
const variableConverters = {
|
|
191
|
+
resolvePathFromValue: vi.fn((value: string) =>
|
|
192
|
+
value === '{{$context.data.updatedAt}}' ? ['$context', 'data', 'updatedAt'] : undefined,
|
|
193
|
+
),
|
|
194
|
+
resolveValueFromPath: vi.fn(() => undefined),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
render(
|
|
198
|
+
<FieldAssignValueInput
|
|
199
|
+
targetPath="status"
|
|
200
|
+
value="{{$context.data.updatedAt}}"
|
|
201
|
+
onChange={vi.fn()}
|
|
202
|
+
variableConverters={variableConverters}
|
|
203
|
+
/>,
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
await waitFor(() => {
|
|
207
|
+
expect(engine.createModel).toHaveBeenCalled();
|
|
208
|
+
});
|
|
209
|
+
const latestVariableInputCall = mockVariableInput.mock.calls.at(-1)?.[0];
|
|
210
|
+
expect(latestVariableInputCall.converters.resolvePathFromValue('{{$context.data.updatedAt}}')).toEqual([
|
|
211
|
+
'$context',
|
|
212
|
+
'data',
|
|
213
|
+
'updatedAt',
|
|
214
|
+
]);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
@@ -45,6 +45,7 @@ interface FilterGroupProps {
|
|
|
45
45
|
value: Record<string, any>;
|
|
46
46
|
/** 自定义筛选项组件 */
|
|
47
47
|
FilterItem?: React.FC<FilterItemProps>;
|
|
48
|
+
disabled?: boolean;
|
|
48
49
|
closeIcon?: ReactNode;
|
|
49
50
|
/** 是否显示边框 */
|
|
50
51
|
showBorder?: boolean;
|
|
@@ -89,6 +90,7 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
89
90
|
const {
|
|
90
91
|
value = { logic: '$and', items: [] },
|
|
91
92
|
FilterItem,
|
|
93
|
+
disabled = false,
|
|
92
94
|
showBorder = false,
|
|
93
95
|
onRemove,
|
|
94
96
|
onChange,
|
|
@@ -120,11 +122,17 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
120
122
|
};
|
|
121
123
|
|
|
122
124
|
const handleLogicChange = (newLogic: '$and' | '$or') => {
|
|
125
|
+
if (disabled) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
123
128
|
value.logic = newLogic;
|
|
124
129
|
onChange?.(value);
|
|
125
130
|
};
|
|
126
131
|
|
|
127
132
|
const handleAddCondition = () => {
|
|
133
|
+
if (disabled) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
128
136
|
items.push({
|
|
129
137
|
path: '',
|
|
130
138
|
operator: '',
|
|
@@ -134,6 +142,9 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
134
142
|
};
|
|
135
143
|
|
|
136
144
|
const handleAddConditionGroup = () => {
|
|
145
|
+
if (disabled) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
137
148
|
items.push({
|
|
138
149
|
logic: '$and',
|
|
139
150
|
items: [],
|
|
@@ -142,6 +153,9 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
142
153
|
};
|
|
143
154
|
|
|
144
155
|
const handleRemoveItem = (index: number) => {
|
|
156
|
+
if (disabled) {
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
145
159
|
items.splice(index, 1);
|
|
146
160
|
onChange?.(value);
|
|
147
161
|
};
|
|
@@ -160,11 +174,12 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
160
174
|
<button
|
|
161
175
|
type="button"
|
|
162
176
|
aria-label="icon-close"
|
|
177
|
+
disabled={disabled}
|
|
163
178
|
style={{
|
|
164
179
|
position: 'absolute',
|
|
165
180
|
right: 10,
|
|
166
181
|
top: 10,
|
|
167
|
-
cursor: 'pointer',
|
|
182
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
168
183
|
background: 'transparent',
|
|
169
184
|
border: 0,
|
|
170
185
|
padding: 0,
|
|
@@ -182,6 +197,7 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
182
197
|
data-testid="filter-select-all-or-any"
|
|
183
198
|
style={{ width: 'auto' }}
|
|
184
199
|
value={logic}
|
|
200
|
+
disabled={disabled}
|
|
185
201
|
onChange={handleLogicChange}
|
|
186
202
|
>
|
|
187
203
|
<Select.Option value="$and">{t('All')}</Select.Option>
|
|
@@ -200,6 +216,7 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
200
216
|
key={getFilterItemKey(item)}
|
|
201
217
|
value={item}
|
|
202
218
|
FilterItem={FilterItem}
|
|
219
|
+
disabled={disabled}
|
|
203
220
|
showBorder={true}
|
|
204
221
|
onRemove={() => handleRemoveItem(index)}
|
|
205
222
|
onChange={(v) => {
|
|
@@ -221,11 +238,12 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
221
238
|
<button
|
|
222
239
|
type="button"
|
|
223
240
|
aria-label="icon-close"
|
|
241
|
+
disabled={disabled}
|
|
224
242
|
style={{
|
|
225
243
|
marginLeft: 8,
|
|
226
244
|
marginRight: 8,
|
|
227
245
|
flex: '0 0 auto',
|
|
228
|
-
cursor: 'pointer',
|
|
246
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
229
247
|
background: 'transparent',
|
|
230
248
|
border: 0,
|
|
231
249
|
padding: 0,
|
|
@@ -251,11 +269,12 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
251
269
|
<button
|
|
252
270
|
type="button"
|
|
253
271
|
aria-label="icon-close"
|
|
272
|
+
disabled={disabled}
|
|
254
273
|
style={{
|
|
255
274
|
marginLeft: 8,
|
|
256
275
|
marginRight: 8,
|
|
257
276
|
flex: '0 0 auto',
|
|
258
|
-
cursor: 'pointer',
|
|
277
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
259
278
|
background: 'transparent',
|
|
260
279
|
border: 0,
|
|
261
280
|
padding: 0,
|
|
@@ -277,11 +296,12 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
277
296
|
<button
|
|
278
297
|
type="button"
|
|
279
298
|
aria-label="icon-close"
|
|
299
|
+
disabled={disabled}
|
|
280
300
|
style={{
|
|
281
301
|
marginLeft: 8,
|
|
282
302
|
marginRight: 8,
|
|
283
303
|
flex: '0 0 auto',
|
|
284
|
-
cursor: 'pointer',
|
|
304
|
+
cursor: disabled ? 'not-allowed' : 'pointer',
|
|
285
305
|
background: 'transparent',
|
|
286
306
|
border: 0,
|
|
287
307
|
padding: 0,
|
|
@@ -298,7 +318,14 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
298
318
|
</div>
|
|
299
319
|
|
|
300
320
|
<Space size={16} style={{ marginTop: 8, marginBottom: 8 }}>
|
|
301
|
-
<Button
|
|
321
|
+
<Button
|
|
322
|
+
style={{ padding: 0 }}
|
|
323
|
+
type="link"
|
|
324
|
+
size="small"
|
|
325
|
+
icon={<PlusOutlined />}
|
|
326
|
+
onClick={handleAddCondition}
|
|
327
|
+
disabled={disabled}
|
|
328
|
+
>
|
|
302
329
|
{t('Add condition')}
|
|
303
330
|
</Button>
|
|
304
331
|
<Button
|
|
@@ -307,6 +334,7 @@ export const FilterGroup: FC<FilterGroupProps> = observer(
|
|
|
307
334
|
size="small"
|
|
308
335
|
icon={<PlusOutlined />}
|
|
309
336
|
onClick={handleAddConditionGroup}
|
|
337
|
+
disabled={disabled}
|
|
310
338
|
>
|
|
311
339
|
{t('Add condition group')}
|
|
312
340
|
</Button>
|
|
@@ -62,6 +62,51 @@ async function buildCollectionLeftMetaTreeLocal(ctx: any): Promise<MetaTreeNode[
|
|
|
62
62
|
return await resolve(subTree);
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
function buildCollectionFieldMetaNode(collection: any, path: string[]): MetaTreeNode | null {
|
|
66
|
+
if (!collection || !path.length) {
|
|
67
|
+
return null;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const normalizedPath = path[0] === 'collection' ? path.slice(1) : path;
|
|
71
|
+
if (!normalizedPath.length) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const parentTitles: string[] = [];
|
|
76
|
+
let currentCollection = collection;
|
|
77
|
+
let currentNode: MetaTreeNode | null = null;
|
|
78
|
+
|
|
79
|
+
for (const segment of normalizedPath) {
|
|
80
|
+
const field =
|
|
81
|
+
currentCollection?.getField?.(segment) ??
|
|
82
|
+
currentCollection?.getFields?.()?.find?.((candidate: any) => candidate?.name === segment);
|
|
83
|
+
if (!field) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const title = field.title || field.uiSchema?.title || field.name || segment;
|
|
88
|
+
currentNode = {
|
|
89
|
+
name: field.name || segment,
|
|
90
|
+
title,
|
|
91
|
+
type: field.type,
|
|
92
|
+
interface: field.interface,
|
|
93
|
+
uiSchema: field.uiSchema,
|
|
94
|
+
options: field.options,
|
|
95
|
+
paths: ['collection', ...normalizedPath.slice(0, parentTitles.length + 1)],
|
|
96
|
+
parentTitles: [...parentTitles],
|
|
97
|
+
} as MetaTreeNode;
|
|
98
|
+
|
|
99
|
+
if (field.targetCollection) {
|
|
100
|
+
parentTitles.push(title);
|
|
101
|
+
currentCollection = field.targetCollection;
|
|
102
|
+
} else {
|
|
103
|
+
currentCollection = null;
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return currentNode;
|
|
108
|
+
}
|
|
109
|
+
|
|
65
110
|
export interface VariableFilterItemValue {
|
|
66
111
|
path: string;
|
|
67
112
|
operator: string;
|
|
@@ -75,6 +120,7 @@ export interface VariableFilterItemProps {
|
|
|
75
120
|
/** 筛选条件值对象 */
|
|
76
121
|
value: VariableFilterItemValue;
|
|
77
122
|
model: FlowModel;
|
|
123
|
+
disabled?: boolean;
|
|
78
124
|
/**
|
|
79
125
|
* 是否启用右侧 VariableInput(变量或静态值二合一)。
|
|
80
126
|
* 默认 false:保持原有行为,右侧仅静态输入组件。
|
|
@@ -85,6 +131,10 @@ export interface VariableFilterItemProps {
|
|
|
85
131
|
* 默认使用整棵 ctx 的 metaTree:model.context.getPropertyMetaTree()
|
|
86
132
|
*/
|
|
87
133
|
rightMetaTree?: MetaTreeNode[] | (() => MetaTreeNode[] | Promise<MetaTreeNode[]>);
|
|
134
|
+
/**
|
|
135
|
+
* 右侧变量的领域转换器。常量和空值仍由 VariableFilterItem 处理,未匹配的值回退到 FlowEngine 默认转换器。
|
|
136
|
+
*/
|
|
137
|
+
rightVariableConverters?: Pick<Converters, 'resolvePathFromValue' | 'resolveValueFromPath'>;
|
|
88
138
|
ignoreFieldNames?: string[];
|
|
89
139
|
maxAssociationFieldDepth?: number;
|
|
90
140
|
}
|
|
@@ -265,11 +315,57 @@ function normalizeRightValueInput(input: unknown) {
|
|
|
265
315
|
return input;
|
|
266
316
|
}
|
|
267
317
|
|
|
318
|
+
function findMetaTreeNodeByPath(metaTree: MetaTreeNode[], targetPath: string[]): MetaTreeNode | null {
|
|
319
|
+
if (!Array.isArray(metaTree) || !targetPath.length) {
|
|
320
|
+
return null;
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
const traverseByName = (nodes: MetaTreeNode[], path: string[]): MetaTreeNode | null => {
|
|
324
|
+
if (!path.length) {
|
|
325
|
+
return null;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const [head, ...rest] = path;
|
|
329
|
+
const matched = nodes.find((node) => String(node.name) === String(head));
|
|
330
|
+
if (!matched) {
|
|
331
|
+
return null;
|
|
332
|
+
}
|
|
333
|
+
if (!rest.length) {
|
|
334
|
+
return matched;
|
|
335
|
+
}
|
|
336
|
+
if (!Array.isArray(matched.children)) {
|
|
337
|
+
return null;
|
|
338
|
+
}
|
|
339
|
+
return traverseByName(matched.children as MetaTreeNode[], rest);
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
const byNames = traverseByName(metaTree, targetPath);
|
|
343
|
+
if (byNames) {
|
|
344
|
+
return byNames;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const topNames = new Set(metaTree.map((node) => String(node.name)));
|
|
348
|
+
if (!topNames.has(String(targetPath[0]))) {
|
|
349
|
+
return traverseByName(metaTree, targetPath.slice(1));
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
268
355
|
/**
|
|
269
356
|
* 上下文筛选项组件
|
|
270
357
|
*/
|
|
271
358
|
export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
272
|
-
({
|
|
359
|
+
({
|
|
360
|
+
value,
|
|
361
|
+
model,
|
|
362
|
+
disabled = false,
|
|
363
|
+
rightAsVariable,
|
|
364
|
+
rightMetaTree,
|
|
365
|
+
rightVariableConverters,
|
|
366
|
+
ignoreFieldNames,
|
|
367
|
+
maxAssociationFieldDepth,
|
|
368
|
+
}) => {
|
|
273
369
|
// 使用 View 上下文,确保可访问 ctx.view 的异步子树
|
|
274
370
|
const ctx = useFlowViewContext();
|
|
275
371
|
const t = model.translate;
|
|
@@ -334,6 +430,9 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
334
430
|
// 处理左侧值变化(值由 converters 决定如何解析)
|
|
335
431
|
const handleLeftChange = useCallback(
|
|
336
432
|
(variableValue: string, meta?: MetaTreeNode) => {
|
|
433
|
+
if (disabled) {
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
337
436
|
const prevPath = value.path || '';
|
|
338
437
|
const nextPath = variableValue || '';
|
|
339
438
|
const changed = nextPath !== prevPath;
|
|
@@ -348,7 +447,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
348
447
|
setLeftMeta(meta);
|
|
349
448
|
}
|
|
350
449
|
},
|
|
351
|
-
[value],
|
|
450
|
+
[disabled, value],
|
|
352
451
|
);
|
|
353
452
|
|
|
354
453
|
// 自定义转换器来捕获 MetaTreeNode,并在选择左值时设置默认操作符
|
|
@@ -376,6 +475,9 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
376
475
|
// 处理操作符变化
|
|
377
476
|
const handleOperatorChange = useCallback(
|
|
378
477
|
(operatorValue: string) => {
|
|
478
|
+
if (disabled) {
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
379
481
|
value.operator = operatorValue;
|
|
380
482
|
const cur = operatorMetaList.find((op) => op.value === operatorValue);
|
|
381
483
|
if (cur?.noValue) {
|
|
@@ -386,7 +488,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
386
488
|
value.noValue = false;
|
|
387
489
|
}
|
|
388
490
|
},
|
|
389
|
-
[operatorMetaList, value],
|
|
491
|
+
[disabled, operatorMetaList, value],
|
|
390
492
|
);
|
|
391
493
|
|
|
392
494
|
// 使用公共静态输入渲染器(抽取到 utils)
|
|
@@ -464,9 +566,12 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
464
566
|
|
|
465
567
|
const setRightValue = useCallback(
|
|
466
568
|
(next: unknown) => {
|
|
569
|
+
if (disabled) {
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
467
572
|
value.value = normalizeRightValueInput(next) as VariableFilterItemValue['value'];
|
|
468
573
|
},
|
|
469
|
-
[value],
|
|
574
|
+
[disabled, value],
|
|
470
575
|
);
|
|
471
576
|
|
|
472
577
|
// 右侧静态输入(无变量模式)与右侧 VariableInput 的静态渲染组件,统一复用
|
|
@@ -520,6 +625,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
520
625
|
{...nextProps}
|
|
521
626
|
style={{ width: '100%', ...pickStyle(nextProps.style) }}
|
|
522
627
|
value={normalized}
|
|
628
|
+
disabled={disabled}
|
|
523
629
|
onChange={(vals: any) => setRightValue(vals)}
|
|
524
630
|
/>
|
|
525
631
|
</div>
|
|
@@ -529,10 +635,19 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
529
635
|
const Comp = staticInputRenderer;
|
|
530
636
|
return (
|
|
531
637
|
<div style={{ flex: '1 1 40%', minWidth: 160, maxWidth: '100%' }}>
|
|
532
|
-
<Comp value={rightValue} onChange={(val) => setRightValue(val)} />
|
|
638
|
+
<Comp value={rightValue} onChange={(val) => setRightValue(val)} disabled={disabled} />
|
|
533
639
|
</div>
|
|
534
640
|
);
|
|
535
|
-
}, [
|
|
641
|
+
}, [
|
|
642
|
+
disabled,
|
|
643
|
+
operator,
|
|
644
|
+
operatorMetaList,
|
|
645
|
+
rightValue,
|
|
646
|
+
staticInputRenderer,
|
|
647
|
+
model.context.app,
|
|
648
|
+
setRightValue,
|
|
649
|
+
enumOptions,
|
|
650
|
+
]);
|
|
536
651
|
|
|
537
652
|
// Null 占位组件(仿照 DefaultValue.tsx 的实现)
|
|
538
653
|
const NullComponent = useMemo(() => {
|
|
@@ -582,17 +697,19 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
582
697
|
const first = meta?.paths?.[0];
|
|
583
698
|
if (first === 'constant') return '';
|
|
584
699
|
if (first === 'null') return null;
|
|
585
|
-
return
|
|
700
|
+
return rightVariableConverters?.resolveValueFromPath?.(meta);
|
|
586
701
|
},
|
|
587
702
|
resolvePathFromValue: (val) => {
|
|
588
703
|
if (val === null) return ['null'];
|
|
589
|
-
//
|
|
590
|
-
const parsed =
|
|
704
|
+
// 变量表达式:优先使用领域转换器,再回退内置解析;其他静态值走 constant
|
|
705
|
+
const parsed =
|
|
706
|
+
rightVariableConverters?.resolvePathFromValue?.(val) ??
|
|
707
|
+
(typeof val === 'string' ? parseValueToPath(val) : undefined);
|
|
591
708
|
if (parsed) return parsed;
|
|
592
709
|
return ['constant'];
|
|
593
710
|
},
|
|
594
711
|
};
|
|
595
|
-
}, [NullComponent, staticInputRenderer]);
|
|
712
|
+
}, [NullComponent, rightVariableConverters, staticInputRenderer]);
|
|
596
713
|
|
|
597
714
|
// 为 2.0 左侧字段选择器追加“接口 filterable.children”定义(如 chinaRegion 的“省市区名称”子项),
|
|
598
715
|
// 以恢复 1.0 左侧子菜单的能力。
|
|
@@ -652,6 +769,34 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
652
769
|
};
|
|
653
770
|
}, [getFieldInterface, model, maxAssociationFieldDepth]);
|
|
654
771
|
|
|
772
|
+
useEffect(() => {
|
|
773
|
+
if (leftMeta || !value.path) {
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
let cancelled = false;
|
|
778
|
+
const restore = async () => {
|
|
779
|
+
const nodes = await enhancedMetaTree();
|
|
780
|
+
if (cancelled) {
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
const path = customConverters.resolvePathFromValue(value.path);
|
|
784
|
+
if (!path?.length) {
|
|
785
|
+
return;
|
|
786
|
+
}
|
|
787
|
+
const matched =
|
|
788
|
+
findMetaTreeNodeByPath(nodes, path) || buildCollectionFieldMetaNode(model.context.collection, path);
|
|
789
|
+
if (matched) {
|
|
790
|
+
setLeftMeta(matched);
|
|
791
|
+
}
|
|
792
|
+
};
|
|
793
|
+
|
|
794
|
+
restore();
|
|
795
|
+
return () => {
|
|
796
|
+
cancelled = true;
|
|
797
|
+
};
|
|
798
|
+
}, [customConverters, enhancedMetaTree, leftMeta, model.context.collection, value.path]);
|
|
799
|
+
|
|
655
800
|
return (
|
|
656
801
|
<Space wrap style={{ width: '100%' }}>
|
|
657
802
|
<VariableInput
|
|
@@ -659,6 +804,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
659
804
|
metaTree={enhancedMetaTree}
|
|
660
805
|
onChange={handleLeftChange}
|
|
661
806
|
converters={customConverters}
|
|
807
|
+
disabled={disabled}
|
|
662
808
|
showValueComponent={false}
|
|
663
809
|
style={{ flex: '1 1 40%', minWidth: 160, maxWidth: '100%' }}
|
|
664
810
|
onlyLeafSelectable={true}
|
|
@@ -670,6 +816,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
670
816
|
style={{ flex: '0 0 140px', minWidth: 120, maxWidth: '100%' }}
|
|
671
817
|
placeholder={t('Comparison')}
|
|
672
818
|
value={operator || undefined}
|
|
819
|
+
disabled={disabled}
|
|
673
820
|
onChange={handleOperatorChange}
|
|
674
821
|
>
|
|
675
822
|
{operatorOptions.map((op) => (
|
|
@@ -686,6 +833,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
686
833
|
onChange={(v) => setRightValue(v)}
|
|
687
834
|
metaTree={mergedRightMetaTree}
|
|
688
835
|
converters={rightConverters}
|
|
836
|
+
disabled={disabled}
|
|
689
837
|
showValueComponent
|
|
690
838
|
style={{ flex: '1 1 40%', minWidth: 160, maxWidth: '100%' }}
|
|
691
839
|
placeholder={t('Enter value')}
|
|
@@ -140,4 +140,49 @@ describe('FilterGroup closeIcon', () => {
|
|
|
140
140
|
expect(onChange).toHaveBeenCalledTimes(1);
|
|
141
141
|
expect(value.items[0].items).toHaveLength(1);
|
|
142
142
|
});
|
|
143
|
+
|
|
144
|
+
it('disables close and add actions when the filter group is disabled', () => {
|
|
145
|
+
const value = {
|
|
146
|
+
logic: '$and',
|
|
147
|
+
items: [
|
|
148
|
+
{
|
|
149
|
+
path: 'name',
|
|
150
|
+
operator: 'eq',
|
|
151
|
+
value: 'test',
|
|
152
|
+
},
|
|
153
|
+
],
|
|
154
|
+
};
|
|
155
|
+
const onRemove = vi.fn();
|
|
156
|
+
const onChange = vi.fn();
|
|
157
|
+
|
|
158
|
+
renderWithProviders(
|
|
159
|
+
<FilterGroup
|
|
160
|
+
value={value}
|
|
161
|
+
FilterItem={DummyFilterItem}
|
|
162
|
+
showBorder
|
|
163
|
+
onRemove={onRemove}
|
|
164
|
+
onChange={onChange}
|
|
165
|
+
disabled
|
|
166
|
+
/>,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const closeButtons = screen
|
|
170
|
+
.getAllByLabelText('icon-close')
|
|
171
|
+
.map((element) => element.closest('button'))
|
|
172
|
+
.filter(Boolean) as HTMLButtonElement[];
|
|
173
|
+
expect(closeButtons).toHaveLength(2);
|
|
174
|
+
expect(closeButtons[0]).toBeDisabled();
|
|
175
|
+
expect(closeButtons[1]).toBeDisabled();
|
|
176
|
+
expect(screen.getByText('Add condition').closest('button')).toBeDisabled();
|
|
177
|
+
expect(screen.getByText('Add condition group').closest('button')).toBeDisabled();
|
|
178
|
+
|
|
179
|
+
fireEvent.click(closeButtons[0]);
|
|
180
|
+
fireEvent.click(closeButtons[1]);
|
|
181
|
+
fireEvent.click(screen.getByText('Add condition').closest('button') as HTMLButtonElement);
|
|
182
|
+
fireEvent.click(screen.getByText('Add condition group').closest('button') as HTMLButtonElement);
|
|
183
|
+
|
|
184
|
+
expect(onRemove).not.toHaveBeenCalled();
|
|
185
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
186
|
+
expect(value.items).toHaveLength(1);
|
|
187
|
+
});
|
|
143
188
|
});
|