This commit is contained in:
Lukas Stancik
2026-04-04 09:40:28 +02:00
commit 5d3afabd24
218 changed files with 82634 additions and 0 deletions
+472
View File
@@ -0,0 +1,472 @@
mod test_utils;
use markdown::{
mdast::{
AttributeContent, AttributeValue, AttributeValueExpression, Blockquote, MdxFlowExpression,
MdxJsxAttribute, MdxJsxTextElement, MdxTextExpression, Node, Paragraph, Root, Text,
},
message, to_html_with_options, to_mdast,
unist::Position,
Constructs, Options, ParseOptions,
};
use pretty_assertions::assert_eq;
use test_utils::swc::{parse_esm, parse_expression};
/// Note: these tests are also in `micromark/micromark-extension-mdx-expression`
/// at `tests/index.js`.
#[test]
fn mdx_expression_flow_agnostic() -> Result<(), message::Message> {
let mdx = Options {
parse: ParseOptions::mdx(),
..Default::default()
};
assert_eq!(
to_html_with_options("{a}", &mdx)?,
"",
"should support an expression"
);
assert_eq!(
to_html_with_options("{}", &mdx)?,
"",
"should support an empty expression"
);
// Note: in MDX, indented code is turned off:
assert_eq!(
to_html_with_options(
" {}",
&Options {
parse: ParseOptions {
constructs: Constructs {
mdx_expression_flow: true,
..Default::default()
},
..Default::default()
},
..Default::default()
}
)?,
"<pre><code>{}\n</code></pre>",
"should prefer indented code over expressions if its enabled"
);
assert_eq!(
to_html_with_options(
" {}",
&Options {
parse: ParseOptions {
constructs: Constructs {
mdx_expression_flow: true,
..Default::default()
},
..Default::default()
},
..Default::default()
}
)?,
"",
"should support indented expressions if indented code is enabled"
);
assert_eq!(
to_html_with_options("{a", &mdx).err().unwrap().to_string(),
"1:3: Unexpected end of file in expression, expected a corresponding closing brace for `{` (markdown-rs:unexpected-eof)",
"should crash if no closing brace is found (1)"
);
assert_eq!(
to_html_with_options("{b { c }", &mdx)
.err()
.unwrap()
.to_string(),
"1:9: Unexpected end of file in expression, expected a corresponding closing brace for `{` (markdown-rs:unexpected-eof)",
"should crash if no closing brace is found (2)"
);
assert_eq!(
to_html_with_options("{\n}\na", &mdx)?,
"<p>a</p>",
"should support a line ending in an expression"
);
assert_eq!(
to_html_with_options("{ a } \t\nb", &mdx)?,
"<p>b</p>",
"should support expressions followed by spaces"
);
assert_eq!(
to_html_with_options(" { a }\nb", &mdx)?,
"<p>b</p>",
"should support expressions preceded by spaces"
);
assert_eq!(
to_html_with_options("a\n\n* b", &mdx)?,
"<p>a</p>\n<ul>\n<li>b</li>\n</ul>",
"should support lists after non-expressions (wooorm/markdown-rs#11)"
);
assert_eq!(
to_html_with_options("> {a\nb}", &mdx)
.err()
.unwrap().to_string(),
"2:1: Unexpected lazy line in expression in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc (markdown-rs:unexpected-lazy)",
"should not support lazyness (1)"
);
assert_eq!(
to_html_with_options("> a\n{b}", &mdx)?,
"<blockquote>\n<p>a</p>\n</blockquote>\n",
"should not support lazyness (2)"
);
assert_eq!(
to_html_with_options("> {a}\nb", &mdx)?,
"<blockquote>\n</blockquote>\n<p>b</p>",
"should not support lazyness (3)"
);
assert_eq!(
to_html_with_options("> {\n> a\nb}", &mdx)
.err()
.unwrap().to_string(),
"3:1: Unexpected lazy line in expression in container, expected line to be prefixed with `>` when in a block quote, whitespace when in a list, etc (markdown-rs:unexpected-lazy)",
"should not support lazyness (4)"
);
assert_eq!(
to_mdast("{alpha +\nbravo}", &mdx.parse)?,
Node::Root(Root {
children: vec![Node::MdxFlowExpression(MdxFlowExpression {
value: "alpha +\nbravo".into(),
position: Some(Position::new(1, 1, 0, 2, 7, 15)),
stops: vec![(0, 1), (7, 8), (8, 9)]
})],
position: Some(Position::new(1, 1, 0, 2, 7, 15))
}),
"should support mdx expressions (flow) as `MdxFlowExpression`s in mdast"
);
assert_eq!(
to_mdast(" {`\n a\n `}", &mdx.parse)?,
Node::Root(Root {
children: vec![Node::MdxFlowExpression(MdxFlowExpression {
value: "`\n a\n`".into(),
position: Some(Position::new(1, 3, 2, 3, 5, 15)),
stops: vec![(0, 3), (1, 4), (2, 7), (5, 10), (6, 13)]
})],
position: Some(Position::new(1, 1, 0, 3, 5, 15))
}),
"should support indent in `MdxFlowExpression` in mdast"
);
Ok(())
}
/// Note: these tests are also in `micromark/micromark-extension-mdx-expression`
/// at `tests/index.js`.
#[test]
fn mdx_expression_flow_gnostic() -> Result<(), message::Message> {
let swc = Options {
parse: ParseOptions {
constructs: Constructs::mdx(),
mdx_esm_parse: Some(Box::new(parse_esm)),
mdx_expression_parse: Some(Box::new(parse_expression)),
..Default::default()
},
..Default::default()
};
assert_eq!(
to_html_with_options("{a}", &swc)?,
"",
"should support an expression"
);
assert_eq!(
to_html_with_options("{}", &swc)?,
"",
"should support an empty expression"
);
assert_eq!(
to_html_with_options("{a", &swc).err().unwrap().to_string(),
"1:3: Unexpected end of file in expression, expected a corresponding closing brace for `{` (markdown-rs:unexpected-eof)",
"should crash if no closing brace is found (1)"
);
assert_eq!(
to_html_with_options("{b { c }", &swc)
.err()
.unwrap()
.to_string(),
"1:9: Could not parse expression with swc: Unexpected content after expression (mdx:swc)",
"should crash if no closing brace is found (2)"
);
assert_eq!(
to_html_with_options("{\n}\na", &swc)?,
"<p>a</p>",
"should support a line ending in an expression"
);
assert_eq!(
to_html_with_options("{ a } \t\nb", &swc)?,
"<p>b</p>",
"should support expressions followed by spaces"
);
assert_eq!(
to_html_with_options(" { a }\nb", &swc)?,
"<p>b</p>",
"should support expressions preceded by spaces"
);
assert_eq!(
to_html_with_options(" {`\n a\n `}", &swc)?,
"",
"should support indented expressions"
);
assert_eq!(
to_html_with_options("a{(b)}c", &swc)?,
"<p>ac</p>",
"should support expressions padded w/ parens"
);
assert_eq!(
to_html_with_options("a{/* b */ ( (c) /* d */ + (e) )}f", &swc)?,
"<p>af</p>",
"should support expressions padded w/ parens and comments"
);
assert_eq!(
to_mdast("{`\n\t`}", &swc.parse)?,
Node::Root(Root {
children: vec![Node::MdxFlowExpression(MdxFlowExpression {
value: "`\n `".into(),
position: Some(Position::new(1, 1, 0, 2, 7, 6)),
stops: vec![(0, 1), (1, 2), (2, 3)]
})],
position: Some(Position::new(1, 1, 0, 2, 7, 6))
}),
"should use correct positional info when tabs are used (1, indent)"
);
assert_eq!(
to_mdast("{`\nalpha\t`}", &swc.parse)?,
Node::Root(Root {
children: vec![Node::MdxFlowExpression(MdxFlowExpression {
value: "`\nalpha\t`".into(),
position: Some(Position::new(1, 1, 0, 2, 11, 11)),
stops: vec![(0, 1), (1, 2), (2, 3)]
})],
position: Some(Position::new(1, 1, 0, 2, 11, 11))
}),
"should use correct positional info when tabs are used (2, content)"
);
assert_eq!(
to_mdast("> aaa <b c={`\n> d\n> `} /> eee", &swc.parse)?,
Node::Root(Root {
children: vec![Node::Blockquote(Blockquote {
children: vec![Node::Paragraph(Paragraph {
children: vec![
Node::Text(Text {
value: "aaa ".into(),
position: Some(Position::new(1, 4, 3, 1, 8, 7))
}),
Node::MdxJsxTextElement(MdxJsxTextElement {
children: vec![],
name: Some("b".into()),
attributes: vec![AttributeContent::Property(MdxJsxAttribute {
name: "c".into(),
value: Some(AttributeValue::Expression(AttributeValueExpression {
value: "`\n d\n`".into(),
stops: vec![(0, 13), (1, 14), (2, 19), (6, 23), (7, 27)]
}))
})],
position: Some(Position::new(1, 8, 7, 3, 9, 32))
}),
Node::Text(Text {
value: " eee".into(),
position: Some(Position::new(3, 9, 32, 3, 13, 36))
})
],
position: Some(Position::new(1, 3, 2, 3, 13, 36))
})],
position: Some(Position::new(1, 1, 0, 3, 13, 36))
})],
position: Some(Position::new(1, 1, 0, 3, 13, 36))
}),
"should support template strings in JSX (text) in block quotes"
);
assert_eq!(
to_mdast("> ab {`\n>\t`}", &swc.parse)?,
Node::Root(Root {
children: vec![Node::Blockquote(Blockquote {
children: vec![Node::Paragraph(Paragraph {
children: vec![
Node::Text(Text {
value: "ab ".into(),
position: Some(Position::new(1, 3, 2, 1, 6, 5))
}),
Node::MdxTextExpression(MdxTextExpression {
value: "`\n`".into(),
stops: vec![(0, 6), (1, 7), (2, 10)],
position: Some(Position::new(1, 6, 5, 2, 7, 12))
})
],
position: Some(Position::new(1, 3, 2, 2, 7, 12))
})],
position: Some(Position::new(1, 1, 0, 2, 7, 12))
})],
position: Some(Position::new(1, 1, 0, 2, 7, 12))
}),
"should use correct positional when there are virtual spaces due to a block quote"
);
assert_eq!(
to_mdast(
"> {`\n> alpha\n> bravo\n> charlie\n> delta\n> `}",
&swc.parse
)?,
Node::Root(Root {
children: vec![Node::Blockquote(Blockquote {
children: vec![Node::MdxFlowExpression(MdxFlowExpression {
value: "`\nalpha\nbravo\ncharlie\n delta\n`".into(),
position: Some(Position::new(1, 3, 2, 6, 5, 49)),
stops: vec![
(0, 3),
(1, 4),
(2, 7),
(7, 12),
(8, 16),
(13, 21),
(14, 26),
(21, 33),
(22, 38),
(28, 44),
(29, 47)
]
})],
position: Some(Position::new(1, 1, 0, 6, 5, 49))
})],
position: Some(Position::new(1, 1, 0, 6, 5, 49))
}),
"should keep the correct number of spaces in a blockquote (flow)"
);
assert_eq!(
to_mdast(
"> {`\n> alpha\n> bravo\n> charlie\n> delta\n> `}",
&swc.parse
)?,
Node::Root(Root {
children: vec![Node::Blockquote(Blockquote {
children: vec![Node::MdxFlowExpression(MdxFlowExpression {
value: "`\nalpha\nbravo\ncharlie\n delta\n`".into(),
position: Some(Position::new(1, 3, 2, 6, 5, 49)),
stops: vec![
(0, 3),
(1, 4),
(2, 7),
(7, 12),
(8, 16),
(13, 21),
(14, 26),
(21, 33),
(22, 38),
(28, 44),
(29, 47)
]
})],
position: Some(Position::new(1, 1, 0, 6, 5, 49))
})],
position: Some(Position::new(1, 1, 0, 6, 5, 49))
}),
"should keep the correct number of spaces in a blockquote (flow)"
);
// Note: the weird character test has to go in mdxjs-rs.
Ok(())
}
/// Note: these tests are also in `micromark/micromark-extension-mdx-expression`
/// at `tests/index.js`.
/// This project includes *all* extensions which means that it can use JSX.
/// There we test something that does not exist in actual MDX but which is used
/// by the separate JSX extension.
#[test]
fn mdx_expression_spread() -> Result<(), message::Message> {
let swc = Options {
parse: ParseOptions {
constructs: Constructs::mdx(),
mdx_esm_parse: Some(Box::new(parse_esm)),
mdx_expression_parse: Some(Box::new(parse_expression)),
..Default::default()
},
..Default::default()
};
assert_eq!(
to_html_with_options("<a {...b} />", &swc)?,
"",
"should support a spread"
);
assert_eq!(
to_html_with_options("<a {b} />", &swc).err().unwrap().to_string(),
"1:5: Unexpected prop in spread (such as `{x}`): only a spread is supported (such as `{...x}`) (mdx:swc)",
"should crash if not a spread"
);
assert_eq!(
to_html_with_options("<a {...?} />", &swc)
.err()
.unwrap()
.to_string(),
"1:13: Could not parse expression with swc: Expression expected (mdx:swc)",
"should crash on an incorrect spread"
);
assert_eq!(
to_html_with_options("<a {b=c}={} d>", &swc).err().unwrap().to_string(),
"1:5: Unexpected prop in spread (such as `{x}`): only a spread is supported (such as `{...x}`) (mdx:swc)",
"should crash on an incorrect spread that looks like an assignment"
);
assert_eq!(
to_html_with_options("<a {...b,c} d>", &swc).err().unwrap().to_string(),
"1:5: Unexpected extra content in spread (such as `{...x,y}`): only a single spread is supported (such as `{...x}`) (mdx:swc)",
"should crash if a spread and other things"
);
assert_eq!(
to_html_with_options("<a {b=c} />", &swc)
.err()
.unwrap()
.to_string(),
"1:12: Could not parse expression with swc: assignment property is invalid syntax (mdx:swc)",
"should crash if not an identifier"
);
// Note: `markdown-rs` has no `allowEmpty`.
assert_eq!(
to_html_with_options("<a {} />", &swc).err().unwrap().to_string(),
"1:9: Unexpected prop in spread (such as `{x}`): only a spread is supported (such as `{...x}`) (mdx:swc)",
"should crash on an empty spread"
);
assert_eq!(
to_html_with_options("<a {/* b */} />", &swc).err().unwrap().to_string(),
"1:5: Unexpected prop in spread (such as `{x}`): only a spread is supported (such as `{...x}`) (mdx:swc)",
"should crash on a comment spread"
);
Ok(())
}