max / pter
6 files changed,
+93 insertions,
-44 deletions
| @@ -94,5 +94,11 @@ fn bench_large(c: &mut Criterion) { | |||
| 94 | 94 | }); | |
| 95 | 95 | } | |
| 96 | 96 | ||
| 97 | - | criterion_group!(benches, bench_simple, bench_newsletter, bench_reply_chain, bench_large); | |
| 97 | + | criterion_group!( | |
| 98 | + | benches, | |
| 99 | + | bench_simple, | |
| 100 | + | bench_newsletter, | |
| 101 | + | bench_reply_chain, | |
| 102 | + | bench_large | |
| 103 | + | ); | |
| 98 | 104 | criterion_main!(benches); |
| @@ -537,10 +537,7 @@ mod tests { | |||
| 537 | 537 | ||
| 538 | 538 | #[test] | |
| 539 | 539 | fn unordered_list() { | |
| 540 | - | assert_eq!( | |
| 541 | - | convert("<ul><li>one</li><li>two</li></ul>"), | |
| 542 | - | "- one\n- two" | |
| 543 | - | ); | |
| 540 | + | assert_eq!(convert("<ul><li>one</li><li>two</li></ul>"), "- one\n- two"); | |
| 544 | 541 | } | |
| 545 | 542 | ||
| 546 | 543 | #[test] | |
| @@ -588,9 +585,7 @@ mod tests { | |||
| 588 | 585 | // After a nested <ul> closes, `list_depth -= 1` must execute to return | |
| 589 | 586 | // to outer scope. If mutated to `+= 1` or `/= 1`, list_depth stays | |
| 590 | 587 | // elevated and the SECOND top-level list ends up incorrectly indented. | |
| 591 | - | let md = convert( | |
| 592 | - | "<ul><li>A<ul><li>B</li></ul></li></ul><ul><li>C</li></ul>", | |
| 593 | - | ); | |
| 588 | + | let md = convert("<ul><li>A<ul><li>B</li></ul></li></ul><ul><li>C</li></ul>"); | |
| 594 | 589 | // The second list's "C" item must appear at column 0, not indented. | |
| 595 | 590 | // We check the exact substring "\n- C" (newline then no leading whitespace). | |
| 596 | 591 | assert!( | |
| @@ -608,16 +603,23 @@ mod tests { | |||
| 608 | 603 | fn ordered_list_decrements_depth_after_nesting() { | |
| 609 | 604 | // Same shape but with <ol> — exercises the L218 `-= 1` mutation in the | |
| 610 | 605 | // OrderedList block, distinct from UnorderedList's L208. | |
| 611 | - | let md = convert( | |
| 612 | - | "<ol><li>A<ol><li>B</li></ol></li></ol><ol><li>C</li></ol>", | |
| 606 | + | let md = convert("<ol><li>A<ol><li>B</li></ol></li></ol><ol><li>C</li></ol>"); | |
| 607 | + | assert!( | |
| 608 | + | md.contains("\n1. C"), | |
| 609 | + | "second ol must restart at depth 1: {md:?}" | |
| 610 | + | ); | |
| 611 | + | assert!( | |
| 612 | + | !md.contains("\n 1. C"), | |
| 613 | + | "second ol indented incorrectly: {md:?}" | |
| 613 | 614 | ); | |
| 614 | - | assert!(md.contains("\n1. C"), "second ol must restart at depth 1: {md:?}"); | |
| 615 | - | assert!(!md.contains("\n 1. C"), "second ol indented incorrectly: {md:?}"); | |
| 616 | 615 | } | |
| 617 | 616 | ||
| 618 | 617 | #[test] | |
| 619 | 618 | fn blockquote() { | |
| 620 | - | assert_eq!(convert("<blockquote>quoted text</blockquote>"), "> quoted text"); | |
| 619 | + | assert_eq!( | |
| 620 | + | convert("<blockquote>quoted text</blockquote>"), | |
| 621 | + | "> quoted text" | |
| 622 | + | ); | |
| 621 | 623 | } | |
| 622 | 624 | ||
| 623 | 625 | #[test] | |
| @@ -639,7 +641,10 @@ mod tests { | |||
| 639 | 641 | ||
| 640 | 642 | #[test] | |
| 641 | 643 | fn horizontal_rule() { | |
| 642 | - | assert_eq!(convert("<p>above</p><hr><p>below</p>"), "above\n\n---\n\nbelow"); | |
| 644 | + | assert_eq!( | |
| 645 | + | convert("<p>above</p><hr><p>below</p>"), | |
| 646 | + | "above\n\n---\n\nbelow" | |
| 647 | + | ); | |
| 643 | 648 | } | |
| 644 | 649 | ||
| 645 | 650 | #[test] |
| @@ -41,9 +41,7 @@ pub enum InlineKind { | |||
| 41 | 41 | pub fn classify(el: &Element) -> ElementAction { | |
| 42 | 42 | match el.name() { | |
| 43 | 43 | // Skip entirely | |
| 44 | - | "script" | "style" | "head" | "meta" | "link" | "title" | "noscript" => { | |
| 45 | - | ElementAction::Skip | |
| 46 | - | } | |
| 44 | + | "script" | "style" | "head" | "meta" | "link" | "title" | "noscript" => ElementAction::Skip, | |
| 47 | 45 | ||
| 48 | 46 | // Block elements | |
| 49 | 47 | "p" => ElementAction::Block(BlockKind::Paragraph), | |
| @@ -65,9 +63,7 @@ pub fn classify(el: &Element) -> ElementAction { | |||
| 65 | 63 | ElementAction::Transparent | |
| 66 | 64 | } | |
| 67 | 65 | "div" | "section" | "article" | "main" | "header" | "footer" | "nav" | "aside" | |
| 68 | - | | "figure" | "figcaption" | "details" | "summary" => { | |
| 69 | - | ElementAction::Block(BlockKind::Div) | |
| 70 | - | } | |
| 66 | + | | "figure" | "figcaption" | "details" | "summary" => ElementAction::Block(BlockKind::Div), | |
| 71 | 67 | ||
| 72 | 68 | // Inline elements | |
| 73 | 69 | "strong" | "b" => ElementAction::Inline(InlineKind::Bold), | |
| @@ -189,22 +185,34 @@ mod tests { | |||
| 189 | 185 | ||
| 190 | 186 | #[test] | |
| 191 | 187 | fn classify_h1_is_heading_1() { | |
| 192 | - | assert!(matches!(classify_tag("h1"), ElementAction::Block(BlockKind::Heading(1)))); | |
| 188 | + | assert!(matches!( | |
| 189 | + | classify_tag("h1"), | |
| 190 | + | ElementAction::Block(BlockKind::Heading(1)) | |
| 191 | + | )); | |
| 193 | 192 | } | |
| 194 | 193 | ||
| 195 | 194 | #[test] | |
| 196 | 195 | fn classify_h4_is_heading_4() { | |
| 197 | - | assert!(matches!(classify_tag("h4"), ElementAction::Block(BlockKind::Heading(4)))); | |
| 196 | + | assert!(matches!( | |
| 197 | + | classify_tag("h4"), | |
| 198 | + | ElementAction::Block(BlockKind::Heading(4)) | |
| 199 | + | )); | |
| 198 | 200 | } | |
| 199 | 201 | ||
| 200 | 202 | #[test] | |
| 201 | 203 | fn classify_h5_is_heading_5() { | |
| 202 | - | assert!(matches!(classify_tag("h5"), ElementAction::Block(BlockKind::Heading(5)))); | |
| 204 | + | assert!(matches!( | |
| 205 | + | classify_tag("h5"), | |
| 206 | + | ElementAction::Block(BlockKind::Heading(5)) | |
| 207 | + | )); | |
| 203 | 208 | } | |
| 204 | 209 | ||
| 205 | 210 | #[test] | |
| 206 | 211 | fn classify_h6_is_heading_6() { | |
| 207 | - | assert!(matches!(classify_tag("h6"), ElementAction::Block(BlockKind::Heading(6)))); | |
| 212 | + | assert!(matches!( | |
| 213 | + | classify_tag("h6"), | |
| 214 | + | ElementAction::Block(BlockKind::Heading(6)) | |
| 215 | + | )); | |
| 208 | 216 | } | |
| 209 | 217 | ||
| 210 | 218 | #[test] | |
| @@ -214,12 +222,18 @@ mod tests { | |||
| 214 | 222 | ||
| 215 | 223 | #[test] | |
| 216 | 224 | fn classify_table_is_block_table() { | |
| 217 | - | assert!(matches!(classify_tag("table"), ElementAction::Block(BlockKind::Table))); | |
| 225 | + | assert!(matches!( | |
| 226 | + | classify_tag("table"), | |
| 227 | + | ElementAction::Block(BlockKind::Table) | |
| 228 | + | )); | |
| 218 | 229 | } | |
| 219 | 230 | ||
| 220 | 231 | #[test] | |
| 221 | 232 | fn classify_strong_is_inline_bold() { | |
| 222 | - | assert!(matches!(classify_tag("strong"), ElementAction::Inline(InlineKind::Bold))); | |
| 233 | + | assert!(matches!( | |
| 234 | + | classify_tag("strong"), | |
| 235 | + | ElementAction::Inline(InlineKind::Bold) | |
| 236 | + | )); | |
| 223 | 237 | } | |
| 224 | 238 | ||
| 225 | 239 | // -- is_tracking_pixel: each || arm needs its own positive test -- | |
| @@ -396,4 +410,3 @@ mod tests { | |||
| 396 | 410 | assert!(!div_is_hidden("")); | |
| 397 | 411 | } | |
| 398 | 412 | } | |
| 399 | - |
| @@ -1,5 +1,5 @@ | |||
| 1 | - | use scraper::node::Node; | |
| 2 | 1 | use scraper::ElementRef; | |
| 2 | + | use scraper::node::Node; | |
| 3 | 3 | ||
| 4 | 4 | /// Check if an element marks the beginning of a quoted reply. | |
| 5 | 5 | /// | |
| @@ -118,7 +118,7 @@ fn is_reply_class(class: &str) -> bool { | |||
| 118 | 118 | | "tutanota_quote" | |
| 119 | 119 | | "moz-cite-prefix" // Thunderbird | |
| 120 | 120 | | "zmail_extra" // Zoho | |
| 121 | - | | "WordSection1" // Outlook (sometimes wraps replies) | |
| 121 | + | | "WordSection1" // Outlook (sometimes wraps replies) | |
| 122 | 122 | ) | |
| 123 | 123 | }) | |
| 124 | 124 | } | |
| @@ -227,12 +227,16 @@ mod tests { | |||
| 227 | 227 | ||
| 228 | 228 | #[test] | |
| 229 | 229 | fn attribution_on_wrote() { | |
| 230 | - | assert!(is_attribution_text("On Mon, Jan 5, 2026 at 3:00 PM Alice <alice@example.com> wrote:")); | |
| 230 | + | assert!(is_attribution_text( | |
| 231 | + | "On Mon, Jan 5, 2026 at 3:00 PM Alice <alice@example.com> wrote:" | |
| 232 | + | )); | |
| 231 | 233 | } | |
| 232 | 234 | ||
| 233 | 235 | #[test] | |
| 234 | 236 | fn attribution_forwarded() { | |
| 235 | - | assert!(is_attribution_text("---------- Forwarded message ----------")); | |
| 237 | + | assert!(is_attribution_text( | |
| 238 | + | "---------- Forwarded message ----------" | |
| 239 | + | )); | |
| 236 | 240 | } | |
| 237 | 241 | ||
| 238 | 242 | #[test] | |
| @@ -336,7 +340,9 @@ mod tests { | |||
| 336 | 340 | ||
| 337 | 341 | #[test] | |
| 338 | 342 | fn attribution_french_le_with_colon_space() { | |
| 339 | - | assert!(is_attribution_text("Le lundi 5 janvier 2026, Alice a écrit :")); | |
| 343 | + | assert!(is_attribution_text( | |
| 344 | + | "Le lundi 5 janvier 2026, Alice a écrit :" | |
| 345 | + | )); | |
| 340 | 346 | } | |
| 341 | 347 | ||
| 342 | 348 | #[test] |
| @@ -227,8 +227,7 @@ mod tests { | |||
| 227 | 227 | ||
| 228 | 228 | #[test] | |
| 229 | 229 | fn role_grid_is_data() { | |
| 230 | - | let doc = | |
| 231 | - | parse_table(r#"<table role="grid"><tr><td>Alice</td><td>30</td></tr></table>"#); | |
| 230 | + | let doc = parse_table(r#"<table role="grid"><tr><td>Alice</td><td>30</td></tr></table>"#); | |
| 232 | 231 | assert!(is_data_table(select_table(&doc))); | |
| 233 | 232 | } | |
| 234 | 233 | ||
| @@ -312,8 +311,7 @@ mod tests { | |||
| 312 | 311 | fn role_table_is_data() { | |
| 313 | 312 | // role="table" → data. Catches L22 == "grid" mutating to != (which would | |
| 314 | 313 | // make grid not match) AND covers the parallel `|| role == "table"` arm. | |
| 315 | - | let doc = | |
| 316 | - | parse_table(r#"<table role="table"><tr><td>a</td></tr></table>"#); | |
| 314 | + | let doc = parse_table(r#"<table role="table"><tr><td>a</td></tr></table>"#); | |
| 317 | 315 | assert!(is_data_table(select_table(&doc))); | |
| 318 | 316 | } | |
| 319 | 317 | ||
| @@ -321,8 +319,7 @@ mod tests { | |||
| 321 | 319 | fn role_unknown_falls_through_to_structural() { | |
| 322 | 320 | // Unknown role → no early decision; structural rules apply. | |
| 323 | 321 | // Single-cell single-row layout table → not data. | |
| 324 | - | let doc = | |
| 325 | - | parse_table(r#"<table role="banner"><tr><td>only one cell</td></tr></table>"#); | |
| 322 | + | let doc = parse_table(r#"<table role="banner"><tr><td>only one cell</td></tr></table>"#); | |
| 326 | 323 | assert!(!is_data_table(select_table(&doc))); | |
| 327 | 324 | } | |
| 328 | 325 |
| @@ -3,9 +3,33 @@ use proptest::prelude::*; | |||
| 3 | 3 | // Strategy: generate arbitrary HTML-like strings | |
| 4 | 4 | fn html_fragment() -> impl Strategy<Value = String> { | |
| 5 | 5 | let tags = prop::sample::select(vec![ | |
| 6 | - | "p", "div", "span", "strong", "em", "a", "h1", "h2", "h3", | |
| 7 | - | "ul", "ol", "li", "blockquote", "pre", "code", "br", "hr", | |
| 8 | - | "img", "table", "tr", "td", "th", "b", "i", "del", "sup", "sub", | |
| 6 | + | "p", | |
| 7 | + | "div", | |
| 8 | + | "span", | |
| 9 | + | "strong", | |
| 10 | + | "em", | |
| 11 | + | "a", | |
| 12 | + | "h1", | |
| 13 | + | "h2", | |
| 14 | + | "h3", | |
| 15 | + | "ul", | |
| 16 | + | "ol", | |
| 17 | + | "li", | |
| 18 | + | "blockquote", | |
| 19 | + | "pre", | |
| 20 | + | "code", | |
| 21 | + | "br", | |
| 22 | + | "hr", | |
| 23 | + | "img", | |
| 24 | + | "table", | |
| 25 | + | "tr", | |
| 26 | + | "td", | |
| 27 | + | "th", | |
| 28 | + | "b", | |
| 29 | + | "i", | |
| 30 | + | "del", | |
| 31 | + | "sup", | |
| 32 | + | "sub", | |
| 9 | 33 | ]); | |
| 10 | 34 | ||
| 11 | 35 | let text = "[a-zA-Z0-9 .,!?]{0,100}"; | |
| @@ -15,9 +39,7 @@ fn html_fragment() -> impl Strategy<Value = String> { | |||
| 15 | 39 | // Plain text | |
| 16 | 40 | text.prop_map(|s| s), | |
| 17 | 41 | // Opening + closing tag with text | |
| 18 | - | (tags.clone(), text).prop_map(|(tag, content)| { | |
| 19 | - | format!("<{tag}>{content}</{tag}>") | |
| 20 | - | }), | |
| 42 | + | (tags.clone(), text).prop_map(|(tag, content)| { format!("<{tag}>{content}</{tag}>") }), | |
| 21 | 43 | // Self-closing tag | |
| 22 | 44 | tags.clone().prop_map(|tag| format!("<{tag}/>")), | |
| 23 | 45 | // Nested tags |