mirror of
https://github.com/lingble/chatwoot.git
synced 2025-10-30 18:47:51 +00:00
<img width="983" height="579" alt="image" src="https://github.com/user-attachments/assets/2972e8d9-5145-4958-8f66-9e84bd9c8c4b" />
442 lines
14 KiB
JavaScript
442 lines
14 KiB
JavaScript
import {
|
|
extractPlainTextFromHtml,
|
|
getEmailSenderName,
|
|
getEmailSenderEmail,
|
|
getEmailDate,
|
|
formatQuotedEmailDate,
|
|
getInboxEmail,
|
|
buildQuotedEmailHeader,
|
|
buildQuotedEmailHeaderFromContact,
|
|
buildQuotedEmailHeaderFromInbox,
|
|
formatQuotedTextAsBlockquote,
|
|
extractQuotedEmailText,
|
|
truncatePreviewText,
|
|
appendQuotedTextToMessage,
|
|
} from '../quotedEmailHelper';
|
|
|
|
describe('quotedEmailHelper', () => {
|
|
describe('extractPlainTextFromHtml', () => {
|
|
it('returns empty string for null or undefined', () => {
|
|
expect(extractPlainTextFromHtml(null)).toBe('');
|
|
expect(extractPlainTextFromHtml(undefined)).toBe('');
|
|
});
|
|
|
|
it('strips HTML tags and returns plain text', () => {
|
|
const html = '<p>Hello <strong>world</strong></p>';
|
|
const result = extractPlainTextFromHtml(html);
|
|
expect(result).toBe('Hello world');
|
|
});
|
|
|
|
it('handles complex HTML structure', () => {
|
|
const html = '<div><p>Line 1</p><p>Line 2</p></div>';
|
|
const result = extractPlainTextFromHtml(html);
|
|
expect(result).toContain('Line 1');
|
|
expect(result).toContain('Line 2');
|
|
});
|
|
});
|
|
|
|
describe('getEmailSenderName', () => {
|
|
it('returns sender name from lastEmail', () => {
|
|
const lastEmail = { sender: { name: 'John Doe' } };
|
|
const result = getEmailSenderName(lastEmail, {});
|
|
expect(result).toBe('John Doe');
|
|
});
|
|
|
|
it('returns contact name if sender name not available', () => {
|
|
const lastEmail = { sender: {} };
|
|
const contact = { name: 'Jane Smith' };
|
|
const result = getEmailSenderName(lastEmail, contact);
|
|
expect(result).toBe('Jane Smith');
|
|
});
|
|
|
|
it('returns empty string if neither available', () => {
|
|
const result = getEmailSenderName({}, {});
|
|
expect(result).toBe('');
|
|
});
|
|
|
|
it('trims whitespace from names', () => {
|
|
const lastEmail = { sender: { name: ' John Doe ' } };
|
|
const result = getEmailSenderName(lastEmail, {});
|
|
expect(result).toBe('John Doe');
|
|
});
|
|
});
|
|
|
|
describe('getEmailSenderEmail', () => {
|
|
it('returns sender email from lastEmail', () => {
|
|
const lastEmail = { sender: { email: 'john@example.com' } };
|
|
const result = getEmailSenderEmail(lastEmail, {});
|
|
expect(result).toBe('john@example.com');
|
|
});
|
|
|
|
it('returns email from contentAttributes if sender email not available', () => {
|
|
const lastEmail = {
|
|
contentAttributes: {
|
|
email: { from: ['jane@example.com'] },
|
|
},
|
|
};
|
|
const result = getEmailSenderEmail(lastEmail, {});
|
|
expect(result).toBe('jane@example.com');
|
|
});
|
|
|
|
it('returns contact email as fallback', () => {
|
|
const lastEmail = {};
|
|
const contact = { email: 'contact@example.com' };
|
|
const result = getEmailSenderEmail(lastEmail, contact);
|
|
expect(result).toBe('contact@example.com');
|
|
});
|
|
|
|
it('trims whitespace from emails', () => {
|
|
const lastEmail = { sender: { email: ' john@example.com ' } };
|
|
const result = getEmailSenderEmail(lastEmail, {});
|
|
expect(result).toBe('john@example.com');
|
|
});
|
|
});
|
|
|
|
describe('getEmailDate', () => {
|
|
it('returns parsed date from email metadata', () => {
|
|
const lastEmail = {
|
|
contentAttributes: {
|
|
email: { date: '2024-01-15T10:30:00Z' },
|
|
},
|
|
};
|
|
const result = getEmailDate(lastEmail);
|
|
expect(result).toBeInstanceOf(Date);
|
|
});
|
|
|
|
it('returns date from created_at timestamp', () => {
|
|
const lastEmail = { created_at: 1705318200 };
|
|
const result = getEmailDate(lastEmail);
|
|
expect(result).toBeInstanceOf(Date);
|
|
});
|
|
|
|
it('handles millisecond timestamps', () => {
|
|
const lastEmail = { created_at: 1705318200000 };
|
|
const result = getEmailDate(lastEmail);
|
|
expect(result).toBeInstanceOf(Date);
|
|
});
|
|
|
|
it('returns null if no valid date found', () => {
|
|
const result = getEmailDate({});
|
|
expect(result).toBeNull();
|
|
});
|
|
});
|
|
|
|
describe('formatQuotedEmailDate', () => {
|
|
it('formats date correctly', () => {
|
|
const date = new Date('2024-01-15T10:30:00Z');
|
|
const result = formatQuotedEmailDate(date);
|
|
expect(result).toMatch(/Mon, Jan 15, 2024 at/);
|
|
});
|
|
|
|
it('returns empty string for invalid date', () => {
|
|
const result = formatQuotedEmailDate('invalid');
|
|
expect(result).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('getInboxEmail', () => {
|
|
it('returns email from contentAttributes.email.to', () => {
|
|
const lastEmail = {
|
|
contentAttributes: {
|
|
email: { to: ['inbox@example.com'] },
|
|
},
|
|
};
|
|
const result = getInboxEmail(lastEmail, {});
|
|
expect(result).toBe('inbox@example.com');
|
|
});
|
|
|
|
it('returns inbox email as fallback', () => {
|
|
const lastEmail = {};
|
|
const inbox = { email: 'support@example.com' };
|
|
const result = getInboxEmail(lastEmail, inbox);
|
|
expect(result).toBe('support@example.com');
|
|
});
|
|
|
|
it('returns empty string if no email found', () => {
|
|
expect(getInboxEmail({}, {})).toBe('');
|
|
});
|
|
|
|
it('trims whitespace from emails', () => {
|
|
const lastEmail = {
|
|
contentAttributes: {
|
|
email: { to: [' inbox@example.com '] },
|
|
},
|
|
};
|
|
const result = getInboxEmail(lastEmail, {});
|
|
expect(result).toBe('inbox@example.com');
|
|
});
|
|
});
|
|
|
|
describe('buildQuotedEmailHeaderFromContact', () => {
|
|
it('builds complete header with name and email', () => {
|
|
const lastEmail = {
|
|
sender: { name: 'John Doe', email: 'john@example.com' },
|
|
contentAttributes: {
|
|
email: { date: '2024-01-15T10:30:00Z' },
|
|
},
|
|
};
|
|
const result = buildQuotedEmailHeaderFromContact(lastEmail, {});
|
|
expect(result).toContain('John Doe');
|
|
expect(result).toContain('john@example.com');
|
|
expect(result).toContain('wrote:');
|
|
});
|
|
|
|
it('builds header without name if not available', () => {
|
|
const lastEmail = {
|
|
sender: { email: 'john@example.com' },
|
|
contentAttributes: {
|
|
email: { date: '2024-01-15T10:30:00Z' },
|
|
},
|
|
};
|
|
const result = buildQuotedEmailHeaderFromContact(lastEmail, {});
|
|
expect(result).toContain('<john@example.com>');
|
|
expect(result).not.toContain('undefined');
|
|
});
|
|
|
|
it('returns empty string if missing required data', () => {
|
|
expect(buildQuotedEmailHeaderFromContact(null, {})).toBe('');
|
|
expect(buildQuotedEmailHeaderFromContact({}, {})).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('buildQuotedEmailHeaderFromInbox', () => {
|
|
it('builds complete header with inbox name and email', () => {
|
|
const lastEmail = {
|
|
contentAttributes: {
|
|
email: {
|
|
date: '2024-01-15T10:30:00Z',
|
|
to: ['support@example.com'],
|
|
},
|
|
},
|
|
};
|
|
const inbox = { name: 'Support Team', email: 'support@example.com' };
|
|
const result = buildQuotedEmailHeaderFromInbox(lastEmail, inbox);
|
|
expect(result).toContain('Support Team');
|
|
expect(result).toContain('support@example.com');
|
|
expect(result).toContain('wrote:');
|
|
});
|
|
|
|
it('builds header without name if not available', () => {
|
|
const lastEmail = {
|
|
contentAttributes: {
|
|
email: {
|
|
date: '2024-01-15T10:30:00Z',
|
|
to: ['inbox@example.com'],
|
|
},
|
|
},
|
|
};
|
|
const inbox = { email: 'inbox@example.com' };
|
|
const result = buildQuotedEmailHeaderFromInbox(lastEmail, inbox);
|
|
expect(result).toContain('<inbox@example.com>');
|
|
expect(result).not.toContain('undefined');
|
|
});
|
|
|
|
it('returns empty string if missing required data', () => {
|
|
expect(buildQuotedEmailHeaderFromInbox(null, {})).toBe('');
|
|
expect(buildQuotedEmailHeaderFromInbox({}, {})).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('buildQuotedEmailHeader', () => {
|
|
it('uses inbox email for outgoing messages (message_type: 1)', () => {
|
|
const lastEmail = {
|
|
message_type: 1,
|
|
contentAttributes: {
|
|
email: {
|
|
date: '2024-01-15T10:30:00Z',
|
|
to: ['support@example.com'],
|
|
},
|
|
},
|
|
};
|
|
const inbox = { name: 'Support', email: 'support@example.com' };
|
|
const contact = { name: 'John Doe', email: 'john@example.com' };
|
|
const result = buildQuotedEmailHeader(lastEmail, contact, inbox);
|
|
expect(result).toContain('Support');
|
|
expect(result).toContain('support@example.com');
|
|
expect(result).not.toContain('John Doe');
|
|
});
|
|
|
|
it('uses contact email for incoming messages (message_type: 0)', () => {
|
|
const lastEmail = {
|
|
message_type: 0,
|
|
sender: { name: 'Jane Smith', email: 'jane@example.com' },
|
|
contentAttributes: {
|
|
email: { date: '2024-01-15T10:30:00Z' },
|
|
},
|
|
};
|
|
const inbox = { name: 'Support', email: 'support@example.com' };
|
|
const contact = { name: 'Jane Smith', email: 'jane@example.com' };
|
|
const result = buildQuotedEmailHeader(lastEmail, contact, inbox);
|
|
expect(result).toContain('Jane Smith');
|
|
expect(result).toContain('jane@example.com');
|
|
expect(result).not.toContain('Support');
|
|
});
|
|
|
|
it('returns empty string if missing required data', () => {
|
|
expect(buildQuotedEmailHeader(null, {}, {})).toBe('');
|
|
expect(buildQuotedEmailHeader({}, {}, {})).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('formatQuotedTextAsBlockquote', () => {
|
|
it('formats single line text', () => {
|
|
const result = formatQuotedTextAsBlockquote('Hello world');
|
|
expect(result).toBe('> Hello world');
|
|
});
|
|
|
|
it('formats multi-line text', () => {
|
|
const text = 'Line 1\nLine 2\nLine 3';
|
|
const result = formatQuotedTextAsBlockquote(text);
|
|
expect(result).toBe('> Line 1\n> Line 2\n> Line 3');
|
|
});
|
|
|
|
it('includes header if provided', () => {
|
|
const result = formatQuotedTextAsBlockquote('Hello', 'Header text');
|
|
expect(result).toContain('> Header text');
|
|
expect(result).toContain('>\n> Hello');
|
|
});
|
|
|
|
it('handles empty lines correctly', () => {
|
|
const text = 'Line 1\n\nLine 3';
|
|
const result = formatQuotedTextAsBlockquote(text);
|
|
expect(result).toBe('> Line 1\n>\n> Line 3');
|
|
});
|
|
|
|
it('returns empty string for empty input', () => {
|
|
expect(formatQuotedTextAsBlockquote('')).toBe('');
|
|
expect(formatQuotedTextAsBlockquote('', '')).toBe('');
|
|
});
|
|
|
|
it('handles Windows line endings', () => {
|
|
const text = 'Line 1\r\nLine 2';
|
|
const result = formatQuotedTextAsBlockquote(text);
|
|
expect(result).toBe('> Line 1\n> Line 2');
|
|
});
|
|
});
|
|
|
|
describe('extractQuotedEmailText', () => {
|
|
it('extracts text from textContent.reply', () => {
|
|
const lastEmail = {
|
|
contentAttributes: {
|
|
email: { textContent: { reply: 'Reply text' } },
|
|
},
|
|
};
|
|
const result = extractQuotedEmailText(lastEmail);
|
|
expect(result).toBe('Reply text');
|
|
});
|
|
|
|
it('falls back to textContent.full', () => {
|
|
const lastEmail = {
|
|
contentAttributes: {
|
|
email: { textContent: { full: 'Full text' } },
|
|
},
|
|
};
|
|
const result = extractQuotedEmailText(lastEmail);
|
|
expect(result).toBe('Full text');
|
|
});
|
|
|
|
it('extracts from htmlContent and converts to plain text', () => {
|
|
const lastEmail = {
|
|
contentAttributes: {
|
|
email: { htmlContent: { reply: '<p>HTML reply</p>' } },
|
|
},
|
|
};
|
|
const result = extractQuotedEmailText(lastEmail);
|
|
expect(result).toBe('HTML reply');
|
|
});
|
|
|
|
it('uses fallback content if structured content not available', () => {
|
|
const lastEmail = { content: 'Fallback content' };
|
|
const result = extractQuotedEmailText(lastEmail);
|
|
expect(result).toBe('Fallback content');
|
|
});
|
|
|
|
it('returns empty string for null or missing email', () => {
|
|
expect(extractQuotedEmailText(null)).toBe('');
|
|
expect(extractQuotedEmailText({})).toBe('');
|
|
});
|
|
});
|
|
|
|
describe('truncatePreviewText', () => {
|
|
it('returns full text if under max length', () => {
|
|
const text = 'Short text';
|
|
const result = truncatePreviewText(text, 80);
|
|
expect(result).toBe('Short text');
|
|
});
|
|
|
|
it('truncates text exceeding max length', () => {
|
|
const text = 'A'.repeat(100);
|
|
const result = truncatePreviewText(text, 80);
|
|
expect(result).toHaveLength(80);
|
|
expect(result).toContain('...');
|
|
});
|
|
|
|
it('collapses multiple spaces', () => {
|
|
const text = 'Text with spaces';
|
|
const result = truncatePreviewText(text);
|
|
expect(result).toBe('Text with spaces');
|
|
});
|
|
|
|
it('trims whitespace', () => {
|
|
const text = ' Text with spaces ';
|
|
const result = truncatePreviewText(text);
|
|
expect(result).toBe('Text with spaces');
|
|
});
|
|
|
|
it('returns empty string for empty input', () => {
|
|
expect(truncatePreviewText('')).toBe('');
|
|
expect(truncatePreviewText(' ')).toBe('');
|
|
});
|
|
|
|
it('uses default max length of 80', () => {
|
|
const text = 'A'.repeat(100);
|
|
const result = truncatePreviewText(text);
|
|
expect(result).toHaveLength(80);
|
|
});
|
|
});
|
|
|
|
describe('appendQuotedTextToMessage', () => {
|
|
it('appends quoted text to message', () => {
|
|
const message = 'My reply';
|
|
const quotedText = 'Original message';
|
|
const header = 'On date sender wrote:';
|
|
const result = appendQuotedTextToMessage(message, quotedText, header);
|
|
|
|
expect(result).toContain('My reply');
|
|
expect(result).toContain('> On date sender wrote:');
|
|
expect(result).toContain('> Original message');
|
|
});
|
|
|
|
it('returns only quoted text if message is empty', () => {
|
|
const result = appendQuotedTextToMessage('', 'Quoted', 'Header');
|
|
expect(result).toContain('> Header');
|
|
expect(result).toContain('> Quoted');
|
|
expect(result).not.toContain('\n\n\n');
|
|
});
|
|
|
|
it('returns message if no quoted text', () => {
|
|
const result = appendQuotedTextToMessage('Message', '', '');
|
|
expect(result).toBe('Message');
|
|
});
|
|
|
|
it('handles proper spacing with double newline', () => {
|
|
const result = appendQuotedTextToMessage('Message', 'Quoted', 'Header');
|
|
expect(result).toContain('Message\n\n>');
|
|
});
|
|
|
|
it('does not add extra newlines if message already ends with newlines', () => {
|
|
const result = appendQuotedTextToMessage(
|
|
'Message\n\n',
|
|
'Quoted',
|
|
'Header'
|
|
);
|
|
expect(result).not.toContain('\n\n\n');
|
|
});
|
|
|
|
it('adds single newline if message ends with one newline', () => {
|
|
const result = appendQuotedTextToMessage('Message\n', 'Quoted', 'Header');
|
|
expect(result).toContain('Message\n\n>');
|
|
});
|
|
});
|
|
});
|