microformats 0.18.0

A union library of the Microformats types and associated parser.
Documentation
use super::*;
use crate::parse::{
    element::test::{from_html_str, grab_element_from_document},
    ElementRef,
};
use microformats_types::temporal;
use tracing_test::traced_test;
use tracing_unwrap::OptionExt;

#[traced_test]
#[test]
fn linked() -> Result<(), crate::Error> {
    let elem = grab_element_from_document(
        &from_html_str(r#"<a href='/foo' class='u-url'>a place</a>"#),
        "a",
    )
    .unwrap_or_log();

    let parser = explicit::PropertyParser::new(
        Arc::new(ElementRef {
            index: 0,
            node: Node { elem },
        }),
        DeclKind::Linked("url".to_string()),
        "http://example.com".parse()?,
        None,
    );

    assert_eq!(
        parser.expand()?,
        Some((
            "url".to_string(),
            PropertyValue::Url("http://example.com/foo".parse().unwrap())
        )),
        "expanded a direct linked property"
    );
    Ok(())
}

#[yare::parameterized(
    direct = {
        r#"<time class="dt-timing" datetime="2014-01-01"></time>"#,
        "time", temporal::Value::Timestamp(temporal::Stamp::parse("2014-01-01")?)
    },
    abbr_title = {
        r#"<abbr class="dt-timing" title="2013-06-29">June 29</abbr>"#,
        "abbr", temporal::Value::Timestamp(temporal::Stamp::parse("2013-06-29")?)
    }
)]
fn temporal_value(html: &str, tag: &str, value: temporal::Value) -> Result<(), crate::Error> {
    let elem = grab_element_from_document(&from_html_str(html), tag).unwrap_or_log();

    let parser = explicit::PropertyParser::new(
        Arc::new(ElementRef {
            index: 0,
            node: Node { elem },
        }),
        DeclKind::Temporal("timing".to_string()),
        "http://example.com".parse()?,
        None,
    );

    assert_eq!(
        parser.expand()?,
        Some(("timing".to_string(), PropertyValue::Temporal(value))),
        "expanded a direct temporal property"
    );

    Ok(())
}

#[traced_test]
#[test]
fn html() -> Result<(), crate::Error> {
    let elem = grab_element_from_document(
        &from_html_str(r#"<span class="e-content">The name is in <strong>bold</strong>.</span>"#),
        "span",
    )
    .unwrap_or_log();

    let parser = explicit::PropertyParser::new(
        Arc::new(ElementRef {
            index: 0,
            node: Node { elem },
        }),
        DeclKind::Hypertext("content".to_string()),
        "http://example.com".parse()?,
        None,
    );

    assert_eq!(
        parser.expand()?,
        Some((
            "content".to_string(),
            PropertyValue::Fragment(Fragment {
                html: "The name is in <strong>bold</strong>.".to_string(),
                value: "The name is in bold.".to_string(),
                ..Default::default()
            })
        )),
        "expanded a direct hypertext property"
    );

    Ok(())
}

#[yare::parameterized(
    bare = { "span", r#"<span class="p-name">The name.</span>"# },
    nested = { "div", r#"<div class="p-name">
        <span class="p-nickname">The</span> <span class="p-place">name.</span>
    </div>"# },
    tag_abbr = { "abbr", r#"<abbr class="p-name value" title="The name.">Wow.</abbr>"# },
    tag_link = { "link", r#"<link class="p-name value" href='/place' title="The name." />"# },
)]
fn plain(tag_name: &str, html: &str) -> Result<(), crate::Error> {
    let elem = grab_element_from_document(&from_html_str(html), tag_name).unwrap_or_log();

    let parser = PropertyParser::new(
        Arc::new(ElementRef {
            index: 0,
            node: Node { elem },
        }),
        DeclKind::Plain("name".to_string()),
        "http://example.com".parse()?,
        None,
    );

    assert_eq!(
        parser.expand()?,
        Some((
            "name".to_string(),
            PropertyValue::Plain("The name.".to_string())
        ))
    );

    Ok(())
}

#[test]
fn plain_allow_empty() -> Result<(), crate::Error> {
    let elem = grab_element_from_document(
        &from_html_str(r#"<br class="p-honorific-suffix" />BSc<br />"#),
        "br",
    )
    .unwrap_or_log();

    let parser = PropertyParser::new(
        Arc::new(ElementRef {
            index: 0,
            node: Node { elem },
        }),
        DeclKind::Plain("honorific-suffix".to_string()),
        "http://example.com".parse()?,
        None,
    );

    assert_eq!(
        parser.expand()?,
        Some((
            "honorific-suffix".to_string(),
            PropertyValue::Plain("".to_string())
        ))
    );

    Ok(())
}

#[traced_test]
#[test]
fn h_review_description_to_content_mapping() -> Result<(), crate::Error> {
    // Test that e-description in h-review context maps to "content" property
    // This verifies the backward compatibility fix for issue #27
    let html =
        r#"<div class="h-review"><div class="e-description">Test description content</div></div>"#;

    let root_url: url::Url = "http://example.com/".parse().unwrap();
    let mut parser = crate::parse::Parser::from_html(html.to_string())?;
    let document = parser.into_document(Some(root_url))?;

    // Extract the h-review item
    let items = &document.items;
    assert_eq!(items.len(), 1, "Should parse exactly one h-review item");

    let h_review = &items[0];
    let properties = &h_review.properties;

    // Check that e-description in h-review maps to "content" property
    let has_content = properties.contains_key("content");
    let has_description = properties.contains_key("description");

    assert!(
        has_content,
        "e-description in h-review should map to 'content' property"
    );
    assert!(
        !has_description,
        "Should not have 'description' property after normalization"
    );

    Ok(())
}

#[traced_test]
#[test]
fn h_review_e_content_mapping() -> Result<(), crate::Error> {
    // Test that e-content in h-review context correctly maps to "content" property
    // This verifies current spec compliance for issue #27
    let html = r#"<div class="h-review"><div class="e-content">Test new style content</div></div>"#;

    let root_url: url::Url = "http://example.com/".parse().unwrap();
    let mut parser = crate::parse::Parser::from_html(html.to_string())?;
    let document = parser.into_document(Some(root_url))?;

    // Extract the h-review item
    let items = &document.items;
    assert_eq!(items.len(), 1, "Should parse exactly one h-review item");

    let h_review = &items[0];
    let properties = &h_review.properties;

    // Check that e-content correctly maps to "content" property
    let has_content = properties.contains_key("content");

    assert!(
        has_content,
        "e-content in h-review should map to 'content' property"
    );

    Ok(())
}