mod test_utils; use markdown::{ mdast::{ AttributeContent, AttributeValue, Emphasis, MdxJsxAttribute, MdxJsxTextElement, Node, Paragraph, Root, Text, }, to_html_with_options, to_mdast, unist::Position, Constructs, Options, ParseOptions, }; use pretty_assertions::assert_eq; use test_utils::swc::{parse_esm, parse_expression}; #[test] fn mdx_jsx_text_core() -> Result<(), String> { let mdx = Options { parse: ParseOptions::mdx(), ..Default::default() }; assert_eq!( to_html_with_options("a c", &mdx)?, "
a c
", "should support mdx jsx (text) if enabled" ); assert_eq!( to_html_with_options("a c.", &mdx)?, "a c.
", "should support a self-closing element" ); assert_eq!( to_html_with_options("a c.", &mdx)?, "a c.
", "should support a closed element" ); assert_eq!( to_html_with_options("a <>> c.", &mdx)?, "a c.
", "should support fragments" ); assert_eq!( to_html_with_options("a *b* c.", &mdx)?, "a b c.
", "should support markdown inside elements" ); assert_eq!( to_mdast("a c.", &mdx.parse)?, Node::Root(Root { children: vec![Node::Paragraph(Paragraph { children: vec![ Node::Text(Text { value: "a ".into(), position: Some(Position::new(1, 1, 0, 1, 3, 2)) }), Node::MdxJsxTextElement(MdxJsxTextElement { name: Some("b".into()), attributes: vec![], children: vec![], position: Some(Position::new(1, 3, 2, 1, 8, 7)) }), Node::Text(Text { value: " c.".into(), position: Some(Position::new(1, 8, 7, 1, 11, 10)) }) ], position: Some(Position::new(1, 1, 0, 1, 11, 10)) })], position: Some(Position::new(1, 1, 0, 1, 11, 10)) }), "should support mdx jsx (text) as `MdxJsxTextElement`s in mdast (self-closing)" ); assert_eq!( to_mdast("a *c* d.", &mdx.parse)?, Node::Root(Root { children: vec![Node::Paragraph(Paragraph { children: vec![ Node::Text(Text { value: "a ".into(), position: Some(Position::new(1, 1, 0, 1, 3, 2)) }), Node::MdxJsxTextElement(MdxJsxTextElement { name: Some("b".into()), attributes: vec![], children: vec![ Node::Emphasis(Emphasis { children: vec![ Node::Text(Text { value: "c".into(), position: Some(Position::new(1, 7, 6, 1, 8, 7)) }), ], position: Some(Position::new(1, 6, 5, 1, 9, 8)) }), ], position: Some(Position::new(1, 3, 2, 1, 13, 12)) }), Node::Text(Text { value: " d.".into(), position: Some(Position::new(1, 13, 12, 1, 16, 15)) }) ], position: Some(Position::new(1, 1, 0, 1, 16, 15)) })], position: Some(Position::new(1, 1, 0, 1, 16, 15)) }), "should support mdx jsx (text) as `MdxJsxTextElement`s in mdast (matched open and close tags)" ); assert_eq!( to_mdast("a c
", "should support a self-closing element" ); assert_eq!( to_html_with_options("a c d", &mdx)?, "a c d
", "should support a closed element" ); assert_eq!( to_html_with_options("a c", &mdx)?, "a c
", "should support an unclosed element" ); assert_eq!( to_html_with_options("a c", &mdx)?, "a c
", "should support an attribute expression" ); assert_eq!( to_html_with_options("a d", &mdx)?, "a d
", "should support an attribute value expression" ); Ok(()) } #[test] fn mdx_jsx_text_gnostic() -> Result<(), String> { 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 c", &swc)?, "a c
", "should support a self-closing element" ); assert_eq!( to_html_with_options("a c d", &swc)?, "a c d
", "should support a closed element" ); assert_eq!( to_html_with_options("a c", &swc)?, "a c
", "should support an unclosed element" ); assert_eq!( to_html_with_options("a d", &swc)?, "a d
", "should support an attribute expression" ); assert_eq!( to_html_with_options("a f", &swc)?, "a f
", "should support more complex attribute expression (1)" ); assert_eq!( to_html_with_options("a d", &swc)?, "a d
", "should support more complex attribute expression (2)" ); assert_eq!( to_html_with_options("a d", &swc)?, "a d
", "should support an attribute value expression" ); assert_eq!( to_html_with_options("a d", &swc).err().unwrap(), "1:15: Could not parse expression with swc: Unexpected eof", "should crash on an empty attribute value expression" ); assert_eq!( to_html_with_options("a c", &swc) .err() .unwrap(), "1:18: Could not parse expression with swc: Expected ',', got '}'", "should crash on a non-spread attribute expression" ); assert_eq!( to_html_with_options("a d", &swc) .err() .unwrap(), "1:16: Could not parse expression with swc: Unexpected token `?`. Expected this, import, async, function, [ for array literal, { for object literal, @ for decorator, function, class, null, true, false, number, bigint, string, regexp, ` for template literal, (, or an identifier", "should crash on invalid JS in an attribute value expression" ); assert_eq!( to_html_with_options("a c", &swc) .err() .unwrap(), "1:14: Could not parse expression with swc: Unexpected token `?`. Expected identifier, string literal, numeric literal or [ for the computed key", "should crash on invalid JS in an attribute expression" ); assert_eq!( to_html_with_options("a f", &swc) .err() .unwrap(), "1:6: Unexpected prop in spread (such as `{x}`): only a spread is supported (such as `{...x}`)", "should crash on invalid JS in an attribute expression (2)" ); assert_eq!( to_html_with_options("a } /> f", &swc)?, "a f
", "should support parenthesized expressions" ); Ok(()) } #[test] fn mdx_jsx_text_complete() -> Result<(), String> { let mdx = Options { parse: ParseOptions::mdx(), ..Default::default() }; assert_eq!( to_html_with_options("a c", &mdx)?, "a c
", "should support an unclosed element" ); assert_eq!( to_html_with_options("a <> c", &mdx)?, "a c
", "should support an unclosed fragment" ); assert_eq!( to_html_with_options("a < \t>b>", &mdx)?, "a < \t>b
", "should *not* support whitespace in the opening tag (fragment)" ); assert_eq!( to_html_with_options("a < \nb\t>b", &mdx)?, "a <\nb\t>b
", "should *not* support whitespace in the opening tag (named)" ); assert_eq!( to_html_with_options("a b", &mdx) .err() .unwrap(), "1:4: Unexpected character `!` (U+0021) before name, expected a character that can start a name, such as a letter, `$`, or `_` (note: to create a comment in MDX, use `{/* text */}`)", "should crash on a nonconforming start identifier" ); assert_eq!( to_html_with_options("a (> b.", &mdx) .err() .unwrap(), "1:5: Unexpected character `(` (U+0028) before name, expected a character that can start a name, such as a letter, `$`, or `_`", "should crash on a nonconforming start identifier in a closing tag" ); assert_eq!( to_html_with_options("a <π /> b.", &mdx)?, "a b.
", "should support non-ascii identifier start characters" ); assert_eq!( to_html_with_options("a <© /> b.", &mdx) .err() .unwrap(), "1:4: Unexpected character U+00A9 before name, expected a character that can start a name, such as a letter, `$`, or `_`", "should crash on non-conforming non-ascii identifier start characters" ); assert_eq!( to_html_with_options("a ", &mdx) .err() .unwrap(), "1:4: Unexpected character `!` (U+0021) before name, expected a character that can start a name, such as a letter, `$`, or `_` (note: to create a comment in MDX, use `{/* text */}`)", "should crash nicely on what might be a comment" ); assert_eq!( to_html_with_options("a / b\nc/>", &mdx) .err() .unwrap(), "1:5: Unexpected character `/` (U+002F) before name, expected a character that can start a name, such as a letter, `$`, or `_` (note: JS comments in JSX tags are not supported in MDX)", "should crash nicely on JS line comments inside tags (1)" ); assert_eq!( to_html_with_options("a ", &mdx) .err() .unwrap(), "1:6: Unexpected character `/` (U+002F) after self-closing slash, expected `>` to end the tag (note: JS comments in JSX tags are not supported in MDX)", "should crash nicely JS line comments inside tags (2)" ); assert_eq!( to_html_with_options("a *b*/c>", &mdx) .err() .unwrap(), "1:5: Unexpected character `*` (U+002A) before name, expected a character that can start a name, such as a letter, `$`, or `_`", "should crash nicely JS multiline comments inside tags (1)" ); assert_eq!( to_html_with_options("a ", &mdx) .err() .unwrap(), "1:6: Unexpected character `*` (U+002A) after self-closing slash, expected `>` to end the tag", "should crash nicely JS multiline comments inside tags (2)" ); assert_eq!( to_html_with_options("a b.", &mdx)?, "a b.
", "should support non-ascii identifier continuation characters" ); assert_eq!( to_html_with_options("a b.", &mdx) .err() .unwrap(), "1:5: Unexpected character U+00AC in name, expected a name character such as letters, digits, `$`, or `_`; whitespace before attributes; or the end of the tag", "should crash on non-conforming non-ascii identifier continuation characters" ); assert_eq!( to_html_with_options("a ", &mdx) .err() .unwrap(), "1:5: Unexpected character `@` (U+0040) in name, expected a name character such as letters, digits, `$`, or `_`; whitespace before attributes; or the end of the tag (note: to create a link in MDX, use `[text](url)`)", "should crash nicely on what might be an email link" ); assert_eq!( to_html_with_options("aa b.
", "should support dashes in names" ); assert_eq!( to_html_with_options("a c.", &mdx) .err() .unwrap(), "1:5: Unexpected character `?` (U+003F) in name, expected a name character such as letters, digits, `$`, or `_`; whitespace before attributes; or the end of the tag", "should crash on nonconforming identifier continuation characters" ); assert_eq!( to_html_with_options("aa b.
", "should support dots in names for method names" ); assert_eq!( to_html_with_options("aa b.
", "should support colons in names for local names" ); assert_eq!( to_html_with_options("aa c.
", "should support attribute expressions" ); assert_eq!( to_html_with_options("a c.", &mdx)?, "a c.
", "should support nested balanced braces in attribute expressions" ); assert_eq!( to_html_with_options(".", &mdx)?, ".
", "should support attribute expressions directly after a name" ); assert_eq!( to_html_with_options(".
", "should support attribute expressions directly after a member name" ); assert_eq!( to_html_with_options(".
", "should support attribute expressions directly after a local name" ); assert_eq!( to_html_with_options("a .", &mdx)?, "a .
", "should support attribute expressions directly after boolean attributes" ); assert_eq!( to_html_with_options("a .", &mdx)?, "a .
", "should support attribute expressions directly after boolean qualified attributes" ); assert_eq!( to_html_with_options("a c.", &mdx)?, "a c.
", "should support attribute expressions and normal attributes" ); assert_eq!( to_html_with_options("a c.", &mdx)?, "a c.
", "should support attributes" ); assert_eq!( to_html_with_options("a c.", &mdx) .err() .unwrap(), "1:12: Unexpected character `~` (U+007E) before attribute name, expected a character that can start an attribute name, such as a letter, `$`, or `_`; whitespace before attributes; or the end of the tag", "should crash on a nonconforming character before an attribute name" ); assert_eq!( to_html_with_options("a c.", &mdx) .err() .unwrap(), "1:7: Unexpected character `@` (U+0040) in attribute name, expected an attribute name character such as letters, digits, `$`, or `_`; `=` to initialize a value; whitespace before attributes; or the end of the tag", "should crash on a nonconforming character in attribute name" ); assert_eq!( to_html_with_options("a c.", &mdx)?, "a c.
", "should support prefixed attributes" ); assert_eq!( to_html_with_options("a .", &mdx)?, "a .
", "should support prefixed and normal attributes" ); assert_eq!( to_html_with_options("a c.", &mdx) .err() .unwrap(), "1:8: Unexpected character `1` (U+0031) after attribute name, expected a character that can start an attribute name, such as a letter, `$`, or `_`; `=` to initialize a value; or the end of the tag", "should crash on a nonconforming character after an attribute name" ); assert_eq!( to_html_with_options("a c.", &mdx) .err() .unwrap(), "1:8: Unexpected character `#` (U+0023) before local attribute name, expected a character that can start an attribute name, such as a letter, `$`, or `_`; `=` to initialize a value; or the end of the tag", "should crash on a nonconforming character to start a local attribute name" ); assert_eq!( to_html_with_options("a c.", &mdx) .err() .unwrap(), "1:9: Unexpected character `%` (U+0025) in local attribute name, expected an attribute name character such as letters, digits, `$`, or `_`; `=` to initialize a value; whitespace before attributes; or the end of the tag", "should crash on a nonconforming character in a local attribute name" ); assert_eq!( to_html_with_options("a c.", &mdx) .err() .unwrap(), "1:10: Unexpected character `^` (U+005E) after local attribute name, expected a character that can start an attribute name, such as a letter, `$`, or `_`; `=` to initialize a value; or the end of the tag", "should crash on a nonconforming character after a local attribute name" ); assert_eq!( to_html_with_options("a c.", &mdx)?, "a c.
", "should support attribute value expressions" ); assert_eq!( to_html_with_options("a c.", &mdx)?, "a c.
", "should support nested balanced braces in attribute value expressions" ); assert_eq!( to_html_with_options("a c.", &mdx) .err() .unwrap(), "1:8: Unexpected character `` ` `` (U+0060) before attribute value, expected a character that can start an attribute value, such as `\"`, `'`, or `{`", "should crash on a nonconforming character before an attribute value" ); assert_eq!( to_html_with_options("a > d.", &mdx) .err() .unwrap(), "1:8: Unexpected character `<` (U+003C) before attribute value, expected a character that can start an attribute value, such as `\"`, `'`, or `{` (note: to use an element or fragment as a prop value in MDX, use `{.
", "should support an attribute directly after a value" ); assert_eq!( to_html_with_options(".", &mdx)?, ".
", "should support an attribute directly after an attribute expression" ); assert_eq!( to_html_with_options("a c.", &mdx) .err() .unwrap(), "1:6: Unexpected character `b` (U+0062) after self-closing slash, expected `>` to end the tag", "should crash on a nonconforming character after a self-closing slash" ); assert_eq!( to_html_with_options(".", &mdx)?, ".
", "should support whitespace directly after closing slash" ); assert_eq!( to_html_with_options("a > c.", &mdx).err(), None, "should *not* crash on closing angle in text" ); assert_eq!( to_html_with_options("a <>`<`> c.", &mdx).err(), None, "should *not* crash on opening angle in tick code in an element" ); assert_eq!( to_html_with_options("a <>`` ``` ``>", &mdx).err(), None, "should *not* crash on ticks in tick code in an element" ); assert_eq!( to_html_with_options("a > c.", &mdx)?, "a c.
", "should support a closing tag w/o open elements" ); assert_eq!( to_html_with_options("a <>", &mdx)?, "a
", "should support mismatched tags (1)" ); assert_eq!( to_html_with_options("a >", &mdx)?, "a
", "should support mismatched tags (2)" ); assert_eq!( to_html_with_options("aa
", "should support mismatched tags (3)" ); assert_eq!( to_html_with_options("a ", &mdx)?, "a
", "should support mismatched tags (4)" ); assert_eq!( to_html_with_options("aa
", "should support mismatched tags (5)" ); assert_eq!( to_html_with_options("aa
", "should support mismatched tags (6)" ); assert_eq!( to_html_with_options("aa
", "should support mismatched tags (7)" ); assert_eq!( to_html_with_options("aa
", "should support mismatched tags (8)" ); assert_eq!( to_html_with_options("aa
", "should support mismatched tags (9)" ); assert_eq!( to_html_with_options("a b", &mdx)?, "a b
", "should support a closing self-closing tag" ); assert_eq!( to_html_with_options("a b", &mdx)?, "a b
", "should support a closing tag w/ attributes" ); assert_eq!( to_html_with_options("a <>b <>c> d>.", &mdx)?, "a b c d.
", "should support nested tags" ); assert_eq!( to_html_with_options( ".
", "should support character references in attribute values" ); assert_eq!( to_html_with_options( "Character references can be used: ", ', <, >, {, and }, they can be named, decimal, or hexadecimal: © ≠ 𝌆.
", "should support character references in text" ); assert_eq!( to_html_with_options(".
", "should support as text if the closing tag is not the last thing" ); assert_eq!( to_html_with_options("aa
", "should support as text if the opening is not the first thing" ); assert_eq!( to_html_with_options("a *open close* c.", &mdx)?, "a open close c.
", "should not care about precedence between attention (emphasis)" ); assert_eq!( to_html_with_options("a **open close** c.", &mdx)?, "a open close c.
", "should not care about precedence between attention (strong)" ); assert_eq!( to_html_with_options("a [open close](c) d.", &mdx)?, "a open close d.
", "should not care about precedence between label (link)" ); assert_eq!( to_html_with_options("a ![open close](c) d.", &mdx)?, "a d.
", "should not care about precedence between label (image)" ); assert_eq!( to_html_with_options("> a \n> c d.", &mdx)?, "\n", "should support line endings in elements" ); assert_eq!( to_html_with_options("> a f", &mdx)?, "a \nc d.
\n
\n", "should support line endings in attribute values" ); assert_eq!( to_html_with_options("> a f", &mdx)?, "a f
\n
\n", "should support line endings in attribute value expressions" ); assert_eq!( to_html_with_options("> a e", &mdx)?, "a f
\n
\n", "should support line endings in attribute expressions" ); assert_eq!( to_html_with_options("> a c", &mdx)?, "a e
\n
\n", "should support lazy text (1)" ); assert_eq!( to_html_with_options("> a e", &mdx)?, "a c
\n
\n", "should support lazy text (2)" ); assert_eq!( to_html_with_options("> a e", &mdx)?, "a e
\n
\n", "should support lazy text (3)" ); assert_eq!( to_html_with_options("> a f", &mdx)?, "a e
\n
\n", "should support lazy text (4)" ); assert_eq!( to_html_with_options("> a f", &mdx)?, "a f
\n
\n", "should support lazy text (5)" ); assert_eq!( to_html_with_options("1 < 3", &mdx)?, "a f
\n
1 < 3
", "should allow `<` followed by markdown whitespace as text in markdown" ); Ok(()) }