mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-10-31 00:12:39 +00:00
The old panel kept its active tab in a store, so every page sat at /admin, the sidebar duplicated the webmail rail by hand, and each page was a stack of boxed cards with their own save buttons. Plugin settings were only reachable through a gear icon in a row of five. - one catch-all route for the whole panel; pages switch through history.pushState, so `/admin/plugins/<id>`, the back button, bookmarks and connector links all work without a route transition - sidebar laid out like the user settings: back to mail, search over pages and their sections, two group headings, change password and sign out at the bottom; the phone shows a list, then the page - pages use SettingsSection rows instead of nested cards, with one page header, one flash message and one save bar at the bottom (Discard, Save, Ctrl+S); the unsaved-changes prompt still guards leaving - plugins and themes are lists of rows with a toggle each; a row opens the extension's own page with its settings, force-on, permissions, kept versions and removal - overview shows stat tiles, server facts and recent activity - old `/admin?tab=x` links redirect; the tab store is gone and the connector registry resolves admin links to the page URL - `NEXT_TURBOPACK_ROOT` overrides the Turbopack root so `next dev` runs from a worktree with a junctioned node_modules
56 lines
2.6 KiB
TypeScript
56 lines
2.6 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
import { ADMIN_PAGES, ADMIN_PAGE_IDS, adminPath, isAdminPageId, parseAdminPath } from '../admin-nav';
|
|
|
|
describe('admin page URLs', () => {
|
|
it('reads the page and the detail from a pathname', () => {
|
|
expect(parseAdminPath('/admin')).toEqual({ page: null, detail: null });
|
|
expect(parseAdminPath('/admin/')).toEqual({ page: null, detail: null });
|
|
expect(parseAdminPath('/admin/settings')).toEqual({ page: 'settings', detail: null });
|
|
expect(parseAdminPath('/admin/plugins/jitsi-meet')).toEqual({ page: 'plugins', detail: 'jitsi-meet' });
|
|
expect(parseAdminPath('/admin/marketplace/quick-reply')).toEqual({ page: 'marketplace', detail: 'quick-reply' });
|
|
});
|
|
|
|
it('decodes a detail id and keeps slashes inside it', () => {
|
|
expect(parseAdminPath('/admin/plugins/acme%2Fwidget')).toEqual({ page: 'plugins', detail: 'acme/widget' });
|
|
expect(parseAdminPath('/admin/plugins/acme/widget')).toEqual({ page: 'plugins', detail: 'acme/widget' });
|
|
});
|
|
|
|
it('ignores a detail on pages that have none, and an unknown page', () => {
|
|
expect(parseAdminPath('/admin/settings/anything')).toEqual({ page: 'settings', detail: null });
|
|
expect(parseAdminPath('/admin/nope')).toEqual({ page: null, detail: null });
|
|
});
|
|
|
|
it('is null outside the admin tree', () => {
|
|
expect(parseAdminPath('/mail')).toBeNull();
|
|
expect(parseAdminPath('/administration')).toBeNull();
|
|
expect(parseAdminPath('/en/admin')).toBeNull();
|
|
});
|
|
|
|
it('builds the paths it parses', () => {
|
|
expect(adminPath()).toBe('/admin');
|
|
expect(adminPath(null)).toBe('/admin');
|
|
expect(adminPath('policy')).toBe('/admin/policy');
|
|
expect(adminPath('themes', 'nord')).toBe('/admin/themes/nord');
|
|
expect(adminPath('plugins', 'acme/widget')).toBe('/admin/plugins/acme%2Fwidget');
|
|
for (const id of ADMIN_PAGE_IDS) {
|
|
expect(parseAdminPath(adminPath(id))).toEqual({ page: id, detail: null });
|
|
}
|
|
expect(parseAdminPath(adminPath('plugins', 'acme/widget'))).toEqual({ page: 'plugins', detail: 'acme/widget' });
|
|
});
|
|
|
|
it('knows its page ids', () => {
|
|
expect(isAdminPageId('plugins')).toBe(true);
|
|
expect(isAdminPageId('login')).toBe(false);
|
|
expect(isAdminPageId(null)).toBe(false);
|
|
});
|
|
|
|
it('registers every page once, with the detail flag only where a detail page exists', () => {
|
|
expect(ADMIN_PAGES.map((p) => p.id)).toEqual([...ADMIN_PAGE_IDS]);
|
|
expect(ADMIN_PAGES.filter((p) => p.hasDetail).map((p) => p.id)).toEqual(['plugins', 'themes', 'marketplace']);
|
|
for (const page of ADMIN_PAGES) {
|
|
const ids = page.sections.map((s) => s.id);
|
|
expect(new Set(ids).size).toBe(ids.length);
|
|
}
|
|
});
|
|
});
|