From 58f1df96f3382c6b4794161f0a6af5798d062bc3 Mon Sep 17 00:00:00 2001 From: gchust Date: Tue, 7 Jul 2026 09:39:12 +0800 Subject: [PATCH] fix: stale page model when switching flow setting (#10005) --- .../__tests__/viewScopedFlowEngine.test.ts | 78 +++++++++++++++++-- packages/core/flow-engine/src/flowEngine.ts | 6 ++ .../flow-engine/src/utils/loadedPageCache.ts | 7 ++ 3 files changed, 85 insertions(+), 6 deletions(-) diff --git a/packages/core/flow-engine/src/__tests__/viewScopedFlowEngine.test.ts b/packages/core/flow-engine/src/__tests__/viewScopedFlowEngine.test.ts index 42aece77760..f5c484423f4 100644 --- a/packages/core/flow-engine/src/__tests__/viewScopedFlowEngine.test.ts +++ b/packages/core/flow-engine/src/__tests__/viewScopedFlowEngine.test.ts @@ -340,9 +340,9 @@ describe('ViewScopedFlowEngine', () => { const repository = new DirtyPageRepository(); root.setModelRepository(repository); - class ParentModel extends FlowModel {} - class PageModel extends FlowModel {} class BlockModel extends FlowModel {} + class PageModel extends FlowModel<{ parent?: FlowModel; subModels: { items: BlockModel[] } }> {} + class ParentModel extends FlowModel<{ parent?: FlowModel; subModels: { page?: PageModel } }> {} root.registerModels({ ParentModel, PageModel, BlockModel }); const parent = root.createModel({ use: 'ParentModel', uid: 'popup-action' }); @@ -357,7 +357,10 @@ describe('ViewScopedFlowEngine', () => { items: [{ use: 'BlockModel', uid: 'stale-block' }], }, }); - const staleBlock = stalePage.findSubModel('items' as any, (item) => item.uid === 'stale-block') as FlowModel; + const staleBlock = stalePage.findSubModel('items', (item) => item.uid === 'stale-block'); + if (!staleBlock) { + throw new Error('Expected stale block to be loaded'); + } parent.setSubModel('page', stalePage); oldScoped.unlinkFromStack(); @@ -372,7 +375,7 @@ describe('ViewScopedFlowEngine', () => { }, }; - root.flowSettings.enable(); + await root.flowSettings.enable(); await staleBlock.saveStepParams(); root.flowSettings.disable(); repository.findOneCalls = 0; @@ -388,8 +391,8 @@ describe('ViewScopedFlowEngine', () => { expect(repository.findOneCalls).toBe(1); expect(loaded).not.toBe(stalePage); - expect((parent.subModels as any).page).toBe(loaded); - expect(loaded?.mapSubModels('items' as any, (item) => item.uid)).toEqual(['fresh-block']); + expect(parent.subModels.page).toBe(loaded); + expect(loaded?.mapSubModels('items', (item) => item.uid)).toEqual(['fresh-block']); repository.findOneCalls = 0; const nextRuntimeScoped = createViewScopedEngine(root); @@ -405,6 +408,69 @@ describe('ViewScopedFlowEngine', () => { expect(loadedAgain?.uid).toBe('popup-page'); }); + it('reloads a page after it was loaded in flow settings mode', async () => { + const root = new FlowEngine(); + const repository = new DirtyPageRepository(); + root.setModelRepository(repository); + + class ParentModel extends FlowModel {} + class PageModel extends FlowModel {} + class BlockModel extends FlowModel {} + root.registerModels({ ParentModel, PageModel, BlockModel }); + + const parent = root.createModel({ use: 'ParentModel', uid: 'settings-popup-action' }); + repository.data = { + use: 'PageModel', + uid: 'settings-popup-page', + parentId: parent.uid, + subKey: 'page', + subType: 'object', + subModels: { + items: [{ use: 'BlockModel', uid: 'stale-settings-block' }], + }, + }; + + await root.flowSettings.enable(); + const designScoped = createViewScopedEngine(root); + const designLoaded = await designScoped.loadOrCreateModel({ + async: true, + parentId: parent.uid, + subKey: 'page', + subType: 'object', + use: 'PageModel', + }); + expect(repository.findOneCalls).toBe(1); + expect(designLoaded?.mapSubModels('items', (item) => item.uid)).toEqual(['stale-settings-block']); + designScoped.unlinkFromStack(); + + repository.data = { + use: 'PageModel', + uid: 'settings-popup-page', + parentId: parent.uid, + subKey: 'page', + subType: 'object', + subModels: { + items: [{ use: 'BlockModel', uid: 'fresh-settings-block' }], + }, + }; + root.flowSettings.disable(); + repository.findOneCalls = 0; + + const runtimeScoped = createViewScopedEngine(root); + const runtimeLoaded = await runtimeScoped.loadOrCreateModel({ + async: true, + parentId: parent.uid, + subKey: 'page', + subType: 'object', + use: 'PageModel', + }); + + expect(repository.findOneCalls).toBe(1); + expect(runtimeLoaded).not.toBe(designLoaded); + expect(parent.subModels.page).toBe(runtimeLoaded); + expect(runtimeLoaded?.mapSubModels('items', (item) => item.uid)).toEqual(['fresh-settings-block']); + }); + it('does not bypass loaded page cache after a non-config save', async () => { const root = new FlowEngine(); const repository = new DirtyPageRepository(); diff --git a/packages/core/flow-engine/src/flowEngine.ts b/packages/core/flow-engine/src/flowEngine.ts index 4d1cf254f47..796114b8fd6 100644 --- a/packages/core/flow-engine/src/flowEngine.ts +++ b/packages/core/flow-engine/src/flowEngine.ts @@ -1343,6 +1343,9 @@ export class FlowEngine { if (!this.ensureModelRepository()) return; const refresh = !!options?.refresh; const bypassLoadedPageCache = this._loadedPageCache.shouldBypass(options, () => this.context.flowSettingsEnabled); + if (this.context.flowSettingsEnabled) { + this._loadedPageCache.markDirtyForOptions(options); + } if (!refresh && !bypassLoadedPageCache) { const model = this.findModelByParentId(options.parentId, options.subKey); if (model) { @@ -1412,6 +1415,9 @@ export class FlowEngine { if (!this.ensureModelRepository()) return; const { uid, parentId, subKey } = options; const bypassLoadedPageCache = this._loadedPageCache.shouldBypass(options, () => this.context.flowSettingsEnabled); + if (this.context.flowSettingsEnabled) { + this._loadedPageCache.markDirtyForOptions(options); + } if (uid && !bypassLoadedPageCache && this._modelInstances.has(uid)) { return this._modelInstances.get(uid) as T; } diff --git a/packages/core/flow-engine/src/utils/loadedPageCache.ts b/packages/core/flow-engine/src/utils/loadedPageCache.ts index 56587e196ee..a62bc2213e0 100644 --- a/packages/core/flow-engine/src/utils/loadedPageCache.ts +++ b/packages/core/flow-engine/src/utils/loadedPageCache.ts @@ -123,6 +123,13 @@ export const createLoadedPageCache = () => { } }, + markDirtyForOptions(options?: LoadedPageOptions): void { + const key = getLoadedPageKey(options); + if (key) { + dirtyKeys.add(key); + } + }, + shouldBypass(options?: LoadedPageOptions, isFlowSettingsEnabled?: () => boolean): boolean { const key = getLoadedPageKey(options); if (!key || !dirtyKeys.has(key)) {