@@ -47,6 +47,179 @@ defmodule ExDoc.DocASTTest do
4747 end
4848 end
4949
50+ describe "to_markdown/1" do
51+ test "converts simple text" do
52+ assert DocAST . to_markdown ( "hello world" ) == "hello world"
53+ end
54+
55+ test "escapes HTML entities in text" do
56+ assert DocAST . to_markdown ( "<script>alert('xss')</script>" ) ==
57+ "<script>alert('xss')</script>"
58+
59+ assert DocAST . to_markdown ( "Tom & Jerry" ) == "Tom & Jerry"
60+ end
61+
62+ test "converts lists of elements" do
63+ ast = [ "Hello " , "world" , "!" ]
64+ assert DocAST . to_markdown ( ast ) == "Hello world!"
65+ end
66+
67+ test "converts paragraphs" do
68+ ast = { :p , [ ] , [ "Hello world" ] , % { } }
69+ assert DocAST . to_markdown ( ast ) == "Hello world\n \n "
70+ end
71+
72+ test "converts multiple paragraphs" do
73+ ast = [
74+ { :p , [ ] , [ "First paragraph" ] , % { } } ,
75+ { :p , [ ] , [ "Second paragraph" ] , % { } }
76+ ]
77+
78+ assert DocAST . to_markdown ( ast ) == "First paragraph\n \n Second paragraph\n \n "
79+ end
80+
81+ test "converts code blocks with language" do
82+ ast = { :code , [ class: "elixir" ] , "defmodule Test do\n def hello, do: :world\n end" , % { } }
83+ expected = "```elixir\n defmodule Test do\n def hello, do: :world\n end\n ```\n "
84+ assert DocAST . to_markdown ( ast ) == expected
85+ end
86+
87+ test "converts code blocks without language" do
88+ ast = { :code , [ ] , "some code" , % { } }
89+ assert DocAST . to_markdown ( ast ) == "```\n some code\n ```\n "
90+ end
91+
92+ test "converts inline code with class attribute" do
93+ ast = { :code , [ class: "language-elixir" ] , "IO.puts" , % { } }
94+ expected = "```language-elixir\n IO.puts\n ```\n "
95+ assert DocAST . to_markdown ( ast ) == expected
96+ end
97+
98+ test "converts links" do
99+ ast = { :a , [ href: "https://example.com" ] , [ "Example" ] , % { } }
100+ assert DocAST . to_markdown ( ast ) == "[Example](https://example.com)"
101+ end
102+
103+ test "converts links with nested content" do
104+ ast = { :a , [ href: "/docs" ] , [ { :code , [ ] , [ "API" ] , % { } } ] , % { } }
105+ assert DocAST . to_markdown ( ast ) == "[```\n API\n ```\n ](/docs)"
106+ end
107+
108+ test "converts images with alt and title" do
109+ ast = { :img , [ src: "image.png" , alt: "Alt text" , title: "Title" ] , [ ] , % { } }
110+ assert DocAST . to_markdown ( ast ) == ""
111+ end
112+
113+ test "converts images with missing attributes" do
114+ ast = { :img , [ src: "image.png" ] , [ ] , % { } }
115+ assert DocAST . to_markdown ( ast ) == ""
116+ end
117+
118+ test "converts horizontal rules" do
119+ ast = { :hr , [ ] , [ ] , % { } }
120+ assert DocAST . to_markdown ( ast ) == "\n \n ---\n \n "
121+ end
122+
123+ test "converts line breaks" do
124+ ast = { :br , [ ] , [ ] , % { } }
125+ assert DocAST . to_markdown ( ast ) == "\n \n "
126+ end
127+
128+ test "converts comments" do
129+ ast = { :comment , [ ] , [ " This is a comment " ] , % { } }
130+ assert DocAST . to_markdown ( ast ) == "<!-- This is a comment -->"
131+ end
132+
133+ test "handles void elements" do
134+ void_elements = [
135+ :area ,
136+ :base ,
137+ :col ,
138+ :embed ,
139+ :input ,
140+ :link ,
141+ :meta ,
142+ :param ,
143+ :source ,
144+ :track ,
145+ :wbr
146+ ]
147+
148+ for element <- void_elements do
149+ ast = { element , [ ] , [ ] , % { } }
150+ assert DocAST . to_markdown ( ast ) == ""
151+ end
152+ end
153+
154+ test "handles verbatim content" do
155+ ast = { :pre , [ ] , [ " verbatim \n content " ] , % { verbatim: true } }
156+ assert DocAST . to_markdown ( ast ) == " verbatim \n content "
157+ end
158+
159+ test "converts nested structures" do
160+ ast = { :p , [ ] , [ "Hello " , { :strong , [ ] , [ "world" ] , % { } } , "!" ] , % { } }
161+
162+ result = DocAST . to_markdown ( ast )
163+ assert result =~ "Hello"
164+ assert result =~ "world"
165+ assert result =~ "!"
166+ assert String . ends_with? ( result , "\n \n " )
167+ end
168+
169+ test "handles unknown elements by extracting content" do
170+ ast = { :custom_element , [ class: "special" ] , [ "Content" ] , % { } }
171+ assert DocAST . to_markdown ( ast ) == "Content"
172+ end
173+
174+ test "handles complex nested document" do
175+ ast = [
176+ { :h1 , [ ] , [ "Main Title" ] , % { } } ,
177+ { :p , [ ] , [ "Introduction paragraph with " , { :a , [ href: "/link" ] , [ "a link" ] , % { } } ] , % { } } ,
178+ { :code , [ class: "elixir" ] , "IO.puts \" Hello\" " , % { } } ,
179+ { :hr , [ ] , [ ] , % { } } ,
180+ { :p , [ ] , [ "Final paragraph" ] , % { } }
181+ ]
182+
183+ result = DocAST . to_markdown ( ast )
184+
185+ assert result =~ "Main Title"
186+ assert result =~ "Introduction paragraph with [a link](/link)"
187+ assert result =~ "```elixir\n IO.puts \" Hello\" \n ```\n "
188+ assert result =~ "\n \n ---\n \n "
189+ assert result =~ "Final paragraph\n \n "
190+ end
191+
192+ test "handles empty content gracefully" do
193+ assert DocAST . to_markdown ( [ ] ) == ""
194+ assert DocAST . to_markdown ( { :p , [ ] , [ ] , % { } } ) == "\n \n "
195+ end
196+
197+ test "preserves whitespace in code blocks" do
198+ code_content = " def hello do\n :world\n end"
199+ ast = { :code , [ class: "elixir" ] , code_content , % { } }
200+ result = DocAST . to_markdown ( ast )
201+
202+ assert result =~ "```elixir"
203+ assert String . contains? ( result , code_content )
204+ assert result =~ "```"
205+ end
206+
207+ test "handles mixed content types" do
208+ ast = [
209+ "Plain text" ,
210+ { :p , [ ] , [ "Paragraph text" ] , % { } } ,
211+ { :code , [ ] , "code" , % { } } ,
212+ "More plain text"
213+ ]
214+
215+ result = DocAST . to_markdown ( ast )
216+ assert result =~ "Plain text"
217+ assert result =~ "Paragraph text\n \n "
218+ assert result =~ "```\n code\n ```\n "
219+ assert result =~ "More plain text"
220+ end
221+ end
222+
50223 describe "to_string/2" do
51224 test "simple" do
52225 markdown = """
@@ -161,21 +334,18 @@ defmodule ExDoc.DocASTTest do
161334
162335 describe "highlight" do
163336 test "with default class" do
164- # Four spaces
165337 assert highlight ( """
166338 mix run --no-halt path/to/file.exs
167339 """ ) =~
168340 ~r{ <pre><code class=\" makeup elixir\" translate="no">.*}
169341
170- # Code block without language
171342 assert highlight ( """
172343 ```
173344 mix run --no-halt path/to/file.exs</code></pre>
174345 ```
175346 """ ) =~
176347 ~r{ <pre><code class=\" makeup elixir\" translate="no">.*}
177348
178- # Pre IAL
179349 assert highlight ( """
180350 ```
181351 mix run --no-halt path/to/file.exs</code></pre>
@@ -184,23 +354,20 @@ defmodule ExDoc.DocASTTest do
184354 """ ) =~
185355 ~r{ <pre class="wrap"><code class=\" makeup elixir\" translate="no">.*}
186356
187- # Code with language
188357 assert highlight ( """
189358 ```html
190359 <foo />
191360 ```
192361 """ ) =~
193362 ~r{ <pre><code class=\" makeup html\" translate="no">.*}
194363
195- # Code with shell detection
196364 assert highlight ( """
197365 ```
198366 $ hello
199367 ```
200368 """ ) =~
201369 ~r{ <pre><code class=\" makeup shell\" translate="no"><span class="gp unselectable">\$ .*}
202370
203- # Nested in another element
204371 assert highlight ( """
205372 > ```elixir
206373 > hello
0 commit comments