Files
native/src/lib/__tests__/compose-format.test.ts
T
Linus Rath 472c4d8f26 feat: add per-message HTML/plain-text toggle #1022
The composer's format came only from the "plain text only" setting, and
a reopened draft was forced into it: with plain text on, an HTML draft
was flattened on its first autosave, and with it off a text draft opened
in the rich-text editor.

Ported from the webmail (f6ad8a59, `compose-format.ts`):

- a reopened draft opens in the format it was written in; an empty
  draft and new messages still follow the setting
- a toolbar button switches just this message between rich and plain
  text, converting the body in place; the signature keeps its place
  (end or above the quote) in the target format, so it is never
  doubled or lost
- images inserted into the rich-text body stay on as ordinary
  attachments after a switch to plain text instead of being dropped
2026-09-24 19:10:45 +02:00

80 lines
3.6 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
htmlComposeBodyToPlainText, initialPlainTextMode, plainComposeBodyToHtml, plainTextToComposerHtml,
} from '../compose-format';
import { QUOTED_BLOCK_START } from '../compose-html';
import {
appendPlainTextSignature, buildEmbeddedSignatureHtml, containsEmbeddedSignature, plainTextBodyHasSignature,
} from '../signature-utils';
const identity = { textSignature: 'Jane Doe\nACME' };
const opts = { separator: true };
describe('initialPlainTextMode', () => {
it('keeps a reopened draft in the format it was written in', () => {
expect(initialPlainTextMode({ htmlBody: '<p>Hi</p>', textBody: 'Hi' }, true)).toBe(false);
expect(initialPlainTextMode({ textBody: 'Hi' }, false)).toBe(true);
});
it('follows the setting for an empty draft and a new message', () => {
expect(initialPlainTextMode({}, true)).toBe(true);
expect(initialPlainTextMode(undefined, false)).toBe(false);
});
});
describe('plainTextToComposerHtml', () => {
it('makes paragraphs and line breaks and escapes markup', () => {
expect(plainTextToComposerHtml('a <b>\nline\n\nnext')).toBe('<p>a &lt;b&gt;<br>line</p><p>next</p>');
expect(plainTextToComposerHtml('')).toBe('');
});
});
describe('htmlComposeBodyToPlainText', () => {
it('drops formatting', () => {
expect(htmlComposeBodyToPlainText('<p><b>Hello</b> there</p>', identity, opts)).toBe('Hello there');
});
it('turns a trailing embedded signature into the plain-text signature', () => {
const html = `<p>Hello</p>${buildEmbeddedSignatureHtml(identity, opts)}`;
const text = htmlComposeBodyToPlainText(html, identity, opts);
expect(text).toBe(appendPlainTextSignature('Hello', identity, opts));
expect(plainTextBodyHasSignature(text, identity)).toBe(true);
});
it('keeps a signature placed above the quote above the quote', () => {
const html = `<p>Hello</p>${buildEmbeddedSignatureHtml(identity, opts)}${QUOTED_BLOCK_START}<p>On Monday, Bob wrote:</p><blockquote>Old</blockquote></div>`;
expect(htmlComposeBodyToPlainText(html, identity, opts))
.toBe('Hello\n\n-- \nJane Doe\nACME\n\nOn Monday, Bob wrote:\n\nOld');
});
});
describe('plainComposeBodyToHtml', () => {
it('turns a trailing plain-text signature into the embedded one', () => {
const html = plainComposeBodyToHtml(appendPlainTextSignature('Hello', identity, opts), identity, opts);
expect(html).toBe(`<p>Hello</p>${buildEmbeddedSignatureHtml(identity, opts)}`);
expect(containsEmbeddedSignature(html)).toBe(true);
});
it('keeps a signature above the quote in place', () => {
const text = 'Hello\n\n-- \nJane Doe\nACME\n\nOn Monday, Bob wrote:\n> Old';
expect(plainComposeBodyToHtml(text, identity, opts))
.toBe(`<p>Hello</p>${buildEmbeddedSignatureHtml(identity, opts)}<p>On Monday, Bob wrote:<br>&gt; Old</p>`);
});
it('gives a signature-only body an empty first paragraph to type into', () => {
const html = plainComposeBodyToHtml(appendPlainTextSignature('', identity, opts), identity, opts);
expect(html).toBe(`<p><br></p>${buildEmbeddedSignatureHtml(identity, opts)}`);
});
it('converts a body without a signature as plain paragraphs', () => {
expect(plainComposeBodyToHtml('Hi', null, opts)).toBe('<p>Hi</p>');
expect(plainComposeBodyToHtml('', identity, opts)).toBe('<p><br></p>');
});
it('round-trips the signature without doubling it', () => {
const start = `<p>Hello</p>${buildEmbeddedSignatureHtml(identity, opts)}`;
const back = plainComposeBodyToHtml(htmlComposeBodyToPlainText(start, identity, opts), identity, opts);
expect(back).toBe(start);
});
});