"Fossies" - the Fresh Open Source Software Archive

Member "asciidoctor-2.0.10/test/document_test.rb" (1 Jun 2019, 71737 Bytes) of package /linux/www/asciidoctor-2.0.10.tar.gz:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Ruby source code syntax highlighting (style: standard) with prefixed line numbers and code folding option. Alternatively you can here view or download the uninterpreted source code file. See also the last Fossies "Diffs" side-by-side code changes report for "document_test.rb": 2.0.8_vs_2.0.9.

    1 # frozen_string_literal: true
    2 require_relative 'test_helper'
    3 
    4 BUILT_IN_ELEMENTS = %w(admonition audio colist dlist document embedded example floating_title image inline_anchor inline_break inline_button inline_callout inline_footnote inline_image inline_indexterm inline_kbd inline_menu inline_quoted listing literal stem olist open page_break paragraph pass preamble quote section sidebar table thematic_break toc ulist verse video)
    5 
    6 context 'Document' do
    7 
    8   context 'Example document' do
    9     test 'document title' do
   10       doc = example_document(:asciidoc_index)
   11       assert_equal 'AsciiDoc Home Page', doc.doctitle
   12       assert_equal 'AsciiDoc Home Page', doc.name
   13       refute_nil doc.header
   14       assert_equal :section, doc.header.context
   15       assert_equal 'header', doc.header.sectname
   16       assert_equal 14, doc.blocks.size
   17       assert_equal :preamble, doc.blocks[0].context
   18       assert_equal :section, doc.blocks[1].context
   19 
   20       # verify compat-mode is set when atx-style doctitle is used
   21       result = doc.blocks[0].convert
   22       assert_xpath %q(//em[text()="Stuart Rackham"]), result, 1
   23     end
   24   end
   25 
   26   context 'Default settings' do
   27     test 'safe mode level set to SECURE by default' do
   28       doc = empty_document
   29       assert_equal Asciidoctor::SafeMode::SECURE, doc.safe
   30     end
   31 
   32     test 'safe mode level set using string' do
   33       doc = empty_document safe: 'server'
   34       assert_equal Asciidoctor::SafeMode::SERVER, doc.safe
   35 
   36       doc = empty_document safe: 'foo'
   37       assert_equal Asciidoctor::SafeMode::SECURE, doc.safe
   38     end
   39 
   40     test 'safe mode level set using symbol' do
   41       doc = empty_document safe: :server
   42       assert_equal Asciidoctor::SafeMode::SERVER, doc.safe
   43 
   44       doc = empty_document safe: :foo
   45       assert_equal Asciidoctor::SafeMode::SECURE, doc.safe
   46     end
   47 
   48     test 'safe mode level set using integer' do
   49       doc = empty_document safe: 10
   50       assert_equal Asciidoctor::SafeMode::SERVER, doc.safe
   51 
   52       doc = empty_document safe: 100
   53       assert_equal 100, doc.safe
   54     end
   55 
   56     test 'safe mode attributes are set on document' do
   57       doc = empty_document
   58       assert_equal Asciidoctor::SafeMode::SECURE, doc.attr('safe-mode-level')
   59       assert_equal 'secure', doc.attr('safe-mode-name')
   60       assert doc.attr?('safe-mode-secure')
   61       refute doc.attr?('safe-mode-unsafe')
   62       refute doc.attr?('safe-mode-safe')
   63       refute doc.attr?('safe-mode-server')
   64     end
   65 
   66     test 'safe mode level can be set in the constructor' do
   67       doc = Asciidoctor::Document.new [], safe: Asciidoctor::SafeMode::SAFE
   68       assert_equal Asciidoctor::SafeMode::SAFE, doc.safe
   69     end
   70 
   71     test 'safe model level cannot be modified' do
   72       doc = empty_document
   73       begin
   74         doc.safe = Asciidoctor::SafeMode::UNSAFE
   75         flunk 'safe mode property of Asciidoctor::Document should not be writable!'
   76       rescue
   77       end
   78     end
   79 
   80     test 'toc and sectnums should be enabled by default in DocBook backend' do
   81       doc = document_from_string 'content', backend: 'docbook', parse: true
   82       assert doc.attr?('toc')
   83       assert doc.attr?('sectnums')
   84       result = doc.convert
   85       assert_match('<?asciidoc-toc?>', result)
   86       assert_match('<?asciidoc-numbered?>', result)
   87     end
   88 
   89     test 'maxdepth attribute should be set on asciidoc-toc and asciidoc-numbered processing instructions in DocBook backend' do
   90       doc = document_from_string 'content', backend: 'docbook', parse: true, attributes: { 'toclevels' => '1', 'sectnumlevels' => '1' }
   91       assert doc.attr?('toc')
   92       assert doc.attr?('sectnums')
   93       result = doc.convert
   94       assert_match('<?asciidoc-toc maxdepth="1"?>', result)
   95       assert_match('<?asciidoc-numbered maxdepth="1"?>', result)
   96     end
   97 
   98     test 'should be able to disable toc and sectnums in document header in DocBook backend' do
   99       input = <<~'EOS'
  100       = Document Title
  101       :toc!:
  102       :sectnums!:
  103       EOS
  104       doc = document_from_string input, backend: 'docbook'
  105       refute doc.attr?('toc')
  106       refute doc.attr?('sectnums')
  107     end
  108 
  109     test 'noheader attribute should suppress info element when converting to DocBook' do
  110       input = <<~'EOS'
  111       = Document Title
  112       :noheader:
  113 
  114       content
  115       EOS
  116       result = convert_string input, backend: 'docbook'
  117       assert_xpath '/article', result, 1
  118       assert_xpath '/article/info', result, 0
  119     end
  120 
  121     test 'should be able to disable section numbering using numbered attribute in document header in DocBook backend' do
  122       input = <<~'EOS'
  123       = Document Title
  124       :numbered!:
  125       EOS
  126       doc = document_from_string input, backend: 'docbook'
  127       refute doc.attr?('sectnums')
  128     end
  129   end
  130 
  131   context 'Docinfo files' do
  132     test 'should include docinfo files for html backend' do
  133       sample_input_path = fixture_path('basic.adoc')
  134 
  135       cases = {
  136         'docinfo'                => { head_script: 1, meta: 0, top_link: 0, footer_script: 1, navbar: 1 },
  137         'docinfo=private'        => { head_script: 1, meta: 0, top_link: 0, footer_script: 1, navbar: 1 },
  138         'docinfo1'               => { head_script: 0, meta: 1, top_link: 1, footer_script: 0, navbar: 0 },
  139         'docinfo=shared'         => { head_script: 0, meta: 1, top_link: 1, footer_script: 0, navbar: 0 },
  140         'docinfo2'               => { head_script: 1, meta: 1, top_link: 1, footer_script: 1, navbar: 1 },
  141         'docinfo docinfo2'       => { head_script: 1, meta: 1, top_link: 1, footer_script: 1, navbar: 1 },
  142         'docinfo=private,shared' => { head_script: 1, meta: 1, top_link: 1, footer_script: 1, navbar: 1 },
  143         'docinfo=private-head'   => { head_script: 1, meta: 0, top_link: 0, footer_script: 0, navbar: 0 },
  144         'docinfo=private-header' => { head_script: 0, meta: 0, top_link: 0, footer_script: 0, navbar: 1 },
  145         'docinfo=shared-head'    => { head_script: 0, meta: 1, top_link: 0, footer_script: 0, navbar: 0 },
  146         'docinfo=private-footer' => { head_script: 0, meta: 0, top_link: 0, footer_script: 1, navbar: 0 },
  147         'docinfo=shared-footer'  => { head_script: 0, meta: 0, top_link: 1, footer_script: 0, navbar: 0 },
  148         'docinfo=private-head\ ,\ shared-footer' => { head_script: 1, meta: 0, top_link: 1, footer_script: 0, navbar: 0 },
  149       }
  150 
  151       cases.each do |attr_val, markup|
  152         output = Asciidoctor.convert_file sample_input_path, to_file: false,
  153             standalone: true, safe: Asciidoctor::SafeMode::SERVER, attributes: %(linkcss copycss! #{attr_val})
  154         refute_empty output
  155         assert_css 'script[src="modernizr.js"]', output, markup[:head_script]
  156         assert_css 'meta[http-equiv="imagetoolbar"]', output, markup[:meta]
  157         assert_css 'body > a#top', output, markup[:top_link]
  158         assert_css 'body > script', output, markup[:footer_script]
  159         assert_css 'body > nav.navbar', output, markup[:navbar]
  160         assert_css 'body > nav.navbar + #header', output, markup[:navbar]
  161       end
  162     end
  163 
  164     test 'should include docinfo header even if noheader attribute is set' do
  165       sample_input_path = fixture_path('basic.adoc')
  166       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  167           standalone: true, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo' => 'private-header', 'noheader' => '' }
  168       refute_empty output
  169       assert_css 'body > nav.navbar', output, 1
  170       assert_css 'body > nav.navbar + #content', output, 1
  171     end
  172 
  173     test 'should include docinfo footer even if nofooter attribute is set' do
  174       sample_input_path = fixture_path('basic.adoc')
  175       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  176           standalone: true, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo1' => '', 'nofooter' => '' }
  177       refute_empty output
  178       assert_css 'body > a#top', output, 1
  179     end
  180 
  181     test 'should include user docinfo after built-in docinfo' do
  182       sample_input_path = fixture_path 'basic.adoc'
  183       attrs = { 'docinfo' => 'shared', 'source-highlighter' => 'highlight.js', 'linkcss' => '', 'copycss' => nil }
  184       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  185           standalone: true, safe: :safe, attributes: attrs
  186       assert_css 'link[rel=stylesheet] + meta[http-equiv=imagetoolbar]', output, 1
  187       assert_css 'meta[http-equiv=imagetoolbar] + *', output, 0
  188       assert_css 'script + a#top', output, 1
  189       assert_css 'a#top + *', output, 0
  190     end
  191 
  192     test 'should include docinfo files for html backend with custom docinfodir' do
  193       sample_input_path = fixture_path('basic.adoc')
  194 
  195       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  196                                         standalone: true, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo' => '', 'docinfodir' => 'custom-docinfodir' }
  197       refute_empty output
  198       assert_css 'script[src="bootstrap.js"]', output, 1
  199       assert_css 'meta[name="robots"]', output, 0
  200 
  201       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  202                                         standalone: true, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo1' => '', 'docinfodir' => 'custom-docinfodir' }
  203       refute_empty output
  204       assert_css 'script[src="bootstrap.js"]', output, 0
  205       assert_css 'meta[name="robots"]', output, 1
  206 
  207       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  208                                         standalone: true, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo2' => '', 'docinfodir' => './custom-docinfodir' }
  209       refute_empty output
  210       assert_css 'script[src="bootstrap.js"]', output, 1
  211       assert_css 'meta[name="robots"]', output, 1
  212 
  213       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  214                                         standalone: true, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo2' => '', 'docinfodir' => 'custom-docinfodir/subfolder' }
  215       refute_empty output
  216       assert_css 'script[src="bootstrap.js"]', output, 0
  217       assert_css 'meta[name="robots"]', output, 0
  218     end
  219 
  220     test 'should include docinfo files in docbook backend' do
  221       sample_input_path = fixture_path('basic.adoc')
  222 
  223       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  224           standalone: true, backend: 'docbook', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo' => '' }
  225       refute_empty output
  226       assert_css 'productname', output, 0
  227       assert_css 'copyright', output, 1
  228 
  229       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  230           standalone: true, backend: 'docbook', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo1' => '' }
  231       refute_empty output
  232       assert_css 'productname', output, 1
  233       assert_xpath '//xmlns:productname[text()="Asciidoctorâ„¢"]', output, 1
  234       assert_css 'edition', output, 1
  235       assert_xpath '//xmlns:edition[text()="1.0"]', output, 1 # verifies substitutions are performed
  236       assert_css 'copyright', output, 0
  237 
  238       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  239           standalone: true, backend: 'docbook', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo2' => '' }
  240       refute_empty output
  241       assert_css 'productname', output, 1
  242       assert_xpath '//xmlns:productname[text()="Asciidoctorâ„¢"]', output, 1
  243       assert_css 'edition', output, 1
  244       assert_xpath '//xmlns:edition[text()="1.0"]', output, 1 # verifies substitutions are performed
  245       assert_css 'copyright', output, 1
  246     end
  247 
  248     test 'should use header docinfo in place of default header' do
  249       output = Asciidoctor.convert_file fixture_path('sample.adoc'), to_file: false,
  250           standalone: true, backend: 'docbook', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo' => 'private-header', 'noheader' => '' }
  251       refute_empty output
  252       assert_css 'article > info', output, 1
  253       assert_css 'article > info > title', output, 1
  254       assert_css 'article > info > revhistory', output, 1
  255       assert_css 'article > info > revhistory > revision', output, 2
  256     end
  257 
  258     test 'should include docinfo footer files for html backend' do
  259       sample_input_path = fixture_path('basic.adoc')
  260 
  261       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  262           standalone: true, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo' => '' }
  263       refute_empty output
  264       assert_css 'body script', output, 1
  265       assert_css 'a#top', output, 0
  266 
  267       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  268           standalone: true, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo1' => '' }
  269       refute_empty output
  270       assert_css 'body script', output, 0
  271       assert_css 'a#top', output, 1
  272 
  273       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  274           standalone: true, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo2' => '' }
  275       refute_empty output
  276       assert_css 'body script', output, 1
  277       assert_css 'a#top', output, 1
  278     end
  279 
  280     test 'should include docinfo footer files in DocBook backend' do
  281       sample_input_path = fixture_path('basic.adoc')
  282 
  283       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  284           standalone: true, backend: 'docbook', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo' => '' }
  285       refute_empty output
  286       assert_css 'article > revhistory', output, 1
  287       assert_xpath '/xmlns:article/xmlns:revhistory/xmlns:revision/xmlns:revnumber[text()="1.0"]', output, 1 # verifies substitutions are performed
  288       assert_css 'glossary', output, 0
  289 
  290       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  291           standalone: true, backend: 'docbook', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo1' => '' }
  292       refute_empty output
  293       assert_css 'article > revhistory', output, 0
  294       assert_css 'glossary[xml|id="_glossary"]', output, 1
  295 
  296       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  297           standalone: true, backend: 'docbook', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo2' => '' }
  298       refute_empty output
  299       assert_css 'article > revhistory', output, 1
  300       assert_xpath '/xmlns:article/xmlns:revhistory/xmlns:revision/xmlns:revnumber[text()="1.0"]', output, 1 # verifies substitutions are performed
  301       assert_css 'glossary[xml|id="_glossary"]', output, 1
  302     end
  303 
  304     # WARNING this test manipulates runtime settings; should probably be run in forked process
  305     test 'should force encoding of docinfo files to UTF-8' do
  306       old_external = Encoding.default_external
  307       old_internal = Encoding.default_internal
  308       old_verbose = $VERBOSE
  309       begin
  310         $VERBOSE = nil # disable warnings since we have to modify constants
  311         Encoding.default_external = Encoding.default_internal = Encoding::IBM437
  312         sample_input_path = fixture_path('basic.adoc')
  313         output = Asciidoctor.convert_file sample_input_path, to_file: false, standalone: true,
  314             backend: 'docbook', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo' => 'private,shared' }
  315         refute_empty output
  316         assert_css 'productname', output, 1
  317         assert_includes output, '<productname>Asciidoctorâ„¢</productname>'
  318         assert_css 'edition', output, 1
  319         assert_xpath '//xmlns:edition[text()="1.0"]', output, 1 # verifies substitutions are performed
  320         assert_css 'copyright', output, 1
  321       ensure
  322         Encoding.default_external = old_external
  323         Encoding.default_internal = old_internal
  324         $VERBOSE = old_verbose
  325       end
  326     end
  327 
  328     test 'should not include docinfo files by default' do
  329       sample_input_path = fixture_path('basic.adoc')
  330 
  331       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  332           standalone: true, safe: Asciidoctor::SafeMode::SERVER
  333       refute_empty output
  334       assert_css 'script[src="modernizr.js"]', output, 0
  335       assert_css 'meta[http-equiv="imagetoolbar"]', output, 0
  336 
  337       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  338           standalone: true, backend: 'docbook', safe: Asciidoctor::SafeMode::SERVER
  339       refute_empty output
  340       assert_css 'productname', output, 0
  341       assert_css 'copyright', output, 0
  342     end
  343 
  344     test 'should not include docinfo files if safe mode is SECURE or greater' do
  345       sample_input_path = fixture_path('basic.adoc')
  346 
  347       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  348           standalone: true, attributes: { 'docinfo2' => '' }
  349       refute_empty output
  350       assert_css 'script[src="modernizr.js"]', output, 0
  351       assert_css 'meta[http-equiv="imagetoolbar"]', output, 0
  352 
  353       output = Asciidoctor.convert_file sample_input_path, to_file: false,
  354           standalone: true, backend: 'docbook', attributes: { 'docinfo2' => '' }
  355       refute_empty output
  356       assert_css 'productname', output, 0
  357       assert_css 'copyright', output, 0
  358     end
  359 
  360     test 'should substitute attributes in docinfo files by default' do
  361       sample_input_path = fixture_path 'subs.adoc'
  362       using_memory_logger do |logger|
  363         output = Asciidoctor.convert_file sample_input_path,
  364             to_file: false,
  365             standalone: true,
  366             safe: :server,
  367             attributes: { 'docinfo' => '', 'bootstrap-version' => nil, 'linkcss' => '', 'attribute-missing' => 'drop-line' }
  368         refute_empty output
  369         assert_css 'script', output, 0
  370         assert_xpath %(//meta[@name="copyright"][@content="(C) OpenDevise"]), output, 1
  371         assert_message logger, :INFO, 'dropping line containing reference to missing attribute: bootstrap-version'
  372       end
  373     end
  374 
  375     test 'should apply explicit substitutions to docinfo files' do
  376       sample_input_path = fixture_path 'subs.adoc'
  377       output = Asciidoctor.convert_file sample_input_path,
  378           to_file: false,
  379           standalone: true,
  380           safe: :server,
  381           attributes: { 'docinfo' => '', 'docinfosubs' => 'attributes,replacements', 'linkcss' => '' }
  382       refute_empty output
  383       assert_css 'script[src="bootstrap.3.2.0.min.js"]', output, 1
  384       assert_xpath %(//meta[@name="copyright"][@content="#{decode_char 169} OpenDevise"]), output, 1
  385     end
  386   end
  387 
  388   context 'MathJax' do
  389     test 'should add MathJax script to HTML head if stem attribute is set' do
  390       output = convert_string '', attributes: { 'stem' => '' }
  391       assert_match('<script type="text/x-mathjax-config">', output)
  392       assert_match('inlineMath: [["\\\\(", "\\\\)"]]', output)
  393       assert_match('displayMath: [["\\\\[", "\\\\]"]]', output)
  394       assert_match('delimiters: [["\\\\$", "\\\\$"]]', output)
  395     end
  396   end
  397 
  398   context 'Converter' do
  399     test 'convert methos on built-in converter are registered by default' do
  400       doc = document_from_string ''
  401       assert_equal 'html5', doc.attributes['backend']
  402       assert doc.attributes.has_key? 'backend-html5'
  403       assert_equal 'html', doc.attributes['basebackend']
  404       assert doc.attributes.has_key? 'basebackend-html'
  405       converter = doc.converter
  406       assert_kind_of Asciidoctor::Converter::Html5Converter, converter
  407       BUILT_IN_ELEMENTS.each do |element|
  408         assert_respond_to converter, %(convert_#{element})
  409       end
  410     end
  411 
  412     test 'convert methods on built-in converter are registered when backend is docbook5' do
  413       doc = document_from_string '', attributes: { 'backend' => 'docbook5' }
  414       converter = doc.converter
  415       assert_equal 'docbook5', doc.attributes['backend']
  416       assert doc.attributes.has_key? 'backend-docbook5'
  417       assert_equal 'docbook', doc.attributes['basebackend']
  418       assert doc.attributes.has_key? 'basebackend-docbook'
  419       converter = doc.converter
  420       assert_kind_of Asciidoctor::Converter::DocBook5Converter, converter
  421       BUILT_IN_ELEMENTS.each do |element|
  422         assert_respond_to converter, %(convert_#{element})
  423       end
  424     end
  425 
  426     test 'should add favicon if favicon attribute is set' do
  427       {
  428         '' => %w(favicon.ico image/x-icon),
  429         '/favicon.ico' => %w(/favicon.ico image/x-icon),
  430         '/img/favicon.png' => %w(/img/favicon.png image/png),
  431       }.each do |val, (href, type)|
  432         result = convert_string '= Untitled', attributes: { 'favicon' => val }
  433         assert_css 'link[rel="icon"]', result, 1
  434         assert_css %(link[rel="icon"][href="#{href}"]), result, 1
  435         assert_css %(link[rel="icon"][type="#{type}"]), result, 1
  436       end
  437     end
  438   end
  439 
  440   context 'Structure' do
  441     test 'document with no doctitle' do
  442       doc = document_from_string('Snorf')
  443       assert_nil doc.doctitle
  444       assert_nil doc.name
  445       refute doc.has_header?
  446       assert_nil doc.header
  447     end
  448 
  449     test 'should enable compat mode for document with legacy doctitle' do
  450       input = <<~'EOS'
  451       Document Title
  452       ==============
  453 
  454       +content+
  455       EOS
  456 
  457       doc = document_from_string input
  458       assert(doc.attr? 'compat-mode')
  459       result = doc.convert
  460       assert_xpath '//code[text()="content"]', result, 1
  461     end
  462 
  463     test 'should not enable compat mode for document with legacy doctitle if compat mode disable by header' do
  464       input = <<~'EOS'
  465       Document Title
  466       ==============
  467       :compat-mode!:
  468 
  469       +content+
  470       EOS
  471 
  472       doc = document_from_string input
  473       assert_nil(doc.attr 'compat-mode')
  474       result = doc.convert
  475       assert_xpath '//code[text()="content"]', result, 0
  476     end
  477 
  478     test 'should not enable compat mode for document with legacy doctitle if compat mode is locked by API' do
  479       input = <<~'EOS'
  480       Document Title
  481       ==============
  482 
  483       +content+
  484       EOS
  485 
  486       doc = document_from_string input, attributes: { 'compat-mode' => nil }
  487       assert(doc.attribute_locked? 'compat-mode')
  488       assert_nil(doc.attr 'compat-mode')
  489       result = doc.convert
  490       assert_xpath '//code[text()="content"]', result, 0
  491     end
  492 
  493     test 'title partition API with default separator' do
  494       title = Asciidoctor::Document::Title.new 'Main Title: And More: Subtitle'
  495       assert_equal 'Main Title: And More', title.main
  496       assert_equal 'Subtitle', title.subtitle
  497     end
  498 
  499     test 'title partition API with custom separator' do
  500       title = Asciidoctor::Document::Title.new 'Main Title:: And More:: Subtitle', separator: '::'
  501       assert_equal 'Main Title:: And More', title.main
  502       assert_equal 'Subtitle', title.subtitle
  503     end
  504 
  505     test 'document with subtitle' do
  506       input = <<~'EOS'
  507       = Main Title: *Subtitle*
  508       Author Name
  509 
  510       content
  511       EOS
  512 
  513       doc = document_from_string input
  514       title = doc.doctitle partition: true, sanitize: true
  515       assert title.subtitle?
  516       assert title.sanitized?
  517       assert_equal 'Main Title', title.main
  518       assert_equal 'Subtitle', title.subtitle
  519     end
  520 
  521     test 'document with subtitle and custom separator' do
  522       input = <<~'EOS'
  523       [separator=::]
  524       = Main Title:: *Subtitle*
  525       Author Name
  526 
  527       content
  528       EOS
  529 
  530       doc = document_from_string input
  531       title = doc.doctitle partition: true, sanitize: true
  532       assert title.subtitle?
  533       assert title.sanitized?
  534       assert_equal 'Main Title', title.main
  535       assert_equal 'Subtitle', title.subtitle
  536     end
  537 
  538     test 'should not honor custom separator for doctitle if attribute is locked by API' do
  539       input = <<~'EOS'
  540       [separator=::]
  541       = Main Title - *Subtitle*
  542       Author Name
  543 
  544       content
  545       EOS
  546 
  547       doc = document_from_string input, attributes: { 'title-separator' => ' -' }
  548       title = doc.doctitle partition: true, sanitize: true
  549       assert title.subtitle?
  550       assert title.sanitized?
  551       assert_equal 'Main Title', title.main
  552       assert_equal 'Subtitle', title.subtitle
  553     end
  554 
  555     test 'document with doctitle defined as attribute entry' do
  556       input = <<~'EOS'
  557       :doctitle: Document Title
  558 
  559       preamble
  560 
  561       == First Section
  562       EOS
  563       doc = document_from_string input
  564       assert_equal 'Document Title', doc.doctitle
  565       assert doc.has_header?
  566       assert_equal 'Document Title', doc.header.title
  567       assert_equal 'Document Title', doc.first_section.title
  568     end
  569 
  570     test 'document with doctitle defined as attribute entry followed by block with title' do
  571       input = <<~'EOS'
  572       :doctitle: Document Title
  573 
  574       .Block title
  575       Block content
  576       EOS
  577 
  578       doc = document_from_string input
  579       assert_equal 'Document Title', doc.doctitle
  580       assert doc.has_header?
  581       assert_equal 1, doc.blocks.size
  582       assert_equal :paragraph, doc.blocks[0].context
  583       assert_equal 'Block title', doc.blocks[0].title
  584     end
  585 
  586     test 'document with title attribute entry overrides doctitle' do
  587       input = <<~'EOS'
  588       = Document Title
  589       :title: Override
  590 
  591       {doctitle}
  592 
  593       == First Section
  594       EOS
  595       doc = document_from_string input
  596       assert_equal 'Override', doc.doctitle
  597       assert_equal 'Override', doc.title
  598       assert doc.has_header?
  599       assert_equal 'Document Title', doc.header.title
  600       assert_equal 'Document Title', doc.first_section.title
  601       assert_xpath '//*[@id="preamble"]//p[text()="Document Title"]', doc.convert, 1
  602     end
  603 
  604     test 'document with blank title attribute entry overrides doctitle' do
  605       input = <<~'EOS'
  606       = Document Title
  607       :title:
  608 
  609       {doctitle}
  610 
  611       == First Section
  612       EOS
  613       doc = document_from_string input
  614       assert_equal '', doc.doctitle
  615       assert_equal '', doc.title
  616       assert doc.has_header?
  617       assert_equal 'Document Title', doc.header.title
  618       assert_equal 'Document Title', doc.first_section.title
  619       assert_xpath '//*[@id="preamble"]//p[text()="Document Title"]', doc.convert, 1
  620     end
  621 
  622     test 'document with title attribute entry overrides doctitle attribute entry' do
  623       input = <<~'EOS'
  624       = Document Title
  625       :snapshot: {doctitle}
  626       :doctitle: doctitle
  627       :title: Override
  628 
  629       {snapshot}, {doctitle}
  630 
  631       == First Section
  632       EOS
  633       doc = document_from_string input
  634       assert_equal 'Override', doc.doctitle
  635       assert_equal 'Override', doc.title
  636       assert doc.has_header?
  637       assert_equal 'doctitle', doc.header.title
  638       assert_equal 'doctitle', doc.first_section.title
  639       assert_xpath '//*[@id="preamble"]//p[text()="Document Title, doctitle"]', doc.convert, 1
  640     end
  641 
  642     test 'document with doctitle attribute entry overrides implicit doctitle' do
  643       input = <<~'EOS'
  644       = Document Title
  645       :snapshot: {doctitle}
  646       :doctitle: Override
  647 
  648       {snapshot}, {doctitle}
  649 
  650       == First Section
  651       EOS
  652       doc = document_from_string input
  653       assert_equal 'Override', doc.doctitle
  654       assert_nil doc.attributes['title']
  655       assert doc.has_header?
  656       assert_equal 'Override', doc.header.title
  657       assert_equal 'Override', doc.first_section.title
  658       assert_xpath '//*[@id="preamble"]//p[text()="Document Title, Override"]', doc.convert, 1
  659     end
  660 
  661     test 'doctitle attribute entry above header overrides implicit doctitle' do
  662       input = <<~'EOS'
  663       :doctitle: Override
  664       = Document Title
  665 
  666       {doctitle}
  667 
  668       == First Section
  669       EOS
  670       doc = document_from_string input
  671       assert_equal 'Override', doc.doctitle
  672       assert_nil doc.attributes['title']
  673       assert doc.has_header?
  674       assert_equal 'Override', doc.header.title
  675       assert_equal 'Override', doc.first_section.title
  676       assert_xpath '//*[@id="preamble"]//p[text()="Override"]', doc.convert, 1
  677     end
  678 
  679     test 'header substitutions should be applied to the value of the doctitle attribute' do
  680       input = <<~'EOS'
  681       = <Foo> & <Bar>
  682 
  683       The name of the game is {doctitle}.
  684       EOS
  685 
  686       doc = document_from_string input
  687       assert_equal '&lt;Foo&gt; &amp; &lt;Bar&gt;', (doc.attr 'doctitle')
  688       assert_includes doc.blocks[0].content, '&lt;Foo&gt; &amp; &lt;Bar&gt;'
  689     end
  690 
  691     test 'should recognize document title when preceded by blank lines' do
  692       input = <<~'EOS'
  693       :doctype: book
  694 
  695       = Title
  696 
  697       preamble
  698 
  699       == Section 1
  700 
  701       text
  702       EOS
  703       output = convert_string input, safe: Asciidoctor::SafeMode::SAFE
  704       assert_css '#header h1', output, 1
  705       assert_css '#content h1', output, 0
  706     end
  707 
  708     test 'document with multiline attribute entry but only one line should not crash' do
  709       input = ':foo: bar' + Asciidoctor::LINE_CONTINUATION
  710       doc = document_from_string input
  711       assert_equal 'bar', doc.attributes['foo']
  712     end
  713 
  714     test 'should sanitize contents of HTML title element' do
  715       input = <<~'EOS'
  716       = *Document* image:logo.png[] _Title_ image:another-logo.png[another logo]
  717 
  718       content
  719       EOS
  720 
  721       output = convert_string input
  722       assert_xpath '/html/head/title[text()="Document Title"]', output, 1
  723       nodes = xmlnodes_at_xpath('//*[@id="header"]/h1', output)
  724       assert_equal 1, nodes.size
  725       assert_match('<h1><strong>Document</strong> <span class="image"><img src="logo.png" alt="logo"></span> <em>Title</em> <span class="image"><img src="another-logo.png" alt="another logo"></span></h1>', output)
  726     end
  727 
  728     test 'should not choke on empty source' do
  729       doc = Asciidoctor::Document.new ''
  730       assert_empty doc.blocks
  731       assert_nil doc.doctitle
  732       refute doc.has_header?
  733       assert_nil doc.header
  734     end
  735 
  736     test 'should not choke on nil source' do
  737       doc = Asciidoctor::Document.new nil
  738       assert_empty doc.blocks
  739       assert_nil doc.doctitle
  740       refute doc.has_header?
  741       assert_nil doc.header
  742     end
  743 
  744     test 'with metadata' do
  745       input = <<~'EOS'
  746       = AsciiDoc
  747       Stuart Rackham <founder@asciidoc.org>
  748       v8.6.8, 2012-07-12: See changelog.
  749       :description: AsciiDoc user guide
  750       :keywords: asciidoc,documentation
  751       :copyright: Stuart Rackham
  752 
  753       == Version 8.6.8
  754 
  755       more info...
  756       EOS
  757       output = convert_string input
  758       assert_xpath '//meta[@name="author"][@content="Stuart Rackham"]', output, 1
  759       assert_xpath '//meta[@name="description"][@content="AsciiDoc user guide"]', output, 1
  760       assert_xpath '//meta[@name="keywords"][@content="asciidoc,documentation"]', output, 1
  761       assert_xpath '//meta[@name="copyright"][@content="Stuart Rackham"]', output, 1
  762       assert_xpath '//*[@id="header"]/*[@class="details"]/span[@id="author"][text()="Stuart Rackham"]', output, 1
  763       assert_xpath '//*[@id="header"]/*[@class="details"]/span[@id="email"]/a[@href="mailto:founder@asciidoc.org"][text()="founder@asciidoc.org"]', output, 1
  764       assert_xpath '//*[@id="header"]/*[@class="details"]/span[@id="revnumber"][text()="version 8.6.8,"]', output, 1
  765       assert_xpath '//*[@id="header"]/*[@class="details"]/span[@id="revdate"][text()="2012-07-12"]', output, 1
  766       assert_xpath '//*[@id="header"]/*[@class="details"]/span[@id="revremark"][text()="See changelog."]', output, 1
  767     end
  768 
  769     test 'should parse revision line if date is empty' do
  770       input = <<~'EOS'
  771       = Document Title
  772       Author Name
  773       v1.0.0,:remark
  774 
  775       content
  776       EOS
  777 
  778       doc = document_from_string input
  779       assert_equal '1.0.0', doc.attributes['revnumber']
  780       assert_nil doc.attributes['revdate']
  781       assert_equal 'remark', doc.attributes['revremark']
  782     end
  783 
  784     test 'should include revision history in DocBook output if revdate and revnumber is set' do
  785       input = <<~'EOS'
  786       = Document Title
  787       Author Name
  788       :revdate: 2011-11-11
  789       :revnumber: 1.0
  790 
  791       content
  792       EOS
  793 
  794       output = convert_string input, backend: 'docbook'
  795       assert_css 'revhistory', output, 1
  796       assert_css 'revhistory > revision', output, 1
  797       assert_css 'revhistory > revision > date', output, 1
  798       assert_css 'revhistory > revision > revnumber', output, 1
  799     end
  800 
  801     test 'should include revision history in DocBook output if revdate and revremark is set' do
  802       input = <<~'EOS'
  803       = Document Title
  804       Author Name
  805       :revdate: 2011-11-11
  806       :revremark: features!
  807 
  808       content
  809       EOS
  810 
  811       output = convert_string input, backend: 'docbook'
  812       assert_css 'revhistory', output, 1
  813       assert_css 'revhistory > revision', output, 1
  814       assert_css 'revhistory > revision > date', output, 1
  815       assert_css 'revhistory > revision > revremark', output, 1
  816     end
  817 
  818     test 'should not include revision history in DocBook output if revdate is not set' do
  819       input = <<~'EOS'
  820       = Document Title
  821       Author Name
  822       :revnumber: 1.0
  823 
  824       content
  825       EOS
  826 
  827       output = convert_string input, backend: 'docbook'
  828       assert_css 'revhistory', output, 0
  829     end
  830 
  831     test 'with metadata to DocBook 5' do
  832       input = <<~'EOS'
  833       = AsciiDoc
  834       Stuart Rackham <founder@asciidoc.org>
  835 
  836       == Version 8.6.8
  837 
  838       more info...
  839       EOS
  840       output = convert_string input, backend: 'docbook5'
  841       assert_xpath '/article/info', output, 1
  842       assert_xpath '/article/info/title[text()="AsciiDoc"]', output, 1
  843       assert_xpath '/article/info/author/personname', output, 1
  844       assert_xpath '/article/info/author/personname/firstname[text()="Stuart"]', output, 1
  845       assert_xpath '/article/info/author/personname/surname[text()="Rackham"]', output, 1
  846       assert_xpath '/article/info/author/email[text()="founder@asciidoc.org"]', output, 1
  847       assert_css 'article:root:not([xml|id])', output, 1
  848       assert_css 'article:root[xml|lang="en"]', output, 1
  849     end
  850 
  851     test 'with document ID to Docbook 5' do
  852       input = <<~'EOS'
  853       [[document-id]]
  854       = Document Title
  855 
  856       more info...
  857       EOS
  858       output = convert_string input, backend: 'docbook', keep_namespaces: true
  859       assert_css 'article:root[xml|id="document-id"]', output, 1
  860     end
  861 
  862     test 'with author defined using attribute entry to DocBook' do
  863       input = <<~'EOS'
  864       = Document Title
  865       :author: Doc Writer
  866       :email: thedoctor@asciidoc.org
  867 
  868       content
  869       EOS
  870 
  871       output = convert_string input, backend: 'docbook'
  872       assert_xpath '/article/info/author', output, 1
  873       assert_xpath '/article/info/author/personname/firstname[text()="Doc"]', output, 1
  874       assert_xpath '/article/info/author/personname/surname[text()="Writer"]', output, 1
  875       assert_xpath '/article/info/author/email[text()="thedoctor@asciidoc.org"]', output, 1
  876       assert_xpath '/article/info/authorinitials[text()="DW"]', output, 1
  877     end
  878 
  879     test 'should substitute replacements in author names in HTML output' do
  880       input = <<~'EOS'
  881       = Document Title
  882       Stephen O'Grady <founder@redmonk.com>
  883 
  884       content
  885       EOS
  886 
  887       output = convert_string input
  888       assert_xpath %(//meta[@name="author"][@content="Stephen O#{decode_char 8217}Grady"]), output, 1
  889       assert_xpath %(//span[@id="author"][text()="Stephen O#{decode_char 8217}Grady"]), output, 1
  890     end
  891 
  892     test 'should substitute replacements in author names in DocBook output' do
  893       input = <<~'EOS'
  894       = Document Title
  895       Stephen O'Grady <founder@redmonk.com>
  896 
  897       content
  898       EOS
  899 
  900       output = convert_string input, backend: 'docbook'
  901       assert_xpath '//author', output, 1
  902       assert_xpath %(//author/personname/surname[text()="O#{decode_char 8217}Grady"]), output, 1
  903     end
  904 
  905     test 'should sanitize content of HTML meta authors tag' do
  906       input = <<~'EOS'
  907       = Document Title
  908       :author: pass:n[http://example.org/community/team.html[Ze *Product* team]]
  909 
  910       content
  911       EOS
  912 
  913       output = convert_string input
  914       assert_xpath '//meta[@name="author"][@content="Ze Product team"]', output, 1
  915     end
  916 
  917     test 'should not double escape ampersand in author attribute' do
  918       input = <<~'EOS'
  919       = Document Title
  920       R&D Lab
  921 
  922       {author}
  923       EOS
  924 
  925       output = convert_string input
  926       assert_includes output, 'R&amp;D Lab', 2
  927     end
  928 
  929     test 'should include multiple authors in HTML output' do
  930       input = <<~'EOS'
  931       = Document Title
  932       Doc Writer <thedoctor@asciidoc.org>; Junior Writer <junior@asciidoctor.org>
  933 
  934       content
  935       EOS
  936 
  937       output = convert_string input
  938       assert_xpath '//span[@id="author"]', output, 1
  939       assert_xpath '//span[@id="author"][text()="Doc Writer"]', output, 1
  940       assert_xpath '//span[@id="email"]', output, 1
  941       assert_xpath '//span[@id="email"]/a', output, 1
  942       assert_xpath '//span[@id="email"]/a[@href="mailto:thedoctor@asciidoc.org"][text()="thedoctor@asciidoc.org"]', output, 1
  943       assert_xpath '//span[@id="author2"]', output, 1
  944       assert_xpath '//span[@id="author2"][text()="Junior Writer"]', output, 1
  945       assert_xpath '//span[@id="email2"]', output, 1
  946       assert_xpath '//span[@id="email2"]/a', output, 1
  947       assert_xpath '//span[@id="email2"]/a[@href="mailto:junior@asciidoctor.org"][text()="junior@asciidoctor.org"]', output, 1
  948     end
  949 
  950     test 'should create authorgroup in DocBook when multiple authors' do
  951       input = <<~'EOS'
  952       = Document Title
  953       Doc Writer <thedoctor@asciidoc.org>; Junior Writer <junior@asciidoctor.org>
  954 
  955       content
  956       EOS
  957 
  958       output = convert_string input, backend: 'docbook'
  959       assert_xpath '/article/info/author', output, 0
  960       assert_xpath '/article/info/authorgroup', output, 1
  961       assert_xpath '/article/info/authorgroup/author', output, 2
  962       assert_xpath '(/article/info/authorgroup/author)[1]/personname/firstname[text()="Doc"]', output, 1
  963       assert_xpath '(/article/info/authorgroup/author)[2]/personname/firstname[text()="Junior"]', output, 1
  964     end
  965 
  966     test 'with author defined by indexed attribute name' do
  967       input = <<~'EOS'
  968       = Document Title
  969       :author_1: Doc Writer
  970 
  971       {author}
  972       EOS
  973 
  974       doc = document_from_string input
  975       assert_equal 'Doc Writer', (doc.attr 'author')
  976       assert_equal 'Doc Writer', (doc.attr 'author_1')
  977     end
  978 
  979     test 'with authors defined using attribute entry to DocBook' do
  980       input = <<~'EOS'
  981       = Document Title
  982       :authors: Doc Writer; Junior Writer
  983       :email_1: thedoctor@asciidoc.org
  984       :email_2: junior@asciidoc.org
  985 
  986       content
  987       EOS
  988 
  989       output = convert_string input, backend: 'docbook'
  990       assert_xpath '/article/info/author', output, 0
  991       assert_xpath '/article/info/authorgroup', output, 1
  992       assert_xpath '/article/info/authorgroup/author', output, 2
  993       assert_xpath '(/article/info/authorgroup/author)[1]/personname/firstname[text()="Doc"]', output, 1
  994       assert_xpath '(/article/info/authorgroup/author)[1]/email[text()="thedoctor@asciidoc.org"]', output, 1
  995       assert_xpath '(/article/info/authorgroup/author)[2]/personname/firstname[text()="Junior"]', output, 1
  996       assert_xpath '(/article/info/authorgroup/author)[2]/email[text()="junior@asciidoc.org"]', output, 1
  997     end
  998 
  999     test 'should populate copyright element in DocBook output if copyright attribute is defined' do
 1000       input = <<~'EOS'
 1001       = Jet Bike
 1002       :copyright: ACME, Inc.
 1003 
 1004       Essential for catching road runners.
 1005       EOS
 1006       output = convert_string input, backend: 'docbook5'
 1007       assert_xpath '/article/info/copyright', output, 1
 1008       assert_xpath '/article/info/copyright/holder[text()="ACME, Inc."]', output, 1
 1009     end
 1010 
 1011     test 'should populate copyright element in DocBook output if copyright attribute is defined with year' do
 1012       input = <<~'EOS'
 1013       = Jet Bike
 1014       :copyright: ACME, Inc. 1956
 1015 
 1016       Essential for catching road runners.
 1017       EOS
 1018       output = convert_string input, backend: 'docbook5'
 1019       assert_xpath '/article/info/copyright', output, 1
 1020       assert_xpath '/article/info/copyright/holder[text()="ACME, Inc."]', output, 1
 1021       assert_xpath '/article/info/copyright/year', output, 1
 1022       assert_xpath '/article/info/copyright/year[text()="1956"]', output, 1
 1023     end
 1024 
 1025     test 'should populate copyright element in DocBook output if copyright attribute is defined with year range' do
 1026       input = <<~'EOS'
 1027       = Jet Bike
 1028       :copyright: ACME, Inc. 1956-2018
 1029 
 1030       Essential for catching road runners.
 1031       EOS
 1032       output = convert_string input, backend: 'docbook5'
 1033       assert_xpath '/article/info/copyright', output, 1
 1034       assert_xpath '/article/info/copyright/holder[text()="ACME, Inc."]', output, 1
 1035       assert_xpath '/article/info/copyright/year', output, 1
 1036       assert_xpath '/article/info/copyright/year[text()="1956-2018"]', output, 1
 1037     end
 1038 
 1039     test 'with header footer' do
 1040       doc = document_from_string "= Title\n\nparagraph"
 1041       refute doc.attr?('embedded')
 1042       result = doc.convert
 1043       assert_xpath '/html', result, 1
 1044       assert_xpath '//*[@id="header"]', result, 1
 1045       assert_xpath '//*[@id="header"]/h1', result, 1
 1046       assert_xpath '//*[@id="footer"]', result, 1
 1047       assert_xpath '//*[@id="content"]', result, 1
 1048     end
 1049 
 1050     test 'does not output footer if nofooter is set' do
 1051       input = <<~'EOS'
 1052       :nofooter:
 1053 
 1054       content
 1055       EOS
 1056 
 1057       result = convert_string input
 1058       assert_xpath '//*[@id="footer"]', result, 0
 1059     end
 1060 
 1061     test 'can disable last updated in footer' do
 1062       doc = document_from_string "= Document Title\n\npreamble", attributes: { 'last-update-label!' => '' }
 1063       result = doc.convert
 1064       assert_xpath '//*[@id="footer-text"]', result, 1
 1065       assert_xpath '//*[@id="footer-text"][normalize-space(text())=""]', result, 1
 1066     end
 1067 
 1068     test 'should create embedded document if standalone option passed to constructor is false' do
 1069       doc = (Asciidoctor::Document.new "= Document Title\n\ncontent", standalone: false).parse
 1070       assert doc.attr?('embedded')
 1071       result = doc.convert
 1072       assert_xpath '/html', result, 0
 1073       assert_xpath '/h1', result, 0
 1074       assert_xpath '/*[@id="header"]', result, 0
 1075       assert_xpath '/*[@id="footer"]', result, 0
 1076       assert_xpath '/*[@class="paragraph"]', result, 1
 1077     end
 1078 
 1079     test 'should create embedded document if standalone option passed to convert method is false' do
 1080       doc = (Asciidoctor::Document.new "= Document Title\n\ncontent", standalone: true).parse
 1081       refute doc.attr?('embedded')
 1082       result = doc.convert standalone: false
 1083       assert_xpath '/html', result, 0
 1084       assert_xpath '/h1', result, 1
 1085       assert_xpath '/*[@id="header"]', result, 0
 1086       assert_xpath '/*[@id="footer"]', result, 0
 1087       assert_xpath '/*[@class="paragraph"]', result, 1
 1088     end
 1089 
 1090     test 'should create embedded document if deprecated header_footer option is false' do
 1091       doc = (Asciidoctor::Document.new "= Document Title\n\ncontent", header_footer: false).parse
 1092       assert doc.attr?('embedded')
 1093       result = doc.convert
 1094       assert_xpath '/html', result, 0
 1095       assert_xpath '/h1', result, 0
 1096       assert_xpath '/*[@id="header"]', result, 0
 1097       assert_xpath '/*[@id="footer"]', result, 0
 1098       assert_xpath '/*[@class="paragraph"]', result, 1
 1099     end
 1100 
 1101     test 'should create embedded document if header_footer option passed to convert method is false' do
 1102       doc = (Asciidoctor::Document.new "= Document Title\n\ncontent", header_footer: true).parse
 1103       refute doc.attr?('embedded')
 1104       result = doc.convert header_footer: false
 1105       assert_xpath '/html', result, 0
 1106       assert_xpath '/h1', result, 1
 1107       assert_xpath '/*[@id="header"]', result, 0
 1108       assert_xpath '/*[@id="footer"]', result, 0
 1109       assert_xpath '/*[@class="paragraph"]', result, 1
 1110     end
 1111 
 1112     test 'enable title in embedded document by unassigning notitle attribute' do
 1113       input = <<~'EOS'
 1114       = Document Title
 1115 
 1116       content
 1117       EOS
 1118 
 1119       result = convert_string_to_embedded input, attributes: { 'notitle!' => '' }
 1120       assert_xpath '/html', result, 0
 1121       assert_xpath '/h1', result, 1
 1122       assert_xpath '/*[@id="header"]', result, 0
 1123       assert_xpath '/*[@id="footer"]', result, 0
 1124       assert_xpath '/*[@class="paragraph"]', result, 1
 1125       assert_xpath '(/*)[1]/self::h1', result, 1
 1126       assert_xpath '(/*)[2]/self::*[@class="paragraph"]', result, 1
 1127     end
 1128 
 1129     test 'enable title in embedded document by assigning showtitle attribute' do
 1130       input = <<~'EOS'
 1131       = Document Title
 1132 
 1133       content
 1134       EOS
 1135 
 1136       result = convert_string_to_embedded input, attributes: { 'showtitle' => '' }
 1137       assert_xpath '/html', result, 0
 1138       assert_xpath '/h1', result, 1
 1139       assert_xpath '/*[@id="header"]', result, 0
 1140       assert_xpath '/*[@id="footer"]', result, 0
 1141       assert_xpath '/*[@class="paragraph"]', result, 1
 1142       assert_xpath '(/*)[1]/self::h1', result, 1
 1143       assert_xpath '(/*)[2]/self::*[@class="paragraph"]', result, 1
 1144     end
 1145 
 1146     test 'parse header only' do
 1147       input = <<~'EOS'
 1148       = Document Title
 1149       Author Name
 1150       :foo: bar
 1151 
 1152       preamble
 1153       EOS
 1154 
 1155       doc = document_from_string input, parse_header_only: true
 1156       assert_equal 'Document Title', doc.doctitle
 1157       assert_equal 'Author Name', doc.author
 1158       assert_equal 'bar', doc.attributes['foo']
 1159       # there would be at least 1 block had it parsed beyond the header
 1160       assert_equal 0, doc.blocks.size
 1161     end
 1162 
 1163     test 'outputs footnotes in footer' do
 1164       input = <<~'EOS'
 1165       A footnote footnote:[An example footnote.];
 1166       a second footnote with a reference ID footnote:note2[Second footnote.];
 1167       and finally a reference to the second footnote footnote:note2[].
 1168       EOS
 1169 
 1170       output = convert_string input
 1171       assert_css '#footnotes', output, 1
 1172       assert_css '#footnotes .footnote', output, 2
 1173       assert_css '#footnotes .footnote#_footnotedef_1', output, 1
 1174       assert_xpath '//div[@id="footnotes"]/div[@id="_footnotedef_1"]/a[@href="#_footnoteref_1"][text()="1"]', output, 1
 1175       text = xmlnodes_at_xpath '//div[@id="footnotes"]/div[@id="_footnotedef_1"]/text()', output
 1176       assert_equal '. An example footnote.', text.text.strip
 1177       assert_css '#footnotes .footnote#_footnotedef_2', output, 1
 1178       assert_xpath '//div[@id="footnotes"]/div[@id="_footnotedef_2"]/a[@href="#_footnoteref_2"][text()="2"]', output, 1
 1179       text = xmlnodes_at_xpath '//div[@id="footnotes"]/div[@id="_footnotedef_2"]/text()', output
 1180       assert_equal '. Second footnote.', text.text.strip
 1181     end
 1182 
 1183     test 'outputs footnotes block in embedded document by default' do
 1184       input = 'Text that has supporting information{empty}footnote:[An example footnote.].'
 1185 
 1186       output = convert_string_to_embedded input
 1187       assert_css '#footnotes', output, 1
 1188       assert_css '#footnotes .footnote', output, 1
 1189       assert_css '#footnotes .footnote#_footnotedef_1', output, 1
 1190       assert_xpath '/div[@id="footnotes"]/div[@id="_footnotedef_1"]/a[@href="#_footnoteref_1"][text()="1"]', output, 1
 1191       text = xmlnodes_at_xpath '/div[@id="footnotes"]/div[@id="_footnotedef_1"]/text()', output
 1192       assert_equal '. An example footnote.', text.text.strip
 1193     end
 1194 
 1195     test 'does not output footnotes block in embedded document if nofootnotes attribute is set' do
 1196       input = 'Text that has supporting information{empty}footnote:[An example footnote.].'
 1197 
 1198       output = convert_string_to_embedded input, attributes: { 'nofootnotes' => '' }
 1199       assert_css '#footnotes', output, 0
 1200     end
 1201   end
 1202 
 1203   context 'Catalog' do
 1204     test 'should alias document catalog as document references' do
 1205       input = <<~'EOS'
 1206       = Document Title
 1207 
 1208       == Section A
 1209 
 1210       Content
 1211 
 1212       == Section B
 1213 
 1214       Content.footnote:[commentary]
 1215       EOS
 1216 
 1217       doc = document_from_string input
 1218       refute_nil doc.catalog
 1219       #assert_equal [:footnotes, :ids, :images, :includes, :indexterms, :links, :refs, :callouts].sort, doc.catalog.keys.sort
 1220       assert_equal [:footnotes, :ids, :images, :includes, :links, :refs, :callouts].sort, doc.catalog.keys.sort
 1221       assert_same doc.catalog, doc.references
 1222       assert_same doc.catalog[:footnotes], doc.references[:footnotes]
 1223       assert_same doc.catalog[:refs], doc.references[:refs]
 1224       assert_equal '_section_a', (doc.resolve_id 'Section A')
 1225     end
 1226 
 1227     test 'should return empty :ids table' do
 1228       doc = empty_document
 1229       refute_nil doc.catalog[:ids]
 1230       assert_empty doc.catalog[:ids]
 1231       assert_nil doc.catalog[:ids]['foobar']
 1232     end
 1233 
 1234     test 'should register entry in :refs table with reftext when request is made to register entry in :ids table' do
 1235       doc = empty_document
 1236       doc.register :ids, ['foobar', 'Foo Bar']
 1237       assert_empty doc.catalog[:ids]
 1238       refute_empty doc.catalog[:refs]
 1239       ref = doc.catalog[:refs]['foobar']
 1240       assert_equal 'Foo Bar', ref.reftext
 1241       assert_equal 'foobar', (doc.resolve_id 'Foo Bar')
 1242     end
 1243 
 1244     test 'should record imagesdir when image is registered with catalog' do
 1245       doc = empty_document attributes: { 'imagesdir' => 'img' }, catalog_assets: true
 1246       doc.register :images, 'diagram.svg'
 1247       assert_equal doc.catalog[:images].size, 1
 1248       assert_equal 'diagram.svg', doc.catalog[:images][0].target
 1249       assert_equal 'img', doc.catalog[:images][0].imagesdir
 1250     end
 1251 
 1252     test 'should catalog assets inside nested document' do
 1253       input = <<~'EOS'
 1254       image::outer.png[]
 1255 
 1256       |===
 1257       a|
 1258       image::inner.png[]
 1259       |===
 1260       EOS
 1261 
 1262       doc = document_from_string input, catalog_assets: true
 1263       images = doc.catalog[:images]
 1264       refute_empty images
 1265       assert_equal 2, images.size
 1266       assert_equal images.map(&:target), ['outer.png', 'inner.png']
 1267     end
 1268   end
 1269 
 1270   context 'Backends and Doctypes' do
 1271     test 'html5 backend doctype article' do
 1272       result = convert_string("= Title\n\nparagraph", attributes: { 'backend' => 'html5' })
 1273       assert_xpath '/html', result, 1
 1274       assert_xpath '/html/body[@class="article"]', result, 1
 1275       assert_xpath '/html//*[@id="header"]/h1[text()="Title"]', result, 1
 1276       assert_xpath '/html//*[@id="content"]//p[text()="paragraph"]', result, 1
 1277     end
 1278 
 1279     test 'html5 backend doctype book' do
 1280       result = convert_string("= Title\n\nparagraph", attributes: { 'backend' => 'html5', 'doctype' => 'book' })
 1281       assert_xpath '/html', result, 1
 1282       assert_xpath '/html/body[@class="book"]', result, 1
 1283       assert_xpath '/html//*[@id="header"]/h1[text()="Title"]', result, 1
 1284       assert_xpath '/html//*[@id="content"]//p[text()="paragraph"]', result, 1
 1285     end
 1286 
 1287     test 'xhtml5 backend should map to html5 and set htmlsyntax to xml' do
 1288       input = 'content'
 1289       doc = document_from_string input, backend: :xhtml5
 1290       assert_equal 'html5', doc.backend
 1291       assert_equal 'xml', (doc.attr 'htmlsyntax')
 1292     end
 1293 
 1294     test 'xhtml backend should map to html5 and set htmlsyntax to xml' do
 1295       input = 'content'
 1296       doc = document_from_string input, backend: :xhtml
 1297       assert_equal 'html5', doc.backend
 1298       assert_equal 'xml', (doc.attr 'htmlsyntax')
 1299     end
 1300 
 1301     test 'honor htmlsyntax attribute passed via API if backend is html' do
 1302       input = '---'
 1303       doc = document_from_string input, safe: :safe, attributes: { 'htmlsyntax' => 'xml' }
 1304       assert_equal 'html5', doc.backend
 1305       assert_equal 'xml', (doc.attr 'htmlsyntax')
 1306       result = doc.convert standalone: false
 1307       assert_equal '<hr/>', result
 1308     end
 1309 
 1310     test 'honor htmlsyntax attribute in document header if followed by backend attribute' do
 1311       input = <<~'EOS'
 1312       :htmlsyntax: xml
 1313       :backend: html5
 1314 
 1315       ---
 1316       EOS
 1317       doc = document_from_string input, safe: :safe
 1318       assert_equal 'html5', doc.backend
 1319       assert_equal 'xml', (doc.attr 'htmlsyntax')
 1320       result = doc.convert standalone: false
 1321       assert_equal '<hr/>', result
 1322     end
 1323 
 1324     test 'does not honor htmlsyntax attribute in document header if not followed by backend attribute' do
 1325       input = <<~'EOS'
 1326       :backend: html5
 1327       :htmlsyntax: xml
 1328 
 1329       ---
 1330       EOS
 1331       result = convert_string_to_embedded input, safe: :safe
 1332       assert_equal '<hr>', result
 1333     end
 1334 
 1335     test 'should close all short tags when htmlsyntax is xml' do
 1336       input = <<~'EOS'
 1337       = Document Title
 1338       Author Name
 1339       v1.0, 2001-01-01
 1340       :icons:
 1341       :favicon:
 1342 
 1343       image:tiger.png[]
 1344 
 1345       image::tiger.png[]
 1346 
 1347       * [x] one
 1348       * [ ] two
 1349 
 1350       |===
 1351       |A |B
 1352       |===
 1353 
 1354       [horizontal, labelwidth="25%", itemwidth="75%"]
 1355       term:: description
 1356 
 1357       NOTE: note
 1358 
 1359       [quote,Author,Source]
 1360       ____
 1361       Quote me.
 1362       ____
 1363 
 1364       [verse,Author,Source]
 1365       ____
 1366       A tall tale.
 1367       ____
 1368 
 1369       [options="autoplay,loop"]
 1370       video::screencast.ogg[]
 1371 
 1372       video::12345[vimeo]
 1373 
 1374       [options="autoplay,loop"]
 1375       audio::podcast.ogg[]
 1376 
 1377       one +
 1378       two
 1379 
 1380       '''
 1381       EOS
 1382       result = convert_string input, safe: :safe, backend: :xhtml
 1383       begin
 1384         Nokogiri::XML::Document.parse(result) do |config|
 1385           config.options = Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NONET
 1386         end
 1387       rescue => e
 1388         flunk "xhtml5 backend did not generate well-formed XML: #{e.message}\n#{result}"
 1389       end
 1390     end
 1391 
 1392     test 'xhtml backend should emit elements in proper namespace' do
 1393       input = 'content'
 1394       result = convert_string input, safe: :safe, backend: :xhtml, keep_namespaces: true
 1395       assert_xpath '//*[not(namespace-uri()="http://www.w3.org/1999/xhtml")]', result, 0
 1396     end
 1397 
 1398     test 'should parse out subtitle when backend is DocBook' do
 1399       input = <<~'EOS'
 1400       = Document Title: Subtitle
 1401       :doctype: book
 1402 
 1403       text
 1404       EOS
 1405       result = convert_string input, backend: 'docbook5'
 1406       assert_xpath '/book', result, 1
 1407       assert_xpath '/book/info/title[text()="Document Title"]', result, 1
 1408       assert_xpath '/book/info/subtitle[text()="Subtitle"]', result, 1
 1409     end
 1410 
 1411     test 'should be able to set doctype to article when converting to DocBoook' do
 1412       input = <<~'EOS'
 1413       = Title
 1414       Author Name
 1415 
 1416       preamble
 1417 
 1418       == First Section
 1419 
 1420       section body
 1421       EOS
 1422       result = convert_string(input, keep_namespaces: true, attributes: { 'backend' => 'docbook5' })
 1423       assert_xpath '/xmlns:article', result, 1
 1424       doc = xmlnodes_at_xpath('/xmlns:article', result, 1)
 1425       assert_equal 'http://docbook.org/ns/docbook', doc.namespaces['xmlns']
 1426       assert_equal 'http://www.w3.org/1999/xlink', doc.namespaces['xmlns:xl']
 1427       assert_xpath '/xmlns:article[@version="5.0"]', result, 1
 1428       assert_xpath '/xmlns:article/xmlns:info/xmlns:title[text()="Title"]', result, 1
 1429       assert_xpath '/xmlns:article/xmlns:simpara[text()="preamble"]', result, 1
 1430       assert_xpath '/xmlns:article/xmlns:section', result, 1
 1431       assert_css 'article:root > section[xml|id="_first_section"]', result, 1
 1432     end
 1433 
 1434     test 'should set doctype to article by default for document with no title when converting to DocBoook' do
 1435       result = convert_string('text', attributes: { 'backend' => 'docbook' })
 1436       assert_xpath '/article', result, 1
 1437       assert_xpath '/article/info/title', result, 1
 1438       assert_xpath '/article/info/title[text()="Untitled"]', result, 1
 1439       assert_xpath '/article/info/date', result, 1
 1440     end
 1441 
 1442     test 'should be able to convert DocBook manpage output when backend is DocBook and doctype is manpage' do
 1443       input = <<~'EOS'
 1444       = asciidoctor(1)
 1445       :mansource: Asciidoctor
 1446       :manmanual: Asciidoctor Manual
 1447 
 1448       == NAME
 1449 
 1450       asciidoctor - Process text
 1451 
 1452       == SYNOPSIS
 1453 
 1454       some text
 1455 
 1456       == First Section
 1457 
 1458       section body
 1459       EOS
 1460       result = convert_string(input, keep_namespaces: true, attributes: { 'backend' => 'docbook5', 'doctype' => 'manpage' })
 1461       assert_xpath '/xmlns:refentry', result, 1
 1462       doc = xmlnodes_at_xpath('/xmlns:refentry', result, 1)
 1463       assert_equal 'http://docbook.org/ns/docbook', doc.namespaces['xmlns']
 1464       assert_equal 'http://www.w3.org/1999/xlink', doc.namespaces['xmlns:xl']
 1465       assert_xpath '/xmlns:refentry[@version="5.0"]', result, 1
 1466       assert_xpath '/xmlns:refentry/xmlns:info/xmlns:title[text()="asciidoctor(1)"]', result, 1
 1467       assert_xpath '/xmlns:refentry/xmlns:refmeta/xmlns:refentrytitle[text()="asciidoctor"]', result, 1
 1468       assert_xpath '/xmlns:refentry/xmlns:refmeta/xmlns:manvolnum[text()="1"]', result, 1
 1469       assert_xpath '/xmlns:refentry/xmlns:refmeta/xmlns:refmiscinfo[@class="source"][text()="Asciidoctor"]', result, 1
 1470       assert_xpath '/xmlns:refentry/xmlns:refmeta/xmlns:refmiscinfo[@class="manual"][text()="Asciidoctor Manual"]', result, 1
 1471       assert_xpath '/xmlns:refentry/xmlns:refnamediv/xmlns:refname[text()="asciidoctor"]', result, 1
 1472       assert_xpath '/xmlns:refentry/xmlns:refnamediv/xmlns:refpurpose[text()="Process text"]', result, 1
 1473       assert_xpath '/xmlns:refentry/xmlns:refsynopsisdiv', result, 1
 1474       assert_xpath '/xmlns:refentry/xmlns:refsynopsisdiv/xmlns:simpara[text()="some text"]', result, 1
 1475       assert_xpath '/xmlns:refentry/xmlns:refsection', result, 1
 1476       assert_css 'refentry:root > refsection[xml|id="_first_section"]', result, 1
 1477     end
 1478 
 1479     test 'should output non-breaking space for source and manual in docbook manpage output if absent from source' do
 1480       input = <<~'EOS'
 1481       = asciidoctor(1)
 1482 
 1483       == NAME
 1484 
 1485       asciidoctor - Process text
 1486 
 1487       == SYNOPSIS
 1488 
 1489       some text
 1490       EOS
 1491       result = convert_string(input, keep_namespaces: true, attributes: { 'backend' => 'docbook5', 'doctype' => 'manpage' })
 1492       assert_xpath %(/xmlns:refentry/xmlns:refmeta/xmlns:refmiscinfo[@class="source"][text()="#{decode_char 160}"]), result, 1
 1493       assert_xpath %(/xmlns:refentry/xmlns:refmeta/xmlns:refmiscinfo[@class="manual"][text()="#{decode_char 160}"]), result, 1
 1494     end
 1495 
 1496     test 'should be able to set doctype to book when converting to DocBoook' do
 1497       input = <<~'EOS'
 1498       = Title
 1499       Author Name
 1500 
 1501       preamble
 1502 
 1503       == First Chapter
 1504 
 1505       chapter body
 1506       EOS
 1507       result = convert_string(input, keep_namespaces: true, attributes: { 'backend' => 'docbook5', 'doctype' => 'book' })
 1508       assert_xpath '/xmlns:book', result, 1
 1509       doc = xmlnodes_at_xpath('/xmlns:book', result, 1)
 1510       assert_equal 'http://docbook.org/ns/docbook', doc.namespaces['xmlns']
 1511       assert_equal 'http://www.w3.org/1999/xlink', doc.namespaces['xmlns:xl']
 1512       assert_xpath '/xmlns:book[@version="5.0"]', result, 1
 1513       assert_xpath '/xmlns:book/xmlns:info/xmlns:title[text()="Title"]', result, 1
 1514       assert_xpath '/xmlns:book/xmlns:preface/xmlns:simpara[text()="preamble"]', result, 1
 1515       assert_xpath '/xmlns:book/xmlns:chapter', result, 1
 1516       assert_css 'book:root > chapter[xml|id="_first_chapter"]', result, 1
 1517     end
 1518 
 1519     test 'should be able to set doctype to book for document with no title when converting to DocBoook' do
 1520       result = convert_string('text', attributes: { 'backend' => 'docbook5', 'doctype' => 'book' })
 1521       assert_xpath '/book', result, 1
 1522       assert_xpath '/book/info/date', result, 1
 1523       # NOTE simpara cannot be a direct child of book, so content must be treated as a preface
 1524       assert_xpath '/book/preface/simpara[text()="text"]', result, 1
 1525     end
 1526 
 1527     test 'adds refname to DocBook output for each name defined in NAME section of manpage' do
 1528       input = <<~'EOS'
 1529       = eve(1)
 1530       Andrew Stanton
 1531       v1.0.0
 1532       :doctype: manpage
 1533       :manmanual: EVE
 1534       :mansource: EVE
 1535 
 1536       == NAME
 1537 
 1538       eve, islifeform - analyzes an image to determine if it's a picture of a life form
 1539 
 1540       == SYNOPSIS
 1541 
 1542       *eve* ['OPTION']... 'FILE'...
 1543       EOS
 1544 
 1545       result = convert_string input, backend: 'docbook5'
 1546       assert_xpath '/refentry/refnamediv/refname', result, 2
 1547       assert_xpath '(/refentry/refnamediv/refname)[1][text()="eve"]', result, 1
 1548       assert_xpath '(/refentry/refnamediv/refname)[2][text()="islifeform"]', result, 1
 1549     end
 1550 
 1551     test 'adds a front and back cover image to DocBook 5 when doctype is book' do
 1552       input = <<~'EOS'
 1553       = Title
 1554       :doctype: book
 1555       :imagesdir: images
 1556       :front-cover-image: image:front-cover.jpg[scaledwidth=210mm]
 1557       :back-cover-image: image:back-cover.jpg[]
 1558 
 1559       preamble
 1560 
 1561       == First Chapter
 1562 
 1563       chapter body
 1564       EOS
 1565 
 1566       result = convert_string input, attributes: { 'backend' => 'docbook5' }
 1567       assert_xpath '//info/cover[@role="front"]', result, 1
 1568       assert_xpath '//info/cover[@role="front"]//imagedata[@fileref="images/front-cover.jpg"]', result, 1
 1569       assert_xpath '//info/cover[@role="back"]', result, 1
 1570       assert_xpath '//info/cover[@role="back"]//imagedata[@fileref="images/back-cover.jpg"]', result, 1
 1571     end
 1572 
 1573     test 'should be able to set backend using :backend option key' do
 1574       doc = empty_document backend: 'html5'
 1575       assert_equal 'html5', doc.attributes['backend']
 1576     end
 1577 
 1578     test ':backend option should override backend attribute' do
 1579       doc = empty_document backend: 'html5', attributes: { 'backend' => 'docbook5' }
 1580       assert_equal 'html5', doc.attributes['backend']
 1581     end
 1582 
 1583     test 'should be able to set doctype using :doctype option key' do
 1584       doc = empty_document doctype: 'book'
 1585       assert_equal 'book', doc.attributes['doctype']
 1586     end
 1587 
 1588     test ':doctype option should override doctype attribute' do
 1589       doc = empty_document doctype: 'book', attributes: { 'doctype' => 'article' }
 1590       assert_equal 'book', doc.attributes['doctype']
 1591     end
 1592 
 1593     test 'do not override explicit author initials' do
 1594       input = <<~'EOS'
 1595       = AsciiDoc
 1596       Stuart Rackham <founder@asciidoc.org>
 1597       :Author Initials: SJR
 1598 
 1599       more info...
 1600       EOS
 1601       output = convert_string input, attributes: { 'backend' => 'docbook5' }
 1602       assert_xpath '/article/info/authorinitials[text()="SJR"]', output, 1
 1603     end
 1604 
 1605     test 'attribute entry can appear immediately after document title' do
 1606       input = <<~'EOS'
 1607       Reference Guide
 1608       ===============
 1609       :toc:
 1610 
 1611       preamble
 1612       EOS
 1613       doc = document_from_string input
 1614       assert doc.attr?('toc')
 1615       assert_equal '', doc.attr('toc')
 1616     end
 1617 
 1618     test 'attribute entry can appear before author line under document title' do
 1619       input = <<~'EOS'
 1620       Reference Guide
 1621       ===============
 1622       :toc:
 1623       Dan Allen
 1624 
 1625       preamble
 1626       EOS
 1627       doc = document_from_string input
 1628       assert doc.attr?('toc')
 1629       assert_equal '', doc.attr('toc')
 1630       assert_equal 'Dan Allen', doc.attr('author')
 1631     end
 1632 
 1633     test 'should parse mantitle and manvolnum from document title for manpage doctype' do
 1634       input = <<~'EOS'
 1635       = asciidoctor ( 1 )
 1636       :doctype: manpage
 1637 
 1638       == NAME
 1639 
 1640       asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats
 1641       EOS
 1642 
 1643       doc = document_from_string input
 1644       assert_equal 'asciidoctor', doc.attr('mantitle')
 1645       assert_equal '1', doc.attr('manvolnum')
 1646     end
 1647 
 1648     test 'should perform attribute substitution on mantitle in manpage doctype' do
 1649       input = <<~'EOS'
 1650       = {app}(1)
 1651       :doctype: manpage
 1652       :app: Asciidoctor
 1653 
 1654       == NAME
 1655 
 1656       asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats
 1657       EOS
 1658 
 1659       doc = document_from_string input
 1660       assert_equal 'asciidoctor', doc.attr('mantitle')
 1661     end
 1662 
 1663     test 'should consume name section as manname and manpurpose for manpage doctype' do
 1664       input = <<~'EOS'
 1665       = asciidoctor(1)
 1666       :doctype: manpage
 1667 
 1668       == NAME
 1669 
 1670       asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats
 1671       EOS
 1672 
 1673       doc = document_from_string input
 1674       assert_equal 'asciidoctor', doc.attr('manname')
 1675       assert_equal 'converts AsciiDoc source files to HTML, DocBook and other formats', doc.attr('manpurpose')
 1676       assert_equal '_name', doc.attr('manname-id')
 1677       assert_equal 0, doc.blocks.size
 1678     end
 1679 
 1680     test 'should set docname and outfilesuffix from manname and manvolnum for manpage backend and doctype' do
 1681       input = <<~'EOS'
 1682       = asciidoctor(1)
 1683       :doctype: manpage
 1684 
 1685       == NAME
 1686 
 1687       asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats
 1688       EOS
 1689 
 1690       doc = document_from_string input, backend: 'manpage'
 1691       assert_equal 'asciidoctor', doc.attributes['docname']
 1692       assert_equal '.1', doc.attributes['outfilesuffix']
 1693     end
 1694 
 1695     test 'should mark synopsis as special section in manpage doctype' do
 1696       input = <<~'EOS'
 1697       = asciidoctor(1)
 1698       :doctype: manpage
 1699 
 1700       == NAME
 1701 
 1702       asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats
 1703 
 1704       == SYNOPSIS
 1705 
 1706       *asciidoctor* ['OPTION']... 'FILE'..
 1707       EOS
 1708 
 1709       doc = document_from_string input
 1710       synopsis_section = doc.blocks.first
 1711       refute_nil synopsis_section
 1712       assert_equal :section, synopsis_section.context
 1713       assert synopsis_section.special
 1714       assert_equal 'synopsis', synopsis_section.sectname
 1715     end
 1716 
 1717     test 'should output special header block in HTML for manpage doctype' do
 1718       input = <<~'EOS'
 1719       = asciidoctor(1)
 1720       :doctype: manpage
 1721 
 1722       == NAME
 1723 
 1724       asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats
 1725 
 1726       == SYNOPSIS
 1727 
 1728       *asciidoctor* ['OPTION']... 'FILE'..
 1729       EOS
 1730 
 1731       output = convert_string input
 1732       assert_css 'body.manpage', output, 1
 1733       assert_xpath '//body/*[@id="header"]/h1[text()="asciidoctor(1) Manual Page"]', output, 1
 1734       assert_xpath '//body/*[@id="header"]/h1/following-sibling::h2[text()="NAME"]', output, 1
 1735       assert_xpath '//h2[@id="_name"][text()="NAME"]', output, 1
 1736       assert_xpath '//h2[text()="NAME"]/following-sibling::*[@class="sectionbody"]', output, 1
 1737       assert_xpath '//h2[text()="NAME"]/following-sibling::*[@class="sectionbody"]/p[text()="asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats"]', output, 1
 1738       assert_xpath '//*[@id="content"]/*[@class="sect1"]/h2[text()="SYNOPSIS"]', output, 1
 1739     end
 1740 
 1741     test 'should output special header block in embeddable HTML for manpage doctype' do
 1742       input = <<~'EOS'
 1743       = asciidoctor(1)
 1744       :doctype: manpage
 1745       :showtitle:
 1746 
 1747       == NAME
 1748 
 1749       asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats
 1750 
 1751       == SYNOPSIS
 1752 
 1753       *asciidoctor* ['OPTION']... 'FILE'..
 1754       EOS
 1755 
 1756       output = convert_string_to_embedded input
 1757       assert_xpath '/h1[text()="asciidoctor(1) Manual Page"]', output, 1
 1758       assert_xpath '/h1/following-sibling::h2[text()="NAME"]', output, 1
 1759       assert_xpath '/h2[@id="_name"][text()="NAME"]', output, 1
 1760       assert_xpath '/h2[text()="NAME"]/following-sibling::*[@class="sectionbody"]', output, 1
 1761       assert_xpath '/h2[text()="NAME"]/following-sibling::*[@class="sectionbody"]/p[text()="asciidoctor - converts AsciiDoc source files to HTML, DocBook and other formats"]', output, 1
 1762     end
 1763   end
 1764 
 1765   context 'Secure Asset Path' do
 1766     test 'allows us to specify a path relative to the current dir' do
 1767       doc = empty_document
 1768       legit_path = Dir.pwd + '/foo'
 1769       assert_equal legit_path, doc.normalize_asset_path(legit_path)
 1770     end
 1771 
 1772     test 'keeps naughty absolute paths from getting outside' do
 1773       naughty_path = "#{disk_root}etc/passwd"
 1774       using_memory_logger do |logger|
 1775         doc = empty_document
 1776         secure_path = doc.normalize_asset_path naughty_path
 1777         refute_equal naughty_path, secure_path
 1778         assert_equal ::File.join(doc.base_dir, 'etc/passwd'), secure_path
 1779         assert_message logger, :WARN, 'path is outside of jail; recovering automatically'
 1780       end
 1781     end
 1782 
 1783     test 'keeps naughty relative paths from getting outside' do
 1784       naughty_path = 'safe/ok/../../../../../etc/passwd'
 1785       using_memory_logger do
 1786         doc = empty_document
 1787         secure_path = doc.normalize_asset_path naughty_path
 1788         refute_equal naughty_path, secure_path
 1789         assert_match(/^#{doc.base_dir}/, secure_path)
 1790       end
 1791     end
 1792 
 1793     test 'should raise an exception when a converter cannot be resolved before conversion' do
 1794       input = <<~'EOS'
 1795       = Document Title
 1796 
 1797       text
 1798       EOS
 1799       exception = assert_raises NotImplementedError do
 1800         Asciidoctor.convert input, backend: 'unknownBackend'
 1801       end
 1802       assert_includes exception.message, 'missing converter for backend \'unknownBackend\''
 1803     end
 1804 
 1805     test 'should raise an exception when a converter cannot be resolved while parsing' do
 1806       input = <<~'EOS'
 1807       = Document Title
 1808 
 1809       == A _Big_ Section
 1810 
 1811       text
 1812       EOS
 1813       exception = assert_raises NotImplementedError do
 1814         Asciidoctor.convert input, backend: 'unknownBackend'
 1815       end
 1816       assert_includes exception.message, 'missing converter for backend \'unknownBackend\''
 1817     end
 1818   end
 1819 
 1820   context 'Timing report' do
 1821     test 'print_report does not lose precision' do
 1822       timings = Asciidoctor::Timings.new
 1823       log = timings.instance_variable_get(:@log)
 1824       log[:read] = 0.00001
 1825       log[:parse] = 0.00003
 1826       log[:convert] = 0.00005
 1827       timings.print_report(sink = StringIO.new)
 1828       expect = ['0.00004', '0.00005', '0.00009']
 1829       result = sink.string.split("\n").map {|l| l.sub(/.*:\s*([\d.]+)/, '\1') }
 1830       assert_equal expect, result
 1831     end
 1832 
 1833     test 'print_report should print 0 for untimed phases' do
 1834       Asciidoctor::Timings.new.print_report(sink = StringIO.new)
 1835       expect = [].fill('0.00000', 0..2)
 1836       result = sink.string.split("\n").map {|l| l.sub(/.*:\s*([\d.]+)/, '\1') }
 1837       assert_equal expect, result
 1838     end
 1839   end
 1840 
 1841   context 'Date time attributes' do
 1842     test 'should compute docyear and docdatetime from docdate and doctime' do
 1843       doc = Asciidoctor::Document.new [], attributes: {'docdate' => '2015-01-01', 'doctime' => '10:00:00-0700'}
 1844       assert_equal '2015-01-01', (doc.attr 'docdate')
 1845       assert_equal '2015', (doc.attr 'docyear')
 1846       assert_equal '10:00:00-0700', (doc.attr 'doctime')
 1847       assert_equal '2015-01-01 10:00:00-0700', (doc.attr 'docdatetime')
 1848     end
 1849 
 1850     test 'should allow docdate and doctime to be overridden' do
 1851       doc = Asciidoctor::Document.new [], input_mtime: ::Time.now, attributes: {'docdate' => '2015-01-01', 'doctime' => '10:00:00-0700'}
 1852       assert_equal '2015-01-01', (doc.attr 'docdate')
 1853       assert_equal '2015', (doc.attr 'docyear')
 1854       assert_equal '10:00:00-0700', (doc.attr 'doctime')
 1855       assert_equal '2015-01-01 10:00:00-0700', (doc.attr 'docdatetime')
 1856     end
 1857 
 1858     test 'should compute docdatetime from doctime' do
 1859       doc = Asciidoctor::Document.new [], attributes: {'doctime' => '10:00:00-0700'}
 1860       assert_equal '10:00:00-0700', (doc.attr 'doctime')
 1861       assert (doc.attr 'docdatetime').end_with?(' 10:00:00-0700')
 1862     end
 1863 
 1864     test 'should compute docyear from docdate' do
 1865       doc = Asciidoctor::Document.new [], attributes: {'docdate' => '2015-01-01'}
 1866       assert_equal '2015', (doc.attr 'docyear')
 1867       assert (doc.attr 'docdatetime').start_with?('2015-01-01 ')
 1868     end
 1869 
 1870     test 'should allow doctime to be overridden' do
 1871       old_source_date_epoch = ENV.delete 'SOURCE_DATE_EPOCH'
 1872       begin
 1873         doc = Asciidoctor::Document.new [], input_mtime: ::Time.new(2019, 01, 02, 3, 4, 5, "+06:00"), attributes: {'doctime' => '10:00:00-0700'}
 1874         assert_equal '2019-01-02', (doc.attr 'docdate')
 1875         assert_equal '2019', (doc.attr 'docyear')
 1876         assert_equal '10:00:00-0700', (doc.attr 'doctime')
 1877         assert_equal '2019-01-02 10:00:00-0700', (doc.attr 'docdatetime')
 1878       ensure
 1879         ENV['SOURCE_DATE_EPOCH'] = old_source_date_epoch if old_source_date_epoch
 1880       end
 1881     end
 1882 
 1883     test 'should allow docdate to be overridden' do
 1884       old_source_date_epoch = ENV.delete 'SOURCE_DATE_EPOCH'
 1885       begin
 1886         doc = Asciidoctor::Document.new [], input_mtime: ::Time.new(2019, 01, 02, 3, 4, 5, "+06:00"), attributes: {'docdate' => '2015-01-01'}
 1887         assert_equal '2015-01-01', (doc.attr 'docdate')
 1888         assert_equal '2015', (doc.attr 'docyear')
 1889         assert_equal '2015-01-01 03:04:05 +0600', (doc.attr 'docdatetime')
 1890       ensure
 1891         ENV['SOURCE_DATE_EPOCH'] = old_source_date_epoch if old_source_date_epoch
 1892       end
 1893     end
 1894   end
 1895 end