rumdl_lib/utils/
mkdocs_test_utils.rs

1/// Shared test utilities for MkDocs pattern testing
2///
3/// This module provides common test helpers to reduce duplication across
4/// MkDocs feature test modules.
5/// A test case for pattern detection
6#[cfg(test)]
7#[derive(Debug)]
8pub struct PatternTestCase {
9    pub input: &'static str,
10    pub expected: bool,
11    pub description: &'static str,
12}
13
14/// A test case for position detection within content
15#[cfg(test)]
16#[derive(Debug)]
17pub struct PositionTestCase {
18    pub content: &'static str,
19    pub test_positions: Vec<(&'static str, bool)>, // (substring to find, expected in context)
20    pub description: &'static str,
21}
22
23/// A test case for indentation detection
24#[cfg(test)]
25#[derive(Debug)]
26pub struct IndentTestCase {
27    pub input: &'static str,
28    pub expected: Option<usize>,
29    pub description: &'static str,
30}
31
32/// Run a batch of pattern detection tests
33#[cfg(test)]
34pub fn run_pattern_tests<F>(test_fn: F, cases: &[PatternTestCase])
35where
36    F: Fn(&str) -> bool,
37{
38    for case in cases {
39        assert_eq!(
40            test_fn(case.input),
41            case.expected,
42            "Failed: {} - Input: {:?}",
43            case.description,
44            case.input
45        );
46    }
47}
48
49/// Run a batch of position detection tests
50#[cfg(test)]
51pub fn run_position_tests<F>(test_fn: F, cases: &[PositionTestCase])
52where
53    F: Fn(&str, usize) -> bool,
54{
55    for case in cases {
56        for (substring, expected) in &case.test_positions {
57            let pos = case.content.find(substring).unwrap_or_else(|| {
58                panic!(
59                    "Substring '{}' not found in content for test: {}",
60                    substring, case.description
61                )
62            });
63            assert_eq!(
64                test_fn(case.content, pos),
65                *expected,
66                "Failed: {} - Position test for substring '{}' at position {}",
67                case.description,
68                substring,
69                pos
70            );
71        }
72    }
73}
74
75/// Run a batch of indentation detection tests
76#[cfg(test)]
77pub fn run_indent_tests<F>(test_fn: F, cases: &[IndentTestCase])
78where
79    F: Fn(&str) -> Option<usize>,
80{
81    for case in cases {
82        assert_eq!(
83            test_fn(case.input),
84            case.expected,
85            "Failed: {} - Input: {:?}",
86            case.description,
87            case.input
88        );
89    }
90}
91
92/// Helper to create a document with various MkDocs features for integration testing
93#[cfg(test)]
94pub fn create_mkdocs_test_document() -> String {
95    r#"# Test Document
96
97Regular paragraph text.
98
99!!! note "Test Note"
100    This is an admonition with content.
101
102    Multiple lines of content.
103
104[^1]: This is a footnote definition
105    with multiple lines
106    of content.
107
108=== "Tab 1"
109
110    Content in tab 1.
111
112    More content.
113
114=== "Tab 2"
115
116    Content in tab 2.
117
118::: mymodule.MyClass
119    handler: python
120    options:
121      show_source: true
122
123--8<-- "included.md"
124
125Regular text with [^1] footnote reference.
126
127<!-- --8<-- [start:section] -->
128Section content
129<!-- --8<-- [end:section] -->
130
131Final paragraph."#
132        .to_string()
133}
134
135/// Helper to assert multiple positions in content
136#[cfg(test)]
137pub fn assert_positions<F>(content: &str, test_fn: F, positions: &[(&str, bool)])
138where
139    F: Fn(&str, usize) -> bool,
140{
141    for (substring, expected) in positions {
142        if let Some(pos) = content.find(substring) {
143            let actual = test_fn(content, pos);
144            assert_eq!(
145                actual, *expected,
146                "Position test failed for '{substring}' at position {pos}. Expected: {expected}, Got: {actual}"
147            );
148        } else {
149            panic!("Substring '{substring}' not found in content");
150        }
151    }
152}
153
154/// Helper to generate test content with specific patterns
155#[cfg(test)]
156pub struct TestContentBuilder {
157    lines: Vec<String>,
158}
159
160#[cfg(test)]
161impl TestContentBuilder {
162    pub fn new() -> Self {
163        Self { lines: Vec::new() }
164    }
165
166    pub fn add_line(mut self, line: &str) -> Self {
167        self.lines.push(line.to_string());
168        self
169    }
170
171    pub fn add_empty_line(mut self) -> Self {
172        self.lines.push(String::new());
173        self
174    }
175
176    pub fn add_indented(mut self, indent: usize, content: &str) -> Self {
177        self.lines.push(format!("{}{}", " ".repeat(indent), content));
178        self
179    }
180
181    pub fn add_admonition(mut self, admon_type: &str, title: Option<&str>) -> Self {
182        let line = if let Some(t) = title {
183            format!("!!! {admon_type} \"{t}\"")
184        } else {
185            format!("!!! {admon_type}")
186        };
187        self.lines.push(line);
188        self
189    }
190
191    pub fn add_footnote_def(mut self, ref_name: &str, content: &str) -> Self {
192        self.lines.push(format!("[^{ref_name}]: {content}"));
193        self
194    }
195
196    pub fn add_tab(mut self, label: &str) -> Self {
197        self.lines.push(format!("=== \"{label}\""));
198        self
199    }
200
201    pub fn add_snippet(mut self, file: &str) -> Self {
202        self.lines.push(format!("--8<-- \"{file}\""));
203        self
204    }
205
206    pub fn build(self) -> String {
207        self.lines.join("\n")
208    }
209}
210
211#[cfg(test)]
212impl Default for TestContentBuilder {
213    fn default() -> Self {
214        Self::new()
215    }
216}