use markdown::{mdast::Node, message::Message, Constructs, ParseOptions};
use test_utils::swc::{parse_esm, parse_expression};
mod test_utils;
#[allow(unused)]
#[derive(Debug)]
enum Error {
Mdast(Message),
Serde(serde_json::Error),
}
#[test]
#[cfg(feature = "serde")]
fn serde_constructs() -> Result<(), Error> {
use pretty_assertions::assert_eq;
assert_eq!(
serde_json::to_string(&Constructs::default()).unwrap(),
r#"{"attention":true,"autolink":true,"blockQuote":true,"characterEscape":true,"characterReference":true,"codeIndented":true,"codeFenced":true,"codeText":true,"definition":true,"frontmatter":false,"gfmAutolinkLiteral":false,"gfmFootnoteDefinition":false,"gfmLabelStartFootnote":false,"gfmStrikethrough":false,"gfmTable":false,"gfmTaskListItem":false,"hardBreakEscape":true,"hardBreakTrailing":true,"headingAtx":true,"headingSetext":true,"htmlFlow":true,"htmlText":true,"labelStartImage":true,"labelStartLink":true,"labelEnd":true,"listItem":true,"mathFlow":false,"mathText":false,"mdxEsm":false,"mdxExpressionFlow":false,"mdxExpressionText":false,"mdxJsxFlow":false,"mdxJsxText":false,"thematicBreak":true}"#
);
Ok(())
}
#[test]
#[cfg(feature = "serde")]
fn serde_compile_options() -> Result<(), Error> {
use pretty_assertions::assert_eq;
assert_eq!(
serde_json::to_string(&markdown::CompileOptions::gfm()).unwrap(),
r#"{"allowAnyImgSrc":false,"allowDangerousHtml":false,"allowDangerousProtocol":false,"defaultLineEnding":"\n","gfmFootnoteBackLabel":null,"gfmFootnoteClobberPrefix":null,"gfmFootnoteLabelAttributes":null,"gfmFootnoteLabelTagName":null,"gfmFootnoteLabel":null,"gfmTaskListItemCheckable":false,"gfmTagfilter":true}"#
);
Ok(())
}
#[test]
#[cfg(feature = "serde")]
fn serde_parse_options() -> Result<(), Error> {
use pretty_assertions::assert_eq;
assert_eq!(
serde_json::to_string(&ParseOptions::gfm()).unwrap(),
r#"{"constructs":{"attention":true,"autolink":true,"blockQuote":true,"characterEscape":true,"characterReference":true,"codeIndented":true,"codeFenced":true,"codeText":true,"definition":true,"frontmatter":false,"gfmAutolinkLiteral":true,"gfmFootnoteDefinition":true,"gfmLabelStartFootnote":true,"gfmStrikethrough":true,"gfmTable":true,"gfmTaskListItem":true,"hardBreakEscape":true,"hardBreakTrailing":true,"headingAtx":true,"headingSetext":true,"htmlFlow":true,"htmlText":true,"labelStartImage":true,"labelStartLink":true,"labelEnd":true,"listItem":true,"mathFlow":false,"mathText":false,"mdxEsm":false,"mdxExpressionFlow":false,"mdxExpressionText":false,"mdxJsxFlow":false,"mdxJsxText":false,"thematicBreak":true},"gfmStrikethroughSingleTilde":true,"mathTextSingleDollar":true}"#
);
Ok(())
}
#[test]
fn serde_blockquote() -> Result<(), Error> {
assert_serde(
"> a",
r#"{
"type": "root",
"children": [
{
"type": "blockquote",
"children": [
{
"type": "paragraph",
"children": [{"type": "text", "value": "a"}]
}
]
}
]
}
"#,
ParseOptions::default(),
)
}
#[test]
fn serde_footnote_definition() -> Result<(), Error> {
assert_serde(
"[^a]: b",
r#"{
"type": "root",
"children": [
{
"type": "footnoteDefinition",
"identifier": "a",
"label": "a",
"children": [
{
"type": "paragraph",
"children": [{"type": "text", "value": "b"}]
}
]
}
]
}"#,
ParseOptions::gfm(),
)
}
#[test]
fn serde_mdx_jsx_flow_element() -> Result<(), Error> {
let options = ParseOptions {
mdx_esm_parse: Some(Box::new(parse_esm)),
mdx_expression_parse: Some(Box::new(parse_expression)),
..ParseOptions::mdx()
};
assert_serde(
"",
r#"{
"type": "root",
"children": [
{
"type": "mdxJsxFlowElement",
"name": "a",
"attributes": [
{"type": "mdxJsxAttribute", "name": "b"},
{
"type": "mdxJsxAttribute",
"name": "c",
"value": {
"_markdownRsStops": [[0, 8]],
"type": "mdxJsxAttributeValueExpression",
"value": "d"
}
},
{"type": "mdxJsxAttribute", "name": "e", "value": "f"},
{
"_markdownRsStops": [[0, 18]],
"type": "mdxJsxExpressionAttribute",
"value": "...g"
}
],
"children": []
}
]
}"#,
options,
)
}
#[test]
fn serde_list() -> Result<(), Error> {
assert_serde(
"* a",
r#"{
"type": "root",
"children": [
{
"type": "list",
"ordered": false,
"spread": false,
"children": [
{
"type": "listItem",
"spread": false,
"children": [
{
"type": "paragraph",
"children": [{"type": "text", "value": "a"}]
}
]
}
]
}
]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_mdxjs_esm() -> Result<(), Error> {
let options = ParseOptions {
mdx_esm_parse: Some(Box::new(parse_esm)),
mdx_expression_parse: Some(Box::new(parse_expression)),
..ParseOptions::mdx()
};
assert_serde(
"import a, {b} from 'c'",
r#"{
"type": "root",
"children": [
{
"_markdownRsStops": [[0, 0]],
"type": "mdxjsEsm",
"value": "import a, {b} from 'c'"
}
]
}"#,
options,
)
}
#[test]
fn serde_toml() -> Result<(), Error> {
assert_serde(
"+++\na: b\n+++",
r#"{
"type": "root",
"children": [{"type": "toml", "value": "a: b"}]
}"#,
ParseOptions {
constructs: Constructs {
frontmatter: true,
..Constructs::default()
},
..ParseOptions::default()
},
)
}
#[test]
fn serde_yaml() -> Result<(), Error> {
assert_serde(
"---\na: b\n---",
r#"{
"type": "root",
"children": [{"type": "yaml", "value": "a: b"}]
}"#,
ParseOptions {
constructs: Constructs {
frontmatter: true,
..Constructs::default()
},
..ParseOptions::default()
},
)
}
#[test]
fn serde_break() -> Result<(), Error> {
assert_serde(
"a\\\nb",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{"type": "text", "value": "a"},
{"type": "break"},
{"type": "text", "value": "b"}
]
}
]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_inline_code() -> Result<(), Error> {
assert_serde(
"`a`",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [{"type": "inlineCode", "value": "a"}]
}
]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_inline_math() -> Result<(), Error> {
assert_serde(
"$a$",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [{"type": "inlineMath","value": "a"}]
}
]
}"#,
ParseOptions {
constructs: Constructs {
math_text: true,
..Constructs::default()
},
..ParseOptions::default()
},
)
}
#[test]
fn serde_delete() -> Result<(), Error> {
assert_serde(
"~~a~~",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "delete",
"children": [{"type": "text","value": "a"}]
}
]
}
]
}"#,
ParseOptions::gfm(),
)
}
#[test]
fn serde_emphasis() -> Result<(), Error> {
assert_serde(
"*a*",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "emphasis",
"children": [{"type": "text","value": "a"}]
}
]
}
]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_mdx_text_expression() -> Result<(), Error> {
let options = ParseOptions {
mdx_esm_parse: Some(Box::new(parse_esm)),
mdx_expression_parse: Some(Box::new(parse_expression)),
..ParseOptions::mdx()
};
assert_serde(
"a {b}",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{"type": "text","value": "a "},
{
"_markdownRsStops": [[0,3]],
"type": "mdxTextExpression",
"value": "b"
}
]
}
]
}"#,
options,
)
}
#[test]
fn serde_footnote_reference() -> Result<(), Error> {
assert_serde(
"[^a]\n\n[^a]: b",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{"type": "footnoteReference", "identifier": "a", "label": "a"}
]
},
{
"type": "footnoteDefinition",
"identifier": "a",
"label": "a",
"children": [
{
"type": "paragraph",
"children": [{"type": "text", "value": "b"}]
}
]
}
]
}"#,
ParseOptions::gfm(),
)
}
#[test]
fn serde_html() -> Result<(), Error> {
assert_serde(
"",
r#"{
"type": "root",
"children": [{"type": "html", "value": ""}]
}"#,
ParseOptions::gfm(),
)
}
#[test]
fn serde_image() -> Result<(), Error> {
assert_serde(
"",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [{"type": "image", "url": "b", "alt": "a"}]
}
]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_image_reference() -> Result<(), Error> {
assert_serde(
"![a]\n\n[a]: b",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "imageReference",
"alt": "a",
"label": "a",
"identifier": "a",
"referenceType": "shortcut"
}
]
},
{"type": "definition", "url": "b", "identifier": "a", "label": "a"}
]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_mdx_jsx_text_element() -> Result<(), Error> {
assert_serde(
"a ",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{"type": "text", "value": "a "},
{
"type": "mdxJsxTextElement",
"name": "b",
"attributes": [{"type": "mdxJsxAttribute", "name": "c"}],
"children": []
}
]
}
]
}"#,
ParseOptions::mdx(),
)
}
#[test]
fn serde_link() -> Result<(), Error> {
assert_serde(
"[a](b)",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "link",
"url": "b",
"children": [{"type": "text", "value": "a"}]
}
]
}
]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_link_reference() -> Result<(), Error> {
assert_serde(
"[a]\n\n[a]: b",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "linkReference",
"identifier": "a",
"label": "a",
"referenceType": "shortcut",
"children": [{"type": "text", "value": "a"}]
}
]
},
{
"type": "definition",
"url": "b",
"identifier": "a",
"label": "a"
}
]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_strong() -> Result<(), Error> {
assert_serde(
"**a**",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [
{
"type": "strong",
"children": [{"type": "text", "value": "a"}]
}
]
}
]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_text() -> Result<(), Error> {
assert_serde(
"a",
r#"{
"type": "root",
"children": [
{"type": "paragraph", "children": [{"type": "text", "value": "a"}]}
]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_code() -> Result<(), Error> {
assert_serde(
"~~~js eval\nconsole.log(1)\n~~~",
r#"{
"type": "root",
"children": [
{"type": "code", "lang": "js", "meta": "eval", "value": "console.log(1)"}
]
}"#,
ParseOptions::default(),
)?;
assert_serde(
"```\nconsole.log(1)\n```",
r#"{
"type": "root",
"children": [{"type": "code", "value": "console.log(1)"}]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_math() -> Result<(), Error> {
assert_serde(
"$$\n1 + 1 = 2\n$$",
r#"{
"type": "root",
"children": [{"type": "math", "value": "1 + 1 = 2"}]
}"#,
ParseOptions {
constructs: Constructs {
math_flow: true,
..Constructs::default()
},
..ParseOptions::default()
},
)
}
#[test]
fn serde_mdx_flow_expression() -> Result<(), Error> {
let options = ParseOptions {
mdx_esm_parse: Some(Box::new(parse_esm)),
mdx_expression_parse: Some(Box::new(parse_expression)),
..ParseOptions::mdx()
};
assert_serde(
"{a}",
r#"{
"type": "root",
"children": [
{"_markdownRsStops": [[0, 1]], "type": "mdxFlowExpression", "value": "a"}
]
}"#,
options,
)
}
#[test]
fn serde_heading() -> Result<(), Error> {
assert_serde(
"# a",
r#"{
"type": "root",
"children": [
{
"type": "heading",
"depth": 1,
"children": [{"type": "text", "value": "a"}]
}
]
}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_table() -> Result<(), Error> {
// To do: `"none"` should serialize in serde as `null`.
assert_serde(
"| a | b | c | d |\n| - | :- | -: | :-: |\n| 1 | 2 | 3 | 4 |",
r#"{
"type": "root",
"children": [
{
"type": "table",
"align": [null, "left", "right", "center"],
"children": [
{
"type": "tableRow",
"children": [
{"type": "tableCell", "children": [{"type": "text", "value": "a"}]},
{"type": "tableCell", "children": [{"type": "text", "value": "b"}]},
{"type": "tableCell", "children": [{"type": "text", "value": "c"}]},
{"type": "tableCell", "children": [{"type": "text", "value": "d"}]}
]
},
{
"type": "tableRow",
"children": [
{"type": "tableCell","children": [{"type": "text", "value": "1"}]},
{"type": "tableCell","children": [{"type": "text", "value": "2"}]},
{"type": "tableCell","children": [{"type": "text", "value": "3"}]},
{"type": "tableCell","children": [{"type": "text", "value": "4"}]}
]
}
]
}
]
}"#,
ParseOptions::gfm(),
)
}
#[test]
fn serde_thematic_break() -> Result<(), Error> {
assert_serde(
"***",
r#"{"type": "root", "children": [{"type": "thematicBreak"}]}"#,
ParseOptions::default(),
)
}
#[test]
fn serde_definition() -> Result<(), Error> {
assert_serde(
"[a]: b",
r###"{
"type": "root",
"children": [
{"type": "definition", "url": "b", "identifier": "a", "label": "a"}
]
}"###,
ParseOptions::default(),
)
}
#[test]
fn serde_paragraph() -> Result<(), Error> {
assert_serde(
"a",
r#"{
"type": "root",
"children": [
{
"type": "paragraph",
"children": [{"type": "text", "value": "a"}]
}
]
}"#,
ParseOptions::default(),
)
}
/// Assert serde of mdast constructs.
///
/// Refer below links for the mdast JSON construct types.
/// *
/// *
/// *
#[cfg(feature = "serde")]
fn assert_serde(input: &str, expected: &str, options: ParseOptions) -> Result<(), Error> {
use pretty_assertions::assert_eq;
let mut source = markdown::to_mdast(input, &options).map_err(Error::Mdast)?;
remove_position(&mut source);
// Serialize to JSON
let actual_value: serde_json::Value = serde_json::to_value(&source).map_err(Error::Serde)?;
let expected_value: serde_json::Value = serde_json::from_str(expected).map_err(Error::Serde)?;
// Assert serialization.
assert_eq!(actual_value, expected_value);
// Assert deserialization.
assert_eq!(
source,
serde_json::from_value(actual_value).map_err(Error::Serde)?
);
Ok(())
}
#[cfg(not(feature = "serde"))]
#[allow(unused_variables)]
fn assert_serde(input: &str, expected: &str, options: ParseOptions) -> Result<(), Error> {
Ok(())
}
#[allow(dead_code)]
fn remove_position(node: &mut Node) {
if let Some(children) = node.children_mut() {
for child in children {
remove_position(child);
}
}
node.position_set(None);
}