620 lines · 33.0 KB · Rust
RawDownload
use super::*; // Simple transformer to make testing easier.fn test_parse_html(source: &str) -> Vec<FormattedTextLine> {    parse_html(source).unwrap().lines.into()} #[test]fn test_parse_plain_text() {    assert_eq!(        test_parse_html("<meta charset='utf-8'>Some"),        vec![FormattedTextLine::Line(vec![            FormattedTextFragment::plain_text("Some"),        ])]    );     assert_eq!(        test_parse_html("<meta charset='utf-8'><p>Some</p><p>tests</p>"),        vec![            FormattedTextLine::Line(vec![FormattedTextFragment::plain_text("Some"),]),            FormattedTextLine::Line(vec![FormattedTextFragment::plain_text("tests"),])        ]    );     // Example from GDocs.    assert_eq!(        test_parse_html(            "<meta charset='utf-8'><meta charset=\"utf-8\"><b style=\"font-weight:normal;\" id=\"docs-internal-guid-27b0e865-7fff-b40d-5b19-8e9e7ccf7c8c\">\        <p dir=\"ltr\" style=\"line-height:1.38;margin-top:0pt;margin-bottom:0pt;\"><span style=\"font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;\        font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;\">Some plain</span></p><p dir=\"ltr\" style=\"line-height:1.38;margin-top:0pt;margin-bottom:0pt;\">\        <span style=\"font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;\        white-space:pre-wrap;\">text</span></p></b>"        ),        vec![            FormattedTextLine::Line(vec![FormattedTextFragment::plain_text("Some plain"),]),            FormattedTextLine::Line(vec![FormattedTextFragment::plain_text("text"),])        ]    );} #[test]fn test_parse_text_styles() {    assert_eq!(        test_parse_html(            "<meta charset='utf-8'>So<span style=\"font-weight:600\" data-token-index=\"1\" class=\"notion-enable-hover\">me</span>"        ),        vec![FormattedTextLine::Line(vec![            FormattedTextFragment::plain_text("So"),            // font-weight:600 preserves as Semibold rather than collapsing to Bold.            FormattedTextFragment::weighted("me", Some(CustomWeight::Semibold)),        ])]    );     assert_eq!(        test_parse_html(            "<meta charset='utf-8'><p>So<strong>me</strong></p><p><em><strong>tes</strong></em>ts</p>"        ),        vec![            FormattedTextLine::Line(vec![                FormattedTextFragment::plain_text("So"),                FormattedTextFragment::bold("me")            ]),            FormattedTextLine::Line(vec![                FormattedTextFragment::bold_italic("tes"),                FormattedTextFragment::plain_text("ts")            ])        ]    );     assert_eq!(        test_parse_html(            "<meta charset='utf-8'><meta charset=\"utf-8\"><b style=\"font-weight:normal;\" id=\"docs-internal-guid-a96b449f-7fff-d755-78a4-efcebc867940\">\        <p dir=\"ltr\" style=\"line-height:1.38;margin-top:0pt;margin-bottom:0pt;\"><span style=\"font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;\        font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;\">So</span>\        <span style=\"font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:italic;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;\">\        me</span><span style=\"font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:700;font-style:italic;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;\">\         p</span><span style=\"font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:700;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;\">\         la</span><span style=\"font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;\">in\         </span></p><span style=\"font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;\">t\         </span><span style=\"font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:italic;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;white-space:pre-wrap;\">ext</span></b>"        ),        vec![            FormattedTextLine::Line(vec![                FormattedTextFragment::plain_text("So"),                FormattedTextFragment::italic("me"),                FormattedTextFragment::bold_italic("p"),                FormattedTextFragment::bold("la"),                FormattedTextFragment::plain_text("in")            ]),            FormattedTextLine::Line(vec![                FormattedTextFragment::plain_text("t"),                FormattedTextFragment::italic("ext")            ])        ]    );     assert_eq!(        test_parse_html(            "<meta charset='utf-8'><meta charset=\"utf-8\"><b style=\"font-weight:normal;\" id=\"docs-internal-guid-27b0e865-7fff-b40d-5b19-8e9e7ccf7c8c\">\        <p dir=\"ltr\" style=\"line-height:1.38;margin-top:0pt;margin-bottom:0pt;\"><span style=\"font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;\        font-variant:normal;text-decoration:underline;vertical-align:baseline;white-space:pre;white-space:pre-wrap;\">This is underlined</span></p><p dir=\"ltr\" style=\"line-height:1.38;margin-top:0pt;margin-bottom:0pt;\">\        <span style=\"font-size:11pt;font-family:Arial;color:#000000;background-color:transparent;font-weight:400;font-style:normal;font-variant:normal;text-decoration:none;vertical-align:baseline;white-space:pre;\        white-space:pre-wrap;\"> text</span></p></b>"        ),        vec![            FormattedTextLine::Line(vec![FormattedTextFragment::underline("This is underlined")]),            FormattedTextLine::Line(vec![FormattedTextFragment::plain_text(" text")]),        ]    );     assert_eq!(        test_parse_html(            "<meta charset='utf-8'>abc <a href=\"https://google.com/\" style=\"cursor:pointer;color:inherit;word-wrap:break-word;text-decoration:inherit\" class=\"notion-link-token notion-focusable-token notion-enable-hover\" rel=\"noopener noreferrer\"\            data-token-index=\"1\" tabindex=\"0\"><span style=\"border-bottom:0.05em solid;border-color:rgba(55,53,47,0.4);opacity:0.7\" class=\"link-annotation-unknown-block-id--760432549\">NewLinadafekene</span></a> ghi def"        ),        vec![FormattedTextLine::Line(vec![            FormattedTextFragment::plain_text("abc "),            FormattedTextFragment::hyperlink("NewLinadafekene", "https://google.com/"),            FormattedTextFragment::plain_text(" ghi def")        ]),]    );     assert_eq!(        test_parse_html(            r#"<meta charset='utf-8'>the <span style="font-family:&quot;SFMono-Regular&quot;, Menlo, Consolas, &quot;PT Mono&quot;, &quot;Liberation Mono&quot;, Courier, monospace;line-height:normal;background:rgba(135,131,120,.15);color:#EB5757;border-radius:3px;font-size:85%;padding:0.2em 0.4em" data-token-index="1" spellcheck="false" class="notion-enable-hover">&lt;ul&gt;</span> and <span style="font-family:&quot;SFMono-Regular&quot;, Menlo, Consolas, &quot;PT Mono&quot;, &quot;Liberation Mono&quot;, Courier, monospace;line-height:normal;background:rgba(135,131,120,.15);color:#EB5757;border-radius:3px;font-size:85%;padding:0.2em 0.4em" data-token-index="3" spellcheck="false" class="notion-enable-hover">&lt;li&gt;</span>"#        ),        vec![FormattedTextLine::Line(vec![            FormattedTextFragment::plain_text("the "),            FormattedTextFragment::inline_code("<ul>"),            FormattedTextFragment::plain_text(" and "),            FormattedTextFragment::inline_code("<li>"),        ]),]    );     assert_eq!(        test_parse_html(            r#"<meta charset='utf-8'><span style="text-decoration:line-through" data-token-index="0" class="notion-enable-hover">Strike</span><span style="font-weight:600" data-token-index="1" class="notion-enable-hover">Bold</span>"#        ),        vec![FormattedTextLine::Line(vec![            FormattedTextFragment::strikethrough("Strike"),            // font-weight:600 preserves as Semibold rather than collapsing to Bold.            FormattedTextFragment::weighted("Bold", Some(CustomWeight::Semibold)),        ]),]    )} #[test]fn test_parse_numeric_font_weights_preserve_custom_weight() {    // Regression test for #14429: numeric CSS font weights on pasted HTML must round-trip to the    // matching `CustomWeight`, not collapse to bold-or-plain. Prior behavior mapped every    // weight > 400 to Bold and everything else to no weight, dropping Light/Medium/Black/etc.    let cases = [        ("100", Some(CustomWeight::Thin)),        ("200", Some(CustomWeight::ExtraLight)),        ("300", Some(CustomWeight::Light)),        ("400", None),        ("500", Some(CustomWeight::Medium)),        ("600", Some(CustomWeight::Semibold)),        ("700", Some(CustomWeight::Bold)),        ("800", Some(CustomWeight::ExtraBold)),        ("900", Some(CustomWeight::Black)),    ];     for (css_weight, expected) in cases {        let html =            format!("<meta charset='utf-8'>So<span style=\"font-weight:{css_weight}\">me</span>");        assert_eq!(            test_parse_html(&html),            vec![FormattedTextLine::Line(vec![                FormattedTextFragment::plain_text("So"),                FormattedTextFragment::weighted("me", expected),            ])],            "font-weight:{css_weight} should map to {expected:?}"        );    }} #[test]fn test_parse_keyword_font_weights() {    // `bold`/`bolder` keywords stay Bold; `normal`/`lighter` carry no custom weight.    assert_eq!(        test_parse_html("<meta charset='utf-8'>So<span style=\"font-weight:bold\">me</span>"),        vec![FormattedTextLine::Line(vec![            FormattedTextFragment::plain_text("So"),            FormattedTextFragment::bold("me"),        ])]    );     assert_eq!(        test_parse_html("<meta charset='utf-8'>So<span style=\"font-weight:normal\">me</span>"),        vec![FormattedTextLine::Line(vec![            FormattedTextFragment::plain_text("So"),            FormattedTextFragment::plain_text("me"),        ])]    );} #[test]fn test_block() {    assert_eq!(        test_parse_html(            "<meta charset='utf-8'><pre><code class=\"language-jsx\">git checkout -b branch</code></pre>"        ),        vec![FormattedTextLine::CodeBlock(CodeBlockText {            lang: "jsx".to_string(),            code: "git checkout -b branch".to_string()        })]    );     assert_eq!(        test_parse_html(            "<meta charset='utf-8'><p>tests</p><pre><code class=\"language-jsx\">git checkout -b branch</code></pre><p>More</p>"        ),        vec![            FormattedTextLine::Line(vec![FormattedTextFragment::plain_text("tests"),]),            FormattedTextLine::CodeBlock(CodeBlockText {                lang: "jsx".to_string(),                code: "git checkout -b branch".to_string()            }),            FormattedTextLine::Line(vec![FormattedTextFragment::plain_text("More"),])        ]    );} #[test]fn test_transform_non_breaking_spaces() {    let expected_text = vec![FormattedTextLine::Line(vec![        FormattedTextFragment::plain_text(            "Open the Docker desktop app. This is necessary to create the symbolic links that will make the",        ),        FormattedTextFragment::plain_text(" "),        FormattedTextFragment::inline_code("docker"),        FormattedTextFragment::plain_text(" "),        FormattedTextFragment::plain_text("CLI available."),    ])];     let chromium_html = r#"Open the Docker desktop app. This is necessary to create the symbolic links that will make the<span> </span><code>docker</code><span> </span>CLI available."#;    assert_eq!(test_parse_html(chromium_html), expected_text);     let safari_html = r#"Open the Docker desktop app. This is necessary to create the symbolic links that will make the<span class="Apple-converted-space"> </span><code>docker</code><span class="Apple-converted-space"> </span>CLI available."#;    assert_eq!(test_parse_html(safari_html), expected_text);} // TODO: remove/update this test when we eventually support these HTML element types!#[test]fn test_unsupported_html_types() {    assert_eq!(        test_parse_html(            "<meta charset='utf-8'><p>Test test</p><blockquote><p>Block quotes with <strong>bold text</strong></p></blockquote>"        ),        vec![            FormattedTextLine::Line(vec![FormattedTextFragment::plain_text("Test test"),]),            FormattedTextLine::Line(vec![                FormattedTextFragment::plain_text("Block quotes with "),                FormattedTextFragment::bold("bold text")            ])        ]    );     assert_eq!(        test_parse_html(            "<meta charset='utf-8'><table><thead><tr><th>Text 1</th><th>Text 2</th></tr></thead><tbody><tr><td>Test</td><td>Test</td></tr></tbody></table>"        ),        vec![            FormattedTextLine::Line(vec![                FormattedTextFragment::plain_text("Text 1"),                FormattedTextFragment::plain_text("Text 2")            ]),            FormattedTextLine::Line(vec![                FormattedTextFragment::plain_text("Test"),                FormattedTextFragment::plain_text("Test")            ])        ]    );} #[test]fn test_sub_lists() {    assert_eq!(        test_parse_html(            "<meta charset='utf-8'><ul><li>def<ul><li>abc<ul><li>sub-list</li></ul></li><li>abc</li></ul></li></ul>"        ),        vec![            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 0,                text: vec![FormattedTextFragment::plain_text("def")]            }),            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 1,                text: vec![FormattedTextFragment::plain_text("abc")]            }),            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 2,                text: vec![FormattedTextFragment::plain_text("sub-list")]            }),            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 1,                text: vec![FormattedTextFragment::plain_text("abc")]            })        ]    );     assert_eq!(        test_parse_html(            "<meta charset='utf-8'><ul><li>d<strong>ef</strong><ul><li><em>abc</em><ul><li>sub-list</li></ul></li><li>abc</li></ul></li></ul><p>normal text</p>"        ),        vec![            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 0,                text: vec![                    FormattedTextFragment::plain_text("d"),                    FormattedTextFragment::bold("ef")                ]            }),            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 1,                text: vec![FormattedTextFragment::italic("abc")]            }),            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 2,                text: vec![FormattedTextFragment::plain_text("sub-list")]            }),            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 1,                text: vec![FormattedTextFragment::plain_text("abc")]            }),            FormattedTextLine::Line(vec![FormattedTextFragment::plain_text("normal text")])        ]    );     assert_eq!(        test_parse_html(            "<meta charset='utf-8'><ul><li>abc<ul><li>def</li></ul></li></ul><ol><li>abc<ol><li>def</li></ol></li></ol>"        ),        vec![            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 0,                text: vec![FormattedTextFragment::plain_text("abc")]            }),            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 1,                text: vec![FormattedTextFragment::plain_text("def")]            }),            FormattedTextLine::OrderedList(OrderedFormattedIndentTextInline {                number: None,                indented_text: FormattedIndentTextInline {                    indent_level: 0,                    text: vec![FormattedTextFragment::plain_text("abc".to_string())]                }            }),            FormattedTextLine::OrderedList(OrderedFormattedIndentTextInline {                number: None,                indented_text: FormattedIndentTextInline {                    indent_level: 1,                    text: vec![FormattedTextFragment::plain_text("def".to_string())]                }            }),        ]    );     assert_eq!(        test_parse_html("<meta charset='utf-8'><ul><li>abc<ol><li>def</li></ol></li></ul>"),        vec![            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 0,                text: vec![FormattedTextFragment::plain_text("abc".to_string())]            }),            FormattedTextLine::OrderedList(OrderedFormattedIndentTextInline {                number: None,                indented_text: FormattedIndentTextInline {                    indent_level: 1,                    text: vec![FormattedTextFragment::plain_text("def")]                }            }),        ]    );} #[test]fn test_formatted_sub_lists() {    assert_eq!(        test_parse_html(            "<meta charset='utf-8'><ul>\n<li>By default, the client goes to staging\n<ul>\n<li>To target localhost, build the client with</li>\n</ul>\n</li>\n</ul>\n"        ),        vec![            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 0,                text: vec![FormattedTextFragment::plain_text(                    "By default, the client goes to staging"                )]            }),            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 1,                text: vec![FormattedTextFragment::plain_text(                    "To target localhost, build the client with"                )]            }),        ]    );} #[test]fn test_ordered_lists() {    let html = r#"<meta charset='utf-8'><ol start="3">  <li>First</li>  <li>Second<ol>      <li>A</li>      <li>B</li>    </ol>  </li>  <li>Third</li>  <li>Fourth<ol start="invalid">        <li>G</li>        <li>H</li>    </ol>  </li></ol>    "#;     assert_eq!(        test_parse_html(html),        vec![            FormattedTextLine::OrderedList(OrderedFormattedIndentTextInline {                number: Some(3),                indented_text: FormattedIndentTextInline {                    indent_level: 0,                    text: vec![FormattedTextFragment::plain_text("First")]                }            }),            FormattedTextLine::OrderedList(OrderedFormattedIndentTextInline {                number: None,                indented_text: FormattedIndentTextInline {                    indent_level: 0,                    text: vec![FormattedTextFragment::plain_text("Second")]                }            }),            FormattedTextLine::OrderedList(OrderedFormattedIndentTextInline {                number: None,                indented_text: FormattedIndentTextInline {                    indent_level: 1,                    text: vec![FormattedTextFragment::plain_text("A")]                }            }),            FormattedTextLine::OrderedList(OrderedFormattedIndentTextInline {                number: None,                indented_text: FormattedIndentTextInline {                    indent_level: 1,                    text: vec![FormattedTextFragment::plain_text("B")]                }            }),            FormattedTextLine::OrderedList(OrderedFormattedIndentTextInline {                number: None,                indented_text: FormattedIndentTextInline {                    indent_level: 0,                    text: vec![FormattedTextFragment::plain_text("Third")]                }            }),            FormattedTextLine::OrderedList(OrderedFormattedIndentTextInline {                number: None,                indented_text: FormattedIndentTextInline {                    indent_level: 0,                    text: vec![FormattedTextFragment::plain_text("Fourth")]                }            }),            FormattedTextLine::OrderedList(OrderedFormattedIndentTextInline {                number: None,                indented_text: FormattedIndentTextInline {                    indent_level: 1,                    text: vec![FormattedTextFragment::plain_text("G")]                }            }),            FormattedTextLine::OrderedList(OrderedFormattedIndentTextInline {                number: None,                indented_text: FormattedIndentTextInline {                    indent_level: 1,                    text: vec![FormattedTextFragment::plain_text("H")]                }            }),        ]    );} #[test]fn test_horizontal_rules() {    assert_eq!(        test_parse_html("<meta charset='utf-8'><ul>\n<li>bcf</li>\n</ul>\n<hr>\n<p>abc</p>"),        vec![            FormattedTextLine::UnorderedList(FormattedIndentTextInline {                indent_level: 0,                text: vec![FormattedTextFragment::plain_text("bcf")]            }),            FormattedTextLine::HorizontalRule,            FormattedTextLine::Line(vec![FormattedTextFragment::plain_text("abc")])        ]    );} #[test]

Lines 1–500 of 620.