mod templates; pub use templates::{ErrorPage, MainPage, Template, POSTS_PER_PAGE, Entry, VCard, Feed}; mod onboarding; pub use onboarding::OnboardingPage; mod login; pub use login::LoginPage; #[cfg(test)] mod tests { use serde_json::json; use microformats::types::Document; enum PostType { Note, Article, ReplyTo(serde_json::Value), ReplyToLink(String), LikeOf(serde_json::Value), LikeOfLink(String) } fn gen_hcard(domain: &str) -> serde_json::Value { use faker_rand::en_us::names::FirstName; json!({ "type": ["h-card"], "properties": { "name": [rand::random::().to_string()], "photo": [format!("https://{domain}/media/me.png")], "uid": [format!("https://{domain}/")], "url": [format!("https://{domain}/")] } }) } fn gen_random_post(domain: &str, kind: PostType) -> serde_json::Value { use faker_rand::lorem::{Paragraph, Word, Sentence}; fn html(content: Paragraph) -> serde_json::Value { json!({ "html": format!("

{}

", content), "value": content.to_string() }) } let uid = format!( "https://{domain}/posts/{}-{}-{}", rand::random::(), rand::random::(), rand::random::() ); let dt = chrono::offset::Local::now() .to_rfc3339_opts(chrono::SecondsFormat::Secs, true); match kind { PostType::Note => { let content = rand::random::(); json!({ "type": ["h-entry"], "properties": { "content": [html(content)], "published": [dt], "uid": [&uid], "url": [&uid], "author": [gen_hcard(domain)] } }) } PostType::Article => { let content = rand::random::(); let name = rand::random::(); json!({ "type": ["h-entry"], "properties": { "content": [html(content)], "published": [dt], "uid": [&uid], "url": [&uid], "author": [gen_hcard(domain)], "name": [name.to_string()] } }) } PostType::ReplyTo(ctx) => { let content = rand::random::(); json!({ "type": ["h-entry"], "properties": { "content": [html(content)], "published": [dt], "uid": [&uid], "url": [&uid], "author": [gen_hcard(domain)], "in-reply-to": [{ "type": ["h-cite"], "properties": ctx["properties"] }] } }) }, PostType::ReplyToLink(link) => { let content = rand::random::(); json!({ "type": ["h-entry"], "properties": { "content": [html(content)], "published": [dt], "uid": [&uid], "url": [&uid], "author": [gen_hcard(domain)], "in-reply-to": [link] } }) }, PostType::LikeOf(ctx) => { json!({ "type": ["h-entry"], "properties": { "published": [dt], "author": [gen_hcard(domain)], "uid": [&uid], "url": [&uid], "like-of": [{ "type": ["h-cite"], "properties": ctx["properties"] }] } }) }, PostType::LikeOfLink(link) => { json!({ "type": ["h-entry"], "properties": { "published": [dt], "author": [gen_hcard(domain)], "uid": [&uid], "url": [&uid], "like-of": [link] } }) } } } #[test] #[ignore = "see https://gitlab.com/maxburon/microformats-parser/-/issues/7"] fn test_note() { use microformats::types::PropertyValue; use faker_rand::en_us::internet::Domain; test_logger::ensure_env_logger_initialized(); let mf2 = gen_random_post( &rand::random::().to_string(), PostType::Note ); let html = crate::templates::Entry { post: &mf2 }.to_string(); println!("\n```html\n{}\n```", &html); let url: microformats::types::Url = mf2["properties"]["uid"][0].as_str() .unwrap() .parse() .unwrap(); let parsed: Document = microformats::from_html(&html, url.clone()).unwrap(); let item = parsed.get_item_by_url(&url).unwrap(); println!("\n```json\n{}\n```", serde_json::to_string_pretty(&item).unwrap()); if let PropertyValue::Item(item) = item { let _item = item.borrow(); let props = _item.properties.borrow(); if let PropertyValue::Fragment(content) = props.get("content").and_then(|v| v.first()).unwrap() { assert_eq!(content.html, mf2["properties"]["content"][0]["html"].as_str().unwrap()); } else { unreachable!() } assert!(props.contains_key("published")); use microformats::types::temporal::Value as TemporalValue; if let Some(PropertyValue::Temporal( TemporalValue::Timestamp(item) )) = props.get("published") .and_then(|v| v.first()) { use chrono::{DateTime, FixedOffset, NaiveDateTime}; // Faithfully reconstruct the original datetime // I wonder why not just have an Enum that would // get you either date, time or a datetime, // potentially with an offset? let offset = item.as_offset().unwrap().data; let ndt: NaiveDateTime = item.as_date().unwrap().data .and_time(item.as_time().unwrap().data) - offset; let dt = DateTime::::from_utc(ndt, offset); let expected: DateTime = chrono::DateTime::parse_from_rfc3339( mf2["properties"]["published"][0].as_str().unwrap() ).unwrap(); assert_eq!(dt, expected); } else { panic!("Failed to find datetime in properties!"); } assert!(props.contains_key("uid")); assert!(props.contains_key("url")); assert!(props.get("url") .unwrap() .iter() .any(|i| i == props.get("uid").and_then(|v| v.first()).unwrap())); // XXX: fails because of https://gitlab.com/maxburon/microformats-parser/-/issues/7 assert!(!props.contains_key("name")); } else { unreachable!() } } }