From 8e46d5e5dce5f57cdc2a031d8bf9cba785aa03a6 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Wed, 4 Nov 2026 06:31:01 +0100 Subject: [PATCH] feat: edit signatures with the composer's formatting editor instead of HTML code The two boxes "Text signature" and "HTML signature" with byte counters become one Signature field with the composer's editor; the plain-text part is generated from it. "Edit as HTML" shows with advanced settings on, the size shows only near the server's limit and a signature over it isn't saved instead of being cut off. With Composing > Plain text only it is a plain text box. The org-signature test looks for the new field's label. --- .../identity-form-org-signature.test.tsx | 9 +- .../__tests__/signature-editor.test.tsx | 96 ++++++++++ components/identity/identity-form.tsx | 120 +++---------- components/identity/signature-editor.tsx | 168 ++++++++++++++++++ 4 files changed, 289 insertions(+), 104 deletions(-) create mode 100644 components/identity/__tests__/signature-editor.test.tsx create mode 100644 components/identity/signature-editor.tsx diff --git a/components/identity/__tests__/identity-form-org-signature.test.tsx b/components/identity/__tests__/identity-form-org-signature.test.tsx index c83a580a9..9e27c7fb4 100644 --- a/components/identity/__tests__/identity-form-org-signature.test.tsx +++ b/components/identity/__tests__/identity-form-org-signature.test.tsx @@ -44,8 +44,7 @@ describe('identity signature editor and the organisation signature', () => { const locked = screen.getByTestId('identity-locked-signature'); expect(locked.innerHTML).toContain('Alice
Phone: +49 1'); expect(locked.querySelector('[data-org-locked]')).not.toBeNull(); - expect(screen.queryByLabelText('text_signature_label')).toBeNull(); - expect(screen.queryByLabelText('html_signature_label')).toBeNull(); + expect(screen.queryByText('signature_label')).toBeNull(); // Saving leaves the identity's own signature as it is. fireEvent.submit(locked.closest('form')!); @@ -58,13 +57,13 @@ describe('identity signature editor and the organisation signature', () => { setPolicy({ hidden: true }); render( {}} />); expect(screen.queryByTestId('identity-locked-signature')).toBeNull(); - expect(screen.queryByLabelText('text_signature_label')).toBeNull(); + expect(screen.queryByText('signature_label')).toBeNull(); }); - it('keeps the editors, with a hint, when not restricted', () => { + it('keeps the editor, with a hint, when not restricted', () => { setPolicy(); render( {}} />); - expect(screen.getByLabelText('text_signature_label')).toBeInTheDocument(); + expect(screen.getByText('signature_label')).toBeInTheDocument(); expect(screen.getByTestId('identity-org-signature-default')).toHaveTextContent('identity_default'); expect(screen.queryByTestId('identity-locked-signature')).toBeNull(); }); diff --git a/components/identity/__tests__/signature-editor.test.tsx b/components/identity/__tests__/signature-editor.test.tsx new file mode 100644 index 000000000..eb403a022 --- /dev/null +++ b/components/identity/__tests__/signature-editor.test.tsx @@ -0,0 +1,96 @@ +import { render, screen, fireEvent, waitFor } from '@testing-library/react'; +import { afterEach, describe, expect, it, vi } from 'vitest'; +import React from 'react'; +import { IdentityForm } from '../identity-form'; +import { useSettingsStore } from '@/stores/settings-store'; +import type { Identity } from '@/lib/jmap/types'; + +// The signature in the identity editor: one field with the composer's +// formatting editor, its plain-text part generated, "Edit as HTML" only with +// advanced settings on, a size line only near the server's limit, and a +// plain text box for someone who writes plain text only. + +// The editor hands the form its HTML as typed. +vi.mock('@/components/email/rich-text-editor', () => ({ + RichTextEditor: ({ content, onChange }: { content: string; onChange: (html: string) => void }) => ( +