use pter::convert;
#[test]
fn simple_email() {
let html = r#"
<html>
<head><title>Email</title></head>
<body>
<h1>Meeting Tomorrow</h1>
<p>Hi Max,</p>
<p>Just confirming our meeting tomorrow at <strong>2pm</strong>.</p>
<p>Best,<br>Alice</p>
</body>
</html>
"#;
let md = convert(html);
assert!(md.contains("# Meeting Tomorrow"));
assert!(md.contains("Hi Max,"));
assert!(md.contains("**2pm**"));
assert!(md.contains("Best,\nAlice"));
}
#[test]
fn email_with_links() {
let html = r#"
<body>
<p>Please review the <a href="https://example.com/doc">document</a>.</p>
<p>Direct link: <a href="https://example.com">https://example.com</a></p>
</body>
"#;
let md = convert(html);
assert!(md.contains("[document](https://example.com/doc)"));
assert!(md.contains("Direct link: https://example.com"));
}
#[test]
fn email_with_tracking_pixels() {
let html = r#"
<body>
<p>Content here</p>
<img src="https://tracker.example.com/open.gif" width="1" height="1" alt="">
<img src="data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7" alt="">
<img src="real-image.jpg" alt="A real photo" width="600">
</body>
"#;
let md = convert(html);
assert!(md.contains("Content here"));
assert!(!md.contains("tracker"));
assert!(!md.contains("data:image"));
assert!(md.contains(""));
}
#[test]
fn email_with_quoted_reply() {
let html = r#"
<body>
<p>Thanks, that works for me.</p>
<blockquote>
<p>Can we meet at 3pm instead?</p>
</blockquote>
</body>
"#;
let md = convert(html);
assert!(md.contains("Thanks, that works for me."));
assert!(md.contains("> Can we meet at 3pm instead?"));
}
#[test]
fn email_with_signature_line() {
let html = r#"
<body>
<p>See you then.</p>
<hr>
<p>Alice Smith</p>
<p>Engineering Lead</p>
</body>
"#;
let md = convert(html);
assert!(md.contains("See you then."));
assert!(md.contains("---"));
assert!(md.contains("Alice Smith"));
}
#[test]
fn deeply_nested_blockquotes() {
let html = r#"
<body>
<p>Got it.</p>
<blockquote>
<p>Sounds good.</p>
<blockquote>
<p>Can we reschedule?</p>
<blockquote>
<p>Original message here.</p>
</blockquote>
</blockquote>
</blockquote>
</body>
"#;
let md = convert(html);
assert!(md.contains("Got it."));
assert!(md.contains("> Sounds good."));
assert!(md.contains("> > Can we reschedule?"));
assert!(md.contains("> > > Original message here."));
}
#[test]
fn complex_list_structure() {
let html = r#"
<body>
<p>Action items:</p>
<ol>
<li>Review the PR
<ul>
<li>Check tests</li>
<li>Check docs</li>
</ul>
</li>
<li>Deploy to staging</li>
</ol>
</body>
"#;
let md = convert(html);
assert!(md.contains("Action items:"));
assert!(md.contains("1. Review the PR"));
assert!(md.contains(" - Check tests"));
assert!(md.contains("2. Deploy to staging"));
}
#[test]
fn pre_block_preserves_formatting() {
let html = r#"
<body>
<p>Here's the code:</p>
<pre><code>fn main() {
println!("hello");
}</code></pre>
</body>
"#;
let md = convert(html);
assert!(md.contains("Here's the code:"));
assert!(md.contains("```\nfn main()"));
assert!(md.contains(" println!"));
}
#[test]
fn hidden_content_stripped() {
let html = r#"
<body>
<p>Visible content</p>
<div style="display: none;">
<p>This should not appear</p>
</div>
<span style="visibility: hidden;">Also hidden</span>
<p>More visible</p>
</body>
"#;
let md = convert(html);
assert!(md.contains("Visible content"));
assert!(!md.contains("should not appear"));
assert!(!md.contains("Also hidden"));
assert!(md.contains("More visible"));
}
#[test]
fn script_and_style_fully_removed() {
let html = r#"
<html>
<head>
<style>body { color: red; }</style>
<script>alert('xss');</script>
</head>
<body>
<p>Safe content</p>
<script>document.write('injected')</script>
</body>
</html>
"#;
let md = convert(html);
assert_eq!(md, "Safe content");
}
#[test]
fn newsletter_table_layout() {
let html = r#"
<html>
<body>
<table width="100%" cellpadding="0" cellspacing="0" role="presentation">
<tr>
<td align="center">
<table width="600" cellpadding="0" cellspacing="0">
<tr>
<td>
<h2>Weekly Digest</h2>
<p>Here are your updates for this week.</p>
<ul>
<li>New release v2.0</li>
<li>Bug fixes</li>
</ul>
<p>Thanks for reading!</p>
</td>
</tr>
</table>
</td>
</tr>
</table>
<img src="https://track.example.com/open.gif" width="1" height="1">
</body>
</html>
"#;
let md = convert(html);
assert!(md.contains("## Weekly Digest"));
assert!(md.contains("Here are your updates for this week."));
assert!(md.contains("- New release v2.0"));
assert!(md.contains("- Bug fixes"));
assert!(md.contains("Thanks for reading!"));
assert!(!md.contains("track.example.com"));
assert!(!md.contains("| "));
}
#[test]
fn data_table_preserved() {
let html = r#"
<body>
<p>Order summary:</p>
<table>
<thead><tr><th>Item</th><th>Qty</th><th>Price</th></tr></thead>
<tbody>
<tr><td>Widget</td><td>3</td><td>$15.00</td></tr>
<tr><td>Gadget</td><td>1</td><td>$29.99</td></tr>
</tbody>
</table>
</body>
"#;
let md = convert(html);
assert!(md.contains("Order summary:"));
assert!(md.contains("| Item | Qty | Price |"));
assert!(md.contains("| --- | --- | --- |"));
assert!(md.contains("| Widget | 3 | $15.00 |"));
assert!(md.contains("| Gadget | 1 | $29.99 |"));
}
#[test]
fn spacer_and_tracking_stripped() {
let html = r#"
<body>
<p>Real content</p>
<div style="font-size: 0; line-height: 0;"> </div>
<img src="pixel.gif" width="1" height="1" style="display:none">
<div style="height:0;overflow:hidden">invisible</div>
<p>More content</p>
</body>
"#;
let md = convert(html);
assert!(md.contains("Real content"));
assert!(md.contains("More content"));
assert!(!md.contains("invisible"));
assert!(!md.contains("pixel.gif"));
}
#[test]
fn gmail_reply_chain() {
let html = r#"
<body>
<div dir="ltr">
<p>Thanks, that works for me.</p>
</div>
<div class="gmail_quote">
<div class="gmail_attr">On Mon, Jan 5, 2026 at 3:00 PM Alice <alice@example.com> wrote:</div>
<blockquote class="gmail_quote">
<div dir="ltr">
<p>Can we meet at 3pm instead of 2pm?</p>
</div>
</blockquote>
</div>
</body>
"#;
let md = convert(html);
assert!(md.contains("Thanks, that works for me."));
assert!(md.contains("> "));
assert!(md.contains("3pm instead of 2pm"));
}
#[test]
fn apple_mail_reply() {
let html = r#"
<body>
<div>Sounds good, see you then.</div>
<div>
<br>
<blockquote type="cite">
<div>Hey, are we still on for lunch?</div>
</blockquote>
</div>
</body>
"#;
let md = convert(html);
assert!(md.contains("Sounds good, see you then."));
assert!(md.contains("> "));
assert!(md.contains("still on for lunch"));
}
#[test]
fn outlook_reply_with_separator() {
let html = r#"
<body>
<div>
<p>I'll handle it.</p>
</div>
<hr>
<div>
<p>From: Alice Smith<br>
Sent: Monday, January 5, 2026<br>
To: Bob Jones<br>
Subject: Action needed</p>
</div>
<div>
<p>Can you take a look at the report?</p>
</div>
</body>
"#;
let md = convert(html);
assert!(md.contains("I'll handle it."));
assert!(md.contains("---")); assert!(md.contains("From: Alice Smith"));
assert!(md.contains("take a look at the report"));
}
#[test]
fn nested_gmail_reply_chain() {
let html = r#"
<body>
<div dir="ltr"><p>Got it, thanks!</p></div>
<div class="gmail_quote">
On Tue, Jan 6, Bob wrote:
<blockquote class="gmail_quote">
<div dir="ltr"><p>Here's the update.</p></div>
<div class="gmail_quote">
On Mon, Jan 5, Alice wrote:
<blockquote class="gmail_quote">
<div dir="ltr"><p>What's the status?</p></div>
</blockquote>
</div>
</blockquote>
</div>
</body>
"#;
let md = convert(html);
assert!(md.contains("Got it, thanks!"));
assert!(md.contains("> "));
assert!(md.contains("Here's the update."));
assert!(md.contains("What's the status?"));
}
#[test]
fn forwarded_message() {
let html = r#"
<body>
<div><p>FYI, see below.</p></div>
<div class="gmail_quote">
---------- Forwarded message ----------
<blockquote>
<p>From: Alice</p>
<p>The deadline has been moved to Friday.</p>
</blockquote>
</div>
</body>
"#;
let md = convert(html);
assert!(md.contains("FYI, see below."));
assert!(md.contains("Forwarded message"));
assert!(md.contains("deadline has been moved"));
}
#[test]
fn protonmail_reply() {
let html = r#"
<body>
<div>Will do, thanks.</div>
<blockquote class="protonmail_quote" type="cite">
<div>Please send me the files by EOD.</div>
</blockquote>
</body>
"#;
let md = convert(html);
assert!(md.contains("Will do, thanks."));
assert!(md.contains("> "));
assert!(md.contains("send me the files"));
}
#[test]
fn attribution_preserved_above_quote() {
let html = r#"
<body>
<p>Agreed.</p>
<div class="gmail_quote">
On Wed, Jan 7, 2026 at 10:00 AM Carol wrote:
<blockquote>
<p>Let's go with option B.</p>
</blockquote>
</div>
</body>
"#;
let md = convert(html);
assert!(md.contains("Agreed."));
assert!(md.contains("Carol wrote:"));
assert!(md.contains("option B"));
}