Feature: contact groups as single expandable recipient chips
Typing a contact group's name in a recipient field suggested the
individual members, and "send email to group" on the contacts page
filled the field with one chip per member - the group itself never
appeared anywhere.
The autocomplete now offers the group as a single entry (group icon
plus member count), and selecting it - like the contacts-page action -
inserts one chip named after the group that carries a snapshot of its
members. The chip expands into the deduplicated member addresses when
the message is sent or saved as a draft, mirroring how Outlook handles
distribution lists. Expansion happens where the outgoing address lists
are built, so validation and every plugin hook see real addresses.
Group chips survive the composer's string boundaries (draft data, dirty
compare, the contacts-page hand-off) as RFC 5322 group syntax
("Team: a@x, b@y;"). A bare colon reliably opens a group there because
display names containing a colon are always quoted. Typed text only
parses as a group when it carries at least one valid member, so stray
"Subject: hello" input stays a plain recipient.
RecipientSuggestion gains an optional group field; plugins that ignore
it keep working unchanged.
This commit is contained in:
@@ -12,7 +12,9 @@ import {
|
||||
splitPastedRecipients,
|
||||
waitForPendingUploads,
|
||||
extractUserAuthoredText,
|
||||
} from "../email-composer-utils";
|
||||
formatRecipientEntry,
|
||||
expandRecipients,
|
||||
} from '../email-composer-utils';
|
||||
|
||||
const FORWARDED_SEPARATOR = "---------- Forwarded message ----------";
|
||||
|
||||
@@ -422,3 +424,56 @@ describe("waitForPendingUploads", () => {
|
||||
expect(result).toBe("cancelled");
|
||||
});
|
||||
});
|
||||
|
||||
describe('contact group recipients (RFC 5322 group syntax)', () => {
|
||||
const group = {
|
||||
name: 'Vertrieb',
|
||||
email: '',
|
||||
group: { members: [
|
||||
{ name: 'Anna Alt', email: 'anna@example.com' },
|
||||
{ email: 'bob@example.com' },
|
||||
] },
|
||||
};
|
||||
|
||||
it('formats a group chip as RFC 5322 group syntax', () => {
|
||||
expect(formatRecipientEntry(group)).toBe('Vertrieb: Anna Alt <anna@example.com>, bob@example.com;');
|
||||
});
|
||||
|
||||
it('round-trips a group through format -> parse', () => {
|
||||
const parsed = parseRecipientList(formatRecipientList([group, { email: 'solo@example.com' }]));
|
||||
expect(parsed).toHaveLength(2);
|
||||
expect(parsed[0].group?.members).toEqual([
|
||||
{ name: 'Anna Alt', email: 'anna@example.com' },
|
||||
{ email: 'bob@example.com' },
|
||||
]);
|
||||
expect(parsed[0].name).toBe('Vertrieb');
|
||||
expect(parsed[0].email).toBe('');
|
||||
expect(parsed[1]).toEqual({ email: 'solo@example.com' });
|
||||
});
|
||||
|
||||
it('quotes group names containing specials and round-trips them', () => {
|
||||
const tricky = { name: 'Sales, EMEA', email: '', group: { members: [{ email: 'a@x.de' }] } };
|
||||
const parsed = parseRecipientList(formatRecipientList([tricky]));
|
||||
expect(parsed[0].name).toBe('Sales, EMEA');
|
||||
expect(parsed[0].group?.members).toEqual([{ email: 'a@x.de' }]);
|
||||
});
|
||||
|
||||
it('keeps commas inside a group while splitting a mixed list', () => {
|
||||
const parsed = parseRecipientList('first@x.de, Team: a@x.de, b@x.de;, last@x.de');
|
||||
expect(parsed.map(r => r.email || r.name)).toEqual(['first@x.de', 'Team', 'last@x.de']);
|
||||
expect(parsed[1].group?.members).toHaveLength(2);
|
||||
});
|
||||
|
||||
it('expandRecipients flattens groups and dedupes against individuals', () => {
|
||||
const expanded = expandRecipients([
|
||||
{ name: 'Anna Alt', email: 'ANNA@example.com' },
|
||||
group,
|
||||
{ email: 'bob@example.com' },
|
||||
]);
|
||||
expect(expanded.map(r => r.email)).toEqual(['ANNA@example.com', 'bob@example.com']);
|
||||
});
|
||||
|
||||
it('leaves plain recipients untouched by expansion', () => {
|
||||
expect(expandRecipients([{ name: 'X', email: 'x@y.z' }])).toEqual([{ name: 'X', email: 'x@y.z' }]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user