diff --git a/packages/core/client-v2/src/flow/components/FieldAssignValueInput.tsx b/packages/core/client-v2/src/flow/components/FieldAssignValueInput.tsx index 3f54c53a160..cfff56e249f 100644 --- a/packages/core/client-v2/src/flow/components/FieldAssignValueInput.tsx +++ b/packages/core/client-v2/src/flow/components/FieldAssignValueInput.tsx @@ -1028,18 +1028,22 @@ export const FieldAssignValueInput: React.FC = ({ collectionField: effectiveCollectionField, }); - const created = engine?.createModel?.({ - use: 'VariableFieldFormModel', - subModels: { - fields: [ - { - use: effectiveFieldModelUse, - stepParams: tempFieldStepParams, - props: tempFieldProps, - }, - ], + const sourceContext = resolved?.itemModel?.context || flowCtx.model?.context; + const created = engine?.createModel?.( + { + use: 'VariableFieldFormModel', + subModels: { + fields: [ + { + use: effectiveFieldModelUse, + stepParams: tempFieldStepParams, + props: tempFieldProps, + }, + ], + }, }, - }); + sourceContext ? { delegate: sourceContext } : undefined, + ); if (!created) return; // 注入上下文(集合/数据源/字段/区块/资源) diff --git a/packages/core/client-v2/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx b/packages/core/client-v2/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx new file mode 100644 index 00000000000..58bdfcee37d --- /dev/null +++ b/packages/core/client-v2/src/flow/components/__tests__/FieldAssignValueInput.context.test.tsx @@ -0,0 +1,134 @@ +/** + * This file is part of the NocoBase (R) project. + * Copyright (c) 2020-2024 NocoBase Co., Ltd. + * Authors: NocoBase Team. + * + * This project is dual-licensed under AGPL-3.0 and NocoBase Commercial License. + * For more information, please refer to: https://www.nocobase.com/agreement. + */ + +import React from 'react'; +import { describe, expect, it, vi } from 'vitest'; +import { render, waitFor } from '@nocobase/test/client'; +import { FieldAssignValueInput } from '../FieldAssignValueInput'; + +const { mockUseFlowContext, mockGetDefaultBindingByField } = vi.hoisted(() => ({ + mockUseFlowContext: vi.fn(), + mockGetDefaultBindingByField: vi.fn(), +})); + +vi.mock('@nocobase/flow-engine', async () => { + const actual = await vi.importActual('@nocobase/flow-engine'); + class MockEditableItemModel extends actual.EditableItemModel {} + MockEditableItemModel.getDefaultBindingByField = mockGetDefaultBindingByField; + + return { + ...actual, + useFlowContext: () => mockUseFlowContext(), + VariableInput: () =>
, + FlowModelRenderer: () =>
, + EditableItemModel: MockEditableItemModel, + }; +}); + +describe('FieldAssignValueInput context', () => { + it('delegates temporary field model context to the source form context', async () => { + type MockContext = { + dataSourceManager: { getDataSource: ReturnType }; + t: (key: string) => string; + engine?: { createModel: ReturnType }; + collection?: MockCollection; + blockModel?: MockFormModel; + }; + type MockFieldModel = { + props: Record; + setProps: ReturnType; + dispatchEvent: ReturnType; + remove: ReturnType; + }; + type MockCollectionField = { + name: string; + interface: string; + uiSchema: { enum: Array<{ label: string; value: string }> }; + isAssociationField: () => boolean; + getComponentProps: () => Record; + }; + type MockCollection = { + dataSourceKey: string; + name: string; + getField: (name: string) => MockCollectionField | null; + getFields: () => MockCollectionField[]; + }; + type MockFormModel = { + context: MockContext; + collection: MockCollection; + subModels: Record; + }; + + const sourceContext: MockContext = { + dataSourceManager: { + getDataSource: vi.fn(() => ({})), + }, + t: (key: string) => key, + }; + const fieldModel: MockFieldModel = { + props: {}, + setProps: vi.fn((props: Record) => { + fieldModel.props = { ...fieldModel.props, ...props }; + }), + dispatchEvent: vi.fn(), + remove: vi.fn(), + }; + const tempRoot = { + context: { + defineProperty: vi.fn(), + }, + subModels: { + fields: [fieldModel], + }, + setProps: vi.fn(), + remove: vi.fn(), + }; + const engine = { + createModel: vi.fn(() => tempRoot), + }; + sourceContext.engine = engine; + + const collectionField: MockCollectionField = { + name: 'status', + interface: 'select', + uiSchema: { + enum: [{ label: 'Open', value: 'open' }], + }, + isAssociationField: () => false, + getComponentProps: () => ({}), + }; + const collection: MockCollection = { + dataSourceKey: 'main', + name: 'tasks', + getField: (name: string) => (name === 'status' ? collectionField : null), + getFields: () => [collectionField], + }; + const formModel: MockFormModel = { + context: sourceContext, + collection, + subModels: {}, + }; + sourceContext.collection = collection; + sourceContext.blockModel = formModel; + + mockGetDefaultBindingByField.mockReturnValue({ modelName: 'SelectFieldModel' }); + mockUseFlowContext.mockReturnValue({ + model: formModel, + t: (key: string) => key, + getPropertyMetaTree: vi.fn(async () => []), + }); + + render(); + + await waitFor(() => { + expect(engine.createModel).toHaveBeenCalled(); + }); + expect(engine.createModel).toHaveBeenCalledWith(expect.any(Object), { delegate: sourceContext }); + }); +});