update layouts

This commit is contained in:
Igor Martyniuk
2026-09-02 12:40:49 +03:00
parent 962f489142
commit f5c9132422
132 changed files with 1728 additions and 5817 deletions
@@ -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);
});
});