1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
//! Under the MkDocs flavor MD077 must place a nested item's continuation where
//! Python-Markdown reads it as that item's content: four columns per nesting
//! level, not the CommonMark content column.
//!
//! pulldown-cmark is CommonMark and cannot judge MkDocs structure, so the
//! expectations are exact outputs. The documents were rendered with
//! Python-Markdown 3.11 (`markdown.markdown(text, extensions=["tables",
//! "def_list", "admonition", "fenced_code"])`); the rendering that fixes each
//! expectation is quoted beside it. The standard-flavor controls do use the
//! pulldown-cmark structure oracle.
use super::cli_md077_continuation_structure_test::structure;
use std::fs;
use std::process::Command;
use tempfile::tempdir;
/// One `rumdl check --fix` run with MD077 alone under `flavor`.
fn fix_once(content: &str, flavor: &str) -> String {
let dir = tempdir().unwrap();
let path = dir.path().join("doc.md");
fs::write(&path, content).unwrap();
// The exit status reports what is left unfixed, which is not under test.
Command::new(env!("CARGO_BIN_EXE_rumdl"))
.current_dir(dir.path())
.args([
"check",
"--isolated",
"--no-cache",
"--fix",
"--flavor",
flavor,
"--enable",
"MD077",
])
.arg(&path)
.output()
.expect("run rumdl check --fix");
fs::read_to_string(&path).unwrap()
}
/// Layouts Python-Markdown reads as content of the innermost item.
const CORRECT: &[&str] = &[
// <ul><li>a<ul><li><p>b</p><p>text</p></li></ul></li></ul>
"- a\n - b\n\n text\n",
// <li>a<ul><li>b<ul><li><p>c</p><p>text</p></li></ul></li></ul></li>
"- a\n - b\n - c\n\n text\n",
// <ol><li>a<ol><li><p>b</p><p>text</p></li></ol></li></ol>
"1. a\n 1. b\n\n text\n",
"1. a\n - b\n\n text\n",
"- a\n 1. b\n\n text\n",
// <ul><li><p>a</p><ul><li><p>b</p></li><li><p>c</p><p>text</p></li></ul></li></ul>
"- a\n - b\n\n - c\n\n text\n",
// <ul><li><ul><li><p>a</p><p>text</p></li></ul></li></ul>
"- - a\n\n text\n",
// <ul><li><ul><li>a</li><li><p>b</p><p>text</p></li></ul></li></ul>
"- a\n - b\n\n ```\n code\n ```\n",
"- - a\n - b\n\n text\n",
// <ol><li>a</li><li>b<pre><code>text</code></pre></li></ol>: code stays code
"1. a\n - b\n\n text\n",
];
#[test]
fn mkdocs_continuation_at_the_nesting_column_is_left_alone() {
for content in CORRECT {
assert_eq!(&fix_once(content, "mkdocs"), content, "{content:?}");
}
}
/// Text at the CommonMark column of a nested item is a paragraph of its parent
/// in Python-Markdown (`<li><p>a</p><ul><li>b</li></ul><p>text</p></li>`); the
/// fix moves it to the item's own column, where it joins the item.
#[test]
fn mkdocs_continuation_at_the_commonmark_column_moves_to_the_nesting_column() {
for (content, fixed) in [
("- a\n - b\n\n text\n", "- a\n - b\n\n text\n"),
(
"- a\n - b\n - c\n\n text\n",
"- a\n - b\n - c\n\n text\n",
),
("1. a\n 1. b\n\n text\n", "1. a\n 1. b\n\n text\n"),
("- a\n - b\n\n text\n", "- a\n - b\n\n text\n"),
] {
assert_eq!(fix_once(content, "mkdocs"), fixed, "{content:?}");
}
}
#[test]
fn mkdocs_nesting_fix_reaches_its_fixed_point_in_one_application() {
for content in [
"- a\n - b\n\n text\n",
"- a\n - b\n - c\n\n text\n",
"1. a\n 1. b\n\n text\n",
"- - a\n\n text\n",
"- a\n - b\n\n text\n",
] {
let once = fix_once(content, "mkdocs");
assert_eq!(once, fix_once(&once, "mkdocs"), "{content:?}");
}
}
/// Control: the standard flavor keeps the CommonMark content column and its fix
/// preserves the structure.
#[test]
fn standard_flavor_nested_continuation_keeps_the_content_column() {
let content = "- a\n - b\n\n text\n";
let after = fix_once(content, "standard");
assert_eq!(after, "- a\n - b\n\n text\n");
assert_eq!(structure(content), structure(&after));
}
/// Only the first line after a blank can open Python-Markdown's indented code
/// block; a deeper line inside a paragraph continues that paragraph
/// (`<p>para text</p>`) and is snapped back like any over-indented continuation.
#[test]
fn mkdocs_over_indented_paragraph_continuation_is_not_taken_for_code() {
assert_eq!(
fix_once("- a\n\n para\n text\n", "mkdocs"),
"- a\n\n para\n text\n"
);
assert_eq!(
fix_once("- a\n - b\n\n para\n text\n", "mkdocs"),
"- a\n - b\n\n para\n text\n"
);
}
/// A marker two levels below its parent is text of the parent's paragraph for
/// Python-Markdown, and what follows it is literal code that must keep its
/// indentation: `<ol><li>a - b<pre><code> text</code></pre></li></ol>`.
#[test]
fn mkdocs_marker_that_python_markdown_does_not_nest_is_left_alone() {
for content in [
"123456. a\n - b\n\n text\n",
"- a\n - b\n\n text\n",
] {
assert_eq!(fix_once(content, "mkdocs"), content, "{content:?}");
}
}
/// A marker with nothing after it is paragraph text for Python-Markdown
/// (`<p>- - b</p><pre><code> text</code></pre>`), so the line below is
/// literal code that keeps its indentation. With a space after the marker it is
/// an item and the line is its content.
#[test]
fn mkdocs_bare_marker_is_not_an_item() {
for content in [
"-\n - b\n\n text\n",
"1.\n - b\n\n text\n",
"- a\n -\n\n text\n",
"-\n -\n\n text\n",
] {
assert_eq!(fix_once(content, "mkdocs"), content, "{content:?}");
}
assert_eq!(
fix_once("- \n - b\n\n text\n", "mkdocs"),
"- \n - b\n\n text\n"
);
}
/// The bare marker above a heading is not the parent of the list below it.
#[test]
fn mkdocs_bare_marker_does_not_unscope_a_later_list() {
assert_eq!(
fix_once("-\n\n# Heading\n\n - b\n\n text\n", "mkdocs"),
"-\n\n# Heading\n\n - b\n\n text\n"
);
}
/// Python-Markdown does not know `1)`: `b` is text of `a`, so text at column 7
/// is a paragraph of `a` and text at column 8 is indented code.
#[test]
fn mkdocs_paren_marker_is_not_an_item() {
let paragraph = "- a\n 1) b\n\n text\n";
assert_eq!(fix_once(paragraph, "mkdocs"), paragraph);
let code = "- a\n 1) b\n\n text\n";
assert_eq!(fix_once(code, "mkdocs"), code);
}
/// Python-Markdown reads `- b` as text of `- a` here, so the text at column 10
/// is a paragraph; at column 12 it would be code.
#[test]
fn mkdocs_item_below_a_same_line_item_is_text() {
let content = "1. - a\n - b\n\n text\n";
assert_eq!(fix_once(content, "mkdocs"), content);
}