recipientClipboard.test.ts2.6 KBView on GitHub
/**
 * Copying a recipient pill (cmd+c on a selected to/cc/bcc badge) must put the bare
 * address on the SYSTEM clipboard, not just the in-app one used to move recipients
 * between fields. Previously cmd+c only populated the in-app clipboard, so pasting
 * the address anywhere outside the composer produced nothing.
 */

import { act, renderHook } from '@testing-library/react';
import { useRecipientClipboard } from '@/modules/drafting/hooks/use-recipient-clipboard';

const writeText = jest.fn<Promise<void>, [string]>(() => Promise.resolve());

beforeEach(() => {
  writeText.mockClear();
  writeText.mockResolvedValue(undefined);
  Object.defineProperty(navigator, 'clipboard', {
    value: { writeText },
    configurable: true,
    writable: true,
  });
});

describe('useRecipientClipboard.copyRecipient', () => {
  it('writes the address to the system clipboard', () => {
    const { result } = renderHook(() => useRecipientClipboard());

    act(() => result.current.copyRecipient('<email>', 'to'));

    expect(writeText).toHaveBeenCalledWith('<email>');
  });

  it('strips the display name before writing', () => {
    const { result } = renderHook(() => useRecipientClipboard());

    act(() => result.current.copyRecipient('Alice Smith <<email>>', 'cc'));

    expect(writeText).toHaveBeenCalledWith('<email>');
  });

  it('still populates the in-app clipboard for cross-field paste', () => {
    const { result } = renderHook(() => useRecipientClipboard());

    act(() => result.current.copyRecipient('Bob <<email>>', 'bcc'));

    expect(result.current.clipboard).toEqual({ email: '<email>', sourceField: 'bcc' });
    expect(result.current.pasteRecipient('to')).toEqual({
      email: '<email>',
      sourceField: 'bcc',
      targetField: 'to',
    });
  });

  it('keeps the in-app clipboard working when the system clipboard is denied', () => {
    writeText.mockRejectedValue(new Error('NotAllowedError'));
    const { result } = renderHook(() => useRecipientClipboard());

    act(() => result.current.copyRecipient('<email>', 'to'));

    expect(result.current.clipboard?.email).toBe('<email>');
  });

  it('does not throw when the clipboard API is unavailable', () => {
    Object.defineProperty(navigator, 'clipboard', { value: undefined, configurable: true });
    const { result } = renderHook(() => useRecipientClipboard());

    expect(() => act(() => result.current.copyRecipient('<email>', 'to'))).not.toThrow();
    expect(result.current.clipboard?.email).toBe('<email>');
  });
});