import type { IntlShape } from 'react-intl'; import { describe, expect, it } from 'vitest'; import type { MenuConfig } from '#app/config/types'; import { createDevtoolsMenu } from '#devtools/config/menu'; import { createMenuPathMatcher, isMenuPathActive } from '#shared/lib/menuPath'; const intl = { formatMessage: ({ id }: { id: string }) => id } as IntlShape; const devtoolsMenu: MenuConfig = [createDevtoolsMenu(intl)]; /** Every menu path the matcher marks active for a route — i.e. what the sidebar highlights. */ function highlighted(menu: MenuConfig, pathname: string): string[] { const matches = createMenuPathMatcher(menu, pathname); const active: string[] = []; const walk = (items: MenuConfig) => { for (const item of items) { if (item.path && matches(item.path)) active.push(item.path); if (item.children) walk(item.children); } }; walk(menu); return active; } describe('isMenuPathActive', () => { it('matches the route itself', () => { expect(isMenuPathActive('/devtools/module-generator', '/devtools/module-generator')).toBe(true); }); it('matches routes nested under the path', () => { expect(isMenuPathActive('/devtools/module-generator', '/devtools/module-generator/docs')).toBe( true ); }); it('does not match a path that merely shares a prefix', () => { expect(isMenuPathActive('/devtools/module-generator', '/devtools/module-generator-v2')).toBe( false ); }); it('ignores a trailing slash on the menu path', () => { expect( isMenuPathActive( '/file-manager/tools/image-editor/-/', '/file-manager/tools/image-editor/-/8' ) ).toBe(true); }); it('matches the root only exactly', () => { expect(isMenuPathActive('/', '/')).toBe(true); expect(isMenuPathActive('/', '/devtools')).toBe(false); }); it('is false without a path', () => { expect(isMenuPathActive(undefined, '/devtools')).toBe(false); }); }); describe('createMenuPathMatcher', () => { it('highlights the section and its index item on the section route', () => { expect(highlighted(devtoolsMenu, '/devtools/module-generator')).toEqual([ '/devtools/module-generator', '/devtools/module-generator' ]); }); it('highlights only the sub-page on a sub-route, not its ancestors', () => { expect(highlighted(devtoolsMenu, '/devtools/module-generator/docs')).toEqual([ '/devtools/module-generator/docs' ]); expect(highlighted(devtoolsMenu, '/devtools/module-generator/version')).toEqual([ '/devtools/module-generator/version' ]); }); it('leaves a sibling section alone', () => { expect(highlighted(devtoolsMenu, '/devtools/sandbox/ui')).toEqual(['/devtools/sandbox/ui']); }); it('keeps the closest entry highlighted on a route with no entry of its own', () => { const menu: MenuConfig = [ { title: 'File manager', children: [ { title: 'Explorer', path: '/file-manager/drive/explorer' }, { title: 'Settings', path: '/file-manager/drive/settings' } ] } ]; expect(highlighted(menu, '/file-manager/drive/explorer/photos/2026')).toEqual([ '/file-manager/drive/explorer' ]); }); it('highlights nothing when no entry matches', () => { expect(highlighted(devtoolsMenu, '/web-studio/pages/listing')).toEqual([]); }); });