atproto blogging
1//! Tests for the AT Protocol ClientWriter
2//!
3//! These tests verify that ClientWriter produces the same output as StaticPageWriter
4//! for core markdown rendering, particularly footnotes/sidenotes.
5
6use super::writer::ClientWriter;
7use markdown_weaver::Parser;
8use markdown_weaver_escape::FmtWriter;
9
10/// Helper: Render markdown to HTML using ClientWriter
11fn render_markdown(input: &str) -> String {
12 let options = crate::default_md_options();
13 let parser = Parser::new_ext(input, options).into_offset_iter();
14 let mut output = String::new();
15 let writer: ClientWriter<'_, _, _, ()> = ClientWriter::new(parser, FmtWriter(&mut output), input);
16 writer.run().unwrap();
17 output
18}
19
20// =============================================================================
21// Basic Rendering Tests
22// =============================================================================
23
24#[test]
25fn test_smoke() {
26 let output = render_markdown("Hello world");
27 assert!(output.contains("Hello world"));
28}
29
30#[test]
31fn test_paragraph_rendering() {
32 let input = "This is a paragraph.\n\nThis is another paragraph.";
33 let output = render_markdown(input);
34 insta::assert_snapshot!(output);
35}
36
37#[test]
38fn test_heading_rendering() {
39 let input = "# Heading 1\n\n## Heading 2\n\n### Heading 3";
40 let output = render_markdown(input);
41 insta::assert_snapshot!(output);
42}
43
44#[test]
45fn test_list_rendering() {
46 let input = "- Item 1\n- Item 2\n - Nested\n\n1. Ordered 1\n2. Ordered 2";
47 let output = render_markdown(input);
48 insta::assert_snapshot!(output);
49}
50
51#[test]
52fn test_code_block_rendering() {
53 let input = "```rust\nfn main() {\n println!(\"Hello\");\n}\n```";
54 let output = render_markdown(input);
55 insta::assert_snapshot!(output);
56}
57
58#[test]
59fn test_table_rendering() {
60 let input = "| Left | Center | Right |\n|:-----|:------:|------:|\n| A | B | C |";
61 let output = render_markdown(input);
62 insta::assert_snapshot!(output);
63}
64
65#[test]
66fn test_blockquote_rendering() {
67 let input = "> This is a quote\n>\n> With multiple lines";
68 let output = render_markdown(input);
69 insta::assert_snapshot!(output);
70}
71
72#[test]
73fn test_math_rendering() {
74 let input = "Inline $x^2$ and display:\n\n$$\ny = mx + b\n$$";
75 let output = render_markdown(input);
76 insta::assert_snapshot!(output);
77}
78
79#[test]
80fn test_empty_input() {
81 let output = render_markdown("");
82 assert_eq!(output, "");
83}
84
85#[test]
86fn test_html_and_special_characters() {
87 // ClientWriter wraps inline HTML in spans to contain embeds etc
88 let input =
89 "Text with <special> & some text. Valid tags: <em>emphasis</em> and <strong>bold</strong>";
90 let output = render_markdown(input);
91 assert!(output.contains("&"));
92 // Inline HTML gets wrapped in html-embed spans
93 assert!(output.contains("html-embed-inline"));
94 assert!(output.contains("<special>"));
95}
96
97#[test]
98fn test_unicode_content() {
99 let input = "Unicode: 你好 🎉 café";
100 let output = render_markdown(input);
101 assert!(output.contains("你好"));
102 assert!(output.contains("🎉"));
103 assert!(output.contains("café"));
104}
105
106// =============================================================================
107// WeaverBlock Prefix Tests
108// =============================================================================
109
110#[test]
111fn test_weaver_block_aside_class() {
112 let input = "\n\n{.aside}\nThis paragraph should be in an aside.";
113 let output = render_markdown(input);
114 insta::assert_snapshot!(output);
115}
116
117#[test]
118fn test_weaver_block_custom_class() {
119 let input = "\n\n{.highlight}\nThis paragraph has a custom class.";
120 let output = render_markdown(input);
121 insta::assert_snapshot!(output);
122}
123
124#[test]
125fn test_weaver_block_custom_attributes() {
126 let input = "\n\n{.foo, width: 300px, data-test: value}\nParagraph with class and attributes.";
127 let output = render_markdown(input);
128 insta::assert_snapshot!(output);
129}
130
131#[test]
132fn test_weaver_block_before_heading() {
133 let input = "\n\n{.aside}\n## Heading in aside\n\nParagraph also in aside.";
134 let output = render_markdown(input);
135 insta::assert_snapshot!(output);
136}
137
138#[test]
139fn test_weaver_block_before_blockquote() {
140 let input = "\n\n{.aside}\n\n> This blockquote is in an aside.";
141 let output = render_markdown(input);
142 insta::assert_snapshot!(output);
143}
144
145#[test]
146fn test_weaver_block_before_list() {
147 let input = "\n\n{.aside}\n\n- Item 1\n- Item 2";
148 let output = render_markdown(input);
149 insta::assert_snapshot!(output);
150}
151
152#[test]
153fn test_weaver_block_before_code_block() {
154 let input = "\n\n{.aside}\n\n```rust\nfn main() {}\n```";
155 let output = render_markdown(input);
156 insta::assert_snapshot!(output);
157}
158
159#[test]
160fn test_weaver_block_multiple_classes() {
161 let input = "\n\n{.aside, .highlight, .important}\nMultiple classes applied.";
162 let output = render_markdown(input);
163 insta::assert_snapshot!(output);
164}
165
166#[test]
167fn test_weaver_block_no_effect_on_following() {
168 let input = "\n\n{.aside}\nFirst paragraph in aside.\n\nSecond paragraph NOT in aside.";
169 let output = render_markdown(input);
170 insta::assert_snapshot!(output);
171}
172
173// =============================================================================
174// Footnote / Sidenote Tests
175// =============================================================================
176
177#[test]
178fn test_footnote_traditional() {
179 let input = "Here is some text[^1].\n[^1]: This is the footnote definition.";
180 let output = render_markdown(input);
181 insta::assert_snapshot!(output);
182}
183
184#[test]
185fn test_footnote_sidenote_inline() {
186 let input = "Here is text[^note]\n[^note]: Sidenote content.";
187 let output = render_markdown(input);
188 insta::assert_snapshot!(output);
189}
190
191#[test]
192fn test_footnote_multiple() {
193 let input = "First[^1] and second[^2] footnotes.\n[^1]: First note.\n[^2]: Second note.";
194 let output = render_markdown(input);
195 insta::assert_snapshot!(output);
196}
197
198#[test]
199fn test_footnote_with_inline_formatting() {
200 let input = "Text with footnote[^fmt].\n[^fmt]: Note with **bold** and *italic*.";
201 let output = render_markdown(input);
202 insta::assert_snapshot!(output);
203}
204
205#[test]
206fn test_footnote_named() {
207 let input = "Reference[^my-note].\n[^my-note]: Named footnote content.";
208 let output = render_markdown(input);
209 insta::assert_snapshot!(output);
210}
211
212#[test]
213fn test_footnote_in_blockquote() {
214 let input = "> Quote with footnote[^q].\n[^q]: Footnote for quote.";
215 let output = render_markdown(input);
216 insta::assert_snapshot!(output);
217}
218
219// =============================================================================
220// Combined WeaverBlock + Footnote Tests
221// =============================================================================
222
223#[test]
224fn test_weaver_block_with_footnote() {
225 let input = "{.aside}\nAside with a footnote[^aside].\n\n[^aside]: Footnote in aside context.";
226 let output = render_markdown(input);
227 insta::assert_snapshot!(output);
228}