import { describe, it, expect } from 'vitest'; import { EmailQuoteExtractor } from '../emailQuoteExtractor.js'; const SAMPLE_EMAIL_HTML = `

method

On Mon, Sep 29, 2025 at 5:18 PM John shivam@chatwoot.com wrote:

Hi

On Mon, Sep 29, 2025 at 5:17 PM Shivam Mishra shivam@chatwoot.com wrote:

Yes, it is.

On Mon, Sep 29, 2025 at 5:16 PM John from Shaneforwoot < shaneforwoot@gmail.com> wrote:

Hey

On Mon, Sep 29, 2025 at 4:59 PM John shivam@chatwoot.com wrote:

This is another quoted quoted text reply

This is nice

On Mon, Sep 29, 2025 at 4:21 PM John from Shaneforwoot < > shaneforwoot@gmail.com> wrote:

Hey there, this is a reply from Chatwoot, notice the quoted text

Hey there

This is an email text, enjoy reading this

-- Shivam Mishra, Chatwoot

`; const EMAIL_WITH_SIGNATURE = `

Latest reply here.

Thanks,

Jane Doe

On Mon, Sep 22, Someone wrote:

Previous reply content

`; const EMAIL_WITH_FOLLOW_UP_CONTENT = `

Inline quote that should stay

Internal note follows

Regards,

`; describe('EmailQuoteExtractor', () => { it('removes blockquote-based quotes from the email body', () => { const cleanedHtml = EmailQuoteExtractor.extractQuotes(SAMPLE_EMAIL_HTML); const container = document.createElement('div'); container.innerHTML = cleanedHtml; expect(container.querySelectorAll('blockquote').length).toBe(0); expect(container.textContent?.trim()).toBe('method'); expect(container.textContent).not.toContain( 'On Mon, Sep 29, 2025 at 5:18 PM' ); }); it('keeps blockquote fallback when it is not the last top-level element', () => { const cleanedHtml = EmailQuoteExtractor.extractQuotes( EMAIL_WITH_FOLLOW_UP_CONTENT ); const container = document.createElement('div'); container.innerHTML = cleanedHtml; expect(container.querySelector('blockquote')).not.toBeNull(); expect(container.lastElementChild?.tagName).toBe('P'); }); it('detects quote indicators in nested blockquotes', () => { const result = EmailQuoteExtractor.hasQuotes(SAMPLE_EMAIL_HTML); expect(result).toBe(true); }); it('does not flag blockquotes that are followed by other elements', () => { expect(EmailQuoteExtractor.hasQuotes(EMAIL_WITH_FOLLOW_UP_CONTENT)).toBe( false ); }); it('returns false when no quote indicators are present', () => { const html = '

Plain content

'; expect(EmailQuoteExtractor.hasQuotes(html)).toBe(false); }); it('removes trailing blockquotes while preserving trailing signatures', () => { const cleanedHtml = EmailQuoteExtractor.extractQuotes(EMAIL_WITH_SIGNATURE); expect(cleanedHtml).toContain('

Thanks,

'); expect(cleanedHtml).toContain('

Jane Doe

'); expect(cleanedHtml).not.toContain(' { expect(EmailQuoteExtractor.hasQuotes(EMAIL_WITH_SIGNATURE)).toBe(true); }); describe('HTML sanitization', () => { it('removes onerror handlers from img tags in extractQuotes', () => { const maliciousHtml = '

Hello

'; const cleanedHtml = EmailQuoteExtractor.extractQuotes(maliciousHtml); expect(cleanedHtml).not.toContain('onerror'); expect(cleanedHtml).toContain('

Hello

'); }); it('removes onerror handlers from img tags in hasQuotes', () => { const maliciousHtml = '

Hello

'; // Should not throw and should safely check for quotes const result = EmailQuoteExtractor.hasQuotes(maliciousHtml); expect(result).toBe(false); }); it('removes script tags in extractQuotes', () => { const maliciousHtml = '

Content

More

'; const cleanedHtml = EmailQuoteExtractor.extractQuotes(maliciousHtml); expect(cleanedHtml).not.toContain('Content

'); expect(cleanedHtml).toContain('

More

'); }); it('removes onclick handlers in extractQuotes', () => { const maliciousHtml = '

Click me

'; const cleanedHtml = EmailQuoteExtractor.extractQuotes(maliciousHtml); expect(cleanedHtml).not.toContain('onclick'); expect(cleanedHtml).toContain('Click me'); }); it('removes javascript: URLs in extractQuotes', () => { const maliciousHtml = 'Link'; const cleanedHtml = EmailQuoteExtractor.extractQuotes(maliciousHtml); // eslint-disable-next-line no-script-url expect(cleanedHtml).not.toContain('javascript:'); expect(cleanedHtml).toContain('Link'); }); it('removes encoded payloads with event handlers in extractQuotes', () => { const maliciousHtml = ''; const cleanedHtml = EmailQuoteExtractor.extractQuotes(maliciousHtml); expect(cleanedHtml).not.toContain('onerror'); expect(cleanedHtml).not.toContain('eval'); }); }); });