use crate::{
block::{ButtonStyle, CalloutType, EmbedType},
converters::ConversionFormat,
Block, BlockType, Document, ListType,
};
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_document_metadata() {
let mut doc = Document::with_title("Test Document".to_string());
doc.metadata
.insert("author".to_string(), "Test Author".to_string());
doc.metadata
.insert("date".to_string(), "2025-01-16".to_string());
assert_eq!(doc.title, "Test Document");
assert_eq!(doc.metadata.get("author"), Some(&"Test Author".to_string()));
assert_eq!(doc.metadata.get("date"), Some(&"2025-01-16".to_string()));
}
#[test]
fn test_all_block_types_creation() {
let blocks = vec![
Block::new(BlockType::Text, "Text content".to_string()),
Block::new(BlockType::Header { level: 1 }, "Header".to_string()),
Block::new(
BlockType::List {
list_type: ListType::Ordered,
},
"Item 1\nItem 2".to_string(),
),
Block::new(
BlockType::List {
list_type: ListType::Unordered,
},
"Item A\nItem B".to_string(),
),
Block::new(
BlockType::Code {
language: Some("rust".to_string()),
},
"fn main() {}".to_string(),
),
Block::new(BlockType::Quote, "Quote text".to_string()),
Block::new(
BlockType::Image {
url: "https://example.com/image.jpg".to_string(),
alt: "Test image".to_string(),
caption: Some("Caption".to_string()),
},
"".to_string(),
),
Block::new(
BlockType::Link {
url: "https://example.com".to_string(),
title: Some("Example".to_string()),
},
"Link text".to_string(),
),
Block::new(
BlockType::Button {
text: "Click me".to_string(),
url: "https://example.com".to_string(),
style: ButtonStyle::Primary,
},
"".to_string(),
),
Block::new(
BlockType::Callout {
callout_type: CalloutType::Info,
title: Some("Info".to_string()),
},
"Callout content".to_string(),
),
Block::new(BlockType::Divider, "".to_string()),
Block::new(
BlockType::Table {
headers: vec!["Col1".to_string(), "Col2".to_string()],
rows: vec![vec!["A".to_string(), "B".to_string()]],
has_header: true,
},
"".to_string(),
),
Block::new(
BlockType::Embed {
embed_type: EmbedType::YouTube,
url: "https://youtube.com/watch?v=test".to_string(),
width: Some(560),
height: Some(315),
},
"".to_string(),
),
Block::new(
BlockType::Details {
summary: "Summary".to_string(),
is_open: false,
},
"Details content".to_string(),
),
Block::new(
BlockType::Columns {
column_count: 2,
content: vec![vec!["Col1".to_string()], vec!["Col2".to_string()]],
},
"".to_string(),
),
];
assert_eq!(blocks.len(), 15);
for block in &blocks {
assert!(
block.validate().is_ok(),
"Block validation failed for type: {:?}",
block.block_type
);
}
}
#[test]
fn test_button_styles() {
let styles = vec![
ButtonStyle::Primary,
ButtonStyle::Secondary,
ButtonStyle::Success,
ButtonStyle::Warning,
ButtonStyle::Danger,
ButtonStyle::Info,
];
for style in styles {
let block = Block::new(
BlockType::Button {
text: "Button".to_string(),
url: "https://example.com".to_string(),
style,
},
"".to_string(),
);
assert!(block.validate().is_ok());
}
}
#[test]
fn test_callout_types() {
let types = vec![
CalloutType::Info,
CalloutType::Warning,
CalloutType::Error,
CalloutType::Success,
CalloutType::Note,
CalloutType::Tip,
];
for callout_type in types {
let block = Block::new(
BlockType::Callout {
callout_type,
title: Some("Title".to_string()),
},
"Content".to_string(),
);
assert!(block.validate().is_ok());
}
}
#[test]
fn test_embed_types() {
let types = vec![
EmbedType::YouTube,
EmbedType::Vimeo,
EmbedType::Iframe,
EmbedType::Audio,
EmbedType::Video,
];
for embed_type in types {
let block = Block::new(
BlockType::Embed {
embed_type,
url: "https://example.com".to_string(),
width: Some(560),
height: Some(315),
},
"".to_string(),
);
assert!(block.validate().is_ok());
}
}
#[test]
fn test_list_types() {
let types = vec![ListType::Ordered, ListType::Unordered];
for list_type in types {
let block = Block::new(BlockType::List { list_type }, "Item 1\nItem 2".to_string());
assert!(block.validate().is_ok());
}
}
#[test]
fn test_header_levels() {
for level in 1..=6 {
let block = Block::new(BlockType::Header { level }, "Header".to_string());
assert!(
block.validate().is_ok(),
"Header level {level} should be valid"
);
}
for level in [0, 7, 10] {
let block = Block::new(BlockType::Header { level }, "Header".to_string());
assert!(
block.validate().is_err(),
"Header level {level} should be invalid"
);
}
}
#[test]
fn test_table_validation() {
let valid_table = Block::new(
BlockType::Table {
headers: vec!["A".to_string(), "B".to_string()],
rows: vec![
vec!["1".to_string(), "2".to_string()],
vec!["3".to_string(), "4".to_string()],
],
has_header: true,
},
"".to_string(),
);
assert!(valid_table.validate().is_ok());
let invalid_table = Block::new(
BlockType::Table {
headers: vec!["A".to_string(), "B".to_string()],
rows: vec![
vec!["1".to_string()], ],
has_header: true,
},
"".to_string(),
);
assert!(invalid_table.validate().is_err());
let empty_table = Block::new(
BlockType::Table {
headers: vec![],
rows: vec![],
has_header: false,
},
"".to_string(),
);
assert!(empty_table.validate().is_ok()); }
#[test]
fn test_columns_validation() {
let valid_columns = Block::new(
BlockType::Columns {
column_count: 3,
content: vec![
vec!["Col1".to_string()],
vec!["Col2".to_string()],
vec!["Col3".to_string()],
],
},
"".to_string(),
);
assert!(valid_columns.validate().is_ok());
let invalid_columns = Block::new(
BlockType::Columns {
column_count: 3,
content: vec![
vec!["Col1".to_string()],
vec!["Col2".to_string()],
],
},
"".to_string(),
);
assert!(invalid_columns.validate().is_err());
let zero_columns = Block::new(
BlockType::Columns {
column_count: 0,
content: vec![],
},
"".to_string(),
);
assert!(zero_columns.validate().is_err());
}
#[test]
fn test_url_validation() {
let valid_urls = vec![
"https://example.com",
"http://example.com",
"https://example.com/path?query=value",
"https://subdomain.example.com",
];
for url in valid_urls {
let block = Block::new(
BlockType::Link {
url: url.to_string(),
title: None,
},
"Link".to_string(),
);
assert!(block.validate().is_ok(), "URL should be valid: {url}");
}
let invalid_urls = vec![
"not-a-url",
"ftp://example.com", "",
"javascript:alert('xss')",
];
for url in invalid_urls {
let block = Block::new(
BlockType::Link {
url: url.to_string(),
title: None,
},
"Link".to_string(),
);
assert!(block.validate().is_err(), "URL should be invalid: {url}");
}
}
#[test]
fn test_image_validation() {
let valid_image = Block::new(
BlockType::Image {
url: "https://example.com/image.jpg".to_string(),
alt: "Image description".to_string(),
caption: None,
},
"".to_string(),
);
assert!(valid_image.validate().is_ok());
let invalid_image = Block::new(
BlockType::Image {
url: "https://example.com/image.jpg".to_string(),
alt: "".to_string(),
caption: None,
},
"".to_string(),
);
assert!(invalid_image.validate().is_err());
let invalid_url_image = Block::new(
BlockType::Image {
url: "not-a-url".to_string(),
alt: "Alt text".to_string(),
caption: None,
},
"".to_string(),
);
assert!(invalid_url_image.validate().is_ok());
}
#[test]
fn test_document_operations() {
let mut doc = Document::with_title("Test".to_string());
doc.add_block(Block::new(BlockType::Text, "Block 1".to_string()));
doc.add_block(Block::new(BlockType::Text, "Block 2".to_string()));
assert_eq!(doc.len(), 2);
doc.insert_block(1, Block::new(BlockType::Text, "Inserted".to_string()))
.unwrap();
assert_eq!(doc.len(), 3);
assert_eq!(doc.blocks[1].content, "Inserted");
doc.remove_block_at(1).unwrap();
assert_eq!(doc.len(), 2);
assert_eq!(doc.blocks[1].content, "Block 2");
doc.clear();
assert_eq!(doc.len(), 0);
}
#[test]
fn test_conversion_formats() {
let mut doc = Document::with_title("Conversion Test".to_string());
doc.add_block(Block::new(
BlockType::Header { level: 1 },
"Title".to_string(),
));
doc.add_block(Block::new(BlockType::Text, "Content".to_string()));
let formats = vec![ConversionFormat::Html, ConversionFormat::Markdown];
for format in formats {
let result = doc.to_format(format);
assert!(result.is_ok(), "Conversion to {format:?} failed");
let content = result.unwrap();
assert!(!content.is_empty(), "Converted content should not be empty");
}
}
#[test]
fn test_complex_document() {
let mut doc = Document::with_title("Complex Document".to_string());
doc.metadata
.insert("author".to_string(), "Test Author".to_string());
doc.metadata
.insert("created".to_string(), "2025-01-16".to_string());
doc.add_block(Block::new(
BlockType::Header { level: 1 },
"Main Title".to_string(),
));
doc.add_block(Block::new(
BlockType::Callout {
callout_type: CalloutType::Info,
title: Some("Introduction".to_string()),
},
"This is a complex document with multiple block types.".to_string(),
));
doc.add_block(Block::new(
BlockType::List {
list_type: ListType::Unordered,
},
"First item\nSecond item\nThird item".to_string(),
));
doc.add_block(Block::new(
BlockType::Table {
headers: vec!["Feature".to_string(), "Supported".to_string()],
rows: vec![
vec!["Headers".to_string(), "✅".to_string()],
vec!["Lists".to_string(), "✅".to_string()],
vec!["Tables".to_string(), "✅".to_string()],
],
has_header: true,
},
"".to_string(),
));
doc.add_block(Block::new(
BlockType::Columns {
column_count: 2,
content: vec![
vec![
"**Left Column**".to_string(),
"Content for the left side".to_string(),
],
vec![
"**Right Column**".to_string(),
"Content for the right side".to_string(),
],
],
},
"".to_string(),
));
doc.add_block(Block::new(
BlockType::Code {
language: Some("rust".to_string()),
},
"fn main() {\n println!(\"Hello, world!\");\n}".to_string(),
));
doc.add_block(Block::new(
BlockType::Button {
text: "Learn More".to_string(),
url: "https://example.com".to_string(),
style: ButtonStyle::Primary,
},
"".to_string(),
));
assert_eq!(doc.len(), 7);
for (i, block) in doc.blocks.iter().enumerate() {
assert!(block.validate().is_ok(), "Block {i} validation failed");
}
let html = doc.to_format(ConversionFormat::Html).unwrap();
let markdown = doc.to_format(ConversionFormat::Markdown).unwrap();
assert!(html.contains("<h1>"));
assert!(markdown.contains("# Main Title"));
}
#[test]
fn test_edge_cases() {
let empty_text = Block::new(BlockType::Text, "".to_string());
assert!(empty_text.validate().is_ok());
let empty_quote = Block::new(BlockType::Quote, "".to_string());
assert!(empty_quote.validate().is_ok());
let long_content = "a".repeat(10000);
let long_block = Block::new(BlockType::Text, long_content);
assert!(long_block.validate().is_ok());
let special_chars = "Special chars: áéíóú ñ 中文 🚀 <>&\"'".to_string();
let special_block = Block::new(BlockType::Text, special_chars);
assert!(special_block.validate().is_ok());
}
#[test]
fn test_performance_large_document() {
let mut doc = Document::with_title("Large Document".to_string());
for i in 0..1000 {
doc.add_block(Block::new(BlockType::Text, format!("Block {i}")));
}
assert_eq!(doc.len(), 1000);
let start = std::time::Instant::now();
let _html = doc.to_format(ConversionFormat::Html).unwrap();
let duration = start.elapsed();
assert!(
duration.as_secs() < 1,
"Large document conversion took too long: {duration:?}"
);
}
#[test]
fn test_serialization() {
let mut doc = Document::with_title("Serialization Test".to_string());
doc.add_block(Block::new(
BlockType::Header { level: 2 },
"Test Header".to_string(),
));
doc.add_block(Block::new(BlockType::Text, "Test content".to_string()));
let json = serde_json::to_string(&doc).unwrap();
assert!(json.contains("Serialization Test"));
assert!(json.contains("Test Header"));
let deserialized: Document = serde_json::from_str(&json).unwrap();
assert_eq!(deserialized.title, doc.title);
assert_eq!(deserialized.len(), doc.len());
assert_eq!(deserialized.blocks[0].content, doc.blocks[0].content);
}
}