atproto blogging
1//! Snapshot tests for EditorWriter output.
2//!
3//! These tests exercise edge cases in paragraph rendering, cursor positioning,
4//! and offset mapping.
5
6use markdown_weaver::Parser;
7
8use crate::text::EditorRope;
9use crate::weaver_renderer;
10
11use super::EditorWriter;
12
13/// Helper to render markdown and return HTML segments + paragraph ranges.
14fn render_markdown(source: &str) -> RenderOutput {
15 let rope = EditorRope::from(source);
16 let parser = Parser::new_ext(source, weaver_renderer::default_md_options()).into_offset_iter();
17
18 let writer: EditorWriter<'_, _, _, (), (), ()> =
19 EditorWriter::new(source, &rope, parser).with_auto_incrementing_prefix(0);
20
21 let result = writer.run().expect("render failed");
22
23 RenderOutput {
24 html_segments: result.html_segments,
25 paragraph_ranges: result
26 .paragraph_ranges
27 .into_iter()
28 .map(|(byte_range, char_range)| ParagraphRange {
29 byte_start: byte_range.start,
30 byte_end: byte_range.end,
31 char_start: char_range.start,
32 char_end: char_range.end,
33 })
34 .collect(),
35 offset_maps: result
36 .offset_maps_by_paragraph
37 .into_iter()
38 .map(|maps| {
39 maps.into_iter()
40 .map(|m| OffsetMapEntry {
41 byte_range: format!("{}..{}", m.byte_range.start, m.byte_range.end),
42 char_range: format!("{}..{}", m.char_range.start, m.char_range.end),
43 node_id: m.node_id.to_string(),
44 char_offset_in_node: m.char_offset_in_node,
45 utf16_len: m.utf16_len,
46 })
47 .collect()
48 })
49 .collect(),
50 }
51}
52
53#[derive(Debug, serde::Serialize)]
54struct RenderOutput {
55 html_segments: Vec<String>,
56 paragraph_ranges: Vec<ParagraphRange>,
57 offset_maps: Vec<Vec<OffsetMapEntry>>,
58}
59
60#[derive(Debug, serde::Serialize)]
61struct ParagraphRange {
62 byte_start: usize,
63 byte_end: usize,
64 char_start: usize,
65 char_end: usize,
66}
67
68#[derive(Debug, serde::Serialize)]
69struct OffsetMapEntry {
70 byte_range: String,
71 char_range: String,
72 node_id: String,
73 char_offset_in_node: usize,
74 utf16_len: usize,
75}
76
77// === Trailing paragraph tests ===
78
79#[test]
80fn test_single_paragraph_no_trailing() {
81 let output = render_markdown("hello world");
82 insta::assert_yaml_snapshot!(output);
83}
84
85#[test]
86fn test_single_paragraph_single_newline() {
87 let output = render_markdown("hello world\n");
88 insta::assert_yaml_snapshot!(output);
89}
90
91#[test]
92fn test_single_paragraph_double_newline() {
93 // This should create a synthetic trailing paragraph
94 let output = render_markdown("hello world\n\n");
95 insta::assert_yaml_snapshot!(output);
96}
97
98#[test]
99fn test_single_paragraph_triple_newline() {
100 // Multiple trailing newlines
101 let output = render_markdown("hello world\n\n\n");
102 insta::assert_yaml_snapshot!(output);
103}
104
105#[test]
106fn test_two_paragraphs() {
107 let output = render_markdown("first\n\nsecond");
108 insta::assert_yaml_snapshot!(output);
109}
110
111#[test]
112fn test_two_paragraphs_trailing() {
113 // Two paragraphs plus trailing newlines
114 let output = render_markdown("first\n\nsecond\n\n");
115 insta::assert_yaml_snapshot!(output);
116}
117
118// === Multiple blank lines tests ===
119
120#[test]
121fn test_three_enters() {
122 // Simulates pressing enter 3 times from empty
123 let output = render_markdown("\n\n\n");
124 insta::assert_yaml_snapshot!(output);
125}
126
127#[test]
128fn test_four_enters() {
129 // Bug report: 4th enter moves cursor backwards
130 let output = render_markdown("\n\n\n\n");
131 insta::assert_yaml_snapshot!(output);
132}
133
134#[test]
135fn test_text_then_four_enters() {
136 let output = render_markdown("test\n\n\n\n");
137 insta::assert_yaml_snapshot!(output);
138}
139
140#[test]
141fn test_many_blank_lines() {
142 // Bug report: arrow keys in many blank lines jumps to top
143 let output = render_markdown("start\n\n\n\n\n\nend");
144 insta::assert_yaml_snapshot!(output);
145}
146
147// === Blockquote tests ===
148
149#[test]
150fn test_blockquote_simple() {
151 let output = render_markdown("> quote");
152 insta::assert_yaml_snapshot!(output);
153}
154
155#[test]
156fn test_blockquote_with_trailing() {
157 let output = render_markdown("> quote\n\n");
158 insta::assert_yaml_snapshot!(output);
159}
160
161#[test]
162fn test_blockquote_multiline() {
163 let output = render_markdown("> line one\n> line two");
164 insta::assert_yaml_snapshot!(output);
165}
166
167#[test]
168fn test_blockquote_then_text() {
169 // Bug report: can't type on right side of >
170 let output = render_markdown("> quote\n\ntext after");
171 insta::assert_yaml_snapshot!(output);
172}
173
174// === Wikilink tests ===
175
176#[test]
177fn test_wikilink_simple() {
178 let output = render_markdown("[[link]]");
179 insta::assert_yaml_snapshot!(output);
180}
181
182#[test]
183fn test_wikilink_partial() {
184 // Partial wikilink
185 let output = render_markdown("[[word");
186 insta::assert_yaml_snapshot!(output);
187}
188
189#[test]
190fn test_wikilink_nested_brackets() {
191 // Bug report: [[][]] structure causes trouble
192 let output = render_markdown("[[][]]");
193 insta::assert_yaml_snapshot!(output);
194}
195
196#[test]
197fn test_wikilink_partial_inside_full() {
198 // Partial link inside full link
199 let output = render_markdown("[[outer[[inner]]outer]]");
200 insta::assert_yaml_snapshot!(output);
201}
202
203#[test]
204fn test_many_opening_brackets() {
205 // From the screenshot - many [[ in sequence
206 let output = render_markdown("[[[[[[[[[[[[z]]\n]]]]");
207 insta::assert_yaml_snapshot!(output);
208}
209
210// === Soft break / line continuation tests ===
211
212#[test]
213fn test_soft_break() {
214 let output = render_markdown("line one\nline two");
215 insta::assert_yaml_snapshot!(output);
216}
217
218#[test]
219fn test_hard_break() {
220 // Two trailing spaces = hard break
221 let output = render_markdown("line one \nline two");
222 insta::assert_yaml_snapshot!(output);
223}
224
225#[test]
226fn test_blank_lines_in_paragraph() {
227 // Text with ZWSP, then blank lines, then more text
228 // This tests the exact case causing cursor jump issues
229 let output = render_markdown("test\n\u{200B}d\n\n\ntsdsd");
230 insta::assert_yaml_snapshot!(output);
231}