// vtest retry: 2 // vtest build: !windows module main import os import arrays import v.ast import document as doc import markdown const vexe_path = @VEXE const vexe_ = os.quoted_path(vexe_path) const tpath = os.join_path(os.vtmp_dir(), 'vod_test_module') fn testsuite_begin() { os.rmdir_all(tpath) or {} os.mkdir_all(tpath)! os.chdir(tpath)! } fn testsuite_end() { os.rmdir_all(tpath) or {} } fn test_trim_doc_node_description() { mod := 'foo' mut readme := '## Description `foo` is a module that provides tools and utility functions to assist in working with bar. It also assists with composing and testing baz.' expected := 'is a module that provides tools and utility functions to assist in working with' res := trim_doc_node_description(mod, readme).trim_space() assert res == expected readme = '# Foo `foo` is a module that provides tools and utility functions to assist in working with bar. It also assists with composing and testing baz.' res2 := trim_doc_node_description(mod, readme).trim_space() assert res2 == res } fn test_ignore_rules() { os.write_file('.vdocignore', ['pattern1', 'pattern2', '/path1'].join_lines())! os.mkdir('subdir')! os.write_file(os.join_path('subdir', '.vdocignore'), ['pattern3', '/path2'].join_lines())! rules := IgnoreRules.get('.') assert rules.patterns['.'] == ['pattern1', 'pattern2'] assert rules.patterns['./subdir'] == ['pattern3'] assert rules.paths == { './path1': true './subdir/path2': true } } fn test_get_module_list() { // For information on leading slash rules, refer to the comments in `IgnoreRules.get`. ignore_rules := ['bravo', '/echo', '/foxtrot/golf', 'hotel.v/', 'india/juliett'] os.write_file('.vdocignore', ignore_rules.join_lines())! /* Create some submodules. Modules inside `testdata` and `tests` directories and modules that only contain `_test.v` files should be ignored by default. */ // Modules NOT to ignore. submodules_no_ignore := [ 'alpha', 'alpha_bravo', // test `bravo` 'bravo_charly', // test `bravo` 'charly', 'charly/alpha', 'charly/delta', // test `delta` in separate ignore file in `alpha` 'charly/echo', // test `/echo` 'charly/foxtrot/golf', // test `/foxtrot/golf` 'foxtrot', 'golf', 'hotel', // will include a `hotel.v` file, whose pattern is in the ignore list with a trailing slash ] // Modules TO ignore. submodules_to_ignore := [ 'alpha/bravo', // test `bravo` 'alpha/delta', // test `delta` in separate ignore file 'alpha/india/juliett/kilo', // test `india/juliett` 'bravo', // test `bravo` 'echo', // test `/echo` 'foxtrot/golf', // test `/foxtrot/golf` 'hotel.v', // test `hotel.v/` 'tests', // test default 'testdata', // test default 'testdata/foxtrot', // test default ] for p in arrays.append(submodules_no_ignore, submodules_to_ignore) { os.mkdir_all(p)! mod_name := p.all_after_last('/') os.write_file(os.join_path(p, '${mod_name}.v'), 'module ${mod_name}')! } // Create a module that only contains a `_test.v` file. os.mkdir('delta')! os.write_file(os.join_path('delta', 'delta_test.v'), 'module delta')! // Add a `.vdocignore` file to a submodule. os.write_file(os.join_path('alpha', '.vdocignore'), 'delta\n')! mod_list := get_modules(tpath) // dump(mod_list) assert mod_list.len == submodules_no_ignore.len for m in submodules_no_ignore.map(os.join_path(tpath, it)) { assert m in mod_list } for m in submodules_to_ignore.map(os.join_path(tpath, it)) { assert m !in mod_list } // `delta` only contains a `_test.v` file. assert !mod_list.any(it.contains(os.join_path(tpath, 'delta'))) } fn test_html_highlight_escapes_html_tokens() { table := ast.new_table() code := 'fn main() { //

owned

assert 1 < 2 }' highlighted := html_highlight(code, table) assert highlighted.contains('// <h1>owned</h1>') assert !highlighted.contains('

owned

') assert highlighted.contains('<') } fn test_get_readme_md_src() { // a special testcase for `src` dir get_readme // https://github.com/vlang/v/issues/24232 os.mkdir('src')! os.write_file('v.mod', "Module { name: 'foobar' description: 'foobar' version: '0.0.0' license: 'MIT' dependencies: [] } ")! os.write_file('src/foobar.v', 'module foobar // square calculates the second power of `x` pub fn square(x int) int { return x * x } ')! res := os.execute_opt('${vexe_} doc -m src/ -v') or { panic(err) } assert res.exit_code == 0 assert res.output.contains('square') } fn test_gen_modules_toc_skips_hash_links_for_prefix_only_groups() { mut vd := VDoc{ cfg: Config{ is_multi: true } } vd.docs = [ doc.Doc{ head: doc.DocNode{ name: 'main' } }, doc.Doc{ head: doc.DocNode{ name: 'db.mysql' } }, doc.Doc{ head: doc.DocNode{ name: 'db.sqlite' } }, ] toc := vd.gen_modules_toc('main') assert !toc.contains('href="#"') assert toc.contains('') assert toc.contains('
  • mysql
  • ') } fn test_gen_modules_toc_uses_prefix_module_page_when_available() { mut vd := VDoc{ cfg: Config{ is_multi: true } } vd.docs = [ doc.Doc{ head: doc.DocNode{ name: 'db.sqlite' } }, doc.Doc{ head: doc.DocNode{ name: 'db' } }, ] toc := vd.gen_modules_toc('db') assert toc.contains('') } fn test_module_overview_uses_post_module_comment_without_readme() { mod_dir := 'module_overview' os.mkdir(mod_dir)! os.write_file(os.join_path(mod_dir, 'overview.v'), "module overview // `overview` uses the first comment after the module declaration as the module overview. pub fn greet() string { return 'hello' } ")! res := os.execute_opt('${vexe_} doc -no-timestamp -f text -o - -readme -comments ${os.quoted_path( './' + mod_dir)}') or { panic(err) } assert res.exit_code == 0 assert res.output.replace('\r\n', '\n').trim_space() == 'module overview `overview` uses the first comment after the module declaration as the module overview. fn greet() string' } fn test_html_keeps_enum_comment_after_top_level_comptime_if() { mod_dir := 'issue_23338' os.mkdir(mod_dir)! os.write_file(os.join_path(mod_dir, 'issue_23338.v'), 'module issue_23338 \$if macos { } // Foo lorem ipsum foo. pub enum Foo { foo } // Bar ipsum lorem bar. pub enum Bar { bar } ')! res := os.execute_opt('${vexe_} doc -no-timestamp -m -f html -o - -html-only-contents ${os.quoted_path( './' + mod_dir)}') or { panic(err) } assert res.exit_code == 0 output := res.output.replace('\r\n', '\n') assert output.contains('Foo lorem ipsum foo.') assert output.contains('Bar ipsum lorem bar.') } fn test_doc_generates_for_modules_without_public_symbols() { mod_dir := 'module_without_public_symbols' os.mkdir(mod_dir)! os.write_file(os.join_path(mod_dir, 'module_without_public_symbols.v'), 'module module_without_public_symbols const internal = 1 ')! res := os.execute_opt('${vexe_} doc -no-timestamp -f text -o - ${os.quoted_path('./' + mod_dir)}') or { panic(err) } assert res.exit_code == 0 assert res.output.replace('\r\n', '\n').trim_space() == 'module module_without_public_symbols' } fn test_resolve_relative_markdown_link() { base := 'https://github.com/vlang/v/blob/master/vlib/net/html/' assert resolve_relative_markdown_link(base, 'parser_test.v') == 'https://github.com/vlang/v/blob/master/vlib/net/html/parser_test.v' assert resolve_relative_markdown_link(base, './html_test.v') == 'https://github.com/vlang/v/blob/master/vlib/net/html/html_test.v' assert resolve_relative_markdown_link(base, '../README.md#usage') == 'https://github.com/vlang/v/blob/master/vlib/net/README.md#usage' } fn test_resolve_relative_markdown_link_keeps_absolute_urls() { base := 'https://github.com/vlang/v/blob/master/vlib/net/html/' assert resolve_relative_markdown_link(base, 'https://vlang.io') == 'https://vlang.io' assert resolve_relative_markdown_link(base, '/rooted/path') == '/rooted/path' assert resolve_relative_markdown_link(base, '#local') == '#local' } fn test_markdown_renderer_resolves_relative_links() ! { base := 'https://github.com/vlang/v/blob/master/vlib/net/html/' mut renderer := markdown.HtmlRenderer{ transformer: &MdHtmlCodeHighlighter{ table: ast.new_table() relative_link_base: base } } out := markdown.render('More examples in [parser](parser_test.v).', mut renderer)! assert out.contains('') } fn test_prepare_markdown_for_html_preserves_blockquote_linebreaks() ! { mut renderer := markdown.HtmlRenderer{ transformer: &MdHtmlCodeHighlighter{ table: ast.new_table() } } out := markdown.render(prepare_markdown_for_html('> **Note**\n> line one\n> line two'), mut renderer)! assert out.contains('
    ') assert out.contains('Note
    line one
    line two') } fn test_prepare_markdown_for_html_skips_fenced_code_blocks() { input := '```sh\n> prompt\n> next\n```' assert prepare_markdown_for_html(input) == input } fn test_markdown_renderer_preserves_wrapped_readme_markdown() ! { 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`.' mut renderer := markdown.HtmlRenderer{ transformer: &MdHtmlCodeHighlighter{ table: ast.new_table() } } out := markdown.render(prepare_markdown_for_html(input), mut renderer)! assert !out.contains('tokens.In') assert !out.contains('mostsimple') assert !out.contains('notabc') assert out.contains('tokens. In a query string a simple character is a token.') assert out.contains('the most simple token') assert out.contains('is not abc OR ebc') }