109 lines
3.6 KiB
TypeScript
109 lines
3.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { MenuConfig } from '#app/config/types';
|
|
import { firstMenuPath, getEnabledMenuItems, getPermittedMenuItems } from '#app/layouts/shared/lib/menu';
|
|
|
|
const menu: MenuConfig = [
|
|
{ title: 'Dashboard', path: '/dashboard' },
|
|
{
|
|
title: 'Access control',
|
|
children: [
|
|
{ title: 'Admins', path: '/access-control/admin', permissions: ['admins.view'] },
|
|
{ title: 'Roles', path: '/access-control/role', permissions: ['role.view'] }
|
|
]
|
|
},
|
|
{
|
|
title: 'Reports',
|
|
permissions: ['reports.view'],
|
|
children: [{ title: 'Sales', path: '/reports/sales', permissions: ['reports.view'] }]
|
|
}
|
|
];
|
|
|
|
describe('getPermittedMenuItems', () => {
|
|
it('keeps entries that declare no permissions', () => {
|
|
expect(getPermittedMenuItems(menu, []).map((item) => item.title)).toEqual(['Dashboard']);
|
|
});
|
|
|
|
it('keeps a section for the children the visitor may reach', () => {
|
|
const [, accessControl] = getPermittedMenuItems(menu, ['role.view']);
|
|
|
|
expect(accessControl.title).toBe('Access control');
|
|
expect(accessControl.children?.map((item) => item.path)).toEqual(['/access-control/role']);
|
|
});
|
|
|
|
it('drops a section whose children were all filtered out', () => {
|
|
const titles = getPermittedMenuItems(menu, ['reports.view']).map((item) => item.title);
|
|
|
|
expect(titles).toEqual(['Dashboard', 'Reports']);
|
|
expect(getPermittedMenuItems(menu, []).map((item) => item.title)).not.toContain('Reports');
|
|
});
|
|
|
|
it('requires every declared permission, not just one', () => {
|
|
const guarded: MenuConfig = [
|
|
{ title: 'Create page', path: '/pages/create', permissions: ['pages.view', 'pages.create'] }
|
|
];
|
|
|
|
expect(getPermittedMenuItems(guarded, ['pages.view'])).toEqual([]);
|
|
expect(getPermittedMenuItems(guarded, ['pages.view', 'pages.create'])).toHaveLength(1);
|
|
});
|
|
|
|
it('leaves the source configuration untouched', () => {
|
|
getPermittedMenuItems(menu, ['role.view']);
|
|
|
|
expect(menu[1].children).toHaveLength(2);
|
|
});
|
|
});
|
|
|
|
describe('firstMenuPath', () => {
|
|
it('returns the path an entry declares itself', () => {
|
|
expect(firstMenuPath({ title: 'Dashboard', path: '/dashboard' })).toBe('/dashboard');
|
|
});
|
|
|
|
it('descends to the first reachable path of a section', () => {
|
|
expect(firstMenuPath(menu[1])).toBe('/access-control/admin');
|
|
});
|
|
|
|
it('returns undefined when nothing inside is linkable', () => {
|
|
expect(firstMenuPath({ title: 'Empty', children: [{ title: 'Also empty' }] })).toBeUndefined();
|
|
});
|
|
});
|
|
|
|
describe('getEnabledMenuItems', () => {
|
|
const withDisabled: MenuConfig = [
|
|
{ title: 'Dashboard', path: '/dashboard' },
|
|
{
|
|
title: 'Web Studio',
|
|
children: [
|
|
{ title: 'Page listing', path: '/web-studio/pages/listing' },
|
|
{ title: 'Edit page', path: '/web-studio/pages/-/', disabled: true }
|
|
]
|
|
},
|
|
{
|
|
title: 'Coming soon',
|
|
children: [{ title: 'Not built', path: '/soon', disabled: true }]
|
|
}
|
|
];
|
|
|
|
it('removes a disabled entry from its section', () => {
|
|
const [, webStudio] = getEnabledMenuItems(withDisabled);
|
|
|
|
expect(webStudio.children?.map((item) => item.title)).toEqual(['Page listing']);
|
|
});
|
|
|
|
it('removes a section left with nothing but disabled entries', () => {
|
|
expect(getEnabledMenuItems(withDisabled).map((item) => item.title)).toEqual([
|
|
'Dashboard',
|
|
'Web Studio'
|
|
]);
|
|
});
|
|
|
|
it('leaves a menu without disabled entries untouched', () => {
|
|
expect(getEnabledMenuItems(menu)).toEqual(menu);
|
|
});
|
|
|
|
it('leaves the source configuration untouched', () => {
|
|
getEnabledMenuItems(withDisabled);
|
|
|
|
expect(withDisabled[1].children).toHaveLength(2);
|
|
});
|
|
});
|