moving tests around

Orual 2c0b32a3 d6592bef

+311 -302
+1
.gitignore
··· 4 4 /dist 5 5 /node_modules 6 6 /plans 7 + /alloy 7 8 .direnv 8 9 .env 9 10 .devenv
+10 -10
Cargo.lock
··· 2143 2143 2144 2144 [[package]] 2145 2145 name = "ignore" 2146 - version = "0.4.24" 2146 + version = "0.4.25" 2147 2147 source = "registry+https://github.com/rust-lang/crates.io-index" 2148 - checksum = "81776e6f9464432afcc28d03e52eb101c93b6f0566f52aef2427663e700f0403" 2148 + checksum = "d3d782a365a015e0f5c04902246139249abf769125006fbe7649e2ee88169b4a" 2149 2149 dependencies = [ 2150 2150 "crossbeam-deque", 2151 2151 "globset", ··· 2783 2783 [[package]] 2784 2784 name = "markdown-weaver" 2785 2785 version = "0.13.0" 2786 - source = "git+https://github.com/rsform/markdown-weaver#83441068a9494f569218c64026880199b41a6e06" 2786 + source = "git+https://github.com/rsform/markdown-weaver#46f3eee6c93118da84a5de3d25fef642735aedd2" 2787 2787 dependencies = [ 2788 2788 "bitflags 2.10.0", 2789 2789 "getopts", ··· 2796 2796 [[package]] 2797 2797 name = "markdown-weaver-escape" 2798 2798 version = "0.11.0" 2799 - source = "git+https://github.com/rsform/markdown-weaver#83441068a9494f569218c64026880199b41a6e06" 2799 + source = "git+https://github.com/rsform/markdown-weaver#46f3eee6c93118da84a5de3d25fef642735aedd2" 2800 2800 2801 2801 [[package]] 2802 2802 name = "markup5ever" ··· 5438 5438 5439 5439 [[package]] 5440 5440 name = "unicode-ident" 5441 - version = "1.0.20" 5441 + version = "1.0.22" 5442 5442 source = "registry+https://github.com/rust-lang/crates.io-index" 5443 - checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06" 5443 + checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5" 5444 5444 5445 5445 [[package]] 5446 5446 name = "unicode-linebreak" ··· 5450 5450 5451 5451 [[package]] 5452 5452 name = "unicode-normalization" 5453 - version = "0.1.24" 5453 + version = "0.1.25" 5454 5454 source = "registry+https://github.com/rust-lang/crates.io-index" 5455 - checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" 5455 + checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8" 5456 5456 dependencies = [ 5457 5457 "tinyvec", 5458 5458 ] 5459 5459 5460 5460 [[package]] 5461 5461 name = "unicode-properties" 5462 - version = "0.1.3" 5462 + version = "0.1.4" 5463 5463 source = "registry+https://github.com/rust-lang/crates.io-index" 5464 - checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0" 5464 + checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d" 5465 5465 5466 5466 [[package]] 5467 5467 name = "unicode-segmentation"
+15 -36
alloy/css/base.css
··· 1 1 /* CSS Reset */ 2 - *, 3 - *::before, 4 - *::after { 2 + *, *::before, *::after { 5 3 box-sizing: border-box; 6 4 margin: 0; 7 5 padding: 0; ··· 14 12 --color-link: #286983; 15 13 --color-link-hover: #56949f; 16 14 17 - --font-body: IBM Plex, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; 18 - --font-heading: IBM Plex Sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; 19 - --font-mono: "IBM Plex Mono", "Berkeley Mono", "Cascadia Code", "Roboto Mono", Consolas, monospace; 15 + --font-body: IBM Plex, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 16 + --font-heading: IBM Plex Sans, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; 17 + --font-mono: 'IBM Plex Mono', 'Berkeley Mono', 'Cascadia Code', 'Roboto Mono', Consolas, monospace; 20 18 21 19 --spacing-base: 16px; 22 20 --spacing-line-height: 1.6; ··· 39 37 } 40 38 41 39 /* Typography */ 42 - h1, 43 - h2, 44 - h3, 45 - h4, 46 - h5, 47 - h6 { 40 + h1, h2, h3, h4, h5, h6 { 48 41 font-family: var(--font-heading); 49 42 margin-top: calc(1rem * var(--spacing-scale)); 50 43 margin-bottom: 0.5rem; 51 44 line-height: 1.2; 52 45 } 53 46 54 - h1 { 55 - font-size: 2.5rem; 56 - } 57 - h2 { 58 - font-size: 2rem; 59 - } 60 - h3 { 61 - font-size: 1.5rem; 62 - } 63 - h4 { 64 - font-size: 1.25rem; 65 - } 66 - h5 { 67 - font-size: 1.125rem; 68 - } 69 - h6 { 70 - font-size: 1rem; 71 - } 47 + h1 { font-size: 2.5rem; } 48 + h2 { font-size: 2rem; } 49 + h3 { font-size: 1.5rem; } 50 + h4 { font-size: 1.25rem; } 51 + h5 { font-size: 1.125rem; } 52 + h6 { font-size: 1rem; } 72 53 73 54 p { 74 55 margin-bottom: 1rem; ··· 85 66 } 86 67 87 68 /* Lists */ 88 - ul, 89 - ol { 69 + ul, ol { 90 70 margin-left: 2rem; 91 71 margin-bottom: 1rem; 92 72 } ··· 98 78 /* Code */ 99 79 code { 100 80 font-family: var(--font-mono); 101 - background-color: rgba(0, 0, 0, 0.03); 81 + background-color: rgba(0, 0, 0, 0.05); 102 82 padding: 0.125rem 0.25rem; 103 83 border-radius: 3px; 104 84 font-size: 0.9em; ··· 112 92 pre code { 113 93 display: block; 114 94 padding: 1rem; 115 - background-color: rgba(0, 0, 0, 0.02); 95 + background-color: rgba(0, 0, 0, 0.03); 116 96 border-radius: 5px; 117 97 } 118 98 ··· 142 122 margin-bottom: 1rem; 143 123 } 144 124 145 - th, 146 - td { 125 + th, td { 147 126 border: 1px solid rgba(0, 0, 0, 0.1); 148 127 padding: 0.5rem; 149 128 text-align: left;
+2 -2
alloy/entry/Features/Allocator.html
··· 4 4 <meta charset="utf-8"> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 6 <title>Allocator</title> 7 - <link rel="stylesheet" href="../..//css/base.css"> 8 - <link rel="stylesheet" href="../..//css/syntax.css"> 7 + <link rel="stylesheet" href="../../css/base.css"> 8 + <link rel="stylesheet" href="../../css/syntax.css"> 9 9 </head> 10 10 <body> 11 11 </body>
+2 -2
alloy/entry/Features/Effects.html
··· 4 4 <meta charset="utf-8"> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 6 <title>Effects</title> 7 - <link rel="stylesheet" href="../..//css/base.css"> 8 - <link rel="stylesheet" href="../..//css/syntax.css"> 7 + <link rel="stylesheet" href="../../css/base.css"> 8 + <link rel="stylesheet" href="../../css/syntax.css"> 9 9 </head> 10 10 <body> 11 11 <h2>Overview</h2>
+2 -2
alloy/entry/Features/Traits.html
··· 4 4 <meta charset="utf-8"> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 6 <title>Traits</title> 7 - <link rel="stylesheet" href="../..//css/base.css"> 8 - <link rel="stylesheet" href="../..//css/syntax.css"> 7 + <link rel="stylesheet" href="../../css/base.css"> 8 + <link rel="stylesheet" href="../../css/syntax.css"> 9 9 </head> 10 10 <body> 11 11 <h1>Trait System Guide</h1>
+2 -2
alloy/entry/Language Overview.html
··· 4 4 <meta charset="utf-8"> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 6 <title>Language Overview</title> 7 - <link rel="stylesheet" href="..//css/base.css"> 8 - <link rel="stylesheet" href="..//css/syntax.css"> 7 + <link rel="stylesheet" href="../css/base.css"> 8 + <link rel="stylesheet" href="../css/syntax.css"> 9 9 </head> 10 10 <body> 11 11 <p><code>.ac</code> file extension</p>
+7 -7
alloy/entry/README.md.html
··· 4 4 <meta charset="utf-8"> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 6 <title>README.md</title> 7 - <link rel="stylesheet" href="..//css/base.css"> 8 - <link rel="stylesheet" href="..//css/syntax.css"> 7 + <link rel="stylesheet" href="../css/base.css"> 8 + <link rel="stylesheet" href="../css/syntax.css"> 9 9 </head> 10 10 <body> 11 11 <p><img src="Logo.svg" alt="Logo.svg" /></p> ··· 42 42 </span></code></pre> 43 43 <h2>Documentation</h2> 44 44 <ul> 45 - <li><a href="./Language%20Overview">Language Overview</a></li> 46 - <li><a href="./Syntax%20Guide">Syntax Guide</a></li> 47 - <li><a href="./Traits">Traits</a></li> 48 - <li><a href="./Effects">Effects</a></li> 45 + <li><a href="./Language%20Overview.html">Language Overview</a></li> 46 + <li><a href="./Syntax%20Guide.html">Syntax Guide</a></li> 47 + <li><a href="./Traits.html">Traits</a></li> 48 + <li><a href="./Effects.html">Effects</a></li> 49 49 </ul> 50 50 <h2>Status</h2> 51 51 <h2>Contributing</h2> 52 52 <h2>License</h2> 53 53 <p>Alloy is licensed under the MPL-2.0.</p> 54 - <p><a href="./licenses/MPL-2.0"><img src="https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg" alt="License: MPL 2.0" /></a></p> 54 + <p><a href="./licenses/MPL-2.html"><img src="https://img.shields.io/badge/License-MPL_2.0-brightgreen.svg" alt="License: MPL 2.0" /></a></p> 55 55 </body> 56 56 </html>
+2 -2
alloy/entry/Syntax Guide.html
··· 4 4 <meta charset="utf-8"> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 6 <title>Syntax Guide</title> 7 - <link rel="stylesheet" href="..//css/base.css"> 8 - <link rel="stylesheet" href="..//css/syntax.css"> 7 + <link rel="stylesheet" href="../css/base.css"> 8 + <link rel="stylesheet" href="../css/syntax.css"> 9 9 </head> 10 10 <body> 11 11 <h2>Core Principles</h2>
+2 -2
alloy/entry/Untitled.html
··· 4 4 <meta charset="utf-8"> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 6 <title>Untitled</title> 7 - <link rel="stylesheet" href="..//css/base.css"> 8 - <link rel="stylesheet" href="..//css/syntax.css"> 7 + <link rel="stylesheet" href="../css/base.css"> 8 + <link rel="stylesheet" href="../css/syntax.css"> 9 9 </head> 10 10 <body> 11 11 <pre><code class="language-Rust"><span class="wvrcode-source wvrcode-rust">
+2 -2
alloy/entry/todo!.html
··· 4 4 <meta charset="utf-8"> 5 5 <meta name="viewport" content="width=device-width, initial-scale=1"> 6 6 <title>todo!</title> 7 - <link rel="stylesheet" href="..//css/base.css"> 8 - <link rel="stylesheet" href="..//css/syntax.css"> 7 + <link rel="stylesheet" href="../css/base.css"> 8 + <link rel="stylesheet" href="../css/syntax.css"> 9 9 </head> 10 10 <body> 11 11 <hr />
+1 -1
crates/weaver-renderer/src/snapshots/weaver_renderer__static_site__tests__blockquote_rendering.snap crates/weaver-renderer/src/static_site/snapshots/weaver_renderer__static_site__tests__blockquote_rendering.snap
··· 1 1 --- 2 - source: crates/weaver-renderer/src/static_site.rs 2 + source: crates/weaver-renderer/src/static_site/tests.rs 3 3 expression: output 4 4 --- 5 5 <blockquote>
+1 -1
crates/weaver-renderer/src/snapshots/weaver_renderer__static_site__tests__code_block_rendering.snap crates/weaver-renderer/src/static_site/snapshots/weaver_renderer__static_site__tests__code_block_rendering.snap
··· 1 1 --- 2 - source: crates/weaver-renderer/src/static_site.rs 2 + source: crates/weaver-renderer/src/static_site/tests.rs 3 3 expression: output 4 4 --- 5 5 <pre><code class="language-Rust"><span class="wvrcode-source wvrcode-rust"><span class="wvrcode-meta wvrcode-function wvrcode-rust"><span class="wvrcode-meta wvrcode-function wvrcode-rust"><span class="wvrcode-storage wvrcode-type wvrcode-function wvrcode-rust">fn</span> </span><span class="wvrcode-entity wvrcode-name wvrcode-function wvrcode-rust">main</span></span><span class="wvrcode-meta wvrcode-function wvrcode-rust"><span class="wvrcode-meta wvrcode-function wvrcode-parameters wvrcode-rust"><span class="wvrcode-punctuation wvrcode-section wvrcode-parameters wvrcode-begin wvrcode-rust">(</span></span><span class="wvrcode-meta wvrcode-function wvrcode-rust"><span class="wvrcode-meta wvrcode-function wvrcode-parameters wvrcode-rust"><span class="wvrcode-punctuation wvrcode-section wvrcode-parameters wvrcode-end wvrcode-rust">)</span></span></span></span><span class="wvrcode-meta wvrcode-function wvrcode-rust"> </span><span class="wvrcode-meta wvrcode-function wvrcode-rust"><span class="wvrcode-meta wvrcode-block wvrcode-rust"><span class="wvrcode-punctuation wvrcode-section wvrcode-block wvrcode-begin wvrcode-rust">{</span>
+1 -1
crates/weaver-renderer/src/snapshots/weaver_renderer__static_site__tests__heading_rendering.snap crates/weaver-renderer/src/static_site/snapshots/weaver_renderer__static_site__tests__heading_rendering.snap
··· 1 1 --- 2 - source: crates/weaver-renderer/src/static_site.rs 2 + source: crates/weaver-renderer/src/static_site/tests.rs 3 3 expression: output 4 4 --- 5 5 <h1>Heading 1</h1>
+1 -1
crates/weaver-renderer/src/snapshots/weaver_renderer__static_site__tests__list_rendering.snap crates/weaver-renderer/src/static_site/snapshots/weaver_renderer__static_site__tests__list_rendering.snap
··· 1 1 --- 2 - source: crates/weaver-renderer/src/static_site.rs 2 + source: crates/weaver-renderer/src/static_site/tests.rs 3 3 expression: output 4 4 --- 5 5 <ul>
+1 -1
crates/weaver-renderer/src/snapshots/weaver_renderer__static_site__tests__math_rendering.snap crates/weaver-renderer/src/static_site/snapshots/weaver_renderer__static_site__tests__math_rendering.snap
··· 1 1 --- 2 - source: crates/weaver-renderer/src/static_site.rs 2 + source: crates/weaver-renderer/src/static_site/tests.rs 3 3 expression: output 4 4 --- 5 5 <p>Inline <span class="math math-inline">x^2</span> and display:</p>
+1 -1
crates/weaver-renderer/src/snapshots/weaver_renderer__static_site__tests__paragraph_rendering.snap crates/weaver-renderer/src/static_site/snapshots/weaver_renderer__static_site__tests__paragraph_rendering.snap
··· 1 1 --- 2 - source: crates/weaver-renderer/src/static_site.rs 2 + source: crates/weaver-renderer/src/static_site/tests.rs 3 3 expression: output 4 4 --- 5 5 <p>This is a paragraph.</p>
+1 -1
crates/weaver-renderer/src/snapshots/weaver_renderer__static_site__tests__table_rendering.snap crates/weaver-renderer/src/static_site/snapshots/weaver_renderer__static_site__tests__table_rendering.snap
··· 1 1 --- 2 - source: crates/weaver-renderer/src/static_site.rs 2 + source: crates/weaver-renderer/src/static_site/tests.rs 3 3 expression: output 4 4 --- 5 5 <table><thead><tr><th style="text-align: left">Left</th><th style="text-align: center">Center</th><th style="text-align: right">Right</th></tr></thead><tbody>
+6 -215
crates/weaver-renderer/src/static_site.rs
··· 171 171 .to_path_buf(); 172 172 173 173 // Check if this is a markdown file 174 - let is_markdown = file.extension() 174 + let is_markdown = file 175 + .extension() 175 176 .and_then(|ext| ext.to_str()) 176 177 .map(|ext| ext == "md" || ext == "markdown") 177 178 .unwrap_or(false); ··· 198 199 tokio::fs::create_dir_all(parent).await.into_diagnostic()?; 199 200 } 200 201 201 - tokio::fs::copy(&file, &output_path).await.into_diagnostic()?; 202 + tokio::fs::copy(&file, &output_path) 203 + .await 204 + .into_diagnostic()?; 202 205 return Ok(()); 203 206 } 204 207 ··· 501 504 } 502 505 503 506 #[cfg(test)] 504 - mod tests { 505 - use crate::NotebookContext; 506 - 507 - use super::*; 508 - use std::path::PathBuf; 509 - use weaver_common::jacquard::client::{ 510 - AtpSession, MemorySessionStore, 511 - credential_session::{CredentialSession, SessionKey}, 512 - }; 513 - 514 - /// Type alias for the session used in tests 515 - type TestSession = CredentialSession< 516 - MemorySessionStore<SessionKey, AtpSession>, 517 - weaver_common::jacquard::identity::JacquardResolver, 518 - >; 519 - 520 - /// Helper: Create test context without network capabilities 521 - fn test_context() -> StaticSiteContext<TestSession> { 522 - let root = PathBuf::from("/tmp/test"); 523 - let destination = PathBuf::from("/tmp/output"); 524 - let mut ctx = StaticSiteContext::new(root, destination, None); 525 - ctx.client = None; // Explicitly disable network 526 - ctx 527 - } 528 - 529 - /// Helper: Render markdown to HTML using test context 530 - async fn render_markdown(input: &str) -> String { 531 - let context = test_context(); 532 - export_page(input, context).await.unwrap() 533 - } 534 - 535 - #[tokio::test] 536 - async fn test_smoke() { 537 - let output = render_markdown("Hello world").await; 538 - assert!(output.contains("Hello world")); 539 - } 540 - 541 - #[tokio::test] 542 - async fn test_paragraph_rendering() { 543 - let input = "This is a paragraph.\n\nThis is another paragraph."; 544 - let output = render_markdown(input).await; 545 - insta::assert_snapshot!(output); 546 - } 547 - 548 - #[tokio::test] 549 - async fn test_heading_rendering() { 550 - let input = "# Heading 1\n\n## Heading 2\n\n### Heading 3"; 551 - let output = render_markdown(input).await; 552 - insta::assert_snapshot!(output); 553 - } 554 - 555 - #[tokio::test] 556 - async fn test_list_rendering() { 557 - let input = "- Item 1\n- Item 2\n - Nested\n\n1. Ordered 1\n2. Ordered 2"; 558 - let output = render_markdown(input).await; 559 - insta::assert_snapshot!(output); 560 - } 561 - 562 - #[tokio::test] 563 - async fn test_code_block_rendering() { 564 - let input = "```rust\nfn main() {\n println!(\"Hello\");\n}\n```"; 565 - let output = render_markdown(input).await; 566 - insta::assert_snapshot!(output); 567 - } 568 - 569 - #[tokio::test] 570 - async fn test_table_rendering() { 571 - let input = "| Left | Center | Right |\n|:-----|:------:|------:|\n| A | B | C |"; 572 - let output = render_markdown(input).await; 573 - insta::assert_snapshot!(output); 574 - } 575 - 576 - #[tokio::test] 577 - async fn test_blockquote_rendering() { 578 - let input = "> This is a quote\n>\n> With multiple lines"; 579 - let output = render_markdown(input).await; 580 - insta::assert_snapshot!(output); 581 - } 582 - 583 - #[tokio::test] 584 - async fn test_math_rendering() { 585 - let input = "Inline $x^2$ and display:\n\n$$\ny = mx + b\n$$"; 586 - let output = render_markdown(input).await; 587 - insta::assert_snapshot!(output); 588 - } 589 - 590 - #[tokio::test] 591 - async fn test_wikilink_resolution() { 592 - let vault_contents = vec![ 593 - PathBuf::from("notes/First Note.md"), 594 - PathBuf::from("notes/Second Note.md"), 595 - ]; 596 - 597 - let mut context = test_context(); 598 - context.dir_contents = Some(vault_contents.into()); 599 - 600 - let input = "[[First Note]] and [[Second Note]]"; 601 - let output = export_page(input, context).await.unwrap(); 602 - println!("{output}"); 603 - assert!(output.contains("./First%20Note")); 604 - assert!(output.contains("./Second%20Note")); 605 - } 606 - 607 - #[tokio::test] 608 - async fn test_broken_wikilink() { 609 - let vault_contents = vec![PathBuf::from("notes/Exists.md")]; 610 - 611 - let mut context = test_context(); 612 - context.dir_contents = Some(vault_contents.into()); 613 - 614 - let input = "[[Does Not Exist]]"; 615 - let output = export_page(input, context).await.unwrap(); 616 - 617 - // Broken wikilinks become links (they just don't point anywhere valid) 618 - // This is acceptable - static site will show 404 on click 619 - assert!(output.contains("<a href=")); 620 - assert!(output.contains("Does Not Exist</a>") || output.contains("Does%20Not%20Exist")); 621 - } 622 - 623 - #[tokio::test] 624 - async fn test_wikilink_with_section() { 625 - let vault_contents = vec![PathBuf::from("Note.md")]; 626 - 627 - let mut context = test_context(); 628 - context.dir_contents = Some(vault_contents.into()); 629 - 630 - let input = "[[Note#Section]]"; 631 - let output = export_page(input, context).await.unwrap(); 632 - println!("{output}"); 633 - assert!(output.contains("Note#Section")); 634 - } 635 - 636 - #[tokio::test] 637 - async fn test_link_flattening_enabled() { 638 - let mut context = test_context(); 639 - context.options = StaticSiteOptions::FLATTEN_STRUCTURE; 640 - 641 - let input = "[Link](path/to/nested/file.md)"; 642 - let output = export_page(input, context).await.unwrap(); 643 - println!("{output}"); 644 - // Should flatten to single parent directory 645 - assert!(output.contains("./entry/file.md")); 646 - } 647 - 648 - #[tokio::test] 649 - async fn test_link_flattening_disabled() { 650 - let mut context = test_context(); 651 - context.options = StaticSiteOptions::empty(); 652 - 653 - let input = "[Link](path/to/nested/file.md)"; 654 - let output = export_page(input, context).await.unwrap(); 655 - println!("{output}"); 656 - // Should preserve original path 657 - assert!(output.contains("path/to/nested/file.md")); 658 - } 659 - 660 - #[tokio::test] 661 - async fn test_frontmatter_parsing() { 662 - let input = "---\ntitle: Test Page\nauthor: Test Author\n---\n\nContent here"; 663 - let context = test_context(); 664 - let output = export_page(input, context.clone()).await.unwrap(); 665 - 666 - // Frontmatter should be parsed but not rendered 667 - assert!(!output.contains("title: Test Page")); 668 - assert!(output.contains("Content here")); 669 - 670 - // Verify frontmatter was captured 671 - let frontmatter = context.frontmatter(); 672 - let yaml = frontmatter.contents(); 673 - let yaml_guard = yaml.read().unwrap(); 674 - assert!(yaml_guard.len() > 0); 675 - } 676 - 677 - #[tokio::test] 678 - async fn test_empty_frontmatter() { 679 - let input = "---\n---\n\nContent"; 680 - let output = render_markdown(input).await; 681 - 682 - assert!(output.contains("Content")); 683 - assert!(!output.contains("---")); 684 - } 685 - 686 - #[tokio::test] 687 - async fn test_empty_input() { 688 - let output = render_markdown("").await; 689 - assert_eq!(output, ""); 690 - } 691 - 692 - #[tokio::test] 693 - async fn test_html_and_special_characters() { 694 - // Test that markdown correctly handles HTML and special chars per CommonMark spec 695 - let input = "Text with <special> & some text. Valid tags: <em>emphasis</em> and <strong>bold</strong>"; 696 - let output = render_markdown(input).await; 697 - 698 - // & must be escaped for valid HTML 699 - assert!(output.contains("&amp;")); 700 - 701 - // Inline HTML tags pass through (CommonMark behavior) 702 - assert!(output.contains("<special>")); 703 - assert!(output.contains("<em>emphasis</em>")); 704 - assert!(output.contains("<strong>bold</strong>")); 705 - } 706 - 707 - #[tokio::test] 708 - async fn test_unicode_content() { 709 - let input = "Unicode: 你好 🎉 café"; 710 - let output = render_markdown(input).await; 711 - 712 - assert!(output.contains("你好")); 713 - assert!(output.contains("🎉")); 714 - assert!(output.contains("café")); 715 - } 716 - } 507 + mod tests;
+22 -8
crates/weaver-renderer/src/static_site/context.rs
··· 392 392 Tag::Link { link_type, dest_url, title, id } => { 393 393 if self.options.contains(StaticSiteOptions::FLATTEN_STRUCTURE) { 394 394 let (parent, filename) = crate::utils::flatten_dir_to_just_one_parent(&dest_url); 395 - let dest_url = if crate::utils::is_relative_link(&dest_url) 396 - && self.options.contains(StaticSiteOptions::CREATE_CHAPTERS_BY_DIRECTORY) { 397 - if !parent.is_empty() { 398 - CowStr::Boxed(format!("./{}/{}", parent, filename).into_boxed_str()) 395 + let dest_url = if crate::utils::is_local_path(&dest_url) { 396 + let filename = PathBuf::from(filename).with_extension("html"); 397 + if crate::utils::is_relative_link(&dest_url) 398 + && self.options.contains(StaticSiteOptions::CREATE_CHAPTERS_BY_DIRECTORY) { 399 + if !parent.is_empty() { 400 + CowStr::Boxed(format!("./{}/{}", parent, filename.display()).into_boxed_str()) 401 + } else { 402 + CowStr::Boxed(format!("./{}", filename.display()).into_boxed_str()) 403 + } 399 404 } else { 400 - CowStr::Boxed(format!("./{}", filename).into_boxed_str()) 405 + CowStr::Boxed(format!("./entry/{}", filename.display()).into_boxed_str()) 401 406 } 402 407 } else { 403 - CowStr::Boxed(format!("./entry/{}", filename).into_boxed_str()) 408 + dest_url.clone() 404 409 }; 405 410 Tag::Link { 406 411 link_type: *link_type, ··· 409 414 id: id.clone(), 410 415 } 411 416 } else { 412 - link 413 - 417 + if crate::utils::is_local_path(&dest_url) { 418 + let filename = PathBuf::from(dest_url.as_ref() as &str).with_extension("html"); 419 + Tag::Link { 420 + link_type: *link_type, 421 + dest_url: CowStr::Boxed(filename.to_string_lossy().into()), 422 + title: title.clone(), 423 + id: id.clone(), 424 + } 425 + } else { 426 + link 427 + } 414 428 } 415 429 }, 416 430 _ => link,
+17 -5
crates/weaver-renderer/src/static_site/document.rs
··· 41 41 // Calculate relative path to root based on output file depth 42 42 let relative_to_root = if let Ok(rel) = output_path.strip_prefix(&context.destination) { 43 43 let depth = rel.components().count() - 1; // -1 because the file itself doesn't count 44 - if depth == 0 { 45 - ".".to_string() 44 + if depth <= 0 { 45 + "./".to_string() 46 46 } else { 47 47 "../".repeat(depth) 48 48 } 49 49 } else { 50 - ".".to_string() 50 + "./".to_string() 51 51 }; 52 52 53 53 writer ··· 77 77 match css_mode { 78 78 CssMode::Linked => { 79 79 writer 80 - .write_all(format!(" <link rel=\"stylesheet\" href=\"{}/css/base.css\">\n", relative_to_root).as_bytes()) 80 + .write_all( 81 + format!( 82 + " <link rel=\"stylesheet\" href=\"{}css/base.css\">\n", 83 + relative_to_root 84 + ) 85 + .as_bytes(), 86 + ) 81 87 .await 82 88 .into_diagnostic()?; 83 89 writer 84 - .write_all(format!(" <link rel=\"stylesheet\" href=\"{}/css/syntax.css\">\n", relative_to_root).as_bytes()) 90 + .write_all( 91 + format!( 92 + " <link rel=\"stylesheet\" href=\"{}css/syntax.css\">\n", 93 + relative_to_root 94 + ) 95 + .as_bytes(), 96 + ) 85 97 .await 86 98 .into_diagnostic()?; 87 99 }
+212
crates/weaver-renderer/src/static_site/tests.rs
··· 1 + use crate::NotebookContext; 2 + 3 + use super::*; 4 + use std::path::PathBuf; 5 + use weaver_common::jacquard::client::{ 6 + AtpSession, MemorySessionStore, 7 + credential_session::{CredentialSession, SessionKey}, 8 + }; 9 + 10 + /// Type alias for the session used in tests 11 + type TestSession = CredentialSession< 12 + MemorySessionStore<SessionKey, AtpSession>, 13 + weaver_common::jacquard::identity::JacquardResolver, 14 + >; 15 + 16 + /// Helper: Create test context without network capabilities 17 + fn 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 26 + async fn render_markdown(input: &str) -> String { 27 + let context = test_context(); 28 + export_page(input, context).await.unwrap() 29 + } 30 + 31 + #[tokio::test] 32 + async fn test_smoke() { 33 + let output = render_markdown("Hello world").await; 34 + assert!(output.contains("Hello world")); 35 + } 36 + 37 + #[tokio::test] 38 + async 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] 45 + async 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] 52 + async 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] 59 + async 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] 66 + async 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] 73 + async 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] 80 + async 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] 87 + async 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] 104 + async 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] 120 + async 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] 133 + async 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] 145 + async 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] 157 + async 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] 174 + async 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] 183 + async fn test_empty_input() { 184 + let output = render_markdown("").await; 185 + assert_eq!(output, ""); 186 + } 187 + 188 + #[tokio::test] 189 + async 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] 205 + async 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 + }