Files
webmail/components/email/__tests__/viewer-plugin-panel-width.test.tsx
T
Linus Rath ad36f8d382 fix: show the side panel only where the plugin does, and keep it steady
- The button and the panel of Calendar, Contacts, Files and Settings
  show only when the plugin's shouldShow says yes, so a plugin switched
  off for these views (admin, user or no service) leaves no button that
  opens an empty panel, and no extra rows in Contacts and Settings.
- Files: while the details are open the panel steps aside there only;
  the other views keep it as the user left it. With both stored open the
  details fold when the page loads.
- Resizing stops where the view keeps its 480 px, so a drag or the arrow
  keys no longer fold the panel away (and the handle keeps the focus).
- Settings and Contacts render the same element with or without the
  panel, so a form keeps what was typed when the panel starts docking.
- Contacts on a phone: the panel button sits over an open contact too.
- The panel buttons of the reader and the views have one name, "Plugin
  panel", with aria-pressed, and name the plugin's panel shortcut in
  their title; the open panels are named landmarks.

The reader tests look the button up by its new name and pressed state
(viewer-toolbar-over-side-panel, reader-plugin-surfaces,
viewer-plugin-panel-width), since "Show/Hide plugin panel" is gone.
2026-11-02 23:54:53 +01:00

79 lines
3.1 KiB
TypeScript

import { render, screen, act, fireEvent } from '@testing-library/react';
import { describe, it, expect, vi, beforeEach } from 'vitest';
import React from 'react';
import type { Email, Mailbox } from '@/lib/jmap/types';
// With a plugin panel open at 280 px next to a 300 px message (three panes
// on a laptop), the subject broke after every few letters. The panel now
// starts collapsed when it would leave the message under 480 px.
vi.mock('@/hooks/use-media-query', () => ({
useDeviceDetection: () => ({ isMobile: false, isTablet: false, isDesktop: true }),
useMediaQuery: () => false,
}));
vi.mock('@/components/tour/tour-provider', () => ({
useTour: () => ({ activeTour: null, startTour: vi.fn(), endTour: vi.fn(), registerStep: vi.fn() }),
}));
vi.mock('@/components/plugins/plugin-slot', () => ({
PluginSlot: ({ name }: { name: string }) => React.createElement('div', { 'data-testid': `slot-${name}` }),
}));
vi.mock('@/hooks/use-plugin-slot-offers', () => ({
usePluginSlotOffers: (slot: string) => (slot === 'email-detail-sidebar' ? [{ pluginId: 'notes', order: 100 }] : []),
}));
let reportWidth = 0;
const observers: Array<(entries: Array<{ contentRect: { width: number } }>) => void> = [];
class FakeResizeObserver {
constructor(private cb: (entries: Array<{ contentRect: { width: number } }>) => void) { observers.push(cb); }
observe() { this.cb([{ contentRect: { width: reportWidth } }]); }
unobserve() {}
disconnect() {}
}
vi.stubGlobal('ResizeObserver', FakeResizeObserver);
const { EmailViewer } = await import('../email-viewer');
const mailboxes = [
{ id: 'inbox', name: 'Inbox', role: 'inbox', parentId: null, myRights: { mayAddItems: true } },
] as unknown as Mailbox[];
const email = {
id: 'e1', blobId: 'b1', threadId: 't1', mailboxIds: { inbox: true }, keywords: { $seen: true },
from: [{ name: 'Alice', email: 'alice@example.com' }], to: [], subject: 'Hello', receivedAt: '2026-01-01T00:00:00Z',
preview: '', hasAttachment: false, size: 1, textBody: [], htmlBody: [], attachments: [], bodyValues: {},
} as unknown as Email;
function renderViewer() {
return render(<EmailViewer email={email} mailboxes={mailboxes} selectedMailbox="inbox" currentMailboxRole="inbox" />);
}
beforeEach(() => {
localStorage.clear();
observers.length = 0;
});
describe('plugin detail panel and narrow messages', () => {
it('starts collapsed when it would leave the message too narrow', () => {
reportWidth = 640;
renderViewer();
expect(screen.queryByTestId('slot-email-detail-sidebar')).not.toBeInTheDocument();
expect(screen.getByRole('button', { name: 'plugin_panel', pressed: false })).toBeInTheDocument();
});
it('opens anyway when the user asks', () => {
reportWidth = 640;
renderViewer();
act(() => { fireEvent.click(screen.getByRole('button', { name: 'plugin_panel', pressed: false })); });
expect(screen.getByTestId('slot-email-detail-sidebar')).toBeInTheDocument();
});
it('is open when there is room', () => {
reportWidth = 1100;
renderViewer();
expect(screen.getByTestId('slot-email-detail-sidebar')).toBeInTheDocument();
});
});