@nocobase/client-v2 2.1.35 → 2.1.37
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/flow/actions/linkageRules.d.ts +24 -0
- package/es/flow/components/FieldAssignValueInput.d.ts +2 -23
- package/es/flow/components/field-value-variable/DateVariableEditor.d.ts +23 -0
- package/es/flow/components/field-value-variable/FieldValueVariableInput.d.ts +28 -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/index.mjs +64 -73
- package/lib/index.js +92 -101
- package/package.json +7 -7
- package/src/flow/actions/__tests__/linkageRules.actionStates.test.ts +33 -0
- package/src/flow/actions/linkageRules.tsx +25 -7
- package/src/flow/components/FieldAssignValueInput.tsx +34 -571
- package/src/flow/components/field-value-variable/DateVariableEditor.tsx +162 -0
- package/src/flow/components/field-value-variable/FieldValueVariableInput.tsx +306 -0
- package/src/flow/components/field-value-variable/__tests__/FieldValueVariableInput.test.tsx +380 -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/models/blocks/assign-form/AssignFormItemModel.tsx +28 -53
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +8 -2
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +398 -0
- package/src/flow/models/blocks/form/value-runtime/rules.ts +39 -9
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +39 -19
- 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/__tests__/RecordPickerFieldModel.itemContext.test.ts +46 -0
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +1 -0
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +20 -2
|
@@ -142,13 +142,32 @@ export class FormValueRuntime {
|
|
|
142
142
|
return this.getForm().getFieldsValue(true);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
private toMirrorSnapshot(value:
|
|
146
|
-
const
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
145
|
+
private toMirrorSnapshot<T>(value: T): T {
|
|
146
|
+
const cloneValue = (input: unknown): unknown =>
|
|
147
|
+
_.cloneDeepWith(input, (item: unknown) => {
|
|
148
|
+
if (isObservable(item)) {
|
|
149
|
+
const plainItem = toJS(item);
|
|
150
|
+
if (plainItem !== item) return cloneValue(plainItem);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Tracking form-value array proxies hide `constructor`, while Lodash's array
|
|
154
|
+
// clone relies on it being callable.
|
|
155
|
+
if (Array.isArray(item) && typeof item.constructor !== 'function') {
|
|
156
|
+
const plainArray = new Array(item.length);
|
|
157
|
+
for (let index = 0; index < item.length; index++) {
|
|
158
|
+
if (Object.hasOwn(item, index)) {
|
|
159
|
+
plainArray[index] = cloneValue(item[index]);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return plainArray;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
if (!item || typeof item !== 'object') return undefined;
|
|
166
|
+
if (Array.isArray(item) || _.isPlainObject(item)) return undefined;
|
|
167
|
+
return item;
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
return cloneValue(value) as T;
|
|
152
171
|
}
|
|
153
172
|
|
|
154
173
|
canApplyDefaultValuePatch(namePath: NamePath, resolved: any) {
|
|
@@ -892,11 +911,12 @@ export class FormValueRuntime {
|
|
|
892
911
|
const changedPaths: NamePath[] = [];
|
|
893
912
|
|
|
894
913
|
if (!Array.isArray(patch)) {
|
|
895
|
-
const patchEntries = Object.entries(patch || {})
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
914
|
+
const patchEntries = Object.entries(patch || {})
|
|
915
|
+
.map(([pathKey, rawValue]) => [pathKey, this.toMirrorSnapshot(rawValue)] as const)
|
|
916
|
+
.filter(([pathKey, value]) => {
|
|
917
|
+
if (shouldSkipByLinkageScope(pathKey)) return false;
|
|
918
|
+
return !_.isEqual(this.getFormValueAtPath([pathKey]), value);
|
|
919
|
+
});
|
|
900
920
|
const patchToApply = Object.fromEntries(patchEntries);
|
|
901
921
|
const patchKeys = patchEntries.map(([pathKey]) => pathKey);
|
|
902
922
|
if (!patchKeys.length) {
|
|
@@ -907,8 +927,7 @@ export class FormValueRuntime {
|
|
|
907
927
|
}
|
|
908
928
|
this.suppressFormCallbackDepth++;
|
|
909
929
|
try {
|
|
910
|
-
for (const [pathKey,
|
|
911
|
-
const value = isObservable(rawValue) ? toJS(rawValue) : rawValue;
|
|
930
|
+
for (const [pathKey, value] of patchEntries) {
|
|
912
931
|
if (typeof form.setFieldValue === 'function') {
|
|
913
932
|
form.setFieldValue(pathKey, value);
|
|
914
933
|
} else if (typeof (form as any).setFields === 'function') {
|
|
@@ -969,7 +988,7 @@ export class FormValueRuntime {
|
|
|
969
988
|
const namePath = this.resolveNamePath(callerCtx, item.path);
|
|
970
989
|
const pathKey = namePathToPathKey(namePath);
|
|
971
990
|
const rawValue = item.value;
|
|
972
|
-
const value =
|
|
991
|
+
const value = this.toMirrorSnapshot(rawValue);
|
|
973
992
|
|
|
974
993
|
if (shouldSkipByLinkageScope(pathKey)) continue;
|
|
975
994
|
|
|
@@ -1108,17 +1127,18 @@ export class FormValueRuntime {
|
|
|
1108
1127
|
if (!form) return;
|
|
1109
1128
|
if (source === 'override' && this.findUserEditedHit(pathKey)) return;
|
|
1110
1129
|
|
|
1130
|
+
const value = this.toMirrorSnapshot(nextValue);
|
|
1111
1131
|
const prevValue = _.get(this.valuesMirror, namePath);
|
|
1112
|
-
if (_.isEqual(prevValue,
|
|
1132
|
+
if (_.isEqual(prevValue, value)) return;
|
|
1113
1133
|
|
|
1114
1134
|
this.writeSeq += 1;
|
|
1115
1135
|
const writeSeq = this.writeSeq;
|
|
1116
1136
|
|
|
1117
1137
|
this.suppressFormCallbackDepth++;
|
|
1118
1138
|
try {
|
|
1119
|
-
form.setFieldValue?.(namePath,
|
|
1120
|
-
_.set(this.valuesMirror, namePath, this.toMirrorSnapshot(
|
|
1121
|
-
this.syncMountedFieldModelValue(namePath,
|
|
1139
|
+
form.setFieldValue?.(namePath, value);
|
|
1140
|
+
_.set(this.valuesMirror, namePath, this.toMirrorSnapshot(value));
|
|
1141
|
+
this.syncMountedFieldModelValue(namePath, value);
|
|
1122
1142
|
this.bumpChangeTick();
|
|
1123
1143
|
} finally {
|
|
1124
1144
|
this.suppressFormCallbackDepth--;
|
|
@@ -28,6 +28,7 @@ import { observer } from '@formily/reactive-react';
|
|
|
28
28
|
import React, { useEffect, useMemo, useState, useRef } from 'react';
|
|
29
29
|
import { useTranslation } from 'react-i18next';
|
|
30
30
|
import { buildRecordPickerPopupContextInputArgs, RecordPickerContent } from '../RecordPickerFieldModel';
|
|
31
|
+
import { buildOpenerUids } from '../recordSelectShared';
|
|
31
32
|
import { AssociationFieldModel } from '../AssociationFieldModel';
|
|
32
33
|
import { adjustColumnOrder } from '../../../blocks/table/utils';
|
|
33
34
|
import { isSubTableColumnFieldComponentContext } from '../SubTableFieldModel/SubTableColumnModel';
|
|
@@ -653,6 +654,11 @@ PopupSubTableFieldModel.registerFlow({
|
|
|
653
654
|
const parentItemOptions = ctx?.getPropertyOptions?.('item');
|
|
654
655
|
const itemIndex = Array.isArray(ctx.model?.props?.value) ? ctx.model.props.value.length : 0;
|
|
655
656
|
const itemLength = itemIndex + 1;
|
|
657
|
+
const associationName = ctx.collectionField?.resourceName;
|
|
658
|
+
const sourceId = parentItem?.value
|
|
659
|
+
? ctx.collectionField?.collection?.getFilterByTK?.(parentItem.value)
|
|
660
|
+
: undefined;
|
|
661
|
+
const openerUids = buildOpenerUids(ctx, ctx.inputArgs);
|
|
656
662
|
ctx.viewer.open({
|
|
657
663
|
type: openMode,
|
|
658
664
|
width: sizeToWidthMap[openMode][size],
|
|
@@ -663,12 +669,14 @@ PopupSubTableFieldModel.registerFlow({
|
|
|
663
669
|
scene: 'subForm',
|
|
664
670
|
dataSourceKey: ctx.collection.dataSourceKey,
|
|
665
671
|
collectionName: ctx.collectionField?.target,
|
|
672
|
+
...(associationName && sourceId != null ? { associationName, sourceId } : {}),
|
|
666
673
|
collectionField: ctx.collectionField,
|
|
667
674
|
parentItem,
|
|
668
675
|
parentItemMeta: parentItemOptions?.meta,
|
|
669
676
|
parentItemResolver: parentItemOptions?.resolveOnServer,
|
|
670
677
|
itemIndex,
|
|
671
678
|
itemLength,
|
|
679
|
+
openerUids,
|
|
672
680
|
},
|
|
673
681
|
content: () => <EditFormContent model={ctx.model} scene="create" />,
|
|
674
682
|
styles: {
|
|
@@ -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: {
|
|
@@ -243,6 +243,52 @@ describe('RecordPickerFieldModel item context', () => {
|
|
|
243
243
|
expect(associationOpts.serverOnlyWhenContextParams).toBe(true);
|
|
244
244
|
});
|
|
245
245
|
|
|
246
|
+
it('builds whole-association descriptors for current and recursive parent item values', async () => {
|
|
247
|
+
const people = {
|
|
248
|
+
name: 'people',
|
|
249
|
+
dataSourceKey: 'main',
|
|
250
|
+
filterTargetKey: 'id',
|
|
251
|
+
getFields: () => [],
|
|
252
|
+
getField: () => undefined,
|
|
253
|
+
} as any;
|
|
254
|
+
const owner = {
|
|
255
|
+
name: 'owner',
|
|
256
|
+
target: 'people',
|
|
257
|
+
targetCollection: people,
|
|
258
|
+
isAssociationField: () => true,
|
|
259
|
+
} as any;
|
|
260
|
+
const collection = {
|
|
261
|
+
name: 'tasks',
|
|
262
|
+
dataSourceKey: 'main',
|
|
263
|
+
getFields: () => [owner],
|
|
264
|
+
getField: (name: string) => (name === 'owner' ? owner : undefined),
|
|
265
|
+
} as any;
|
|
266
|
+
const options = createAssociationItemChainContextPropertyOptions({
|
|
267
|
+
t: (value) => value,
|
|
268
|
+
title: 'Current item',
|
|
269
|
+
collectionAccessor: () => collection,
|
|
270
|
+
propertiesAccessor: (ctx) => ctx.item.value,
|
|
271
|
+
resolverPropertiesAccessor: () => ({ owner: { id: 7 } }),
|
|
272
|
+
parentCollectionAccessor: () => collection,
|
|
273
|
+
parentAccessors: {
|
|
274
|
+
parentPropertiesAccessor: () => ({ owner: { id: 8 } }),
|
|
275
|
+
parentItemMetaAccessor: () => undefined,
|
|
276
|
+
parentItemResolverAccessor: () => undefined,
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
const meta = await options.meta();
|
|
280
|
+
const params = await meta.buildVariablesParams({
|
|
281
|
+
item: { value: { owner: { id: 7 } }, parentItem: { value: { owner: { id: 8 } } } },
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
expect(params).toEqual({
|
|
285
|
+
parentItem: { value: { owner: { collection: 'people', dataSourceKey: 'main', filterByTk: 8 } } },
|
|
286
|
+
value: { owner: { collection: 'people', dataSourceKey: 'main', filterByTk: 7 } },
|
|
287
|
+
});
|
|
288
|
+
expect(options.resolveOnServer('value.owner.name')).toBe(true);
|
|
289
|
+
expect(options.resolveOnServer('parentItem.value.owner.name')).toBe(true);
|
|
290
|
+
});
|
|
291
|
+
|
|
246
292
|
it('disables current item attributes for select-record popup item context', async () => {
|
|
247
293
|
const parentCtx = new FlowContext();
|
|
248
294
|
const departmentsCollection = {
|
|
@@ -158,8 +158,15 @@ vi.mock('antd-mobile', () => {
|
|
|
158
158
|
mockState.popupProps = props;
|
|
159
159
|
return props.visible ? <div data-testid="popup">{props.children}</div> : null;
|
|
160
160
|
},
|
|
161
|
-
SearchBar: ({ value, onChange }: any) => (
|
|
162
|
-
<
|
|
161
|
+
SearchBar: ({ value, onChange, onCancel, cancelText, showCancelButton }: any) => (
|
|
162
|
+
<div>
|
|
163
|
+
<input data-testid="search" value={value ?? ''} onChange={(e) => onChange?.(e.target.value)} />
|
|
164
|
+
{showCancelButton && value ? (
|
|
165
|
+
<button type="button" onClick={onCancel}>
|
|
166
|
+
{cancelText ?? '取消'}
|
|
167
|
+
</button>
|
|
168
|
+
) : null}
|
|
169
|
+
</div>
|
|
163
170
|
),
|
|
164
171
|
CheckList: MockCheckList,
|
|
165
172
|
};
|
|
@@ -289,6 +296,17 @@ describe('MobileLazySelect', () => {
|
|
|
289
296
|
resetMockState();
|
|
290
297
|
});
|
|
291
298
|
|
|
299
|
+
it('renders a translated cancel action after searching', () => {
|
|
300
|
+
renderMobileLazySelect();
|
|
301
|
+
|
|
302
|
+
openLazyPopup();
|
|
303
|
+
act(() => {
|
|
304
|
+
fireEvent.change(screen.getByTestId('search'), { target: { value: '11' } });
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
expect(screen.getByRole('button', { name: 'Cancel' })).toBeInTheDocument();
|
|
308
|
+
});
|
|
309
|
+
|
|
292
310
|
it('keeps pending relation records selected until confirm', () => {
|
|
293
311
|
const { onChange, rerender } = renderMobileLazySelect();
|
|
294
312
|
|