test_markdown.py 8.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. from typing import Type
  2. import pytest
  3. from reflex.components.component import Component, memo
  4. from reflex.components.datadisplay.code import CodeBlock
  5. from reflex.components.datadisplay.shiki_code_block import ShikiHighLevelCodeBlock
  6. from reflex.components.markdown.markdown import Markdown, MarkdownComponentMap
  7. from reflex.components.radix.themes.layout.box import Box
  8. from reflex.components.radix.themes.typography.heading import Heading
  9. from reflex.vars.base import Var
  10. class CustomMarkdownComponent(Component, MarkdownComponentMap):
  11. """A custom markdown component."""
  12. tag = "CustomMarkdownComponent"
  13. library = "custom"
  14. @classmethod
  15. def get_fn_args(cls) -> tuple[str, ...]:
  16. """Return the function arguments.
  17. Returns:
  18. The function arguments.
  19. """
  20. return ("custom_node", "custom_children", "custom_props")
  21. @classmethod
  22. def get_fn_body(cls) -> Var:
  23. """Return the function body.
  24. Returns:
  25. The function body.
  26. """
  27. return Var(_js_expr="{return custom_node + custom_children + custom_props}")
  28. def syntax_highlighter_memoized_component(codeblock: Type[Component]):
  29. @memo
  30. def code_block(code: str, language: str):
  31. return Box.create(
  32. codeblock.create(
  33. code,
  34. language=language,
  35. class_name="code-block",
  36. can_copy=True,
  37. ),
  38. class_name="relative mb-4",
  39. )
  40. def code_block_markdown(*children, **props):
  41. return code_block(
  42. code=children[0], language=props.pop("language", "plain"), **props
  43. )
  44. return code_block_markdown
  45. @pytest.mark.parametrize(
  46. "fn_body, fn_args, explicit_return, expected",
  47. [
  48. (
  49. None,
  50. None,
  51. False,
  52. Var(_js_expr="(({node, children, ...props}) => undefined)"),
  53. ),
  54. ("return node", ("node",), True, Var(_js_expr="(({node}) => {return node})")),
  55. (
  56. "return node + children",
  57. ("node", "children"),
  58. True,
  59. Var(_js_expr="(({node, children}) => {return node + children})"),
  60. ),
  61. (
  62. "return node + props",
  63. ("node", "...props"),
  64. True,
  65. Var(_js_expr="(({node, ...props}) => {return node + props})"),
  66. ),
  67. (
  68. "return node + children + props",
  69. ("node", "children", "...props"),
  70. True,
  71. Var(
  72. _js_expr="(({node, children, ...props}) => {return node + children + props})"
  73. ),
  74. ),
  75. ],
  76. )
  77. def test_create_map_fn_var(fn_body, fn_args, explicit_return, expected):
  78. result = MarkdownComponentMap.create_map_fn_var(
  79. fn_body=Var(_js_expr=fn_body, _var_type=str) if fn_body else None,
  80. fn_args=fn_args,
  81. explicit_return=explicit_return,
  82. )
  83. assert result._js_expr == expected._js_expr
  84. @pytest.mark.parametrize(
  85. ("cls", "fn_body", "fn_args", "explicit_return", "expected"),
  86. [
  87. (
  88. MarkdownComponentMap,
  89. None,
  90. None,
  91. False,
  92. Var(_js_expr="(({node, children, ...props}) => undefined)"),
  93. ),
  94. (
  95. MarkdownComponentMap,
  96. "return node",
  97. ("node",),
  98. True,
  99. Var(_js_expr="(({node}) => {return node})"),
  100. ),
  101. (
  102. CustomMarkdownComponent,
  103. None,
  104. None,
  105. True,
  106. Var(
  107. _js_expr="(({custom_node, custom_children, custom_props}) => {return custom_node + custom_children + custom_props})"
  108. ),
  109. ),
  110. (
  111. CustomMarkdownComponent,
  112. "return custom_node",
  113. ("custom_node",),
  114. True,
  115. Var(_js_expr="(({custom_node}) => {return custom_node})"),
  116. ),
  117. ],
  118. )
  119. def test_create_map_fn_var_subclass(cls, fn_body, fn_args, explicit_return, expected):
  120. result = cls.create_map_fn_var(
  121. fn_body=Var(_js_expr=fn_body, _var_type=int) if fn_body else None,
  122. fn_args=fn_args,
  123. explicit_return=explicit_return,
  124. )
  125. assert result._js_expr == expected._js_expr
  126. @pytest.mark.parametrize(
  127. "key,component_map, expected",
  128. [
  129. (
  130. "code",
  131. {},
  132. """(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); const _language = match ? match[1] : ''; if (_language) { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Error importing language module for ${_language}:`, error); } })(); } ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <SyntaxHighlighter children={((Array.isArray(children)) ? children.join("\\n") : children)} css={({ ["marginTop"] : "1em", ["marginBottom"] : "1em" })} customStyle={({ ["marginTop"] : "1em", ["marginBottom"] : "1em" })} language={_language} style={((resolvedColorMode === "light") ? oneLight : oneDark)} wrapLongLines={true} {...props}/> ); })""",
  133. ),
  134. (
  135. "code",
  136. {
  137. "codeblock": lambda value, **props: ShikiHighLevelCodeBlock.create(
  138. value, **props
  139. )
  140. },
  141. """(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); const _language = match ? match[1] : ''; ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <RadixThemesBox css={({ ["pre"] : ({ ["margin"] : "0", ["padding"] : "24px", ["background"] : "transparent", ["overflow-x"] : "auto", ["border-radius"] : "6px" }) })} {...props}><ShikiCode code={((Array.isArray(children)) ? children.join("\\n") : children)} decorations={[]} language={_language} theme={((resolvedColorMode === "light") ? "one-light" : "one-dark-pro")} transformers={[]}/></RadixThemesBox> ); })""",
  142. ),
  143. (
  144. "h1",
  145. {
  146. "h1": lambda value: CustomMarkdownComponent.create(
  147. Heading.create(value, as_="h1", size="6", margin_y="0.5em")
  148. )
  149. },
  150. """(({custom_node, custom_children, custom_props}) => (<CustomMarkdownComponent {...props}><RadixThemesHeading as={"h1"} css={({ ["marginTop"] : "0.5em", ["marginBottom"] : "0.5em" })} size={"6"}>{children}</RadixThemesHeading></CustomMarkdownComponent>))""",
  151. ),
  152. (
  153. "code",
  154. {"codeblock": syntax_highlighter_memoized_component(CodeBlock)},
  155. """(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); const _language = match ? match[1] : ''; if (_language) { (async () => { try { const module = await import(`react-syntax-highlighter/dist/cjs/languages/prism/${_language}`); SyntaxHighlighter.registerLanguage(_language, module.default); } catch (error) { console.error(`Error importing language module for ${_language}:`, error); } })(); } ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <CodeBlock code={((Array.isArray(children)) ? children.join("\\n") : children)} language={_language} {...props}/> ); })""",
  156. ),
  157. (
  158. "code",
  159. {
  160. "codeblock": syntax_highlighter_memoized_component(
  161. ShikiHighLevelCodeBlock
  162. )
  163. },
  164. """(({node, inline, className, children, ...props}) => { const match = (className || '').match(/language-(?<lang>.*)/); const _language = match ? match[1] : ''; ; return inline ? ( <RadixThemesCode {...props}>{children}</RadixThemesCode> ) : ( <CodeBlock code={((Array.isArray(children)) ? children.join("\\n") : children)} language={_language} {...props}/> ); })""",
  165. ),
  166. ],
  167. )
  168. def test_markdown_format_component(key, component_map, expected):
  169. markdown = Markdown.create("# header", component_map=component_map)
  170. result = markdown.format_component_map()
  171. assert str(result[key]) == expected