@nocobase/client-v2 2.1.0-beta.43 → 2.1.0-beta.44
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/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +1 -0
- package/es/index.mjs +85 -85
- package/lib/index.js +85 -85
- package/package.json +7 -7
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +15 -1
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +67 -0
- package/src/flow/models/base/GridModel.tsx +1 -1
- package/src/flow/models/base/PageModel/ChildPageModel.tsx +5 -1
- package/src/flow/models/base/PageModel/__tests__/ChildPageModel.test.ts +12 -0
- package/src/flow/models/base/__tests__/GridModel.render.test.tsx +53 -0
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +29 -0
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +5 -1
- package/src/flow/models/fields/mobile-components/MobileLazySelect.tsx +5 -3
- package/src/flow/models/fields/mobile-components/__tests__/MobileSelect.test.tsx +70 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nocobase/client-v2",
|
|
3
|
-
"version": "2.1.0-beta.
|
|
3
|
+
"version": "2.1.0-beta.44",
|
|
4
4
|
"license": "Apache-2.0",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"module": "es/index.mjs",
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
"@formily/antd-v5": "1.2.3",
|
|
28
28
|
"@formily/react": "^2.2.27",
|
|
29
29
|
"@formily/shared": "^2.2.27",
|
|
30
|
-
"@nocobase/evaluators": "2.1.0-beta.
|
|
31
|
-
"@nocobase/flow-engine": "2.1.0-beta.
|
|
32
|
-
"@nocobase/sdk": "2.1.0-beta.
|
|
33
|
-
"@nocobase/shared": "2.1.0-beta.
|
|
34
|
-
"@nocobase/utils": "2.1.0-beta.
|
|
30
|
+
"@nocobase/evaluators": "2.1.0-beta.44",
|
|
31
|
+
"@nocobase/flow-engine": "2.1.0-beta.44",
|
|
32
|
+
"@nocobase/sdk": "2.1.0-beta.44",
|
|
33
|
+
"@nocobase/shared": "2.1.0-beta.44",
|
|
34
|
+
"@nocobase/utils": "2.1.0-beta.44",
|
|
35
35
|
"ahooks": "^3.7.2",
|
|
36
36
|
"antd": "5.24.2",
|
|
37
37
|
"antd-style": "3.7.1",
|
|
@@ -45,5 +45,5 @@
|
|
|
45
45
|
"react-i18next": "^11.15.1",
|
|
46
46
|
"react-router-dom": "^6.30.1"
|
|
47
47
|
},
|
|
48
|
-
"gitHead": "
|
|
48
|
+
"gitHead": "540d4897b1696cc24c0754521b0b766978b4933c"
|
|
49
49
|
}
|
|
@@ -55,6 +55,15 @@ const insertPositionToMethod = {
|
|
|
55
55
|
afterEnd: 'insertAfter',
|
|
56
56
|
} as const;
|
|
57
57
|
|
|
58
|
+
const omitMenuRuntimeProps = (props: Record<string, unknown> = {}) => {
|
|
59
|
+
const persistableProps = { ...props };
|
|
60
|
+
delete persistableProps.item;
|
|
61
|
+
delete persistableProps.dom;
|
|
62
|
+
delete persistableProps.options;
|
|
63
|
+
delete persistableProps.renderType;
|
|
64
|
+
return persistableProps;
|
|
65
|
+
};
|
|
66
|
+
|
|
58
67
|
export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStructure> {
|
|
59
68
|
private creationPersisted = false;
|
|
60
69
|
private persistedStateHydrated = false;
|
|
@@ -261,6 +270,12 @@ export class AdminLayoutMenuItemModel extends FlowModel<AdminLayoutMenuItemStruc
|
|
|
261
270
|
return currentFlowCount > 0 || initialFlowCount > 0;
|
|
262
271
|
}
|
|
263
272
|
|
|
273
|
+
serialize(): ReturnType<FlowModel['serialize']> {
|
|
274
|
+
const data = super.serialize();
|
|
275
|
+
data.props = omitMenuRuntimeProps(data.props);
|
|
276
|
+
return data;
|
|
277
|
+
}
|
|
278
|
+
|
|
264
279
|
setHidden(value: boolean) {
|
|
265
280
|
const previous = this.hidden;
|
|
266
281
|
super.setHidden(value);
|
|
@@ -698,7 +713,6 @@ AdminLayoutMenuItemModel.registerFlow({
|
|
|
698
713
|
AdminLayoutMenuItemModel.registerFlow({
|
|
699
714
|
key: 'menuSettings',
|
|
700
715
|
title: 'Menu settings',
|
|
701
|
-
manual: true,
|
|
702
716
|
steps: {
|
|
703
717
|
edit: {
|
|
704
718
|
title: 'Edit',
|
|
@@ -950,6 +950,73 @@ describe('AdminLayoutModel menu items', () => {
|
|
|
950
950
|
});
|
|
951
951
|
});
|
|
952
952
|
|
|
953
|
+
it('should save menu linkage rules without serializing runtime render props', async () => {
|
|
954
|
+
type SerializedFlowModel = Record<string, unknown> & {
|
|
955
|
+
props?: Record<string, unknown>;
|
|
956
|
+
subModels?: unknown;
|
|
957
|
+
};
|
|
958
|
+
const save = vi.fn(
|
|
959
|
+
async (targetModel: { serialize: () => SerializedFlowModel }, options?: { onlyStepParams?: boolean }) => {
|
|
960
|
+
const data = targetModel.serialize();
|
|
961
|
+
if (options?.onlyStepParams) {
|
|
962
|
+
delete data.subModels;
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
expect(data.props?.item).toBeUndefined();
|
|
966
|
+
expect(data.props?.dom).toBeUndefined();
|
|
967
|
+
expect(data.props?.options).toBeUndefined();
|
|
968
|
+
expect(data.props?.renderType).toBeUndefined();
|
|
969
|
+
expect(() => JSON.stringify(data)).not.toThrow();
|
|
970
|
+
return data;
|
|
971
|
+
},
|
|
972
|
+
);
|
|
973
|
+
const updateRoute = vi.fn().mockResolvedValue(undefined);
|
|
974
|
+
engine.setModelRepository({ save } as Parameters<FlowEngine['setModelRepository']>[0]);
|
|
975
|
+
engine.context.routeRepository.updateRoute = updateRoute;
|
|
976
|
+
|
|
977
|
+
const route = createRoute();
|
|
978
|
+
const model = engine.createModel<AdminLayoutMenuItemModel>({
|
|
979
|
+
uid: 'menu-item-linkage-runtime-props',
|
|
980
|
+
use: AdminLayoutMenuItemModel,
|
|
981
|
+
props: {
|
|
982
|
+
route,
|
|
983
|
+
},
|
|
984
|
+
});
|
|
985
|
+
const item = {
|
|
986
|
+
name: 'Page 1',
|
|
987
|
+
path: '/admin/page-1',
|
|
988
|
+
_route: route,
|
|
989
|
+
_model: model,
|
|
990
|
+
};
|
|
991
|
+
|
|
992
|
+
model.setProps({
|
|
993
|
+
item,
|
|
994
|
+
dom: React.createElement('span', null, 'Page 1'),
|
|
995
|
+
renderType: 'item',
|
|
996
|
+
options: { collapsed: false },
|
|
997
|
+
});
|
|
998
|
+
model.setStepParams('menuSettings', 'linkageRules', {
|
|
999
|
+
value: [
|
|
1000
|
+
{
|
|
1001
|
+
key: 'r1',
|
|
1002
|
+
title: 'Hide menu item',
|
|
1003
|
+
enable: true,
|
|
1004
|
+
condition: { logic: '$and', items: [] },
|
|
1005
|
+
actions: [{ key: 'a1', name: 'linkageSetMenuItemProps', params: { value: 'hidden' } }],
|
|
1006
|
+
},
|
|
1007
|
+
],
|
|
1008
|
+
});
|
|
1009
|
+
|
|
1010
|
+
await model.saveStepParams();
|
|
1011
|
+
|
|
1012
|
+
expect(save).toHaveBeenCalledWith(model, { onlyStepParams: true });
|
|
1013
|
+
expect(updateRoute).toHaveBeenCalledWith(1, {
|
|
1014
|
+
options: {
|
|
1015
|
+
hasPersistedMenuInstanceFlow: true,
|
|
1016
|
+
},
|
|
1017
|
+
});
|
|
1018
|
+
});
|
|
1019
|
+
|
|
953
1020
|
it('should clear persisted menu linkage rules when no persisted state remains', async () => {
|
|
954
1021
|
const saveModel = vi.spyOn(engine, 'saveModel').mockResolvedValue(undefined as any);
|
|
955
1022
|
const destroy = vi.fn().mockResolvedValue(true);
|
|
@@ -1006,7 +1006,7 @@ export class GridModel<T extends { subModels: { items: FlowModel[] } } = Default
|
|
|
1006
1006
|
<Space
|
|
1007
1007
|
ref={this.gridContainerRef}
|
|
1008
1008
|
direction={'vertical'}
|
|
1009
|
-
style={{ width: '100%', marginBottom: this.props.rowGap }}
|
|
1009
|
+
style={{ width: '100%', marginBottom: this.props.rowGap, display: 'flex' }}
|
|
1010
1010
|
size={this.props.rowGap}
|
|
1011
1011
|
>
|
|
1012
1012
|
<DndProvider
|
|
@@ -49,7 +49,11 @@ export class ChildPageModel extends PageModel {
|
|
|
49
49
|
throw new Error('Invalid drag event');
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
if (event.active.id === event.over.id) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await this.flowEngine.moveModel(event.active.id, event.over.id);
|
|
53
57
|
}
|
|
54
58
|
|
|
55
59
|
render() {
|
|
@@ -107,6 +107,18 @@ describe('ChildPageModel', () => {
|
|
|
107
107
|
expect(mockFlowEngine.moveModel).toHaveBeenCalledWith('active-model-id', 'over-model-id');
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
+
it('should ignore self-drop', async () => {
|
|
111
|
+
await childPageModel.handleDragEnd({
|
|
112
|
+
...mockDragEndEvent,
|
|
113
|
+
over: {
|
|
114
|
+
...(mockDragEndEvent.over as NonNullable<DragEndEvent['over']>),
|
|
115
|
+
id: 'active-model-id',
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
expect(mockFlowEngine.moveModel).not.toHaveBeenCalled();
|
|
120
|
+
});
|
|
121
|
+
|
|
110
122
|
it('should handle case when over is null', async () => {
|
|
111
123
|
const eventWithNullOver = {
|
|
112
124
|
...mockDragEndEvent,
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* This file is part of the NocoBase (R) project.
|
|
3
|
+
* Copyright (c) 2020-2024 NocoBase Co., Ltd.
|
|
4
|
+
* Authors: NocoBase Team.
|
|
5
|
+
*
|
|
6
|
+
* This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License.
|
|
7
|
+
* For more information, please refer to: https://www.nocobase.com/agreement.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { FlowEngine, FlowEngineProvider } from '@nocobase/flow-engine';
|
|
11
|
+
import { render } from '@testing-library/react';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { beforeEach, describe, expect, it } from 'vitest';
|
|
14
|
+
import { GridModel } from '../GridModel';
|
|
15
|
+
|
|
16
|
+
describe('GridModel.render', () => {
|
|
17
|
+
let engine: FlowEngine;
|
|
18
|
+
|
|
19
|
+
beforeEach(async () => {
|
|
20
|
+
engine = new FlowEngine();
|
|
21
|
+
engine.registerModels({ GridModel });
|
|
22
|
+
await engine.flowSettings.disable();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it('renders the Space wrapper as block-level flex to avoid inline baseline spacing', () => {
|
|
26
|
+
const item = engine.createModel({ use: 'FlowModel', uid: 'item-1' });
|
|
27
|
+
const model = engine.createModel<GridModel>({
|
|
28
|
+
use: 'GridModel',
|
|
29
|
+
uid: 'grid-render',
|
|
30
|
+
props: {
|
|
31
|
+
rows: {
|
|
32
|
+
row1: [['item-1']],
|
|
33
|
+
},
|
|
34
|
+
sizes: {
|
|
35
|
+
row1: [24],
|
|
36
|
+
},
|
|
37
|
+
rowGap: 16,
|
|
38
|
+
},
|
|
39
|
+
structure: {} as any,
|
|
40
|
+
});
|
|
41
|
+
(model as any).subModels = { items: [item] };
|
|
42
|
+
|
|
43
|
+
expect(Object.keys((model as any).getVisibleLayout().rows)).toEqual(['row1']);
|
|
44
|
+
|
|
45
|
+
const { container } = render(<FlowEngineProvider engine={engine}>{model.render()}</FlowEngineProvider>);
|
|
46
|
+
const space = container.querySelector('.ant-space') as HTMLElement;
|
|
47
|
+
|
|
48
|
+
expect(space).toBeTruthy();
|
|
49
|
+
expect(space.style.width).toBe('100%');
|
|
50
|
+
expect(space.style.marginBottom).toBe('16px');
|
|
51
|
+
expect(space.style.display).toBe('flex');
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -221,6 +221,23 @@ describe('FlowModel core behaviors (collected)', () => {
|
|
|
221
221
|
expect(i1).toEqual({ ping: 1 });
|
|
222
222
|
});
|
|
223
223
|
|
|
224
|
+
it('unchanged stepParams do not trigger a beforeRender rerun', async () => {
|
|
225
|
+
vi.useFakeTimers();
|
|
226
|
+
const spy = vi.fn().mockResolvedValue([]);
|
|
227
|
+
(engine as any).executor.dispatchEvent = spy;
|
|
228
|
+
const model = engine.createModel<FlowModel>({
|
|
229
|
+
use: 'FlowModel',
|
|
230
|
+
uid: 'm-flow-3-noop',
|
|
231
|
+
stepParams: { anyFlow: { anyStep: { x: 1 } } },
|
|
232
|
+
});
|
|
233
|
+
|
|
234
|
+
await model.dispatchEvent('beforeRender', { ping: 1 });
|
|
235
|
+
model.setStepParams('anyFlow', 'anyStep', { x: 1 });
|
|
236
|
+
await vi.advanceTimersByTimeAsync(150);
|
|
237
|
+
|
|
238
|
+
expect(spy).toHaveBeenCalledTimes(1);
|
|
239
|
+
});
|
|
240
|
+
|
|
224
241
|
it('applyFlow delegates to executor.runFlow', async () => {
|
|
225
242
|
const spyRun = vi.fn().mockResolvedValue('ok');
|
|
226
243
|
(engine as any).executor.runFlow = spyRun;
|
|
@@ -15,6 +15,7 @@ import { observable } from '@formily/reactive';
|
|
|
15
15
|
import { get as lodashGet, merge as lodashMerge, set as lodashSet } from 'lodash';
|
|
16
16
|
import { FlowContext, JSRunner } from '@nocobase/flow-engine';
|
|
17
17
|
import { FormValueRuntime } from '..';
|
|
18
|
+
import type { FormInstance } from 'antd';
|
|
18
19
|
|
|
19
20
|
function createFormStub(initialValues: any = {}) {
|
|
20
21
|
const store: any = JSON.parse(JSON.stringify(initialValues || {}));
|
|
@@ -121,6 +122,34 @@ function createFieldContext(runtime: FormValueRuntime) {
|
|
|
121
122
|
}
|
|
122
123
|
|
|
123
124
|
describe('FormValueRuntime (default rules)', () => {
|
|
125
|
+
it('skips object patches when values are unchanged', async () => {
|
|
126
|
+
const engineEmitter = new EventEmitter();
|
|
127
|
+
const blockEmitter = new EventEmitter();
|
|
128
|
+
const formStub = createFormStub({ name: 'old' });
|
|
129
|
+
const dispatchEvent = vi.fn();
|
|
130
|
+
|
|
131
|
+
const blockModel = {
|
|
132
|
+
uid: 'form-noop-patch',
|
|
133
|
+
flowEngine: { emitter: engineEmitter },
|
|
134
|
+
emitter: blockEmitter,
|
|
135
|
+
dispatchEvent,
|
|
136
|
+
getAclActionName: () => 'create',
|
|
137
|
+
} as ConstructorParameters<typeof FormValueRuntime>[0]['model'];
|
|
138
|
+
|
|
139
|
+
const runtime = new FormValueRuntime({ model: blockModel, getForm: () => formStub as unknown as FormInstance });
|
|
140
|
+
runtime.mount({ sync: true });
|
|
141
|
+
|
|
142
|
+
const blockCtx = createFieldContext(runtime);
|
|
143
|
+
await runtime.setFormValues(blockCtx, { name: 'old' }, { source: 'system' });
|
|
144
|
+
|
|
145
|
+
expect(dispatchEvent).not.toHaveBeenCalled();
|
|
146
|
+
|
|
147
|
+
await runtime.setFormValues(blockCtx, { name: 'new' }, { source: 'system' });
|
|
148
|
+
|
|
149
|
+
expect(formStub.getFieldValue(['name'])).toBe('new');
|
|
150
|
+
expect(dispatchEvent).toHaveBeenCalledTimes(1);
|
|
151
|
+
});
|
|
152
|
+
|
|
124
153
|
it('recomputes default on dependency change when current equals last default; user change disables default permanently', async () => {
|
|
125
154
|
const engineEmitter = new EventEmitter();
|
|
126
155
|
const blockEmitter = new EventEmitter();
|
|
@@ -846,7 +846,11 @@ export class FormValueRuntime {
|
|
|
846
846
|
const changedPaths: NamePath[] = [];
|
|
847
847
|
|
|
848
848
|
if (!Array.isArray(patch)) {
|
|
849
|
-
const patchEntries = Object.entries(patch || {}).filter(([pathKey]) =>
|
|
849
|
+
const patchEntries = Object.entries(patch || {}).filter(([pathKey, rawValue]) => {
|
|
850
|
+
if (shouldSkipByLinkageScope(pathKey)) return false;
|
|
851
|
+
const value = isObservable(rawValue) ? toJS(rawValue) : rawValue;
|
|
852
|
+
return !_.isEqual(this.getFormValueAtPath([pathKey]), value);
|
|
853
|
+
});
|
|
850
854
|
const patchToApply = Object.fromEntries(patchEntries);
|
|
851
855
|
const patchKeys = patchEntries.map(([pathKey]) => pathKey);
|
|
852
856
|
if (!patchKeys.length) {
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
import { useFlowModelContext } from '@nocobase/flow-engine';
|
|
11
11
|
import { Select } from 'antd';
|
|
12
12
|
import type { CSSProperties } from 'react';
|
|
13
|
-
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
|
13
|
+
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
14
14
|
import { Button, CheckList, Popup, SearchBar, SpinLoading } from 'antd-mobile';
|
|
15
15
|
import { css } from '@emotion/css';
|
|
16
16
|
import {
|
|
@@ -51,7 +51,7 @@ function deriveRecordsFromValue(
|
|
|
51
51
|
) {
|
|
52
52
|
if (isMultiple) {
|
|
53
53
|
if (Array.isArray(value)) {
|
|
54
|
-
return (value.filter(
|
|
54
|
+
return (value.filter((item) => item !== null && item !== undefined) as any[]).map((item) => {
|
|
55
55
|
if (valueMode === 'value') {
|
|
56
56
|
return optionMap.get(item) ?? { [valueKey]: item };
|
|
57
57
|
}
|
|
@@ -113,11 +113,13 @@ export function MobileLazySelect(props: Readonly<LazySelectProps>) {
|
|
|
113
113
|
);
|
|
114
114
|
|
|
115
115
|
const [selectedRecords, setSelectedRecords] = useState<AssociationOption[]>(() => derivedRecords);
|
|
116
|
+
const wasVisibleRef = useRef(visible);
|
|
116
117
|
|
|
117
118
|
useEffect(() => {
|
|
118
|
-
if (visible) {
|
|
119
|
+
if (visible && !wasVisibleRef.current) {
|
|
119
120
|
setSelectedRecords(derivedRecords);
|
|
120
121
|
}
|
|
122
|
+
wasVisibleRef.current = visible;
|
|
121
123
|
}, [derivedRecords, visible]);
|
|
122
124
|
|
|
123
125
|
useEffect(() => {
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { beforeEach, describe, it, expect, vi } from 'vitest';
|
|
12
12
|
import { act, fireEvent, render, screen } from '@nocobase/test/client';
|
|
13
|
+
import { MobileLazySelect } from '../MobileLazySelect';
|
|
13
14
|
import { MobileSelect } from '../MobileSelect';
|
|
14
15
|
|
|
15
16
|
const DEFAULT_OPTIONS = [
|
|
@@ -17,6 +18,11 @@ const DEFAULT_OPTIONS = [
|
|
|
17
18
|
{ label: 'Option B', value: 'b' },
|
|
18
19
|
];
|
|
19
20
|
|
|
21
|
+
const RELATION_OPTIONS = [
|
|
22
|
+
{ uuid: '05f6a3b4-bfb7-7943-578a-3819e2687a7e' },
|
|
23
|
+
{ uuid: 'c7d99828-a1de-9e70-4c2d-b0139abdf02e' },
|
|
24
|
+
];
|
|
25
|
+
|
|
20
26
|
const mockState = vi.hoisted(() => ({
|
|
21
27
|
selectProps: undefined as any,
|
|
22
28
|
popupProps: undefined as any,
|
|
@@ -44,6 +50,13 @@ function openPopup() {
|
|
|
44
50
|
expect(screen.getByTestId('popup')).toBeInTheDocument();
|
|
45
51
|
}
|
|
46
52
|
|
|
53
|
+
function openLazyPopup() {
|
|
54
|
+
act(() => {
|
|
55
|
+
mockState.selectProps?.onClick?.();
|
|
56
|
+
});
|
|
57
|
+
expect(screen.getByTestId('popup')).toBeInTheDocument();
|
|
58
|
+
}
|
|
59
|
+
|
|
47
60
|
function selectValues(values: string[]) {
|
|
48
61
|
act(() => {
|
|
49
62
|
mockState.checklistProps?.onChange?.(values);
|
|
@@ -73,6 +86,30 @@ function renderMobileSelect(props: Record<string, any> = {}) {
|
|
|
73
86
|
return { onChange, onChangeComplete };
|
|
74
87
|
}
|
|
75
88
|
|
|
89
|
+
function renderMobileLazySelect(props: Record<string, any> = {}) {
|
|
90
|
+
const onChange = props.onChange ?? vi.fn();
|
|
91
|
+
const renderComponent = (nextProps: Record<string, any> = {}) => (
|
|
92
|
+
<MobileLazySelect
|
|
93
|
+
fieldNames={{ label: 'uuid', value: 'uuid' }}
|
|
94
|
+
value={[]}
|
|
95
|
+
multiple
|
|
96
|
+
allowMultiple
|
|
97
|
+
options={RELATION_OPTIONS}
|
|
98
|
+
{...props}
|
|
99
|
+
{...nextProps}
|
|
100
|
+
onChange={onChange}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
const result = render(renderComponent());
|
|
105
|
+
|
|
106
|
+
return {
|
|
107
|
+
...result,
|
|
108
|
+
onChange,
|
|
109
|
+
rerender: (nextProps: Record<string, any> = {}) => result.rerender(renderComponent(nextProps)),
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
|
|
76
113
|
vi.mock('@nocobase/flow-engine', async () => {
|
|
77
114
|
const actual = await vi.importActual<any>('@nocobase/flow-engine');
|
|
78
115
|
return {
|
|
@@ -80,6 +117,12 @@ vi.mock('@nocobase/flow-engine', async () => {
|
|
|
80
117
|
useFlowModelContext: () => ({
|
|
81
118
|
t: (value: string) => value,
|
|
82
119
|
}),
|
|
120
|
+
useFlowModel: () => ({
|
|
121
|
+
context: {
|
|
122
|
+
collectionField: {},
|
|
123
|
+
},
|
|
124
|
+
subModels: {},
|
|
125
|
+
}),
|
|
83
126
|
};
|
|
84
127
|
});
|
|
85
128
|
|
|
@@ -240,3 +283,30 @@ describe('MobileSelect in SubForm/SubTable containers', () => {
|
|
|
240
283
|
expect(onCommit).toHaveBeenNthCalledWith(2, ['a', 'b']);
|
|
241
284
|
});
|
|
242
285
|
});
|
|
286
|
+
|
|
287
|
+
describe('MobileLazySelect', () => {
|
|
288
|
+
beforeEach(() => {
|
|
289
|
+
resetMockState();
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('keeps pending relation records selected until confirm', () => {
|
|
293
|
+
const { onChange, rerender } = renderMobileLazySelect();
|
|
294
|
+
|
|
295
|
+
openLazyPopup();
|
|
296
|
+
expect(mockState.checklistProps?.value).toEqual([]);
|
|
297
|
+
|
|
298
|
+
selectValues(['c7d99828-a1de-9e70-4c2d-b0139abdf02e']);
|
|
299
|
+
expect(mockState.checklistProps?.value).toEqual(['c7d99828-a1de-9e70-4c2d-b0139abdf02e']);
|
|
300
|
+
|
|
301
|
+
rerender({
|
|
302
|
+
options: RELATION_OPTIONS.map((item) => ({ ...item })),
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
expect(mockState.checklistProps?.value).toEqual(['c7d99828-a1de-9e70-4c2d-b0139abdf02e']);
|
|
306
|
+
|
|
307
|
+
confirmSelection();
|
|
308
|
+
|
|
309
|
+
expect(onChange).toHaveBeenCalledTimes(1);
|
|
310
|
+
expect(onChange).toHaveBeenCalledWith([RELATION_OPTIONS[1]]);
|
|
311
|
+
});
|
|
312
|
+
});
|