mirror of
https://github.com/bulwarkmail/webmail.git
synced 2026-11-01 16:12:02 +00:00
173 lines
8.0 KiB
TypeScript
173 lines
8.0 KiB
TypeScript
import { describe, expect, it } from 'vitest';
|
|
|
|
import { buildMentionCandidates, filterMentionCandidates, mentionLabel, mentionSuggestions } from '../recipient-mentions';
|
|
|
|
describe('mentionLabel', () => {
|
|
it('takes the first name from a display name', () => {
|
|
expect(mentionLabel('Max Mustermann', 'mm@dornig.de')).toBe('Max');
|
|
expect(mentionLabel('Mustermann, Max', 'mm@dornig.de')).toBe('Max');
|
|
expect(mentionLabel('Dr. Max Mustermann', 'mm@dornig.de')).toBe('Max');
|
|
expect(mentionLabel('Prof. Dr.-Ing. Max Mustermann', 'mm@dornig.de')).toBe('Max');
|
|
expect(mentionLabel('Jürgen Müller', 'jm@dornig.de')).toBe('Jürgen');
|
|
expect(mentionLabel("'Max Mustermann'", 'mm@dornig.de')).toBe('Max');
|
|
});
|
|
|
|
it('takes the first name from a firstname.lastname address when no name is known', () => {
|
|
expect(mentionLabel(undefined, 'max.mustermann@dornig.de')).toBe('Max');
|
|
expect(mentionLabel('', 'anna-lena.schmidt@dornig.de')).toBe('Anna-Lena');
|
|
expect(mentionLabel(undefined, 'max.mustermann+rechnungen@dornig.de')).toBe('Max');
|
|
expect(mentionLabel(undefined, 'juergen.mueller@dornig.de')).toBe('Juergen');
|
|
expect(mentionLabel(undefined, 'info@dornig.de')).toBe('Info');
|
|
});
|
|
|
|
it('lets a firstname.lastname address tell which word of the name is the first name', () => {
|
|
expect(mentionLabel('Mustermann Max', 'max.mustermann@dornig.de')).toBe('Max');
|
|
expect(mentionLabel('Müller Jürgen', 'juergen.mueller@dornig.de')).toBe('Jürgen');
|
|
expect(mentionLabel('Dornig GmbH - Max Mustermann', 'max.mustermann@dornig.de')).toBe('Max');
|
|
// A one-part mailbox name confirms nothing: it may be the last name.
|
|
expect(mentionLabel('Max Mustermann', 'mustermann@dornig.de')).toBe('Max');
|
|
});
|
|
|
|
it('treats a display name that is an address or mailbox name like the address', () => {
|
|
expect(mentionLabel('max.mustermann@dornig.de', 'max.mustermann@dornig.de')).toBe('Max');
|
|
expect(mentionLabel('max.mustermann', 'mm@dornig.de')).toBe('Max');
|
|
});
|
|
|
|
it('keeps the whole mailbox name when the first name is a bare initial', () => {
|
|
expect(mentionLabel(undefined, 'm.mustermann@dornig.de')).toBe('m.mustermann');
|
|
expect(mentionLabel(undefined, 'm.mustermann+news@dornig.de')).toBe('m.mustermann');
|
|
expect(mentionLabel('M. Mustermann', 'm.mustermann@dornig.de')).toBe('m.mustermann');
|
|
// The address may know more than the display name.
|
|
expect(mentionLabel('M. Mustermann', 'max.mustermann@dornig.de')).toBe('Max');
|
|
});
|
|
|
|
it('stays fast on a crafted display name from an incoming message', () => {
|
|
// Uncapped, trimming this token takes seconds (quadratic backtracking).
|
|
const crafted = `a${'!'.repeat(100_000)}b`;
|
|
const started = performance.now();
|
|
expect(mentionLabel(crafted, 'max.mustermann@dornig.de')).toBe('Max');
|
|
expect(performance.now() - started).toBeLessThan(1000);
|
|
});
|
|
});
|
|
|
|
describe('buildMentionCandidates', () => {
|
|
it('offers To and Cc recipients in order, each address once', () => {
|
|
const candidates = buildMentionCandidates(
|
|
[{ name: 'Max Mustermann', email: 'max.mustermann@dornig.de' }, { email: 'anna.schmidt@dornig.de' }],
|
|
[{ email: 'MAX.MUSTERMANN@dornig.de' }, { name: 'Eva Weber', email: 'eva.weber@dornig.de' }],
|
|
);
|
|
expect(candidates).toEqual([
|
|
{ label: 'Max', name: 'Max Mustermann', email: 'max.mustermann@dornig.de' },
|
|
{ label: 'Anna', name: undefined, email: 'anna.schmidt@dornig.de' },
|
|
{ label: 'Eva', name: 'Eva Weber', email: 'eva.weber@dornig.de' },
|
|
]);
|
|
});
|
|
|
|
it('expands contact groups into their members and skips entries without an address', () => {
|
|
const candidates = buildMentionCandidates(
|
|
[{
|
|
name: 'Team',
|
|
email: '',
|
|
group: { members: [{ name: 'Max Mustermann', email: 'max.mustermann@dornig.de' }, { email: 'eva.weber@dornig.de' }] },
|
|
}],
|
|
[{ email: 'not-an-address' }],
|
|
);
|
|
expect(candidates.map((c) => c.label)).toEqual(['Max', 'Eva']);
|
|
});
|
|
|
|
it('tells apart recipients who would share a first name', () => {
|
|
const labels = (to: Array<{ name?: string; email: string }>) => buildMentionCandidates(to, []).map((c) => c.label);
|
|
expect(labels([
|
|
{ name: 'Max Mustermann', email: 'a@dornig.de' },
|
|
{ name: 'Mueller, Max', email: 'b@dornig.de' },
|
|
{ name: 'Eva Weber', email: 'c@dornig.de' },
|
|
])).toEqual(['Max Mustermann', 'Max Mueller', 'Eva']);
|
|
expect(labels([
|
|
{ name: 'Max Mustermann', email: 'max.mustermann@dornig.de' },
|
|
{ email: 'max.mueller@dornig.de' },
|
|
])).toEqual(['Max Mustermann', 'max.mueller']);
|
|
expect(labels([
|
|
{ name: 'Max Mustermann', email: 'max.mustermann@dornig.de' },
|
|
{ name: 'Max Mustermann', email: 'max.mustermann2@dornig.de' },
|
|
])).toEqual(['max.mustermann', 'max.mustermann2']);
|
|
});
|
|
});
|
|
|
|
describe('filterMentionCandidates', () => {
|
|
const candidates = buildMentionCandidates(
|
|
[
|
|
{ name: 'Max Mustermann', email: 'max.mustermann@dornig.de' },
|
|
{ name: 'Jürgen Müller', email: 'juergen.mueller@dornig.de' },
|
|
{ email: 'anna-lena.schmidt@dornig.de' },
|
|
],
|
|
[],
|
|
);
|
|
const labels = (query: string) => filterMentionCandidates(candidates, query).map((c) => c.label);
|
|
|
|
it('offers everyone for a bare @', () => {
|
|
expect(labels('')).toEqual(['Max', 'Jürgen', 'Anna-Lena']);
|
|
});
|
|
|
|
it('matches the start of the label, a name word or a mailbox part, ignoring case', () => {
|
|
expect(labels('ma')).toEqual(['Max']);
|
|
expect(labels('MUSTER')).toEqual(['Max']);
|
|
expect(labels('lena')).toEqual(['Anna-Lena']);
|
|
expect(labels('schm')).toEqual(['Anna-Lena']);
|
|
expect(labels('xyz')).toEqual([]);
|
|
});
|
|
|
|
it('finds umlaut names typed either way', () => {
|
|
expect(labels('jü')).toEqual(['Jürgen']);
|
|
expect(labels('jue')).toEqual(['Jürgen']);
|
|
expect(labels('mül')).toEqual(['Jürgen']);
|
|
});
|
|
});
|
|
|
|
describe('mentionSuggestions', () => {
|
|
const recipients = buildMentionCandidates(
|
|
[{ name: 'Max Mustermann', email: 'max.mustermann@dornig.de' }],
|
|
[{ name: 'Eva Weber', email: 'eva.weber@dornig.de' }],
|
|
);
|
|
const shown = (list: ReturnType<typeof mentionSuggestions>) => list.map((c) => `${c.label} ${c.email} ${c.addsTo ?? '-'}`);
|
|
|
|
it('offers matching recipients first, then contacts marked with the field they go to', () => {
|
|
const contacts = [{ name: 'Marta Klein', email: 'marta@dornig.de' }];
|
|
expect(shown(mentionSuggestions(recipients, contacts, 'ma', 'to'))).toEqual([
|
|
'Max max.mustermann@dornig.de -',
|
|
'Marta marta@dornig.de to',
|
|
]);
|
|
expect(mentionSuggestions(recipients, contacts, 'ma', 'cc')[1].addsTo).toBe('cc');
|
|
});
|
|
|
|
it('leaves out contacts who are already recipients', () => {
|
|
const contacts = [{ name: 'Eva W.', email: 'Eva.Weber@dornig.de' }];
|
|
expect(shown(mentionSuggestions(recipients, contacts, 'ev', 'to'))).toEqual(['Eva eva.weber@dornig.de -']);
|
|
});
|
|
|
|
it('drops contacts that only matched in the middle of a word', () => {
|
|
// The address autocomplete matches anywhere; an @ matches word starts.
|
|
const contacts = [{ name: 'Thomas Bauer', email: 'thomas@dornig.de' }];
|
|
expect(mentionSuggestions(recipients, contacts, 'ma', 'to').map((c) => c.email)).toEqual(['max.mustermann@dornig.de']);
|
|
});
|
|
|
|
it('skips groups and entries without a usable address', () => {
|
|
const contacts = [{ name: 'Marketing', email: '' }, { name: 'Mara', email: 'mara' }];
|
|
expect(mentionSuggestions([], contacts, 'ma', 'to')).toEqual([]);
|
|
});
|
|
|
|
it('renames a contact who shares a first name with a recipient, not the recipient', () => {
|
|
const contacts = [{ name: 'Max Müller', email: 'max.mueller@dornig.de' }];
|
|
expect(shown(mentionSuggestions(recipients, contacts, 'max', 'to'))).toEqual([
|
|
'Max max.mustermann@dornig.de -',
|
|
'Max Müller max.mueller@dornig.de to',
|
|
]);
|
|
// The recipient list itself keeps its labels.
|
|
expect(recipients[0].label).toBe('Max');
|
|
});
|
|
|
|
it('caps the contacts it offers', () => {
|
|
const contacts = Array.from({ length: 20 }, (_, i) => ({ name: `Mia ${i}`, email: `mia${i}@dornig.de` }));
|
|
expect(mentionSuggestions([], contacts, 'mia', 'to')).toHaveLength(8);
|
|
});
|
|
});
|