mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-01 08:12:43 +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
54 lines
2.3 KiB
TypeScript
54 lines
2.3 KiB
TypeScript
import { fireEvent, render, screen } from '@testing-library/react';
|
|
import { describe, expect, it, vi } from 'vitest';
|
|
import { AdminSaveBar } from '../save-bar';
|
|
import { AdminShellContext } from '../shell-context';
|
|
|
|
describe('AdminSaveBar', () => {
|
|
it('shows nothing while there is nothing to save', () => {
|
|
render(<AdminSaveBar dirty={false} onSave={() => {}} />);
|
|
expect(screen.queryByTestId('admin-save-bar')).toBeNull();
|
|
});
|
|
|
|
it('offers Save and Discard once something changed', () => {
|
|
const onSave = vi.fn();
|
|
const onDiscard = vi.fn();
|
|
render(<AdminSaveBar dirty onSave={onSave} onDiscard={onDiscard} saveLabel="Save policy" note="Users see it on login" />);
|
|
expect(screen.getByText('Unsaved changes')).toBeTruthy();
|
|
expect(screen.getByText(/Users see it on login/)).toBeTruthy();
|
|
fireEvent.click(screen.getByRole('button', { name: 'Save policy' }));
|
|
expect(onSave).toHaveBeenCalledTimes(1);
|
|
fireEvent.click(screen.getByRole('button', { name: 'Discard' }));
|
|
expect(onDiscard).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('saves on Ctrl+S, but not while a save is running', () => {
|
|
const onSave = vi.fn();
|
|
const { rerender } = render(<AdminSaveBar dirty onSave={onSave} />);
|
|
fireEvent.keyDown(window, { key: 's', ctrlKey: true });
|
|
expect(onSave).toHaveBeenCalledTimes(1);
|
|
rerender(<AdminSaveBar dirty saving onSave={onSave} />);
|
|
fireEvent.keyDown(window, { key: 's', ctrlKey: true });
|
|
expect(onSave).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('ignores Ctrl+S when nothing is dirty', () => {
|
|
const onSave = vi.fn();
|
|
render(<AdminSaveBar dirty={false} onSave={onSave} />);
|
|
fireEvent.keyDown(window, { key: 's', ctrlKey: true });
|
|
expect(onSave).not.toHaveBeenCalled();
|
|
});
|
|
|
|
it('renders into the shell host when there is one', () => {
|
|
const host = document.createElement('div');
|
|
document.body.appendChild(host);
|
|
render(
|
|
<AdminShellContext.Provider value={{ saveBarHost: host, refreshBadges: () => {} }}>
|
|
<div data-testid="page"><AdminSaveBar dirty onSave={() => {}} /></div>
|
|
</AdminShellContext.Provider>,
|
|
);
|
|
expect(host.querySelector('[data-testid="admin-save-bar"]')).not.toBeNull();
|
|
expect(screen.getByTestId('page').querySelector('[data-testid="admin-save-bar"]')).toBeNull();
|
|
host.remove();
|
|
});
|
|
});
|