#[cfg(test)]
mod tests {
use crate::{Block, BlockType, Document, ListType};
use proptest::prelude::*;
fn safe_string() -> impl Strategy<Value = String> {
r"[a-zA-Z0-9\s\-_.,!?]{1,100}"
.prop_filter("Non-empty content", |s: &String| !s.trim().is_empty())
}
fn header_level() -> impl Strategy<Value = u8> {
1u8..=6u8
}
fn safe_url() -> impl Strategy<Value = String> {
prop_oneof![
Just("https://example.com".to_string()),
Just("http://test.org".to_string()),
Just("/local/path".to_string()),
r"https://[a-z]{3,10}\.com".prop_map(|s| s.to_string())
]
}
fn alt_text() -> impl Strategy<Value = String> {
r"[a-zA-Z0-9\s\-_]{1,50}"
.prop_filter("Non-empty alt text", |s: &String| !s.trim().is_empty())
}
proptest! {
#[test]
fn test_text_block_property(content in safe_string()) {
let block = Block::new(BlockType::Text, content.clone());
prop_assert_eq!(&block.content, &content);
prop_assert!(block.validate().is_ok());
}
#[test]
fn test_header_block_property(level in header_level(), content in safe_string()) {
let block = Block::new(BlockType::Header { level }, content.clone());
prop_assert_eq!(&block.content, &content);
prop_assert!(block.validate().is_ok());
}
#[test]
fn test_link_block_property(url in safe_url(), content in safe_string()) {
let block = Block::new(
BlockType::Link {
url: url.clone(),
title: None
},
content.clone()
);
prop_assert_eq!(&block.content, &content);
prop_assert!(block.validate().is_ok());
}
#[test]
fn test_image_block_property(
url in safe_url(),
alt in alt_text()
) {
let block = Block::new(
BlockType::Image {
url: url.clone(),
alt: alt.clone(),
caption: None
},
"".to_string()
);
prop_assert!(block.validate().is_ok());
}
#[test]
fn test_document_add_blocks_property(blocks_count in 1usize..=20) {
let mut doc = Document::with_title("Test Document".to_string());
for i in 0..blocks_count {
let content = format!("Block content {i}");
doc.add_block(Block::new(BlockType::Text, content));
}
prop_assert_eq!(doc.len(), blocks_count);
prop_assert!(!doc.is_empty());
}
#[test]
fn test_list_block_property(content in safe_string()) {
let ordered_block = Block::new(
BlockType::List { list_type: ListType::Ordered },
content.clone()
);
prop_assert!(ordered_block.validate().is_ok());
let unordered_block = Block::new(
BlockType::List { list_type: ListType::Unordered },
content.clone()
);
prop_assert!(unordered_block.validate().is_ok());
}
#[test]
fn test_code_block_property(content in ".*", language in r"[a-z]{2,10}") {
let block = Block::new(
BlockType::Code {
language: Some(language.clone())
},
content.clone()
);
prop_assert_eq!(&block.content, &content);
prop_assert!(block.validate().is_ok());
}
#[test]
fn test_quote_block_property(content in safe_string()) {
let block = Block::new(BlockType::Quote, content.clone());
prop_assert_eq!(&block.content, &content);
prop_assert!(block.validate().is_ok());
}
#[test]
fn test_document_conversion_property(title in safe_string()) {
let mut doc = Document::with_title(title.clone());
doc.add_block(Block::new(BlockType::Header { level: 1 }, "Test Header".to_string()));
doc.add_block(Block::new(BlockType::Text, "Test content".to_string()));
let markdown = doc.to_format(crate::converters::ConversionFormat::Markdown);
let html = doc.to_format(crate::converters::ConversionFormat::Html);
prop_assert!(markdown.is_ok());
prop_assert!(html.is_ok());
}
#[test]
fn test_table_consistency_property(
headers in prop::collection::vec(r"[A-Za-z]{1,10}", 1..=5),
rows_count in 1usize..=5
) {
let col_count = headers.len();
let rows: Vec<Vec<String>> = (0..rows_count)
.map(|i| (0..col_count).map(|j| format!("Cell{i}_{j}")).collect())
.collect();
let block = Block::new(
BlockType::Table {
headers,
rows,
has_header: true
},
"".to_string()
);
prop_assert!(block.validate().is_ok());
}
#[test]
fn test_roundtrip_markdown_property(_content in safe_string()) {
let original_doc = Document::with_title("Test".to_string());
let markdown = original_doc.to_format(crate::converters::ConversionFormat::Markdown);
prop_assert!(markdown.is_ok());
}
#[test]
fn test_block_metadata_property(
key in r"[a-zA-Z_]{1,20}",
value in safe_string()
) {
let mut block = Block::new(BlockType::Text, "Test content".to_string());
block.add_metadata(key.clone(), value.clone());
prop_assert_eq!(block.get_metadata(&key), Some(&value));
}
#[test]
fn test_document_operations_property(
title in safe_string(),
operations_count in 1usize..=10
) {
let mut doc = Document::with_title(title.clone());
for i in 0..operations_count {
doc.add_block(Block::new(
BlockType::Text,
format!("Content {i}")
));
}
let initial_len = doc.len();
prop_assert_eq!(initial_len, operations_count);
for _ in 0..(operations_count / 2) {
if !doc.is_empty() {
let _ = doc.remove_block_at(0);
}
}
prop_assert!(doc.len() <= initial_len);
}
#[test]
fn test_validation_consistency_property(content in safe_string()) {
let block = Block::new(BlockType::Text, content);
let first_validation = block.validate();
let second_validation = block.validate();
prop_assert_eq!(first_validation.is_ok(), second_validation.is_ok());
}
#[test]
fn test_serialization_roundtrip_property(content in safe_string()) {
let original_block = Block::new(BlockType::Text, content);
let serialized = serde_json::to_string(&original_block);
prop_assert!(serialized.is_ok());
let deserialized: Result<Block, _> = serde_json::from_str(&serialized.unwrap());
prop_assert!(deserialized.is_ok());
let deserialized_block = deserialized.unwrap();
prop_assert_eq!(original_block.content, deserialized_block.content);
prop_assert_eq!(original_block.block_type, deserialized_block.block_type);
}
}
}