diff --git a/lib/node.rs b/lib/node.rs index 3b8bf8f..212fb33 100644 --- a/lib/node.rs +++ b/lib/node.rs @@ -8,7 +8,7 @@ pub enum Node { ListItem { children: Vec }, CodeBlock { children: Vec }, BlockQuote { children: Vec }, - Image { src: String, alt: String }, + Link { href: String, children: Vec }, // Inline Nodes Text { content: String }, @@ -16,7 +16,9 @@ pub enum Node { Italic { content: String }, BoldItalic { content: String }, Code { content: String }, - Link { href: String, content: String }, + + // Special Nodes + Image { src: String, alt: String }, } impl Node { @@ -60,7 +62,10 @@ impl Node { let inner = children.iter().map(|x| x.to_html()).collect::(); format!("
{}
\n", inner) } - Node::Image { src, alt } => format!("\"{}\"\n", src, alt), + Node::Link { href, children} => { + let inner = children.iter().map(|x| x.to_html()).collect::(); + format!("{}", href, inner) + } // Inline nodes Node::Text { content } => format!("{}", content), @@ -68,7 +73,9 @@ impl Node { Node::Italic { content } => format!("{}", content), Node::BoldItalic { content } => format!("{}", content), Node::Code { content } => format!("{}", content), - Node::Link { href, content } => format!("{}", href, content), + + // Special nodes + Node::Image { src, alt } => format!("\"{}\"\n", src, alt), } } @@ -81,13 +88,11 @@ impl Node { Node::Document { children } | Node::Heading { level: _, children } | Node::Paragraph { children } - | Node::List { - ordered: _, - children, - } + | Node::List { ordered: _, children } | Node::ListItem { children } | Node::CodeBlock { children } | Node::BlockQuote { children } => children.is_empty(), + Node::Link { href, children } => children.is_empty() && href.is_empty(), // Inline nodes Node::Text { content } @@ -97,7 +102,6 @@ impl Node { | Node::Code { content } => content.is_empty(), // Special rules - Node::Link { href, content } => content.is_empty() && href.is_empty(), Node::Image { src, alt } => src.is_empty() && alt.is_empty(), } } @@ -110,13 +114,11 @@ impl Node { Node::Document { children } | Node::Heading { level: _, children } | Node::Paragraph { children } - | Node::List { - ordered: _, - children, - } + | Node::List { ordered: _, children} | Node::ListItem { children } | Node::CodeBlock { children } - | Node::BlockQuote { children } => Some(&children), + | Node::BlockQuote { children } + | Node::Link { href: _, children } => Some(&children), // Inline Nodes Node::Text { content: _ } @@ -126,10 +128,6 @@ impl Node { | Node::Code { content: _ } => None, // Special Nodes - Node::Link { - href: _, - content: _, - } => None, Node::Image { src: _, alt: _ } => None, } } @@ -142,13 +140,11 @@ impl Node { Node::Document { children } | Node::Heading { level: _, children } | Node::Paragraph { children } - | Node::List { - ordered: _, - children, - } + | Node::List { ordered: _, children } | Node::ListItem { children } | Node::CodeBlock { children } - | Node::BlockQuote { children } => children.push(child), + | Node::BlockQuote { children } + | Node::Link { href: _, children } => children.push(child), // Inline Nodes Node::Text { content: _ } @@ -158,10 +154,6 @@ impl Node { | Node::Code { content: _ } => panic!("Can't add child to this node type."), // Special Nodes - Node::Link { - href: _, - content: _, - } => panic!("Can't add child to this node type."), Node::Image { src: _, alt: _ } => panic!("Can't add child to this node type."), }; } @@ -172,7 +164,133 @@ mod node_tests { use super::Node; #[test] - fn can_return_when_empty() { + fn can_return_html_string_for_structure_nodes() { + let child = Node::Text { content: "CONTENT".into() }; + + // Document + { + let node = Node::Document { children: vec![child.clone()] }; + let html = "\n\n\n\n\nDocument\n\n\nCONTENT\n"; + assert_eq!(node.to_html(), html); + } + + // Heading + { + let node_h1 = Node::Heading { level: 1, children: vec![child.clone()] }; + let node_h2 = Node::Heading { level: 2, children: vec![child.clone()] }; + let node_h3 = Node::Heading { level: 3, children: vec![child.clone()] }; + let node_h4 = Node::Heading { level: 4, children: vec![child.clone()] }; + let node_h5 = Node::Heading { level: 5, children: vec![child.clone()] }; + let node_h6 = Node::Heading { level: 6, children: vec![child.clone()] }; + let html_h1 = "

CONTENT

\n"; + let html_h2 = "

CONTENT

\n"; + let html_h3 = "

CONTENT

\n"; + let html_h4 = "

CONTENT

\n"; + let html_h5 = "
CONTENT
\n"; + let html_h6 = "
CONTENT
\n"; + assert_eq!(node_h1.to_html(), html_h1); + assert_eq!(node_h2.to_html(), html_h2); + assert_eq!(node_h3.to_html(), html_h3); + assert_eq!(node_h4.to_html(), html_h4); + assert_eq!(node_h5.to_html(), html_h5); + assert_eq!(node_h6.to_html(), html_h6); + } + + // Paragraph + { + let node = Node::Paragraph { children: vec![child.clone()] }; + let html = "

CONTENT

\n"; + assert_eq!(node.to_html(), html); + } + + // List + { + let node_ul = Node::List { ordered: false, children: vec![child.clone()] }; + let node_ol = Node::List { ordered: true, children: vec![child.clone()] }; + let html_ul = "
    CONTENT
\n"; + let html_ol = "
    CONTENT
\n"; + assert_eq!(node_ul.to_html(), html_ul); + assert_eq!(node_ol.to_html(), html_ol); + } + + // List Item + { + let node = Node::ListItem { children: vec![child.clone()] }; + let html = "
  • CONTENT
  • \n"; + assert_eq!(node.to_html(), html); + } + + // CodeBlock + { + let node = Node::CodeBlock { children: vec![child.clone()] }; + let html = "CONTENT\n"; + assert_eq!(node.to_html(), html); + } + + // BlockQuote + { + let node = Node::BlockQuote { children: vec![child.clone()] }; + let html = "
    CONTENT
    \n"; + assert_eq!(node.to_html(), html); + } + + // Link + { + let node = Node::Link { href: "HREF".into(), children: vec![child.clone()]}; + let html = "CONTENT"; + assert_eq!(node.to_html(), html); + } + } + + #[test] + fn can_return_html_string_for_inline_nodes() { + // Text + { + let node = Node::Text { content: "x".into() }; + let html = "x"; + assert_eq!(node.to_html(), html); + } + + // Bold + { + let node = Node::Bold { content: "x".into() }; + let html = "x"; + assert_eq!(node.to_html(), html); + } + + // Italic + { + let node = Node::Italic { content: "x".into() }; + let html = "x"; + assert_eq!(node.to_html(), html); + } + + // BoldItalic + { + let node = Node::BoldItalic { content: "x".into() }; + let html = "x"; + assert_eq!(node.to_html(), html); + } + + // Code + { + let node = Node::Code { content: "x".into() }; + let html = "x"; + assert_eq!(node.to_html(), html); + } + } + + #[test] + fn can_return_html_string_for_special_nodes() { + { + let node = Node::Image { src: "SOURCE".into(), alt: "ALT".into() }; + let html = "\"ALT\"\n"; + assert_eq!(node.to_html(), html); + } + } + + #[test] + fn can_return_when_empty_for_structure_nodes() { // Document let document_node = Node::Document { children: vec![Node::Paragraph { children: vec![] }], @@ -196,7 +314,7 @@ mod node_tests { // Paragraph let paragraph_node = Node::Paragraph { children: vec![Node::Text { - content: String::from("hello"), + content: "hello".into(), }], }; let paragraph_node_empty = Node::Paragraph { children: vec![] }; @@ -208,7 +326,7 @@ mod node_tests { ordered: false, children: vec![Node::ListItem { children: vec![Node::Text { - content: String::from("item"), + content: "item".into(), }], }], }; @@ -222,7 +340,7 @@ mod node_tests { // ListItem let list_item_node = Node::ListItem { children: vec![Node::Text { - content: String::from("item"), + content: "item".into(), }], }; let list_item_node_empty = Node::ListItem { children: vec![] }; @@ -232,7 +350,7 @@ mod node_tests { // CodeBlock let code_block_node = Node::CodeBlock { children: vec![Node::Text { - content: String::from("code"), + content: "code".into(), }], }; let code_block_node_empty = Node::CodeBlock { children: vec![] }; @@ -242,44 +360,37 @@ mod node_tests { // BlockQuote let block_quote_node = Node::BlockQuote { children: vec![Node::Text { - content: String::from("quote"), + content: "quote".into(), }], }; let block_quote_node_empty = Node::BlockQuote { children: vec![] }; assert!(!block_quote_node.is_empty()); assert!(block_quote_node_empty.is_empty()); - // ImageNode (structure-ish but with fields) - let image_node = Node::Image { - src: String::from("src"), - alt: String::from("alt"), - }; - let image_node_empty = Node::Image { - src: String::from(""), - alt: String::from(""), - }; - assert!(!image_node.is_empty()); - assert!(image_node_empty.is_empty()); + // Link + let link_node = Node::Link { href: "x".into(), children: vec![Node::Text{content: "link".into()}] }; + let link_node_empty = Node::Link { href: "".into(), children: vec![] }; + assert!(!link_node.is_empty()); + assert!(link_node_empty.is_empty()); + } - // Inline nodes: non-empty + #[test] + fn can_return_when_empty_for_inline_nodes() { + // non-empty let text_node = Node::Text { - content: String::from("text"), + content: "text".into(), }; let bold_node = Node::Bold { - content: String::from("bold"), + content: "bold".into(), }; let italic_node = Node::Italic { - content: String::from("italic"), + content: "italic".into(), }; let bold_italic_node = Node::BoldItalic { - content: String::from("both"), + content: "both".into(), }; let code_node = Node::Code { - content: String::from("code"), - }; - let link_node = Node::Link { - href: String::from("https://example.com"), - content: String::from("link"), + content: "code".into(), }; assert!(!text_node.is_empty()); @@ -287,27 +398,22 @@ mod node_tests { assert!(!italic_node.is_empty()); assert!(!bold_italic_node.is_empty()); assert!(!code_node.is_empty()); - assert!(!link_node.is_empty()); - // Inline nodes: empty + // empty let text_node_empty = Node::Text { - content: String::from(""), + content: "".into(), }; let bold_node_empty = Node::Bold { - content: String::from(""), + content: "".into(), }; let italic_node_empty = Node::Italic { - content: String::from(""), + content: "".into(), }; let bold_italic_node_empty = Node::BoldItalic { - content: String::from(""), + content: "".into(), }; let code_node_empty = Node::Code { - content: String::from(""), - }; - let link_node_empty = Node::Link { - href: String::from(""), - content: String::from(""), + content: "".into(), }; assert!(text_node_empty.is_empty()); @@ -315,7 +421,14 @@ mod node_tests { assert!(italic_node_empty.is_empty()); assert!(bold_italic_node_empty.is_empty()); assert!(code_node_empty.is_empty()); - assert!(link_node_empty.is_empty()); + } + + #[test] + fn can_return_when_empty_for_special_nodes() { + let image_node = Node::Image { src: "x".into(), alt: "x".into() }; + let image_node_empty = Node::Image { src: "".into(), alt: "".into() }; + assert!(!image_node.is_empty()); + assert!(image_node_empty.is_empty()); } #[test] @@ -407,34 +520,78 @@ mod node_tests { #[test] fn children_returns_none_for_special_nodes() { - let link = Node::Link { - href: "x".into(), - content: "x".into(), - }; let image = Node::Image { src: "x".into(), alt: "x".into(), }; - - assert!(link.children().is_none()); assert!(image.children().is_none()); } #[test] fn add_child_succeeds_for_structure_nodes() { - let child = Node::Text { - content: "x".into(), - }; + let child = Node::Text { content: "x".into() }; + // Document { - // Document - let mut document = Node::Document { children: vec![] }; - document.add_child(child); - if let Some(children) = document.children() { - assert_eq!(children.len(), 1); - } else { - panic!("Document should have children"); - } + let mut node = Node::Document { children: vec![] }; + node.add_child(child.clone()); + let len = node.children().map(|c| c.len()).unwrap_or(0); + assert_eq!(len, 1, "Document should have 1 child"); + } + + // Heading + { + let mut node = Node::Heading { level: 1, children: vec![] }; + node.add_child(child.clone()); + let len = node.children().map(|c| c.len()).unwrap_or(0); + assert_eq!(len, 1, "Heading should have 1 child"); + } + + // Paragraph + { + let mut node = Node::Paragraph { children: vec![] }; + node.add_child(child.clone()); + let len = node.children().map(|c| c.len()).unwrap_or(0); + assert_eq!(len, 1, "Paragraph should have 1 child"); + } + + // List + { + let mut node = Node::List { ordered: false, children: vec![] }; + node.add_child(child.clone()); + let len = node.children().map(|c| c.len()).unwrap_or(0); + assert_eq!(len, 1, "List should have 1 child"); + } + + // ListItem + { + let mut node = Node::ListItem { children: vec![] }; + node.add_child(child.clone()); + let len = node.children().map(|c| c.len()).unwrap_or(0); + assert_eq!(len, 1, "ListItem should have 1 child"); + } + + // CodeBlock + { + let mut node = Node::CodeBlock { children: vec![] }; + node.add_child(child.clone()); + let len = node.children().map(|c| c.len()).unwrap_or(0); + assert_eq!(len, 1, "CodeBlock should have 1 child"); + } + + // BlockQuote + { + let mut node = Node::BlockQuote { children: vec![] }; + node.add_child(child.clone()); + let len = node.children().map(|c| c.len()).unwrap_or(0); + assert_eq!(len, 1, "BlockQuote should have 1 child"); + } + // Link + { + let mut node = Node::Link { href: "x".into(), children: vec![] }; + node.add_child(child.clone()); + let len = node.children().map(|c| c.len()).unwrap_or(0); + assert_eq!(len, 1, "Link should have 1 child"); } } @@ -474,17 +631,11 @@ mod node_tests { let child = Node::Text { content: "x".into(), }; - - let mut link = Node::Link { - href: "x".into(), - content: "x".into(), - }; let mut image = Node::Image { src: "x".into(), alt: "x".into(), }; - link.add_child(child.clone()); image.add_child(child.clone()); } }