@nocobase/client-v2 2.2.0-alpha.7 → 2.2.0-alpha.8
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/components/FieldAssignValueInput.d.ts +6 -0
- package/es/flow/components/filter/FilterGroup.d.ts +1 -0
- package/es/flow/components/filter/VariableFilterItem.d.ts +1 -0
- package/es/index.mjs +10 -10
- package/lib/index.js +30 -30
- package/package.json +7 -7
- package/src/BaseApplication.tsx +3 -2
- package/src/__tests__/app.test.tsx +29 -0
- package/src/components/form/TypedVariableInput.tsx +12 -8
- package/src/components/form/__tests__/TypedVariableInput.test.tsx +44 -3
- package/src/flow/actions/__tests__/linkageRulesRefresh.test.ts +7 -3
- package/src/flow/actions/linkageRulesRefresh.tsx +2 -6
- package/src/flow/components/FieldAssignValueInput.tsx +21 -0
- package/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx +84 -2
- package/src/flow/components/filter/FilterGroup.tsx +33 -5
- package/src/flow/components/filter/VariableFilterItem.tsx +139 -6
- 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 +21 -0
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +2 -1
- package/src/flow/models/fields/VariableFieldFormModel.tsx +3 -3
- package/src/flow/models/fields/__tests__/VariableFieldFormModel.test.tsx +51 -0
|
@@ -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:保持原有行为,右侧仅静态输入组件。
|
|
@@ -265,11 +311,48 @@ function normalizeRightValueInput(input: unknown) {
|
|
|
265
311
|
return input;
|
|
266
312
|
}
|
|
267
313
|
|
|
314
|
+
function findMetaTreeNodeByPath(metaTree: MetaTreeNode[], targetPath: string[]): MetaTreeNode | null {
|
|
315
|
+
if (!Array.isArray(metaTree) || !targetPath.length) {
|
|
316
|
+
return null;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
const traverseByName = (nodes: MetaTreeNode[], path: string[]): MetaTreeNode | null => {
|
|
320
|
+
if (!path.length) {
|
|
321
|
+
return null;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const [head, ...rest] = path;
|
|
325
|
+
const matched = nodes.find((node) => String(node.name) === String(head));
|
|
326
|
+
if (!matched) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
if (!rest.length) {
|
|
330
|
+
return matched;
|
|
331
|
+
}
|
|
332
|
+
if (!Array.isArray(matched.children)) {
|
|
333
|
+
return null;
|
|
334
|
+
}
|
|
335
|
+
return traverseByName(matched.children as MetaTreeNode[], rest);
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
const byNames = traverseByName(metaTree, targetPath);
|
|
339
|
+
if (byNames) {
|
|
340
|
+
return byNames;
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
const topNames = new Set(metaTree.map((node) => String(node.name)));
|
|
344
|
+
if (!topNames.has(String(targetPath[0]))) {
|
|
345
|
+
return traverseByName(metaTree, targetPath.slice(1));
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
return null;
|
|
349
|
+
}
|
|
350
|
+
|
|
268
351
|
/**
|
|
269
352
|
* 上下文筛选项组件
|
|
270
353
|
*/
|
|
271
354
|
export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
272
|
-
({ value, model, rightAsVariable, rightMetaTree, ignoreFieldNames, maxAssociationFieldDepth }) => {
|
|
355
|
+
({ value, model, disabled = false, rightAsVariable, rightMetaTree, ignoreFieldNames, maxAssociationFieldDepth }) => {
|
|
273
356
|
// 使用 View 上下文,确保可访问 ctx.view 的异步子树
|
|
274
357
|
const ctx = useFlowViewContext();
|
|
275
358
|
const t = model.translate;
|
|
@@ -334,6 +417,9 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
334
417
|
// 处理左侧值变化(值由 converters 决定如何解析)
|
|
335
418
|
const handleLeftChange = useCallback(
|
|
336
419
|
(variableValue: string, meta?: MetaTreeNode) => {
|
|
420
|
+
if (disabled) {
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
337
423
|
const prevPath = value.path || '';
|
|
338
424
|
const nextPath = variableValue || '';
|
|
339
425
|
const changed = nextPath !== prevPath;
|
|
@@ -348,7 +434,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
348
434
|
setLeftMeta(meta);
|
|
349
435
|
}
|
|
350
436
|
},
|
|
351
|
-
[value],
|
|
437
|
+
[disabled, value],
|
|
352
438
|
);
|
|
353
439
|
|
|
354
440
|
// 自定义转换器来捕获 MetaTreeNode,并在选择左值时设置默认操作符
|
|
@@ -376,6 +462,9 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
376
462
|
// 处理操作符变化
|
|
377
463
|
const handleOperatorChange = useCallback(
|
|
378
464
|
(operatorValue: string) => {
|
|
465
|
+
if (disabled) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
379
468
|
value.operator = operatorValue;
|
|
380
469
|
const cur = operatorMetaList.find((op) => op.value === operatorValue);
|
|
381
470
|
if (cur?.noValue) {
|
|
@@ -386,7 +475,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
386
475
|
value.noValue = false;
|
|
387
476
|
}
|
|
388
477
|
},
|
|
389
|
-
[operatorMetaList, value],
|
|
478
|
+
[disabled, operatorMetaList, value],
|
|
390
479
|
);
|
|
391
480
|
|
|
392
481
|
// 使用公共静态输入渲染器(抽取到 utils)
|
|
@@ -464,9 +553,12 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
464
553
|
|
|
465
554
|
const setRightValue = useCallback(
|
|
466
555
|
(next: unknown) => {
|
|
556
|
+
if (disabled) {
|
|
557
|
+
return;
|
|
558
|
+
}
|
|
467
559
|
value.value = normalizeRightValueInput(next) as VariableFilterItemValue['value'];
|
|
468
560
|
},
|
|
469
|
-
[value],
|
|
561
|
+
[disabled, value],
|
|
470
562
|
);
|
|
471
563
|
|
|
472
564
|
// 右侧静态输入(无变量模式)与右侧 VariableInput 的静态渲染组件,统一复用
|
|
@@ -520,6 +612,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
520
612
|
{...nextProps}
|
|
521
613
|
style={{ width: '100%', ...pickStyle(nextProps.style) }}
|
|
522
614
|
value={normalized}
|
|
615
|
+
disabled={disabled}
|
|
523
616
|
onChange={(vals: any) => setRightValue(vals)}
|
|
524
617
|
/>
|
|
525
618
|
</div>
|
|
@@ -529,10 +622,19 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
529
622
|
const Comp = staticInputRenderer;
|
|
530
623
|
return (
|
|
531
624
|
<div style={{ flex: '1 1 40%', minWidth: 160, maxWidth: '100%' }}>
|
|
532
|
-
<Comp value={rightValue} onChange={(val) => setRightValue(val)} />
|
|
625
|
+
<Comp value={rightValue} onChange={(val) => setRightValue(val)} disabled={disabled} />
|
|
533
626
|
</div>
|
|
534
627
|
);
|
|
535
|
-
}, [
|
|
628
|
+
}, [
|
|
629
|
+
disabled,
|
|
630
|
+
operator,
|
|
631
|
+
operatorMetaList,
|
|
632
|
+
rightValue,
|
|
633
|
+
staticInputRenderer,
|
|
634
|
+
model.context.app,
|
|
635
|
+
setRightValue,
|
|
636
|
+
enumOptions,
|
|
637
|
+
]);
|
|
536
638
|
|
|
537
639
|
// Null 占位组件(仿照 DefaultValue.tsx 的实现)
|
|
538
640
|
const NullComponent = useMemo(() => {
|
|
@@ -652,6 +754,34 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
652
754
|
};
|
|
653
755
|
}, [getFieldInterface, model, maxAssociationFieldDepth]);
|
|
654
756
|
|
|
757
|
+
useEffect(() => {
|
|
758
|
+
if (leftMeta || !value.path) {
|
|
759
|
+
return;
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
let cancelled = false;
|
|
763
|
+
const restore = async () => {
|
|
764
|
+
const nodes = await enhancedMetaTree();
|
|
765
|
+
if (cancelled) {
|
|
766
|
+
return;
|
|
767
|
+
}
|
|
768
|
+
const path = customConverters.resolvePathFromValue(value.path);
|
|
769
|
+
if (!path?.length) {
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
const matched =
|
|
773
|
+
findMetaTreeNodeByPath(nodes, path) || buildCollectionFieldMetaNode(model.context.collection, path);
|
|
774
|
+
if (matched) {
|
|
775
|
+
setLeftMeta(matched);
|
|
776
|
+
}
|
|
777
|
+
};
|
|
778
|
+
|
|
779
|
+
restore();
|
|
780
|
+
return () => {
|
|
781
|
+
cancelled = true;
|
|
782
|
+
};
|
|
783
|
+
}, [customConverters, enhancedMetaTree, leftMeta, model.context.collection, value.path]);
|
|
784
|
+
|
|
655
785
|
return (
|
|
656
786
|
<Space wrap style={{ width: '100%' }}>
|
|
657
787
|
<VariableInput
|
|
@@ -659,6 +789,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
659
789
|
metaTree={enhancedMetaTree}
|
|
660
790
|
onChange={handleLeftChange}
|
|
661
791
|
converters={customConverters}
|
|
792
|
+
disabled={disabled}
|
|
662
793
|
showValueComponent={false}
|
|
663
794
|
style={{ flex: '1 1 40%', minWidth: 160, maxWidth: '100%' }}
|
|
664
795
|
onlyLeafSelectable={true}
|
|
@@ -670,6 +801,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
670
801
|
style={{ flex: '0 0 140px', minWidth: 120, maxWidth: '100%' }}
|
|
671
802
|
placeholder={t('Comparison')}
|
|
672
803
|
value={operator || undefined}
|
|
804
|
+
disabled={disabled}
|
|
673
805
|
onChange={handleOperatorChange}
|
|
674
806
|
>
|
|
675
807
|
{operatorOptions.map((op) => (
|
|
@@ -686,6 +818,7 @@ export const VariableFilterItem: React.FC<VariableFilterItemProps> = observer(
|
|
|
686
818
|
onChange={(v) => setRightValue(v)}
|
|
687
819
|
metaTree={mergedRightMetaTree}
|
|
688
820
|
converters={rightConverters}
|
|
821
|
+
disabled={disabled}
|
|
689
822
|
showValueComponent
|
|
690
823
|
style={{ flex: '1 1 40%', minWidth: 160, maxWidth: '100%' }}
|
|
691
824
|
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
|
});
|
|
@@ -193,4 +193,13 @@ describe('VariableFilterItem with leftMetaTree', () => {
|
|
|
193
193
|
fireEvent.click(screen.getByTestId('variable-input'));
|
|
194
194
|
expect(value.path).toBe('title');
|
|
195
195
|
});
|
|
196
|
+
|
|
197
|
+
it('restores the saved left meta so the operator select shows its label on reopen', async () => {
|
|
198
|
+
const value = { path: 'title', operator: '$eq', value: 'x' } as any;
|
|
199
|
+
const model = createModelWithCollection();
|
|
200
|
+
|
|
201
|
+
render(<VariableFilterItem value={value} model={model} rightAsVariable={false} />);
|
|
202
|
+
|
|
203
|
+
expect(await screen.findByText('Equals')).toBeInTheDocument();
|
|
204
|
+
});
|
|
196
205
|
});
|
|
@@ -158,6 +158,27 @@ describe('VariableFilterItem', () => {
|
|
|
158
158
|
expect(value.value).toBe('abc');
|
|
159
159
|
});
|
|
160
160
|
|
|
161
|
+
it('passes disabled through to the left selector, operator select, and right variable input', async () => {
|
|
162
|
+
const value: VariableFilterItemValue = { path: '', operator: '', value: '' };
|
|
163
|
+
const model = CreateModel();
|
|
164
|
+
|
|
165
|
+
render(<VariableFilterItem value={value} model={model} rightAsVariable disabled />);
|
|
166
|
+
|
|
167
|
+
const leftVariableInputProps = (globalThis as any).__LAST_VARIABLE_INPUT_PROPS__;
|
|
168
|
+
expect(leftVariableInputProps.disabled).toBe(true);
|
|
169
|
+
|
|
170
|
+
fireEvent.click(screen.getAllByTestId('variable-input')[0]);
|
|
171
|
+
|
|
172
|
+
const variableInputs = screen.getAllByTestId('variable-input');
|
|
173
|
+
expect(variableInputs.length).toBeGreaterThanOrEqual(2);
|
|
174
|
+
const rightVariableInputProps = (globalThis as any).__LAST_VARIABLE_INPUT_PROPS__;
|
|
175
|
+
expect(rightVariableInputProps.disabled).toBe(true);
|
|
176
|
+
|
|
177
|
+
const operatorSelect = document.body.querySelector('.ant-select') as HTMLDivElement | null;
|
|
178
|
+
expect(operatorSelect).not.toBeNull();
|
|
179
|
+
expect(operatorSelect).toHaveClass('ant-select-disabled');
|
|
180
|
+
});
|
|
181
|
+
|
|
161
182
|
it('uses scoped context dataSourceManager when app dataSourceManager has no field interface manager', async () => {
|
|
162
183
|
const value = observable({ path: '', operator: '', value: '' }) as any;
|
|
163
184
|
const model = CreateModel();
|
|
@@ -121,10 +121,11 @@ const Columns = observer<any>(({ record, model, index }) => {
|
|
|
121
121
|
fork.context.defineProperty('recordIndex', {
|
|
122
122
|
get: () => index,
|
|
123
123
|
});
|
|
124
|
+
const rendererKey = `${fork.uid}:${fork.forkId}`;
|
|
124
125
|
const renderer = (
|
|
125
126
|
<FlowModelRenderer
|
|
126
127
|
showFlowSettings={{ showBorder: false, toolbarPosition: 'above' }}
|
|
127
|
-
key={
|
|
128
|
+
key={rendererKey}
|
|
128
129
|
model={fork}
|
|
129
130
|
inputArgs={record}
|
|
130
131
|
fallback={<Skeleton.Button size="small" />}
|
|
@@ -48,12 +48,12 @@ export class VariableFieldFormModel extends FlowModel {
|
|
|
48
48
|
<FormProvider form={this.form}>
|
|
49
49
|
<FormLayout layout={'vertical'}>
|
|
50
50
|
{this.mapSubModels('fields', (field) => {
|
|
51
|
-
//
|
|
51
|
+
// Avoid conflicting with the DOM nodeName property when rendering the temporary input.
|
|
52
52
|
const init = field?.getStepParams?.('fieldSettings', 'init') || {};
|
|
53
53
|
const fp = init?.fieldPath as string | undefined;
|
|
54
54
|
if (fp) {
|
|
55
|
-
const namePath = fp
|
|
56
|
-
const toSet:
|
|
55
|
+
const namePath = (fp === 'nodeName' ? '__nb_variable_field_nodeName' : fp).split('.');
|
|
56
|
+
const toSet: Record<string, unknown> = {};
|
|
57
57
|
if (!field?.props?.id) toSet.id = namePath;
|
|
58
58
|
if (!field?.props?.name) toSet.name = namePath;
|
|
59
59
|
if (Object.keys(toSet).length) field?.setProps?.(toSet);
|
|
@@ -0,0 +1,51 @@
|
|
|
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, screen, waitFor } from '@testing-library/react';
|
|
12
|
+
import React from 'react';
|
|
13
|
+
import { describe, expect, it } from 'vitest';
|
|
14
|
+
import { InputFieldModel } from '../InputFieldModel';
|
|
15
|
+
import { VariableFieldFormModel } from '../VariableFieldFormModel';
|
|
16
|
+
|
|
17
|
+
describe('VariableFieldFormModel', () => {
|
|
18
|
+
it('uses DOM-safe name and id for temporary controls created from reserved form property names', async () => {
|
|
19
|
+
const engine = new FlowEngine();
|
|
20
|
+
engine.registerModels({ InputFieldModel, VariableFieldFormModel });
|
|
21
|
+
const model = engine.createModel<VariableFieldFormModel>({
|
|
22
|
+
use: 'VariableFieldFormModel',
|
|
23
|
+
subModels: {
|
|
24
|
+
fields: [
|
|
25
|
+
{
|
|
26
|
+
use: 'InputFieldModel',
|
|
27
|
+
stepParams: {
|
|
28
|
+
fieldSettings: {
|
|
29
|
+
init: {
|
|
30
|
+
fieldPath: 'nodeName',
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
});
|
|
38
|
+
const field = model.subModels.fields[0];
|
|
39
|
+
|
|
40
|
+
render(<FlowEngineProvider engine={engine}>{model.render()}</FlowEngineProvider>);
|
|
41
|
+
|
|
42
|
+
const input = await screen.findByRole('textbox');
|
|
43
|
+
await waitFor(() => {
|
|
44
|
+
expect(field.props.name).toEqual(['__nb_variable_field_nodeName']);
|
|
45
|
+
});
|
|
46
|
+
expect(input).toHaveAttribute('name', '__nb_variable_field_nodeName');
|
|
47
|
+
expect(input).toHaveAttribute('id', '__nb_variable_field_nodeName');
|
|
48
|
+
expect(field.props.id).toEqual(['__nb_variable_field_nodeName']);
|
|
49
|
+
expect(field.getStepParams('fieldSettings', 'init')).toEqual({ fieldPath: 'nodeName' });
|
|
50
|
+
});
|
|
51
|
+
});
|