1use markodown::{offset_to_line_col, transform, ComponentImports};
2use std::fs;
3use std::path::Path;
4
5/// Run an error fixture: transform the `.mdo` input, expect it to fail,
6/// and compare the formatted errors against the `.err` file.
7///
8/// Each line of the `.err` file is `line:col message`, e.g.:
9/// 3:5 Mismatched closing tag: expected </div>, found </span>
10///
11/// The first label on each diagnostic determines the reported position.
12/// Diagnostics with no labels are reported as `1:1`.
13fn run_error_fixture(name: &str) {
14 run_error_fixture_with_options(name, None, None);
15}
16
17fn run_error_fixture_with_options(
18 name: &str,
19 layout: Option<&str>,
20 component_imports: Option<ComponentImports>,
21) {
22 let base = Path::new("tests/error-fixtures").join(name);
23 let input_path = base.with_extension("mdo");
24 let err_path = base.with_extension("err");
25
26 let source = fs::read_to_string(&input_path)
27 .unwrap_or_else(|e| panic!("failed to read {}: {e}", input_path.display()));
28 let expected = fs::read_to_string(&err_path)
29 .unwrap_or_else(|e| panic!("failed to read {}: {e}", err_path.display()));
30
31 let result = transform(
32 &source,
33 None,
34 layout.map(|s| s.to_string()),
35 component_imports,
36 None,
37 false,
38 None,
39 );
40
41 let errors = match result {
42 Err(e) => e,
43 Ok(_) => panic!("[{name}] expected transform to fail but it succeeded"),
44 };
45
46 // Format each diagnostic as:
47 // error: <message>
48 // line:col+len [label message]
49 // line:col+len [label message]
50 // Multiple errors are separated by a blank line.
51 let actual_lines: Vec<String> = errors
52 .iter()
53 .map(|diag| {
54 let mut out = format!("error: {}", diag.message);
55 if let Some(labels) = diag.labels.as_deref() {
56 for label in labels {
57 let (line, col) = offset_to_line_col(&source, label.offset());
58 let len = label.len();
59 if let Some(msg) = label.label() {
60 out.push_str(&format!("\n {line}:{col}+{len} {msg}"));
61 } else {
62 out.push_str(&format!("\n {line}:{col}+{len}"));
63 }
64 }
65 }
66 out
67 })
68 .collect();
69
70 let actual = actual_lines.join("\n\n");
71 let expected = expected.trim();
72 let actual = actual.trim();
73
74 assert_eq!(
75 actual,
76 expected,
77 "\n\n[{name}] error output mismatch\n\n--- expected ---\n{expected}\n--- actual ---\n{actual}\n"
78 );
79}
80
81// -------------------------------------------------------------------------
82// Mismatched close tag
83// -------------------------------------------------------------------------
84
85#[test]
86fn error_fixture_mismatched_close() {
87 run_error_fixture("mismatched-close");
88}
89
90// -------------------------------------------------------------------------
91// Unclosed tag
92// -------------------------------------------------------------------------
93
94#[test]
95fn error_fixture_unclosed_tag() {
96 run_error_fixture("unclosed-tag");
97}
98
99// -------------------------------------------------------------------------
100// Close without open
101// -------------------------------------------------------------------------
102
103#[test]
104fn error_fixture_close_without_open() {
105 run_error_fixture("close-without-open");
106}
107
108// -------------------------------------------------------------------------
109// Unclosed inline tag
110// -------------------------------------------------------------------------
111
112#[test]
113fn error_fixture_inline_unclosed() {
114 run_error_fixture("inline-unclosed");
115}
116
117// -------------------------------------------------------------------------
118// Invalid template expression
119// -------------------------------------------------------------------------
120
121#[test]
122fn error_fixture_bad_template() {
123 run_error_fixture("bad-template");
124}
125
126// -------------------------------------------------------------------------
127// Malformed import/statement
128// -------------------------------------------------------------------------
129
130#[test]
131fn error_fixture_bad_statement() {
132 run_error_fixture("bad-statement");
133}
134
135// -------------------------------------------------------------------------
136// Invalid YAML frontmatter
137// -------------------------------------------------------------------------
138
139#[test]
140fn error_fixture_bad_frontmatter() {
141 run_error_fixture("bad-frontmatter");
142}