update layouts
This commit is contained in:
@@ -0,0 +1,36 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
import { APP_SETTINGS, DEFAULT_CONTAINER_BY_LAYOUT } from '#app/config/settings';
|
||||
import { LayoutNames } from '#app/config/types';
|
||||
|
||||
const layoutNames = Object.values(LayoutNames);
|
||||
|
||||
describe('layout settings', () => {
|
||||
it('carries options for every layout, so any layout can read its own', () => {
|
||||
for (const name of layoutNames) {
|
||||
expect(APP_SETTINGS.layouts[name], name).toEqual({
|
||||
sidebarCollapse: false,
|
||||
sidebarTheme: 'light',
|
||||
headerSticky: true,
|
||||
headerStickyOffset: 200
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('gives each layout its own options object, so one layout cannot edit another', () => {
|
||||
const options = layoutNames.map((name) => APP_SETTINGS.layouts[name]);
|
||||
|
||||
expect(new Set(options).size).toBe(layoutNames.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe('layout containers', () => {
|
||||
it('assigns a container width to every layout', () => {
|
||||
for (const name of layoutNames) {
|
||||
expect(DEFAULT_CONTAINER_BY_LAYOUT[name], name).toMatch(/^(fixed|fluid)$/);
|
||||
}
|
||||
});
|
||||
|
||||
it('gives the mega-menu layout a fluid container, since its panels span the viewport', () => {
|
||||
expect(DEFAULT_CONTAINER_BY_LAYOUT[LayoutNames.ADVANCE_MEGA_MENU]).toBe('fluid');
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,108 @@
|
||||
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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user