@nocobase/client-v2 2.2.0-beta.3 → 2.2.0-beta.6
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/RouteRepository.d.ts +21 -6
- package/es/components/form/JsonTextArea.d.ts +2 -1
- package/es/components/form/VariableJsonTextArea.d.ts +19 -0
- package/es/components/form/index.d.ts +1 -0
- package/es/components/index.d.ts +1 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.d.ts +2 -1
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuModels.d.ts +2 -0
- package/es/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.d.ts +3 -2
- package/es/flow/admin-shell/admin-layout/AdminLayoutSlotModels.d.ts +4 -0
- package/es/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.d.ts +6 -0
- package/es/flow/components/FieldAssignRulesEditor.d.ts +3 -1
- package/es/flow/models/base/GridModel.d.ts +1 -1
- package/es/flow/models/blocks/filter-form/FilterFormBlockModel.d.ts +5 -0
- package/es/flow/models/blocks/form/FormBlockModel.d.ts +4 -0
- package/es/flow/models/blocks/form/value-runtime/rules.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/runtime.d.ts +21 -0
- package/es/flow/models/blocks/form/value-runtime/types.d.ts +2 -2
- package/es/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.d.ts +2 -0
- package/es/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.d.ts +1 -0
- package/es/flow/utils/dateTimeDisplayProps.d.ts +49 -0
- package/es/flow-compat/FieldValidation.d.ts +2 -1
- package/es/index.d.ts +2 -1
- package/es/index.mjs +167 -121
- package/lib/index.js +167 -121
- package/package.json +7 -7
- package/src/RouteRepository.ts +126 -24
- package/src/__tests__/RouteRepository.test.ts +216 -0
- package/src/__tests__/exports.test.ts +16 -0
- package/src/__tests__/settings-center.test.tsx +33 -0
- package/src/components/form/JsonTextArea.tsx +21 -11
- package/src/components/form/VariableJsonTextArea.tsx +175 -0
- package/src/components/form/__tests__/JsonTextArea.test.tsx +43 -0
- package/src/components/form/__tests__/VariableJsonTextArea.test.tsx +86 -0
- package/src/components/form/index.tsx +1 -0
- package/src/components/index.ts +1 -0
- package/src/flow/__tests__/FlowRoute.test.tsx +443 -5
- package/src/flow/actions/__tests__/fieldLinkageRules.scopeDepth.test.ts +150 -1
- package/src/flow/actions/__tests__/linkageAssignField.legacy.test.ts +135 -0
- package/src/flow/actions/__tests__/linkageRules.hiddenSync.restore.test.ts +19 -5
- package/src/flow/actions/__tests__/linkageRules.subFormSetFieldProps.test.ts +92 -0
- package/src/flow/actions/customVariable.tsx +1 -2
- package/src/flow/actions/dateTimeFormat.tsx +42 -28
- package/src/flow/actions/linkageRules.tsx +122 -34
- package/src/flow/actions/runjs.tsx +2 -14
- package/src/flow/actions/validation.tsx +62 -30
- package/src/flow/admin-shell/admin-layout/AdminLayoutComponent.tsx +33 -10
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.test.tsx +310 -0
- package/src/flow/admin-shell/admin-layout/AdminLayoutEntryGuard.tsx +44 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuFlowUtils.ts +5 -3
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuModels.tsx +61 -9
- package/src/flow/admin-shell/admin-layout/AdminLayoutMenuUtils.tsx +71 -8
- package/src/flow/admin-shell/admin-layout/AdminLayoutModel.tsx +1 -1
- package/src/flow/admin-shell/admin-layout/AdminLayoutSlotModels.tsx +21 -8
- package/src/flow/admin-shell/admin-layout/HelpLite.tsx +1 -3
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutComponent.test.tsx +188 -0
- package/src/flow/admin-shell/admin-layout/__tests__/AdminLayoutMenuModels.test.ts +99 -0
- package/src/flow/admin-shell/admin-layout/__tests__/TopbarActionsBar.test.tsx +166 -2
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.test.ts +20 -0
- package/src/flow/admin-shell/admin-layout/resolveAdminRouteRuntimeTarget.ts +33 -5
- package/src/flow/components/DefaultValue.tsx +1 -2
- package/src/flow/components/DynamicFlowsIcon.tsx +447 -126
- package/src/flow/components/FieldAssignRulesEditor.tsx +62 -12
- package/src/flow/components/FlowRoute.tsx +56 -11
- package/src/flow/components/__tests__/DynamicFlowsIcon.test.tsx +148 -2
- package/src/flow/components/__tests__/FieldAssignRulesEditor.test.tsx +81 -4
- package/src/flow/components/code-editor/__tests__/useCodeRunner.test.tsx +2 -17
- package/src/flow/components/code-editor/hooks/useCodeRunner.ts +2 -14
- package/src/flow/components/code-editor/runjsDiagnostics.ts +8 -45
- package/src/flow/flows/editMarkdownFlow.tsx +1 -1
- package/src/flow/index.ts +1 -1
- package/src/flow/internal/utils/operatorSchemaHelper.ts +15 -1
- package/src/flow/models/actions/JSActionModel.tsx +2 -7
- package/src/flow/models/actions/JSCollectionActionModel.tsx +2 -7
- package/src/flow/models/actions/JSItemActionModel.tsx +2 -7
- package/src/flow/models/actions/JSRecordActionModel.tsx +2 -7
- package/src/flow/models/base/ActionModel.tsx +12 -1
- package/src/flow/models/base/GridModel.tsx +38 -7
- package/src/flow/models/base/PageModel/PageModel.tsx +4 -1
- package/src/flow/models/base/PageModel/__tests__/PageModel.test.ts +28 -4
- package/src/flow/models/base/__tests__/ActionModel.test.ts +83 -0
- package/src/flow/models/base/__tests__/GridModel.dragSnapshotContainer.test.ts +239 -1
- package/src/flow/models/base/__tests__/transformRowsToSingleColumn.test.ts +48 -0
- package/src/flow/models/blocks/filter-form/FilterFormBlockModel.tsx +31 -3
- package/src/flow/models/blocks/filter-form/FilterFormJSActionModel.tsx +2 -7
- package/src/flow/models/blocks/filter-form/__tests__/defaultValues.wiring.test.ts +77 -0
- package/src/flow/models/blocks/filter-manager/flow-actions/__tests__/customizeFilterRender.test.tsx +56 -1
- package/src/flow/models/blocks/form/EditFormModel.tsx +1 -0
- package/src/flow/models/blocks/form/FormBlockModel.tsx +7 -0
- package/src/flow/models/blocks/form/JSFormActionModel.tsx +2 -7
- package/src/flow/models/blocks/form/__tests__/FormBlockModel.test.tsx +17 -0
- package/src/flow/models/blocks/form/value-runtime/__tests__/runtime.test.ts +880 -102
- package/src/flow/models/blocks/form/value-runtime/rules.ts +445 -13
- package/src/flow/models/blocks/form/value-runtime/runtime.ts +257 -13
- package/src/flow/models/blocks/form/value-runtime/types.ts +2 -2
- package/src/flow/models/blocks/js-block/JSBlock.tsx +2 -7
- package/src/flow/models/blocks/table/JSColumnModel.tsx +1 -9
- package/src/flow/models/blocks/table/TableActionsColumnModel.tsx +1 -1
- package/src/flow/models/blocks/table/TableBlockModel.tsx +13 -3
- package/src/flow/models/blocks/table/TableColumnModel.tsx +36 -3
- package/src/flow/models/blocks/table/__tests__/TableActionsColumnModel.test.tsx +54 -0
- package/src/flow/models/blocks/table/__tests__/TableBlockModel.mobileSettingsButtons.test.tsx +78 -0
- package/src/flow/models/blocks/table/__tests__/TableColumnModel.test.tsx +159 -1
- package/src/flow/models/fields/AssociationFieldModel/AssociationFieldModel.tsx +1 -1
- package/src/flow/models/fields/AssociationFieldModel/PopupSubTableFieldModel/PopupSubTableFieldModel.tsx +29 -6
- package/src/flow/models/fields/AssociationFieldModel/RecordSelectFieldModel.tsx +16 -4
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableColumnModel.tsx +24 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/SubTableField.tsx +13 -1
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableColumnModel.rowRecord.test.ts +12 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/__tests__/SubTableFieldModel.reset.test.ts +180 -0
- package/src/flow/models/fields/AssociationFieldModel/SubTableFieldModel/index.tsx +6 -4
- package/src/flow/models/fields/AssociationFieldModel/__tests__/AssociationFieldModel.updateAssociation.test.ts +72 -0
- package/src/flow/models/fields/AssociationFieldModel/__tests__/RecordPickerFieldModel.itemContext.test.ts +23 -0
- package/src/flow/models/fields/ClickableFieldModel.tsx +5 -0
- package/src/flow/models/fields/DisplayDateTimeFieldModel.tsx +29 -1
- package/src/flow/models/fields/InputFieldModel.tsx +14 -22
- package/src/flow/models/fields/JSEditableFieldModel.tsx +2 -11
- package/src/flow/models/fields/JSFieldModel.tsx +2 -7
- package/src/flow/models/fields/JSItemModel.tsx +2 -14
- package/src/flow/models/fields/SelectFieldModel.tsx +6 -4
- package/src/flow/models/fields/__tests__/DisplayDateTimeFieldModel.test.tsx +96 -0
- package/src/flow/models/fields/__tests__/InputFieldModel.test.tsx +17 -0
- package/src/flow/models/fields/__tests__/SelectFieldModel.test.tsx +43 -0
- package/src/flow/models/topbar/TopbarActionModel.tsx +93 -10
- package/src/flow/utils/__tests__/dateTimeFormat.test.ts +258 -0
- package/src/flow/utils/dateTimeDisplayProps.ts +135 -0
- package/src/flow-compat/FieldValidation.tsx +122 -60
- package/src/flow-compat/Popover.tsx +43 -4
- package/src/flow-compat/__tests__/Popover.test.tsx +34 -0
- package/src/index.ts +8 -1
- package/src/layout-manager/__tests__/LayoutRoute.test.tsx +41 -1
- package/src/settings-center/AdminSettingsLayout.tsx +3 -0
- package/src/settings-center/plugin-manager/index.tsx +3 -0
|
@@ -28,7 +28,7 @@ import { buildFieldAssignCascaderOptionsFromCollection, type FieldAssignCascader
|
|
|
28
28
|
import { isRunJSValue, isVariableExpression, type MetaTreeNode } from '@nocobase/flow-engine';
|
|
29
29
|
import { isToManyAssociationField } from '../internal/utils/modelUtils';
|
|
30
30
|
|
|
31
|
-
export type AssignMode = 'default' | 'assign';
|
|
31
|
+
export type AssignMode = 'default' | 'assign' | 'override';
|
|
32
32
|
|
|
33
33
|
type CollectionFieldLike = {
|
|
34
34
|
name?: unknown;
|
|
@@ -89,6 +89,8 @@ export interface FieldAssignRulesEditorProps {
|
|
|
89
89
|
defaultMode?: AssignMode;
|
|
90
90
|
/** 固定 mode:用于“仅默认值/仅赋值”的场景 */
|
|
91
91
|
fixedMode?: AssignMode;
|
|
92
|
+
/** 可选 mode 列表;fixedMode 优先级更高 */
|
|
93
|
+
allowedModes?: AssignMode[];
|
|
92
94
|
/** 是否显示 condition */
|
|
93
95
|
showCondition?: boolean;
|
|
94
96
|
/** 是否显示 enable 开关 */
|
|
@@ -109,6 +111,23 @@ export interface FieldAssignRulesEditorProps {
|
|
|
109
111
|
maxAssociationFieldDepth?: number;
|
|
110
112
|
}
|
|
111
113
|
|
|
114
|
+
const ALL_ASSIGN_MODES: AssignMode[] = ['default', 'override', 'assign'];
|
|
115
|
+
|
|
116
|
+
function isAssignMode(mode: unknown): mode is AssignMode {
|
|
117
|
+
return mode === 'default' || mode === 'override' || mode === 'assign';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function normalizeAssignModes(modes?: AssignMode[]): AssignMode[] {
|
|
121
|
+
const source = Array.isArray(modes) && modes.length ? modes : ALL_ASSIGN_MODES;
|
|
122
|
+
const out: AssignMode[] = [];
|
|
123
|
+
for (const mode of source) {
|
|
124
|
+
if (!isAssignMode(mode)) continue;
|
|
125
|
+
if (out.includes(mode)) continue;
|
|
126
|
+
out.push(mode);
|
|
127
|
+
}
|
|
128
|
+
return out.length ? out : ALL_ASSIGN_MODES;
|
|
129
|
+
}
|
|
130
|
+
|
|
112
131
|
export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (props) => {
|
|
113
132
|
const {
|
|
114
133
|
t,
|
|
@@ -118,6 +137,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
118
137
|
onChange,
|
|
119
138
|
defaultMode = 'assign',
|
|
120
139
|
fixedMode,
|
|
140
|
+
allowedModes: rawAllowedModes,
|
|
121
141
|
showCondition = true,
|
|
122
142
|
showEnable = true,
|
|
123
143
|
showValueEditorWhenNoField = false,
|
|
@@ -129,6 +149,28 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
129
149
|
} = props;
|
|
130
150
|
|
|
131
151
|
const value = React.useMemo(() => (Array.isArray(rawValue) ? rawValue : []), [rawValue]);
|
|
152
|
+
const allowedModes = React.useMemo(
|
|
153
|
+
() => normalizeAssignModes(fixedMode ? [fixedMode] : rawAllowedModes),
|
|
154
|
+
[fixedMode, rawAllowedModes],
|
|
155
|
+
);
|
|
156
|
+
const getDefaultMode = React.useCallback((): AssignMode => {
|
|
157
|
+
if (fixedMode) return fixedMode;
|
|
158
|
+
if (isAssignMode(defaultMode) && allowedModes.includes(defaultMode)) return defaultMode;
|
|
159
|
+
return allowedModes[0] || 'assign';
|
|
160
|
+
}, [allowedModes, defaultMode, fixedMode]);
|
|
161
|
+
const normalizeItemMode = React.useCallback(
|
|
162
|
+
(item: FieldAssignRuleItem): FieldAssignRuleItem => {
|
|
163
|
+
const mode = isAssignMode(item?.mode) && allowedModes.includes(item.mode) ? item.mode : getDefaultMode();
|
|
164
|
+
return item?.mode === mode ? item : { ...item, mode };
|
|
165
|
+
},
|
|
166
|
+
[allowedModes, getDefaultMode],
|
|
167
|
+
);
|
|
168
|
+
const emitChange = React.useCallback(
|
|
169
|
+
(next: FieldAssignRuleItem[]) => {
|
|
170
|
+
onChange?.(next.map((item) => normalizeItemMode(item)));
|
|
171
|
+
},
|
|
172
|
+
[normalizeItemMode, onChange],
|
|
173
|
+
);
|
|
132
174
|
const [cascaderOptions, setCascaderOptions] = React.useState<FieldAssignCascaderOption[]>(() =>
|
|
133
175
|
Array.isArray(fieldOptions) ? (fieldOptions as FieldAssignCascaderOption[]) : [],
|
|
134
176
|
);
|
|
@@ -312,7 +354,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
312
354
|
|
|
313
355
|
const patchItem = (index: number, patch: Partial<FieldAssignRuleItem>) => {
|
|
314
356
|
const next = value.map((it, i) => (i === index ? { ...it, ...patch } : it));
|
|
315
|
-
|
|
357
|
+
emitChange(next);
|
|
316
358
|
};
|
|
317
359
|
|
|
318
360
|
const removeItem = (index: number) => {
|
|
@@ -334,7 +376,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
334
376
|
setSyncingRuleKey(null);
|
|
335
377
|
}
|
|
336
378
|
const next = value.filter((_, i) => i !== index);
|
|
337
|
-
|
|
379
|
+
emitChange(next);
|
|
338
380
|
};
|
|
339
381
|
|
|
340
382
|
const moveItem = (index: number, direction: 'up' | 'down') => {
|
|
@@ -343,7 +385,7 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
343
385
|
if (targetIndex < 0 || targetIndex >= next.length) return;
|
|
344
386
|
const [item] = next.splice(index, 1);
|
|
345
387
|
next.splice(targetIndex, 0, item);
|
|
346
|
-
|
|
388
|
+
emitChange(next);
|
|
347
389
|
};
|
|
348
390
|
|
|
349
391
|
const addItem = () => {
|
|
@@ -352,29 +394,33 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
352
394
|
{
|
|
353
395
|
key: uid(),
|
|
354
396
|
enable: true,
|
|
355
|
-
mode:
|
|
397
|
+
mode: getDefaultMode(),
|
|
356
398
|
condition: { logic: '$and', items: [] },
|
|
357
399
|
targetPath: undefined,
|
|
358
400
|
value: undefined,
|
|
359
401
|
},
|
|
360
402
|
];
|
|
361
|
-
|
|
403
|
+
emitChange(next);
|
|
362
404
|
};
|
|
363
405
|
|
|
364
406
|
const getEffectiveMode = (item: FieldAssignRuleItem): AssignMode => {
|
|
365
407
|
if (fixedMode) return fixedMode;
|
|
366
|
-
|
|
408
|
+
if (isAssignMode(item?.mode) && allowedModes.includes(item.mode)) return item.mode;
|
|
409
|
+
return getDefaultMode();
|
|
367
410
|
};
|
|
368
411
|
|
|
369
412
|
const renderAssignModeLabel = React.useCallback(
|
|
370
413
|
(mode: AssignMode) => {
|
|
371
|
-
const modeText =
|
|
414
|
+
const modeText =
|
|
415
|
+
mode === 'default' ? t('Default value') : mode === 'override' ? t('Override value') : t('Fixed value');
|
|
372
416
|
const modeHelpText =
|
|
373
417
|
mode === 'default'
|
|
374
418
|
? t(
|
|
375
419
|
'A pre-filled value. Editable, for new entries only, and won’t affect existing data (including empty values).',
|
|
376
420
|
)
|
|
377
|
-
:
|
|
421
|
+
: mode === 'override'
|
|
422
|
+
? t('Overrides existing values until the user edits it.')
|
|
423
|
+
: t('A system-set value. Read-only.');
|
|
378
424
|
|
|
379
425
|
const preventModeToggle = (e: React.MouseEvent<HTMLElement>) => {
|
|
380
426
|
e.preventDefault();
|
|
@@ -627,7 +673,8 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
627
673
|
|
|
628
674
|
const renderPanelHeader = (item: FieldAssignRuleItem, index: number) => {
|
|
629
675
|
const mode = getEffectiveMode(item);
|
|
630
|
-
const modeText =
|
|
676
|
+
const modeText =
|
|
677
|
+
mode === 'default' ? t('Default value') : mode === 'override' ? t('Override value') : t('Fixed value');
|
|
631
678
|
const fieldLabel = getFieldLabel(item.targetPath);
|
|
632
679
|
const title = fieldLabel ? String(fieldLabel) : t('Please select field');
|
|
633
680
|
|
|
@@ -935,8 +982,11 @@ export const FieldAssignRulesEditor: React.FC<FieldAssignRulesEditorProps> = (pr
|
|
|
935
982
|
onChange={(event) => patchItem(index, { mode: event.target.value as AssignMode })}
|
|
936
983
|
>
|
|
937
984
|
<Space size={16}>
|
|
938
|
-
|
|
939
|
-
|
|
985
|
+
{allowedModes.map((allowedMode) => (
|
|
986
|
+
<Radio key={allowedMode} value={allowedMode}>
|
|
987
|
+
{renderAssignModeLabel(allowedMode)}
|
|
988
|
+
</Radio>
|
|
989
|
+
))}
|
|
940
990
|
</Space>
|
|
941
991
|
</Radio.Group>
|
|
942
992
|
</div>
|
|
@@ -28,6 +28,13 @@ type FlowRouteGuardState = {
|
|
|
28
28
|
|
|
29
29
|
export type LegacyPageBehavior = 'redirect' | 'notFound' | 'bridge';
|
|
30
30
|
|
|
31
|
+
type FlowRouteLayoutContext = {
|
|
32
|
+
authCheck?: boolean;
|
|
33
|
+
routePath?: string;
|
|
34
|
+
routeName?: string;
|
|
35
|
+
uid?: string;
|
|
36
|
+
};
|
|
37
|
+
|
|
31
38
|
export type FlowRouteProps = {
|
|
32
39
|
pageUid?: string;
|
|
33
40
|
active?: boolean;
|
|
@@ -38,7 +45,7 @@ export type FlowRouteProps = {
|
|
|
38
45
|
const getDefaultAdminLayoutModel = (flowEngine: FlowEngine) =>
|
|
39
46
|
getAdminLayoutModel<AdminLayoutModel>(flowEngine, { required: true });
|
|
40
47
|
|
|
41
|
-
const getDefaultLayoutModel = (flowEngine: FlowEngine, contextLayout?:
|
|
48
|
+
const getDefaultLayoutModel = (flowEngine: FlowEngine, contextLayout?: FlowRouteLayoutContext) => {
|
|
42
49
|
const layout = contextLayout || flowEngine.context.layout;
|
|
43
50
|
|
|
44
51
|
if (layout?.uid) {
|
|
@@ -48,7 +55,10 @@ const getDefaultLayoutModel = (flowEngine: FlowEngine, contextLayout?: any) => {
|
|
|
48
55
|
return getDefaultAdminLayoutModel(flowEngine);
|
|
49
56
|
};
|
|
50
57
|
|
|
51
|
-
const getDefaultLegacyPageBehavior = (
|
|
58
|
+
const getDefaultLegacyPageBehavior = (
|
|
59
|
+
flowEngine: FlowEngine,
|
|
60
|
+
contextLayout?: FlowRouteLayoutContext,
|
|
61
|
+
): LegacyPageBehavior => {
|
|
52
62
|
const layout = contextLayout || flowEngine.context.layout;
|
|
53
63
|
|
|
54
64
|
if (layout?.routeName && layout.routeName !== 'admin') {
|
|
@@ -58,6 +68,8 @@ const getDefaultLegacyPageBehavior = (flowEngine: FlowEngine, contextLayout?: an
|
|
|
58
68
|
return 'redirect';
|
|
59
69
|
};
|
|
60
70
|
|
|
71
|
+
const shouldRequireAccessibleRoute = (contextLayout?: FlowRouteLayoutContext) => contextLayout?.authCheck !== false;
|
|
72
|
+
|
|
61
73
|
const hasFlowModel = async (flowEngine: FlowEngine, pageUid: string) => {
|
|
62
74
|
if (flowEngine.getModel(pageUid)) {
|
|
63
75
|
return true;
|
|
@@ -148,18 +160,46 @@ const BridgeFlowRoute = ({
|
|
|
148
160
|
* @throws {Error} 当缺少 `route.params.name` 时抛出异常
|
|
149
161
|
*/
|
|
150
162
|
const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
163
|
+
const {
|
|
164
|
+
active,
|
|
165
|
+
getLayoutModel: getLayoutModelProp,
|
|
166
|
+
legacyPageBehavior: legacyPageBehaviorProp,
|
|
167
|
+
pageUid: pageUidProp,
|
|
168
|
+
} = props;
|
|
151
169
|
const flowEngine = useFlowEngine();
|
|
152
|
-
const flowContext = useFlowContext<
|
|
170
|
+
const flowContext = useFlowContext<{ layout?: FlowRouteLayoutContext }>();
|
|
153
171
|
const contextLayout = flowContext?.layout;
|
|
172
|
+
const propsLayoutModel = useMemo(() => getLayoutModelProp?.(flowEngine), [flowEngine, getLayoutModelProp]);
|
|
173
|
+
const rawRouteLayout = contextLayout || propsLayoutModel?.layout;
|
|
174
|
+
const routeLayoutUid = rawRouteLayout?.uid;
|
|
175
|
+
const routeLayoutRouteName = rawRouteLayout?.routeName;
|
|
176
|
+
const routeLayoutRoutePath = rawRouteLayout?.routePath;
|
|
177
|
+
const routeLayoutAuthCheck = rawRouteLayout?.authCheck;
|
|
178
|
+
const routeLayout = useMemo(() => {
|
|
179
|
+
if (
|
|
180
|
+
!routeLayoutUid &&
|
|
181
|
+
!routeLayoutRouteName &&
|
|
182
|
+
!routeLayoutRoutePath &&
|
|
183
|
+
typeof routeLayoutAuthCheck === 'undefined'
|
|
184
|
+
) {
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
authCheck: routeLayoutAuthCheck,
|
|
189
|
+
routeName: routeLayoutRouteName,
|
|
190
|
+
routePath: routeLayoutRoutePath,
|
|
191
|
+
uid: routeLayoutUid,
|
|
192
|
+
};
|
|
193
|
+
}, [routeLayoutAuthCheck, routeLayoutRouteName, routeLayoutRoutePath, routeLayoutUid]);
|
|
154
194
|
const getLayoutModel = useMemo(
|
|
155
|
-
() =>
|
|
156
|
-
[
|
|
195
|
+
() => getLayoutModelProp || ((engine: FlowEngine) => getDefaultLayoutModel(engine, routeLayout)),
|
|
196
|
+
[getLayoutModelProp, routeLayout],
|
|
157
197
|
);
|
|
158
|
-
const legacyPageBehavior =
|
|
198
|
+
const legacyPageBehavior = legacyPageBehaviorProp || getDefaultLegacyPageBehavior(flowEngine, routeLayout);
|
|
159
199
|
const app = useApp();
|
|
160
200
|
const routeRepository = flowEngine.context.routeRepository;
|
|
161
201
|
const params = useParams();
|
|
162
|
-
const pageUid =
|
|
202
|
+
const pageUid = pageUidProp || params?.name;
|
|
163
203
|
const skipRouteRepositoryCheck = !routeRepository;
|
|
164
204
|
const [guardState, setGuardState] = useState<FlowRouteGuardState>({
|
|
165
205
|
pending: true,
|
|
@@ -196,6 +236,11 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
196
236
|
}
|
|
197
237
|
|
|
198
238
|
const route = skipRouteRepositoryCheck ? undefined : routeRepository?.getRouteBySchemaUid?.(pageUid);
|
|
239
|
+
if (!route && !skipRouteRepositoryCheck && shouldRequireAccessibleRoute(routeLayout)) {
|
|
240
|
+
setGuardState({ pending: false, allowBridge: false, notFound: true });
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
199
244
|
if (!route && legacyPageBehavior === 'notFound') {
|
|
200
245
|
const flowModelExists = await hasFlowModel(flowEngine, pageUid);
|
|
201
246
|
if (active && requestId === requestIdRef.current) {
|
|
@@ -245,12 +290,12 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
245
290
|
}
|
|
246
291
|
};
|
|
247
292
|
|
|
248
|
-
|
|
293
|
+
run();
|
|
249
294
|
|
|
250
295
|
return () => {
|
|
251
296
|
active = false;
|
|
252
297
|
};
|
|
253
|
-
}, [app, flowEngine, legacyPageBehavior, pageUid, routeRepository, skipRouteRepositoryCheck]);
|
|
298
|
+
}, [app, flowEngine, legacyPageBehavior, pageUid, routeLayout, routeRepository, skipRouteRepositoryCheck]);
|
|
254
299
|
|
|
255
300
|
const content = useMemo(() => {
|
|
256
301
|
if (guardState.pending) {
|
|
@@ -265,8 +310,8 @@ const FlowRoute = (props: FlowRouteProps = {}) => {
|
|
|
265
310
|
return null;
|
|
266
311
|
}
|
|
267
312
|
|
|
268
|
-
return <BridgeFlowRoute pageUid={pageUid} active={
|
|
269
|
-
}, [getLayoutModel, guardState.allowBridge, guardState.notFound, guardState.pending, pageUid
|
|
313
|
+
return <BridgeFlowRoute pageUid={pageUid} active={active} getLayoutModel={getLayoutModel} />;
|
|
314
|
+
}, [active, getLayoutModel, guardState.allowBridge, guardState.notFound, guardState.pending, pageUid]);
|
|
270
315
|
|
|
271
316
|
return content;
|
|
272
317
|
};
|
|
@@ -10,19 +10,24 @@
|
|
|
10
10
|
import React from 'react';
|
|
11
11
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
12
12
|
import { render, screen, userEvent, waitFor } from '@nocobase/test/client';
|
|
13
|
-
import { ActionScene, FlowEngine, FlowModel, GLOBAL_EMBED_CONTAINER_ID } from '@nocobase/flow-engine';
|
|
13
|
+
import { ActionScene, FlowEngine, FlowModel, GLOBAL_EMBED_CONTAINER_ID, useFlowContext } from '@nocobase/flow-engine';
|
|
14
14
|
import { DynamicFlowsIcon } from '../DynamicFlowsIcon';
|
|
15
15
|
|
|
16
16
|
const mockState = vi.hoisted(() => ({
|
|
17
17
|
capturedSelectProps: [] as any[],
|
|
18
|
+
capturedTabsProps: [] as any[],
|
|
18
19
|
flowContextValue: undefined as any,
|
|
19
20
|
}));
|
|
20
21
|
|
|
21
22
|
vi.mock('@nocobase/flow-engine', async (importOriginal) => {
|
|
22
23
|
const actual = await importOriginal<typeof import('@nocobase/flow-engine')>();
|
|
24
|
+
const React = await import('react');
|
|
23
25
|
return {
|
|
24
26
|
...actual,
|
|
25
|
-
useFlowContext: () =>
|
|
27
|
+
useFlowContext: () => {
|
|
28
|
+
const context = React.useContext(actual.FlowReactContext);
|
|
29
|
+
return context?.view ? context : mockState.flowContextValue;
|
|
30
|
+
},
|
|
26
31
|
};
|
|
27
32
|
});
|
|
28
33
|
|
|
@@ -62,6 +67,33 @@ vi.mock('antd', async (importOriginal) => {
|
|
|
62
67
|
return <div data-testid={String(props.placeholder || 'select')} />;
|
|
63
68
|
},
|
|
64
69
|
Space: ({ children }: any) => <div>{children}</div>,
|
|
70
|
+
Tabs: (props: any) => {
|
|
71
|
+
mockState.capturedTabsProps.push(props);
|
|
72
|
+
const { items, activeKey, onChange, tabBarStyle } = props;
|
|
73
|
+
const currentKey = activeKey || items?.[0]?.key;
|
|
74
|
+
return (
|
|
75
|
+
<div data-testid="dynamic-flow-source-tabs">
|
|
76
|
+
<div role="tablist" style={tabBarStyle}>
|
|
77
|
+
{items?.map((item: any) => (
|
|
78
|
+
<button
|
|
79
|
+
key={item.key}
|
|
80
|
+
role="tab"
|
|
81
|
+
aria-selected={item.key === currentKey}
|
|
82
|
+
type="button"
|
|
83
|
+
onClick={() => onChange?.(item.key)}
|
|
84
|
+
>
|
|
85
|
+
{item.label}
|
|
86
|
+
</button>
|
|
87
|
+
))}
|
|
88
|
+
</div>
|
|
89
|
+
{items?.map((item: any) => (
|
|
90
|
+
<div key={item.key} hidden={item.key !== currentKey}>
|
|
91
|
+
{item.children}
|
|
92
|
+
</div>
|
|
93
|
+
))}
|
|
94
|
+
</div>
|
|
95
|
+
);
|
|
96
|
+
},
|
|
65
97
|
Tooltip: ({ children }: any) => <>{children}</>,
|
|
66
98
|
};
|
|
67
99
|
});
|
|
@@ -83,6 +115,8 @@ const openDynamicFlowsEditor = async (model: FlowModel) => {
|
|
|
83
115
|
expect(trigger).not.toBeNull();
|
|
84
116
|
await userEvent.click(trigger as Element);
|
|
85
117
|
|
|
118
|
+
await waitFor(() => expect(model.context.viewer.embed).toHaveBeenCalled());
|
|
119
|
+
|
|
86
120
|
const embedCall = (model.context.viewer.embed as any).mock.calls.at(-1)?.[0];
|
|
87
121
|
expect(embedCall?.content).toBeTruthy();
|
|
88
122
|
|
|
@@ -163,6 +197,38 @@ const createModel = (options: { preventClose?: boolean; hiddenClose?: boolean; s
|
|
|
163
197
|
return model;
|
|
164
198
|
};
|
|
165
199
|
|
|
200
|
+
const createPeerModel = (model: FlowModel, uid: string, saveStepParams = vi.fn(async () => undefined)) => {
|
|
201
|
+
const PeerModel = model.constructor as typeof FlowModel;
|
|
202
|
+
const peer = new PeerModel({
|
|
203
|
+
uid,
|
|
204
|
+
use: PeerModel,
|
|
205
|
+
flowEngine: model.flowEngine,
|
|
206
|
+
stepParams: {},
|
|
207
|
+
subModels: {},
|
|
208
|
+
flowRegistry: {
|
|
209
|
+
flow1: {
|
|
210
|
+
title: 'Template event flow',
|
|
211
|
+
on: 'click',
|
|
212
|
+
steps: {
|
|
213
|
+
notifyStep: {
|
|
214
|
+
use: 'notify',
|
|
215
|
+
},
|
|
216
|
+
},
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
} as any);
|
|
220
|
+
|
|
221
|
+
peer.context.defineProperty('message', {
|
|
222
|
+
value: {
|
|
223
|
+
error: vi.fn(),
|
|
224
|
+
success: vi.fn(),
|
|
225
|
+
},
|
|
226
|
+
});
|
|
227
|
+
vi.spyOn(peer, 'saveStepParams').mockImplementation(saveStepParams);
|
|
228
|
+
|
|
229
|
+
return peer;
|
|
230
|
+
};
|
|
231
|
+
|
|
166
232
|
const createView = () => {
|
|
167
233
|
let destroyed = false;
|
|
168
234
|
let closingPromise: Promise<boolean | void> | undefined;
|
|
@@ -189,6 +255,9 @@ const createView = () => {
|
|
|
189
255
|
destroyed = true;
|
|
190
256
|
}),
|
|
191
257
|
beforeClose: undefined as any,
|
|
258
|
+
inputArgs: {
|
|
259
|
+
pageActive: true,
|
|
260
|
+
},
|
|
192
261
|
};
|
|
193
262
|
return view;
|
|
194
263
|
};
|
|
@@ -196,6 +265,7 @@ const createView = () => {
|
|
|
196
265
|
describe('DynamicFlowsIcon', () => {
|
|
197
266
|
beforeEach(() => {
|
|
198
267
|
mockState.capturedSelectProps.length = 0;
|
|
268
|
+
mockState.capturedTabsProps.length = 0;
|
|
199
269
|
mockState.flowContextValue = {
|
|
200
270
|
modal: {
|
|
201
271
|
confirm: vi.fn(),
|
|
@@ -377,4 +447,80 @@ describe('DynamicFlowsIcon', () => {
|
|
|
377
447
|
expect(view.destroy).toHaveBeenCalledTimes(1);
|
|
378
448
|
expect(model.context.message.success).toHaveBeenCalledWith('Configuration saved');
|
|
379
449
|
});
|
|
450
|
+
|
|
451
|
+
it('renders source tabs and closes the editor when saving one source', async () => {
|
|
452
|
+
const model = createModel();
|
|
453
|
+
const targetSaveStepParams = vi.fn(async () => undefined);
|
|
454
|
+
const target = createPeerModel(model, 'target-model', targetSaveStepParams);
|
|
455
|
+
|
|
456
|
+
model.flowEngine.flowSettings.registerDynamicFlowSourceProvider({
|
|
457
|
+
key: 'test-source-provider',
|
|
458
|
+
getSources: () => [{ key: 'referenced-template', label: 'Referenced template', model: target }],
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const { view } = await openDynamicFlowsEditor(model);
|
|
462
|
+
|
|
463
|
+
expect(screen.getByRole('tab', { name: 'Current block' })).toHaveAttribute('aria-selected', 'true');
|
|
464
|
+
expect(mockState.capturedTabsProps.at(-1)?.tabBarStyle?.paddingLeft).toBeGreaterThan(0);
|
|
465
|
+
await userEvent.click(screen.getByRole('tab', { name: 'Referenced template' }));
|
|
466
|
+
|
|
467
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
468
|
+
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
|
469
|
+
|
|
470
|
+
expect(targetSaveStepParams).toHaveBeenCalledTimes(1);
|
|
471
|
+
expect(target.flowRegistry.getFlows().size).toBe(2);
|
|
472
|
+
expect(view.destroy).toHaveBeenCalledTimes(1);
|
|
473
|
+
expect(target.context.message.success).toHaveBeenCalledWith('Configuration saved');
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
it('saves all dirty sources without discard confirmation', async () => {
|
|
477
|
+
const saveStepParams = vi.fn(async () => undefined);
|
|
478
|
+
const model = createModel({ saveStepParams });
|
|
479
|
+
const targetSaveStepParams = vi.fn(async () => undefined);
|
|
480
|
+
const target = createPeerModel(model, 'target-model', targetSaveStepParams);
|
|
481
|
+
mockState.flowContextValue.modal.confirm.mockResolvedValue(false);
|
|
482
|
+
|
|
483
|
+
model.flowEngine.flowSettings.registerDynamicFlowSourceProvider({
|
|
484
|
+
key: 'test-source-unsaved-provider',
|
|
485
|
+
getSources: () => [{ key: 'referenced-template', label: 'Referenced template', model: target }],
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
const { view } = await openDynamicFlowsEditor(model);
|
|
489
|
+
|
|
490
|
+
await userEvent.click(screen.getByRole('tab', { name: 'Referenced template' }));
|
|
491
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
492
|
+
await userEvent.click(screen.getByRole('tab', { name: 'Current block' }));
|
|
493
|
+
await userEvent.click(screen.getByRole('button', { name: 'Add event flow' }));
|
|
494
|
+
await userEvent.click(screen.getByRole('button', { name: 'Save' }));
|
|
495
|
+
|
|
496
|
+
expect(saveStepParams).toHaveBeenCalledTimes(1);
|
|
497
|
+
expect(targetSaveStepParams).toHaveBeenCalledTimes(1);
|
|
498
|
+
expect(mockState.flowContextValue.modal.confirm).not.toHaveBeenCalled();
|
|
499
|
+
expect(view.close).toHaveBeenCalledTimes(1);
|
|
500
|
+
expect(view.destroy).toHaveBeenCalledTimes(1);
|
|
501
|
+
expect(model.flowRegistry.getFlows().size).toBe(2);
|
|
502
|
+
expect(target.flowRegistry.getFlows().size).toBe(2);
|
|
503
|
+
});
|
|
504
|
+
|
|
505
|
+
it('provides the active source model context inside source tabs', async () => {
|
|
506
|
+
const model = createModel();
|
|
507
|
+
const target = createPeerModel(model, 'target-model');
|
|
508
|
+
const seenModelUids: string[] = [];
|
|
509
|
+
const ContextProbe = () => {
|
|
510
|
+
const ctx = useFlowContext<any>();
|
|
511
|
+
seenModelUids.push(ctx?.model?.uid);
|
|
512
|
+
return null;
|
|
513
|
+
};
|
|
514
|
+
model.flowEngine.flowSettings.renderStepForm = vi.fn(() => <ContextProbe />) as any;
|
|
515
|
+
|
|
516
|
+
model.flowEngine.flowSettings.registerDynamicFlowSourceProvider({
|
|
517
|
+
key: 'test-source-context-provider',
|
|
518
|
+
getSources: () => [{ key: 'referenced-template', label: 'Referenced template', model: target }],
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
await openDynamicFlowsEditor(model);
|
|
522
|
+
await userEvent.click(screen.getByRole('tab', { name: 'Referenced template' }));
|
|
523
|
+
|
|
524
|
+
await waitFor(() => expect(seenModelUids).toContain('target-model'));
|
|
525
|
+
});
|
|
380
526
|
});
|
|
@@ -795,15 +795,23 @@ describe('FieldAssignRulesEditor', () => {
|
|
|
795
795
|
);
|
|
796
796
|
|
|
797
797
|
expect(screen.getByLabelText('Default value')).toBeInTheDocument();
|
|
798
|
+
expect(screen.getByLabelText('Override value')).toBeInTheDocument();
|
|
798
799
|
expect(screen.getByLabelText('Fixed value')).toBeInTheDocument();
|
|
799
800
|
expect(screen.getByLabelText('Fixed value')).toBeChecked();
|
|
800
801
|
await userEvent.click(screen.getByLabelText('Default value'));
|
|
801
802
|
await waitFor(() => {
|
|
802
803
|
expect(onChange).toHaveBeenCalled();
|
|
803
804
|
});
|
|
804
|
-
|
|
805
|
-
const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as FieldAssignRuleItem[];
|
|
805
|
+
let lastCall = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as FieldAssignRuleItem[];
|
|
806
806
|
expect(lastCall?.[0]?.mode).toBe('default');
|
|
807
|
+
|
|
808
|
+
onChange.mockClear();
|
|
809
|
+
await userEvent.click(screen.getByLabelText('Override value'));
|
|
810
|
+
await waitFor(() => {
|
|
811
|
+
expect(onChange).toHaveBeenCalled();
|
|
812
|
+
});
|
|
813
|
+
lastCall = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as FieldAssignRuleItem[];
|
|
814
|
+
expect(lastCall?.[0]?.mode).toBe('override');
|
|
807
815
|
});
|
|
808
816
|
|
|
809
817
|
it('shows assignment mode tooltips for each option', async () => {
|
|
@@ -821,7 +829,7 @@ describe('FieldAssignRulesEditor', () => {
|
|
|
821
829
|
);
|
|
822
830
|
|
|
823
831
|
const questionIcons = Array.from(container.querySelectorAll('.anticon-question-circle'));
|
|
824
|
-
expect(questionIcons).toHaveLength(
|
|
832
|
+
expect(questionIcons).toHaveLength(3);
|
|
825
833
|
|
|
826
834
|
await userEvent.hover(questionIcons[0] as Element);
|
|
827
835
|
await waitFor(() => {
|
|
@@ -835,11 +843,80 @@ describe('FieldAssignRulesEditor', () => {
|
|
|
835
843
|
await userEvent.unhover(questionIcons[0] as Element);
|
|
836
844
|
|
|
837
845
|
await userEvent.hover(questionIcons[1] as Element);
|
|
846
|
+
await waitFor(() => {
|
|
847
|
+
expect(screen.getByText('Overrides existing values until the user edits it.')).toBeInTheDocument();
|
|
848
|
+
});
|
|
849
|
+
|
|
850
|
+
await userEvent.unhover(questionIcons[1] as Element);
|
|
851
|
+
|
|
852
|
+
await userEvent.hover(questionIcons[2] as Element);
|
|
838
853
|
await waitFor(() => {
|
|
839
854
|
expect(screen.getByText('A system-set value. Read-only.')).toBeInTheDocument();
|
|
840
855
|
});
|
|
841
856
|
});
|
|
842
857
|
|
|
858
|
+
it('honors allowed assignment modes', () => {
|
|
859
|
+
const value: FieldAssignRuleItem[] = [
|
|
860
|
+
{
|
|
861
|
+
key: '1',
|
|
862
|
+
enable: true,
|
|
863
|
+
targetPath: 'title',
|
|
864
|
+
mode: 'override',
|
|
865
|
+
},
|
|
866
|
+
];
|
|
867
|
+
|
|
868
|
+
render(
|
|
869
|
+
wrap(
|
|
870
|
+
<FieldAssignRulesEditor
|
|
871
|
+
t={t}
|
|
872
|
+
fieldOptions={[]}
|
|
873
|
+
value={value}
|
|
874
|
+
allowedModes={['default', 'assign']}
|
|
875
|
+
showCondition={false}
|
|
876
|
+
showEnable={false}
|
|
877
|
+
/>,
|
|
878
|
+
),
|
|
879
|
+
);
|
|
880
|
+
|
|
881
|
+
expect(screen.getByLabelText('Default value')).toBeInTheDocument();
|
|
882
|
+
expect(screen.getByLabelText('Fixed value')).toBeInTheDocument();
|
|
883
|
+
expect(screen.queryByLabelText('Override value')).toBeNull();
|
|
884
|
+
expect(screen.getByLabelText('Fixed value')).toBeChecked();
|
|
885
|
+
});
|
|
886
|
+
|
|
887
|
+
it('normalizes disallowed assignment mode before emitting changes', async () => {
|
|
888
|
+
const onChange = vi.fn();
|
|
889
|
+
const value: FieldAssignRuleItem[] = [
|
|
890
|
+
{
|
|
891
|
+
key: '1',
|
|
892
|
+
enable: true,
|
|
893
|
+
targetPath: 'title',
|
|
894
|
+
mode: 'override',
|
|
895
|
+
},
|
|
896
|
+
];
|
|
897
|
+
|
|
898
|
+
render(
|
|
899
|
+
wrap(
|
|
900
|
+
<FieldAssignRulesEditor
|
|
901
|
+
t={t}
|
|
902
|
+
fieldOptions={[]}
|
|
903
|
+
value={value}
|
|
904
|
+
onChange={onChange}
|
|
905
|
+
allowedModes={['default', 'assign']}
|
|
906
|
+
showCondition={false}
|
|
907
|
+
/>,
|
|
908
|
+
),
|
|
909
|
+
);
|
|
910
|
+
|
|
911
|
+
await userEvent.click(screen.getByRole('switch'));
|
|
912
|
+
|
|
913
|
+
await waitFor(() => {
|
|
914
|
+
expect(onChange).toHaveBeenCalled();
|
|
915
|
+
});
|
|
916
|
+
const lastCall = onChange.mock.calls[onChange.mock.calls.length - 1]?.[0] as FieldAssignRuleItem[];
|
|
917
|
+
expect(lastCall?.[0]?.mode).toBe('assign');
|
|
918
|
+
});
|
|
919
|
+
|
|
843
920
|
it('merges extra item tree into base item while keeping order', () => {
|
|
844
921
|
const base: MetaTreeNode[] = [
|
|
845
922
|
{ name: 'formValues', title: 'Current form', type: 'object', paths: ['formValues'] },
|
|
@@ -897,7 +974,7 @@ describe('FieldAssignRulesEditor', () => {
|
|
|
897
974
|
|
|
898
975
|
const itemNode = merged.find((node) => node.name === 'item');
|
|
899
976
|
expect(itemNode).toBeTruthy();
|
|
900
|
-
const parent = (itemNode
|
|
977
|
+
const parent = ((itemNode?.children as MetaTreeNode[]) || []).find((node) => node.name === 'parentItem');
|
|
901
978
|
expect(parent).toBeTruthy();
|
|
902
979
|
|
|
903
980
|
const nestedParent = ((parent?.children as MetaTreeNode[]) || []).find((node) => node.name === 'parentItem');
|
|
@@ -20,8 +20,6 @@ import {
|
|
|
20
20
|
FlowEngineProvider,
|
|
21
21
|
FlowModelRenderer,
|
|
22
22
|
ElementProxy,
|
|
23
|
-
createSafeWindow,
|
|
24
|
-
createSafeDocument,
|
|
25
23
|
createViewScopedEngine,
|
|
26
24
|
} from '@nocobase/flow-engine';
|
|
27
25
|
import { JSEditableFieldModel } from '../../../models/fields/JSEditableFieldModel';
|
|
@@ -167,11 +165,7 @@ describe('useCodeRunner (beforeRender)', () => {
|
|
|
167
165
|
const code = ctx?.inputArgs?.preview?.code || '';
|
|
168
166
|
ctx.onRefReady(ctx.ref, async (el) => {
|
|
169
167
|
ctx.defineProperty('element', { get: () => new ElementProxy(el as any) });
|
|
170
|
-
await ctx.runjs(
|
|
171
|
-
code,
|
|
172
|
-
{ window: createSafeWindow(), document: createSafeDocument() },
|
|
173
|
-
{ preprocessTemplates: true },
|
|
174
|
-
);
|
|
168
|
+
await ctx.runjs(code, undefined, { preprocessTemplates: true });
|
|
175
169
|
});
|
|
176
170
|
},
|
|
177
171
|
},
|
|
@@ -286,16 +280,7 @@ return currentUsername;
|
|
|
286
280
|
const code = ctx?.inputArgs?.preview?.code || '';
|
|
287
281
|
ctx.onRefReady(ctx.ref, async (el) => {
|
|
288
282
|
ctx.defineProperty('element', { get: () => new ElementProxy(el as any) });
|
|
289
|
-
|
|
290
|
-
await ctx.runjs(
|
|
291
|
-
code,
|
|
292
|
-
{
|
|
293
|
-
window: createSafeWindow({ navigator }),
|
|
294
|
-
document: createSafeDocument(),
|
|
295
|
-
navigator,
|
|
296
|
-
},
|
|
297
|
-
{ preprocessTemplates: true },
|
|
298
|
-
);
|
|
283
|
+
await ctx.runjs(code, undefined, { preprocessTemplates: true });
|
|
299
284
|
});
|
|
300
285
|
},
|
|
301
286
|
},
|