at main 337 lines 11 kB view raw
1use crate::NotebookContext; 2 3use super::*; 4use std::path::PathBuf; 5use weaver_common::jacquard::client::{ 6 AtpSession, MemorySessionStore, 7 credential_session::{CredentialSession, SessionKey}, 8}; 9 10/// Type alias for the session used in tests 11type TestSession = CredentialSession< 12 MemorySessionStore<SessionKey, AtpSession>, 13 weaver_common::jacquard::identity::JacquardResolver, 14>; 15 16/// Helper: Create test context without network capabilities 17fn test_context() -> StaticSiteContext<TestSession> { 18 let root = PathBuf::from("/tmp/test"); 19 let destination = PathBuf::from("/tmp/output"); 20 let mut ctx = StaticSiteContext::new(root, destination, None); 21 ctx.client = None; // Explicitly disable network 22 ctx 23} 24 25/// Helper: Render markdown to HTML using test context 26async fn render_markdown(input: &str) -> String { 27 let context = test_context(); 28 export_page(input, context).await.unwrap() 29} 30 31#[tokio::test] 32async fn test_smoke() { 33 let output = render_markdown("Hello world").await; 34 assert!(output.contains("Hello world")); 35} 36 37#[tokio::test] 38async fn test_paragraph_rendering() { 39 let input = "This is a paragraph.\n\nThis is another paragraph."; 40 let output = render_markdown(input).await; 41 insta::assert_snapshot!(output); 42} 43 44#[tokio::test] 45async fn test_heading_rendering() { 46 let input = "# Heading 1\n\n## Heading 2\n\n### Heading 3"; 47 let output = render_markdown(input).await; 48 insta::assert_snapshot!(output); 49} 50 51#[tokio::test] 52async fn test_list_rendering() { 53 let input = "- Item 1\n- Item 2\n - Nested\n\n1. Ordered 1\n2. Ordered 2"; 54 let output = render_markdown(input).await; 55 insta::assert_snapshot!(output); 56} 57 58#[tokio::test] 59async fn test_code_block_rendering() { 60 let input = "```rust\nfn main() {\n println!(\"Hello\");\n}\n```"; 61 let output = render_markdown(input).await; 62 insta::assert_snapshot!(output); 63} 64 65#[tokio::test] 66async fn test_table_rendering() { 67 let input = "| Left | Center | Right |\n|:-----|:------:|------:|\n| A | B | C |"; 68 let output = render_markdown(input).await; 69 insta::assert_snapshot!(output); 70} 71 72#[tokio::test] 73async fn test_blockquote_rendering() { 74 let input = "> This is a quote\n>\n> With multiple lines"; 75 let output = render_markdown(input).await; 76 insta::assert_snapshot!(output); 77} 78 79#[tokio::test] 80async fn test_math_rendering() { 81 let input = "Inline $x^2$ and display:\n\n$$\ny = mx + b\n$$"; 82 let output = render_markdown(input).await; 83 insta::assert_snapshot!(output); 84} 85 86#[tokio::test] 87async fn test_wikilink_resolution() { 88 let vault_contents = vec![ 89 PathBuf::from("notes/First Note.md"), 90 PathBuf::from("notes/Second Note.md"), 91 ]; 92 93 let mut context = test_context(); 94 context.dir_contents = Some(vault_contents.into()); 95 96 let input = "[[First Note]] and [[Second Note]]"; 97 let output = export_page(input, context).await.unwrap(); 98 println!("{output}"); 99 assert!(output.contains("./First%20Note.html")); 100 assert!(output.contains("./Second%20Note.html")); 101} 102 103#[tokio::test] 104async fn test_broken_wikilink() { 105 let vault_contents = vec![PathBuf::from("notes/Exists.md")]; 106 107 let mut context = test_context(); 108 context.dir_contents = Some(vault_contents.into()); 109 110 let input = "[[Does Not Exist]]"; 111 let output = export_page(input, context).await.unwrap(); 112 113 // Broken wikilinks become links (they just don't point anywhere valid) 114 // This is acceptable - static site will show 404 on click 115 assert!(output.contains("<a href=")); 116 assert!(output.contains("Does Not Exist</a>") || output.contains("Does%20Not%20Exist")); 117} 118 119#[tokio::test] 120async fn test_wikilink_with_section() { 121 let vault_contents = vec![PathBuf::from("Note.md")]; 122 123 let mut context = test_context(); 124 context.dir_contents = Some(vault_contents.into()); 125 126 let input = "[[Note#Section]]"; 127 let output = export_page(input, context).await.unwrap(); 128 println!("{output}"); 129 assert!(output.contains("Note#Section")); 130} 131 132#[tokio::test] 133async fn test_link_flattening_enabled() { 134 let mut context = test_context(); 135 context.options = StaticSiteOptions::FLATTEN_STRUCTURE; 136 137 let input = "[Link](path/to/nested/file.md)"; 138 let output = export_page(input, context).await.unwrap(); 139 println!("{output}"); 140 // Should flatten to single parent directory 141 assert!(output.contains("./entry/file.html")); 142} 143 144#[tokio::test] 145async fn test_link_flattening_disabled() { 146 let mut context = test_context(); 147 context.options = StaticSiteOptions::empty(); 148 149 let input = "[Link](path/to/nested/file.md)"; 150 let output = export_page(input, context).await.unwrap(); 151 println!("{output}"); 152 // Should preserve original path 153 assert!(output.contains("path/to/nested/file.html")); 154} 155 156#[tokio::test] 157async fn test_frontmatter_parsing() { 158 let input = "---\ntitle: Test Page\nauthor: Test Author\n---\n\nContent here"; 159 let context = test_context(); 160 let output = export_page(input, context.clone()).await.unwrap(); 161 162 // Frontmatter should be parsed but not rendered 163 assert!(!output.contains("title: Test Page")); 164 assert!(output.contains("Content here")); 165 166 // Verify frontmatter was captured 167 let frontmatter = context.frontmatter(); 168 let yaml = frontmatter.contents(); 169 let yaml_guard = yaml.read().unwrap(); 170 assert!(yaml_guard.len() > 0); 171} 172 173#[tokio::test] 174async fn test_empty_frontmatter() { 175 let input = "---\n---\n\nContent"; 176 let output = render_markdown(input).await; 177 178 assert!(output.contains("Content")); 179 assert!(!output.contains("---")); 180} 181 182#[tokio::test] 183async fn test_empty_input() { 184 let output = render_markdown("").await; 185 assert_eq!(output, ""); 186} 187 188#[tokio::test] 189async fn test_html_and_special_characters() { 190 // Test that markdown correctly handles HTML and special chars per CommonMark spec 191 let input = 192 "Text with <special> & some text. Valid tags: <em>emphasis</em> and <strong>bold</strong>"; 193 let output = render_markdown(input).await; 194 195 // & must be escaped for valid HTML 196 assert!(output.contains("&amp;")); 197 198 // Inline HTML tags pass through (CommonMark behavior) 199 assert!(output.contains("<special>")); 200 assert!(output.contains("<em>emphasis</em>")); 201 assert!(output.contains("<strong>bold</strong>")); 202} 203 204#[tokio::test] 205async fn test_unicode_content() { 206 let input = "Unicode: 你好 🎉 café"; 207 let output = render_markdown(input).await; 208 209 assert!(output.contains("你好")); 210 assert!(output.contains("🎉")); 211 assert!(output.contains("café")); 212} 213 214// ============================================================================= 215// WeaverBlock Prefix Tests 216// ============================================================================= 217 218#[tokio::test] 219async fn test_weaver_block_aside_class() { 220 let input = "\n\n{.aside}\nThis paragraph should be in an aside."; 221 let output = render_markdown(input).await; 222 insta::assert_snapshot!(output); 223} 224 225#[tokio::test] 226async fn test_weaver_block_custom_class() { 227 let input = "\n\n{.highlight}\nThis paragraph has a custom class."; 228 let output = render_markdown(input).await; 229 insta::assert_snapshot!(output); 230} 231 232#[tokio::test] 233async fn test_weaver_block_custom_attributes() { 234 let input = "\n\n{.foo, width: 300px, data-test: value}\nParagraph with class and attributes."; 235 let output = render_markdown(input).await; 236 insta::assert_snapshot!(output); 237} 238 239#[tokio::test] 240async fn test_weaver_block_before_heading() { 241 let input = "\n\n{.aside}\n## Heading in aside\n\nParagraph also in aside."; 242 let output = render_markdown(input).await; 243 insta::assert_snapshot!(output); 244} 245 246#[tokio::test] 247async fn test_weaver_block_before_blockquote() { 248 let input = "\n\n{.aside}\n\n> This blockquote is in an aside."; 249 let output = render_markdown(input).await; 250 insta::assert_snapshot!(output); 251} 252 253#[tokio::test] 254async fn test_weaver_block_before_list() { 255 let input = "\n\n{.aside}\n\n- Item 1\n- Item 2"; 256 let output = render_markdown(input).await; 257 insta::assert_snapshot!(output); 258} 259 260#[tokio::test] 261async fn test_weaver_block_before_code_block() { 262 let input = "\n\n{.aside}\n\n```rust\nfn main() {}\n```"; 263 let output = render_markdown(input).await; 264 insta::assert_snapshot!(output); 265} 266 267#[tokio::test] 268async fn test_weaver_block_multiple_classes() { 269 let input = "\n\n{.aside, .highlight, .important}\nMultiple classes applied."; 270 let output = render_markdown(input).await; 271 insta::assert_snapshot!(output); 272} 273 274#[tokio::test] 275async fn test_weaver_block_no_effect_on_following() { 276 let input = "\n\n{.aside}\nFirst paragraph in aside.\n\nSecond paragraph NOT in aside."; 277 let output = render_markdown(input).await; 278 insta::assert_snapshot!(output); 279} 280 281// ============================================================================= 282// Footnote / Sidenote Tests 283// ============================================================================= 284 285#[tokio::test] 286async fn test_footnote_traditional() { 287 let input = "Here is some text[^1].\n[^1]: This is the footnote definition."; 288 let output = render_markdown(input).await; 289 insta::assert_snapshot!(output); 290} 291 292#[tokio::test] 293async fn test_footnote_sidenote_inline() { 294 // When definition immediately follows reference in the same paragraph flow 295 let input = "Here is text[^note]\n[^note]: Sidenote content."; 296 let output = render_markdown(input).await; 297 insta::assert_snapshot!(output); 298} 299 300#[tokio::test] 301async fn test_footnote_multiple() { 302 let input = "First[^1] and second[^2] footnotes.\n[^1]: First note.\n[^2]: Second note."; 303 let output = render_markdown(input).await; 304 insta::assert_snapshot!(output); 305} 306 307#[tokio::test] 308async fn test_footnote_with_inline_formatting() { 309 let input = "Text with footnote[^fmt].\n[^fmt]: Note with **bold** and *italic*."; 310 let output = render_markdown(input).await; 311 insta::assert_snapshot!(output); 312} 313 314#[tokio::test] 315async fn test_footnote_named() { 316 let input = "Reference[^my-note].\n[^my-note]: Named footnote content."; 317 let output = render_markdown(input).await; 318 insta::assert_snapshot!(output); 319} 320 321#[tokio::test] 322async fn test_footnote_in_blockquote() { 323 let input = "> Quote with footnote[^q].\n[^q]: Footnote for quote."; 324 let output = render_markdown(input).await; 325 insta::assert_snapshot!(output); 326} 327 328// ============================================================================= 329// Combined WeaverBlock + Footnote Tests 330// ============================================================================= 331 332#[tokio::test] 333async fn test_weaver_block_with_footnote() { 334 let input = "{.aside}\nAside with a footnote[^aside].\n\n[^aside]: Footnote in aside context."; 335 let output = render_markdown(input).await; 336 insta::assert_snapshot!(output); 337}