import type { RouteLocationNormalizedLoadedGeneric } from 'vue-router'; import { getTabKey } from '@easyflow/stores'; import { describe, expect, it } from 'vitest'; import { getRouteViewKey, shouldUseKeepAlive } from './route-view'; interface RouteOptions { fullPath: string; id?: string; keepAlive?: boolean; name: string; navTitle?: string; pageKey?: string; paramId?: string; path: string; } function createRoute( options: RouteOptions, ): RouteLocationNormalizedLoadedGeneric { const query: Record = {}; if (options.id) { query.id = options.id; } if (options.navTitle) { query.navTitle = options.navTitle; } if (options.pageKey) { query.pageKey = options.pageKey; } return { fullPath: options.fullPath, hash: '', matched: [], meta: { keepAlive: options.keepAlive, title: options.name, }, name: options.name, params: options.paramId ? { id: options.paramId } : {}, path: options.path, query, redirectedFrom: undefined, } as RouteLocationNormalizedLoadedGeneric; } describe('route view identity', () => { it('separates list and detail rendering while preserving one tab key', () => { const listRoute = createRoute({ fullPath: '/ai/workflow', keepAlive: false, name: 'WorkflowList', path: '/ai/workflow', }); const designRoute = createRoute({ fullPath: '/ai/workflow/design?id=1&pageKey=/ai/workflow&navTitle=Workflow', id: '1', name: 'WorkflowDesign', navTitle: 'Workflow', pageKey: '/ai/workflow', path: '/ai/workflow/design', }); expect(getTabKey(listRoute)).toBe('/ai/workflow'); expect(getTabKey(designRoute)).toBe('/ai/workflow'); expect(getRouteViewKey(listRoute)).not.toBe(getRouteViewKey(designRoute)); }); it('keeps title-only query changes on the same component instance', () => { const firstRoute = createRoute({ fullPath: '/ai/workflow/design?id=1&pageKey=/ai/workflow&navTitle=First', id: '1', name: 'WorkflowDesign', navTitle: 'First', pageKey: '/ai/workflow', path: '/ai/workflow/design', }); const renamedRoute = createRoute({ fullPath: '/ai/workflow/design?id=1&pageKey=/ai/workflow&navTitle=Renamed', id: '1', name: 'WorkflowDesign', navTitle: 'Renamed', pageKey: '/ai/workflow', path: '/ai/workflow/design', }); expect(getRouteViewKey(firstRoute)).toBe(getRouteViewKey(renamedRoute)); }); it('replaces the component instance when the resource id changes', () => { const firstRoute = createRoute({ fullPath: '/ai/workflow/design?id=1&pageKey=/ai/workflow', id: '1', name: 'WorkflowDesign', pageKey: '/ai/workflow', path: '/ai/workflow/design', }); const secondRoute = createRoute({ fullPath: '/ai/workflow/design?id=2&pageKey=/ai/workflow', id: '2', name: 'WorkflowDesign', pageKey: '/ai/workflow', path: '/ai/workflow/design', }); expect(getRouteViewKey(firstRoute)).not.toBe(getRouteViewKey(secondRoute)); }); it('separates path-param resources that share a tab key', () => { const firstRoute = createRoute({ fullPath: '/ai/agents/designer/1?pageKey=/ai/agents', name: 'AgentDesigner', pageKey: '/ai/agents', paramId: '1', path: '/ai/agents/designer/1', }); const secondRoute = createRoute({ fullPath: '/ai/agents/designer/2?pageKey=/ai/agents', name: 'AgentDesigner', pageKey: '/ai/agents', paramId: '2', path: '/ai/agents/designer/2', }); expect(getRouteViewKey(firstRoute)).not.toBe(getRouteViewKey(secondRoute)); }); it('bypasses KeepAlive only when caching is explicitly disabled', () => { const uncachedRoute = createRoute({ fullPath: '/ai/workflow', keepAlive: false, name: 'WorkflowList', path: '/ai/workflow', }); const defaultRoute = createRoute({ fullPath: '/ai/workflow/design?id=1', id: '1', name: 'WorkflowDesign', path: '/ai/workflow/design', }); expect(shouldUseKeepAlive(uncachedRoute, true)).toBe(false); expect(shouldUseKeepAlive(defaultRoute, true)).toBe(true); expect(shouldUseKeepAlive(defaultRoute, false)).toBe(false); }); });