104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
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([]);
|
|
});
|
|
});
|