UNPKG

@nocobase/flow-engine

Version:

A standalone flow engine for NocoBase, managing workflows, models, and actions.

235 lines (187 loc) 7.18 kB
/** * 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 { describe, expect, it, vi } from 'vitest'; import { FlowContext } from '../flowContext'; describe('MetaTreeNode Cache Mechanism', () => { describe('Node-level Caching', () => { it('should return the same MetaTreeNode instance for the same property', () => { const ctx = new FlowContext(); ctx.defineProperty('user', { meta: { type: 'object', title: 'User', properties: { name: { type: 'string', title: 'Name' }, }, }, }); // 获取两次相同的 meta tree const tree1 = ctx.getPropertyMetaTree(); const tree2 = ctx.getPropertyMetaTree(); // 应该返回相同的实例 expect(tree1[0]).toBe(tree2[0]); expect(tree1[0].children?.[0]).toBe(tree2[0].children?.[0]); }); it('should return the same MetaTreeNode for same meta objects', () => { const ctx = new FlowContext(); // 创建可复用的 meta 对象 const profileMeta = { type: 'object', title: 'Profile', properties: { bio: { type: 'string', title: 'Bio' }, }, }; ctx.defineProperty('user', { meta: { type: 'object', title: 'User', properties: { profile: profileMeta, }, }, }); ctx.defineProperty('admin', { meta: { type: 'object', title: 'Admin', properties: { profile: profileMeta, // 复用同一个 meta 对象 }, }, }); const tree = ctx.getPropertyMetaTree(); // 找到 user.profile 和 admin.profile 节点 const userProfileNode = (tree[0].children as any)?.[0]; const adminProfileNode = (tree[1].children as any)?.[0]; // 应该是同一个实例(因为使用相同的 meta 对象) expect(userProfileNode).toBe(adminProfileNode); expect(userProfileNode.title).toBe('Profile'); }); }); describe('Async Meta Upgrade', () => { it('should upgrade async meta node in-place', async () => { const ctx = new FlowContext(); const asyncMeta = vi.fn(async () => { await new Promise((resolve) => setTimeout(resolve, 10)); return { type: 'object', title: 'Loaded User', properties: { name: { type: 'string', title: 'User Name' }, email: { type: 'string', title: 'Email' }, }, }; }); ctx.defineProperty('user', { meta: asyncMeta }); const tree = ctx.getPropertyMetaTree(); const userNode = tree[0]; // 初始状态:使用默认值 expect(userNode.title).toBe('user'); // 默认使用 name expect(userNode.type).toBe('object'); expect(typeof userNode.children).toBe('function'); // 异步加载 const children = await (userNode.children as () => Promise<any>)(); // 验证原地更新:节点本身的属性被更新了 expect(userNode.title).toBe('Loaded User'); // 已被更新 expect(userNode.type).toBe('object'); expect(userNode.children).toEqual(children); // children 被替换为实际结果 // 验证子节点 expect(children).toHaveLength(2); expect(children[0].name).toBe('name'); expect(children[1].name).toBe('email'); // 再次获取应该返回相同实例 const tree2 = ctx.getPropertyMetaTree(); expect(tree2[0]).toBe(userNode); }); it('should handle async meta loading errors gracefully', async () => { const ctx = new FlowContext(); const errorMeta = vi.fn(async () => { throw new Error('Meta loading failed'); }); ctx.defineProperty('errorProp', { meta: errorMeta }); const tree = ctx.getPropertyMetaTree(); const errorNode = tree[0]; const consoleSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); // 异步加载应该返回空数组 const children = await (errorNode.children as () => Promise<any>)(); expect(children).toEqual([]); expect(consoleSpy).toHaveBeenCalledWith('Failed to load meta for errorProp:', expect.any(Error)); consoleSpy.mockRestore(); }); }); describe('Cache Invalidation', () => { it('should clear cache when property is redefined', () => { const ctx = new FlowContext(); ctx.defineProperty('user', { meta: { type: 'string', title: 'User String' }, }); const tree1 = ctx.getPropertyMetaTree(); expect(tree1[0].title).toBe('User String'); // 重新定义属性 ctx.defineProperty('user', { meta: { type: 'object', title: 'User Object' }, }); const tree2 = ctx.getPropertyMetaTree(); expect(tree2[0].title).toBe('User Object'); // 应该是不同的实例(缓存已清除) expect(tree1[0]).not.toBe(tree2[0]); }); it('should preserve cache when delegate is added/removed', () => { const ctx1 = new FlowContext(); const ctx2 = new FlowContext(); ctx2.defineProperty('shared', { meta: { type: 'string', title: 'Shared Property' }, }); ctx1.defineProperty('own', { meta: { type: 'string', title: 'Own Property' }, }); const tree1 = ctx1.getPropertyMetaTree(); expect(tree1).toHaveLength(1); // only 'own' const ownNode1 = tree1.find((n) => n.name === 'own'); // 添加委托 ctx1.addDelegate(ctx2); const tree2 = ctx1.getPropertyMetaTree(); expect(tree2).toHaveLength(2); // 'own' + 'shared' // 验证缓存被保留了('own' 是同一个实例) const ownNode2 = tree2.find((n) => n.name === 'own'); expect(ownNode1).toBe(ownNode2); // 新的 'shared' 节点是首次创建 const sharedNode = tree2.find((n) => n.name === 'shared'); expect(sharedNode).toBeDefined(); expect(sharedNode?.title).toBe('Shared Property'); }); }); describe('Reference Stability', () => { it('should maintain reference stability across multiple calls', () => { const ctx = new FlowContext(); ctx.defineProperty('stable', { meta: { type: 'object', title: 'Stable Node', properties: { child: { type: 'string', title: 'Child' }, }, }, }); // 多次调用应返回相同引用 const calls = Array.from({ length: 5 }, () => ctx.getPropertyMetaTree()); // 所有调用应返回相同的根节点实例 const rootNodes = calls.map((tree) => tree[0]); rootNodes.forEach((node) => { expect(node).toBe(rootNodes[0]); }); // 子节点也应该稳定 const childNodes = calls.map((tree) => (tree[0].children as any)?.[0]); childNodes.forEach((node) => { expect(node).toBe(childNodes[0]); }); }); }); });