tangled
alpha
login
or
join now
nonbinary.computer
/
weaver
atproto blogging
24
fork
atom
overview
issues
2
pulls
pipelines
moving tests around
Orual
3 months ago
2c0b32a3
d6592bef
+311
-302
22 changed files
expand all
collapse all
unified
split
.gitignore
Cargo.lock
alloy
css
base.css
entry
Features
Allocator.html
Effects.html
Traits.html
Language Overview.html
README.md.html
Syntax Guide.html
Untitled.html
todo!.html
crates
weaver-renderer
src
static_site
context.rs
document.rs
snapshots
weaver_renderer__static_site__tests__blockquote_rendering.snap
weaver_renderer__static_site__tests__code_block_rendering.snap
weaver_renderer__static_site__tests__heading_rendering.snap
weaver_renderer__static_site__tests__list_rendering.snap
weaver_renderer__static_site__tests__math_rendering.snap
weaver_renderer__static_site__tests__paragraph_rendering.snap
weaver_renderer__static_site__tests__table_rendering.snap
tests.rs
static_site.rs
+1
.gitignore
···
4
4
/dist
5
5
/node_modules
6
6
/plans
7
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
2146
-
version = "0.4.24"
2146
2146
+
version = "0.4.25"
2147
2147
source = "registry+https://github.com/rust-lang/crates.io-index"
2148
2148
-
checksum = "81776e6f9464432afcc28d03e52eb101c93b6f0566f52aef2427663e700f0403"
2148
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
2786
-
source = "git+https://github.com/rsform/markdown-weaver#83441068a9494f569218c64026880199b41a6e06"
2786
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
2799
-
source = "git+https://github.com/rsform/markdown-weaver#83441068a9494f569218c64026880199b41a6e06"
2799
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
5441
-
version = "1.0.20"
5441
5441
+
version = "1.0.22"
5442
5442
source = "registry+https://github.com/rust-lang/crates.io-index"
5443
5443
-
checksum = "462eeb75aeb73aea900253ce739c8e18a67423fadf006037cd3ff27e82748a06"
5443
5443
+
checksum = "9312f7c4f6ff9069b165498234ce8be658059c6728633667c526e27dc2cf1df5"
5444
5444
5445
5445
[[package]]
5446
5446
name = "unicode-linebreak"
···
5450
5450
5451
5451
[[package]]
5452
5452
name = "unicode-normalization"
5453
5453
-
version = "0.1.24"
5453
5453
+
version = "0.1.25"
5454
5454
source = "registry+https://github.com/rust-lang/crates.io-index"
5455
5455
-
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
5455
5455
+
checksum = "5fd4f6878c9cb28d874b009da9e8d183b5abc80117c40bbd187a1fde336be6e8"
5456
5456
dependencies = [
5457
5457
"tinyvec",
5458
5458
]
5459
5459
5460
5460
[[package]]
5461
5461
name = "unicode-properties"
5462
5462
-
version = "0.1.3"
5462
5462
+
version = "0.1.4"
5463
5463
source = "registry+https://github.com/rust-lang/crates.io-index"
5464
5464
-
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
5464
5464
+
checksum = "7df058c713841ad818f1dc5d3fd88063241cc61f49f5fbea4b951e8cf5a8d71d"
5465
5465
5466
5466
[[package]]
5467
5467
name = "unicode-segmentation"
+15
-36
alloy/css/base.css
···
1
1
/* CSS Reset */
2
2
-
*,
3
3
-
*::before,
4
4
-
*::after {
2
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
17
-
--font-body: IBM Plex, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
18
18
-
--font-heading: IBM Plex Sans, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
19
19
-
--font-mono: "IBM Plex Mono", "Berkeley Mono", "Cascadia Code", "Roboto Mono", Consolas, monospace;
15
15
+
--font-body: IBM Plex, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
16
16
+
--font-heading: IBM Plex Sans, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
17
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
42
-
h1,
43
43
-
h2,
44
44
-
h3,
45
45
-
h4,
46
46
-
h5,
47
47
-
h6 {
40
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
54
-
h1 {
55
55
-
font-size: 2.5rem;
56
56
-
}
57
57
-
h2 {
58
58
-
font-size: 2rem;
59
59
-
}
60
60
-
h3 {
61
61
-
font-size: 1.5rem;
62
62
-
}
63
63
-
h4 {
64
64
-
font-size: 1.25rem;
65
65
-
}
66
66
-
h5 {
67
67
-
font-size: 1.125rem;
68
68
-
}
69
69
-
h6 {
70
70
-
font-size: 1rem;
71
71
-
}
47
47
+
h1 { font-size: 2.5rem; }
48
48
+
h2 { font-size: 2rem; }
49
49
+
h3 { font-size: 1.5rem; }
50
50
+
h4 { font-size: 1.25rem; }
51
51
+
h5 { font-size: 1.125rem; }
52
52
+
h6 { font-size: 1rem; }
72
53
73
54
p {
74
55
margin-bottom: 1rem;
···
85
66
}
86
67
87
68
/* Lists */
88
88
-
ul,
89
89
-
ol {
69
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
101
-
background-color: rgba(0, 0, 0, 0.03);
81
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
115
-
background-color: rgba(0, 0, 0, 0.02);
95
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
145
-
th,
146
146
-
td {
125
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
7
-
<link rel="stylesheet" href="../..//css/base.css">
8
8
-
<link rel="stylesheet" href="../..//css/syntax.css">
7
7
+
<link rel="stylesheet" href="../../css/base.css">
8
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
7
-
<link rel="stylesheet" href="../..//css/base.css">
8
8
-
<link rel="stylesheet" href="../..//css/syntax.css">
7
7
+
<link rel="stylesheet" href="../../css/base.css">
8
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
7
-
<link rel="stylesheet" href="../..//css/base.css">
8
8
-
<link rel="stylesheet" href="../..//css/syntax.css">
7
7
+
<link rel="stylesheet" href="../../css/base.css">
8
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
7
-
<link rel="stylesheet" href="..//css/base.css">
8
8
-
<link rel="stylesheet" href="..//css/syntax.css">
7
7
+
<link rel="stylesheet" href="../css/base.css">
8
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
7
-
<link rel="stylesheet" href="..//css/base.css">
8
8
-
<link rel="stylesheet" href="..//css/syntax.css">
7
7
+
<link rel="stylesheet" href="../css/base.css">
8
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
45
-
<li><a href="./Language%20Overview">Language Overview</a></li>
46
46
-
<li><a href="./Syntax%20Guide">Syntax Guide</a></li>
47
47
-
<li><a href="./Traits">Traits</a></li>
48
48
-
<li><a href="./Effects">Effects</a></li>
45
45
+
<li><a href="./Language%20Overview.html">Language Overview</a></li>
46
46
+
<li><a href="./Syntax%20Guide.html">Syntax Guide</a></li>
47
47
+
<li><a href="./Traits.html">Traits</a></li>
48
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
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
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
7
-
<link rel="stylesheet" href="..//css/base.css">
8
8
-
<link rel="stylesheet" href="..//css/syntax.css">
7
7
+
<link rel="stylesheet" href="../css/base.css">
8
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
7
-
<link rel="stylesheet" href="..//css/base.css">
8
8
-
<link rel="stylesheet" href="..//css/syntax.css">
7
7
+
<link rel="stylesheet" href="../css/base.css">
8
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
7
-
<link rel="stylesheet" href="..//css/base.css">
8
8
-
<link rel="stylesheet" href="..//css/syntax.css">
7
7
+
<link rel="stylesheet" href="../css/base.css">
8
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
2
-
source: crates/weaver-renderer/src/static_site.rs
2
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
2
-
source: crates/weaver-renderer/src/static_site.rs
2
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
2
-
source: crates/weaver-renderer/src/static_site.rs
2
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
2
-
source: crates/weaver-renderer/src/static_site.rs
2
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
2
-
source: crates/weaver-renderer/src/static_site.rs
2
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
2
-
source: crates/weaver-renderer/src/static_site.rs
2
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
2
-
source: crates/weaver-renderer/src/static_site.rs
2
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
174
-
let is_markdown = file.extension()
174
174
+
let is_markdown = file
175
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
201
-
tokio::fs::copy(&file, &output_path).await.into_diagnostic()?;
202
202
+
tokio::fs::copy(&file, &output_path)
203
203
+
.await
204
204
+
.into_diagnostic()?;
202
205
return Ok(());
203
206
}
204
207
···
501
504
}
502
505
503
506
#[cfg(test)]
504
504
-
mod tests {
505
505
-
use crate::NotebookContext;
506
506
-
507
507
-
use super::*;
508
508
-
use std::path::PathBuf;
509
509
-
use weaver_common::jacquard::client::{
510
510
-
AtpSession, MemorySessionStore,
511
511
-
credential_session::{CredentialSession, SessionKey},
512
512
-
};
513
513
-
514
514
-
/// Type alias for the session used in tests
515
515
-
type TestSession = CredentialSession<
516
516
-
MemorySessionStore<SessionKey, AtpSession>,
517
517
-
weaver_common::jacquard::identity::JacquardResolver,
518
518
-
>;
519
519
-
520
520
-
/// Helper: Create test context without network capabilities
521
521
-
fn test_context() -> StaticSiteContext<TestSession> {
522
522
-
let root = PathBuf::from("/tmp/test");
523
523
-
let destination = PathBuf::from("/tmp/output");
524
524
-
let mut ctx = StaticSiteContext::new(root, destination, None);
525
525
-
ctx.client = None; // Explicitly disable network
526
526
-
ctx
527
527
-
}
528
528
-
529
529
-
/// Helper: Render markdown to HTML using test context
530
530
-
async fn render_markdown(input: &str) -> String {
531
531
-
let context = test_context();
532
532
-
export_page(input, context).await.unwrap()
533
533
-
}
534
534
-
535
535
-
#[tokio::test]
536
536
-
async fn test_smoke() {
537
537
-
let output = render_markdown("Hello world").await;
538
538
-
assert!(output.contains("Hello world"));
539
539
-
}
540
540
-
541
541
-
#[tokio::test]
542
542
-
async fn test_paragraph_rendering() {
543
543
-
let input = "This is a paragraph.\n\nThis is another paragraph.";
544
544
-
let output = render_markdown(input).await;
545
545
-
insta::assert_snapshot!(output);
546
546
-
}
547
547
-
548
548
-
#[tokio::test]
549
549
-
async fn test_heading_rendering() {
550
550
-
let input = "# Heading 1\n\n## Heading 2\n\n### Heading 3";
551
551
-
let output = render_markdown(input).await;
552
552
-
insta::assert_snapshot!(output);
553
553
-
}
554
554
-
555
555
-
#[tokio::test]
556
556
-
async fn test_list_rendering() {
557
557
-
let input = "- Item 1\n- Item 2\n - Nested\n\n1. Ordered 1\n2. Ordered 2";
558
558
-
let output = render_markdown(input).await;
559
559
-
insta::assert_snapshot!(output);
560
560
-
}
561
561
-
562
562
-
#[tokio::test]
563
563
-
async fn test_code_block_rendering() {
564
564
-
let input = "```rust\nfn main() {\n println!(\"Hello\");\n}\n```";
565
565
-
let output = render_markdown(input).await;
566
566
-
insta::assert_snapshot!(output);
567
567
-
}
568
568
-
569
569
-
#[tokio::test]
570
570
-
async fn test_table_rendering() {
571
571
-
let input = "| Left | Center | Right |\n|:-----|:------:|------:|\n| A | B | C |";
572
572
-
let output = render_markdown(input).await;
573
573
-
insta::assert_snapshot!(output);
574
574
-
}
575
575
-
576
576
-
#[tokio::test]
577
577
-
async fn test_blockquote_rendering() {
578
578
-
let input = "> This is a quote\n>\n> With multiple lines";
579
579
-
let output = render_markdown(input).await;
580
580
-
insta::assert_snapshot!(output);
581
581
-
}
582
582
-
583
583
-
#[tokio::test]
584
584
-
async fn test_math_rendering() {
585
585
-
let input = "Inline $x^2$ and display:\n\n$$\ny = mx + b\n$$";
586
586
-
let output = render_markdown(input).await;
587
587
-
insta::assert_snapshot!(output);
588
588
-
}
589
589
-
590
590
-
#[tokio::test]
591
591
-
async fn test_wikilink_resolution() {
592
592
-
let vault_contents = vec![
593
593
-
PathBuf::from("notes/First Note.md"),
594
594
-
PathBuf::from("notes/Second Note.md"),
595
595
-
];
596
596
-
597
597
-
let mut context = test_context();
598
598
-
context.dir_contents = Some(vault_contents.into());
599
599
-
600
600
-
let input = "[[First Note]] and [[Second Note]]";
601
601
-
let output = export_page(input, context).await.unwrap();
602
602
-
println!("{output}");
603
603
-
assert!(output.contains("./First%20Note"));
604
604
-
assert!(output.contains("./Second%20Note"));
605
605
-
}
606
606
-
607
607
-
#[tokio::test]
608
608
-
async fn test_broken_wikilink() {
609
609
-
let vault_contents = vec![PathBuf::from("notes/Exists.md")];
610
610
-
611
611
-
let mut context = test_context();
612
612
-
context.dir_contents = Some(vault_contents.into());
613
613
-
614
614
-
let input = "[[Does Not Exist]]";
615
615
-
let output = export_page(input, context).await.unwrap();
616
616
-
617
617
-
// Broken wikilinks become links (they just don't point anywhere valid)
618
618
-
// This is acceptable - static site will show 404 on click
619
619
-
assert!(output.contains("<a href="));
620
620
-
assert!(output.contains("Does Not Exist</a>") || output.contains("Does%20Not%20Exist"));
621
621
-
}
622
622
-
623
623
-
#[tokio::test]
624
624
-
async fn test_wikilink_with_section() {
625
625
-
let vault_contents = vec![PathBuf::from("Note.md")];
626
626
-
627
627
-
let mut context = test_context();
628
628
-
context.dir_contents = Some(vault_contents.into());
629
629
-
630
630
-
let input = "[[Note#Section]]";
631
631
-
let output = export_page(input, context).await.unwrap();
632
632
-
println!("{output}");
633
633
-
assert!(output.contains("Note#Section"));
634
634
-
}
635
635
-
636
636
-
#[tokio::test]
637
637
-
async fn test_link_flattening_enabled() {
638
638
-
let mut context = test_context();
639
639
-
context.options = StaticSiteOptions::FLATTEN_STRUCTURE;
640
640
-
641
641
-
let input = "[Link](path/to/nested/file.md)";
642
642
-
let output = export_page(input, context).await.unwrap();
643
643
-
println!("{output}");
644
644
-
// Should flatten to single parent directory
645
645
-
assert!(output.contains("./entry/file.md"));
646
646
-
}
647
647
-
648
648
-
#[tokio::test]
649
649
-
async fn test_link_flattening_disabled() {
650
650
-
let mut context = test_context();
651
651
-
context.options = StaticSiteOptions::empty();
652
652
-
653
653
-
let input = "[Link](path/to/nested/file.md)";
654
654
-
let output = export_page(input, context).await.unwrap();
655
655
-
println!("{output}");
656
656
-
// Should preserve original path
657
657
-
assert!(output.contains("path/to/nested/file.md"));
658
658
-
}
659
659
-
660
660
-
#[tokio::test]
661
661
-
async fn test_frontmatter_parsing() {
662
662
-
let input = "---\ntitle: Test Page\nauthor: Test Author\n---\n\nContent here";
663
663
-
let context = test_context();
664
664
-
let output = export_page(input, context.clone()).await.unwrap();
665
665
-
666
666
-
// Frontmatter should be parsed but not rendered
667
667
-
assert!(!output.contains("title: Test Page"));
668
668
-
assert!(output.contains("Content here"));
669
669
-
670
670
-
// Verify frontmatter was captured
671
671
-
let frontmatter = context.frontmatter();
672
672
-
let yaml = frontmatter.contents();
673
673
-
let yaml_guard = yaml.read().unwrap();
674
674
-
assert!(yaml_guard.len() > 0);
675
675
-
}
676
676
-
677
677
-
#[tokio::test]
678
678
-
async fn test_empty_frontmatter() {
679
679
-
let input = "---\n---\n\nContent";
680
680
-
let output = render_markdown(input).await;
681
681
-
682
682
-
assert!(output.contains("Content"));
683
683
-
assert!(!output.contains("---"));
684
684
-
}
685
685
-
686
686
-
#[tokio::test]
687
687
-
async fn test_empty_input() {
688
688
-
let output = render_markdown("").await;
689
689
-
assert_eq!(output, "");
690
690
-
}
691
691
-
692
692
-
#[tokio::test]
693
693
-
async fn test_html_and_special_characters() {
694
694
-
// Test that markdown correctly handles HTML and special chars per CommonMark spec
695
695
-
let input = "Text with <special> & some text. Valid tags: <em>emphasis</em> and <strong>bold</strong>";
696
696
-
let output = render_markdown(input).await;
697
697
-
698
698
-
// & must be escaped for valid HTML
699
699
-
assert!(output.contains("&"));
700
700
-
701
701
-
// Inline HTML tags pass through (CommonMark behavior)
702
702
-
assert!(output.contains("<special>"));
703
703
-
assert!(output.contains("<em>emphasis</em>"));
704
704
-
assert!(output.contains("<strong>bold</strong>"));
705
705
-
}
706
706
-
707
707
-
#[tokio::test]
708
708
-
async fn test_unicode_content() {
709
709
-
let input = "Unicode: 你好 🎉 café";
710
710
-
let output = render_markdown(input).await;
711
711
-
712
712
-
assert!(output.contains("你好"));
713
713
-
assert!(output.contains("🎉"));
714
714
-
assert!(output.contains("café"));
715
715
-
}
716
716
-
}
507
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
395
-
let dest_url = if crate::utils::is_relative_link(&dest_url)
396
396
-
&& self.options.contains(StaticSiteOptions::CREATE_CHAPTERS_BY_DIRECTORY) {
397
397
-
if !parent.is_empty() {
398
398
-
CowStr::Boxed(format!("./{}/{}", parent, filename).into_boxed_str())
395
395
+
let dest_url = if crate::utils::is_local_path(&dest_url) {
396
396
+
let filename = PathBuf::from(filename).with_extension("html");
397
397
+
if crate::utils::is_relative_link(&dest_url)
398
398
+
&& self.options.contains(StaticSiteOptions::CREATE_CHAPTERS_BY_DIRECTORY) {
399
399
+
if !parent.is_empty() {
400
400
+
CowStr::Boxed(format!("./{}/{}", parent, filename.display()).into_boxed_str())
401
401
+
} else {
402
402
+
CowStr::Boxed(format!("./{}", filename.display()).into_boxed_str())
403
403
+
}
399
404
} else {
400
400
-
CowStr::Boxed(format!("./{}", filename).into_boxed_str())
405
405
+
CowStr::Boxed(format!("./entry/{}", filename.display()).into_boxed_str())
401
406
}
402
407
} else {
403
403
-
CowStr::Boxed(format!("./entry/{}", filename).into_boxed_str())
408
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
412
-
link
413
413
-
417
417
+
if crate::utils::is_local_path(&dest_url) {
418
418
+
let filename = PathBuf::from(dest_url.as_ref() as &str).with_extension("html");
419
419
+
Tag::Link {
420
420
+
link_type: *link_type,
421
421
+
dest_url: CowStr::Boxed(filename.to_string_lossy().into()),
422
422
+
title: title.clone(),
423
423
+
id: id.clone(),
424
424
+
}
425
425
+
} else {
426
426
+
link
427
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
44
-
if depth == 0 {
45
45
-
".".to_string()
44
44
+
if depth <= 0 {
45
45
+
"./".to_string()
46
46
} else {
47
47
"../".repeat(depth)
48
48
}
49
49
} else {
50
50
-
".".to_string()
50
50
+
"./".to_string()
51
51
};
52
52
53
53
writer
···
77
77
match css_mode {
78
78
CssMode::Linked => {
79
79
writer
80
80
-
.write_all(format!(" <link rel=\"stylesheet\" href=\"{}/css/base.css\">\n", relative_to_root).as_bytes())
80
80
+
.write_all(
81
81
+
format!(
82
82
+
" <link rel=\"stylesheet\" href=\"{}css/base.css\">\n",
83
83
+
relative_to_root
84
84
+
)
85
85
+
.as_bytes(),
86
86
+
)
81
87
.await
82
88
.into_diagnostic()?;
83
89
writer
84
84
-
.write_all(format!(" <link rel=\"stylesheet\" href=\"{}/css/syntax.css\">\n", relative_to_root).as_bytes())
90
90
+
.write_all(
91
91
+
format!(
92
92
+
" <link rel=\"stylesheet\" href=\"{}css/syntax.css\">\n",
93
93
+
relative_to_root
94
94
+
)
95
95
+
.as_bytes(),
96
96
+
)
85
97
.await
86
98
.into_diagnostic()?;
87
99
}
+212
crates/weaver-renderer/src/static_site/tests.rs
···
1
1
+
use crate::NotebookContext;
2
2
+
3
3
+
use super::*;
4
4
+
use std::path::PathBuf;
5
5
+
use weaver_common::jacquard::client::{
6
6
+
AtpSession, MemorySessionStore,
7
7
+
credential_session::{CredentialSession, SessionKey},
8
8
+
};
9
9
+
10
10
+
/// Type alias for the session used in tests
11
11
+
type TestSession = CredentialSession<
12
12
+
MemorySessionStore<SessionKey, AtpSession>,
13
13
+
weaver_common::jacquard::identity::JacquardResolver,
14
14
+
>;
15
15
+
16
16
+
/// Helper: Create test context without network capabilities
17
17
+
fn test_context() -> StaticSiteContext<TestSession> {
18
18
+
let root = PathBuf::from("/tmp/test");
19
19
+
let destination = PathBuf::from("/tmp/output");
20
20
+
let mut ctx = StaticSiteContext::new(root, destination, None);
21
21
+
ctx.client = None; // Explicitly disable network
22
22
+
ctx
23
23
+
}
24
24
+
25
25
+
/// Helper: Render markdown to HTML using test context
26
26
+
async fn render_markdown(input: &str) -> String {
27
27
+
let context = test_context();
28
28
+
export_page(input, context).await.unwrap()
29
29
+
}
30
30
+
31
31
+
#[tokio::test]
32
32
+
async fn test_smoke() {
33
33
+
let output = render_markdown("Hello world").await;
34
34
+
assert!(output.contains("Hello world"));
35
35
+
}
36
36
+
37
37
+
#[tokio::test]
38
38
+
async fn test_paragraph_rendering() {
39
39
+
let input = "This is a paragraph.\n\nThis is another paragraph.";
40
40
+
let output = render_markdown(input).await;
41
41
+
insta::assert_snapshot!(output);
42
42
+
}
43
43
+
44
44
+
#[tokio::test]
45
45
+
async fn test_heading_rendering() {
46
46
+
let input = "# Heading 1\n\n## Heading 2\n\n### Heading 3";
47
47
+
let output = render_markdown(input).await;
48
48
+
insta::assert_snapshot!(output);
49
49
+
}
50
50
+
51
51
+
#[tokio::test]
52
52
+
async fn test_list_rendering() {
53
53
+
let input = "- Item 1\n- Item 2\n - Nested\n\n1. Ordered 1\n2. Ordered 2";
54
54
+
let output = render_markdown(input).await;
55
55
+
insta::assert_snapshot!(output);
56
56
+
}
57
57
+
58
58
+
#[tokio::test]
59
59
+
async fn test_code_block_rendering() {
60
60
+
let input = "```rust\nfn main() {\n println!(\"Hello\");\n}\n```";
61
61
+
let output = render_markdown(input).await;
62
62
+
insta::assert_snapshot!(output);
63
63
+
}
64
64
+
65
65
+
#[tokio::test]
66
66
+
async fn test_table_rendering() {
67
67
+
let input = "| Left | Center | Right |\n|:-----|:------:|------:|\n| A | B | C |";
68
68
+
let output = render_markdown(input).await;
69
69
+
insta::assert_snapshot!(output);
70
70
+
}
71
71
+
72
72
+
#[tokio::test]
73
73
+
async fn test_blockquote_rendering() {
74
74
+
let input = "> This is a quote\n>\n> With multiple lines";
75
75
+
let output = render_markdown(input).await;
76
76
+
insta::assert_snapshot!(output);
77
77
+
}
78
78
+
79
79
+
#[tokio::test]
80
80
+
async fn test_math_rendering() {
81
81
+
let input = "Inline $x^2$ and display:\n\n$$\ny = mx + b\n$$";
82
82
+
let output = render_markdown(input).await;
83
83
+
insta::assert_snapshot!(output);
84
84
+
}
85
85
+
86
86
+
#[tokio::test]
87
87
+
async fn test_wikilink_resolution() {
88
88
+
let vault_contents = vec![
89
89
+
PathBuf::from("notes/First Note.md"),
90
90
+
PathBuf::from("notes/Second Note.md"),
91
91
+
];
92
92
+
93
93
+
let mut context = test_context();
94
94
+
context.dir_contents = Some(vault_contents.into());
95
95
+
96
96
+
let input = "[[First Note]] and [[Second Note]]";
97
97
+
let output = export_page(input, context).await.unwrap();
98
98
+
println!("{output}");
99
99
+
assert!(output.contains("./First%20Note.html"));
100
100
+
assert!(output.contains("./Second%20Note.html"));
101
101
+
}
102
102
+
103
103
+
#[tokio::test]
104
104
+
async fn test_broken_wikilink() {
105
105
+
let vault_contents = vec![PathBuf::from("notes/Exists.md")];
106
106
+
107
107
+
let mut context = test_context();
108
108
+
context.dir_contents = Some(vault_contents.into());
109
109
+
110
110
+
let input = "[[Does Not Exist]]";
111
111
+
let output = export_page(input, context).await.unwrap();
112
112
+
113
113
+
// Broken wikilinks become links (they just don't point anywhere valid)
114
114
+
// This is acceptable - static site will show 404 on click
115
115
+
assert!(output.contains("<a href="));
116
116
+
assert!(output.contains("Does Not Exist</a>") || output.contains("Does%20Not%20Exist"));
117
117
+
}
118
118
+
119
119
+
#[tokio::test]
120
120
+
async fn test_wikilink_with_section() {
121
121
+
let vault_contents = vec![PathBuf::from("Note.md")];
122
122
+
123
123
+
let mut context = test_context();
124
124
+
context.dir_contents = Some(vault_contents.into());
125
125
+
126
126
+
let input = "[[Note#Section]]";
127
127
+
let output = export_page(input, context).await.unwrap();
128
128
+
println!("{output}");
129
129
+
assert!(output.contains("Note#Section"));
130
130
+
}
131
131
+
132
132
+
#[tokio::test]
133
133
+
async fn test_link_flattening_enabled() {
134
134
+
let mut context = test_context();
135
135
+
context.options = StaticSiteOptions::FLATTEN_STRUCTURE;
136
136
+
137
137
+
let input = "[Link](path/to/nested/file.md)";
138
138
+
let output = export_page(input, context).await.unwrap();
139
139
+
println!("{output}");
140
140
+
// Should flatten to single parent directory
141
141
+
assert!(output.contains("./entry/file.html"));
142
142
+
}
143
143
+
144
144
+
#[tokio::test]
145
145
+
async fn test_link_flattening_disabled() {
146
146
+
let mut context = test_context();
147
147
+
context.options = StaticSiteOptions::empty();
148
148
+
149
149
+
let input = "[Link](path/to/nested/file.md)";
150
150
+
let output = export_page(input, context).await.unwrap();
151
151
+
println!("{output}");
152
152
+
// Should preserve original path
153
153
+
assert!(output.contains("path/to/nested/file.html"));
154
154
+
}
155
155
+
156
156
+
#[tokio::test]
157
157
+
async fn test_frontmatter_parsing() {
158
158
+
let input = "---\ntitle: Test Page\nauthor: Test Author\n---\n\nContent here";
159
159
+
let context = test_context();
160
160
+
let output = export_page(input, context.clone()).await.unwrap();
161
161
+
162
162
+
// Frontmatter should be parsed but not rendered
163
163
+
assert!(!output.contains("title: Test Page"));
164
164
+
assert!(output.contains("Content here"));
165
165
+
166
166
+
// Verify frontmatter was captured
167
167
+
let frontmatter = context.frontmatter();
168
168
+
let yaml = frontmatter.contents();
169
169
+
let yaml_guard = yaml.read().unwrap();
170
170
+
assert!(yaml_guard.len() > 0);
171
171
+
}
172
172
+
173
173
+
#[tokio::test]
174
174
+
async fn test_empty_frontmatter() {
175
175
+
let input = "---\n---\n\nContent";
176
176
+
let output = render_markdown(input).await;
177
177
+
178
178
+
assert!(output.contains("Content"));
179
179
+
assert!(!output.contains("---"));
180
180
+
}
181
181
+
182
182
+
#[tokio::test]
183
183
+
async fn test_empty_input() {
184
184
+
let output = render_markdown("").await;
185
185
+
assert_eq!(output, "");
186
186
+
}
187
187
+
188
188
+
#[tokio::test]
189
189
+
async fn test_html_and_special_characters() {
190
190
+
// Test that markdown correctly handles HTML and special chars per CommonMark spec
191
191
+
let input =
192
192
+
"Text with <special> & some text. Valid tags: <em>emphasis</em> and <strong>bold</strong>";
193
193
+
let output = render_markdown(input).await;
194
194
+
195
195
+
// & must be escaped for valid HTML
196
196
+
assert!(output.contains("&"));
197
197
+
198
198
+
// Inline HTML tags pass through (CommonMark behavior)
199
199
+
assert!(output.contains("<special>"));
200
200
+
assert!(output.contains("<em>emphasis</em>"));
201
201
+
assert!(output.contains("<strong>bold</strong>"));
202
202
+
}
203
203
+
204
204
+
#[tokio::test]
205
205
+
async fn test_unicode_content() {
206
206
+
let input = "Unicode: 你好 🎉 café";
207
207
+
let output = render_markdown(input).await;
208
208
+
209
209
+
assert!(output.contains("你好"));
210
210
+
assert!(output.contains("🎉"));
211
211
+
assert!(output.contains("café"));
212
212
+
}