v / cmd / tools / vdoc / vdoc_test.v
319 lines · 286 sloc · 9.85 KB · 57238a1c6da6e94fbe5373add35e273d4430d34c
Raw
1// vtest retry: 2
2// vtest build: !windows
3module main
4
5import os
6import arrays
7import v.ast
8import document as doc
9import markdown
10
11const vexe_path = @VEXE
12const vexe_ = os.quoted_path(vexe_path)
13const tpath = os.join_path(os.vtmp_dir(), 'vod_test_module')
14
15fn testsuite_begin() {
16 os.rmdir_all(tpath) or {}
17 os.mkdir_all(tpath)!
18 os.chdir(tpath)!
19}
20
21fn testsuite_end() {
22 os.rmdir_all(tpath) or {}
23}
24
25fn test_trim_doc_node_description() {
26 mod := 'foo'
27 mut readme := '## Description
28
29`foo` is a module that provides tools and utility functions to assist in working with bar.
30It also assists with composing and testing baz.'
31 expected := 'is a module that provides tools and utility functions to assist in working with'
32 res := trim_doc_node_description(mod, readme).trim_space()
33 assert res == expected
34
35 readme = '# Foo
36`foo` is a module that provides tools and utility functions to assist in working with bar.
37It also assists with composing and testing baz.'
38 res2 := trim_doc_node_description(mod, readme).trim_space()
39 assert res2 == res
40}
41
42fn test_ignore_rules() {
43 os.write_file('.vdocignore', ['pattern1', 'pattern2', '/path1'].join_lines())!
44 os.mkdir('subdir')!
45 os.write_file(os.join_path('subdir', '.vdocignore'), ['pattern3', '/path2'].join_lines())!
46 rules := IgnoreRules.get('.')
47 assert rules.patterns['.'] == ['pattern1', 'pattern2']
48 assert rules.patterns['./subdir'] == ['pattern3']
49 assert rules.paths == {
50 './path1': true
51 './subdir/path2': true
52 }
53}
54
55fn test_get_module_list() {
56 // For information on leading slash rules, refer to the comments in `IgnoreRules.get`.
57 ignore_rules := ['bravo', '/echo', '/foxtrot/golf', 'hotel.v/', 'india/juliett']
58 os.write_file('.vdocignore', ignore_rules.join_lines())!
59
60 /* Create some submodules.
61 Modules inside `testdata` and `tests` directories and modules that
62 only contain `_test.v` files should be ignored by default. */
63 // Modules NOT to ignore.
64 submodules_no_ignore := [
65 'alpha',
66 'alpha_bravo', // test `bravo`
67 'bravo_charly', // test `bravo`
68 'charly',
69 'charly/alpha',
70 'charly/delta', // test `delta` in separate ignore file in `alpha`
71 'charly/echo', // test `/echo`
72 'charly/foxtrot/golf', // test `/foxtrot/golf`
73 'foxtrot',
74 'golf',
75 'hotel', // will include a `hotel.v` file, whose pattern is in the ignore list with a trailing slash
76 ]
77 // Modules TO ignore.
78 submodules_to_ignore := [
79 'alpha/bravo', // test `bravo`
80 'alpha/delta', // test `delta` in separate ignore file
81 'alpha/india/juliett/kilo', // test `india/juliett`
82 'bravo', // test `bravo`
83 'echo', // test `/echo`
84 'foxtrot/golf', // test `/foxtrot/golf`
85 'hotel.v', // test `hotel.v/`
86 'tests', // test default
87 'testdata', // test default
88 'testdata/foxtrot', // test default
89 ]
90 for p in arrays.append(submodules_no_ignore, submodules_to_ignore) {
91 os.mkdir_all(p)!
92 mod_name := p.all_after_last('/')
93 os.write_file(os.join_path(p, '${mod_name}.v'), 'module ${mod_name}')!
94 }
95 // Create a module that only contains a `_test.v` file.
96 os.mkdir('delta')!
97 os.write_file(os.join_path('delta', 'delta_test.v'), 'module delta')!
98 // Add a `.vdocignore` file to a submodule.
99 os.write_file(os.join_path('alpha', '.vdocignore'), 'delta\n')!
100
101 mod_list := get_modules(tpath)
102 // dump(mod_list)
103 assert mod_list.len == submodules_no_ignore.len
104 for m in submodules_no_ignore.map(os.join_path(tpath, it)) {
105 assert m in mod_list
106 }
107 for m in submodules_to_ignore.map(os.join_path(tpath, it)) {
108 assert m !in mod_list
109 }
110 // `delta` only contains a `_test.v` file.
111 assert !mod_list.any(it.contains(os.join_path(tpath, 'delta')))
112}
113
114fn test_html_highlight_escapes_html_tokens() {
115 table := ast.new_table()
116 code := 'fn main() {
117 // <h1>owned</h1>
118 assert 1 < 2
119}'
120 highlighted := html_highlight(code, table)
121 assert highlighted.contains('// <h1>owned</h1>')
122 assert !highlighted.contains('<h1>owned</h1>')
123 assert highlighted.contains('<span class="token operator"><</span>')
124}
125
126fn test_get_readme_md_src() {
127 // a special testcase for `src` dir get_readme
128 // https://github.com/vlang/v/issues/24232
129
130 os.mkdir('src')!
131 os.write_file('v.mod', "Module {
132 name: 'foobar'
133 description: 'foobar'
134 version: '0.0.0'
135 license: 'MIT'
136 dependencies: []
137}
138")!
139 os.write_file('src/foobar.v', 'module foobar
140
141// square calculates the second power of `x`
142pub fn square(x int) int {
143 return x * x
144}
145')!
146 res := os.execute_opt('${vexe_} doc -m src/ -v') or { panic(err) }
147 assert res.exit_code == 0
148 assert res.output.contains('square')
149}
150
151fn test_gen_modules_toc_skips_hash_links_for_prefix_only_groups() {
152 mut vd := VDoc{
153 cfg: Config{
154 is_multi: true
155 }
156 }
157 vd.docs = [
158 doc.Doc{
159 head: doc.DocNode{
160 name: 'main'
161 }
162 },
163 doc.Doc{
164 head: doc.DocNode{
165 name: 'db.mysql'
166 }
167 },
168 doc.Doc{
169 head: doc.DocNode{
170 name: 'db.sqlite'
171 }
172 },
173 ]
174 toc := vd.gen_modules_toc('main')
175 assert !toc.contains('href="#"')
176 assert toc.contains('<div class="menu-row"><a>db</a></div>')
177 assert toc.contains('<li><a href="./db.mysql.html">mysql</a></li>')
178}
179
180fn test_gen_modules_toc_uses_prefix_module_page_when_available() {
181 mut vd := VDoc{
182 cfg: Config{
183 is_multi: true
184 }
185 }
186 vd.docs = [
187 doc.Doc{
188 head: doc.DocNode{
189 name: 'db.sqlite'
190 }
191 },
192 doc.Doc{
193 head: doc.DocNode{
194 name: 'db'
195 }
196 },
197 ]
198 toc := vd.gen_modules_toc('db')
199 assert toc.contains('<div class="menu-row"><a href="./db.html">db</a></div>')
200}
201
202fn test_module_overview_uses_post_module_comment_without_readme() {
203 mod_dir := 'module_overview'
204 os.mkdir(mod_dir)!
205 os.write_file(os.join_path(mod_dir, 'overview.v'), "module overview
206
207// `overview` uses the first comment after the module declaration as the module overview.
208
209pub fn greet() string {
210 return 'hello'
211}
212")!
213 res := os.execute_opt('${vexe_} doc -no-timestamp -f text -o - -readme -comments ${os.quoted_path(
214 './' + mod_dir)}') or { panic(err) }
215 assert res.exit_code == 0
216 assert res.output.replace('\r\n', '\n').trim_space() == 'module overview
217 `overview` uses the first comment after the module declaration as the module overview.
218
219fn greet() string'
220}
221
222fn test_html_keeps_enum_comment_after_top_level_comptime_if() {
223 mod_dir := 'issue_23338'
224 os.mkdir(mod_dir)!
225 os.write_file(os.join_path(mod_dir, 'issue_23338.v'), 'module issue_23338
226
227\$if macos {
228}
229
230// Foo lorem ipsum foo.
231pub enum Foo {
232 foo
233}
234
235// Bar ipsum lorem bar.
236pub enum Bar {
237 bar
238}
239')!
240 res := os.execute_opt('${vexe_} doc -no-timestamp -m -f html -o - -html-only-contents ${os.quoted_path(
241 './' + mod_dir)}') or { panic(err) }
242 assert res.exit_code == 0
243 output := res.output.replace('\r\n', '\n')
244 assert output.contains('Foo lorem ipsum foo.')
245 assert output.contains('Bar ipsum lorem bar.')
246}
247
248fn test_doc_generates_for_modules_without_public_symbols() {
249 mod_dir := 'module_without_public_symbols'
250 os.mkdir(mod_dir)!
251 os.write_file(os.join_path(mod_dir, 'module_without_public_symbols.v'), 'module module_without_public_symbols
252
253const internal = 1
254')!
255 res := os.execute_opt('${vexe_} doc -no-timestamp -f text -o - ${os.quoted_path('./' + mod_dir)}') or {
256 panic(err)
257 }
258 assert res.exit_code == 0
259 assert res.output.replace('\r\n', '\n').trim_space() == 'module module_without_public_symbols'
260}
261
262fn test_resolve_relative_markdown_link() {
263 base := 'https://github.com/vlang/v/blob/master/vlib/net/html/'
264 assert resolve_relative_markdown_link(base, 'parser_test.v') == 'https://github.com/vlang/v/blob/master/vlib/net/html/parser_test.v'
265 assert resolve_relative_markdown_link(base, './html_test.v') == 'https://github.com/vlang/v/blob/master/vlib/net/html/html_test.v'
266 assert resolve_relative_markdown_link(base, '../README.md#usage') == 'https://github.com/vlang/v/blob/master/vlib/net/README.md#usage'
267}
268
269fn test_resolve_relative_markdown_link_keeps_absolute_urls() {
270 base := 'https://github.com/vlang/v/blob/master/vlib/net/html/'
271 assert resolve_relative_markdown_link(base, 'https://vlang.io') == 'https://vlang.io'
272 assert resolve_relative_markdown_link(base, '/rooted/path') == '/rooted/path'
273 assert resolve_relative_markdown_link(base, '#local') == '#local'
274}
275
276fn test_markdown_renderer_resolves_relative_links() ! {
277 base := 'https://github.com/vlang/v/blob/master/vlib/net/html/'
278 mut renderer := markdown.HtmlRenderer{
279 transformer: &MdHtmlCodeHighlighter{
280 table: ast.new_table()
281 relative_link_base: base
282 }
283 }
284 out := markdown.render('More examples in [parser](parser_test.v).', mut renderer)!
285 assert out.contains('<a href="https://github.com/vlang/v/blob/master/vlib/net/html/parser_test.v">')
286}
287
288fn test_prepare_markdown_for_html_preserves_blockquote_linebreaks() ! {
289 mut renderer := markdown.HtmlRenderer{
290 transformer: &MdHtmlCodeHighlighter{
291 table: ast.new_table()
292 }
293 }
294 out := markdown.render(prepare_markdown_for_html('> **Note**\n> line one\n> line two'), mut
295 renderer)!
296 assert out.contains('<blockquote>')
297 assert out.contains('<strong>Note</strong><br />line one<br />line two')
298}
299
300fn test_prepare_markdown_for_html_skips_fenced_code_blocks() {
301 input := '```sh\n> prompt\n> next\n```'
302 assert prepare_markdown_for_html(input) == input
303}
304
305fn test_markdown_renderer_preserves_wrapped_readme_markdown() ! {
306 input := '1. The basic atomic elements of this regex engine are the tokens.\n In a query string a simple character is a token.\n\n- The basic element **is the token not the sequence of symbols**,\n and the most simple token, is a single character.\n\n- `|` **the OR operator acts on tokens,** for example `abc|ebc` is not\n `abc` OR `ebc`.'
307 mut renderer := markdown.HtmlRenderer{
308 transformer: &MdHtmlCodeHighlighter{
309 table: ast.new_table()
310 }
311 }
312 out := markdown.render(prepare_markdown_for_html(input), mut renderer)!
313 assert !out.contains('tokens.In')
314 assert !out.contains('mostsimple')
315 assert !out.contains('not<code>abc</code>')
316 assert out.contains('tokens. In a query string a simple character is a token.')
317 assert out.contains('the most simple token')
318 assert out.contains('is not <code>abc</code> OR <code>ebc</code>')
319}
320