"Fossies" - the Fresh Open Source Software Archive

Member "asciidoctor-2.0.10/test/sections_test.rb" (1 Jun 2019, 103475 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.

    1 # frozen_string_literal: true
    2 require_relative 'test_helper'
    3 
    4 context 'Sections' do
    5   context 'Ids' do
    6     test 'synthetic id is generated by default' do
    7       sec = block_from_string('== Section One')
    8       assert_equal '_section_one', sec.id
    9     end
   10 
   11     test 'duplicate synthetic id is automatically enumerated' do
   12       doc = document_from_string <<~'EOS'
   13       == Section One
   14 
   15       == Section One
   16       EOS
   17       assert_equal 2, doc.blocks.size
   18       assert_equal '_section_one', doc.blocks[0].id
   19       assert_equal '_section_one_2', doc.blocks[1].id
   20     end
   21 
   22     test 'synthetic id removes non-word characters' do
   23       sec = block_from_string("== We’re back!")
   24       assert_equal '_were_back', sec.id
   25     end
   26 
   27     test 'synthetic id removes repeating separators' do
   28       sec = block_from_string('== Section $ One')
   29       assert_equal '_section_one', sec.id
   30     end
   31 
   32     test 'synthetic id removes entities' do
   33       sec = block_from_string('== Ben & Jerry &amp; Company&sup1; &#34;Ice Cream Brothers&#34; &#12354;')
   34       assert_equal '_ben_jerry_company_ice_cream_brothers', sec.id
   35     end
   36 
   37     test 'synthetic id removes adjacent entities with mixed case' do
   38       sec = block_from_string('== a &#xae;&AMP;&#xA9; b')
   39       assert_equal '_a_b', sec.id
   40     end
   41 
   42     test 'synthetic id removes XML tags' do
   43       sec = block_from_string('== Use the `run` command to make it icon:gear[]')
   44       assert_equal '_use_the_run_command_to_make_it_gear', sec.id
   45     end
   46 
   47     test 'synthetic id collapses repeating spaces' do
   48       sec = block_from_string('== Go    Far')
   49       assert_equal '_go_far', sec.id
   50     end
   51 
   52     test 'synthetic id replaces hyphens with separator' do
   53       sec = block_from_string('== State-of-the-art design')
   54       assert_equal '_state_of_the_art_design', sec.id
   55     end
   56 
   57     test 'synthetic id replaces dots with separator' do
   58       sec = block_from_string("== Section 1.1.1")
   59       assert_equal '_section_1_1_1', sec.id
   60     end
   61 
   62     test 'synthetic id prefix can be customized' do
   63       sec = block_from_string(":idprefix: id_\n\n== Section One")
   64       assert_equal 'id_section_one', sec.id
   65     end
   66 
   67     test 'synthetic id prefix can be set to blank' do
   68       sec = block_from_string(":idprefix:\n\n== Section One")
   69       assert_equal 'section_one', sec.id
   70     end
   71 
   72     test 'synthetic id prefix is stripped from beginning of id if set to blank' do
   73       sec = block_from_string(":idprefix:\n\n== & ! More")
   74       assert_equal 'more', sec.id
   75     end
   76 
   77     test 'synthetic id separator can be customized' do
   78       sec = block_from_string(":idseparator: -\n\n== Section One")
   79       assert_equal '_section-one', sec.id
   80     end
   81 
   82     test 'synthetic id separator can be hyphen and hyphens are preserved' do
   83       sec = block_from_string(":idseparator: -\n\n== State-of-the-art design")
   84       assert_equal '_state-of-the-art-design', sec.id
   85     end
   86 
   87     test 'synthetic id separator can be dot and dots are preserved' do
   88       sec = block_from_string(":idseparator: .\n\n== Version 5.0.1")
   89       assert_equal '_version.5.0.1', sec.id
   90     end
   91 
   92     test 'synthetic id separator can only be one character' do
   93       input = <<~'EOS'
   94       :idseparator: -=-
   95 
   96       == This Section Is All You Need
   97       EOS
   98       sec = block_from_string input
   99       assert_equal '_this-section-is-all-you-need', sec.id
  100     end
  101 
  102     test 'synthetic id separator can be set to blank' do
  103       sec = block_from_string(":idseparator:\n\n== Section One")
  104       assert_equal '_sectionone', sec.id
  105     end
  106 
  107     test 'synthetic id separator can be set to blank when idprefix is blank' do
  108       sec = block_from_string(":idprefix:\n:idseparator:\n\n== Section One")
  109       assert_equal 'sectionone', sec.id
  110     end
  111 
  112     test 'synthetic id separator is removed from beginning of id when idprefix is blank' do
  113       sec = block_from_string(":idprefix:\n:idseparator: _\n\n== +Section One")
  114       assert_equal 'section_one', sec.id
  115     end
  116 
  117     test 'synthetic ids can be disabled' do
  118       sec = block_from_string(":sectids!:\n\n== Section One\n")
  119       assert_nil sec.id
  120     end
  121 
  122     test 'explicit id in anchor above section title overrides synthetic id' do
  123       sec = block_from_string("[[one]]\n== Section One")
  124       assert_equal 'one', sec.id
  125     end
  126 
  127     test 'explicit id in block attributes above section title overrides synthetic id' do
  128       sec = block_from_string("[id=one]\n== Section One")
  129       assert_equal 'one', sec.id
  130     end
  131 
  132     test 'explicit id set using shorthand in style above section title overrides synthetic id' do
  133       sec = block_from_string("[#one]\n== Section One")
  134       assert_equal 'one', sec.id
  135     end
  136 
  137     test 'should use explicit id from last block attribute line above section title that defines an explicit id' do
  138       input = <<~'EOS'
  139       [#un]
  140       [#one]
  141       == Section One
  142       EOS
  143       sec = block_from_string input
  144       assert_equal 'one', sec.id
  145     end
  146 
  147     test 'explicit id can be defined using an embedded anchor' do
  148       sec = block_from_string("== Section One [[one]] ==")
  149       assert_equal 'one', sec.id
  150       assert_equal 'Section One', sec.title
  151     end
  152 
  153     test 'explicit id can be defined using an embedded anchor when using setext section titles' do
  154       input = <<~'EOS'
  155       Section Title [[refid,reftext]]
  156       -------------------------------
  157       EOS
  158       sec = block_from_string input
  159       assert_equal 'Section Title', sec.title
  160       assert_equal 'refid', sec.id
  161       assert_equal 'reftext', (sec.attr 'reftext')
  162     end
  163 
  164     test 'explicit id can be defined using an embedded anchor with reftext' do
  165       sec = block_from_string("== Section One [[one,Section Uno]] ==")
  166       assert_equal 'one', sec.id
  167       assert_equal 'Section One', sec.title
  168       assert_equal 'Section Uno', (sec.attr 'reftext')
  169     end
  170 
  171     test 'id and reftext in embedded anchor cannot be quoted' do
  172       sec = block_from_string(%(== Section One [["one","Section Uno"]] ==))
  173       refute_equal 'one', sec.id
  174       assert_equal 'Section One [["one","Section Uno"]]', sec.title
  175       assert_nil(sec.attr 'reftext')
  176     end
  177 
  178     test 'reftext in embedded anchor may contain comma' do
  179       sec = block_from_string(%(== Section One [[one, Section,Uno]] ==))
  180       assert_equal 'one', sec.id
  181       assert_equal 'Section One', sec.title
  182       assert_equal 'Section,Uno', (sec.attr 'reftext')
  183     end
  184 
  185     test 'should unescape but not process inline anchor' do
  186       sec = block_from_string(%(== Section One \\[[one]] ==))
  187       refute_equal 'one', sec.id
  188       assert_equal 'Section One [[one]]', sec.title
  189     end
  190 
  191     test 'should not process inline anchor in section title if section has explicit ID' do
  192       sec = block_from_string(%([#sect-one]\n== Section One [[one]]))
  193       assert_equal 'sect-one', sec.id
  194       assert_equal 'Section One <a id="one"></a>', sec.title
  195     end
  196 
  197     test 'title substitutions are applied before generating id' do
  198       sec = block_from_string("== Section{sp}One\n")
  199       assert_equal '_section_one', sec.id
  200     end
  201 
  202     test 'synthetic ids are unique' do
  203       input = <<~'EOS'
  204       == Some section
  205 
  206       text
  207 
  208       == Some section
  209 
  210       text
  211       EOS
  212       doc = document_from_string input
  213       assert_equal '_some_section', doc.blocks[0].id
  214       assert_equal '_some_section_2', doc.blocks[1].id
  215     end
  216 
  217     # NOTE test cannot be run in parallel with other tests
  218     test 'can set start index of synthetic ids' do
  219       old_unique_id_start_index = Asciidoctor::Compliance.unique_id_start_index
  220       begin
  221         input = <<~'EOS'
  222         == Some section
  223 
  224         text
  225 
  226         == Some section
  227 
  228         text
  229         EOS
  230         Asciidoctor::Compliance.unique_id_start_index = 1
  231         doc = document_from_string input
  232         assert_equal '_some_section', doc.blocks[0].id
  233         assert_equal '_some_section_1', doc.blocks[1].id
  234       ensure
  235         Asciidoctor::Compliance.unique_id_start_index = old_unique_id_start_index
  236       end
  237     end
  238 
  239     test 'should use specified id and reftext when registering section reference' do
  240       input = <<~'EOS'
  241       [[install,Install Procedure]]
  242       == Install
  243 
  244       content
  245       EOS
  246 
  247       doc = document_from_string input
  248       ref = doc.catalog[:refs]['install']
  249       refute_nil ref
  250       assert_equal 'Install Procedure', ref.reftext
  251       assert_equal 'install', (doc.resolve_id 'Install Procedure')
  252     end
  253 
  254     test 'should use specified reftext when registering section reference' do
  255       input = <<~'EOS'
  256       [reftext="Install Procedure"]
  257       == Install
  258 
  259       content
  260       EOS
  261 
  262       doc = document_from_string input
  263       ref = doc.catalog[:refs]['_install']
  264       refute_nil ref
  265       assert_equal 'Install Procedure', ref.reftext
  266       assert_equal '_install', (doc.resolve_id 'Install Procedure')
  267     end
  268 
  269     test 'should resolve attribute reference in title using attribute defined at location of section title' do
  270       input = <<~'EOS'
  271       :platform-id: linux
  272       :platform-name: Linux
  273 
  274       [#install-{platform-id}]
  275       == Install on {platform-name}
  276 
  277       content
  278 
  279       :platform-id: win32
  280       :platform-name: Windows
  281 
  282       [#install-{platform-id}]
  283       == Install on {platform-name}
  284 
  285       content
  286       EOS
  287 
  288       doc = document_from_string input
  289       ref = doc.catalog[:refs]['install-win32']
  290       refute_nil ref
  291       assert_equal 'Install on Windows', ref.title
  292       assert_equal 'install-win32', (doc.resolve_id 'Install on Windows')
  293     end
  294 
  295     test 'should substitute attributes when registering reftext for section' do
  296       input = <<~'EOS'
  297       :platform-name: n/a
  298       == Overview
  299 
  300       :platform-name: Linux
  301 
  302       [[install,install on {platform-name}]]
  303       == Install
  304 
  305       content
  306       EOS
  307 
  308       doc = document_from_string input
  309       ref = doc.catalog[:refs]['install']
  310       refute_nil ref
  311       assert_equal 'install on Linux', ref.reftext
  312       assert_equal 'install', (doc.resolve_id 'install on Linux')
  313     end
  314 
  315     test 'duplicate section id should not overwrite existing section id entry in references table' do
  316       input = <<~'EOS'
  317       [#install]
  318       == First Install
  319 
  320       content
  321 
  322       [#install]
  323       == Second Install
  324 
  325       content
  326       EOS
  327 
  328       using_memory_logger do |logger|
  329         doc = document_from_string input
  330         ref = doc.catalog[:refs]['install']
  331         refute_nil ref
  332         assert_nil ref.reftext
  333         assert_equal 'First Install', ref.title
  334         assert_equal 'install', (doc.resolve_id 'First Install')
  335         assert_message logger, :WARN, '<stdin>: line 7: id assigned to section already in use: install', Hash
  336       end
  337     end
  338 
  339     test 'should warn if explicit section ID matches auto-generated section ID' do
  340       input = <<~'EOS'
  341       == Do Not Repeat Yourself
  342 
  343       content
  344 
  345       [#_do_not_repeat_yourself]
  346       == Do Not Repeat Yourself
  347 
  348       content
  349       EOS
  350 
  351       using_memory_logger do |logger|
  352         doc = document_from_string input
  353         ref = doc.catalog[:refs]['_do_not_repeat_yourself']
  354         refute_nil ref
  355         assert_nil ref.reftext
  356         assert_equal 'Do Not Repeat Yourself', ref.title
  357         assert_equal '_do_not_repeat_yourself', (doc.resolve_id 'Do Not Repeat Yourself')
  358         assert_message logger, :WARN, '<stdin>: line 6: id assigned to section already in use: _do_not_repeat_yourself', Hash
  359         assert_equal 2, (doc.convert.scan 'id="_do_not_repeat_yourself"').size
  360       end
  361     end
  362 
  363     test 'duplicate block id should not overwrite existing section id entry in references table' do
  364       input = <<~'EOS'
  365       [#install]
  366       == First Install
  367 
  368       content
  369 
  370       [#install]
  371       content
  372       EOS
  373 
  374       using_memory_logger do |logger|
  375         doc = document_from_string input
  376         ref = doc.catalog[:refs]['install']
  377         refute_nil ref
  378         assert_nil ref.reftext
  379         assert_equal 'First Install', ref.title
  380         assert_equal 'install', (doc.resolve_id 'First Install')
  381         assert_message logger, :WARN, '<stdin>: line 7: id assigned to block already in use: install', Hash
  382       end
  383     end
  384   end
  385 
  386   context 'Levels' do
  387     context 'Document Title (Level 0)' do
  388       test "document title with multiline syntax" do
  389         title = "My Title"
  390         chars = "=" * title.length
  391         assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars)
  392         assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars + "\n")
  393       end
  394 
  395       test "document title with multiline syntax, give a char" do
  396         title = "My Title"
  397         chars = "=" * (title.length + 1)
  398         assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars)
  399         assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars + "\n")
  400       end
  401 
  402       test "document title with multiline syntax, take a char" do
  403         title = "My Title"
  404         chars = "=" * (title.length - 1)
  405         assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars)
  406         assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string(title + "\n" + chars + "\n")
  407       end
  408 
  409       test 'document title with multiline syntax and unicode characters' do
  410         input = <<~'EOS'
  411         AsciiDoc Writer’s Guide
  412         =======================
  413         Author Name
  414 
  415         preamble
  416         EOS
  417 
  418         result = convert_string input
  419         assert_xpath '//h1', result, 1
  420         assert_xpath '//h1[text()="AsciiDoc Writer’s Guide"]', result, 1
  421       end
  422 
  423       test "not enough chars for a multiline document title" do
  424         title = "My Title"
  425         chars = "=" * (title.length - 2)
  426         using_memory_logger do |logger|
  427           output = convert_string(title + "\n" + chars)
  428           assert_xpath '//h1', output, 0
  429           refute logger.empty?
  430           logger.clear
  431           output = convert_string(title + "\n" + chars + "\n")
  432           assert_xpath '//h1', output, 0
  433           refute logger.empty?
  434         end
  435       end
  436 
  437       test "too many chars for a multiline document title" do
  438         title = "My Title"
  439         chars = "=" * (title.length + 2)
  440         using_memory_logger do |logger|
  441           output = convert_string(title + "\n" + chars)
  442           assert_xpath '//h1', output, 0
  443           refute logger.empty?
  444           logger.clear
  445           output = convert_string(title + "\n" + chars + "\n")
  446           assert_xpath '//h1', output, 0
  447           refute logger.empty?
  448         end
  449       end
  450 
  451       test "document title with multiline syntax cannot begin with a dot" do
  452         title = ".My Title"
  453         chars = "=" * title.length
  454         using_memory_logger do |logger|
  455           output = convert_string(title + "\n" + chars)
  456           assert_xpath '//h1', output, 0
  457           refute logger.empty?
  458         end
  459       end
  460 
  461       test "document title with atx syntax" do
  462         assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string("= My Title")
  463       end
  464 
  465       test "document title with symmetric syntax" do
  466         assert_xpath "//h1[not(@id)][text() = 'My Title']", convert_string("= My Title =")
  467       end
  468 
  469       test 'document title created from leveloffset shift defined in document' do
  470         assert_xpath "//h1[not(@id)][text() = 'Document Title']", convert_string(%(:leveloffset: -1\n== Document Title))
  471       end
  472 
  473       test 'document title created from leveloffset shift defined in API' do
  474         assert_xpath "//h1[not(@id)][text() = 'Document Title']", convert_string('== Document Title', attributes: { 'leveloffset' => '-1@' })
  475       end
  476 
  477       test 'should assign id on document title to body' do
  478         input = <<~'EOS'
  479         [[idname]]
  480         = Document Title
  481 
  482         content
  483         EOS
  484         output = convert_string input
  485         assert_css 'body#idname', output, 1
  486       end
  487 
  488       test 'should assign id defined using shorthand syntax on document title to body' do
  489         input = <<~'EOS'
  490         [#idname]
  491         = Document Title
  492 
  493         content
  494         EOS
  495         output = convert_string input
  496         assert_css 'body#idname', output, 1
  497       end
  498 
  499       test 'should use ID defined in block attributes instead of ID defined inline' do
  500         input = <<~'EOS'
  501         [#idname-block]
  502         = Document Title [[idname-inline]]
  503 
  504         content
  505         EOS
  506         output = convert_string input
  507         assert_css 'body#idname-block', output, 1
  508       end
  509 
  510       test 'block id above document title sets id on document' do
  511         input = <<~'EOS'
  512         [[reference]]
  513         = Reference Manual
  514         :css-signature: refguide
  515 
  516         preamble
  517         EOS
  518         doc = document_from_string input
  519         assert_equal 'reference', doc.id
  520         assert_equal 'refguide', doc.attr('css-signature')
  521         output = doc.convert
  522         assert_css 'body#reference', output, 1
  523       end
  524 
  525       test 'should register document in catalog if id is set' do
  526         input = <<~'EOS'
  527         [[manual,Manual]]
  528         = Reference Manual
  529 
  530         preamble
  531         EOS
  532         doc = document_from_string input
  533         assert_equal 'manual', doc.id
  534         assert_equal 'Manual', doc.attributes['reftext']
  535         assert_equal doc, doc.catalog[:refs]['manual']
  536       end
  537 
  538       test 'should compute xreftext to document title' do
  539         input = <<~'EOS'
  540         [#manual]
  541         = Reference Manual
  542         :xrefstyle: full
  543 
  544         This is the <<manual>>.
  545         EOS
  546         output = convert_string input
  547         assert_xpath '//a[text()="Reference Manual"]', output, 1
  548       end
  549 
  550       test 'should discard style, role and options shorthand attributes defined on document title' do
  551         input = <<~'EOS'
  552         [style#idname.rolename%optionname]
  553         = Document Title
  554 
  555         content
  556         EOS
  557         doc = document_from_string input
  558         assert_empty doc.blocks[0].attributes
  559         output = doc.convert
  560         assert_css '#idname', output, 1
  561         assert_css 'body#idname', output, 1
  562         assert_css '.rolename', output, 1
  563         assert_css 'body.rolename', output, 1
  564       end
  565     end
  566 
  567     context 'Level 1' do
  568       test "with multiline syntax" do
  569         assert_xpath "//h2[@id='_my_section'][text() = 'My Section']", convert_string("My Section\n-----------")
  570       end
  571 
  572       test 'should not recognize underline containing a mix of characters as setext section title' do
  573         input = <<~'EOS'
  574         My Section
  575         ----^^----
  576         EOS
  577 
  578         result = convert_string_to_embedded input
  579         assert_xpath '//h2[@id="_my_section"][text() = "My Section"]', result, 0
  580         assert_includes result, '----^^----'
  581       end
  582 
  583       test 'should not recognize section title that does not contain alphanumeric character' do
  584         input = <<~'EOS'
  585         !@#$
  586         ----
  587         EOS
  588 
  589         using_memory_logger do |logger|
  590           result = convert_string_to_embedded input
  591           assert_css 'h2', result, 0
  592         end
  593       end
  594 
  595       test 'should not recognize section title that consists of only underscores' do
  596         input = <<~'EOS'
  597         ____
  598         ----
  599         EOS
  600 
  601         using_memory_logger do |logger|
  602           result = convert_string_to_embedded input
  603           assert_css 'h2', result, 0
  604         end
  605       end
  606 
  607       test 'should preprocess second line of setext section title' do
  608         input = <<~'EOS'
  609         Section Title
  610         ifdef::asciidoctor[]
  611         -------------
  612         endif::[]
  613         EOS
  614         result = convert_string_to_embedded input
  615         assert_xpath '//h2', result, 1
  616       end
  617 
  618       test "heading title with multiline syntax cannot begin with a dot" do
  619         title = ".My Title"
  620         chars = "-" * title.length
  621         using_memory_logger do |logger|
  622           output = convert_string(title + "\n" + chars)
  623           assert_xpath '//h2', output, 0
  624           refute logger.empty?
  625         end
  626       end
  627 
  628       test "with atx syntax" do
  629         assert_xpath "//h2[@id='_my_title'][text() = 'My Title']", convert_string("== My Title")
  630       end
  631 
  632       test "with atx symmetric syntax" do
  633         assert_xpath "//h2[@id='_my_title'][text() = 'My Title']", convert_string("== My Title ==")
  634       end
  635 
  636       test "with atx non-matching symmetric syntax" do
  637         assert_xpath "//h2[@id='_my_title'][text() = 'My Title ===']", convert_string("== My Title ===")
  638       end
  639 
  640       test "with XML entity" do
  641         assert_xpath "//h2[@id='_whats_new'][text() = \"What#{decode_char 8217}s new?\"]", convert_string("== What's new?")
  642       end
  643 
  644       test "with non-word character" do
  645         assert_xpath "//h2[@id='_whats_new'][text() = \"What’s new?\"]", convert_string("== What’s new?")
  646       end
  647 
  648       test "with sequential non-word characters" do
  649         assert_xpath "//h2[@id='_what_the_is_this'][text() = 'What the \#@$ is this?']", convert_string('== What the #@$ is this?')
  650       end
  651 
  652       test "with trailing whitespace" do
  653         assert_xpath "//h2[@id='_my_title'][text() = 'My Title']", convert_string("== My Title ")
  654       end
  655 
  656       test "with custom blank idprefix" do
  657         assert_xpath "//h2[@id='my_title'][text() = 'My Title']", convert_string(":idprefix:\n\n== My Title ")
  658       end
  659 
  660       test "with custom non-blank idprefix" do
  661         assert_xpath "//h2[@id='ref_my_title'][text() = 'My Title']", convert_string(":idprefix: ref_\n\n== My Title ")
  662       end
  663 
  664       test 'with multibyte characters' do
  665         input = '== Asciidoctor in 中文'
  666         output = convert_string input
  667         assert_xpath '//h2[@id="_asciidoctor_in_中文"][text()="Asciidoctor in 中文"]', output
  668       end
  669 
  670       test 'with only multibyte characters' do
  671         input = '== 视图'
  672         output = convert_string_to_embedded input
  673         assert_xpath '//h2[@id="_视图"][text()="视图"]', output
  674       end
  675 
  676       test 'multiline syntax with only multibyte characters' do
  677         input = <<~'EOS'
  678         视图
  679         --
  680 
  681         content
  682 
  683         连接器
  684         ---
  685 
  686         content
  687         EOS
  688         # see https://github.com/oracle/truffleruby/issues/1563
  689         input = String.new input, encoding: ::Encoding::UTF_8 if RUBY_ENGINE == 'truffleruby'
  690         output = convert_string_to_embedded input
  691         assert_xpath '//h2[@id="_视图"][text()="视图"]', output
  692         assert_xpath '//h2[@id="_连接器"][text()="连接器"]', output
  693       end
  694     end
  695 
  696     context 'Level 2' do
  697       test "with multiline syntax" do
  698         assert_xpath "//h3[@id='_my_section'][text() = 'My Section']", convert_string(":fragment:\nMy Section\n~~~~~~~~~~~")
  699       end
  700 
  701       test "with atx line syntax" do
  702         assert_xpath "//h3[@id='_my_title'][text() = 'My Title']", convert_string(":fragment:\n=== My Title")
  703       end
  704     end
  705 
  706     context 'Level 3' do
  707       test "with multiline syntax" do
  708         assert_xpath "//h4[@id='_my_section'][text() = 'My Section']", convert_string(":fragment:\nMy Section\n^^^^^^^^^^")
  709       end
  710 
  711       test 'with atx line syntax' do
  712         assert_xpath "//h4[@id='_my_title'][text() = 'My Title']", convert_string(":fragment:\n==== My Title")
  713       end
  714     end
  715 
  716     context 'Level 4' do
  717       test "with multiline syntax" do
  718         assert_xpath "//h5[@id='_my_section'][text() = 'My Section']", convert_string(":fragment:\nMy Section\n++++++++++")
  719       end
  720 
  721       test "with atx line syntax" do
  722         assert_xpath "//h5[@id='_my_title'][text() = 'My Title']", convert_string(":fragment:\n===== My Title")
  723       end
  724     end
  725 
  726     context 'Level 5' do
  727       test "with atx line syntax" do
  728         assert_xpath "//h6[@id='_my_title'][text() = 'My Title']", convert_string(":fragment:\n====== My Title")
  729       end
  730     end
  731   end
  732 
  733   context 'Substitutions' do
  734     test 'should apply substitutions in normal order' do
  735       input = <<~'EOS'
  736       == {link-url}[{link-text}]{tm}
  737 
  738       The one and only!
  739       EOS
  740 
  741       output = convert_string_to_embedded input, attributes: {
  742         'link-url' => 'https://acme.com',
  743         'link-text' => 'ACME',
  744         'tm' => '(TM)',
  745       }
  746       assert_css 'h2', output, 1
  747       assert_css 'h2 a[href="https://acme.com"]', output, 1
  748       assert_xpath %(//h2[contains(text(),"#{decode_char 8482}")]), output, 1
  749     end
  750   end
  751 
  752   context 'Nesting' do
  753     test 'should warn if section title is out of sequence' do
  754       input = <<~'EOS'
  755       = Document Title
  756 
  757       == Section A
  758 
  759       ==== Nested Section
  760 
  761       content
  762 
  763       == Section B
  764 
  765       content
  766       EOS
  767 
  768       using_memory_logger do |logger|
  769         result = convert_string_to_embedded input
  770         assert_xpath '//h4[text()="Nested Section"]', result, 1
  771         assert_message logger, :WARN, '<stdin>: line 5: section title out of sequence: expected level 2, got level 3', Hash
  772       end
  773     end
  774 
  775     test 'should warn if chapter title is out of sequence' do
  776       input = <<~'EOS'
  777       = Document Title
  778       :doctype: book
  779 
  780       === Not a Chapter
  781 
  782       content
  783       EOS
  784 
  785       using_memory_logger do |logger|
  786         result = convert_string_to_embedded input
  787         assert_xpath '//h3[text()="Not a Chapter"]', result, 1
  788         assert_message logger, :WARN, '<stdin>: line 4: section title out of sequence: expected levels 0 or 1, got level 2', Hash
  789       end
  790     end
  791 
  792     test 'should not warn if top-level section title is out of sequence when fragment attribute is set on document' do
  793       input = <<~'EOS'
  794       = Document Title
  795 
  796       === First Section
  797 
  798       content
  799       EOS
  800 
  801       using_memory_logger do |logger|
  802         convert_string_to_embedded input, attributes: { 'fragment' => '' }
  803         assert logger.empty?
  804       end
  805     end
  806 
  807     test 'should warn if nested section title is out of sequence when fragment attribute is set on document' do
  808       input = <<~'EOS'
  809       = Document Title
  810 
  811       === First Section
  812 
  813       ===== Nested Section
  814       EOS
  815 
  816       using_memory_logger do |logger|
  817         convert_string_to_embedded input, attributes: { 'fragment' => '' }
  818         assert_message logger, :WARN, '<stdin>: line 5: section title out of sequence: expected level 3, got level 4', Hash
  819       end
  820     end
  821     test 'should log error if subsections are found in special sections in article that do not support subsections' do
  822       input = <<~'EOS'
  823       = Document Title
  824 
  825       == Section
  826 
  827       === Subsection of Section
  828 
  829       allowed
  830 
  831       [appendix]
  832       == Appendix
  833 
  834       === Subsection of Appendix
  835 
  836       allowed
  837 
  838       [glossary]
  839       == Glossary
  840 
  841       === Subsection of Glossary
  842 
  843       not allowed
  844 
  845       [bibliography]
  846       == Bibliography
  847 
  848       === Subsection of Bibliography
  849 
  850       not allowed
  851       EOS
  852 
  853       using_memory_logger do |logger|
  854         convert_string_to_embedded input
  855         assert_messages logger, [
  856           [:ERROR, '<stdin>: line 19: glossary sections do not support nested sections', Hash],
  857           [:ERROR, '<stdin>: line 26: bibliography sections do not support nested sections', Hash],
  858         ]
  859       end
  860     end
  861 
  862     test 'should log error if subsections are found in special sections in book that do not support subsections' do
  863       input = <<~'EOS'
  864       = Document Title
  865       :doctype: book
  866 
  867       [preface]
  868       = Preface
  869 
  870       === Subsection of Preface
  871 
  872       allowed
  873 
  874       [colophon]
  875       = Colophon
  876 
  877       === Subsection of Colophon
  878 
  879       not allowed
  880 
  881       [dedication]
  882       = Dedication
  883 
  884       === Subsection of Dedication
  885 
  886       not allowed
  887 
  888       = Part 1
  889 
  890       [abstract]
  891       == Abstract
  892 
  893       === Subsection of Abstract
  894 
  895       allowed
  896 
  897       == Chapter 1
  898 
  899       === Subsection of Chapter
  900 
  901       allowed
  902 
  903       [appendix]
  904       = Appendix
  905 
  906       === Subsection of Appendix
  907 
  908       allowed
  909 
  910       [glossary]
  911       = Glossary
  912 
  913       === Subsection of Glossary
  914 
  915       not allowed
  916 
  917       [bibliography]
  918       = Bibliography
  919 
  920       === Subsection of Bibliography
  921 
  922       not allowed
  923       EOS
  924 
  925       using_memory_logger do |logger|
  926         convert_string_to_embedded input
  927         assert_messages logger, [
  928           [:ERROR, '<stdin>: line 14: colophon sections do not support nested sections', Hash],
  929           [:ERROR, '<stdin>: line 21: dedication sections do not support nested sections', Hash],
  930           [:ERROR, '<stdin>: line 50: glossary sections do not support nested sections', Hash],
  931           [:ERROR, '<stdin>: line 57: bibliography sections do not support nested sections', Hash]
  932         ]
  933       end
  934     end
  935   end
  936 
  937   context 'Markdown-style headings' do
  938     test 'atx document title with leading marker' do
  939       input = '# Document Title'
  940       output = convert_string input
  941       assert_xpath "//h1[not(@id)][text() = 'Document Title']", output, 1
  942     end
  943 
  944     test 'atx document title with symmetric markers' do
  945       input = '# Document Title #'
  946       output = convert_string input
  947       assert_xpath "//h1[not(@id)][text() = 'Document Title']", output, 1
  948     end
  949 
  950     test 'atx section title with leading marker' do
  951       input = <<~'EOS'
  952       ## Section One
  953 
  954       blah blah
  955       EOS
  956       output = convert_string input
  957       assert_xpath "//h2[@id='_section_one'][text() = 'Section One']", output, 1
  958     end
  959 
  960     test 'atx section title with symmetric markers' do
  961       input = <<~'EOS'
  962       ## Section One ##
  963 
  964       blah blah
  965       EOS
  966       output = convert_string input
  967       assert_xpath "//h2[@id='_section_one'][text() = 'Section One']", output, 1
  968     end
  969 
  970     test 'should not match atx syntax with mixed markers' do
  971       input = '=#= My Title'
  972       output = convert_string_to_embedded input
  973       assert_xpath "//h3[@id='_my_title'][text() = 'My Title']", output, 0
  974       assert_includes output, '<p>=#= My Title</p>'
  975     end
  976   end
  977 
  978   context 'Discrete Heading' do
  979     test 'should create discrete heading instead of section if style is float' do
  980       input = <<~'EOS'
  981       [float]
  982       = Independent Heading!
  983 
  984       not in section
  985       EOS
  986 
  987       output = convert_string_to_embedded input
  988       assert_xpath '/h1[@id="_independent_heading"]', output, 1
  989       assert_xpath '/h1[@class="float"]', output, 1
  990       assert_xpath %(/h1[@class="float"][text()="Independent Heading!"]), output, 1
  991       assert_xpath '/h1/following-sibling::*[@class="paragraph"]', output, 1
  992       assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p', output, 1
  993       assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p[text()="not in section"]', output, 1
  994     end
  995 
  996     test 'should create discrete heading instead of section if style is discrete' do
  997       input = <<~'EOS'
  998       [discrete]
  999       === Independent Heading!
 1000 
 1001       not in section
 1002       EOS
 1003 
 1004       output = convert_string_to_embedded input
 1005       assert_xpath '/h3', output, 1
 1006       assert_xpath '/h3[@id="_independent_heading"]', output, 1
 1007       assert_xpath '/h3[@class="discrete"]', output, 1
 1008       assert_xpath %(/h3[@class="discrete"][text()="Independent Heading!"]), output, 1
 1009       assert_xpath '/h3/following-sibling::*[@class="paragraph"]', output, 1
 1010       assert_xpath '/h3/following-sibling::*[@class="paragraph"]/p', output, 1
 1011       assert_xpath '/h3/following-sibling::*[@class="paragraph"]/p[text()="not in section"]', output, 1
 1012     end
 1013 
 1014     test 'should generate id for discrete heading from converted title' do
 1015       input = <<~'EOS'
 1016       [discrete]
 1017       === {sp}Heading{sp}
 1018 
 1019       not in section
 1020       EOS
 1021 
 1022       output = convert_string_to_embedded input
 1023       assert_xpath '/h3', output, 1
 1024       assert_xpath '/h3[@class="discrete"][@id="_heading"]', output, 1
 1025       assert_xpath '/h3[@class="discrete"][@id="_heading"][text()=" Heading "]', output, 1
 1026     end
 1027 
 1028     test 'should create discrete heading if style is float with shorthand role and id' do
 1029       input = <<~'EOS'
 1030       [float.independent#first]
 1031       = Independent Heading!
 1032 
 1033       not in section
 1034       EOS
 1035 
 1036       output = convert_string_to_embedded input
 1037       assert_xpath '/h1[@id="first"]', output, 1
 1038       assert_xpath '/h1[@class="float independent"]', output, 1
 1039       assert_xpath %(/h1[@class="float independent"][text()="Independent Heading!"]), output, 1
 1040       assert_xpath '/h1/following-sibling::*[@class="paragraph"]', output, 1
 1041       assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p', output, 1
 1042       assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p[text()="not in section"]', output, 1
 1043     end
 1044 
 1045     test 'should create discrete heading if style is discrete with shorthand role and id' do
 1046       input = <<~'EOS'
 1047       [discrete.independent#first]
 1048       = Independent Heading!
 1049 
 1050       not in section
 1051       EOS
 1052 
 1053       output = convert_string_to_embedded input
 1054       assert_xpath '/h1[@id="first"]', output, 1
 1055       assert_xpath '/h1[@class="discrete independent"]', output, 1
 1056       assert_xpath %(/h1[@class="discrete independent"][text()="Independent Heading!"]), output, 1
 1057       assert_xpath '/h1/following-sibling::*[@class="paragraph"]', output, 1
 1058       assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p', output, 1
 1059       assert_xpath '/h1/following-sibling::*[@class="paragraph"]/p[text()="not in section"]', output, 1
 1060     end
 1061 
 1062     test 'discrete heading should be a block with context floating_title' do
 1063       input = <<~'EOS'
 1064       [float]
 1065       === Independent Heading!
 1066 
 1067       not in section
 1068       EOS
 1069 
 1070       doc = document_from_string input
 1071       heading = doc.blocks.first
 1072       assert_kind_of Asciidoctor::Block, heading
 1073       assert_equal :floating_title, heading.context
 1074       assert_equal '_independent_heading', heading.id
 1075       assert doc.catalog[:refs].key? '_independent_heading'
 1076     end
 1077 
 1078     test 'should preprocess second line of setext discrete heading' do
 1079       input = <<~'EOS'
 1080       [discrete]
 1081       Heading Title
 1082       ifdef::asciidoctor[]
 1083       -------------
 1084       endif::[]
 1085       EOS
 1086       result = convert_string_to_embedded input
 1087       assert_xpath '//h2', result, 1
 1088     end
 1089 
 1090     test 'can assign explicit id to discrete heading' do
 1091       input = <<~'EOS'
 1092       [[unchained]]
 1093       [float]
 1094       === Independent Heading!
 1095 
 1096       not in section
 1097       EOS
 1098 
 1099       doc = document_from_string input
 1100       heading = doc.blocks.first
 1101       assert_equal 'unchained', heading.id
 1102       assert doc.catalog[:refs].key? 'unchained'
 1103     end
 1104 
 1105     test 'should not include discrete heading in toc' do
 1106       input = <<~'EOS'
 1107       :toc:
 1108 
 1109       == Section One
 1110 
 1111       [float]
 1112       === Miss Independent
 1113 
 1114       == Section Two
 1115       EOS
 1116 
 1117       output = convert_string input
 1118       assert_xpath '//*[@id="toc"]', output, 1
 1119       assert_xpath %(//*[@id="toc"]//a[contains(text(), "Section ")]), output, 2
 1120       assert_xpath %(//*[@id="toc"]//a[text()="Miss Independent"]), output, 0
 1121     end
 1122 
 1123     test 'should not set id on discrete heading if sectids attribute is unset' do
 1124       input = <<~'EOS'
 1125       [float]
 1126       === Independent Heading!
 1127 
 1128       not in section
 1129       EOS
 1130 
 1131       output = convert_string_to_embedded input, attributes: { 'sectids' => nil }
 1132       assert_xpath '/h3', output, 1
 1133       assert_xpath '/h3[@id="_independent_heading"]', output, 0
 1134       assert_xpath '/h3[@class="float"]', output, 1
 1135     end
 1136 
 1137     test 'should use explicit id for discrete heading if specified' do
 1138       input = <<~'EOS'
 1139       [[free]]
 1140       [float]
 1141       == Independent Heading!
 1142 
 1143       not in section
 1144       EOS
 1145 
 1146       output = convert_string_to_embedded input
 1147       assert_xpath '/h2', output, 1
 1148       assert_xpath '/h2[@id="free"]', output, 1
 1149       assert_xpath '/h2[@class="float"]', output, 1
 1150     end
 1151 
 1152     test 'should add role to class attribute on discrete heading' do
 1153       input = <<~'EOS'
 1154       [float, role="isolated"]
 1155       == Independent Heading!
 1156 
 1157       not in section
 1158       EOS
 1159 
 1160       output = convert_string_to_embedded input
 1161       assert_xpath '/h2', output, 1
 1162       assert_xpath '/h2[@id="_independent_heading"]', output, 1
 1163       assert_xpath '/h2[@class="float isolated"]', output, 1
 1164     end
 1165 
 1166     test 'should ignore title attribute on discrete heading' do
 1167       input = <<~'EOS'
 1168       [discrete,title="Captured!"]
 1169       == Independent Heading!
 1170 
 1171       not in section
 1172       EOS
 1173 
 1174       doc = document_from_string input
 1175       heading = doc.blocks[0]
 1176       assert_equal 'Independent Heading!', heading.title
 1177       refute heading.attributes.key? 'title'
 1178     end
 1179 
 1180     test 'should use specified id and reftext when registering discrete section reference' do
 1181       input = <<~'EOS'
 1182       [[install,Install Procedure]]
 1183       [discrete]
 1184       == Install
 1185 
 1186       content
 1187       EOS
 1188 
 1189       doc = document_from_string input
 1190       ref = doc.catalog[:refs]['install']
 1191       refute_nil ref
 1192       assert_equal 'Install Procedure', ref.reftext
 1193       assert_equal 'install', (doc.resolve_id 'Install Procedure')
 1194     end
 1195 
 1196     test 'should use specified reftext when registering discrete section reference' do
 1197       input = <<~'EOS'
 1198       [reftext="Install Procedure"]
 1199       [discrete]
 1200       == Install
 1201 
 1202       content
 1203       EOS
 1204 
 1205       doc = document_from_string input
 1206       ref = doc.catalog[:refs]['_install']
 1207       refute_nil ref
 1208       assert_equal 'Install Procedure', ref.reftext
 1209       assert_equal '_install', (doc.resolve_id 'Install Procedure')
 1210     end
 1211 
 1212     test 'should not process inline anchor in discrete heading if explicit ID is assigned' do
 1213       input = <<~'EOS'
 1214       [discrete#install]
 1215       == Install [[installation]]
 1216 
 1217       content
 1218       EOS
 1219 
 1220       block = block_from_string input
 1221       assert_equal block.id, 'install'
 1222       assert_equal 'Install <a id="installation"></a>', block.title
 1223     end
 1224   end
 1225 
 1226   context 'Level offset' do
 1227     test 'should print error if standalone document is included without level offset' do
 1228       input = <<~'EOS'
 1229       = Master Document
 1230       Doc Writer
 1231 
 1232       text in master
 1233 
 1234       // begin simulated include::[]
 1235       = Standalone Document
 1236       :author: Junior Writer
 1237 
 1238       text in standalone
 1239 
 1240       // end simulated include::[]
 1241       EOS
 1242 
 1243       using_memory_logger do |logger|
 1244         convert_string input
 1245         assert_message logger, :ERROR, '<stdin>: line 7: level 0 sections can only be used when doctype is book', Hash
 1246       end
 1247     end
 1248 
 1249     test 'should add level offset to section level' do
 1250       input = <<~'EOS'
 1251       = Master Document
 1252       Doc Writer
 1253 
 1254       Master document written by {author}.
 1255 
 1256       :leveloffset: 1
 1257 
 1258       // begin simulated include::[]
 1259       = Standalone Document
 1260       :author: Junior Writer
 1261 
 1262       Standalone document written by {author}.
 1263 
 1264       == Section in Standalone
 1265 
 1266       Standalone section text.
 1267       // end simulated include::[]
 1268 
 1269       :leveloffset!:
 1270 
 1271       == Section in Master
 1272 
 1273       Master section text.
 1274       EOS
 1275 
 1276       output = nil
 1277       using_memory_logger do |logger|
 1278         output = convert_string input
 1279         assert logger.empty?
 1280       end
 1281 
 1282       assert_match(/Master document written by Doc Writer/, output)
 1283       assert_match(/Standalone document written by Junior Writer/, output)
 1284       assert_xpath '//*[@class="sect1"]/h2[text() = "Standalone Document"]', output, 1
 1285       assert_xpath '//*[@class="sect2"]/h3[text() = "Section in Standalone"]', output, 1
 1286       assert_xpath '//*[@class="sect1"]/h2[text() = "Section in Master"]', output, 1
 1287     end
 1288 
 1289     test 'level offset should be added to discrete heading' do
 1290       input = <<~'EOS'
 1291       = Master Document
 1292       Doc Writer
 1293 
 1294       :leveloffset: 1
 1295 
 1296       [float]
 1297       = Discrete Heading
 1298       EOS
 1299 
 1300       output = convert_string input
 1301       assert_xpath '//h2[@class="float"][text() = "Discrete Heading"]', output, 1
 1302     end
 1303 
 1304     test 'should be able to reset level offset' do
 1305       input = <<~'EOS'
 1306       = Master Document
 1307       Doc Writer
 1308 
 1309       Master preamble.
 1310 
 1311       :leveloffset: 1
 1312 
 1313       = Standalone Document
 1314 
 1315       Standalone preamble.
 1316 
 1317       :leveloffset!:
 1318 
 1319       == Level 1 Section
 1320       EOS
 1321 
 1322       output = convert_string input
 1323       assert_xpath '//*[@class = "sect1"]/h2[text() = "Standalone Document"]', output, 1
 1324       assert_xpath '//*[@class = "sect1"]/h2[text() = "Level 1 Section"]', output, 1
 1325     end
 1326 
 1327     test 'should add relative offset value to current leveloffset' do
 1328       input = <<~'EOS'
 1329       = Master Document
 1330       Doc Writer
 1331 
 1332       Master preamble.
 1333 
 1334       :leveloffset: 1
 1335 
 1336       = Chapter 1
 1337 
 1338       content
 1339 
 1340       :leveloffset: +1
 1341 
 1342       = Standalone Section
 1343 
 1344       content
 1345       EOS
 1346 
 1347       output = convert_string input
 1348       assert_xpath '//*[@class = "sect1"]/h2[text() = "Chapter 1"]', output, 1
 1349       assert_xpath '//*[@class = "sect2"]/h3[text() = "Standalone Section"]', output, 1
 1350     end
 1351   end
 1352 
 1353   context 'Section Numbering' do
 1354     test 'should create section number with one entry for level 1' do
 1355       doc = empty_document
 1356       sect1 = Asciidoctor::Section.new nil, nil, true
 1357       doc << sect1
 1358       assert_equal '1.', sect1.sectnum
 1359     end
 1360 
 1361     test 'should create section number with two entries for level 2' do
 1362       doc = empty_document
 1363       sect1 = Asciidoctor::Section.new nil, nil, true
 1364       doc << sect1
 1365       sect1_1 = Asciidoctor::Section.new sect1, nil, true
 1366       sect1 << sect1_1
 1367       assert_equal '1.1.', sect1_1.sectnum
 1368     end
 1369 
 1370     test 'should create section number with three entries for level 3' do
 1371       doc = empty_document
 1372       sect1 = Asciidoctor::Section.new nil, nil, true
 1373       doc << sect1
 1374       sect1_1 = Asciidoctor::Section.new sect1, nil, true
 1375       sect1 << sect1_1
 1376       sect1_1_1 = Asciidoctor::Section.new sect1_1, nil, true
 1377       sect1_1 << sect1_1_1
 1378       assert_equal '1.1.1.', sect1_1_1.sectnum
 1379     end
 1380 
 1381     test 'should create section number for second section in level' do
 1382       doc = empty_document
 1383       sect1 = Asciidoctor::Section.new nil, nil, true
 1384       doc << sect1
 1385       sect1_1 = Asciidoctor::Section.new sect1, nil, true
 1386       sect1 << sect1_1
 1387       sect1_2 = Asciidoctor::Section.new sect1, nil, true
 1388       sect1 << sect1_2
 1389       assert_equal '1.2.', sect1_2.sectnum
 1390     end
 1391 
 1392     test 'sectnum should use specified delimiter and append string' do
 1393       doc = empty_document
 1394       sect1 = Asciidoctor::Section.new nil, nil, true
 1395       doc << sect1
 1396       sect1_1 = Asciidoctor::Section.new sect1, nil, true
 1397       sect1 << sect1_1
 1398       sect1_1_1 = Asciidoctor::Section.new sect1_1, nil, true
 1399       sect1_1 << sect1_1_1
 1400       assert_equal '1,1,1,', sect1_1_1.sectnum(',')
 1401       assert_equal '1:1:1', sect1_1_1.sectnum(':', false)
 1402     end
 1403 
 1404     test 'should output section numbers when sectnums attribute is set' do
 1405       input = <<~'EOS'
 1406       = Title
 1407       :sectnums:
 1408 
 1409       == Section_1
 1410 
 1411       text
 1412 
 1413       === Section_1_1
 1414 
 1415       text
 1416 
 1417       ==== Section_1_1_1
 1418 
 1419       text
 1420 
 1421       == Section_2
 1422 
 1423       text
 1424 
 1425       === Section_2_1
 1426 
 1427       text
 1428 
 1429       === Section_2_2
 1430 
 1431       text
 1432       EOS
 1433 
 1434       output = convert_string input
 1435       assert_xpath '//h2[@id="_section_1"][starts-with(text(), "1. ")]', output, 1
 1436       assert_xpath '//h3[@id="_section_1_1"][starts-with(text(), "1.1. ")]', output, 1
 1437       assert_xpath '//h4[@id="_section_1_1_1"][starts-with(text(), "1.1.1. ")]', output, 1
 1438       assert_xpath '//h2[@id="_section_2"][starts-with(text(), "2. ")]', output, 1
 1439       assert_xpath '//h3[@id="_section_2_1"][starts-with(text(), "2.1. ")]', output, 1
 1440       assert_xpath '//h3[@id="_section_2_2"][starts-with(text(), "2.2. ")]', output, 1
 1441     end
 1442 
 1443     test 'should output section numbers when numbered attribute is set' do
 1444       input = <<~'EOS'
 1445       = Title
 1446       :numbered:
 1447 
 1448       == Section_1
 1449 
 1450       text
 1451 
 1452       === Section_1_1
 1453 
 1454       text
 1455 
 1456       ==== Section_1_1_1
 1457 
 1458       text
 1459 
 1460       == Section_2
 1461 
 1462       text
 1463 
 1464       === Section_2_1
 1465 
 1466       text
 1467 
 1468       === Section_2_2
 1469 
 1470       text
 1471       EOS
 1472 
 1473       output = convert_string input
 1474       assert_xpath '//h2[@id="_section_1"][starts-with(text(), "1. ")]', output, 1
 1475       assert_xpath '//h3[@id="_section_1_1"][starts-with(text(), "1.1. ")]', output, 1
 1476       assert_xpath '//h4[@id="_section_1_1_1"][starts-with(text(), "1.1.1. ")]', output, 1
 1477       assert_xpath '//h2[@id="_section_2"][starts-with(text(), "2. ")]', output, 1
 1478       assert_xpath '//h3[@id="_section_2_1"][starts-with(text(), "2.1. ")]', output, 1
 1479       assert_xpath '//h3[@id="_section_2_2"][starts-with(text(), "2.2. ")]', output, 1
 1480     end
 1481 
 1482     test 'should not crash if child section of part is out of sequence and part numbering is disabled' do
 1483       input = <<~'EOS'
 1484       = Document Title
 1485       :doctype: book
 1486       :sectnums:
 1487 
 1488       = Part
 1489 
 1490       === Out of Sequence Section
 1491       EOS
 1492 
 1493       using_memory_logger do |logger|
 1494         output = convert_string input
 1495         assert_xpath '//h1[text()="Part"]', output, 1
 1496         assert_xpath '//h3[text()=".1. Out of Sequence Section"]', output, 1
 1497       end
 1498     end
 1499 
 1500     test 'should not hang if relative leveloffset attempts to make resolved section level negative' do
 1501       input = <<~'EOS'
 1502       = Document Title
 1503       :doctype: book
 1504       :leveloffset: -1
 1505 
 1506       = Part Title
 1507 
 1508       == Chapter Title
 1509       EOS
 1510 
 1511       using_memory_logger do |logger|
 1512         output = convert_string input
 1513         assert_xpath '//h1[text()="Part Title"]', output, 1
 1514         assert_xpath '//h1[text()="Chapter Title"]', output, 1
 1515       end
 1516     end
 1517 
 1518     test 'should number parts when doctype is book and partnums attributes is set' do
 1519       input = <<~'EOS'
 1520       = Book Title
 1521       :doctype: book
 1522       :sectnums:
 1523       :partnums:
 1524 
 1525       = Language
 1526 
 1527       == Syntax
 1528 
 1529       content
 1530 
 1531       = Processor
 1532 
 1533       == CLI
 1534 
 1535       content
 1536       EOS
 1537 
 1538       output = convert_string input
 1539       assert_xpath '//h1[@id="_language"][text() = "I: Language"]', output, 1
 1540       assert_xpath '//h1[@id="_processor"][text() = "II: Processor"]', output, 1
 1541     end
 1542 
 1543     test 'should assign sequential roman numerals to book parts' do
 1544       input = <<~'EOS'
 1545       = Book Title
 1546       :doctype: book
 1547       :sectnums:
 1548       :partnums:
 1549 
 1550       = First Part
 1551 
 1552       part intro
 1553 
 1554       == First Chapter
 1555 
 1556       = Second Part
 1557 
 1558       part intro
 1559 
 1560       == Second Chapter
 1561       EOS
 1562 
 1563       doc = document_from_string input
 1564       assert_equal 'I', doc.sections[0].numeral
 1565       assert_equal '1', doc.sections[0].sections[0].numeral
 1566       assert_equal 'II', doc.sections[1].numeral
 1567       assert_equal '2', doc.sections[1].sections[0].numeral
 1568     end
 1569 
 1570     test 'should prepend value of part-signifier attribute to title of numbered part' do
 1571       input = <<~'EOS'
 1572       = Book Title
 1573       :doctype: book
 1574       :sectnums:
 1575       :partnums:
 1576       :part-signifier: Part
 1577 
 1578       = Language
 1579 
 1580       == Syntax
 1581 
 1582       content
 1583 
 1584       = Processor
 1585 
 1586       == CLI
 1587 
 1588       content
 1589       EOS
 1590 
 1591       output = convert_string input
 1592       assert_xpath '//h1[@id="_language"][text() = "Part I: Language"]', output, 1
 1593       assert_xpath '//h1[@id="_processor"][text() = "Part II: Processor"]', output, 1
 1594     end
 1595 
 1596     test 'should prepend value of chapter-signifier attribute to title of numbered chapter' do
 1597       input = <<~'EOS'
 1598       = Book Title
 1599       :doctype: book
 1600       :sectnums:
 1601       :partnums:
 1602       :chapter-signifier: Chapter
 1603 
 1604       = Language
 1605 
 1606       == Syntax
 1607 
 1608       content
 1609 
 1610       = Processor
 1611 
 1612       == CLI
 1613 
 1614       content
 1615       EOS
 1616 
 1617       output = convert_string input
 1618       assert_xpath '//h2[@id="_syntax"][text() = "Chapter 1. Syntax"]', output, 1
 1619       assert_xpath '//h2[@id="_cli"][text() = "Chapter 2. CLI"]', output, 1
 1620     end
 1621 
 1622     test 'blocks should have level' do
 1623       input = <<~'EOS'
 1624       = Title
 1625 
 1626       preamble
 1627 
 1628       == Section 1
 1629 
 1630       paragraph
 1631 
 1632       === Section 1.1
 1633 
 1634       paragraph
 1635       EOS
 1636       doc = document_from_string input
 1637       assert_equal 0, doc.blocks[0].level
 1638       assert_equal 1, doc.blocks[1].level
 1639       assert_equal 1, doc.blocks[1].blocks[0].level
 1640       assert_equal 2, doc.blocks[1].blocks[1].level
 1641       assert_equal 2, doc.blocks[1].blocks[1].blocks[0].level
 1642     end
 1643 
 1644     test 'section numbers should not increment when numbered attribute is turned off within document' do
 1645       input = <<~'EOS'
 1646       = Document Title
 1647       :numbered:
 1648 
 1649       :numbered!:
 1650 
 1651       == Colophon Section
 1652 
 1653       == Another Colophon Section
 1654 
 1655       == Final Colophon Section
 1656 
 1657       :numbered:
 1658 
 1659       == Section One
 1660 
 1661       === Section One Subsection
 1662 
 1663       == Section Two
 1664 
 1665       == Section Three
 1666       EOS
 1667 
 1668       output = convert_string input
 1669       assert_xpath '//h1[text()="Document Title"]', output, 1
 1670       assert_xpath '//h2[@id="_colophon_section"][text()="Colophon Section"]', output, 1
 1671       assert_xpath '//h2[@id="_another_colophon_section"][text()="Another Colophon Section"]', output, 1
 1672       assert_xpath '//h2[@id="_final_colophon_section"][text()="Final Colophon Section"]', output, 1
 1673       assert_xpath '//h2[@id="_section_one"][text()="1. Section One"]', output, 1
 1674       assert_xpath '//h3[@id="_section_one_subsection"][text()="1.1. Section One Subsection"]', output, 1
 1675       assert_xpath '//h2[@id="_section_two"][text()="2. Section Two"]', output, 1
 1676       assert_xpath '//h2[@id="_section_three"][text()="3. Section Three"]', output, 1
 1677     end
 1678 
 1679     test 'section numbers can be toggled even if numbered attribute is enable via the API' do
 1680       input = <<~'EOS'
 1681       = Document Title
 1682 
 1683       :numbered!:
 1684 
 1685       == Colophon Section
 1686 
 1687       == Another Colophon Section
 1688 
 1689       == Final Colophon Section
 1690 
 1691       :numbered:
 1692 
 1693       == Section One
 1694 
 1695       === Section One Subsection
 1696 
 1697       == Section Two
 1698 
 1699       == Section Three
 1700       EOS
 1701 
 1702       output = convert_string input, attributes: { 'numbered' => '' }
 1703       assert_xpath '//h1[text()="Document Title"]', output, 1
 1704       assert_xpath '//h2[@id="_colophon_section"][text()="Colophon Section"]', output, 1
 1705       assert_xpath '//h2[@id="_another_colophon_section"][text()="Another Colophon Section"]', output, 1
 1706       assert_xpath '//h2[@id="_final_colophon_section"][text()="Final Colophon Section"]', output, 1
 1707       assert_xpath '//h2[@id="_section_one"][text()="1. Section One"]', output, 1
 1708       assert_xpath '//h3[@id="_section_one_subsection"][text()="1.1. Section One Subsection"]', output, 1
 1709       assert_xpath '//h2[@id="_section_two"][text()="2. Section Two"]', output, 1
 1710       assert_xpath '//h2[@id="_section_three"][text()="3. Section Three"]', output, 1
 1711     end
 1712 
 1713     test 'section numbers cannot be toggled even if numbered attribute is disabled via the API' do
 1714       input = <<~'EOS'
 1715       = Document Title
 1716 
 1717       :numbered!:
 1718 
 1719       == Colophon Section
 1720 
 1721       == Another Colophon Section
 1722 
 1723       == Final Colophon Section
 1724 
 1725       :numbered:
 1726 
 1727       == Section One
 1728 
 1729       === Section One Subsection
 1730 
 1731       == Section Two
 1732 
 1733       == Section Three
 1734       EOS
 1735 
 1736       output = convert_string input, attributes: { 'numbered!' => '' }
 1737       assert_xpath '//h1[text()="Document Title"]', output, 1
 1738       assert_xpath '//h2[@id="_colophon_section"][text()="Colophon Section"]', output, 1
 1739       assert_xpath '//h2[@id="_another_colophon_section"][text()="Another Colophon Section"]', output, 1
 1740       assert_xpath '//h2[@id="_final_colophon_section"][text()="Final Colophon Section"]', output, 1
 1741       assert_xpath '//h2[@id="_section_one"][text()="Section One"]', output, 1
 1742       assert_xpath '//h3[@id="_section_one_subsection"][text()="Section One Subsection"]', output, 1
 1743       assert_xpath '//h2[@id="_section_two"][text()="Section Two"]', output, 1
 1744       assert_xpath '//h2[@id="_section_three"][text()="Section Three"]', output, 1
 1745     end
 1746 
 1747     # NOTE AsciiDoc Python fails this test because it does not properly check for a None value when looking up the numbered attribute
 1748     test 'section numbers should not increment until numbered attribute is turned back on' do
 1749       input = <<~'EOS'
 1750       = Document Title
 1751       :numbered!:
 1752 
 1753       == Colophon Section
 1754 
 1755       == Another Colophon Section
 1756 
 1757       == Final Colophon Section
 1758 
 1759       :numbered:
 1760 
 1761       == Section One
 1762 
 1763       === Section One Subsection
 1764 
 1765       == Section Two
 1766 
 1767       == Section Three
 1768       EOS
 1769 
 1770       output = convert_string input
 1771       assert_xpath '//h1[text()="Document Title"]', output, 1
 1772       assert_xpath '//h2[@id="_colophon_section"][text()="Colophon Section"]', output, 1
 1773       assert_xpath '//h2[@id="_another_colophon_section"][text()="Another Colophon Section"]', output, 1
 1774       assert_xpath '//h2[@id="_final_colophon_section"][text()="Final Colophon Section"]', output, 1
 1775       assert_xpath '//h2[@id="_section_one"][text()="1. Section One"]', output, 1
 1776       assert_xpath '//h3[@id="_section_one_subsection"][text()="1.1. Section One Subsection"]', output, 1
 1777       assert_xpath '//h2[@id="_section_two"][text()="2. Section Two"]', output, 1
 1778       assert_xpath '//h2[@id="_section_three"][text()="3. Section Three"]', output, 1
 1779     end
 1780 
 1781     test 'table with asciidoc content should not disable numbering of subsequent sections' do
 1782       input = <<~'EOS'
 1783       = Document Title
 1784       :numbered:
 1785 
 1786       preamble
 1787 
 1788       == Section One
 1789 
 1790       |===
 1791       a|content
 1792       |===
 1793 
 1794       == Section Two
 1795 
 1796       content
 1797       EOS
 1798 
 1799       output = convert_string input
 1800       assert_xpath '//h2[@id="_section_one"]', output, 1
 1801       assert_xpath '//h2[@id="_section_one"][text()="1. Section One"]', output, 1
 1802       assert_xpath '//h2[@id="_section_two"]', output, 1
 1803       assert_xpath '//h2[@id="_section_two"][text()="2. Section Two"]', output, 1
 1804     end
 1805 
 1806     test 'should not number parts when doctype is book' do
 1807       input = <<~'EOS'
 1808       = Document Title
 1809       :doctype: book
 1810       :numbered:
 1811 
 1812       = Part 1
 1813 
 1814       == Chapter 1
 1815 
 1816       content
 1817 
 1818       = Part 2
 1819 
 1820       == Chapter 2
 1821 
 1822       content
 1823       EOS
 1824 
 1825       output = convert_string input
 1826       assert_xpath '(//h1)[1][text()="Document Title"]', output, 1
 1827       assert_xpath '(//h1)[2][text()="Part 1"]', output, 1
 1828       assert_xpath '(//h1)[3][text()="Part 2"]', output, 1
 1829       assert_xpath '(//h2)[1][text()="1. Chapter 1"]', output, 1
 1830       assert_xpath '(//h2)[2][text()="2. Chapter 2"]', output, 1
 1831     end
 1832 
 1833     test 'should number chapters sequentially even when divided into parts' do
 1834       input = <<~'EOS'
 1835       = Document Title
 1836       :doctype: book
 1837       :numbered:
 1838 
 1839       == Chapter 1
 1840 
 1841       content
 1842 
 1843       = Part 1
 1844 
 1845       == Chapter 2
 1846 
 1847       content
 1848 
 1849       = Part 2
 1850 
 1851       == Chapter 3
 1852 
 1853       content
 1854 
 1855       == Chapter 4
 1856 
 1857       content
 1858       EOS
 1859 
 1860       result = convert_string input
 1861       (1..4).each do |num|
 1862         assert_xpath %(//h2[@id="_chapter_#{num}"]), result, 1
 1863         assert_xpath %(//h2[@id="_chapter_#{num}"][text()="#{num}. Chapter #{num}"]), result, 1
 1864       end
 1865     end
 1866 
 1867     test 'reindex_sections should correct section enumeration after sections are modified' do
 1868       input = <<~'EOS'
 1869       :sectnums:
 1870 
 1871       == First Section
 1872 
 1873       content
 1874 
 1875       == Last Section
 1876 
 1877       content
 1878       EOS
 1879 
 1880       doc = document_from_string input
 1881       second_section = Asciidoctor::Section.new doc, nil, true
 1882       doc.blocks.insert 1, second_section
 1883       doc.reindex_sections
 1884       sections = doc.sections
 1885       [0, 1, 2].each do |index|
 1886         assert_equal index, sections[index].index
 1887         assert_equal (index + 1).to_s, sections[index].numeral
 1888         assert_equal index + 1, sections[index].number
 1889       end
 1890     end
 1891 
 1892     test 'should allow sections to be renumbered using numberal property' do
 1893       input = <<~'EOS'
 1894       == Somewhere in the Middle
 1895 
 1896       == The End
 1897       EOS
 1898 
 1899       doc = document_from_string input, attributes: { 'sectnums' => '' }
 1900       doc.sections.each do |sect|
 1901         sect.numeral = sect.numeral.next
 1902       end
 1903 
 1904       output = doc.convert standalone: false
 1905       assert_xpath '//h2[text()="2. Somewhere in the Middle"]', output, 1
 1906       assert_xpath '//h2[text()="3. The End"]', output, 1
 1907     end
 1908   end
 1909 
 1910   context 'Links and anchors' do
 1911     test 'should include anchor if sectanchors document attribute is set' do
 1912       input = <<~'EOS'
 1913       == Installation
 1914 
 1915       Installation section.
 1916 
 1917       === Linux
 1918 
 1919       Linux installation instructions.
 1920       EOS
 1921 
 1922       output = convert_string_to_embedded input, attributes: { 'sectanchors' => '' }
 1923       assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a', output, 1
 1924       assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a[@class="anchor"][@href="#_installation"]', output, 1
 1925       assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a/following-sibling::text()="Installation"', output, true
 1926       assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a', output, 1
 1927       assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a[@class="anchor"][@href="#_linux"]', output, 1
 1928       assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a/following-sibling::text()="Linux"', output, true
 1929     end
 1930 
 1931     test 'should position after title text if sectanchors is set to after' do
 1932       input = <<~'EOS'
 1933       == Installation
 1934 
 1935       Installation section.
 1936 
 1937       === Linux
 1938 
 1939       Linux installation instructions.
 1940       EOS
 1941 
 1942       output = convert_string_to_embedded input, attributes: { 'sectanchors' => 'after' }
 1943       assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a', output, 1
 1944       assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a[@class="anchor"][@href="#_installation"]', output, 1
 1945       assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a/preceding-sibling::text()="Installation"', output, true
 1946       assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a', output, 1
 1947       assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a[@class="anchor"][@href="#_linux"]', output, 1
 1948       assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a/preceding-sibling::text()="Linux"', output, true
 1949     end
 1950 
 1951     test 'should link section if sectlinks document attribute is set' do
 1952       input = <<~'EOS'
 1953       == Installation
 1954 
 1955       Installation section.
 1956 
 1957       === Linux
 1958 
 1959       Linux installation instructions.
 1960       EOS
 1961 
 1962       output = convert_string_to_embedded input, attributes: { 'sectlinks' => '' }
 1963       assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a', output, 1
 1964       assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a[@class="link"][@href="#_installation"]', output, 1
 1965       assert_xpath '/*[@class="sect1"]/h2[@id="_installation"]/a[text()="Installation"]', output, 1
 1966       assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a', output, 1
 1967       assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a[@class="link"][@href="#_linux"]', output, 1
 1968       assert_xpath '//*[@class="sect2"]/h3[@id="_linux"]/a[text()="Linux"]', output, 1
 1969     end
 1970   end
 1971 
 1972   context 'Special sections' do
 1973     test 'should ignore style if it matches sectN' do
 1974       input = <<~'EOS'
 1975       = Document Title
 1976 
 1977       [sect1]
 1978       == Section Level 1
 1979 
 1980       content
 1981 
 1982       [sect2]
 1983       == Section Level 2
 1984 
 1985       content
 1986       EOS
 1987 
 1988       output = convert_string input, backend: :docbook
 1989       assert_xpath '//section', output, 2
 1990       assert_xpath '//sect1', output, 0
 1991       assert_xpath '//sect2', output, 0
 1992     end
 1993 
 1994     test 'should assign sectname, caption, and numeral to appendix section by default' do
 1995       input = <<~'EOS'
 1996       [appendix]
 1997       == Attribute Options
 1998 
 1999       Details
 2000       EOS
 2001 
 2002       appendix = block_from_string input
 2003       assert_equal 'appendix', appendix.sectname
 2004       assert_equal 'Appendix A: ', appendix.caption
 2005       assert_equal 'A', appendix.numeral
 2006       assert_equal 'A', appendix.number
 2007       assert_equal true, appendix.numbered
 2008     end
 2009 
 2010     test 'should prefix appendix title by numbered label even when section numbering is disabled' do
 2011       input = <<~'EOS'
 2012       [appendix]
 2013       == Attribute Options
 2014 
 2015       Details
 2016       EOS
 2017 
 2018       output = convert_string_to_embedded input
 2019       assert_xpath '//h2[text()="Appendix A: Attribute Options"]', output, 1
 2020     end
 2021 
 2022     test 'should use style from last block attribute line above section that defines a style' do
 2023       input = <<~'EOS'
 2024       [glossary]
 2025       [appendix]
 2026       == Attribute Options
 2027 
 2028       Details
 2029       EOS
 2030 
 2031       output = convert_string_to_embedded input
 2032       assert_xpath '//h2[text()="Appendix A: Attribute Options"]', output, 1
 2033     end
 2034 
 2035     test 'setting ID using style shorthand should not clear section style' do
 2036       input = <<~'EOS'
 2037       [appendix]
 2038       [#attribute-options]
 2039       == Attribute Options
 2040 
 2041       Details
 2042       EOS
 2043 
 2044       output = convert_string_to_embedded input
 2045       assert_xpath '//h2[@id="attribute-options"][text()="Appendix A: Attribute Options"]', output, 1
 2046     end
 2047 
 2048     test 'should use custom appendix caption if specified' do
 2049       input = <<~'EOS'
 2050       :appendix-caption: App
 2051 
 2052       [appendix]
 2053       == Attribute Options
 2054 
 2055       Details
 2056       EOS
 2057 
 2058       output = convert_string_to_embedded input
 2059       assert_xpath '//h2[text()="App A: Attribute Options"]', output, 1
 2060     end
 2061 
 2062     test 'should only assign letter to appendix when numbered is enabled and appendix caption is not set' do
 2063       input = <<~'EOS'
 2064       :numbered:
 2065       :!appendix-caption:
 2066 
 2067       [appendix]
 2068       == Attribute Options
 2069 
 2070       Details
 2071       EOS
 2072 
 2073       output = convert_string_to_embedded input
 2074       assert_xpath '//h2[text()="A. Attribute Options"]', output, 1
 2075     end
 2076 
 2077     test 'should increment appendix number for each appendix section' do
 2078       input = <<~'EOS'
 2079       [appendix]
 2080       == Attribute Options
 2081 
 2082       Details
 2083 
 2084       [appendix]
 2085       == Migration
 2086 
 2087       Details
 2088       EOS
 2089 
 2090       output = convert_string_to_embedded input
 2091       assert_xpath '(//h2)[1][text()="Appendix A: Attribute Options"]', output, 1
 2092       assert_xpath '(//h2)[2][text()="Appendix B: Migration"]', output, 1
 2093     end
 2094 
 2095     test 'should continue numbering after appendix' do
 2096       input = <<~'EOS'
 2097       :numbered:
 2098 
 2099       == First Section
 2100 
 2101       content
 2102 
 2103       [appendix]
 2104       == Attribute Options
 2105 
 2106       content
 2107 
 2108       == Migration
 2109 
 2110       content
 2111       EOS
 2112 
 2113       output = convert_string_to_embedded input
 2114       assert_xpath '(//h2)[1][text()="1. First Section"]', output, 1
 2115       assert_xpath '(//h2)[2][text()="Appendix A: Attribute Options"]', output, 1
 2116       assert_xpath '(//h2)[3][text()="2. Migration"]', output, 1
 2117     end
 2118 
 2119     test 'should number appendix subsections using appendix letter' do
 2120       input = <<~'EOS'
 2121       :numbered:
 2122 
 2123       [appendix]
 2124       == Attribute Options
 2125 
 2126       Details
 2127 
 2128       === Optional Attributes
 2129 
 2130       Details
 2131       EOS
 2132 
 2133       output = convert_string_to_embedded input
 2134       assert_xpath '(//h2)[1][text()="Appendix A: Attribute Options"]', output, 1
 2135       assert_xpath '(//h3)[1][text()="A.1. Optional Attributes"]', output, 1
 2136     end
 2137 
 2138     test 'should not number level 4 section by default' do
 2139       input = <<~'EOS'
 2140       :numbered:
 2141 
 2142       == Level_1
 2143 
 2144       === Level_2
 2145 
 2146       ==== Level_3
 2147 
 2148       ===== Level_4
 2149 
 2150       text
 2151       EOS
 2152       output = convert_string_to_embedded input
 2153       assert_xpath '//h5', output, 1
 2154       assert_xpath '//h5[text()="Level_4"]', output, 1
 2155     end
 2156 
 2157     test 'should only number levels up to value defined by sectnumlevels attribute' do
 2158       input = <<~'EOS'
 2159       :numbered:
 2160       :sectnumlevels: 2
 2161 
 2162       == Level_1
 2163 
 2164       === Level_2
 2165 
 2166       ==== Level_3
 2167 
 2168       ===== Level_4
 2169 
 2170       text
 2171       EOS
 2172       output = convert_string_to_embedded input
 2173       assert_xpath '//h2', output, 1
 2174       assert_xpath '//h2[text()="1. Level_1"]', output, 1
 2175       assert_xpath '//h3', output, 1
 2176       assert_xpath '//h3[text()="1.1. Level_2"]', output, 1
 2177       assert_xpath '//h4', output, 1
 2178       assert_xpath '//h4[text()="Level_3"]', output, 1
 2179       assert_xpath '//h5', output, 1
 2180       assert_xpath '//h5[text()="Level_4"]', output, 1
 2181     end
 2182 
 2183     test 'should not number sections or subsections in regions where numbered is off' do
 2184       input = <<~'EOS'
 2185       :numbered:
 2186 
 2187       == Section One
 2188 
 2189       :numbered!:
 2190 
 2191       [appendix]
 2192       == Attribute Options
 2193 
 2194       Details
 2195 
 2196       [appendix]
 2197       == Migration
 2198 
 2199       Details
 2200 
 2201       === Gotchas
 2202 
 2203       Details
 2204 
 2205       [glossary]
 2206       == Glossary
 2207 
 2208       Terms
 2209       EOS
 2210 
 2211       output = convert_string_to_embedded input
 2212       assert_xpath '(//h2)[1][text()="1. Section One"]', output, 1
 2213       assert_xpath '(//h2)[2][text()="Appendix A: Attribute Options"]', output, 1
 2214       assert_xpath '(//h2)[3][text()="Appendix B: Migration"]', output, 1
 2215       assert_xpath '(//h3)[1][text()="Gotchas"]', output, 1
 2216       assert_xpath '(//h2)[4][text()="Glossary"]', output, 1
 2217     end
 2218 
 2219     test 'should not number sections or subsections in toc in regions where numbered is off' do
 2220       input = <<~'EOS'
 2221       :numbered:
 2222       :toc:
 2223 
 2224       == Section One
 2225 
 2226       :numbered!:
 2227 
 2228       [appendix]
 2229       == Attribute Options
 2230 
 2231       Details
 2232 
 2233       [appendix]
 2234       == Migration
 2235 
 2236       Details
 2237 
 2238       === Gotchas
 2239 
 2240       Details
 2241 
 2242       [glossary]
 2243       == Glossary
 2244 
 2245       Terms
 2246       EOS
 2247 
 2248       output = convert_string input
 2249       assert_xpath '//*[@id="toc"]/ul//li/a[text()="1. Section One"]', output, 1
 2250       assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix A: Attribute Options"]', output, 1
 2251       assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix B: Migration"]', output, 1
 2252       assert_xpath '//*[@id="toc"]/ul//li/a[text()="Gotchas"]', output, 1
 2253       assert_xpath '//*[@id="toc"]/ul//li/a[text()="Glossary"]', output, 1
 2254     end
 2255 
 2256     test 'should only number sections in toc up to value defined by sectnumlevels attribute' do
 2257       input = <<~'EOS'
 2258       :numbered:
 2259       :toc:
 2260       :sectnumlevels: 2
 2261       :toclevels: 3
 2262 
 2263       == Level 1
 2264 
 2265       === Level 2
 2266 
 2267       ==== Level 3
 2268       EOS
 2269 
 2270       output = convert_string input
 2271       assert_xpath '//*[@id="toc"]//a[@href="#_level_1"][text()="1. Level 1"]', output, 1
 2272       assert_xpath '//*[@id="toc"]//a[@href="#_level_2"][text()="1.1. Level 2"]', output, 1
 2273       assert_xpath '//*[@id="toc"]//a[@href="#_level_3"][text()="Level 3"]', output, 1
 2274     end
 2275 
 2276     test 'should not number special sections or their subsections by default except for appendices' do
 2277       input = <<~'EOS'
 2278       :doctype: book
 2279       :sectnums:
 2280 
 2281       [preface]
 2282       == Preface
 2283 
 2284       === Preface Subsection
 2285 
 2286       content
 2287 
 2288       == Section One
 2289 
 2290       content
 2291 
 2292       [appendix]
 2293       == Attribute Options
 2294 
 2295       Details
 2296 
 2297       [appendix]
 2298       == Migration
 2299 
 2300       Details
 2301 
 2302       === Gotchas
 2303 
 2304       Details
 2305 
 2306       [glossary]
 2307       == Glossary
 2308 
 2309       Terms
 2310       EOS
 2311 
 2312       output = convert_string_to_embedded input
 2313       assert_xpath '(//h2)[1][text()="Preface"]', output, 1
 2314       assert_xpath '(//h3)[1][text()="Preface Subsection"]', output, 1
 2315       assert_xpath '(//h2)[2][text()="1. Section One"]', output, 1
 2316       assert_xpath '(//h2)[3][text()="Appendix A: Attribute Options"]', output, 1
 2317       assert_xpath '(//h2)[4][text()="Appendix B: Migration"]', output, 1
 2318       assert_xpath '(//h3)[2][text()="B.1. Gotchas"]', output, 1
 2319       assert_xpath '(//h2)[5][text()="Glossary"]', output, 1
 2320     end
 2321 
 2322     test 'should not number special sections or their subsections in toc by default except for appendices' do
 2323       input = <<~'EOS'
 2324       :doctype: book
 2325       :sectnums:
 2326       :toc:
 2327 
 2328       [preface]
 2329       == Preface
 2330 
 2331       === Preface Subsection
 2332 
 2333       content
 2334 
 2335       == Section One
 2336 
 2337       content
 2338 
 2339       [appendix]
 2340       == Attribute Options
 2341 
 2342       Details
 2343 
 2344       [appendix]
 2345       == Migration
 2346 
 2347       Details
 2348 
 2349       === Gotchas
 2350 
 2351       Details
 2352 
 2353       [glossary]
 2354       == Glossary
 2355 
 2356       Terms
 2357       EOS
 2358 
 2359       output = convert_string input
 2360       assert_xpath '//*[@id="toc"]/ul//li/a[text()="Preface"]', output, 1
 2361       assert_xpath '//*[@id="toc"]/ul//li/a[text()="Preface Subsection"]', output, 1
 2362       assert_xpath '//*[@id="toc"]/ul//li/a[text()="1. Section One"]', output, 1
 2363       assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix A: Attribute Options"]', output, 1
 2364       assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix B: Migration"]', output, 1
 2365       assert_xpath '//*[@id="toc"]/ul//li/a[text()="B.1. Gotchas"]', output, 1
 2366       assert_xpath '//*[@id="toc"]/ul//li/a[text()="Glossary"]', output, 1
 2367     end
 2368 
 2369     test 'should number special sections and their subsections when sectnums is all' do
 2370       input = <<~'EOS'
 2371       :doctype: book
 2372       :sectnums: all
 2373 
 2374       [preface]
 2375       == Preface
 2376 
 2377       === Preface Subsection
 2378 
 2379       content
 2380 
 2381       == Section One
 2382 
 2383       content
 2384 
 2385       [appendix]
 2386       == Attribute Options
 2387 
 2388       Details
 2389 
 2390       [appendix]
 2391       == Migration
 2392 
 2393       Details
 2394 
 2395       === Gotchas
 2396 
 2397       Details
 2398 
 2399       [glossary]
 2400       == Glossary
 2401 
 2402       Terms
 2403       EOS
 2404 
 2405       output = convert_string_to_embedded input
 2406       assert_xpath '(//h2)[1][text()="1. Preface"]', output, 1
 2407       assert_xpath '(//h3)[1][text()="1.1. Preface Subsection"]', output, 1
 2408       assert_xpath '(//h2)[2][text()="2. Section One"]', output, 1
 2409       assert_xpath '(//h2)[3][text()="Appendix A: Attribute Options"]', output, 1
 2410       assert_xpath '(//h2)[4][text()="Appendix B: Migration"]', output, 1
 2411       assert_xpath '(//h3)[2][text()="B.1. Gotchas"]', output, 1
 2412       assert_xpath '(//h2)[5][text()="3. Glossary"]', output, 1
 2413     end
 2414 
 2415     test 'should number special sections and their subsections in toc when sectnums is all' do
 2416       input = <<~'EOS'
 2417       :doctype: book
 2418       :sectnums: all
 2419       :toc:
 2420 
 2421       [preface]
 2422       == Preface
 2423 
 2424       === Preface Subsection
 2425 
 2426       content
 2427 
 2428       == Section One
 2429 
 2430       content
 2431 
 2432       [appendix]
 2433       == Attribute Options
 2434 
 2435       Details
 2436 
 2437       [appendix]
 2438       == Migration
 2439 
 2440       Details
 2441 
 2442       === Gotchas
 2443 
 2444       Details
 2445 
 2446       [glossary]
 2447       == Glossary
 2448 
 2449       Terms
 2450       EOS
 2451 
 2452       output = convert_string input
 2453       assert_xpath '//*[@id="toc"]/ul//li/a[text()="1. Preface"]', output, 1
 2454       assert_xpath '//*[@id="toc"]/ul//li/a[text()="1.1. Preface Subsection"]', output, 1
 2455       assert_xpath '//*[@id="toc"]/ul//li/a[text()="2. Section One"]', output, 1
 2456       assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix A: Attribute Options"]', output, 1
 2457       assert_xpath '//*[@id="toc"]/ul//li/a[text()="Appendix B: Migration"]', output, 1
 2458       assert_xpath '//*[@id="toc"]/ul//li/a[text()="B.1. Gotchas"]', output, 1
 2459       assert_xpath '//*[@id="toc"]/ul//li/a[text()="3. Glossary"]', output, 1
 2460     end
 2461 
 2462     test 'level 0 special sections in multipart book should be coerced to level 1' do
 2463       input = <<~'EOS'
 2464       = Multipart Book
 2465       Doc Writer
 2466       :doctype: book
 2467 
 2468       [preface]
 2469       = Preface
 2470 
 2471       Preface text
 2472 
 2473       [appendix]
 2474       = Appendix
 2475 
 2476       Appendix text
 2477       EOS
 2478 
 2479       output = convert_string input
 2480       assert_xpath '//h2[@id = "_preface"]', output, 1
 2481       assert_xpath '//h2[@id = "_appendix"]', output, 1
 2482     end
 2483 
 2484     test 'should output docbook elements that correspond to special sections in book doctype' do
 2485       input = <<~'EOS'
 2486       = Multipart Book
 2487       :doctype: book
 2488       :idprefix:
 2489 
 2490       [abstract]
 2491       = Abstract Title
 2492 
 2493       Normal chapter (no abstract in book)
 2494 
 2495       [dedication]
 2496       = Dedication Title
 2497 
 2498       Dedication content
 2499 
 2500       [preface]
 2501       = Preface Title
 2502 
 2503       Preface content
 2504 
 2505       === Preface sub-section
 2506 
 2507       Preface subsection content
 2508 
 2509       = Part 1
 2510 
 2511       [partintro]
 2512       .Part intro title
 2513       Part intro content
 2514 
 2515       == Chapter 1
 2516 
 2517       blah blah
 2518 
 2519       == Chapter 2
 2520 
 2521       blah blah
 2522 
 2523       = Part 2
 2524 
 2525       [partintro]
 2526       blah blah
 2527 
 2528       == Chapter 3
 2529 
 2530       blah blah
 2531 
 2532       == Chapter 4
 2533 
 2534       blah blah
 2535 
 2536       [appendix]
 2537       = Appendix Title
 2538 
 2539       Appendix content
 2540 
 2541       === Appendix sub-section
 2542 
 2543       Appendix sub-section content
 2544 
 2545       [bibliography]
 2546       = Bibliography Title
 2547 
 2548       Bibliography content
 2549 
 2550       [glossary]
 2551       = Glossary Title
 2552 
 2553       Glossary content
 2554 
 2555       [colophon]
 2556       = Colophon Title
 2557 
 2558       Colophon content
 2559 
 2560       [index]
 2561       = Index Title
 2562       EOS
 2563 
 2564       output = convert_string input, backend: 'docbook'
 2565       assert_xpath '/book/chapter[@xml:id="abstract_title"]', output, 1
 2566       assert_xpath '/book/chapter[@xml:id="abstract_title"]/title[text()="Abstract Title"]', output, 1
 2567       assert_xpath '/book/chapter/following-sibling::dedication[@xml:id="dedication_title"]', output, 1
 2568       assert_xpath '/book/chapter/following-sibling::dedication[@xml:id="dedication_title"]/title[text()="Dedication Title"]', output, 1
 2569       assert_xpath '/book/dedication/following-sibling::preface[@xml:id="preface_title"]', output, 1
 2570       assert_xpath '/book/dedication/following-sibling::preface[@xml:id="preface_title"]/title[text()="Preface Title"]', output, 1
 2571       assert_xpath '/book/preface/section[@xml:id="preface_sub_section"]', output, 1
 2572       assert_xpath '/book/preface/section[@xml:id="preface_sub_section"]/title[text()="Preface sub-section"]', output, 1
 2573       assert_xpath '/book/preface/following-sibling::part[@xml:id="part_1"]', output, 1
 2574       assert_xpath '/book/preface/following-sibling::part[@xml:id="part_1"]/title[text()="Part 1"]', output, 1
 2575       assert_xpath '/book/part[@xml:id="part_1"]/partintro', output, 1
 2576       assert_xpath '/book/part[@xml:id="part_1"]/partintro/title[text()="Part intro title"]', output, 1
 2577       assert_xpath '/book/part[@xml:id="part_1"]/partintro/following-sibling::chapter[@xml:id="chapter_1"]', output, 1
 2578       assert_xpath '/book/part[@xml:id="part_1"]/partintro/following-sibling::chapter[@xml:id="chapter_1"]/title[text()="Chapter 1"]', output, 1
 2579       assert_xpath '(/book/part)[2]/following-sibling::appendix[@xml:id="appendix_title"]', output, 1
 2580       assert_xpath '(/book/part)[2]/following-sibling::appendix[@xml:id="appendix_title"]/title[text()="Appendix Title"]', output, 1
 2581       assert_xpath '/book/appendix/section[@xml:id="appendix_sub_section"]', output, 1
 2582       assert_xpath '/book/appendix/section[@xml:id="appendix_sub_section"]/title[text()="Appendix sub-section"]', output, 1
 2583       assert_xpath '/book/appendix/following-sibling::bibliography[@xml:id="bibliography_title"]', output, 1
 2584       assert_xpath '/book/appendix/following-sibling::bibliography[@xml:id="bibliography_title"]/title[text()="Bibliography Title"]', output, 1
 2585       assert_xpath '/book/bibliography/following-sibling::glossary[@xml:id="glossary_title"]', output, 1
 2586       assert_xpath '/book/bibliography/following-sibling::glossary[@xml:id="glossary_title"]/title[text()="Glossary Title"]', output, 1
 2587       assert_xpath '/book/glossary/following-sibling::colophon[@xml:id="colophon_title"]', output, 1
 2588       assert_xpath '/book/glossary/following-sibling::colophon[@xml:id="colophon_title"]/title[text()="Colophon Title"]', output, 1
 2589       assert_xpath '/book/colophon/following-sibling::index[@xml:id="index_title"]', output, 1
 2590       assert_xpath '/book/colophon/following-sibling::index[@xml:id="index_title"]/title[text()="Index Title"]', output, 1
 2591     end
 2592 
 2593     test 'abstract section maps to abstract element in docbook for article doctype' do
 2594       input = <<~'EOS'
 2595       = Article
 2596       :idprefix:
 2597 
 2598       [abstract]
 2599       == Abstract Title
 2600 
 2601       Abstract content
 2602       EOS
 2603 
 2604       output = convert_string_to_embedded input, backend: 'docbook'
 2605       assert_xpath '/abstract[@xml:id="abstract_title"]', output, 1
 2606       assert_xpath '/abstract[@xml:id="abstract_title"]/title[text()="Abstract Title"]', output, 1
 2607     end
 2608 
 2609     test 'should allow a special section to be nested at arbitrary depth in DocBook output' do
 2610       input = <<~'EOS'
 2611       = Document Title
 2612       :doctype: book
 2613 
 2614       == Glossaries
 2615 
 2616       [glossary]
 2617       === Glossary A
 2618 
 2619       Glossaries are optional.
 2620       Glossaries entries are an example of a style of AsciiDoc description lists.
 2621 
 2622       [glossary]
 2623       A glossary term::
 2624       The corresponding definition.
 2625 
 2626       A second glossary term::
 2627       The corresponding definition.
 2628       EOS
 2629 
 2630       output = convert_string input, backend: :docbook
 2631       assert_xpath '//glossary', output, 1
 2632       assert_xpath '//chapter/glossary', output, 1
 2633       assert_xpath '//glossary/title[text()="Glossary A"]', output, 1
 2634       assert_xpath '//glossary/glossentry', output, 2
 2635     end
 2636 
 2637     test 'should drop title on special section in DocBook output if untitled option is set' do
 2638       input = <<~'EOS'
 2639       [dedication%untitled]
 2640       == Dedication
 2641 
 2642       content
 2643       EOS
 2644 
 2645       output = convert_string_to_embedded input, backend: :docbook
 2646       assert_xpath '/dedication', output, 1
 2647       assert_xpath '/dedication/title', output, 0
 2648     end
 2649   end
 2650 
 2651   context "heading patterns in blocks" do
 2652     test "should not interpret a listing block as a heading" do
 2653       input = <<~'EOS'
 2654       Section
 2655       -------
 2656 
 2657       ----
 2658       code
 2659       ----
 2660 
 2661       fin.
 2662       EOS
 2663       output = convert_string input
 2664       assert_xpath "//h2", output, 1
 2665     end
 2666 
 2667     test "should not interpret an open block as a heading" do
 2668       input = <<~'EOS'
 2669       Section
 2670       -------
 2671 
 2672       --
 2673       ha
 2674       --
 2675 
 2676       fin.
 2677       EOS
 2678       output = convert_string input
 2679       assert_xpath "//h2", output, 1
 2680     end
 2681 
 2682     test "should not interpret an attribute list as a heading" do
 2683       input = <<~'EOS'
 2684       Section
 2685       =======
 2686 
 2687       preamble
 2688 
 2689       [TIP]
 2690       ====
 2691       This should be a tip, not a heading.
 2692       ====
 2693       EOS
 2694       output = convert_string input
 2695       assert_xpath "//*[@class='admonitionblock tip']//p[text() = 'This should be a tip, not a heading.']", output, 1
 2696     end
 2697 
 2698     test "should not match a heading in a description list" do
 2699       input = <<~'EOS'
 2700       Section
 2701       -------
 2702 
 2703       term1::
 2704       +
 2705       ----
 2706       list = [1, 2, 3];
 2707       ----
 2708       term2::
 2709       == not a heading
 2710       term3:: def
 2711 
 2712       //
 2713 
 2714       fin.
 2715       EOS
 2716       output = convert_string input
 2717       assert_xpath "//h2", output, 1
 2718       assert_xpath "//dl", output, 1
 2719     end
 2720 
 2721     test "should not match a heading in a bulleted list" do
 2722       input = <<~'EOS'
 2723       Section
 2724       -------
 2725 
 2726       * first
 2727       +
 2728       ----
 2729       list = [1, 2, 3];
 2730       ----
 2731       +
 2732       * second
 2733       == not a heading
 2734       * third
 2735 
 2736       fin.
 2737       EOS
 2738       output = convert_string input
 2739       assert_xpath "//h2", output, 1
 2740       assert_xpath "//ul", output, 1
 2741     end
 2742 
 2743     test "should not match a heading in a block" do
 2744       input = <<~'EOS'
 2745       ====
 2746 
 2747       == not a heading
 2748 
 2749       ====
 2750       EOS
 2751       output = convert_string input
 2752       assert_xpath "//h2", output, 0
 2753       assert_xpath "//*[@class='exampleblock']//p[text() = '== not a heading']", output, 1
 2754     end
 2755   end
 2756 
 2757   context 'Table of Contents' do
 2758     test 'should output unnumbered table of contents in header if toc attribute is set' do
 2759       input = <<~'EOS'
 2760       = Article
 2761       :toc:
 2762 
 2763       == Section One
 2764 
 2765       It was a dark and stormy night...
 2766 
 2767       == Section Two
 2768 
 2769       They couldn't believe their eyes when...
 2770 
 2771       === Interlude
 2772 
 2773       While they were waiting...
 2774 
 2775       == Section Three
 2776 
 2777       That's all she wrote!
 2778       EOS
 2779       output = convert_string input
 2780       assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc"]', output, 1
 2781       assert_xpath '//*[@id="header"]//*[@id="toc"]/*[@id="toctitle"][text()="Table of Contents"]', output, 1
 2782       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul', output, 1
 2783       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul[@class="sectlevel1"]', output, 1
 2784       assert_xpath '//*[@id="header"]//*[@id="toc"]//ul', output, 2
 2785       assert_xpath '//*[@id="header"]//*[@id="toc"]//li', output, 4
 2786       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="Section One"]', output, 1
 2787       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul', output, 1
 2788       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul[@class="sectlevel2"]', output, 1
 2789       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li', output, 1
 2790       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li/a[@href="#_interlude"][text()="Interlude"]', output, 1
 2791       assert_xpath '((//*[@id="header"]//*[@id="toc"]/ul)[1]/li)[3]/a[@href="#_section_three"][text()="Section Three"]', output, 1
 2792     end
 2793 
 2794     test 'should output numbered table of contents in header if toc and numbered attributes are set' do
 2795       input = <<~'EOS'
 2796       = Article
 2797       :toc:
 2798       :numbered:
 2799 
 2800       == Section One
 2801 
 2802       It was a dark and stormy night...
 2803 
 2804       == Section Two
 2805 
 2806       They couldn't believe their eyes when...
 2807 
 2808       === Interlude
 2809 
 2810       While they were waiting...
 2811 
 2812       == Section Three
 2813 
 2814       That's all she wrote!
 2815       EOS
 2816       output = convert_string input
 2817       assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc"]', output, 1
 2818       assert_xpath '//*[@id="header"]//*[@id="toc"]/*[@id="toctitle"][text()="Table of Contents"]', output, 1
 2819       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul', output, 1
 2820       assert_xpath '//*[@id="header"]//*[@id="toc"]//ul', output, 2
 2821       assert_xpath '//*[@id="header"]//*[@id="toc"]//li', output, 4
 2822       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
 2823       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li', output, 1
 2824       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li/a[@href="#_interlude"][text()="2.1. Interlude"]', output, 1
 2825       assert_xpath '((//*[@id="header"]//*[@id="toc"]/ul)[1]/li)[3]/a[@href="#_section_three"][text()="3. Section Three"]', output, 1
 2826     end
 2827 
 2828     test 'should output a table of contents that honors numbered setting at position of section in document' do
 2829       input = <<~'EOS'
 2830       = Article
 2831       :toc:
 2832       :numbered:
 2833 
 2834       == Section One
 2835 
 2836       It was a dark and stormy night...
 2837 
 2838       == Section Two
 2839 
 2840       They couldn't believe their eyes when...
 2841 
 2842       === Interlude
 2843 
 2844       While they were waiting...
 2845 
 2846       :numbered!:
 2847 
 2848       == Section Three
 2849 
 2850       That's all she wrote!
 2851       EOS
 2852       output = convert_string input
 2853       assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc"]', output, 1
 2854       assert_xpath '//*[@id="header"]//*[@id="toc"]/*[@id="toctitle"][text()="Table of Contents"]', output, 1
 2855       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul', output, 1
 2856       assert_xpath '//*[@id="header"]//*[@id="toc"]//ul', output, 2
 2857       assert_xpath '//*[@id="header"]//*[@id="toc"]//li', output, 4
 2858       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
 2859       assert_xpath '((//*[@id="header"]//*[@id="toc"]/ul)[1]/li)[3]/a[@href="#_section_three"][text()="Section Three"]', output, 1
 2860     end
 2861 
 2862     test 'should not number parts in table of contents for book doctype when numbered attribute is set' do
 2863       input = <<~'EOS'
 2864       = Book
 2865       :doctype: book
 2866       :toc:
 2867       :numbered:
 2868 
 2869       = Part 1
 2870 
 2871       == First Section of Part 1
 2872 
 2873       blah
 2874 
 2875       == Second Section of Part 1
 2876 
 2877       blah
 2878 
 2879       = Part 2
 2880 
 2881       == First Section of Part 2
 2882 
 2883       blah
 2884       EOS
 2885 
 2886       output = convert_string input
 2887       assert_xpath '//*[@id="toc"]', output, 1
 2888       assert_xpath '//*[@id="toc"]/ul', output, 1
 2889       assert_xpath '//*[@id="toc"]/ul[@class="sectlevel0"]', output, 1
 2890       assert_xpath '//*[@id="toc"]/ul[@class="sectlevel0"]/li', output, 2
 2891       assert_xpath '(//*[@id="toc"]/ul[@class="sectlevel0"]/li)[1]/a[text()="Part 1"]', output, 1
 2892       assert_xpath '(//*[@id="toc"]/ul[@class="sectlevel0"]/li)[2]/a[text()="Part 2"]', output, 1
 2893       assert_xpath '(//*[@id="toc"]/ul[@class="sectlevel0"]/li)[1]/ul', output, 1
 2894       assert_xpath '(//*[@id="toc"]/ul[@class="sectlevel0"]/li)[1]/ul[@class="sectlevel1"]', output, 1
 2895       assert_xpath '(//*[@id="toc"]/ul[@class="sectlevel0"]/li)[1]/ul/li', output, 2
 2896       assert_xpath '((//*[@id="toc"]/ul[@class="sectlevel0"]/li)[1]/ul/li)[1]/a[text()="1. First Section of Part 1"]', output, 1
 2897     end
 2898 
 2899     test 'should output table of contents in header if toc2 attribute is set' do
 2900       input = <<~'EOS'
 2901       = Article
 2902       :toc2:
 2903       :numbered:
 2904 
 2905       == Section One
 2906 
 2907       It was a dark and stormy night...
 2908 
 2909       == Section Two
 2910 
 2911       They couldn't believe their eyes when...
 2912       EOS
 2913 
 2914       output = convert_string input
 2915       assert_xpath '//body[@class="article toc2 toc-left"]', output, 1
 2916       assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc2"]', output, 1
 2917       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
 2918     end
 2919 
 2920     test 'should set toc position if toc attribute is set to position' do
 2921       input = <<~'EOS'
 2922       = Article
 2923       :toc: >
 2924       :numbered:
 2925 
 2926       == Section One
 2927 
 2928       It was a dark and stormy night...
 2929 
 2930       == Section Two
 2931 
 2932       They couldn't believe their eyes when...
 2933       EOS
 2934 
 2935       output = convert_string input
 2936       assert_xpath '//body[@class="article toc2 toc-right"]', output, 1
 2937       assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc2"]', output, 1
 2938       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
 2939     end
 2940 
 2941     test 'should set toc position if toc and toc-position attributes are set' do
 2942       input = <<~'EOS'
 2943       = Article
 2944       :toc:
 2945       :toc-position: right
 2946       :numbered:
 2947 
 2948       == Section One
 2949 
 2950       It was a dark and stormy night...
 2951 
 2952       == Section Two
 2953 
 2954       They couldn't believe their eyes when...
 2955       EOS
 2956 
 2957       output = convert_string input
 2958       assert_xpath '//body[@class="article toc2 toc-right"]', output, 1
 2959       assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc2"]', output, 1
 2960       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
 2961     end
 2962 
 2963     test 'should set toc position if toc2 and toc-position attribute are set' do
 2964       input = <<~'EOS'
 2965       = Article
 2966       :toc2:
 2967       :toc-position: right
 2968       :numbered:
 2969 
 2970       == Section One
 2971 
 2972       It was a dark and stormy night...
 2973 
 2974       == Section Two
 2975 
 2976       They couldn't believe their eyes when...
 2977       EOS
 2978 
 2979       output = convert_string input
 2980       assert_xpath '//body[@class="article toc2 toc-right"]', output, 1
 2981       assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc2"]', output, 1
 2982       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
 2983     end
 2984 
 2985     test 'should set toc position if toc attribute is set to direction' do
 2986       input = <<~'EOS'
 2987       = Article
 2988       :toc: right
 2989       :numbered:
 2990 
 2991       == Section One
 2992 
 2993       It was a dark and stormy night...
 2994 
 2995       == Section Two
 2996 
 2997       They couldn't believe their eyes when...
 2998       EOS
 2999 
 3000       output = convert_string input
 3001       assert_xpath '//body[@class="article toc2 toc-right"]', output, 1
 3002       assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc2"]', output, 1
 3003       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[1]/a[@href="#_section_one"][text()="1. Section One"]', output, 1
 3004     end
 3005 
 3006     test 'should set toc placement to preamble if toc attribute is set to preamble' do
 3007       input = <<~'EOS'
 3008       = Article
 3009       :toc: preamble
 3010 
 3011       Yada yada
 3012 
 3013       == Section One
 3014 
 3015       It was a dark and stormy night...
 3016 
 3017       == Section Two
 3018 
 3019       They couldn't believe their eyes when...
 3020       EOS
 3021 
 3022       output = convert_string input
 3023       assert_css '#preamble #toc', output, 1
 3024       assert_css '#preamble .sectionbody + #toc', output, 1
 3025     end
 3026 
 3027     test 'should use document attributes toc-class, toc-title and toclevels to create toc' do
 3028       input = <<~'EOS'
 3029       = Article
 3030       :toc:
 3031       :toc-title: Contents
 3032       :toc-class: toc2
 3033       :toclevels: 1
 3034 
 3035       == Section 1
 3036 
 3037       === Section 1.1
 3038 
 3039       ==== Section 1.1.1
 3040 
 3041       ==== Section 1.1.2
 3042 
 3043       === Section 1.2
 3044 
 3045       == Section 2
 3046 
 3047       Fin.
 3048       EOS
 3049       output = convert_string input
 3050       assert_css '#header #toc', output, 1
 3051       assert_css '#header #toc.toc2', output, 1
 3052       assert_css '#header #toc li', output, 2
 3053       assert_css '#header #toc #toctitle', output, 1
 3054       assert_xpath '//*[@id="header"]//*[@id="toc"]/*[@id="toctitle"][text()="Contents"]', output, 1
 3055     end
 3056 
 3057     test 'should not output table of contents if toc-placement attribute is unset' do
 3058       input = <<~'EOS'
 3059       = Article
 3060       :toc:
 3061       :toc-placement!:
 3062 
 3063       == Section One
 3064 
 3065       It was a dark and stormy night...
 3066 
 3067       == Section Two
 3068 
 3069       They couldn't believe their eyes when...
 3070       EOS
 3071 
 3072       output = convert_string input
 3073       assert_xpath '//*[@id="toc"]', output, 0
 3074     end
 3075 
 3076     test 'should output table of contents at location of toc macro' do
 3077       input = <<~'EOS'
 3078       = Article
 3079       :toc:
 3080       :toc-placement: macro
 3081 
 3082       Once upon a time...
 3083 
 3084       toc::[]
 3085 
 3086       == Section One
 3087 
 3088       It was a dark and stormy night...
 3089 
 3090       == Section Two
 3091 
 3092       They couldn't believe their eyes when...
 3093       EOS
 3094 
 3095       output = convert_string input
 3096       assert_css '#preamble #toc', output, 1
 3097       assert_css '#preamble .paragraph + #toc', output, 1
 3098     end
 3099 
 3100     test 'should output table of contents at location of toc macro in embedded document' do
 3101       input = <<~'EOS'
 3102       = Article
 3103       :toc:
 3104       :toc-placement: macro
 3105 
 3106       Once upon a time...
 3107 
 3108       toc::[]
 3109 
 3110       == Section One
 3111 
 3112       It was a dark and stormy night...
 3113 
 3114       == Section Two
 3115 
 3116       They couldn't believe their eyes when...
 3117       EOS
 3118 
 3119       output = convert_string_to_embedded input
 3120       assert_css '#preamble:root #toc', output, 1
 3121       assert_css '#preamble:root .paragraph + #toc', output, 1
 3122     end
 3123 
 3124     test 'should output table of contents at default location in embedded document if toc attribute is set' do
 3125       input = <<~'EOS'
 3126       = Article
 3127       :showtitle:
 3128       :toc:
 3129 
 3130       Once upon a time...
 3131 
 3132       == Section One
 3133 
 3134       It was a dark and stormy night...
 3135 
 3136       == Section Two
 3137 
 3138       They couldn't believe their eyes when...
 3139       EOS
 3140 
 3141       output = convert_string_to_embedded input
 3142       assert_css 'h1:root', output, 1
 3143       assert_css 'h1:root + #toc:root', output, 1
 3144       assert_css 'h1:root + #toc:root + #preamble:root', output, 1
 3145     end
 3146 
 3147     test 'should not activate toc macro if toc-placement is not set' do
 3148       input = <<~'EOS'
 3149       = Article
 3150       :toc:
 3151 
 3152       Once upon a time...
 3153 
 3154       toc::[]
 3155 
 3156       == Section One
 3157 
 3158       It was a dark and stormy night...
 3159 
 3160       == Section Two
 3161 
 3162       They couldn't believe their eyes when...
 3163       EOS
 3164 
 3165       output = convert_string input
 3166 
 3167       assert_css '#toc', output, 1
 3168       assert_css '#toctitle', output, 1
 3169       assert_css '.toc', output, 1
 3170       assert_css '#content .toc', output, 0
 3171     end
 3172 
 3173     test 'should only output toc at toc macro if toc is macro' do
 3174       input = <<~'EOS'
 3175       = Article
 3176       :toc: macro
 3177 
 3178       Once upon a time...
 3179 
 3180       toc::[]
 3181 
 3182       == Section One
 3183 
 3184       It was a dark and stormy night...
 3185 
 3186       == Section Two
 3187 
 3188       They couldn't believe their eyes when...
 3189       EOS
 3190 
 3191       output = convert_string input
 3192 
 3193       assert_css '#toc', output, 1
 3194       assert_css '#toctitle', output, 1
 3195       assert_css '.toc', output, 1
 3196       assert_css '#content .toc', output, 1
 3197     end
 3198 
 3199     test 'should use global attributes for toc-title, toc-class and toclevels for toc macro' do
 3200       input = <<~'EOS'
 3201       = Article
 3202       :toc:
 3203       :toc-placement: macro
 3204       :toc-title: Contents
 3205       :toc-class: contents
 3206       :toclevels: 1
 3207 
 3208       Preamble.
 3209 
 3210       toc::[]
 3211 
 3212       == Section 1
 3213 
 3214       === Section 1.1
 3215 
 3216       ==== Section 1.1.1
 3217 
 3218       ==== Section 1.1.2
 3219 
 3220       === Section 1.2
 3221 
 3222       == Section 2
 3223 
 3224       Fin.
 3225       EOS
 3226 
 3227       output = convert_string input
 3228       assert_css '#toc', output, 1
 3229       assert_css '#toctitle', output, 1
 3230       assert_css '#preamble #toc', output, 1
 3231       assert_css '#preamble #toc.contents', output, 1
 3232       assert_xpath '//*[@id="toc"]/*[@class="title"][text() = "Contents"]', output, 1
 3233       assert_css '#toc li', output, 2
 3234       assert_xpath '(//*[@id="toc"]//li)[1]/a[text() = "Section 1"]', output, 1
 3235       assert_xpath '(//*[@id="toc"]//li)[2]/a[text() = "Section 2"]', output, 1
 3236     end
 3237 
 3238     test 'should honor id, title, role and level attributes on toc macro' do
 3239       input = <<~'EOS'
 3240       = Article
 3241       :toc:
 3242       :toc-placement: macro
 3243       :toc-title: Ignored
 3244       :toc-class: ignored
 3245       :toclevels: 5
 3246       :tocdepth: 1
 3247 
 3248       Preamble.
 3249 
 3250       [[contents]]
 3251       [role="contents"]
 3252       .Contents
 3253       toc::[levels={tocdepth}]
 3254 
 3255       == Section 1
 3256 
 3257       === Section 1.1
 3258 
 3259       ==== Section 1.1.1
 3260 
 3261       ==== Section 1.1.2
 3262 
 3263       === Section 1.2
 3264 
 3265       == Section 2
 3266 
 3267       Fin.
 3268       EOS
 3269 
 3270       output = convert_string input
 3271       assert_css '#toc', output, 0
 3272       assert_css '#toctitle', output, 0
 3273       assert_css '#preamble #contents', output, 1
 3274       assert_css '#preamble #contents.contents', output, 1
 3275       assert_xpath '//*[@id="contents"]/*[@class="title"][text() = "Contents"]', output, 1
 3276       assert_css '#contents li', output, 2
 3277       assert_xpath '(//*[@id="contents"]//li)[1]/a[text() = "Section 1"]', output, 1
 3278       assert_xpath '(//*[@id="contents"]//li)[2]/a[text() = "Section 2"]', output, 1
 3279     end
 3280 
 3281     test 'child toc levels should not have additional bullet at parent level in html' do
 3282       input = <<~'EOS'
 3283       = Article
 3284       :toc:
 3285 
 3286       == Section One
 3287 
 3288       It was a dark and stormy night...
 3289 
 3290       == Section Two
 3291 
 3292       They couldn't believe their eyes when...
 3293 
 3294       === Interlude
 3295 
 3296       While they were waiting...
 3297 
 3298       == Section Three
 3299 
 3300       That's all she wrote!
 3301       EOS
 3302       output = convert_string input
 3303       assert_xpath '//*[@id="header"]//*[@id="toc"][@class="toc"]', output, 1
 3304       assert_xpath '//*[@id="header"]//*[@id="toc"]/*[@id="toctitle"][text()="Table of Contents"]', output, 1
 3305       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul', output, 1
 3306       assert_xpath '//*[@id="header"]//*[@id="toc"]//ul', output, 2
 3307       assert_xpath '//*[@id="header"]//*[@id="toc"]//li', output, 4
 3308       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[2]/a[@href="#_section_two"][text()="Section Two"]', output, 1
 3309       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li', output, 1
 3310       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li[2]/ul/li', output, 1
 3311       assert_xpath '//*[@id="header"]//*[@id="toc"]/ul/li/ul/li/a[@href="#_interlude"][text()="Interlude"]', output, 1
 3312       assert_xpath '((//*[@id="header"]//*[@id="toc"]/ul)[1]/li)[3]/a[@href="#_section_three"][text()="Section Three"]', output, 1
 3313     end
 3314 
 3315     test 'should not display a table of contents if document has no sections' do
 3316       input_src = <<~'EOS'
 3317       = Document Title
 3318       :toc:
 3319 
 3320       toc::[]
 3321 
 3322       This document has no sections.
 3323 
 3324       It only has content.
 3325       EOS
 3326 
 3327       ['', 'left', 'preamble', 'macro'].each do |placement|
 3328         input = input_src.gsub(':toc:', "\\& #{placement}")
 3329         output = convert_string input
 3330         assert_css '#toctitle', output, 0
 3331       end
 3332     end
 3333 
 3334     test 'should drop anchors from contents of entries in table of contents' do
 3335       input = <<~'EOS'
 3336       = Document Title
 3337       :toc:
 3338 
 3339       == [[un]]Section One
 3340 
 3341       content
 3342 
 3343       == [[two]][[deux]]Section Two
 3344 
 3345       content
 3346 
 3347       == Plant Trees by https://ecosia.org[Searching]
 3348 
 3349       content
 3350       EOS
 3351 
 3352       output = convert_string_to_embedded input
 3353       assert_xpath '/*[@id="toc"]', output, 1
 3354       toc_links = xmlnodes_at_xpath '/*[@id="toc"]//li', output
 3355       assert_equal 3, toc_links.size
 3356       assert_equal '<a href="#_section_one">Section One</a>', toc_links[0].inner_html
 3357       assert_equal '<a href="#_section_two">Section Two</a>', toc_links[1].inner_html
 3358       assert_equal '<a href="#_plant_trees_by_searching">Plant Trees by Searching</a>', toc_links[2].inner_html
 3359     end
 3360 
 3361     test 'should not remove non-anchor tags from contents of entries in table of contents' do
 3362       input = <<~'EOS'
 3363       = Document Title
 3364       :toc:
 3365       :icons: font
 3366 
 3367       == `run` command
 3368 
 3369       content
 3370 
 3371       == icon:bug[] Issues
 3372 
 3373       content
 3374 
 3375       == https://ecosia.org[_Sustainable_ Searches]
 3376 
 3377       content
 3378       EOS
 3379 
 3380       output = convert_string_to_embedded input, safe: :safe
 3381       assert_xpath '/*[@id="toc"]', output, 1
 3382       toc_links = xmlnodes_at_xpath '/*[@id="toc"]//li', output
 3383       assert_equal 3, toc_links.size
 3384       assert_equal '<a href="#_run_command"><code>run</code> command</a>', toc_links[0].inner_html
 3385       assert_equal '<a href="#_issues"><span class="icon"><i class="fa fa-bug"></i></span> Issues</a>', toc_links[1].inner_html
 3386       assert_equal '<a href="#_sustainable_searches"><em>Sustainable</em> Searches</a>', toc_links[2].inner_html
 3387     end
 3388   end
 3389 
 3390   context 'article doctype' do
 3391     test 'should create only sections in docbook backend' do
 3392       input = <<~'EOS'
 3393       = Article
 3394       Doc Writer
 3395 
 3396       == Section 1
 3397 
 3398       The adventure.
 3399 
 3400       === Subsection One
 3401 
 3402       It was a dark and stormy night...
 3403 
 3404       === Subsection Two
 3405 
 3406       They couldn't believe their eyes when...
 3407 
 3408       == Section 2
 3409 
 3410       The return.
 3411 
 3412       === Subsection Three
 3413 
 3414       While they were returning...
 3415 
 3416       === Subsection Four
 3417 
 3418       That's all she wrote!
 3419       EOS
 3420 
 3421       output = convert_string input, backend: 'docbook'
 3422       assert_xpath '//part', output, 0
 3423       assert_xpath '//chapter', output, 0
 3424       assert_xpath '/article/section', output, 2
 3425       assert_xpath '/article/section[1]/title[text() = "Section 1"]', output, 1
 3426       assert_xpath '/article/section[2]/title[text() = "Section 2"]', output, 1
 3427       assert_xpath '/article/section/section', output, 4
 3428       assert_xpath '/article/section[1]/section[1]/title[text() = "Subsection One"]', output, 1
 3429       assert_xpath '/article/section[2]/section[1]/title[text() = "Subsection Three"]', output, 1
 3430     end
 3431   end
 3432 
 3433   context 'book doctype' do
 3434     test 'document title with level 0 headings' do
 3435       input = <<~'EOS'
 3436       = Book
 3437       Doc Writer
 3438       :doctype: book
 3439 
 3440       = Chapter One
 3441 
 3442       [partintro]
 3443       It was a dark and stormy night...
 3444 
 3445       == Scene One
 3446 
 3447       Someone's gonna get axed.
 3448 
 3449       = Chapter Two
 3450 
 3451       [partintro]
 3452       They couldn't believe their eyes when...
 3453 
 3454       == Interlude
 3455 
 3456       While they were waiting...
 3457 
 3458       = Chapter Three
 3459 
 3460       == Scene One
 3461 
 3462       That's all she wrote!
 3463       EOS
 3464 
 3465       output = convert_string(input)
 3466       assert_css 'body.book', output, 1
 3467       assert_css 'h1', output, 4
 3468       assert_css '#header h1', output, 1
 3469       assert_css '#content h1', output, 3
 3470       assert_css '#content h1.sect0', output, 3
 3471       assert_css 'h2', output, 3
 3472       assert_css '#content h2', output, 3
 3473       assert_xpath '//h1[@id="_chapter_one"][text() = "Chapter One"]', output, 1
 3474       assert_xpath '//h1[@id="_chapter_two"][text() = "Chapter Two"]', output, 1
 3475       assert_xpath '//h1[@id="_chapter_three"][text() = "Chapter Three"]', output, 1
 3476     end
 3477 
 3478     test 'should print error if level 0 section comes after nested section and doctype is not book' do
 3479       input = <<~'EOS'
 3480       = Document Title
 3481 
 3482       == Level 1 Section
 3483 
 3484       === Level 2 Section
 3485 
 3486       = Level 0 Section
 3487       EOS
 3488 
 3489       using_memory_logger do |logger|
 3490         convert_string input
 3491         assert_message logger, :ERROR, '<stdin>: line 7: level 0 sections can only be used when doctype is book', Hash
 3492       end
 3493     end
 3494 
 3495     test 'should add class matching role to part' do
 3496       input = <<~'EOS'
 3497       = Book Title
 3498       :doctype: book
 3499 
 3500       [.newbie]
 3501       = Part 1
 3502 
 3503       == Chapter A
 3504 
 3505       content
 3506 
 3507       = Part 2
 3508 
 3509       == Chapter B
 3510 
 3511       content
 3512       EOS
 3513 
 3514       result = convert_string_to_embedded input
 3515       assert_css 'h1.sect0', result, 2
 3516       assert_css 'h1.sect0.newbie', result, 1
 3517       assert_css 'h1.sect0.newbie#_part_1', result, 1
 3518     end
 3519 
 3520     test 'should assign appropriate sectname for section type' do
 3521       input = <<~'EOS'
 3522       = Book Title
 3523       :doctype: book
 3524       :idprefix:
 3525       :idseparator: -
 3526 
 3527       = Part Title
 3528 
 3529       == Chapter Title
 3530 
 3531       === Section Title
 3532 
 3533       content
 3534 
 3535       [appendix]
 3536       == Appendix Title
 3537 
 3538       === Appendix Section Title
 3539 
 3540       content
 3541       EOS
 3542 
 3543       doc = document_from_string input
 3544       assert_equal 'header', doc.header.sectname
 3545       assert_equal 'part', (doc.find_by id: 'part-title')[0].sectname
 3546       assert_equal 'chapter', (doc.find_by id: 'chapter-title')[0].sectname
 3547       assert_equal 'section', (doc.find_by id: 'section-title')[0].sectname
 3548       assert_equal 'appendix', (doc.find_by id: 'appendix-title')[0].sectname
 3549       assert_equal 'section', (doc.find_by id: 'appendix-section-title')[0].sectname
 3550     end
 3551 
 3552     test 'should add partintro style to child paragraph of part' do
 3553       input = <<~'EOS'
 3554       = Book
 3555       :doctype: book
 3556 
 3557       = Part 1
 3558 
 3559       part intro
 3560 
 3561       == Chapter 1
 3562       EOS
 3563 
 3564       doc = document_from_string input
 3565       partintro = doc.blocks.first.blocks.first
 3566       assert_equal :open, partintro.context
 3567       assert_equal 'partintro', partintro.style
 3568     end
 3569 
 3570     test 'should add partintro style to child open block of part' do
 3571       input = <<~'EOS'
 3572       = Book
 3573       :doctype: book
 3574 
 3575       = Part 1
 3576 
 3577       --
 3578       part intro
 3579       --
 3580 
 3581       == Chapter 1
 3582       EOS
 3583 
 3584       doc = document_from_string input
 3585       partintro = doc.blocks.first.blocks.first
 3586       assert_equal :open, partintro.context
 3587       assert_equal 'partintro', partintro.style
 3588     end
 3589 
 3590     test 'should wrap child paragraphs of part in partintro open block' do
 3591       input = <<~'EOS'
 3592       = Book
 3593       :doctype: book
 3594 
 3595       = Part 1
 3596 
 3597       part intro
 3598 
 3599       more part intro
 3600 
 3601       == Chapter 1
 3602       EOS
 3603 
 3604       doc = document_from_string input
 3605       partintro = doc.blocks.first.blocks.first
 3606       assert_equal :open, partintro.context
 3607       assert_equal 'partintro', partintro.style
 3608       assert_equal 2, partintro.blocks.size
 3609       assert_equal :paragraph, partintro.blocks[0].context
 3610       assert_equal :paragraph, partintro.blocks[1].context
 3611     end
 3612 
 3613     test 'should warn if part has no sections' do
 3614       input = <<~'EOS'
 3615       = Book
 3616       :doctype: book
 3617 
 3618       = Part 1
 3619 
 3620       [partintro]
 3621       intro
 3622       EOS
 3623 
 3624       using_memory_logger do |logger|
 3625         document_from_string input
 3626         assert_message logger, :ERROR, '<stdin>: line 8: invalid part, must have at least one section (e.g., chapter, appendix, etc.)', Hash
 3627       end
 3628     end
 3629 
 3630     test 'should create parts and chapters in docbook backend' do
 3631       input = <<~'EOS'
 3632       = Book
 3633       Doc Writer
 3634       :doctype: book
 3635 
 3636       = Part 1
 3637 
 3638       [partintro]
 3639       The adventure.
 3640 
 3641       == Chapter One
 3642 
 3643       It was a dark and stormy night...
 3644 
 3645       == Chapter Two
 3646 
 3647       They couldn't believe their eyes when...
 3648 
 3649       = Part 2
 3650 
 3651       [partintro]
 3652       The return.
 3653 
 3654       == Chapter Three
 3655 
 3656       While they were returning...
 3657 
 3658       == Chapter Four
 3659 
 3660       That's all she wrote!
 3661       EOS
 3662 
 3663       output = convert_string input, backend: 'docbook'
 3664       assert_xpath '//chapter/chapter', output, 0
 3665       assert_xpath '/book/part', output, 2
 3666       assert_xpath '/book/part[1]/title[text() = "Part 1"]', output, 1
 3667       assert_xpath '/book/part[2]/title[text() = "Part 2"]', output, 1
 3668       assert_xpath '/book/part/chapter', output, 4
 3669       assert_xpath '/book/part[1]/chapter[1]/title[text() = "Chapter One"]', output, 1
 3670       assert_xpath '/book/part[2]/chapter[1]/title[text() = "Chapter Three"]', output, 1
 3671     end
 3672 
 3673     test 'subsections in preface and appendix should start at level 2' do
 3674       input = <<~'EOS'
 3675       = Multipart Book
 3676       Doc Writer
 3677       :doctype: book
 3678 
 3679       [preface]
 3680       = Preface
 3681 
 3682       Preface content
 3683 
 3684       === Preface subsection
 3685 
 3686       Preface subsection content
 3687 
 3688       = Part 1
 3689 
 3690       .Part intro title
 3691       [partintro]
 3692       Part intro content
 3693 
 3694       == Chapter 1
 3695 
 3696       content
 3697 
 3698       [appendix]
 3699       = Appendix
 3700 
 3701       Appendix content
 3702 
 3703       === Appendix subsection
 3704 
 3705       Appendix subsection content
 3706       EOS
 3707 
 3708       output = nil
 3709       using_memory_logger do |logger|
 3710         output = convert_string input, backend: 'docbook'
 3711         assert logger.empty?
 3712       end
 3713       assert_xpath '/book/preface', output, 1
 3714       assert_xpath '/book/preface/section', output, 1
 3715       assert_xpath '/book/part', output, 1
 3716       assert_xpath '/book/part/partintro', output, 1
 3717       assert_xpath '/book/part/partintro/title', output, 1
 3718       assert_xpath '/book/part/partintro/simpara', output, 1
 3719       assert_xpath '/book/appendix', output, 1
 3720       assert_xpath '/book/appendix/section', output, 1
 3721     end
 3722   end
 3723 end