From fcf6bedd631e6912a4af2cf789ad2f5417ab1054 Mon Sep 17 00:00:00 2001 From: Zeke Zhang <958414905@qq.com> Date: Tue, 25 Aug 2026 22:25:17 +0800 Subject: [PATCH] fix(ui-templates): isolate reference view context (#10415) --- .../models/__tests__/referenceShared.test.ts | 41 +++++++++++++++++++ .../src/client-v2/models/referenceShared.tsx | 14 ++++++- 2 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 packages/plugins/@nocobase/plugin-ui-templates/src/client-v2/models/__tests__/referenceShared.test.ts diff --git a/packages/plugins/@nocobase/plugin-ui-templates/src/client-v2/models/__tests__/referenceShared.test.ts b/packages/plugins/@nocobase/plugin-ui-templates/src/client-v2/models/__tests__/referenceShared.test.ts new file mode 100644 index 00000000000..7c2957ec085 --- /dev/null +++ b/packages/plugins/@nocobase/plugin-ui-templates/src/client-v2/models/__tests__/referenceShared.test.ts @@ -0,0 +1,41 @@ +/** + * 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 { FlowContext, FlowEngine } from '@nocobase/flow-engine'; +import { describe, expect, it } from 'vitest'; +import { ensureBlockScopedEngine, ensureScopedEngineView } from '../referenceShared'; + +describe('referenceShared', () => { + it('keeps the bridged view lookup local after the host view delegate is removed', () => { + const parentEngine = new FlowEngine(); + const hostModel = parentEngine.createModel({ use: 'FlowModel', uid: 'host-model' }); + const hostViewContext = new FlowContext(); + const hostView = { uid: 'host-view' }; + hostViewContext.defineProperty('view', { value: hostView }); + hostModel.context.addDelegate(hostViewContext); + + const scopedEngine = ensureBlockScopedEngine(parentEngine); + ensureScopedEngineView(scopedEngine, hostModel.context); + + expect(scopedEngine.context).not.toBe(parentEngine.context); + expect(scopedEngine.context.engine).toBe(scopedEngine); + expect(scopedEngine.context.view).toBe(hostView); + expect(parentEngine.context.getPropertyOptions('view')).toBeUndefined(); + + const nextHostView = { uid: 'next-host-view' }; + hostViewContext.defineProperty('view', { value: nextHostView }); + expect(scopedEngine.context.view).toBe(nextHostView); + + hostModel.context.removeDelegate(hostViewContext); + + expect(() => scopedEngine.context.view).not.toThrow(); + expect(scopedEngine.context.view).toBeUndefined(); + expect(parentEngine.context.getPropertyOptions('view')).toBeUndefined(); + }); +}); diff --git a/packages/plugins/@nocobase/plugin-ui-templates/src/client-v2/models/referenceShared.tsx b/packages/plugins/@nocobase/plugin-ui-templates/src/client-v2/models/referenceShared.tsx index 0e5e201a00a..347aae51655 100644 --- a/packages/plugins/@nocobase/plugin-ui-templates/src/client-v2/models/referenceShared.tsx +++ b/packages/plugins/@nocobase/plugin-ui-templates/src/client-v2/models/referenceShared.tsx @@ -22,7 +22,19 @@ import { import { NAMESPACE } from '../locale'; export function ensureBlockScopedEngine(flowEngine: FlowEngine, scopedEngine?: FlowEngine): FlowEngine { - return scopedEngine ?? createBlockScopedEngine(flowEngine); + if (scopedEngine) return scopedEngine; + + const engine = createBlockScopedEngine(flowEngine); + // BlockScopedFlowEngine shares the parent engine context. Add a reference-local overlay so view bridging stays local while other context values still delegate to the parent. + const parentContext = engine.context; + const context = new FlowContext(); + context.defineProperty('engine', { value: engine }); + context.addDelegate(parentContext); + Object.defineProperty(engine, 'context', { + configurable: true, + value: context, + }); + return engine; } export function ensureScopedEngineView(engine: FlowEngine, hostContext?: FlowContext): void {