@nocobase/client-v2 2.3.0-alpha.1 → 2.3.0-beta.10
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 +12 -0
- package/es/components/form/ScanInput/useCodeScanner.d.ts +12 -2
- package/es/components/form/ScanInput/zxingWasmDecoder.d.ts +9 -0
- package/es/flow/actions/linkageRules.d.ts +25 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +6 -30
- package/es/flow/components/FlowRoute.d.ts +2 -2
- package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +24 -0
- package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +32 -0
- package/es/flow/components/field-value-variable/dateValue.d.ts +36 -0
- package/es/flow/components/field-value-variable/index.d.ts +11 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +7 -0
- package/es/flow/components/placeholders/BlockPlaceholder.d.ts +1 -0
- package/es/flow/internal/registerDeviceTypeContext.d.ts +10 -0
- package/es/flow/internal/utils/operatorSchemaHelper.d.ts +2 -2
- package/es/flow/models/actions/UpdateRecordActionUtils.d.ts +8 -2
- 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/CollectionBlockModel.d.ts +3 -0
- package/es/flow/models/blocks/details/DetailsBlockModel.d.ts +3 -0
- package/es/flow/models/blocks/filter-form/FilterFormGridModel.d.ts +1 -0
- package/es/flow/models/blocks/filter-form/FilterFormSubmitActionModel.d.ts +3 -1
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +7 -0
- package/es/flow/models/blocks/form/FormGridModel.d.ts +6 -0
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +1 -0
- package/es/flow/models/blocks/table/JSColumnModel.d.ts +2 -0
- package/es/flow/models/blocks/table/TableBlockModel.d.ts +1 -0
- package/es/flow/models/blocks/table/TableColumnModel.d.ts +12 -0
- package/es/flow/models/blocks/table/utils.d.ts +1 -0
- package/es/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.d.ts +2 -1
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +0 -17
- package/es/flow/models/fields/AssociationFieldModel/recordSelectShared.d.ts +41 -0
- package/es/flow/models/fields/DateTimeFieldModel/dateLimit.d.ts +15 -7
- package/es/flow-compat/passwordUtils.d.ts +1 -1
- package/es/index.d.ts +1 -0
- package/es/index.mjs +148 -145
- package/es/ui-operation/index.d.ts +15 -0
- package/es/ui-operation/ui-operation-codec.d.ts +10 -0
- package/lib/index.js +170 -167
- package/lib/locale/languageCodes.js +1 -0
- package/package.json +10 -8
- package/src/APIClient.ts +92 -0
- package/src/Application.tsx +3 -1
- package/src/__tests__/APIClient.test.tsx +58 -0
- package/src/__tests__/nocobase-buildin-plugin-auth.test.tsx +7 -2
- package/src/__tests__/settings-center.test.tsx +270 -6
- package/src/acl/ACLProvider.tsx +2 -0
- package/src/acl/__tests__/ACLProvider.test.tsx +92 -0
- package/src/collection-manager/__tests__/field-configure.test.ts +52 -0
- package/src/collection-manager/field-configure.ts +2 -2
- package/src/components/form/ScanInput/CodeScanner.tsx +64 -35
- package/src/components/form/ScanInput/__tests__/CodeScanner.test.tsx +107 -0
- package/src/components/form/ScanInput/__tests__/useCodeScanner.test.tsx +294 -24
- package/src/components/form/ScanInput/__tests__/zxingWasmDecoder.test.ts +78 -0
- package/src/components/form/ScanInput/useCodeScanner.ts +281 -23
- package/src/components/form/ScanInput/zxingWasmDecoder.ts +64 -0
- package/src/components/form/filter/CollectionFilterItem.tsx +4 -2
- package/src/components/form/filter/__tests__/CollectionFilterItem.test.tsx +17 -0
- package/src/components/form/table/Table.tsx +81 -6
- package/src/components/form/table/__tests__/Table.columnWidth.test.tsx +433 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +126 -8
- 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.actionStates.test.ts +33 -0
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +72 -0
- package/src/flow/actions/linkageRules.tsx +59 -16
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +78 -59
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +228 -0
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +42 -23
- package/src/flow/common/Markdown/style.ts +4 -0
- package/src/flow/components/BlockItemCard.tsx +2 -2
- package/src/flow/components/FieldAssignValueInput.tsx +42 -621
- package/src/flow/components/FlowRoute.tsx +71 -29
- package/src/flow/components/field-value-variable/DateVariableEditor.tsx +181 -0
- package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +335 -0
- package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +389 -0
- package/src/flow/components/field-value-variable/dateValue.ts +223 -0
- package/src/flow/components/field-value-variable/index.ts +12 -0
- package/src/flow/components/filter/VariableFilterItem.tsx +23 -6
- package/src/flow/components/filter/__tests__/VariableFilterItem.rightMetaTree.test.tsx +158 -0
- package/src/flow/components/filter/__tests__/VariableFilterItem.test.tsx +125 -0
- package/src/flow/components/placeholders/BlockPlaceholder.tsx +70 -23
- package/src/flow/components/placeholders/__tests__/BlockPlaceholder.test.tsx +57 -7
- package/src/flow/index.ts +2 -0
- package/src/flow/internal/components/Markdown/DisplayMarkdown.tsx +14 -9
- package/src/flow/internal/components/Markdown/__tests__/DisplayMarkdown.test.tsx +46 -0
- package/src/flow/internal/registerDeviceTypeContext.ts +39 -0
- package/src/flow/internal/utils/operatorSchemaHelper.ts +3 -2
- package/src/flow/models/actions/UpdateRecordActionModel.tsx +18 -1
- package/src/flow/models/actions/UpdateRecordActionUtils.ts +18 -6
- package/src/flow/models/actions/__tests__/UpdateRecordActionModel.test.ts +76 -4
- 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 +38 -3
- package/src/flow/models/base/GridModel.tsx +0 -1
- package/src/flow/models/base/PageModel/PageTabModel.tsx +67 -17
- package/src/flow/models/base/PageModel/__tests__/PageTabModel.test.ts +509 -12
- 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/base/__tests__/CollectionBlockModel.initialBeforeRenderRefresh.test.ts +125 -9
- package/src/flow/models/blocks/assign-form/AssignFormGridModel.tsx +22 -1
- package/src/flow/models/blocks/assign-form/AssignFormItemModel.tsx +36 -50
- package/src/flow/models/blocks/assign-form/__tests__/assignFieldValuesFlow.editor.test.tsx +140 -0
- package/src/flow/models/blocks/details/DetailsBlockModel.tsx +8 -3
- package/src/flow/models/blocks/details/__tests__/DetailsBlockModel.initialPaginationChangeRefresh.test.ts +32 -1
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +27 -3
- package/src/flow/models/blocks/filter-form/FilterFormGridModel.tsx +36 -5
- package/src/flow/models/blocks/filter-form/FilterFormItemModel.tsx +129 -14
- package/src/flow/models/blocks/filter-form/FilterFormSubmitActionModel.tsx +57 -4
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.onModelCreated.test.ts +174 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormGridModel.toggleFormFieldsCollapse.test.ts +29 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.defineChildren.test.ts +359 -1
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormItemModel.getFilterValue.test.ts +72 -0
- package/src/flow/models/blocks/filter-form/__tests__/FilterFormSubmitActionModel.lifecycle.test.ts +90 -0
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +32 -4
- package/src/flow/models/blocks/filter-form/fields/FieldComponentProps.tsx +1 -1
- package/src/flow/models/blocks/filter-form/fields/__tests__/FieldComponentProps.options.test.tsx +61 -0
- package/src/flow/models/blocks/filter-manager/FilterManager.ts +5 -0
- package/src/flow/models/blocks/filter-manager/__tests__/FilterManager.test.ts +40 -0
- package/src/flow/models/blocks/form/FormBlockModel.tsx +73 -5
- package/src/flow/models/blocks/form/FormGridModel.tsx +4 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +153 -2
- package/src/flow/models/blocks/form/__tests__/popupLinkage.test.tsx +175 -0
- package/src/flow/models/blocks/form/__tests__/runJsFormSubmit.test.ts +131 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +435 -0
- package/src/flow/models/blocks/form/value-runtime/rules.ts +67 -14
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +43 -19
- package/src/flow/models/blocks/js-block/JSBlock.tsx +1 -1
- package/src/flow/models/blocks/table/JSColumnModel.tsx +10 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +28 -2
- package/src/flow/models/blocks/table/TableColumnModel.tsx +48 -9
- package/src/flow/models/blocks/table/__tests__/JSColumnModel.test.tsx +52 -5
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.filterReset.test.ts +78 -0
- 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/blocks/table/utils.ts +7 -0
- package/src/flow/models/fields/AssociationFieldModel/CascadeSelectFieldModel.tsx +33 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +8 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/__tests__/popupContext.test.ts +120 -0
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/actions/PopupSubTableEditActionModel.tsx +10 -2
- package/src/flow/models/fields/AssociationFieldModel/RecordPickerFieldModel.tsx +29 -3
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +9 -136
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +191 -3
- package/src/flow/models/fields/AssociationFieldModel/recordSelectShared.tsx +156 -0
- 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/DisplayAssociationField/DisplaySubTableFieldModel.tsx +42 -7
- package/src/flow/models/fields/DisplayAssociationField/__tests__/DisplaySubTableFieldModel.test.tsx +351 -0
- package/src/flow/models/fields/DisplayHtmlFieldModel.tsx +2 -1
- package/src/flow/models/fields/DisplayNumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/JsonFieldModel.tsx +31 -8
- package/src/flow/models/fields/NumberFieldModel.tsx +1 -1
- package/src/flow/models/fields/RichTextFieldModel/__tests__/RichTextFieldModel.test.tsx +74 -0
- package/src/flow/models/fields/RichTextFieldModel/index.tsx +27 -6
- package/src/flow/models/fields/__tests__/DisplayNumberFieldModel.test.ts +33 -0
- package/src/flow/models/fields/__tests__/JsonFieldModel.test.ts +82 -0
- package/src/flow/models/fields/__tests__/NumberFieldModel.test.tsx +47 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +6 -0
- package/src/flow/models/fields/mobile-components/MobileSelect.tsx +27 -2
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +139 -13
- package/src/flow/models/topbar/TopbarActionModel.tsx +5 -5
- package/src/flow/utils/dataScopeFormValueClear.ts +4 -3
- package/src/index.ts +1 -0
- package/src/layout-manager/LayoutContentRoute.tsx +2 -1
- package/src/layout-manager/LayoutRoute.tsx +2 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +87 -1
- package/src/locale/languageCodes.ts +1 -0
- package/src/settings-center/AdminSettingsLayout.tsx +23 -2
- package/src/ui-operation/index.ts +33 -0
- package/src/ui-operation/ui-operation-codec.ts +54 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
11
|
+
import { PopupSubTableFieldModel } from '../PopupSubTableFieldModel';
|
|
12
|
+
import { PopupSubTableEditActionModel } from '../actions/PopupSubTableEditActionModel';
|
|
13
|
+
|
|
14
|
+
function getOpenViewHandler(modelClass: typeof PopupSubTableFieldModel | typeof PopupSubTableEditActionModel) {
|
|
15
|
+
const flow = modelClass.globalFlowRegistry.getFlow('popupSettings');
|
|
16
|
+
const step = flow?.getStep('openView');
|
|
17
|
+
const handler = step?.serialize().handler;
|
|
18
|
+
|
|
19
|
+
if (!handler) {
|
|
20
|
+
throw new Error('popupSettings.openView handler is not registered');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return handler;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function createContext(record?: Record<string, unknown>, parentRecord: Record<string, unknown> = { id: 1 }) {
|
|
27
|
+
const open = vi.fn();
|
|
28
|
+
const sourceCollection = {
|
|
29
|
+
getFilterByTK: vi.fn((sourceRecord: Record<string, unknown>) => sourceRecord.id),
|
|
30
|
+
};
|
|
31
|
+
const model = {
|
|
32
|
+
uid: 'popup-subtable-uid',
|
|
33
|
+
props: {
|
|
34
|
+
value: record ? [record] : [],
|
|
35
|
+
},
|
|
36
|
+
context: {
|
|
37
|
+
inputArgs: {},
|
|
38
|
+
},
|
|
39
|
+
flowEngine: {
|
|
40
|
+
context: {
|
|
41
|
+
themeToken: {
|
|
42
|
+
colorBgLayout: '#fff',
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
open,
|
|
50
|
+
context: {
|
|
51
|
+
inputArgs: {},
|
|
52
|
+
item: {
|
|
53
|
+
value: parentRecord,
|
|
54
|
+
},
|
|
55
|
+
view: {
|
|
56
|
+
inputArgs: {
|
|
57
|
+
viewUid: 'parent-popup-uid',
|
|
58
|
+
openerUids: ['root-page-uid'],
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
viewer: { open },
|
|
62
|
+
layoutContentElement: {},
|
|
63
|
+
model,
|
|
64
|
+
associationModel: model,
|
|
65
|
+
collection: {
|
|
66
|
+
dataSourceKey: 'main',
|
|
67
|
+
filterTargetKey: 'id',
|
|
68
|
+
},
|
|
69
|
+
collectionField: {
|
|
70
|
+
target: 'roles',
|
|
71
|
+
resourceName: 'users.roles',
|
|
72
|
+
collection: sourceCollection,
|
|
73
|
+
},
|
|
74
|
+
record,
|
|
75
|
+
getFormValues: () => ({ id: 1 }),
|
|
76
|
+
getPropertyOptions: () => undefined,
|
|
77
|
+
},
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
describe('PopupSubTable popup context', () => {
|
|
82
|
+
it('passes popup and parent record context to the add-new popup', () => {
|
|
83
|
+
const { context, open } = createContext();
|
|
84
|
+
const handler = getOpenViewHandler(PopupSubTableFieldModel);
|
|
85
|
+
|
|
86
|
+
handler(context, { mode: 'drawer', size: 'medium' });
|
|
87
|
+
|
|
88
|
+
expect(open).toHaveBeenCalledOnce();
|
|
89
|
+
expect(open.mock.calls[0][0].inputArgs).toMatchObject({
|
|
90
|
+
openerUids: ['root-page-uid', 'parent-popup-uid'],
|
|
91
|
+
associationName: 'users.roles',
|
|
92
|
+
sourceId: 1,
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('passes popup and parent record context to the edit popup', () => {
|
|
97
|
+
const { context, open } = createContext({ id: 2, name: 'Member' });
|
|
98
|
+
const handler = getOpenViewHandler(PopupSubTableEditActionModel);
|
|
99
|
+
|
|
100
|
+
handler(context, { mode: 'dialog', size: 'medium' });
|
|
101
|
+
|
|
102
|
+
expect(open).toHaveBeenCalledOnce();
|
|
103
|
+
expect(open.mock.calls[0][0].inputArgs).toMatchObject({
|
|
104
|
+
openerUids: ['root-page-uid', 'parent-popup-uid'],
|
|
105
|
+
associationName: 'users.roles',
|
|
106
|
+
sourceId: 1,
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('does not expose a parent record reference before the parent record is persisted', () => {
|
|
111
|
+
const { context, open } = createContext(undefined, { nickname: 'Draft user' });
|
|
112
|
+
const handler = getOpenViewHandler(PopupSubTableFieldModel);
|
|
113
|
+
|
|
114
|
+
handler(context, { mode: 'drawer', size: 'medium' });
|
|
115
|
+
|
|
116
|
+
expect(open).toHaveBeenCalledOnce();
|
|
117
|
+
expect(open.mock.calls[0][0].inputArgs).not.toHaveProperty('associationName');
|
|
118
|
+
expect(open.mock.calls[0][0].inputArgs).not.toHaveProperty('sourceId');
|
|
119
|
+
});
|
|
120
|
+
});
|
|
@@ -17,6 +17,7 @@ import { capitalize } from 'lodash';
|
|
|
17
17
|
import '../../../../base/ActionModel';
|
|
18
18
|
import { ActionModel, ActionWithoutPermission } from '../../../../base/ActionModelCore';
|
|
19
19
|
import { SkeletonFallback } from '../../../../../components/SkeletonFallback';
|
|
20
|
+
import { buildOpenerUids } from '../../recordSelectShared';
|
|
20
21
|
import { bindPopupSubTableBeforeClose } from './popupSubTableBeforeClose';
|
|
21
22
|
|
|
22
23
|
function FieldWithoutPermissionPlaceholder({ targetModel, children }) {
|
|
@@ -29,7 +30,7 @@ function FieldWithoutPermissionPlaceholder({ targetModel, children }) {
|
|
|
29
30
|
const dataSourcePrefix = `${t(dataSource.displayName || dataSource.key)} > `;
|
|
30
31
|
const collectionPrefix = collection ? `${t(collection.title) || collection.name || collection.tableName} > ` : '';
|
|
31
32
|
return `${dataSourcePrefix}${collectionPrefix}${name}`;
|
|
32
|
-
}, []);
|
|
33
|
+
}, [collection, dataSource.displayName, dataSource.key, name, t]);
|
|
33
34
|
const { actionName } = fieldModel.forbidden || {};
|
|
34
35
|
const messageValue = useMemo(() => {
|
|
35
36
|
return t(
|
|
@@ -39,7 +40,7 @@ function FieldWithoutPermissionPlaceholder({ targetModel, children }) {
|
|
|
39
40
|
actionName: t(capitalize(actionName)),
|
|
40
41
|
},
|
|
41
42
|
).replaceAll('>', '>');
|
|
42
|
-
}, [nameValue, t]);
|
|
43
|
+
}, [actionName, nameValue, t]);
|
|
43
44
|
return <Tooltip title={messageValue}>{children}</Tooltip>;
|
|
44
45
|
}
|
|
45
46
|
|
|
@@ -347,6 +348,11 @@ PopupSubTableEditActionModel.registerFlow({
|
|
|
347
348
|
return undefined;
|
|
348
349
|
}
|
|
349
350
|
})();
|
|
351
|
+
const associationName = ctx.collectionField?.resourceName;
|
|
352
|
+
const sourceId = parentItem?.value
|
|
353
|
+
? ctx.collectionField?.collection?.getFilterByTK?.(parentItem.value)
|
|
354
|
+
: undefined;
|
|
355
|
+
const openerUids = buildOpenerUids(ctx, ctx.inputArgs);
|
|
350
356
|
ctx.viewer.open({
|
|
351
357
|
type: openMode,
|
|
352
358
|
width: sizeToWidthMap[openMode][size],
|
|
@@ -357,6 +363,7 @@ PopupSubTableEditActionModel.registerFlow({
|
|
|
357
363
|
scene: 'subForm',
|
|
358
364
|
dataSourceKey: ctx.collection.dataSourceKey,
|
|
359
365
|
collectionName: ctx.collectionField?.target,
|
|
366
|
+
...(associationName && sourceId != null ? { associationName, sourceId } : {}),
|
|
360
367
|
collectionField: ctx.collectionField,
|
|
361
368
|
record: ctx.record,
|
|
362
369
|
parentItem,
|
|
@@ -364,6 +371,7 @@ PopupSubTableEditActionModel.registerFlow({
|
|
|
364
371
|
parentItemResolver: parentItemOptions?.resolveOnServer,
|
|
365
372
|
itemIndex,
|
|
366
373
|
itemLength,
|
|
374
|
+
openerUids,
|
|
367
375
|
},
|
|
368
376
|
content: () => <EditFormContent model={ctx.model} />,
|
|
369
377
|
styles: {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import {
|
|
11
11
|
CollectionField,
|
|
12
|
+
MultiRecordResource,
|
|
12
13
|
EditableItemModel,
|
|
13
14
|
tExpr,
|
|
14
15
|
FlowModel,
|
|
@@ -31,7 +32,13 @@ import {
|
|
|
31
32
|
createRootItemChain,
|
|
32
33
|
type ItemChain,
|
|
33
34
|
} from './itemChain';
|
|
34
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
buildOpenerUids,
|
|
37
|
+
LabelByField,
|
|
38
|
+
normalizeAssociationFieldNames,
|
|
39
|
+
type AssociationFieldNames,
|
|
40
|
+
useAssociationValueHydration,
|
|
41
|
+
} from './recordSelectShared';
|
|
35
42
|
|
|
36
43
|
const MULTIPLE_ASSOCIATION_TYPES = ['belongsToMany', 'hasMany', 'belongsToArray'];
|
|
37
44
|
|
|
@@ -325,22 +332,31 @@ function RecordPickerField(props) {
|
|
|
325
332
|
const { fieldNames, onClick, disabled } = props;
|
|
326
333
|
const ctx = useFlowContext();
|
|
327
334
|
const allowMultiple = canRecordPickerSelectMultiple(ctx.collectionField, props.allowMultiple);
|
|
335
|
+
const normalizedFieldNames = normalizeAssociationFieldNames(fieldNames, ctx.collectionField?.targetCollection);
|
|
328
336
|
useEffect(() => {
|
|
329
337
|
ctx.model.selectedRows.value = props.value;
|
|
330
338
|
}, [ctx.model.selectedRows, props.value]);
|
|
339
|
+
useAssociationValueHydration({
|
|
340
|
+
model: ctx.model,
|
|
341
|
+
value: props.value,
|
|
342
|
+
isMultiple: allowMultiple,
|
|
343
|
+
fieldNames: normalizedFieldNames,
|
|
344
|
+
onChange: props.onChange,
|
|
345
|
+
});
|
|
331
346
|
|
|
332
347
|
return (
|
|
333
348
|
<Select
|
|
334
349
|
{...props}
|
|
350
|
+
fieldNames={normalizedFieldNames}
|
|
335
351
|
open={false}
|
|
336
352
|
onClick={(e) => {
|
|
337
353
|
if (!disabled) {
|
|
338
354
|
onClick(e);
|
|
339
355
|
}
|
|
340
356
|
}}
|
|
341
|
-
value={normalizeRecordPickerValue(props.value,
|
|
357
|
+
value={normalizeRecordPickerValue(props.value, normalizedFieldNames, allowMultiple)}
|
|
342
358
|
labelRender={(item) => {
|
|
343
|
-
return <LabelByField option={item} fieldNames={
|
|
359
|
+
return <LabelByField option={item} fieldNames={normalizedFieldNames} />;
|
|
344
360
|
}}
|
|
345
361
|
labelInValue
|
|
346
362
|
mode={allowMultiple ? 'multiple' : undefined}
|
|
@@ -400,6 +416,7 @@ function RecordPickerField(props) {
|
|
|
400
416
|
}
|
|
401
417
|
|
|
402
418
|
export class RecordPickerFieldModel extends FieldModel {
|
|
419
|
+
declare resource: MultiRecordResource;
|
|
403
420
|
selectedRows = observable.ref([]);
|
|
404
421
|
_closeView;
|
|
405
422
|
selectBlockModel;
|
|
@@ -564,6 +581,15 @@ RecordPickerFieldModel.registerFlow({
|
|
|
564
581
|
title: tExpr('RecordPicker settings'),
|
|
565
582
|
sort: 200,
|
|
566
583
|
steps: {
|
|
584
|
+
init: {
|
|
585
|
+
handler(ctx) {
|
|
586
|
+
const { target, dataSourceKey } = ctx.model.collectionField;
|
|
587
|
+
const resource = ctx.createResource(MultiRecordResource);
|
|
588
|
+
resource.setDataSourceKey(dataSourceKey);
|
|
589
|
+
resource.setResourceName(target);
|
|
590
|
+
ctx.model.resource = resource;
|
|
591
|
+
},
|
|
592
|
+
},
|
|
567
593
|
fieldNames: {
|
|
568
594
|
use: 'titleField',
|
|
569
595
|
},
|
|
@@ -22,7 +22,7 @@ import { css } from '@emotion/css';
|
|
|
22
22
|
import { debounce } from 'lodash';
|
|
23
23
|
import { useRequest } from 'ahooks';
|
|
24
24
|
import { PlusOutlined } from '@ant-design/icons';
|
|
25
|
-
import React, {
|
|
25
|
+
import React, { useMemo, useRef, useState } from 'react';
|
|
26
26
|
import { useTranslation } from 'react-i18next';
|
|
27
27
|
import { SkeletonFallback } from '../../../components/SkeletonFallback';
|
|
28
28
|
import { AssociationFieldModel } from './AssociationFieldModel';
|
|
@@ -32,6 +32,7 @@ import {
|
|
|
32
32
|
normalizeAssociationFieldNames,
|
|
33
33
|
resolveOptions,
|
|
34
34
|
toSelectValue,
|
|
35
|
+
useAssociationValueHydration,
|
|
35
36
|
type AssociationOption,
|
|
36
37
|
type LazySelectProps,
|
|
37
38
|
} from './recordSelectShared';
|
|
@@ -41,81 +42,6 @@ import { ActionWithoutPermission } from '../../base/ActionModel';
|
|
|
41
42
|
import { EditFormModel } from '../../blocks';
|
|
42
43
|
import { hasAncestorModel } from './recordSelectSettingsUtils';
|
|
43
44
|
|
|
44
|
-
function isPlainObject(val: unknown): val is Record<string, any> {
|
|
45
|
-
return !!val && typeof val === 'object' && !Array.isArray(val);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
type HydrateStatus = 'pending' | 'done';
|
|
49
|
-
|
|
50
|
-
type HydrationCandidate = {
|
|
51
|
-
item: AssociationOption;
|
|
52
|
-
tk: any;
|
|
53
|
-
tkKey: string;
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export function collectAssociationHydrationCandidates(options: {
|
|
57
|
-
value: LazySelectProps['value'];
|
|
58
|
-
isMultiple: boolean;
|
|
59
|
-
valueKey: string;
|
|
60
|
-
labelKey: string;
|
|
61
|
-
statusMap: Map<string, HydrateStatus>;
|
|
62
|
-
}): HydrationCandidate[] {
|
|
63
|
-
const { value, isMultiple, valueKey, labelKey, statusMap } = options;
|
|
64
|
-
const list = isMultiple ? (Array.isArray(value) ? value : []) : value != null ? [value] : [];
|
|
65
|
-
|
|
66
|
-
const activeTkKeys = new Set<string>();
|
|
67
|
-
for (const item of list) {
|
|
68
|
-
if (!isPlainObject(item)) continue;
|
|
69
|
-
const tk = item?.[valueKey];
|
|
70
|
-
if (tk == null) continue;
|
|
71
|
-
const tkKey = typeof tk === 'object' ? JSON.stringify(tk) : String(tk);
|
|
72
|
-
if (!tkKey) continue;
|
|
73
|
-
activeTkKeys.add(tkKey);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
for (const key of Array.from(statusMap.keys())) {
|
|
77
|
-
if (!activeTkKeys.has(key)) {
|
|
78
|
-
statusMap.delete(key);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const candidates: HydrationCandidate[] = [];
|
|
83
|
-
for (const item of list) {
|
|
84
|
-
if (!isPlainObject(item)) continue;
|
|
85
|
-
const tk = item?.[valueKey];
|
|
86
|
-
if (tk == null) continue;
|
|
87
|
-
if (item?.[labelKey] != null) continue;
|
|
88
|
-
|
|
89
|
-
const tkKey = typeof tk === 'object' ? JSON.stringify(tk) : String(tk);
|
|
90
|
-
if (!tkKey) continue;
|
|
91
|
-
|
|
92
|
-
const status = statusMap.get(tkKey);
|
|
93
|
-
if (status === 'pending' || status === 'done') continue;
|
|
94
|
-
|
|
95
|
-
statusMap.set(tkKey, 'pending');
|
|
96
|
-
candidates.push({ item, tk, tkKey });
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
return candidates;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
export function getAssociationHydrationNamePath(model: any) {
|
|
103
|
-
return model?.context?.fieldPathArray ?? model?.context?.fieldPath ?? model?.props?.name;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export function getAssociationHydrationSetterContext(model: any) {
|
|
107
|
-
if (typeof model?.context?.setFormValue === 'function') {
|
|
108
|
-
return model.context;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return model?.context?.blockModel?.context;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function markAssociationHydrationDone(statusMap: Map<string, HydrateStatus>, tkKey: string | null | undefined) {
|
|
115
|
-
if (!tkKey) return;
|
|
116
|
-
statusMap.set(tkKey, 'done');
|
|
117
|
-
}
|
|
118
|
-
|
|
119
45
|
function RemoteModelRenderer({ options }) {
|
|
120
46
|
const ctx = useFlowViewContext();
|
|
121
47
|
const { data, loading } = useRequest(
|
|
@@ -281,66 +207,13 @@ const LazySelect = (props: Readonly<LazySelectProps>) => {
|
|
|
281
207
|
};
|
|
282
208
|
const isConfigMode = !!model.context.flowSettingsEnabled;
|
|
283
209
|
const { t } = useTranslation();
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
if (!valueKey || !labelKey) return;
|
|
292
|
-
|
|
293
|
-
const current = value;
|
|
294
|
-
const candidates = collectAssociationHydrationCandidates({
|
|
295
|
-
value: current,
|
|
296
|
-
isMultiple,
|
|
297
|
-
valueKey,
|
|
298
|
-
labelKey,
|
|
299
|
-
statusMap: hydrateStatusRef.current,
|
|
300
|
-
});
|
|
301
|
-
if (!candidates.length) return;
|
|
302
|
-
|
|
303
|
-
const namePath = getAssociationHydrationNamePath(model);
|
|
304
|
-
const setterCtx: any = getAssociationHydrationSetterContext(model);
|
|
305
|
-
|
|
306
|
-
candidates.forEach(({ item, tk, tkKey }) => {
|
|
307
|
-
void (async () => {
|
|
308
|
-
try {
|
|
309
|
-
const record = await resource.get(tk);
|
|
310
|
-
if (!record || typeof record !== 'object') {
|
|
311
|
-
return;
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
const merge = { ...(item as any), ...(record as any) };
|
|
315
|
-
if (merge?.[labelKey] == null) {
|
|
316
|
-
return;
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const nextValue = isMultiple
|
|
320
|
-
? (Array.isArray(current) ? current : []).map((v: any) => {
|
|
321
|
-
if (!isPlainObject(v)) return v;
|
|
322
|
-
return v?.[valueKey] === tk ? merge : v;
|
|
323
|
-
})
|
|
324
|
-
: merge;
|
|
325
|
-
|
|
326
|
-
if (setterCtx && typeof setterCtx.setFormValue === 'function' && namePath != null) {
|
|
327
|
-
await setterCtx.setFormValue(namePath, nextValue, {
|
|
328
|
-
source: 'default',
|
|
329
|
-
markExplicit: false,
|
|
330
|
-
triggerEvent: false,
|
|
331
|
-
});
|
|
332
|
-
return;
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
onChange(nextValue as any);
|
|
336
|
-
} catch (error) {
|
|
337
|
-
// ignore
|
|
338
|
-
} finally {
|
|
339
|
-
markAssociationHydrationDone(hydrateStatusRef.current, tkKey);
|
|
340
|
-
}
|
|
341
|
-
})();
|
|
342
|
-
});
|
|
343
|
-
}, [isMultiple, model, normalizedFieldNames.label, normalizedFieldNames.value, onChange, value]);
|
|
210
|
+
useAssociationValueHydration({
|
|
211
|
+
model,
|
|
212
|
+
value,
|
|
213
|
+
isMultiple,
|
|
214
|
+
fieldNames: normalizedFieldNames,
|
|
215
|
+
onChange,
|
|
216
|
+
});
|
|
344
217
|
|
|
345
218
|
const QuickAddContent = ({ searchText }) => {
|
|
346
219
|
return (
|
|
@@ -7,7 +7,9 @@
|
|
|
7
7
|
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import
|
|
10
|
+
import React from 'react';
|
|
11
|
+
import { FlowContext, FlowEngine, FlowModel, FlowModelProvider } from '@nocobase/flow-engine';
|
|
12
|
+
import { render, renderHook, waitFor } from '@testing-library/react';
|
|
11
13
|
import { describe, expect, it, vi } from 'vitest';
|
|
12
14
|
import {
|
|
13
15
|
buildCurrentItemTitle,
|
|
@@ -21,6 +23,7 @@ import {
|
|
|
21
23
|
} from '../itemChain';
|
|
22
24
|
import { injectRecordPickerPopupContext } from '@nocobase/client-v2';
|
|
23
25
|
import {
|
|
26
|
+
RecordPickerFieldModel,
|
|
24
27
|
canRecordPickerSelectMultiple,
|
|
25
28
|
getRecordPickerClearedValue,
|
|
26
29
|
getRecordPickerEmptyValue,
|
|
@@ -28,8 +31,12 @@ import {
|
|
|
28
31
|
normalizeRecordPickerValue,
|
|
29
32
|
shouldClearRecordPickerValueOnMultipleChange,
|
|
30
33
|
} from '../RecordPickerFieldModel';
|
|
31
|
-
import {
|
|
32
|
-
|
|
34
|
+
import {
|
|
35
|
+
collectAssociationHydrationCandidates,
|
|
36
|
+
useAssociationValueHydration,
|
|
37
|
+
getAssociationHydrationNamePath,
|
|
38
|
+
getAssociationHydrationSetterContext,
|
|
39
|
+
} from '../recordSelectShared';
|
|
33
40
|
function createMockCollection() {
|
|
34
41
|
return {
|
|
35
42
|
name: 'users',
|
|
@@ -78,6 +85,141 @@ describe('RecordPickerFieldModel item context', () => {
|
|
|
78
85
|
expect(normalizeRecordPickerValue(undefined, fieldNames, false)).toBeUndefined();
|
|
79
86
|
});
|
|
80
87
|
|
|
88
|
+
it('renders existing values before field names are initialized', () => {
|
|
89
|
+
const engine = new FlowEngine();
|
|
90
|
+
engine.registerModels({ RecordPickerFieldModel });
|
|
91
|
+
const field = engine.createModel<RecordPickerFieldModel>({
|
|
92
|
+
use: 'RecordPickerFieldModel',
|
|
93
|
+
uid: 'record-picker-without-field-names',
|
|
94
|
+
});
|
|
95
|
+
field.context.defineProperty('collectionField', {
|
|
96
|
+
value: {
|
|
97
|
+
type: 'hasMany',
|
|
98
|
+
targetCollection: {
|
|
99
|
+
filterTargetKey: 'id',
|
|
100
|
+
titleCollectionField: { name: 'name' },
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
field.setProps({ value: [{ id: 1, name: 'Alice' }], onClick: vi.fn(), onChange: vi.fn() });
|
|
105
|
+
|
|
106
|
+
const result = render(React.createElement(FlowModelProvider, { model: field }, field.render()));
|
|
107
|
+
|
|
108
|
+
expect(result.getByRole('combobox')).toBeTruthy();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it('queues ID-only picker values for association label hydration', () => {
|
|
112
|
+
const statusMap = new Map<string, 'pending' | 'done'>();
|
|
113
|
+
const fieldNames = { label: 'name', value: 'id' };
|
|
114
|
+
|
|
115
|
+
expect(
|
|
116
|
+
collectAssociationHydrationCandidates({
|
|
117
|
+
value: [{ id: 1 }, { id: 2, name: 'Already hydrated' }],
|
|
118
|
+
isMultiple: true,
|
|
119
|
+
valueKey: fieldNames.value,
|
|
120
|
+
labelKey: fieldNames.label,
|
|
121
|
+
statusMap,
|
|
122
|
+
}),
|
|
123
|
+
).toEqual([{ item: { id: 1 }, tk: 1, tkKey: '1' }]);
|
|
124
|
+
expect(statusMap).toEqual(new Map([['1', 'pending']]));
|
|
125
|
+
|
|
126
|
+
expect(
|
|
127
|
+
collectAssociationHydrationCandidates({
|
|
128
|
+
value: { id: 1 },
|
|
129
|
+
isMultiple: false,
|
|
130
|
+
valueKey: fieldNames.value,
|
|
131
|
+
labelKey: fieldNames.label,
|
|
132
|
+
statusMap,
|
|
133
|
+
}),
|
|
134
|
+
).toEqual([]);
|
|
135
|
+
});
|
|
136
|
+
|
|
137
|
+
it('hydrates an ID-only picker value back into the form', async () => {
|
|
138
|
+
const resource = {
|
|
139
|
+
get: vi.fn().mockResolvedValue({ id: 1, name: '北京启明教育' }),
|
|
140
|
+
};
|
|
141
|
+
const setFormValue = vi.fn();
|
|
142
|
+
const onChange = vi.fn();
|
|
143
|
+
const model = {
|
|
144
|
+
resource,
|
|
145
|
+
props: { name: 'company' },
|
|
146
|
+
context: {
|
|
147
|
+
fieldPathArray: ['company'],
|
|
148
|
+
setFormValue,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const { rerender } = renderHook(() =>
|
|
153
|
+
useAssociationValueHydration({
|
|
154
|
+
model,
|
|
155
|
+
value: { id: 1 },
|
|
156
|
+
isMultiple: false,
|
|
157
|
+
fieldNames: { label: 'name', value: 'id' },
|
|
158
|
+
onChange,
|
|
159
|
+
}),
|
|
160
|
+
);
|
|
161
|
+
|
|
162
|
+
await waitFor(() => {
|
|
163
|
+
expect(setFormValue).toHaveBeenCalledWith(
|
|
164
|
+
['company'],
|
|
165
|
+
{ id: 1, name: '北京启明教育' },
|
|
166
|
+
{ source: 'default', markExplicit: false, triggerEvent: false },
|
|
167
|
+
);
|
|
168
|
+
});
|
|
169
|
+
rerender();
|
|
170
|
+
|
|
171
|
+
expect(resource.get).toHaveBeenCalledTimes(1);
|
|
172
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('initializes a target resource before hydrating an ID-only picker value', async () => {
|
|
176
|
+
const engine = new FlowEngine();
|
|
177
|
+
engine.registerModels({ RecordPickerFieldModel });
|
|
178
|
+
|
|
179
|
+
const formItem = engine.createModel<FlowModel>({
|
|
180
|
+
use: 'FlowModel',
|
|
181
|
+
uid: 'form-item',
|
|
182
|
+
});
|
|
183
|
+
const field = engine.createModel<RecordPickerFieldModel>({
|
|
184
|
+
use: 'RecordPickerFieldModel',
|
|
185
|
+
uid: 'company-field',
|
|
186
|
+
parentId: formItem.uid,
|
|
187
|
+
});
|
|
188
|
+
const setFormValue = vi.fn();
|
|
189
|
+
field.context.defineProperty('collectionField', {
|
|
190
|
+
value: {
|
|
191
|
+
dataSourceKey: 'main',
|
|
192
|
+
target: 'rp_na_companies',
|
|
193
|
+
},
|
|
194
|
+
});
|
|
195
|
+
field.context.defineProperty('fieldPathArray', { value: ['company'] });
|
|
196
|
+
field.context.defineProperty('setFormValue', { value: setFormValue });
|
|
197
|
+
|
|
198
|
+
await field.applyFlow('recordPickerSettings');
|
|
199
|
+
const get = vi.spyOn(field.resource, 'get').mockResolvedValue({ id: 1, name: '北京启明教育' });
|
|
200
|
+
const onChange = vi.fn();
|
|
201
|
+
|
|
202
|
+
renderHook(() =>
|
|
203
|
+
useAssociationValueHydration({
|
|
204
|
+
model: field,
|
|
205
|
+
value: { id: 1 },
|
|
206
|
+
isMultiple: false,
|
|
207
|
+
fieldNames: { label: 'name', value: 'id' },
|
|
208
|
+
onChange,
|
|
209
|
+
}),
|
|
210
|
+
);
|
|
211
|
+
|
|
212
|
+
await waitFor(() => {
|
|
213
|
+
expect(get).toHaveBeenCalledWith(1);
|
|
214
|
+
expect(setFormValue).toHaveBeenCalledWith(
|
|
215
|
+
['company'],
|
|
216
|
+
{ id: 1, name: '北京启明教育' },
|
|
217
|
+
{ source: 'default', markExplicit: false, triggerEvent: false },
|
|
218
|
+
);
|
|
219
|
+
});
|
|
220
|
+
expect(onChange).not.toHaveBeenCalled();
|
|
221
|
+
});
|
|
222
|
+
|
|
81
223
|
it('returns the empty popup select value for the current multiple mode', () => {
|
|
82
224
|
expect(getRecordPickerEmptyValue(true)).toEqual([]);
|
|
83
225
|
expect(getRecordPickerEmptyValue(false)).toBeUndefined();
|
|
@@ -243,6 +385,52 @@ describe('RecordPickerFieldModel item context', () => {
|
|
|
243
385
|
expect(associationOpts.serverOnlyWhenContextParams).toBe(true);
|
|
244
386
|
});
|
|
245
387
|
|
|
388
|
+
it('builds whole-association descriptors for current and recursive parent item values', async () => {
|
|
389
|
+
const people = {
|
|
390
|
+
name: 'people',
|
|
391
|
+
dataSourceKey: 'main',
|
|
392
|
+
filterTargetKey: 'id',
|
|
393
|
+
getFields: () => [],
|
|
394
|
+
getField: () => undefined,
|
|
395
|
+
} as any;
|
|
396
|
+
const owner = {
|
|
397
|
+
name: 'owner',
|
|
398
|
+
target: 'people',
|
|
399
|
+
targetCollection: people,
|
|
400
|
+
isAssociationField: () => true,
|
|
401
|
+
} as any;
|
|
402
|
+
const collection = {
|
|
403
|
+
name: 'tasks',
|
|
404
|
+
dataSourceKey: 'main',
|
|
405
|
+
getFields: () => [owner],
|
|
406
|
+
getField: (name: string) => (name === 'owner' ? owner : undefined),
|
|
407
|
+
} as any;
|
|
408
|
+
const options = createAssociationItemChainContextPropertyOptions({
|
|
409
|
+
t: (value) => value,
|
|
410
|
+
title: 'Current item',
|
|
411
|
+
collectionAccessor: () => collection,
|
|
412
|
+
propertiesAccessor: (ctx) => ctx.item.value,
|
|
413
|
+
resolverPropertiesAccessor: () => ({ owner: { id: 7 } }),
|
|
414
|
+
parentCollectionAccessor: () => collection,
|
|
415
|
+
parentAccessors: {
|
|
416
|
+
parentPropertiesAccessor: () => ({ owner: { id: 8 } }),
|
|
417
|
+
parentItemMetaAccessor: () => undefined,
|
|
418
|
+
parentItemResolverAccessor: () => undefined,
|
|
419
|
+
},
|
|
420
|
+
});
|
|
421
|
+
const meta = await options.meta();
|
|
422
|
+
const params = await meta.buildVariablesParams({
|
|
423
|
+
item: { value: { owner: { id: 7 } }, parentItem: { value: { owner: { id: 8 } } } },
|
|
424
|
+
});
|
|
425
|
+
|
|
426
|
+
expect(params).toEqual({
|
|
427
|
+
parentItem: { value: { owner: { collection: 'people', dataSourceKey: 'main', filterByTk: 8 } } },
|
|
428
|
+
value: { owner: { collection: 'people', dataSourceKey: 'main', filterByTk: 7 } },
|
|
429
|
+
});
|
|
430
|
+
expect(options.resolveOnServer('value.owner.name')).toBe(true);
|
|
431
|
+
expect(options.resolveOnServer('parentItem.value.owner.name')).toBe(true);
|
|
432
|
+
});
|
|
433
|
+
|
|
246
434
|
it('disables current item attributes for select-record popup item context', async () => {
|
|
247
435
|
const parentCtx = new FlowContext();
|
|
248
436
|
const departmentsCollection = {
|