"Fossies" - the Fresh Open Source Software Archive

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


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

    1 # frozen_string_literal: true
    2 require_relative 'test_helper'
    3 
    4 context 'Blocks' do
    5   default_logger = Asciidoctor::LoggerManager.logger
    6 
    7   setup do
    8     Asciidoctor::LoggerManager.logger = (@logger = Asciidoctor::MemoryLogger.new)
    9   end
   10 
   11   teardown do
   12     Asciidoctor::LoggerManager.logger = default_logger
   13   end
   14 
   15   context 'Layout Breaks' do
   16     test 'horizontal rule' do
   17       %w(''' '''' '''''').each do |line|
   18         output = convert_string_to_embedded line
   19         assert_includes output, '<hr>'
   20       end
   21     end
   22 
   23     test 'horizontal rule with markdown syntax disabled' do
   24       old_markdown_syntax = Asciidoctor::Compliance.markdown_syntax
   25       begin
   26         Asciidoctor::Compliance.markdown_syntax = false
   27         %w(''' '''' '''''').each do |line|
   28           output = convert_string_to_embedded line
   29           assert_includes output, '<hr>'
   30         end
   31         %w(--- *** ___).each do |line|
   32           output = convert_string_to_embedded line
   33           refute_includes output, '<hr>'
   34         end
   35       ensure
   36         Asciidoctor::Compliance.markdown_syntax = old_markdown_syntax
   37       end
   38     end
   39 
   40     test '< 3 chars does not make horizontal rule' do
   41       %w(' '').each do |line|
   42         output = convert_string_to_embedded line
   43         refute_includes output, '<hr>'
   44         assert_includes output, %(<p>#{line}</p>)
   45       end
   46     end
   47 
   48     test 'mixed chars does not make horizontal rule' do
   49       [%q(''<), %q('''<), %q(' ' ')].each do |line|
   50         output = convert_string_to_embedded line
   51         refute_includes output, '<hr>'
   52         assert_includes output, %(<p>#{line.sub '<', '&lt;'}</p>)
   53       end
   54     end
   55 
   56     test 'horizontal rule between blocks' do
   57       output = convert_string_to_embedded %(Block above\n\n'''\n\nBlock below)
   58       assert_xpath '/hr', output, 1
   59       assert_xpath '/hr/preceding-sibling::*', output, 1
   60       assert_xpath '/hr/following-sibling::*', output, 1
   61     end
   62 
   63     test 'page break' do
   64       output = convert_string_to_embedded %(page 1\n\n<<<\n\npage 2)
   65       assert_xpath '/*[translate(@style, ";", "")="page-break-after: always"]', output, 1
   66       assert_xpath '/*[translate(@style, ";", "")="page-break-after: always"]/preceding-sibling::div/p[text()="page 1"]', output, 1
   67       assert_xpath '/*[translate(@style, ";", "")="page-break-after: always"]/following-sibling::div/p[text()="page 2"]', output, 1
   68     end
   69   end
   70 
   71   context 'Comments' do
   72     test 'line comment between paragraphs offset by blank lines' do
   73       input = <<~'EOS'
   74       first paragraph
   75 
   76       // line comment
   77 
   78       second paragraph
   79       EOS
   80       output = convert_string_to_embedded input
   81       refute_match(/line comment/, output)
   82       assert_xpath '//p', output, 2
   83     end
   84 
   85     test 'adjacent line comment between paragraphs' do
   86       input = <<~'EOS'
   87       first line
   88       // line comment
   89       second line
   90       EOS
   91       output = convert_string_to_embedded input
   92       refute_match(/line comment/, output)
   93       assert_xpath '//p', output, 1
   94       assert_xpath "//p[1][text()='first line\nsecond line']", output, 1
   95     end
   96 
   97     test 'comment block between paragraphs offset by blank lines' do
   98       input = <<~'EOS'
   99       first paragraph
  100 
  101       ////
  102       block comment
  103       ////
  104 
  105       second paragraph
  106       EOS
  107       output = convert_string_to_embedded input
  108       refute_match(/block comment/, output)
  109       assert_xpath '//p', output, 2
  110     end
  111 
  112     test 'comment block between paragraphs offset by blank lines inside delimited block' do
  113       input = <<~'EOS'
  114       ====
  115       first paragraph
  116 
  117       ////
  118       block comment
  119       ////
  120 
  121       second paragraph
  122       ====
  123       EOS
  124       output = convert_string_to_embedded input
  125       refute_match(/block comment/, output)
  126       assert_xpath '//p', output, 2
  127     end
  128 
  129     test 'adjacent comment block between paragraphs' do
  130       input = <<~'EOS'
  131       first paragraph
  132       ////
  133       block comment
  134       ////
  135       second paragraph
  136       EOS
  137       output = convert_string_to_embedded input
  138       refute_match(/block comment/, output)
  139       assert_xpath '//p', output, 2
  140     end
  141 
  142     test "can convert with block comment at end of document with trailing newlines" do
  143       input = <<~'EOS'
  144       paragraph
  145 
  146       ////
  147       block comment
  148       ////
  149 
  150 
  151       EOS
  152       output = convert_string_to_embedded input
  153       refute_match(/block comment/, output)
  154     end
  155 
  156     test "trailing newlines after block comment at end of document does not create paragraph" do
  157       input = <<~'EOS'
  158       paragraph
  159 
  160       ////
  161       block comment
  162       ////
  163 
  164 
  165       EOS
  166       d = document_from_string input
  167       assert_equal 1, d.blocks.size
  168       assert_xpath '//p', d.convert, 1
  169     end
  170 
  171     test 'line starting with three slashes should not be line comment' do
  172       input = '/// not a line comment'
  173       output = convert_string_to_embedded input
  174       refute_empty output.strip, "Line should be emitted => #{input.rstrip}"
  175     end
  176 
  177     test 'preprocessor directives should not be processed within comment block within block metadata' do
  178       input = <<~'EOS'
  179       .sample title
  180       ////
  181       ifdef::asciidoctor[////]
  182       ////
  183       line should be shown
  184       EOS
  185 
  186       output = convert_string_to_embedded input
  187       assert_xpath '//p[text()="line should be shown"]', output, 1
  188     end
  189 
  190     test 'preprocessor directives should not be processed within comment block' do
  191       input = <<~'EOS'
  192       dummy line
  193 
  194       ////
  195       ifdef::asciidoctor[////]
  196       ////
  197 
  198       line should be shown
  199       EOS
  200 
  201       output = convert_string_to_embedded input
  202       assert_xpath '//p[text()="line should be shown"]', output, 1
  203     end
  204 
  205     test 'should warn if unterminated comment block is detected in body' do
  206       input = <<~'EOS'
  207       before comment block
  208 
  209       ////
  210       content that has been disabled
  211 
  212       supposed to be after comment block, except it got swallowed by block comment
  213       EOS
  214 
  215       convert_string_to_embedded input
  216       assert_message @logger, :WARN, '<stdin>: line 3: unterminated comment block', Hash
  217     end
  218 
  219     test 'should warn if unterminated comment block is detected inside another block' do
  220       input = <<~'EOS'
  221       before sidebar block
  222 
  223       ****
  224       ////
  225       content that has been disabled
  226       ****
  227 
  228       supposed to be after sidebar block, except it got swallowed by block comment
  229       EOS
  230 
  231       convert_string_to_embedded input
  232       assert_message @logger, :WARN, '<stdin>: line 4: unterminated comment block', Hash
  233     end
  234 
  235     # WARNING if first line of content is a directive, it will get interpretted before we know it's a comment block
  236     # it happens because we always look a line ahead...not sure what we can do about it
  237     test 'preprocessor directives should not be processed within comment open block' do
  238       input = <<~'EOS'
  239       [comment]
  240       --
  241       first line of comment
  242       ifdef::asciidoctor[--]
  243       line should not be shown
  244       --
  245 
  246       EOS
  247 
  248       output = convert_string_to_embedded input
  249       assert_xpath '//p', output, 0
  250     end
  251 
  252     # WARNING this assertion fails if the directive is the first line of the paragraph instead of the second
  253     # it happens because we always look a line ahead; not sure what we can do about it
  254     test 'preprocessor directives should not be processed on subsequent lines of a comment paragraph' do
  255       input = <<~'EOS'
  256       [comment]
  257       first line of content
  258       ifdef::asciidoctor[////]
  259 
  260       this line should be shown
  261       EOS
  262 
  263       output = convert_string_to_embedded input
  264       assert_xpath '//p[text()="this line should be shown"]', output, 1
  265     end
  266 
  267     test 'comment style on open block should only skip block' do
  268       input = <<~'EOS'
  269       [comment]
  270       --
  271       skip
  272 
  273       this block
  274       --
  275 
  276       not this text
  277       EOS
  278       result = convert_string_to_embedded input
  279       assert_xpath '//p', result, 1
  280       assert_xpath '//p[text()="not this text"]', result, 1
  281     end
  282 
  283     test 'comment style on paragraph should only skip paragraph' do
  284       input = <<~'EOS'
  285       [comment]
  286       skip
  287       this paragraph
  288 
  289       not this text
  290       EOS
  291       result = convert_string_to_embedded input
  292       assert_xpath '//p', result, 1
  293       assert_xpath '//p[text()="not this text"]', result, 1
  294     end
  295 
  296     test 'comment style on paragraph should not cause adjacent block to be skipped' do
  297       input = <<~'EOS'
  298       [comment]
  299       skip
  300       this paragraph
  301       [example]
  302       not this text
  303       EOS
  304       result = convert_string_to_embedded input
  305       assert_xpath '/*[@class="exampleblock"]', result, 1
  306       assert_xpath '/*[@class="exampleblock"]//*[normalize-space(text())="not this text"]', result, 1
  307     end
  308 
  309     # NOTE this test verifies the nil return value of Parser#next_block
  310     test 'should not drop content that follows skipped content inside a delimited block' do
  311       input = <<~'EOS'
  312       ====
  313       paragraph
  314 
  315       [comment#idname]
  316       skip
  317 
  318       paragraph
  319       ====
  320       EOS
  321       result = convert_string_to_embedded input
  322       assert_xpath '/*[@class="exampleblock"]', result, 1
  323       assert_xpath '/*[@class="exampleblock"]//*[@class="paragraph"]', result, 2
  324       assert_xpath '//*[@class="paragraph"][@id="idname"]', result, 0
  325     end
  326   end
  327 
  328   context 'Sidebar Blocks' do
  329     test 'should parse sidebar block' do
  330       input = <<~'EOS'
  331       == Section
  332 
  333       .Sidebar
  334       ****
  335       Content goes here
  336       ****
  337       EOS
  338       result = convert_string input
  339       assert_xpath "//*[@class='sidebarblock']//p", result, 1
  340     end
  341   end
  342 
  343   context 'Quote and Verse Blocks' do
  344     test 'quote block with no attribution' do
  345       input = <<~'EOS'
  346       ____
  347       A famous quote.
  348       ____
  349       EOS
  350       output = convert_string input
  351       assert_css '.quoteblock', output, 1
  352       assert_css '.quoteblock > blockquote', output, 1
  353       assert_css '.quoteblock > blockquote > .paragraph > p', output, 1
  354       assert_css '.quoteblock > .attribution', output, 0
  355       assert_xpath '//*[@class="quoteblock"]//p[text()="A famous quote."]', output, 1
  356     end
  357 
  358     test 'quote block with attribution' do
  359       input = <<~'EOS'
  360       [quote, Famous Person, Famous Book (1999)]
  361       ____
  362       A famous quote.
  363       ____
  364       EOS
  365       output = convert_string input
  366       assert_css '.quoteblock', output, 1
  367       assert_css '.quoteblock > blockquote', output, 1
  368       assert_css '.quoteblock > blockquote > .paragraph > p', output, 1
  369       assert_css '.quoteblock > .attribution', output, 1
  370       assert_css '.quoteblock > .attribution > cite', output, 1
  371       assert_css '.quoteblock > .attribution > br + cite', output, 1
  372       assert_xpath '//*[@class="quoteblock"]/*[@class="attribution"]/cite[text()="Famous Book (1999)"]', output, 1
  373       attribution = xmlnodes_at_xpath '//*[@class="quoteblock"]/*[@class="attribution"]', output, 1
  374       author = attribution.children.first
  375       assert_equal "#{decode_char 8212} Famous Person", author.text.strip
  376     end
  377 
  378     test 'quote block with attribute and id and role shorthand' do
  379       input = <<~'EOS'
  380       [quote#justice-to-all.solidarity, Martin Luther King, Jr.]
  381       ____
  382       Injustice anywhere is a threat to justice everywhere.
  383       ____
  384       EOS
  385 
  386       output = convert_string_to_embedded input
  387       assert_css '.quoteblock', output, 1
  388       assert_css '#justice-to-all.quoteblock.solidarity', output, 1
  389       assert_css '.quoteblock > .attribution', output, 1
  390     end
  391 
  392     test 'setting ID using style shorthand should not reset block style' do
  393       input = <<~'EOS'
  394       [quote]
  395       [#justice-to-all.solidarity, Martin Luther King, Jr.]
  396       ____
  397       Injustice anywhere is a threat to justice everywhere.
  398       ____
  399       EOS
  400 
  401       output = convert_string_to_embedded input
  402       assert_css '.quoteblock', output, 1
  403       assert_css '#justice-to-all.quoteblock.solidarity', output, 1
  404       assert_css '.quoteblock > .attribution', output, 1
  405     end
  406 
  407     test 'quote block with complex content' do
  408       input = <<~'EOS'
  409       ____
  410       A famous quote.
  411 
  412       NOTE: _That_ was inspiring.
  413       ____
  414       EOS
  415       output = convert_string input
  416       assert_css '.quoteblock', output, 1
  417       assert_css '.quoteblock > blockquote', output, 1
  418       assert_css '.quoteblock > blockquote > .paragraph', output, 1
  419       assert_css '.quoteblock > blockquote > .paragraph + .admonitionblock', output, 1
  420     end
  421 
  422     test 'quote block with attribution converted to DocBook' do
  423       input = <<~'EOS'
  424       [quote, Famous Person, Famous Book (1999)]
  425       ____
  426       A famous quote.
  427       ____
  428       EOS
  429       output = convert_string input, backend: :docbook
  430       assert_css 'blockquote', output, 1
  431       assert_css 'blockquote > simpara', output, 1
  432       assert_css 'blockquote > attribution', output, 1
  433       assert_css 'blockquote > attribution > citetitle', output, 1
  434       assert_xpath '//blockquote/attribution/citetitle[text()="Famous Book (1999)"]', output, 1
  435       attribution = xmlnodes_at_xpath '//blockquote/attribution', output, 1
  436       author = attribution.children.first
  437       assert_equal 'Famous Person', author.text.strip
  438     end
  439 
  440     test 'epigraph quote block with attribution converted to DocBook' do
  441       input = <<~'EOS'
  442       [.epigraph, Famous Person, Famous Book (1999)]
  443       ____
  444       A famous quote.
  445       ____
  446       EOS
  447       output = convert_string input, backend: :docbook
  448       assert_css 'epigraph', output, 1
  449       assert_css 'epigraph > simpara', output, 1
  450       assert_css 'epigraph > attribution', output, 1
  451       assert_css 'epigraph > attribution > citetitle', output, 1
  452       assert_xpath '//epigraph/attribution/citetitle[text()="Famous Book (1999)"]', output, 1
  453       attribution = xmlnodes_at_xpath '//epigraph/attribution', output, 1
  454       author = attribution.children.first
  455       assert_equal 'Famous Person', author.text.strip
  456     end
  457 
  458     test 'markdown-style quote block with single paragraph and no attribution' do
  459       input = <<~'EOS'
  460       > A famous quote.
  461       > Some more inspiring words.
  462       EOS
  463       output = convert_string input
  464       assert_css '.quoteblock', output, 1
  465       assert_css '.quoteblock > blockquote', output, 1
  466       assert_css '.quoteblock > blockquote > .paragraph > p', output, 1
  467       assert_css '.quoteblock > .attribution', output, 0
  468       assert_xpath %(//*[@class="quoteblock"]//p[text()="A famous quote.\nSome more inspiring words."]), output, 1
  469     end
  470 
  471     test 'lazy markdown-style quote block with single paragraph and no attribution' do
  472       input = <<~'EOS'
  473       > A famous quote.
  474       Some more inspiring words.
  475       EOS
  476       output = convert_string input
  477       assert_css '.quoteblock', output, 1
  478       assert_css '.quoteblock > blockquote', output, 1
  479       assert_css '.quoteblock > blockquote > .paragraph > p', output, 1
  480       assert_css '.quoteblock > .attribution', output, 0
  481       assert_xpath %(//*[@class="quoteblock"]//p[text()="A famous quote.\nSome more inspiring words."]), output, 1
  482     end
  483 
  484     test 'markdown-style quote block with multiple paragraphs and no attribution' do
  485       input = <<~'EOS'
  486       > A famous quote.
  487       >
  488       > Some more inspiring words.
  489       EOS
  490       output = convert_string input
  491       assert_css '.quoteblock', output, 1
  492       assert_css '.quoteblock > blockquote', output, 1
  493       assert_css '.quoteblock > blockquote > .paragraph > p', output, 2
  494       assert_css '.quoteblock > .attribution', output, 0
  495       assert_xpath %((//*[@class="quoteblock"]//p)[1][text()="A famous quote."]), output, 1
  496       assert_xpath %((//*[@class="quoteblock"]//p)[2][text()="Some more inspiring words."]), output, 1
  497     end
  498 
  499     test 'markdown-style quote block with multiple blocks and no attribution' do
  500       input = <<~'EOS'
  501       > A famous quote.
  502       >
  503       > NOTE: Some more inspiring words.
  504       EOS
  505       output = convert_string input
  506       assert_css '.quoteblock', output, 1
  507       assert_css '.quoteblock > blockquote', output, 1
  508       assert_css '.quoteblock > blockquote > .paragraph > p', output, 1
  509       assert_css '.quoteblock > blockquote > .admonitionblock', output, 1
  510       assert_css '.quoteblock > .attribution', output, 0
  511       assert_xpath %((//*[@class="quoteblock"]//p)[1][text()="A famous quote."]), output, 1
  512       assert_xpath %((//*[@class="quoteblock"]//*[@class="admonitionblock note"]//*[@class="content"])[1][normalize-space(text())="Some more inspiring words."]), output, 1
  513     end
  514 
  515     test 'markdown-style quote block with single paragraph and attribution' do
  516       input = <<~'EOS'
  517       > A famous quote.
  518       > Some more inspiring words.
  519       > -- Famous Person, Famous Source, Volume 1 (1999)
  520       EOS
  521       output = convert_string input
  522       assert_css '.quoteblock', output, 1
  523       assert_css '.quoteblock > blockquote', output, 1
  524       assert_css '.quoteblock > blockquote > .paragraph > p', output, 1
  525       assert_xpath %(//*[@class="quoteblock"]//p[text()="A famous quote.\nSome more inspiring words."]), output, 1
  526       assert_css '.quoteblock > .attribution', output, 1
  527       assert_css '.quoteblock > .attribution > cite', output, 1
  528       assert_css '.quoteblock > .attribution > br + cite', output, 1
  529       assert_xpath '//*[@class="quoteblock"]/*[@class="attribution"]/cite[text()="Famous Source, Volume 1 (1999)"]', output, 1
  530       attribution = xmlnodes_at_xpath '//*[@class="quoteblock"]/*[@class="attribution"]', output, 1
  531       author = attribution.children.first
  532       assert_equal "#{decode_char 8212} Famous Person", author.text.strip
  533     end
  534 
  535     test 'markdown-style quote block with only attribution' do
  536       input = '> -- Anonymous'
  537       output = convert_string input
  538       assert_css '.quoteblock', output, 1
  539       assert_css '.quoteblock > blockquote', output, 1
  540       assert_css '.quoteblock > blockquote > *', output, 0
  541       assert_css '.quoteblock > .attribution', output, 1
  542       assert_xpath %(//*[@class="quoteblock"]//*[@class="attribution"][contains(text(),"Anonymous")]), output, 1
  543     end
  544 
  545     test 'should parse credit line in markdown-style quote block like positional block attributes' do
  546       input = <<~'EOS'
  547       > I hold it that a little rebellion now and then is a good thing,
  548       > and as necessary in the political world as storms in the physical.
  549       -- Thomas Jefferson, https://jeffersonpapers.princeton.edu/selected-documents/james-madison-1[The Papers of Thomas Jefferson, Volume 11]
  550       EOS
  551 
  552       output = convert_string_to_embedded input
  553       assert_css '.quoteblock', output, 1
  554       assert_css '.quoteblock cite a[href="https://jeffersonpapers.princeton.edu/selected-documents/james-madison-1"]', output, 1
  555     end
  556 
  557     test 'quoted paragraph-style quote block with attribution' do
  558       input = <<~'EOS'
  559       "A famous quote.
  560       Some more inspiring words."
  561       -- Famous Person, Famous Source, Volume 1 (1999)
  562       EOS
  563       output = convert_string input
  564       assert_css '.quoteblock', output, 1
  565       assert_css '.quoteblock > blockquote', output, 1
  566       assert_xpath %(//*[@class="quoteblock"]/blockquote[normalize-space(text())="A famous quote. Some more inspiring words."]), output, 1
  567       assert_css '.quoteblock > .attribution', output, 1
  568       assert_css '.quoteblock > .attribution > cite', output, 1
  569       assert_css '.quoteblock > .attribution > br + cite', output, 1
  570       assert_xpath '//*[@class="quoteblock"]/*[@class="attribution"]/cite[text()="Famous Source, Volume 1 (1999)"]', output, 1
  571       attribution = xmlnodes_at_xpath '//*[@class="quoteblock"]/*[@class="attribution"]', output, 1
  572       author = attribution.children.first
  573       assert_equal "#{decode_char 8212} Famous Person", author.text.strip
  574     end
  575 
  576     test 'should parse credit line in quoted paragraph-style quote block like positional block attributes' do
  577       input = <<~'EOS'
  578       "I hold it that a little rebellion now and then is a good thing,
  579       and as necessary in the political world as storms in the physical."
  580       -- Thomas Jefferson, https://jeffersonpapers.princeton.edu/selected-documents/james-madison-1[The Papers of Thomas Jefferson, Volume 11]
  581       EOS
  582 
  583       output = convert_string_to_embedded input
  584       assert_css '.quoteblock', output, 1
  585       assert_css '.quoteblock cite a[href="https://jeffersonpapers.princeton.edu/selected-documents/james-madison-1"]', output, 1
  586     end
  587 
  588     test 'single-line verse block without attribution' do
  589       input = <<~'EOS'
  590       [verse]
  591       ____
  592       A famous verse.
  593       ____
  594       EOS
  595       output = convert_string input
  596       assert_css '.verseblock', output, 1
  597       assert_css '.verseblock > pre', output, 1
  598       assert_css '.verseblock > .attribution', output, 0
  599       assert_css '.verseblock p', output, 0
  600       assert_xpath '//*[@class="verseblock"]/pre[normalize-space(text())="A famous verse."]', output, 1
  601     end
  602 
  603     test 'single-line verse block with attribution' do
  604       input = <<~'EOS'
  605       [verse, Famous Poet, Famous Poem]
  606       ____
  607       A famous verse.
  608       ____
  609       EOS
  610       output = convert_string input
  611       assert_css '.verseblock', output, 1
  612       assert_css '.verseblock p', output, 0
  613       assert_css '.verseblock > pre', output, 1
  614       assert_css '.verseblock > .attribution', output, 1
  615       assert_css '.verseblock > .attribution > cite', output, 1
  616       assert_css '.verseblock > .attribution > br + cite', output, 1
  617       assert_xpath '//*[@class="verseblock"]/*[@class="attribution"]/cite[text()="Famous Poem"]', output, 1
  618       attribution = xmlnodes_at_xpath '//*[@class="verseblock"]/*[@class="attribution"]', output, 1
  619       author = attribution.children.first
  620       assert_equal "#{decode_char 8212} Famous Poet", author.text.strip
  621     end
  622 
  623     test 'single-line verse block with attribution converted to DocBook' do
  624       input = <<~'EOS'
  625       [verse, Famous Poet, Famous Poem]
  626       ____
  627       A famous verse.
  628       ____
  629       EOS
  630       output = convert_string input, backend: :docbook
  631       assert_css 'blockquote', output, 1
  632       assert_css 'blockquote simpara', output, 0
  633       assert_css 'blockquote > literallayout', output, 1
  634       assert_css 'blockquote > attribution', output, 1
  635       assert_css 'blockquote > attribution > citetitle', output, 1
  636       assert_xpath '//blockquote/attribution/citetitle[text()="Famous Poem"]', output, 1
  637       attribution = xmlnodes_at_xpath '//blockquote/attribution', output, 1
  638       author = attribution.children.first
  639       assert_equal 'Famous Poet', author.text.strip
  640     end
  641 
  642     test 'single-line epigraph verse block with attribution converted to DocBook' do
  643       input = <<~'EOS'
  644       [verse.epigraph, Famous Poet, Famous Poem]
  645       ____
  646       A famous verse.
  647       ____
  648       EOS
  649       output = convert_string input, backend: :docbook
  650       assert_css 'epigraph', output, 1
  651       assert_css 'epigraph simpara', output, 0
  652       assert_css 'epigraph > literallayout', output, 1
  653       assert_css 'epigraph > attribution', output, 1
  654       assert_css 'epigraph > attribution > citetitle', output, 1
  655       assert_xpath '//epigraph/attribution/citetitle[text()="Famous Poem"]', output, 1
  656       attribution = xmlnodes_at_xpath '//epigraph/attribution', output, 1
  657       author = attribution.children.first
  658       assert_equal 'Famous Poet', author.text.strip
  659     end
  660 
  661     test 'multi-stanza verse block' do
  662       input = <<~'EOS'
  663       [verse]
  664       ____
  665       A famous verse.
  666 
  667       Stanza two.
  668       ____
  669       EOS
  670       output = convert_string input
  671       assert_xpath '//*[@class="verseblock"]', output, 1
  672       assert_xpath '//*[@class="verseblock"]/pre', output, 1
  673       assert_xpath '//*[@class="verseblock"]//p', output, 0
  674       assert_xpath '//*[@class="verseblock"]/pre[contains(text(), "A famous verse.")]', output, 1
  675       assert_xpath '//*[@class="verseblock"]/pre[contains(text(), "Stanza two.")]', output, 1
  676     end
  677 
  678     test 'verse block does not contain block elements' do
  679       input = <<~'EOS'
  680       [verse]
  681       ____
  682       A famous verse.
  683 
  684       ....
  685       not a literal
  686       ....
  687       ____
  688       EOS
  689       output = convert_string input
  690       assert_css '.verseblock', output, 1
  691       assert_css '.verseblock > pre', output, 1
  692       assert_css '.verseblock p', output, 0
  693       assert_css '.verseblock .literalblock', output, 0
  694     end
  695 
  696     test 'verse should have normal subs' do
  697       input = <<~'EOS'
  698       [verse]
  699       ____
  700       A famous verse
  701       ____
  702       EOS
  703 
  704       verse = block_from_string input
  705       assert_equal Asciidoctor::Substitutors::NORMAL_SUBS, verse.subs
  706     end
  707 
  708     test 'should not recognize callouts in a verse' do
  709       input = <<~'EOS'
  710       [verse]
  711       ____
  712       La la la <1>
  713       ____
  714       <1> Not pointing to a callout
  715       EOS
  716 
  717       output = convert_string_to_embedded input
  718       assert_xpath '//pre[text()="La la la <1>"]', output, 1
  719       assert_message @logger, :WARN, '<stdin>: line 5: no callout found for <1>', Hash
  720     end
  721 
  722     test 'should perform normal subs on a verse block' do
  723       input = <<~'EOS'
  724       [verse]
  725       ____
  726       _GET /groups/link:#group-id[\{group-id\}]_
  727       ____
  728       EOS
  729 
  730       output = convert_string_to_embedded input
  731       assert_includes output, '<pre class="content"><em>GET /groups/<a href="#group-id">{group-id}</a></em></pre>'
  732     end
  733   end
  734 
  735   context "Example Blocks" do
  736     test "can convert example block" do
  737       input = <<~'EOS'
  738       ====
  739       This is an example of an example block.
  740 
  741       How crazy is that?
  742       ====
  743       EOS
  744 
  745       output = convert_string input
  746       assert_xpath '//*[@class="exampleblock"]//p', output, 2
  747     end
  748 
  749     test 'assigns sequential numbered caption to example block with title' do
  750       input = <<~'EOS'
  751       .Writing Docs with AsciiDoc
  752       ====
  753       Here's how you write AsciiDoc.
  754 
  755       You just write.
  756       ====
  757 
  758       .Writing Docs with DocBook
  759       ====
  760       Here's how you write DocBook.
  761 
  762       You futz with XML.
  763       ====
  764       EOS
  765 
  766       doc = document_from_string input
  767       assert_equal 1, doc.blocks[0].numeral
  768       assert_equal 1, doc.blocks[0].number
  769       assert_equal 2, doc.blocks[1].numeral
  770       assert_equal 2, doc.blocks[1].number
  771       output = doc.convert
  772       assert_xpath '(//*[@class="exampleblock"])[1]/*[@class="title"][text()="Example 1. Writing Docs with AsciiDoc"]', output, 1
  773       assert_xpath '(//*[@class="exampleblock"])[2]/*[@class="title"][text()="Example 2. Writing Docs with DocBook"]', output, 1
  774       assert_equal 2, doc.attributes['example-number']
  775     end
  776 
  777     test 'assigns sequential character caption to example block with title' do
  778       input = <<~'EOS'
  779       :example-number: @
  780 
  781       .Writing Docs with AsciiDoc
  782       ====
  783       Here's how you write AsciiDoc.
  784 
  785       You just write.
  786       ====
  787 
  788       .Writing Docs with DocBook
  789       ====
  790       Here's how you write DocBook.
  791 
  792       You futz with XML.
  793       ====
  794       EOS
  795 
  796       doc = document_from_string input
  797       assert_equal 'A', doc.blocks[0].numeral
  798       assert_equal 'A', doc.blocks[0].number
  799       assert_equal 'B', doc.blocks[1].numeral
  800       assert_equal 'B', doc.blocks[1].number
  801       output = doc.convert
  802       assert_xpath '(//*[@class="exampleblock"])[1]/*[@class="title"][text()="Example A. Writing Docs with AsciiDoc"]', output, 1
  803       assert_xpath '(//*[@class="exampleblock"])[2]/*[@class="title"][text()="Example B. Writing Docs with DocBook"]', output, 1
  804       assert_equal 'B', doc.attributes['example-number']
  805     end
  806 
  807     test "explicit caption is used if provided" do
  808       input = <<~'EOS'
  809       [caption="Look! "]
  810       .Writing Docs with AsciiDoc
  811       ====
  812       Here's how you write AsciiDoc.
  813 
  814       You just write.
  815       ====
  816       EOS
  817 
  818       doc = document_from_string input
  819       assert_nil doc.blocks[0].numeral
  820       output = doc.convert
  821       assert_xpath '(//*[@class="exampleblock"])[1]/*[@class="title"][text()="Look! Writing Docs with AsciiDoc"]', output, 1
  822       refute doc.attributes.has_key?('example-number')
  823     end
  824 
  825     test 'automatic caption can be turned off and on and modified' do
  826       input = <<~'EOS'
  827       .first example
  828       ====
  829       an example
  830       ====
  831 
  832       :caption:
  833 
  834       .second example
  835       ====
  836       another example
  837       ====
  838 
  839       :caption!:
  840       :example-caption: Exhibit
  841 
  842       .third example
  843       ====
  844       yet another example
  845       ====
  846       EOS
  847 
  848       output = convert_string_to_embedded input
  849       assert_xpath '/*[@class="exampleblock"]', output, 3
  850       assert_xpath '(/*[@class="exampleblock"])[1]/*[@class="title"][starts-with(text(), "Example ")]', output, 1
  851       assert_xpath '(/*[@class="exampleblock"])[2]/*[@class="title"][text()="second example"]', output, 1
  852       assert_xpath '(/*[@class="exampleblock"])[3]/*[@class="title"][starts-with(text(), "Exhibit ")]', output, 1
  853     end
  854 
  855     test 'should create details/summary set if collapsible option is set' do
  856       input = <<~'EOS'
  857       .Toggle Me
  858       [%collapsible]
  859       ====
  860       This content is revealed when the user clicks the words "Toggle Me".
  861       ====
  862       EOS
  863 
  864       output = convert_string_to_embedded input
  865       assert_css 'details', output, 1
  866       assert_css 'details[open]', output, 0
  867       assert_css 'details > summary.title', output, 1
  868       assert_xpath '//details/summary[text()="Toggle Me"]', output, 1
  869       assert_css 'details > summary.title + .content', output, 1
  870       assert_css 'details > summary.title + .content p', output, 1
  871     end
  872 
  873     test 'should open details/summary set if collapsible and open options are set' do
  874       input = <<~'EOS'
  875       .Toggle Me
  876       [%collapsible%open]
  877       ====
  878       This content is revealed when the user clicks the words "Toggle Me".
  879       ====
  880       EOS
  881 
  882       output = convert_string_to_embedded input
  883       assert_css 'details', output, 1
  884       assert_css 'details[open]', output, 1
  885       assert_css 'details > summary.title', output, 1
  886       assert_xpath '//details/summary[text()="Toggle Me"]', output, 1
  887     end
  888 
  889     test 'should add default summary element if collapsible option is set and title is not specifed' do
  890       input = <<~'EOS'
  891       [%collapsible]
  892       ====
  893       This content is revealed when the user clicks the words "Toggle Me".
  894       ====
  895       EOS
  896 
  897       output = convert_string_to_embedded input
  898       assert_css 'details', output, 1
  899       assert_css 'details > summary.title', output, 1
  900       assert_xpath '//details/summary[text()="Details"]', output, 1
  901     end
  902 
  903     test 'should warn if example block is not terminated' do
  904       input = <<~'EOS'
  905       outside
  906 
  907       ====
  908       inside
  909 
  910       still inside
  911 
  912       eof
  913       EOS
  914 
  915       output = convert_string_to_embedded input
  916       assert_xpath '/*[@class="exampleblock"]', output, 1
  917       assert_message @logger, :WARN, '<stdin>: line 3: unterminated example block', Hash
  918     end
  919   end
  920 
  921   context 'Admonition Blocks' do
  922     test 'caption block-level attribute should be used as caption' do
  923       input = <<~'EOS'
  924       :tip-caption: Pro Tip
  925 
  926       [caption="Pro Tip"]
  927       TIP: Override the caption of an admonition block using an attribute entry
  928       EOS
  929 
  930       output = convert_string_to_embedded input
  931       assert_xpath '/*[@class="admonitionblock tip"]//*[@class="icon"]/*[@class="title"][text()="Pro Tip"]', output, 1
  932     end
  933 
  934     test 'can override caption of admonition block using document attribute' do
  935       input = <<~'EOS'
  936       :tip-caption: Pro Tip
  937 
  938       TIP: Override the caption of an admonition block using an attribute entry
  939       EOS
  940 
  941       output = convert_string_to_embedded input
  942       assert_xpath '/*[@class="admonitionblock tip"]//*[@class="icon"]/*[@class="title"][text()="Pro Tip"]', output, 1
  943     end
  944 
  945     test 'blank caption document attribute should not blank admonition block caption' do
  946       input = <<~'EOS'
  947       :caption:
  948 
  949       TIP: Override the caption of an admonition block using an attribute entry
  950       EOS
  951 
  952       output = convert_string_to_embedded input
  953       assert_xpath '/*[@class="admonitionblock tip"]//*[@class="icon"]/*[@class="title"][text()="Tip"]', output, 1
  954     end
  955   end
  956 
  957   context "Preformatted Blocks" do
  958     test 'should separate adjacent paragraphs and listing into blocks' do
  959       input = <<~'EOS'
  960       paragraph 1
  961       ----
  962       listing content
  963       ----
  964       paragraph 2
  965       EOS
  966 
  967       output = convert_string_to_embedded input
  968       assert_xpath '/*[@class="paragraph"]/p', output, 2
  969       assert_xpath '/*[@class="listingblock"]', output, 1
  970       assert_xpath '(/*[@class="paragraph"]/following-sibling::*)[1][@class="listingblock"]', output, 1
  971     end
  972 
  973     test 'should warn if listing block is not terminated' do
  974       input = <<~'EOS'
  975       outside
  976 
  977       ----
  978       inside
  979 
  980       still inside
  981 
  982       eof
  983       EOS
  984 
  985       output = convert_string_to_embedded input
  986       assert_xpath '/*[@class="listingblock"]', output, 1
  987       assert_message @logger, :WARN, '<stdin>: line 3: unterminated listing block', Hash
  988     end
  989 
  990     test 'should not crash if listing block has no lines' do
  991       input = <<~'EOS'
  992       ----
  993       ----
  994       EOS
  995       output = convert_string_to_embedded input
  996       assert_css 'pre', output, 1
  997       assert_css 'pre:empty', output, 1
  998     end
  999 
 1000     test 'should preserve newlines in literal block' do
 1001       input = <<~'EOS'
 1002       ....
 1003       line one
 1004 
 1005       line two
 1006 
 1007       line three
 1008       ....
 1009       EOS
 1010       [true, false].each do |standalone|
 1011         output = convert_string input, standalone: standalone
 1012         assert_xpath '//pre', output, 1
 1013         assert_xpath '//pre/text()', output, 1
 1014         text = xmlnodes_at_xpath('//pre/text()', output, 1).text
 1015         lines = text.lines
 1016         assert_equal 5, lines.size
 1017         expected = "line one\n\nline two\n\nline three".lines
 1018         assert_equal expected, lines
 1019         blank_lines = output.scan(/\n[ \t]*\n/).size
 1020         assert blank_lines >= 2
 1021       end
 1022     end
 1023 
 1024     test 'should preserve newlines in listing block' do
 1025       input = <<~'EOS'
 1026       ----
 1027       line one
 1028 
 1029       line two
 1030 
 1031       line three
 1032       ----
 1033       EOS
 1034       [true, false].each do |standalone|
 1035         output = convert_string input, standalone: standalone
 1036         assert_xpath '//pre', output, 1
 1037         assert_xpath '//pre/text()', output, 1
 1038         text = xmlnodes_at_xpath('//pre/text()', output, 1).text
 1039         lines = text.lines
 1040         assert_equal 5, lines.size
 1041         expected = "line one\n\nline two\n\nline three".lines
 1042         assert_equal expected, lines
 1043         blank_lines = output.scan(/\n[ \t]*\n/).size
 1044         assert blank_lines >= 2
 1045       end
 1046     end
 1047 
 1048     test 'should preserve newlines in verse block' do
 1049       input = <<~'EOS'
 1050       --
 1051       [verse]
 1052       ____
 1053       line one
 1054 
 1055       line two
 1056 
 1057       line three
 1058       ____
 1059       --
 1060       EOS
 1061       [true, false].each do |standalone|
 1062         output = convert_string input, standalone: standalone
 1063         assert_xpath '//*[@class="verseblock"]/pre', output, 1
 1064         assert_xpath '//*[@class="verseblock"]/pre/text()', output, 1
 1065         text = xmlnodes_at_xpath('//*[@class="verseblock"]/pre/text()', output, 1).text
 1066         lines = text.lines
 1067         assert_equal 5, lines.size
 1068         expected = "line one\n\nline two\n\nline three".lines
 1069         assert_equal expected, lines
 1070         blank_lines = output.scan(/\n[ \t]*\n/).size
 1071         assert blank_lines >= 2
 1072       end
 1073     end
 1074 
 1075     test 'should strip leading and trailing blank lines when converting verbatim block' do
 1076       # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1077       input = <<~EOS
 1078       [subs="attributes"]
 1079       ....
 1080 
 1081 
 1082         first line
 1083 
 1084       last line
 1085 
 1086       {empty}
 1087 
 1088       ....
 1089       EOS
 1090 
 1091       doc = document_from_string input, standalone: false
 1092       block = doc.blocks.first
 1093       assert_equal ['', '', '  first line', '', 'last line', '', '{empty}', ''], block.lines
 1094       result = doc.convert
 1095       assert_xpath %(//pre[text()="  first line\n\nlast line"]), result, 1
 1096     end
 1097 
 1098     test 'should process block with CRLF line endings' do
 1099       input = <<~EOS
 1100       ----\r
 1101       source line 1\r
 1102       source line 2\r
 1103       ----\r
 1104       EOS
 1105 
 1106       output = convert_string_to_embedded input
 1107       assert_xpath '/*[@class="listingblock"]//pre', output, 1
 1108       assert_xpath %(/*[@class="listingblock"]//pre[text()="source line 1\nsource line 2"]), output, 1
 1109     end
 1110 
 1111     test 'should remove block indent if indent attribute is 0' do
 1112       # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1113       input = <<~EOS
 1114       [indent="0"]
 1115       ----
 1116           def names
 1117 
 1118             @names.split
 1119 
 1120           end
 1121       ----
 1122       EOS
 1123 
 1124       # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1125       expected = <<~EOS.chop
 1126       def names
 1127 
 1128         @names.split
 1129 
 1130       end
 1131       EOS
 1132 
 1133       output = convert_string_to_embedded input
 1134       assert_css 'pre', output, 1
 1135       assert_css '.listingblock pre', output, 1
 1136       result = xmlnodes_at_xpath('//pre', output, 1).text
 1137       assert_equal expected, result
 1138     end
 1139 
 1140     test 'should not remove block indent if indent attribute is -1' do
 1141       # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1142       input = <<~EOS
 1143       [indent="-1"]
 1144       ----
 1145           def names
 1146 
 1147             @names.split
 1148 
 1149           end
 1150       ----
 1151       EOS
 1152 
 1153       expected = (input.lines.slice 2, 5).join.chop
 1154 
 1155       output = convert_string_to_embedded input
 1156       assert_css 'pre', output, 1
 1157       assert_css '.listingblock pre', output, 1
 1158       result = xmlnodes_at_xpath('//pre', output, 1).text
 1159       assert_equal expected, result
 1160     end
 1161 
 1162     test 'should set block indent to value specified by indent attribute' do
 1163       # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1164       input = <<~EOS
 1165       [indent="1"]
 1166       ----
 1167           def names
 1168 
 1169             @names.split
 1170 
 1171           end
 1172       ----
 1173       EOS
 1174 
 1175       expected = (input.lines.slice 2, 5).map {|l| l.sub '    ', ' ' }.join.chop
 1176 
 1177       output = convert_string_to_embedded input
 1178       assert_css 'pre', output, 1
 1179       assert_css '.listingblock pre', output, 1
 1180       result = xmlnodes_at_xpath('//pre', output, 1).text
 1181       assert_equal expected, result
 1182     end
 1183 
 1184     test 'should set block indent to value specified by indent document attribute' do
 1185       # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1186       input = <<~EOS
 1187       :source-indent: 1
 1188 
 1189       [source,ruby]
 1190       ----
 1191           def names
 1192 
 1193             @names.split
 1194 
 1195           end
 1196       ----
 1197       EOS
 1198 
 1199       expected = (input.lines.slice 4, 5).map {|l| l.sub '    ', ' '}.join.chop
 1200 
 1201       output = convert_string_to_embedded input
 1202       assert_css 'pre', output, 1
 1203       assert_css '.listingblock pre', output, 1
 1204       result = xmlnodes_at_xpath('//pre', output, 1).text
 1205       assert_equal expected, result
 1206     end
 1207 
 1208     test 'should expand tabs if tabsize attribute is positive' do
 1209       input = <<~EOS
 1210       :tabsize: 4
 1211 
 1212       [indent=0]
 1213       ----
 1214       \tdef names
 1215 
 1216       \t\t@names.split
 1217 
 1218       \tend
 1219       ----
 1220       EOS
 1221 
 1222       # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1223       expected = <<~EOS.chop
 1224       def names
 1225 
 1226           @names.split
 1227 
 1228       end
 1229       EOS
 1230 
 1231       output = convert_string_to_embedded input
 1232       assert_css 'pre', output, 1
 1233       assert_css '.listingblock pre', output, 1
 1234       result = xmlnodes_at_xpath('//pre', output, 1).text
 1235       assert_equal expected, result
 1236     end
 1237 
 1238     test 'literal block should honor nowrap option' do
 1239       input = <<~'EOS'
 1240       [options="nowrap"]
 1241       ----
 1242       Do not wrap me if I get too long.
 1243       ----
 1244       EOS
 1245 
 1246       output = convert_string_to_embedded input
 1247       assert_css 'pre.nowrap', output, 1
 1248     end
 1249 
 1250     test 'literal block should set nowrap class if prewrap document attribute is disabled' do
 1251       input = <<~'EOS'
 1252       :prewrap!:
 1253 
 1254       ----
 1255       Do not wrap me if I get too long.
 1256       ----
 1257       EOS
 1258 
 1259       output = convert_string_to_embedded input
 1260       assert_css 'pre.nowrap', output, 1
 1261     end
 1262 
 1263     test 'literal block should honor explicit subs list' do
 1264       input = <<~'EOS'
 1265       [subs="verbatim,quotes"]
 1266       ----
 1267       Map<String, String> *attributes*; //<1>
 1268       ----
 1269       EOS
 1270 
 1271       block = block_from_string input
 1272       assert_equal [:specialcharacters,:callouts,:quotes], block.subs
 1273       output = block.convert
 1274       assert_includes output, 'Map&lt;String, String&gt; <strong>attributes</strong>;'
 1275       assert_xpath '//pre/b[text()="(1)"]', output, 1
 1276     end
 1277 
 1278     test 'should be able to disable callouts for literal block' do
 1279       input = <<~'EOS'
 1280       [subs="specialcharacters"]
 1281       ----
 1282       No callout here <1>
 1283       ----
 1284       EOS
 1285       block = block_from_string input
 1286       assert_equal [:specialcharacters], block.subs
 1287       output = block.convert
 1288       assert_xpath '//pre/b[text()="(1)"]', output, 0
 1289     end
 1290 
 1291     test 'listing block should honor explicit subs list' do
 1292       input = <<~'EOS'
 1293       [subs="specialcharacters,quotes"]
 1294       ----
 1295       $ *python functional_tests.py*
 1296       Traceback (most recent call last):
 1297         File "functional_tests.py", line 4, in <module>
 1298           assert 'Django' in browser.title
 1299       AssertionError
 1300       ----
 1301       EOS
 1302 
 1303       output = convert_string_to_embedded input
 1304 
 1305       assert_css '.listingblock pre', output, 1
 1306       assert_css '.listingblock pre strong', output, 1
 1307       assert_css '.listingblock pre em', output, 0
 1308 
 1309       input2 = <<~'EOS'
 1310       [subs="specialcharacters,macros"]
 1311       ----
 1312       $ pass:quotes[*python functional_tests.py*]
 1313       Traceback (most recent call last):
 1314         File "functional_tests.py", line 4, in <module>
 1315           assert pass:quotes['Django'] in browser.title
 1316       AssertionError
 1317       ----
 1318       EOS
 1319 
 1320       output2 = convert_string_to_embedded input2
 1321       # FIXME JRuby is adding extra trailing newlines in the second document,
 1322       # for now, rstrip is necessary
 1323       assert_equal output.rstrip, output2.rstrip
 1324     end
 1325 
 1326     test 'first character of block title may be a period if not followed by space' do
 1327       input = <<~'EOS'
 1328       ..gitignore
 1329       ----
 1330       /.bundle/
 1331       /build/
 1332       /Gemfile.lock
 1333       ----
 1334       EOS
 1335 
 1336       output = convert_string_to_embedded input
 1337       assert_xpath '//*[@class="title"][text()=".gitignore"]', output
 1338     end
 1339 
 1340     test 'listing block without title should generate screen element in docbook' do
 1341       input = <<~'EOS'
 1342       ----
 1343       listing block
 1344       ----
 1345       EOS
 1346 
 1347       output = convert_string_to_embedded input, backend: 'docbook'
 1348       assert_xpath '/screen[text()="listing block"]', output, 1
 1349     end
 1350 
 1351     test 'listing block with title should generate screen element inside formalpara element in docbook' do
 1352       input = <<~'EOS'
 1353       .title
 1354       ----
 1355       listing block
 1356       ----
 1357       EOS
 1358 
 1359       output = convert_string_to_embedded input, backend: 'docbook'
 1360       assert_xpath '/formalpara', output, 1
 1361       assert_xpath '/formalpara/title[text()="title"]', output, 1
 1362       assert_xpath '/formalpara/para/screen[text()="listing block"]', output, 1
 1363     end
 1364 
 1365     test 'listing block without an explicit style and with a second positional argument should be promoted to a source block' do
 1366       input = <<~'EOS'
 1367       [,ruby]
 1368       ----
 1369       puts 'Hello, Ruby!'
 1370       ----
 1371       EOS
 1372       matches = (document_from_string input).find_by context: :listing, style: 'source'
 1373       assert_equal 1, matches.length
 1374       assert_equal 'ruby', (matches[0].attr 'language')
 1375     end
 1376 
 1377     test 'listing block without an explicit style should be promoted to a source block if source-language is set' do
 1378       input = <<~'EOS'
 1379       :source-language: ruby
 1380 
 1381       ----
 1382       puts 'Hello, Ruby!'
 1383       ----
 1384       EOS
 1385       matches = (document_from_string input).find_by context: :listing, style: 'source'
 1386       assert_equal 1, matches.length
 1387       assert_equal 'ruby', (matches[0].attr 'language')
 1388     end
 1389 
 1390     test 'listing block with an explicit style and a second positional argument should not be promoted to a source block' do
 1391       input = <<~'EOS'
 1392       [listing,ruby]
 1393       ----
 1394       puts 'Hello, Ruby!'
 1395       ----
 1396       EOS
 1397       matches = (document_from_string input).find_by context: :listing
 1398       assert_equal 1, matches.length
 1399       assert_equal 'listing', matches[0].style
 1400       assert_nil (matches[0].attr 'language')
 1401     end
 1402 
 1403     test 'listing block with an explicit style should not be promoted to a source block if source-language is set' do
 1404       input = <<~'EOS'
 1405       :source-language: ruby
 1406 
 1407       [listing]
 1408       ----
 1409       puts 'Hello, Ruby!'
 1410       ----
 1411       EOS
 1412       matches = (document_from_string input).find_by context: :listing
 1413       assert_equal 1, matches.length
 1414       assert_equal 'listing', matches[0].style
 1415       assert_nil (matches[0].attr 'language')
 1416     end
 1417 
 1418     test 'source block with no title or language should generate screen element in docbook' do
 1419       input = <<~'EOS'
 1420       [source]
 1421       ----
 1422       source block
 1423       ----
 1424       EOS
 1425 
 1426       output = convert_string_to_embedded input, backend: 'docbook'
 1427       assert_xpath '/screen[@linenumbering="unnumbered"][text()="source block"]', output, 1
 1428     end
 1429 
 1430     test 'source block with title and no language should generate screen element inside formalpara element for docbook' do
 1431       input = <<~'EOS'
 1432       [source]
 1433       .title
 1434       ----
 1435       source block
 1436       ----
 1437       EOS
 1438 
 1439       output = convert_string_to_embedded input, backend: 'docbook'
 1440       assert_xpath '/formalpara', output, 1
 1441       assert_xpath '/formalpara/title[text()="title"]', output, 1
 1442       assert_xpath '/formalpara/para/screen[@linenumbering="unnumbered"][text()="source block"]', output, 1
 1443     end
 1444   end
 1445 
 1446   context "Open Blocks" do
 1447     test "can convert open block" do
 1448       input = <<~'EOS'
 1449       --
 1450       This is an open block.
 1451 
 1452       It can span multiple lines.
 1453       --
 1454       EOS
 1455 
 1456       output = convert_string input
 1457       assert_xpath '//*[@class="openblock"]//p', output, 2
 1458     end
 1459 
 1460     test "open block can contain another block" do
 1461       input = <<~'EOS'
 1462       --
 1463       This is an open block.
 1464 
 1465       It can span multiple lines.
 1466 
 1467       ____
 1468       It can hold great quotes like this one.
 1469       ____
 1470       --
 1471       EOS
 1472 
 1473       output = convert_string input
 1474       assert_xpath '//*[@class="openblock"]//p', output, 3
 1475       assert_xpath '//*[@class="openblock"]//*[@class="quoteblock"]', output, 1
 1476     end
 1477 
 1478     test 'should transfer id and reftext on open block to DocBook output' do
 1479       input = <<~'EOS'
 1480       Check out that <<open>>!
 1481 
 1482       [[open,Open Block]]
 1483       --
 1484       This is an open block.
 1485 
 1486       TIP: An open block can have other blocks inside of it.
 1487       --
 1488 
 1489       Back to our regularly scheduled programming.
 1490       EOS
 1491 
 1492       output = convert_string input, backend: :docbook, keep_namespaces: true
 1493       assert_css 'article:root > para[xml|id="open"]', output, 1
 1494       assert_css 'article:root > para[xreflabel="Open Block"]', output, 1
 1495       assert_css 'article:root > simpara', output, 2
 1496       assert_css 'article:root > para', output, 1
 1497       assert_css 'article:root > para > simpara', output, 1
 1498       assert_css 'article:root > para > tip', output, 1
 1499     end
 1500 
 1501     test 'should transfer id and reftext on open paragraph to DocBook output' do
 1502       input = <<~'EOS'
 1503       [open#openpara,reftext="Open Paragraph"]
 1504       This is an open paragraph.
 1505       EOS
 1506 
 1507       output = convert_string input, backend: :docbook, keep_namespaces: true
 1508       assert_css 'article:root > simpara', output, 1
 1509       assert_css 'article:root > simpara[xml|id="openpara"]', output, 1
 1510       assert_css 'article:root > simpara[xreflabel="Open Paragraph"]', output, 1
 1511     end
 1512 
 1513     test 'should transfer title on open block to DocBook output' do
 1514       input = <<~'EOS'
 1515       .Behold the open
 1516       --
 1517       This is an open block with a title.
 1518       --
 1519       EOS
 1520 
 1521       output = convert_string input, backend: :docbook
 1522       assert_css 'article > formalpara', output, 1
 1523       assert_css 'article > formalpara > *', output, 2
 1524       assert_css 'article > formalpara > title', output, 1
 1525       assert_xpath '/article/formalpara/title[text()="Behold the open"]', output, 1
 1526       assert_css 'article > formalpara > para', output, 1
 1527       assert_css 'article > formalpara > para > simpara', output, 1
 1528     end
 1529 
 1530     test 'should transfer title on open paragraph to DocBook output' do
 1531       input = <<~'EOS'
 1532       .Behold the open
 1533       This is an open paragraph with a title.
 1534       EOS
 1535 
 1536       output = convert_string input, backend: :docbook
 1537       assert_css 'article > formalpara', output, 1
 1538       assert_css 'article > formalpara > *', output, 2
 1539       assert_css 'article > formalpara > title', output, 1
 1540       assert_xpath '/article/formalpara/title[text()="Behold the open"]', output, 1
 1541       assert_css 'article > formalpara > para', output, 1
 1542       assert_css 'article > formalpara > para[text()="This is an open paragraph with a title."]', output, 1
 1543     end
 1544 
 1545     test 'should transfer role on open block to DocBook output' do
 1546       input = <<~'EOS'
 1547       [.container]
 1548       --
 1549       This is an open block.
 1550       It holds stuff.
 1551       --
 1552       EOS
 1553 
 1554       output = convert_string input, backend: :docbook
 1555       assert_css 'article > para[role=container]', output, 1
 1556       assert_css 'article > para[role=container] > simpara', output, 1
 1557     end
 1558 
 1559     test 'should transfer role on open paragraph to DocBook output' do
 1560       input = <<~'EOS'
 1561       [.container]
 1562       This is an open block.
 1563       It holds stuff.
 1564       EOS
 1565 
 1566       output = convert_string input, backend: :docbook
 1567       assert_css 'article > simpara[role=container]', output, 1
 1568     end
 1569   end
 1570 
 1571   context 'Passthrough Blocks' do
 1572     test 'can parse a passthrough block' do
 1573       input = <<~'EOS'
 1574       ++++
 1575       This is a passthrough block.
 1576       ++++
 1577       EOS
 1578 
 1579       block = block_from_string input
 1580       refute_nil block
 1581       assert_equal 1, block.lines.size
 1582       assert_equal 'This is a passthrough block.', block.source
 1583     end
 1584 
 1585     test 'does not perform subs on a passthrough block by default' do
 1586       input = <<~'EOS'
 1587       :type: passthrough
 1588 
 1589       ++++
 1590       This is a '{type}' block.
 1591       http://asciidoc.org
 1592       image:tiger.png[]
 1593       ++++
 1594       EOS
 1595 
 1596       expected = %(This is a '{type}' block.\nhttp://asciidoc.org\nimage:tiger.png[])
 1597       output = convert_string_to_embedded input
 1598       assert_equal expected, output.strip
 1599     end
 1600 
 1601     test 'does not perform subs on a passthrough block with pass style by default' do
 1602       input = <<~'EOS'
 1603       :type: passthrough
 1604 
 1605       [pass]
 1606       ++++
 1607       This is a '{type}' block.
 1608       http://asciidoc.org
 1609       image:tiger.png[]
 1610       ++++
 1611       EOS
 1612 
 1613       expected = %(This is a '{type}' block.\nhttp://asciidoc.org\nimage:tiger.png[])
 1614       output = convert_string_to_embedded input
 1615       assert_equal expected, output.strip
 1616     end
 1617 
 1618     test 'passthrough block honors explicit subs list' do
 1619       input = <<~'EOS'
 1620       :type: passthrough
 1621 
 1622       [subs="attributes,quotes,macros"]
 1623       ++++
 1624       This is a _{type}_ block.
 1625       http://asciidoc.org
 1626       ++++
 1627       EOS
 1628 
 1629       expected = %(This is a <em>passthrough</em> block.\n<a href="http://asciidoc.org" class="bare">http://asciidoc.org</a>)
 1630       output = convert_string_to_embedded input
 1631       assert_equal expected, output.strip
 1632     end
 1633 
 1634     test 'should strip leading and trailing blank lines when converting raw block' do
 1635       # NOTE cannot use single-quoted heredoc because of https://github.com/jruby/jruby/issues/4260
 1636       input = <<~EOS
 1637       ++++
 1638       line above
 1639       ++++
 1640 
 1641       ++++
 1642 
 1643 
 1644         first line
 1645 
 1646       last line
 1647 
 1648 
 1649       ++++
 1650 
 1651       ++++
 1652       line below
 1653       ++++
 1654       EOS
 1655 
 1656       doc = document_from_string input, standalone: false
 1657       block = doc.blocks[1]
 1658       assert_equal ['', '', '  first line', '', 'last line', '', ''], block.lines
 1659       result = doc.convert
 1660       assert_equal "line above\n  first line\n\nlast line\nline below", result, 1
 1661     end
 1662   end
 1663 
 1664   context 'Math blocks' do
 1665     test 'should not crash when converting to HTML if stem block is empty' do
 1666       input = <<~'EOS'
 1667       [stem]
 1668       ++++
 1669       ++++
 1670       EOS
 1671 
 1672       output = convert_string_to_embedded input
 1673       assert_css '.stemblock', output, 1
 1674     end
 1675 
 1676     test 'should add LaTeX math delimiters around latexmath block content' do
 1677       input = <<~'EOS'
 1678       [latexmath]
 1679       ++++
 1680       \sqrt{3x-1}+(1+x)^2 < y
 1681       ++++
 1682       EOS
 1683 
 1684       output = convert_string_to_embedded input
 1685       assert_css '.stemblock', output, 1
 1686       nodes = xmlnodes_at_xpath '//*[@class="content"]/child::text()', output
 1687       assert_equal '\[\sqrt{3x-1}+(1+x)^2 &lt; y\]', nodes.first.to_s.strip
 1688     end
 1689 
 1690     test 'should not add LaTeX math delimiters around latexmath block content if already present' do
 1691       input = <<~'EOS'
 1692       [latexmath]
 1693       ++++
 1694       \[\sqrt{3x-1}+(1+x)^2 < y\]
 1695       ++++
 1696       EOS
 1697 
 1698       output = convert_string_to_embedded input
 1699       assert_css '.stemblock', output, 1
 1700       nodes = xmlnodes_at_xpath '//*[@class="content"]/child::text()', output
 1701       assert_equal '\[\sqrt{3x-1}+(1+x)^2 &lt; y\]', nodes.first.to_s.strip
 1702     end
 1703 
 1704     test 'should display latexmath block in alt of equation in DocBook backend' do
 1705       input = <<~'EOS'
 1706       [latexmath]
 1707       ++++
 1708       \sqrt{3x-1}+(1+x)^2 < y
 1709       ++++
 1710       EOS
 1711 
 1712       expect = <<~'EOS'
 1713       <informalequation>
 1714       <alt><![CDATA[\sqrt{3x-1}+(1+x)^2 < y]]></alt>
 1715       <mathphrase><![CDATA[\sqrt{3x-1}+(1+x)^2 < y]]></mathphrase>
 1716       </informalequation>
 1717       EOS
 1718 
 1719       output = convert_string_to_embedded input, backend: :docbook
 1720       assert_equal expect.strip, output.strip
 1721     end
 1722 
 1723     test 'should not split equation in AsciiMath block at single newline' do
 1724       input = <<~'EOS'
 1725       [asciimath]
 1726       ++++
 1727       f: bbb"N" -> bbb"N"
 1728       f: x |-> x + 1
 1729       ++++
 1730       EOS
 1731       expected = <<~'EOS'.chop
 1732       \$f: bbb"N" -&gt; bbb"N"
 1733       f: x |-&gt; x + 1\$
 1734       EOS
 1735 
 1736       output = convert_string_to_embedded input
 1737       assert_css '.stemblock', output, 1
 1738       nodes = xmlnodes_at_xpath '//*[@class="content"]', output
 1739       assert_equal expected, nodes.first.inner_html.strip
 1740     end
 1741 
 1742     test 'should split equation in AsciiMath block at escaped newline' do
 1743       input = <<~'EOS'
 1744       [asciimath]
 1745       ++++
 1746       f: bbb"N" -> bbb"N" \
 1747       f: x |-> x + 1
 1748       ++++
 1749       EOS
 1750       expected = <<~'EOS'.chop
 1751       \$f: bbb"N" -&gt; bbb"N"\$<br>
 1752       \$f: x |-&gt; x + 1\$
 1753       EOS
 1754 
 1755       output = convert_string_to_embedded input
 1756       assert_css '.stemblock', output, 1
 1757       nodes = xmlnodes_at_xpath '//*[@class="content"]', output
 1758       assert_equal expected, nodes.first.inner_html.strip
 1759     end
 1760 
 1761     test 'should split equation in AsciiMath block at sequence of escaped newlines' do
 1762       input = <<~'EOS'
 1763       [asciimath]
 1764       ++++
 1765       f: bbb"N" -> bbb"N" \
 1766       \
 1767       f: x |-> x + 1
 1768       ++++
 1769       EOS
 1770       expected = <<~'EOS'.chop
 1771       \$f: bbb"N" -&gt; bbb"N"\$<br>
 1772       <br>
 1773       \$f: x |-&gt; x + 1\$
 1774       EOS
 1775 
 1776       output = convert_string_to_embedded input
 1777       assert_css '.stemblock', output, 1
 1778       nodes = xmlnodes_at_xpath '//*[@class="content"]', output
 1779       assert_equal expected, nodes.first.inner_html.strip
 1780     end
 1781 
 1782     test 'should split equation in AsciiMath block at newline sequence and preserve breaks' do
 1783       input = <<~'EOS'
 1784       [asciimath]
 1785       ++++
 1786       f: bbb"N" -> bbb"N"
 1787 
 1788 
 1789       f: x |-> x + 1
 1790       ++++
 1791       EOS
 1792       expected = <<~'EOS'.chop
 1793       \$f: bbb"N" -&gt; bbb"N"\$<br>
 1794       <br>
 1795       <br>
 1796       \$f: x |-&gt; x + 1\$
 1797       EOS
 1798 
 1799       output = convert_string_to_embedded input
 1800       assert_css '.stemblock', output, 1
 1801       nodes = xmlnodes_at_xpath '//*[@class="content"]', output
 1802       assert_equal expected, nodes.first.inner_html.strip
 1803     end
 1804 
 1805     test 'should add AsciiMath delimiters around asciimath block content' do
 1806       input = <<~'EOS'
 1807       [asciimath]
 1808       ++++
 1809       sqrt(3x-1)+(1+x)^2 < y
 1810       ++++
 1811       EOS
 1812 
 1813       output = convert_string_to_embedded input
 1814       assert_css '.stemblock', output, 1
 1815       nodes = xmlnodes_at_xpath '//*[@class="content"]/child::text()', output
 1816       assert_equal '\$sqrt(3x-1)+(1+x)^2 &lt; y\$', nodes.first.to_s.strip
 1817     end
 1818 
 1819     test 'should not add AsciiMath delimiters around asciimath block content if already present' do
 1820       input = <<~'EOS'
 1821       [asciimath]
 1822       ++++
 1823       \$sqrt(3x-1)+(1+x)^2 < y\$
 1824       ++++
 1825       EOS
 1826 
 1827       output = convert_string_to_embedded input
 1828       assert_css '.stemblock', output, 1
 1829       nodes = xmlnodes_at_xpath '//*[@class="content"]/child::text()', output
 1830       assert_equal '\$sqrt(3x-1)+(1+x)^2 &lt; y\$', nodes.first.to_s.strip
 1831     end
 1832 
 1833     test 'should convert contents of asciimath block to MathML in DocBook output if asciimath gem is available' do
 1834       asciimath_available = !(Asciidoctor::Helpers.require_library 'asciimath', true, :ignore).nil?
 1835       input = <<~'EOS'
 1836       [asciimath]
 1837       ++++
 1838       x+b/(2a)<+-sqrt((b^2)/(4a^2)-c/a)
 1839       ++++
 1840 
 1841       [asciimath]
 1842       ++++
 1843       ++++
 1844       EOS
 1845 
 1846       expect = <<~'EOS'.chop
 1847       <informalequation>
 1848       <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"><mml:mi>x</mml:mi><mml:mo>+</mml:mo><mml:mfrac><mml:mi>b</mml:mi><mml:mrow><mml:mn>2</mml:mn><mml:mi>a</mml:mi></mml:mrow></mml:mfrac><mml:mo>&lt;</mml:mo><mml:mo>&#xB1;</mml:mo><mml:msqrt><mml:mrow><mml:mfrac><mml:msup><mml:mi>b</mml:mi><mml:mn>2</mml:mn></mml:msup><mml:mrow><mml:mn>4</mml:mn><mml:msup><mml:mi>a</mml:mi><mml:mn>2</mml:mn></mml:msup></mml:mrow></mml:mfrac><mml:mo>&#x2212;</mml:mo><mml:mfrac><mml:mi>c</mml:mi><mml:mi>a</mml:mi></mml:mfrac></mml:mrow></mml:msqrt></mml:math>
 1849       </informalequation>
 1850       <informalequation>
 1851       <mml:math xmlns:mml="http://www.w3.org/1998/Math/MathML"></mml:math>
 1852       </informalequation>
 1853       EOS
 1854 
 1855       using_memory_logger do |logger|
 1856         doc = document_from_string input, backend: :docbook, standalone: false
 1857         actual = doc.convert
 1858         if asciimath_available
 1859           assert_equal expect, actual.strip
 1860           assert_equal :loaded, doc.converter.instance_variable_get(:@asciimath_status)
 1861         else
 1862           assert_message logger, :WARN, 'optional gem \'asciimath\' is not available. Functionality disabled.'
 1863           assert_equal :unavailable, doc.converter.instance_variable_get(:@asciimath_status)
 1864         end
 1865       end
 1866     end
 1867 
 1868     test 'should output title for latexmath block if defined' do
 1869       input = <<~'EOS'
 1870       .The Lorenz Equations
 1871       [latexmath]
 1872       ++++
 1873       \begin{aligned}
 1874       \dot{x} & = \sigma(y-x) \\
 1875       \dot{y} & = \rho x - y - xz \\
 1876       \dot{z} & = -\beta z + xy
 1877       \end{aligned}
 1878       ++++
 1879       EOS
 1880 
 1881       output = convert_string_to_embedded input
 1882       assert_css '.stemblock', output, 1
 1883       assert_css '.stemblock .title', output, 1
 1884       assert_xpath '//*[@class="title"][text()="The Lorenz Equations"]', output, 1
 1885     end
 1886 
 1887     test 'should output title for asciimath block if defined' do
 1888       input = <<~'EOS'
 1889       .Simple fraction
 1890       [asciimath]
 1891       ++++
 1892       a//b
 1893       ++++
 1894       EOS
 1895 
 1896       output = convert_string_to_embedded input
 1897       assert_css '.stemblock', output, 1
 1898       assert_css '.stemblock .title', output, 1
 1899       assert_xpath '//*[@class="title"][text()="Simple fraction"]', output, 1
 1900     end
 1901 
 1902     test 'should add AsciiMath delimiters around stem block content if stem attribute is asciimath, empty, or not set' do
 1903       input = <<~'EOS'
 1904       [stem]
 1905       ++++
 1906       sqrt(3x-1)+(1+x)^2 < y
 1907       ++++
 1908       EOS
 1909 
 1910       [
 1911         {},
 1912         { 'stem' => '' },
 1913         { 'stem' => 'asciimath' },
 1914         { 'stem' => 'bogus' },
 1915       ].each do |attributes|
 1916         output = convert_string_to_embedded input, attributes: attributes
 1917         assert_css '.stemblock', output, 1
 1918         nodes = xmlnodes_at_xpath '//*[@class="content"]/child::text()', output
 1919         assert_equal '\$sqrt(3x-1)+(1+x)^2 &lt; y\$', nodes.first.to_s.strip
 1920       end
 1921     end
 1922 
 1923     test 'should add LaTeX math delimiters around stem block content if stem attribute is latexmath, latex, or tex' do
 1924       input = <<~'EOS'
 1925       [stem]
 1926       ++++
 1927       \sqrt{3x-1}+(1+x)^2 < y
 1928       ++++
 1929       EOS
 1930 
 1931       [
 1932         { 'stem' => 'latexmath' },
 1933         { 'stem' => 'latex' },
 1934         { 'stem' => 'tex' },
 1935       ].each do |attributes|
 1936         output = convert_string_to_embedded input, attributes: attributes
 1937         assert_css '.stemblock', output, 1
 1938         nodes = xmlnodes_at_xpath '//*[@class="content"]/child::text()', output
 1939         assert_equal '\[\sqrt{3x-1}+(1+x)^2 &lt; y\]', nodes.first.to_s.strip
 1940       end
 1941     end
 1942 
 1943     test 'should allow stem style to be set using second positional argument of block attributes' do
 1944       input = <<~'EOS'
 1945       :stem: latexmath
 1946 
 1947       [stem,asciimath]
 1948       ++++
 1949       sqrt(3x-1)+(1+x)^2 < y
 1950       ++++
 1951       EOS
 1952 
 1953       doc = document_from_string input
 1954       stemblock = doc.blocks[0]
 1955       assert_equal :stem, stemblock.context
 1956       assert_equal 'asciimath', stemblock.attributes['style']
 1957       output = doc.convert standalone: false
 1958       assert_css '.stemblock', output, 1
 1959       nodes = xmlnodes_at_xpath '//*[@class="content"]/child::text()', output
 1960       assert_equal '\$sqrt(3x-1)+(1+x)^2 &lt; y\$', nodes.first.to_s.strip
 1961     end
 1962   end
 1963 
 1964   context 'Custom Blocks' do
 1965     test 'should not warn if block style is unknown' do
 1966       input = <<~'EOS'
 1967       [foo]
 1968       --
 1969       bar
 1970       --
 1971       EOS
 1972       convert_string_to_embedded input
 1973       assert_empty @logger.messages
 1974     end
 1975 
 1976     test 'should log debug message if block style is unknown and debug level is enabled' do
 1977       input = <<~'EOS'
 1978       [foo]
 1979       --
 1980       bar
 1981       --
 1982       EOS
 1983       using_memory_logger Logger::Severity::DEBUG do |logger|
 1984         convert_string_to_embedded input
 1985         assert_message logger, :DEBUG, '<stdin>: line 2: unknown style for open block: foo', Hash
 1986       end
 1987     end
 1988   end
 1989 
 1990   context 'Metadata' do
 1991     test 'block title above section gets carried over to first block in section' do
 1992       input = <<~'EOS'
 1993       .Title
 1994       == Section
 1995 
 1996       paragraph
 1997       EOS
 1998       output = convert_string input
 1999       assert_xpath '//*[@class="paragraph"]', output, 1
 2000       assert_xpath '//*[@class="paragraph"]/*[@class="title"][text()="Title"]', output, 1
 2001       assert_xpath '//*[@class="paragraph"]/p[text()="paragraph"]', output, 1
 2002     end
 2003 
 2004     test 'block title above document title demotes document title to a section title' do
 2005       input = <<~'EOS'
 2006       .Block title
 2007       = Section Title
 2008 
 2009       section paragraph
 2010       EOS
 2011       output = convert_string input
 2012       assert_xpath '//*[@id="header"]/*', output, 0
 2013       assert_xpath '//*[@id="preamble"]/*', output, 0
 2014       assert_xpath '//*[@id="content"]/h1[text()="Section Title"]', output, 1
 2015       assert_xpath '//*[@class="paragraph"]', output, 1
 2016       assert_xpath '//*[@class="paragraph"]/*[@class="title"][text()="Block title"]', output, 1
 2017       assert_message @logger, :ERROR, '<stdin>: line 2: level 0 sections can only be used when doctype is book', Hash
 2018     end
 2019 
 2020     test 'block title above document title gets carried over to first block in first section if no preamble' do
 2021       input = <<~'EOS'
 2022       :doctype: book
 2023       .Block title
 2024       = Document Title
 2025 
 2026       == First Section
 2027 
 2028       paragraph
 2029       EOS
 2030       doc = document_from_string input
 2031       # NOTE block title demotes document title to level-0 section
 2032       refute doc.header?
 2033       output = doc.convert
 2034       assert_xpath '//*[@class="sect1"]//*[@class="paragraph"]/*[@class="title"][text()="Block title"]', output, 1
 2035     end
 2036 
 2037     test 'should apply substitutions to a block title in normal order' do
 2038       input = <<~'EOS'
 2039       .{link-url}[{link-text}]{tm}
 2040       The one and only!
 2041       EOS
 2042 
 2043       output = convert_string_to_embedded input, attributes: {
 2044         'link-url' => 'https://acme.com',
 2045         'link-text' => 'ACME',
 2046         'tm' => '(TM)',
 2047       }
 2048       assert_css '.title', output, 1
 2049       assert_css '.title a[href="https://acme.com"]', output, 1
 2050       assert_xpath %(//*[@class="title"][contains(text(),"#{decode_char 8482}")]), output, 1
 2051     end
 2052 
 2053     test 'empty attribute list should not appear in output' do
 2054       input = <<~'EOS'
 2055       []
 2056       --
 2057       Block content
 2058       --
 2059       EOS
 2060 
 2061       output = convert_string_to_embedded input
 2062       assert_includes output, 'Block content'
 2063       refute_includes output, '[]'
 2064     end
 2065 
 2066     test 'empty block anchor should not appear in output' do
 2067       input = <<~'EOS'
 2068       [[]]
 2069       --
 2070       Block content
 2071       --
 2072       EOS
 2073 
 2074       output = convert_string_to_embedded input
 2075       assert_includes output, 'Block content'
 2076       refute_includes output, '[[]]'
 2077     end
 2078   end
 2079 
 2080   context 'Images' do
 2081     test 'can convert block image with alt text defined in macro' do
 2082       input = 'image::images/tiger.png[Tiger]'
 2083       output = convert_string_to_embedded input
 2084       assert_xpath '/*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
 2085     end
 2086 
 2087     test 'converts SVG image using img element by default' do
 2088       input = 'image::tiger.svg[Tiger]'
 2089       output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SERVER
 2090       assert_xpath '/*[@class="imageblock"]//img[@src="tiger.svg"][@alt="Tiger"]', output, 1
 2091     end
 2092 
 2093     test 'converts interactive SVG image with alt text using object element' do
 2094       input = <<~'EOS'
 2095       :imagesdir: images
 2096 
 2097       [%interactive]
 2098       image::tiger.svg[Tiger,100]
 2099       EOS
 2100 
 2101       output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SERVER
 2102       assert_xpath '/*[@class="imageblock"]//object[@type="image/svg+xml"][@data="images/tiger.svg"][@width="100"]/span[@class="alt"][text()="Tiger"]', output, 1
 2103     end
 2104 
 2105     test 'converts SVG image with alt text using img element when safe mode is secure' do
 2106       input = <<~'EOS'
 2107       [%interactive]
 2108       image::images/tiger.svg[Tiger,100]
 2109       EOS
 2110 
 2111       output = convert_string_to_embedded input
 2112       assert_xpath '/*[@class="imageblock"]//img[@src="images/tiger.svg"][@alt="Tiger"]', output, 1
 2113     end
 2114 
 2115     test 'inserts fallback image for SVG inside object element using same dimensions' do
 2116       input = <<~'EOS'
 2117       :imagesdir: images
 2118 
 2119       [%interactive]
 2120       image::tiger.svg[Tiger,100,fallback=tiger.png]
 2121       EOS
 2122 
 2123       output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SERVER
 2124       assert_xpath '/*[@class="imageblock"]//object[@type="image/svg+xml"][@data="images/tiger.svg"][@width="100"]/img[@src="images/tiger.png"][@width="100"]', output, 1
 2125     end
 2126 
 2127     test 'detects SVG image URI that contains a query string' do
 2128       input = <<~'EOS'
 2129       :imagesdir: images
 2130 
 2131       [%interactive]
 2132       image::http://example.org/tiger.svg?foo=bar[Tiger,100]
 2133       EOS
 2134 
 2135       output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SERVER
 2136       assert_xpath '/*[@class="imageblock"]//object[@type="image/svg+xml"][@data="http://example.org/tiger.svg?foo=bar"][@width="100"]/span[@class="alt"][text()="Tiger"]', output, 1
 2137     end
 2138 
 2139     test 'detects SVG image when format attribute is svg' do
 2140       input = <<~'EOS'
 2141       :imagesdir: images
 2142 
 2143       [%interactive]
 2144       image::http://example.org/tiger-svg[Tiger,100,format=svg]
 2145       EOS
 2146 
 2147       output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SERVER
 2148       assert_xpath '/*[@class="imageblock"]//object[@type="image/svg+xml"][@data="http://example.org/tiger-svg"][@width="100"]/span[@class="alt"][text()="Tiger"]', output, 1
 2149     end
 2150 
 2151     test 'converts inline SVG image using svg element' do
 2152       input = <<~'EOS'
 2153       :imagesdir: fixtures
 2154 
 2155       [%inline]
 2156       image::circle.svg[Tiger,100]
 2157       EOS
 2158 
 2159       output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docdir' => testdir }
 2160       assert_match(/<svg\s[^>]*width="100px"[^>]*>/, output, 1)
 2161       refute_match(/<svg\s[^>]*width="500px"[^>]*>/, output)
 2162       refute_match(/<svg\s[^>]*height="500px"[^>]*>/, output)
 2163       refute_match(/<svg\s[^>]*style="width:500px;height:500px"[^>]*>/, output)
 2164     end
 2165 
 2166     test 'converts inline SVG image using svg element even when data-uri is set' do
 2167       input = <<~'EOS'
 2168       :imagesdir: fixtures
 2169       :data-uri:
 2170 
 2171       [%inline]
 2172       image::circle.svg[Tiger,100]
 2173       EOS
 2174 
 2175       output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docdir' => testdir }
 2176       assert_match(/<svg\s[^>]*width="100px">/, output, 1)
 2177     end
 2178 
 2179     test 'embeds remote inline SVG when allow-uri-read is set' do
 2180       input = %(image::http://#{resolve_localhost}:9876/fixtures/circle.svg[Circle,100,100,opts=inline])
 2181       output = using_test_webserver do
 2182         convert_string_to_embedded input, safe: :safe, attributes: { 'allow-uri-read' => '' }
 2183       end
 2184 
 2185       assert_css 'svg', output, 1
 2186       assert_css 'svg[style]', output, 0
 2187       assert_css 'svg[width="100px"]', output, 1
 2188       assert_css 'svg[height="100px"]', output, 1
 2189       assert_css 'svg circle', output, 1
 2190     end
 2191 
 2192     test 'converts alt text for inline svg element if svg cannot be read' do
 2193       input = <<~'EOS'
 2194       [%inline]
 2195       image::no-such-image.svg[Alt Text]
 2196       EOS
 2197 
 2198       output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SERVER
 2199       assert_xpath '//span[@class="alt"][text()="Alt Text"]', output, 1
 2200       assert_message @logger, :WARN, '~SVG does not exist or cannot be read'
 2201     end
 2202 
 2203     test 'can convert block image with alt text defined in macro containing square bracket' do
 2204       input = 'image::images/tiger.png[A [Bengal] Tiger]'
 2205       output = convert_string input
 2206       img = xmlnodes_at_xpath '//img', output, 1
 2207       assert_equal 'A [Bengal] Tiger', img.attr('alt')
 2208     end
 2209 
 2210     test 'can convert block image with target containing spaces' do
 2211       input = 'image::images/big tiger.png[A Big Tiger]'
 2212       output = convert_string input
 2213       img = xmlnodes_at_xpath '//img', output, 1
 2214       assert_equal 'images/big%20tiger.png', img.attr('src')
 2215       assert_equal 'A Big Tiger', img.attr('alt')
 2216     end
 2217 
 2218     test 'should not recognize block image if target has leading or trailing spaces' do
 2219       [' tiger.png', 'tiger.png '].each do |target|
 2220         input = %(image::#{target}[Tiger])
 2221 
 2222         output = convert_string_to_embedded input
 2223         assert_xpath '//img', output, 0
 2224       end
 2225     end
 2226 
 2227     test 'can convert block image with alt text defined in block attribute above macro' do
 2228       input = <<~'EOS'
 2229       [Tiger]
 2230       image::images/tiger.png[]
 2231       EOS
 2232 
 2233       output = convert_string_to_embedded input
 2234       assert_xpath '/*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
 2235     end
 2236 
 2237     test 'alt text in macro overrides alt text above macro' do
 2238       input = <<~'EOS'
 2239       [Alt Text]
 2240       image::images/tiger.png[Tiger]
 2241       EOS
 2242 
 2243       output = convert_string_to_embedded input
 2244       assert_xpath '/*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
 2245     end
 2246 
 2247     test 'should substitute attribute references in alt text defined in image block macro' do
 2248       input = <<~'EOS'
 2249       :alt-text: Tiger
 2250 
 2251       image::images/tiger.png[{alt-text}]
 2252       EOS
 2253       output = convert_string_to_embedded input
 2254       assert_xpath '/*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
 2255     end
 2256 
 2257     test 'should set direction CSS class on image if float attribute is set' do
 2258       input = <<~'EOS'
 2259       [float=left]
 2260       image::images/tiger.png[Tiger]
 2261       EOS
 2262 
 2263       output = convert_string_to_embedded input
 2264       assert_css '.imageblock.left', output, 1
 2265       assert_css '.imageblock[style]', output, 0
 2266     end
 2267 
 2268     test 'should set text alignment CSS class on image if align attribute is set' do
 2269       input = <<~'EOS'
 2270       [align=center]
 2271       image::images/tiger.png[Tiger]
 2272       EOS
 2273 
 2274       output = convert_string_to_embedded input
 2275       assert_css '.imageblock.text-center', output, 1
 2276       assert_css '.imageblock[style]', output, 0
 2277     end
 2278 
 2279     test 'style attribute is dropped from image macro' do
 2280       input = <<~'EOS'
 2281       [style=value]
 2282       image::images/tiger.png[Tiger]
 2283       EOS
 2284 
 2285       doc = document_from_string input
 2286       img = doc.blocks[0]
 2287       refute(img.attributes.key? 'style')
 2288       assert_nil img.style
 2289     end
 2290 
 2291     test 'should apply specialcharacters and replacement substitutions to alt text' do
 2292       input = 'A tiger\'s "roar" is < a bear\'s "growl"'
 2293       expected = 'A tiger&#8217;s &quot;roar&quot; is &lt; a bear&#8217;s &quot;growl&quot;'
 2294       result = convert_string_to_embedded %(image::images/tiger-roar.png[#{input}])
 2295       assert_includes result, %(alt="#{expected}")
 2296     end
 2297 
 2298     test 'should not encode double quotes in alt text when converting to DocBook' do
 2299       input = 'Select "File > Open"'
 2300       expected = 'Select "File &gt; Open"'
 2301       result = convert_string_to_embedded %(image::images/open.png[#{input}]), backend: :docbook
 2302       assert_includes result, %(<phrase>#{expected}</phrase>)
 2303     end
 2304 
 2305     test 'should auto-generate alt text for block image if alt text is not specified' do
 2306       input = 'image::images/lions-and-tigers.png[]'
 2307       image = block_from_string input
 2308       assert_equal 'lions and tigers', (image.attr 'alt')
 2309       assert_equal 'lions and tigers', (image.attr 'default-alt')
 2310       output = image.convert
 2311       assert_xpath '/*[@class="imageblock"]//img[@src="images/lions-and-tigers.png"][@alt="lions and tigers"]', output, 1
 2312     end
 2313 
 2314     test "can convert block image with alt text and height and width" do
 2315       input = 'image::images/tiger.png[Tiger, 200, 300]'
 2316       output = convert_string_to_embedded input
 2317       assert_xpath '/*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"][@width="200"][@height="300"]', output, 1
 2318     end
 2319 
 2320     test "can convert block image with link" do
 2321       input = <<~'EOS'
 2322       image::images/tiger.png[Tiger, link='http://en.wikipedia.org/wiki/Tiger']
 2323       EOS
 2324 
 2325       output = convert_string_to_embedded input
 2326       assert_xpath '/*[@class="imageblock"]//a[@class="image"][@href="http://en.wikipedia.org/wiki/Tiger"]/img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
 2327     end
 2328 
 2329     test 'adds rel=noopener attribute to block image with link that targets _blank window' do
 2330       input = 'image::images/tiger.png[Tiger,link=http://en.wikipedia.org/wiki/Tiger,window=_blank]'
 2331       output = convert_string_to_embedded input
 2332       assert_xpath '/*[@class="imageblock"]//a[@class="image"][@href="http://en.wikipedia.org/wiki/Tiger"][@target="_blank"][@rel="noopener"]/img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
 2333     end
 2334 
 2335     test 'adds rel=noopener attribute to block image with link that targets name window when the noopener option is set' do
 2336       input = 'image::images/tiger.png[Tiger,link=http://en.wikipedia.org/wiki/Tiger,window=name,opts=noopener]'
 2337       output = convert_string_to_embedded input
 2338       assert_xpath '/*[@class="imageblock"]//a[@class="image"][@href="http://en.wikipedia.org/wiki/Tiger"][@target="name"][@rel="noopener"]/img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
 2339     end
 2340 
 2341     test 'adds rel=nofollow attribute to block image with a link when the nofollow option is set' do
 2342       input = 'image::images/tiger.png[Tiger,link=http://en.wikipedia.org/wiki/Tiger,opts=nofollow]'
 2343       output = convert_string_to_embedded input
 2344       assert_xpath '/*[@class="imageblock"]//a[@class="image"][@href="http://en.wikipedia.org/wiki/Tiger"][@rel="nofollow"]/img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
 2345     end
 2346 
 2347     test 'can convert block image with caption' do
 2348       input = <<~'EOS'
 2349       .The AsciiDoc Tiger
 2350       image::images/tiger.png[Tiger]
 2351       EOS
 2352 
 2353       doc = document_from_string input
 2354       assert_equal 1, doc.blocks[0].numeral
 2355       output = doc.convert
 2356       assert_xpath '//*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
 2357       assert_xpath '//*[@class="imageblock"]/*[@class="title"][text()="Figure 1. The AsciiDoc Tiger"]', output, 1
 2358       assert_equal 1, doc.attributes['figure-number']
 2359     end
 2360 
 2361     test 'can convert block image with explicit caption' do
 2362       input = <<~'EOS'
 2363       [caption="Voila! "]
 2364       .The AsciiDoc Tiger
 2365       image::images/tiger.png[Tiger]
 2366       EOS
 2367 
 2368       doc = document_from_string input
 2369       assert_nil doc.blocks[0].numeral
 2370       output = doc.convert
 2371       assert_xpath '//*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
 2372       assert_xpath '//*[@class="imageblock"]/*[@class="title"][text()="Voila! The AsciiDoc Tiger"]', output, 1
 2373       refute doc.attributes.has_key?('figure-number')
 2374     end
 2375 
 2376     test 'can align image in DocBook backend' do
 2377       input = 'image::images/sunset.jpg[Sunset,align=right]'
 2378       output = convert_string_to_embedded input, backend: :docbook
 2379       assert_xpath '//imagedata', output, 1
 2380       assert_xpath '//imagedata[@align="right"]', output, 1
 2381     end
 2382 
 2383     test 'should set content width and depth in DocBook backend if no scaling' do
 2384       input = 'image::images/sunset.jpg[Sunset,500,332]'
 2385       output = convert_string_to_embedded input, backend: :docbook
 2386       assert_xpath '//imagedata', output, 1
 2387       assert_xpath '//imagedata[@contentwidth="500"]', output, 1
 2388       assert_xpath '//imagedata[@contentdepth="332"]', output, 1
 2389       assert_xpath '//imagedata[@width]', output, 0
 2390       assert_xpath '//imagedata[@depth]', output, 0
 2391     end
 2392 
 2393     test 'can scale image in DocBook backend' do
 2394       input = 'image::images/sunset.jpg[Sunset,500,332,scale=200]'
 2395       output = convert_string_to_embedded input, backend: :docbook
 2396       assert_xpath '//imagedata', output, 1
 2397       assert_xpath '//imagedata[@scale="200"]', output, 1
 2398       assert_xpath '//imagedata[@width]', output, 0
 2399       assert_xpath '//imagedata[@depth]', output, 0
 2400       assert_xpath '//imagedata[@contentwidth]', output, 0
 2401       assert_xpath '//imagedata[@contentdepth]', output, 0
 2402     end
 2403 
 2404     test 'scale image width in DocBook backend' do
 2405       input = 'image::images/sunset.jpg[Sunset,500,332,scaledwidth=25%]'
 2406       output = convert_string_to_embedded input, backend: :docbook
 2407       assert_xpath '//imagedata', output, 1
 2408       assert_xpath '//imagedata[@width="25%"]', output, 1
 2409       assert_xpath '//imagedata[@depth]', output, 0
 2410       assert_xpath '//imagedata[@contentwidth]', output, 0
 2411       assert_xpath '//imagedata[@contentdepth]', output, 0
 2412     end
 2413 
 2414     test 'adds % to scaled width if no units given in DocBook backend ' do
 2415       input = 'image::images/sunset.jpg[Sunset,scaledwidth=25]'
 2416       output = convert_string_to_embedded input, backend: :docbook
 2417       assert_xpath '//imagedata', output, 1
 2418       assert_xpath '//imagedata[@width="25%"]', output, 1
 2419     end
 2420 
 2421     test 'keeps attribute reference unprocessed if image target is missing attribute reference and attribute-missing is skip' do
 2422       input = <<~'EOS'
 2423       :attribute-missing: skip
 2424 
 2425       image::{bogus}[]
 2426       EOS
 2427 
 2428       output = convert_string_to_embedded input
 2429       assert_css 'img[src="{bogus}"]', output, 1
 2430       assert_empty @logger
 2431     end
 2432 
 2433     test 'should not drop line if image target is missing attribute reference and attribute-missing is drop' do
 2434       input = <<~'EOS'
 2435       :attribute-missing: drop
 2436 
 2437       image::{bogus}/photo.jpg[]
 2438       EOS
 2439 
 2440       output = convert_string_to_embedded input
 2441       assert_css 'img[src="/photo.jpg"]', output, 1
 2442       assert_empty @logger
 2443     end
 2444 
 2445     test 'drops line if image target is missing attribute reference and attribute-missing is drop-line' do
 2446       input = <<~'EOS'
 2447       :attribute-missing: drop-line
 2448 
 2449       image::{bogus}[]
 2450       EOS
 2451 
 2452       output = convert_string_to_embedded input
 2453       assert_empty output.strip
 2454       assert_message @logger, :INFO, 'dropping line containing reference to missing attribute: bogus'
 2455     end
 2456 
 2457     test 'should not drop line if image target resolves to blank and attribute-missing is drop-line' do
 2458       input = <<~'EOS'
 2459       :attribute-missing: drop-line
 2460 
 2461       image::{blank}[]
 2462       EOS
 2463 
 2464       output = convert_string_to_embedded input
 2465       assert_css 'img[src=""]', output, 1
 2466       assert_empty @logger
 2467     end
 2468 
 2469     test 'dropped image does not break processing of following section and attribute-missing is drop-line' do
 2470       input = <<~'EOS'
 2471       :attribute-missing: drop-line
 2472 
 2473       image::{bogus}[]
 2474 
 2475       == Section Title
 2476       EOS
 2477 
 2478       output = convert_string_to_embedded input
 2479       assert_css 'img', output, 0
 2480       assert_css 'h2', output, 1
 2481       refute_includes output, '== Section Title'
 2482       assert_message @logger, :INFO, 'dropping line containing reference to missing attribute: bogus'
 2483     end
 2484 
 2485     test 'should pass through image that references uri' do
 2486       input = <<~'EOS'
 2487       :imagesdir: images
 2488 
 2489       image::http://asciidoc.org/images/tiger.png[Tiger]
 2490       EOS
 2491 
 2492       output = convert_string_to_embedded input
 2493       assert_xpath '/*[@class="imageblock"]//img[@src="http://asciidoc.org/images/tiger.png"][@alt="Tiger"]', output, 1
 2494     end
 2495 
 2496     test 'should encode spaces in image target if value is a URI' do
 2497       input = 'image::http://example.org/svg?digraph=digraph G { a -> b; }[diagram]'
 2498       output = convert_string_to_embedded input
 2499       assert_xpath %(/*[@class="imageblock"]//img[@src="http://example.org/svg?digraph=digraph%20G%20{%20a%20-#{decode_char 62}%20b;%20}"]), output, 1
 2500     end
 2501 
 2502     test 'can resolve image relative to imagesdir' do
 2503       input = <<~'EOS'
 2504       :imagesdir: images
 2505 
 2506       image::tiger.png[Tiger]
 2507       EOS
 2508 
 2509       output = convert_string_to_embedded input
 2510       assert_xpath '/*[@class="imageblock"]//img[@src="images/tiger.png"][@alt="Tiger"]', output, 1
 2511     end
 2512 
 2513     test 'embeds base64-encoded data uri for image when data-uri attribute is set' do
 2514       input = <<~'EOS'
 2515       :data-uri:
 2516       :imagesdir: fixtures
 2517 
 2518       image::dot.gif[Dot]
 2519       EOS
 2520 
 2521       doc = document_from_string input, safe: Asciidoctor::SafeMode::SAFE, attributes: { 'docdir' => testdir }
 2522       assert_equal 'fixtures', doc.attributes['imagesdir']
 2523       output = doc.convert
 2524       assert_xpath '//img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Dot"]', output, 1
 2525     end
 2526 
 2527     test 'embeds SVG image with image/svg+xml mimetype when file extension is .svg' do
 2528       input = <<~'EOS'
 2529       :imagesdir: fixtures
 2530       :data-uri:
 2531 
 2532       image::circle.svg[Tiger,100]
 2533       EOS
 2534 
 2535       output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docdir' => testdir }
 2536       assert_xpath '//img[starts-with(@src,"data:image/svg+xml;base64,")]', output, 1
 2537     end
 2538 
 2539     test 'embeds empty base64-encoded data uri for unreadable image when data-uri attribute is set' do
 2540       input = <<~'EOS'
 2541       :data-uri:
 2542       :imagesdir: fixtures
 2543 
 2544       image::unreadable.gif[Dot]
 2545       EOS
 2546 
 2547       doc = document_from_string input, safe: Asciidoctor::SafeMode::SAFE, attributes: { 'docdir' => testdir }
 2548       assert_equal 'fixtures', doc.attributes['imagesdir']
 2549       output = doc.convert
 2550       assert_xpath '//img[@src="data:image/gif;base64,"]', output, 1
 2551       assert_message @logger, :WARN, '~image to embed not found or not readable'
 2552     end
 2553 
 2554     test 'embeds base64-encoded data uri with application/octet-stream mimetype when file extension is missing' do
 2555       input = <<~'EOS'
 2556       :data-uri:
 2557       :imagesdir: fixtures
 2558 
 2559       image::dot[Dot]
 2560       EOS
 2561 
 2562       doc = document_from_string input, safe: Asciidoctor::SafeMode::SAFE, attributes: { 'docdir' => testdir }
 2563       assert_equal 'fixtures', doc.attributes['imagesdir']
 2564       output = doc.convert
 2565       assert_xpath '//img[starts-with(@src,"data:application/octet-stream;base64,")]', output, 1
 2566     end
 2567 
 2568     test 'embeds base64-encoded data uri for remote image when data-uri attribute is set' do
 2569       input = <<~EOS
 2570       :data-uri:
 2571 
 2572       image::http://#{resolve_localhost}:9876/fixtures/dot.gif[Dot]
 2573       EOS
 2574 
 2575       output = using_test_webserver do
 2576         convert_string_to_embedded input, safe: :safe, attributes: { 'allow-uri-read' => '' }
 2577       end
 2578 
 2579       assert_xpath '//img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Dot"]', output, 1
 2580     end
 2581 
 2582     test 'embeds base64-encoded data uri for remote image when imagesdir is a URI and data-uri attribute is set' do
 2583       input = <<~EOS
 2584       :data-uri:
 2585       :imagesdir: http://#{resolve_localhost}:9876/fixtures
 2586 
 2587       image::dot.gif[Dot]
 2588       EOS
 2589 
 2590       output = using_test_webserver do
 2591         convert_string_to_embedded input, safe: :safe, attributes: { 'allow-uri-read' => '' }
 2592       end
 2593 
 2594       assert_xpath '//img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Dot"]', output, 1
 2595     end
 2596 
 2597     test 'uses remote image uri when data-uri attribute is set and image cannot be retrieved' do
 2598       image_uri = "http://#{resolve_localhost}:9876/fixtures/missing-image.gif"
 2599       input = <<~EOS
 2600       :data-uri:
 2601 
 2602       image::#{image_uri}[Missing image]
 2603       EOS
 2604 
 2605       output = using_test_webserver do
 2606         convert_string_to_embedded input, safe: :safe, attributes: { 'allow-uri-read' => '' }
 2607       end
 2608 
 2609       assert_xpath %(/*[@class="imageblock"]//img[@src="#{image_uri}"][@alt="Missing image"]), output, 1
 2610       assert_message @logger, :WARN, '~could not retrieve image data from URI'
 2611     end
 2612 
 2613     test 'uses remote image uri when data-uri attribute is set and allow-uri-read is not set' do
 2614       image_uri = "http://#{resolve_localhost}:9876/fixtures/dot.gif"
 2615       input = <<~EOS
 2616       :data-uri:
 2617 
 2618       image::#{image_uri}[Dot]
 2619       EOS
 2620 
 2621       output = using_test_webserver do
 2622         convert_string_to_embedded input, safe: :safe
 2623       end
 2624 
 2625       assert_xpath %(/*[@class="imageblock"]//img[@src="#{image_uri}"][@alt="Dot"]), output, 1
 2626     end
 2627 
 2628     test 'can handle embedded data uri images' do
 2629       input = 'image::data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=[Dot]'
 2630       output = convert_string_to_embedded input
 2631       assert_xpath '//img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Dot"]', output, 1
 2632     end
 2633 
 2634     test 'can handle embedded data uri images when data-uri attribute is set' do
 2635       input = <<~'EOS'
 2636       :data-uri:
 2637 
 2638       image::data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs=[Dot]
 2639       EOS
 2640 
 2641       output = convert_string_to_embedded input
 2642       assert_xpath '//img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Dot"]', output, 1
 2643     end
 2644 
 2645     test 'cleans reference to ancestor directories in imagesdir before reading image if safe mode level is at least SAFE' do
 2646       input = <<~'EOS'
 2647       :data-uri:
 2648       :imagesdir: ../..//fixtures/./../../fixtures
 2649 
 2650       image::dot.gif[Dot]
 2651       EOS
 2652 
 2653       doc = document_from_string input, safe: Asciidoctor::SafeMode::SAFE, attributes: { 'docdir' => testdir }
 2654       assert_equal '../..//fixtures/./../../fixtures', doc.attributes['imagesdir']
 2655       output = doc.convert
 2656       # image target resolves to fixtures/dot.gif relative to docdir (which is explicitly set to the directory of this file)
 2657       # the reference cannot fall outside of the document directory in safe mode
 2658       assert_xpath '//img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Dot"]', output, 1
 2659       assert_message @logger, :WARN, 'image has illegal reference to ancestor of jail; recovering automatically'
 2660     end
 2661 
 2662     test 'cleans reference to ancestor directories in target before reading image if safe mode level is at least SAFE' do
 2663       input = <<~'EOS'
 2664       :data-uri:
 2665       :imagesdir: ./
 2666 
 2667       image::../..//fixtures/./../../fixtures/dot.gif[Dot]
 2668       EOS
 2669 
 2670       doc = document_from_string input, safe: Asciidoctor::SafeMode::SAFE, attributes: { 'docdir' => testdir }
 2671       assert_equal './', doc.attributes['imagesdir']
 2672       output = doc.convert
 2673       # image target resolves to fixtures/dot.gif relative to docdir (which is explicitly set to the directory of this file)
 2674       # the reference cannot fall outside of the document directory in safe mode
 2675       assert_xpath '//img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Dot"]', output, 1
 2676       assert_message @logger, :WARN, 'image has illegal reference to ancestor of jail; recovering automatically'
 2677     end
 2678   end
 2679 
 2680   context 'Media' do
 2681     test 'should detect and convert video macro' do
 2682       input = 'video::cats-vs-dogs.avi[]'
 2683       output = convert_string_to_embedded input
 2684       assert_css 'video', output, 1
 2685       assert_css 'video[src="cats-vs-dogs.avi"]', output, 1
 2686     end
 2687 
 2688     test 'should detect and convert video macro with positional attributes for poster and dimensions' do
 2689       input = 'video::cats-vs-dogs.avi[cats-and-dogs.png, 200, 300]'
 2690       output = convert_string_to_embedded input
 2691       assert_css 'video', output, 1
 2692       assert_css 'video[src="cats-vs-dogs.avi"]', output, 1
 2693       assert_css 'video[poster="cats-and-dogs.png"]', output, 1
 2694       assert_css 'video[width="200"]', output, 1
 2695       assert_css 'video[height="300"]', output, 1
 2696     end
 2697 
 2698     test 'should set direction CSS class on video block if float attribute is set' do
 2699       input = 'video::cats-vs-dogs.avi[cats-and-dogs.png,float=right]'
 2700       output = convert_string_to_embedded input
 2701       assert_css 'video', output, 1
 2702       assert_css 'video[src="cats-vs-dogs.avi"]', output, 1
 2703       assert_css '.videoblock.right', output, 1
 2704     end
 2705 
 2706     test 'should set text alignment CSS class on video block if align attribute is set' do
 2707       input = 'video::cats-vs-dogs.avi[cats-and-dogs.png,align=center]'
 2708       output = convert_string_to_embedded input
 2709       assert_css 'video', output, 1
 2710       assert_css 'video[src="cats-vs-dogs.avi"]', output, 1
 2711       assert_css '.videoblock.text-center', output, 1
 2712     end
 2713 
 2714     test 'video macro should honor all options' do
 2715       input = 'video::cats-vs-dogs.avi[options="autoplay,nocontrols,loop",preload="metadata"]'
 2716       output = convert_string_to_embedded input
 2717       assert_css 'video', output, 1
 2718       assert_css 'video[autoplay]', output, 1
 2719       assert_css 'video:not([controls])', output, 1
 2720       assert_css 'video[loop]', output, 1
 2721       assert_css 'video[preload=metadata]', output, 1
 2722     end
 2723 
 2724     test 'video macro should add time range anchor with start time if start attribute is set' do
 2725       input = 'video::cats-vs-dogs.avi[start="30"]'
 2726       output = convert_string_to_embedded input
 2727       assert_css 'video', output, 1
 2728       assert_xpath '//video[@src="cats-vs-dogs.avi#t=30"]', output, 1
 2729     end
 2730 
 2731     test 'video macro should add time range anchor with end time if end attribute is set' do
 2732       input = 'video::cats-vs-dogs.avi[end="30"]'
 2733       output = convert_string_to_embedded input
 2734       assert_css 'video', output, 1
 2735       assert_xpath '//video[@src="cats-vs-dogs.avi#t=,30"]', output, 1
 2736     end
 2737 
 2738     test 'video macro should add time range anchor with start and end time if start and end attributes are set' do
 2739       input = 'video::cats-vs-dogs.avi[start="30",end="60"]'
 2740       output = convert_string_to_embedded input
 2741       assert_css 'video', output, 1
 2742       assert_xpath '//video[@src="cats-vs-dogs.avi#t=30,60"]', output, 1
 2743     end
 2744 
 2745     test 'video macro should use imagesdir attribute to resolve target and poster' do
 2746       input = <<~'EOS'
 2747       :imagesdir: assets
 2748 
 2749       video::cats-vs-dogs.avi[cats-and-dogs.png, 200, 300]
 2750       EOS
 2751 
 2752       output = convert_string_to_embedded input
 2753       assert_css 'video', output, 1
 2754       assert_css 'video[src="assets/cats-vs-dogs.avi"]', output, 1
 2755       assert_css 'video[poster="assets/cats-and-dogs.png"]', output, 1
 2756       assert_css 'video[width="200"]', output, 1
 2757       assert_css 'video[height="300"]', output, 1
 2758     end
 2759 
 2760     test 'video macro should not use imagesdir attribute to resolve target if target is a URL' do
 2761       input = <<~'EOS'
 2762       :imagesdir: assets
 2763 
 2764       video::http://example.org/videos/cats-vs-dogs.avi[]
 2765       EOS
 2766 
 2767       output = convert_string_to_embedded input
 2768       assert_css 'video', output, 1
 2769       assert_css 'video[src="http://example.org/videos/cats-vs-dogs.avi"]', output, 1
 2770     end
 2771 
 2772     test 'video macro should output custom HTML with iframe for vimeo service' do
 2773       input = 'video::67480300[vimeo, 400, 300, start=60, options="autoplay,muted"]'
 2774       output = convert_string_to_embedded input
 2775       assert_css 'video', output, 0
 2776       assert_css 'iframe', output, 1
 2777       assert_css 'iframe[src="https://player.vimeo.com/video/67480300?autoplay=1&muted=1#at=60"]', output, 1
 2778       assert_css 'iframe[width="400"]', output, 1
 2779       assert_css 'iframe[height="300"]', output, 1
 2780     end
 2781 
 2782     test 'video macro should output custom HTML with iframe for youtube service' do
 2783       input = 'video::U8GBXvdmHT4/PLg7s6cbtAD15Das5LK9mXt_g59DLWxKUe[youtube, 640, 360, start=60, options="autoplay,muted,modest", theme=light]'
 2784       output = convert_string_to_embedded input
 2785       assert_css 'video', output, 0
 2786       assert_css 'iframe', output, 1
 2787       assert_css 'iframe[src="https://www.youtube.com/embed/U8GBXvdmHT4?rel=0&start=60&autoplay=1&mute=1&list=PLg7s6cbtAD15Das5LK9mXt_g59DLWxKUe&modestbranding=1&theme=light"]', output, 1
 2788       assert_css 'iframe[width="640"]', output, 1
 2789       assert_css 'iframe[height="360"]', output, 1
 2790     end
 2791 
 2792     test 'video macro should output custom HTML with iframe for youtube service with dynamic playlist' do
 2793       input = 'video::SCZF6I-Rc4I,AsKGOeonbIs,HwrPhOp6-aM[youtube, 640, 360, start=60, options=autoplay]'
 2794       output = convert_string_to_embedded input
 2795       assert_css 'video', output, 0
 2796       assert_css 'iframe', output, 1
 2797       assert_css 'iframe[src="https://www.youtube.com/embed/SCZF6I-Rc4I?rel=0&start=60&autoplay=1&playlist=AsKGOeonbIs,HwrPhOp6-aM"]', output, 1
 2798       assert_css 'iframe[width="640"]', output, 1
 2799       assert_css 'iframe[height="360"]', output, 1
 2800     end
 2801 
 2802     test 'should detect and convert audio macro' do
 2803       input = 'audio::podcast.mp3[]'
 2804       output = convert_string_to_embedded input
 2805       assert_css 'audio', output, 1
 2806       assert_css 'audio[src="podcast.mp3"]', output, 1
 2807     end
 2808 
 2809     test 'audio macro should use imagesdir attribute to resolve target' do
 2810       input = <<~'EOS'
 2811       :imagesdir: assets
 2812 
 2813       audio::podcast.mp3[]
 2814       EOS
 2815 
 2816       output = convert_string_to_embedded input
 2817       assert_css 'audio', output, 1
 2818       assert_css 'audio[src="assets/podcast.mp3"]', output, 1
 2819     end
 2820 
 2821     test 'audio macro should not use imagesdir attribute to resolve target if target is a URL' do
 2822       input = <<~'EOS'
 2823       :imagesdir: assets
 2824 
 2825       video::http://example.org/podcast.mp3[]
 2826       EOS
 2827 
 2828       output = convert_string_to_embedded input
 2829       assert_css 'video', output, 1
 2830       assert_css 'video[src="http://example.org/podcast.mp3"]', output, 1
 2831     end
 2832 
 2833     test 'audio macro should honor all options' do
 2834       input = 'audio::podcast.mp3[options="autoplay,nocontrols,loop"]'
 2835       output = convert_string_to_embedded input
 2836       assert_css 'audio', output, 1
 2837       assert_css 'audio[autoplay]', output, 1
 2838       assert_css 'audio:not([controls])', output, 1
 2839       assert_css 'audio[loop]', output, 1
 2840     end
 2841 
 2842     test 'audio macro should support start and end time' do
 2843       input = 'audio::podcast.mp3[start=1,end=2]'
 2844       output = convert_string_to_embedded input
 2845       assert_css 'audio', output, 1
 2846       assert_css 'audio[controls]', output, 1
 2847       assert_css 'audio[src="podcast.mp3#t=1,2"]', output, 1
 2848     end
 2849   end
 2850 
 2851   context 'Admonition icons' do
 2852     test 'can resolve icon relative to default iconsdir' do
 2853       input = <<~'EOS'
 2854       :icons:
 2855 
 2856       [TIP]
 2857       You can use icons for admonitions by setting the 'icons' attribute.
 2858       EOS
 2859 
 2860       output = convert_string input, safe: Asciidoctor::SafeMode::SERVER
 2861       assert_xpath '//*[@class="admonitionblock tip"]//*[@class="icon"]/img[@src="./images/icons/tip.png"][@alt="Tip"]', output, 1
 2862     end
 2863 
 2864     test 'can resolve icon relative to custom iconsdir' do
 2865       input = <<~'EOS'
 2866       :icons:
 2867       :iconsdir: icons
 2868 
 2869       [TIP]
 2870       You can use icons for admonitions by setting the 'icons' attribute.
 2871       EOS
 2872 
 2873       output = convert_string input, safe: Asciidoctor::SafeMode::SERVER
 2874       assert_xpath '//*[@class="admonitionblock tip"]//*[@class="icon"]/img[@src="icons/tip.png"][@alt="Tip"]', output, 1
 2875     end
 2876 
 2877     test 'should add file extension to custom icon if not specified' do
 2878       input = <<~'EOS'
 2879       :icons: font
 2880       :iconsdir: images/icons
 2881 
 2882       [TIP,icon=a]
 2883       Override the icon of an admonition block using an attribute
 2884       EOS
 2885 
 2886       output = convert_string input, safe: Asciidoctor::SafeMode::SERVER
 2887       assert_xpath '//*[@class="admonitionblock tip"]//*[@class="icon"]/img[@src="images/icons/a.png"]', output, 1
 2888     end
 2889 
 2890     test 'should allow icontype to be specified when using built-in admonition icon' do
 2891       input = 'TIP: Set the icontype using either the icontype attribute on the icons attribute.'
 2892       [
 2893         { 'icons' => '', 'ext' => 'png' },
 2894         { 'icons' => '', 'icontype' => 'jpg', 'ext' => 'jpg' },
 2895         { 'icons' => 'jpg', 'ext' => 'jpg' },
 2896         { 'icons' => 'image', 'ext' => 'png' },
 2897       ].each do |attributes|
 2898         expected_src = %(./images/icons/tip.#{attributes.delete 'ext'})
 2899         output = convert_string input, attributes: attributes
 2900         assert_xpath %(//*[@class="admonitionblock tip"]//*[@class="icon"]/img[@src="#{expected_src}"]), output, 1
 2901       end
 2902     end
 2903 
 2904     test 'should allow icontype to be specified when using custom admonition icon' do
 2905       input = <<~'EOS'
 2906       [TIP,icon=hint]
 2907       Set the icontype using either the icontype attribute on the icons attribute.
 2908       EOS
 2909       [
 2910         { 'icons' => '', 'ext' => 'png' },
 2911         { 'icons' => '', 'icontype' => 'jpg', 'ext' => 'jpg' },
 2912         { 'icons' => 'jpg', 'ext' => 'jpg' },
 2913         { 'icons' => 'image', 'ext' => 'png' },
 2914       ].each do |attributes|
 2915         expected_src = %(./images/icons/hint.#{attributes.delete 'ext'})
 2916         output = convert_string input, attributes: attributes
 2917         assert_xpath %(//*[@class="admonitionblock tip"]//*[@class="icon"]/img[@src="#{expected_src}"]), output, 1
 2918       end
 2919     end
 2920 
 2921     test 'embeds base64-encoded data uri of icon when data-uri attribute is set and safe mode level is less than SECURE' do
 2922       input = <<~'EOS'
 2923       :icons:
 2924       :iconsdir: fixtures
 2925       :icontype: gif
 2926       :data-uri:
 2927 
 2928       [TIP]
 2929       You can use icons for admonitions by setting the 'icons' attribute.
 2930       EOS
 2931 
 2932       output = convert_string input, safe: Asciidoctor::SafeMode::SAFE, attributes: { 'docdir' => testdir }
 2933       assert_xpath '//*[@class="admonitionblock tip"]//*[@class="icon"]/img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Tip"]', output, 1
 2934     end
 2935 
 2936     test 'should embed base64-encoded data uri of custom icon when data-uri attribute is set' do
 2937       input = <<~'EOS'
 2938       :icons:
 2939       :iconsdir: fixtures
 2940       :icontype: gif
 2941       :data-uri:
 2942 
 2943       [TIP,icon=tip]
 2944       You can set a custom icon using the icon attribute on the block.
 2945       EOS
 2946 
 2947       output = convert_string input, safe: Asciidoctor::SafeMode::SAFE, attributes: { 'docdir' => testdir }
 2948       assert_xpath '//*[@class="admonitionblock tip"]//*[@class="icon"]/img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Tip"]', output, 1
 2949     end
 2950 
 2951     test 'does not embed base64-encoded data uri of icon when safe mode level is SECURE or greater' do
 2952       input = <<~'EOS'
 2953       :icons:
 2954       :iconsdir: fixtures
 2955       :icontype: gif
 2956       :data-uri:
 2957 
 2958       [TIP]
 2959       You can use icons for admonitions by setting the 'icons' attribute.
 2960       EOS
 2961 
 2962       output = convert_string input, attributes: { 'icons' => '' }
 2963       assert_xpath '//*[@class="admonitionblock tip"]//*[@class="icon"]/img[@src="fixtures/tip.gif"][@alt="Tip"]', output, 1
 2964     end
 2965 
 2966     test 'cleans reference to ancestor directories before reading icon if safe mode level is at least SAFE' do
 2967       input = <<~'EOS'
 2968       :icons:
 2969       :iconsdir: ../fixtures
 2970       :icontype: gif
 2971       :data-uri:
 2972 
 2973       [TIP]
 2974       You can use icons for admonitions by setting the 'icons' attribute.
 2975       EOS
 2976 
 2977       output = convert_string input, safe: Asciidoctor::SafeMode::SAFE, attributes: { 'docdir' => testdir }
 2978       assert_xpath '//*[@class="admonitionblock tip"]//*[@class="icon"]/img[@src="data:image/gif;base64,R0lGODlhAQABAIAAAAUEBAAAACwAAAAAAQABAAACAkQBADs="][@alt="Tip"]', output, 1
 2979       assert_message @logger, :WARN, 'image has illegal reference to ancestor of jail; recovering automatically'
 2980     end
 2981 
 2982     test 'should import Font Awesome and use font-based icons when value of icons attribute is font' do
 2983       input = <<~'EOS'
 2984       :icons: font
 2985 
 2986       [TIP]
 2987       You can use icons for admonitions by setting the 'icons' attribute.
 2988       EOS
 2989 
 2990       output = convert_string input, safe: Asciidoctor::SafeMode::SERVER
 2991       assert_css %(html > head > link[rel="stylesheet"][href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/#{Asciidoctor::FONT_AWESOME_VERSION}/css/font-awesome.min.css"]), output, 1
 2992       assert_xpath '//*[@class="admonitionblock tip"]//*[@class="icon"]/i[@class="fa icon-tip"]', output, 1
 2993     end
 2994 
 2995     test 'font-based icon should not override icon specified on admonition' do
 2996       input = <<~'EOS'
 2997       :icons: font
 2998       :iconsdir: images/icons
 2999 
 3000       [TIP,icon=a.png]
 3001       Override the icon of an admonition block using an attribute
 3002       EOS
 3003 
 3004       output = convert_string input, safe: Asciidoctor::SafeMode::SERVER
 3005       assert_xpath '//*[@class="admonitionblock tip"]//*[@class="icon"]/i[@class="fa icon-tip"]', output, 0
 3006       assert_xpath '//*[@class="admonitionblock tip"]//*[@class="icon"]/img[@src="images/icons/a.png"]', output, 1
 3007     end
 3008 
 3009     test 'should use http uri scheme for assets when asset-uri-scheme is http' do
 3010       input = <<~'EOS'
 3011       :asset-uri-scheme: http
 3012       :icons: font
 3013       :source-highlighter: highlightjs
 3014 
 3015       TIP: You can control the URI scheme used for assets with the asset-uri-scheme attribute
 3016 
 3017       [source,ruby]
 3018       puts "AsciiDoc, FTW!"
 3019       EOS
 3020 
 3021       output = convert_string input, safe: Asciidoctor::SafeMode::SAFE
 3022       assert_css %(html > head > link[rel="stylesheet"][href="http://cdnjs.cloudflare.com/ajax/libs/font-awesome/#{Asciidoctor::FONT_AWESOME_VERSION}/css/font-awesome.min.css"]), output, 1
 3023       assert_css %(html > body > script[src="http://cdnjs.cloudflare.com/ajax/libs/highlight.js/#{Asciidoctor::HIGHLIGHT_JS_VERSION}/highlight.min.js"]), output, 1
 3024     end
 3025 
 3026     test 'should use no uri scheme for assets when asset-uri-scheme is blank' do
 3027       input = <<~'EOS'
 3028       :asset-uri-scheme:
 3029       :icons: font
 3030       :source-highlighter: highlightjs
 3031 
 3032       TIP: You can control the URI scheme used for assets with the asset-uri-scheme attribute
 3033 
 3034       [source,ruby]
 3035       puts "AsciiDoc, FTW!"
 3036       EOS
 3037 
 3038       output = convert_string input, safe: Asciidoctor::SafeMode::SAFE
 3039       assert_css %(html > head > link[rel="stylesheet"][href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/#{Asciidoctor::FONT_AWESOME_VERSION}/css/font-awesome.min.css"]), output, 1
 3040       assert_css %(html > body > script[src="//cdnjs.cloudflare.com/ajax/libs/highlight.js/#{Asciidoctor::HIGHLIGHT_JS_VERSION}/highlight.min.js"]), output, 1
 3041     end
 3042   end
 3043 
 3044   context 'Image paths' do
 3045     test 'restricts access to ancestor directories when safe mode level is at least SAFE' do
 3046       input = 'image::asciidoctor.png[Asciidoctor]'
 3047       basedir = testdir
 3048       block = block_from_string input, attributes: { 'docdir' => basedir }
 3049       doc = block.document
 3050       assert doc.safe >= Asciidoctor::SafeMode::SAFE
 3051 
 3052       assert_equal File.join(basedir, 'images'), block.normalize_asset_path('images')
 3053       assert_equal File.join(basedir, 'etc/images'), block.normalize_asset_path("#{disk_root}etc/images")
 3054       assert_equal File.join(basedir, 'images'), block.normalize_asset_path('../../images')
 3055     end
 3056 
 3057     test 'does not restrict access to ancestor directories when safe mode is disabled' do
 3058       input = 'image::asciidoctor.png[Asciidoctor]'
 3059       basedir = testdir
 3060       block = block_from_string input, safe: Asciidoctor::SafeMode::UNSAFE, attributes: { 'docdir' => basedir }
 3061       doc = block.document
 3062       assert doc.safe == Asciidoctor::SafeMode::UNSAFE
 3063 
 3064       assert_equal File.join(basedir, 'images'), block.normalize_asset_path('images')
 3065       absolute_path = "#{disk_root}etc/images"
 3066       assert_equal absolute_path, block.normalize_asset_path(absolute_path)
 3067       assert_equal File.expand_path(File.join(basedir, '../../images')), block.normalize_asset_path('../../images')
 3068     end
 3069 
 3070   end
 3071 
 3072   context 'Source code' do
 3073     test 'should support fenced code block using backticks' do
 3074       input = <<~'EOS'
 3075       ```
 3076       puts "Hello, World!"
 3077       ```
 3078       EOS
 3079 
 3080       output = convert_string_to_embedded input
 3081       assert_css '.listingblock', output, 1
 3082       assert_css '.listingblock pre code', output, 1
 3083       assert_css '.listingblock pre code:not([class])', output, 1
 3084     end
 3085 
 3086     test 'should not recognize fenced code blocks with more than three delimiters' do
 3087       input = <<~'EOS'
 3088       ````ruby
 3089       puts "Hello, World!"
 3090       ````
 3091 
 3092       ~~~~ javascript
 3093       alert("Hello, World!")
 3094       ~~~~
 3095       EOS
 3096 
 3097       output = convert_string_to_embedded input
 3098       assert_css '.listingblock', output, 0
 3099     end
 3100 
 3101     test 'should support fenced code blocks with languages' do
 3102       input = <<~'EOS'
 3103       ```ruby
 3104       puts "Hello, World!"
 3105       ```
 3106 
 3107       ``` javascript
 3108       alert("Hello, World!")
 3109       ```
 3110       EOS
 3111 
 3112       output = convert_string_to_embedded input
 3113       assert_css '.listingblock', output, 2
 3114       assert_css '.listingblock pre code.language-ruby[data-lang=ruby]', output, 1
 3115       assert_css '.listingblock pre code.language-javascript[data-lang=javascript]', output, 1
 3116     end
 3117 
 3118     test 'should support fenced code blocks with languages and numbering' do
 3119       input = <<~'EOS'
 3120       ```ruby,numbered
 3121       puts "Hello, World!"
 3122       ```
 3123 
 3124       ``` javascript, numbered
 3125       alert("Hello, World!")
 3126       ```
 3127       EOS
 3128 
 3129       output = convert_string_to_embedded input
 3130       assert_css '.listingblock', output, 2
 3131       assert_css '.listingblock pre code.language-ruby[data-lang=ruby]', output, 1
 3132       assert_css '.listingblock pre code.language-javascript[data-lang=javascript]', output, 1
 3133     end
 3134   end
 3135 
 3136   context 'Abstract and Part Intro' do
 3137     test 'should make abstract on open block without title a quote block for article' do
 3138       input = <<~'EOS'
 3139       = Article
 3140 
 3141       [abstract]
 3142       --
 3143       This article is about stuff.
 3144 
 3145       And other stuff.
 3146       --
 3147 
 3148       == Section One
 3149 
 3150       content
 3151       EOS
 3152 
 3153       output = convert_string input
 3154       assert_css '.quoteblock', output, 1
 3155       assert_css '.quoteblock.abstract', output, 1
 3156       assert_css '#preamble .quoteblock', output, 1
 3157       assert_css '.quoteblock > blockquote', output, 1
 3158       assert_css '.quoteblock > blockquote > .paragraph', output, 2
 3159     end
 3160 
 3161     test 'should make abstract on open block with title a quote block with title for article' do
 3162       input = <<~'EOS'
 3163       = Article
 3164 
 3165       .My abstract
 3166       [abstract]
 3167       --
 3168       This article is about stuff.
 3169       --
 3170 
 3171       == Section One
 3172 
 3173       content
 3174       EOS
 3175 
 3176       output = convert_string input
 3177       assert_css '.quoteblock', output, 1
 3178       assert_css '.quoteblock.abstract', output, 1
 3179       assert_css '#preamble .quoteblock', output, 1
 3180       assert_css '.quoteblock > .title', output, 1
 3181       assert_css '.quoteblock > .title + blockquote', output, 1
 3182       assert_css '.quoteblock > .title + blockquote > .paragraph', output, 1
 3183     end
 3184 
 3185     test 'should allow abstract in document with title if doctype is book' do
 3186       input = <<~'EOS'
 3187       = Book
 3188       :doctype: book
 3189 
 3190       [abstract]
 3191       Abstract for book with title is valid
 3192       EOS
 3193 
 3194       output = convert_string input
 3195       assert_css '.abstract', output, 1
 3196     end
 3197 
 3198     test 'should not allow abstract as direct child of document if doctype is book' do
 3199       input = <<~'EOS'
 3200       :doctype: book
 3201 
 3202       [abstract]
 3203       Abstract for book without title is invalid.
 3204       EOS
 3205 
 3206       output = convert_string input
 3207       assert_css '.abstract', output, 0
 3208       assert_message @logger, :WARN, 'abstract block cannot be used in a document without a title when doctype is book. Excluding block content.'
 3209     end
 3210 
 3211     test 'should make abstract on open block without title converted to DocBook' do
 3212       input = <<~'EOS'
 3213       = Article
 3214 
 3215       [abstract]
 3216       --
 3217       This article is about stuff.
 3218 
 3219       And other stuff.
 3220       --
 3221       EOS
 3222 
 3223       output = convert_string input, backend: 'docbook'
 3224       assert_css 'abstract', output, 1
 3225       assert_css 'abstract > simpara', output, 2
 3226     end
 3227 
 3228     test 'should make abstract on open block with title converted to DocBook' do
 3229       input = <<~'EOS'
 3230       = Article
 3231 
 3232       .My abstract
 3233       [abstract]
 3234       --
 3235       This article is about stuff.
 3236       --
 3237       EOS
 3238 
 3239       output = convert_string input, backend: 'docbook'
 3240       assert_css 'abstract', output, 1
 3241       assert_css 'abstract > title', output, 1
 3242       assert_css 'abstract > title + simpara', output, 1
 3243     end
 3244 
 3245     test 'should allow abstract in document with title if doctype is book converted to DocBook' do
 3246       input = <<~'EOS'
 3247       = Book
 3248       :doctype: book
 3249 
 3250       [abstract]
 3251       Abstract for book with title is valid
 3252       EOS
 3253 
 3254       output = convert_string input, backend: 'docbook'
 3255       assert_css 'abstract', output, 1
 3256     end
 3257 
 3258     test 'should not allow abstract as direct child of document if doctype is book converted to DocBook' do
 3259       input = <<~'EOS'
 3260       :doctype: book
 3261 
 3262       [abstract]
 3263       Abstract for book is invalid.
 3264       EOS
 3265 
 3266       output = convert_string input, backend: 'docbook'
 3267       assert_css 'abstract', output, 0
 3268       assert_message @logger, :WARN, 'abstract block cannot be used in a document without a title when doctype is book. Excluding block content.'
 3269     end
 3270 
 3271     # TODO partintro shouldn't be recognized if doctype is not book, should be in proper place
 3272     test 'should accept partintro on open block without title' do
 3273       input = <<~'EOS'
 3274       = Book
 3275       :doctype: book
 3276 
 3277       = Part 1
 3278 
 3279       [partintro]
 3280       --
 3281       This is a part intro.
 3282 
 3283       It can have multiple paragraphs.
 3284       --
 3285 
 3286       == Chapter 1
 3287 
 3288       content
 3289       EOS
 3290 
 3291       output = convert_string input
 3292       assert_css '.openblock', output, 1
 3293       assert_css '.openblock.partintro', output, 1
 3294       assert_css '.openblock .title', output, 0
 3295       assert_css '.openblock .content', output, 1
 3296       assert_xpath %(//h1[@id="_part_1"]/following-sibling::*[#{contains_class(:openblock)}]), output, 1
 3297       assert_xpath %(//*[#{contains_class(:openblock)}]/*[@class="content"]/*[@class="paragraph"]), output, 2
 3298     end
 3299 
 3300     test 'should accept partintro on open block with title' do
 3301       input = <<~'EOS'
 3302       = Book
 3303       :doctype: book
 3304 
 3305       = Part 1
 3306 
 3307       .Intro title
 3308       [partintro]
 3309       --
 3310       This is a part intro with a title.
 3311       --
 3312 
 3313       == Chapter 1
 3314 
 3315       content
 3316       EOS
 3317 
 3318       output = convert_string input
 3319       assert_css '.openblock', output, 1
 3320       assert_css '.openblock.partintro', output, 1
 3321       assert_css '.openblock .title', output, 1
 3322       assert_css '.openblock .content', output, 1
 3323       assert_xpath %(//h1[@id="_part_1"]/following-sibling::*[#{contains_class(:openblock)}]), output, 1
 3324       assert_xpath %(//*[#{contains_class(:openblock)}]/*[@class="title"][text()="Intro title"]), output, 1
 3325       assert_xpath %(//*[#{contains_class(:openblock)}]/*[@class="content"]/*[@class="paragraph"]), output, 1
 3326     end
 3327 
 3328     test 'should exclude partintro if not a child of part' do
 3329       input = <<~'EOS'
 3330       = Book
 3331       :doctype: book
 3332 
 3333       [partintro]
 3334       part intro paragraph
 3335       EOS
 3336 
 3337       output = convert_string input
 3338       assert_css '.partintro', output, 0
 3339       assert_message @logger, :ERROR, 'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
 3340     end
 3341 
 3342     test 'should not allow partintro unless doctype is book' do
 3343       input = <<~'EOS'
 3344       [partintro]
 3345       part intro paragraph
 3346       EOS
 3347 
 3348       output = convert_string input
 3349       assert_css '.partintro', output, 0
 3350       assert_message @logger, :ERROR, 'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
 3351     end
 3352 
 3353     test 'should accept partintro on open block without title converted to DocBook' do
 3354       input = <<~'EOS'
 3355       = Book
 3356       :doctype: book
 3357 
 3358       = Part 1
 3359 
 3360       [partintro]
 3361       --
 3362       This is a part intro.
 3363 
 3364       It can have multiple paragraphs.
 3365       --
 3366 
 3367       == Chapter 1
 3368 
 3369       content
 3370       EOS
 3371 
 3372       output = convert_string input, backend: 'docbook'
 3373       assert_css 'partintro', output, 1
 3374       assert_css 'part[xml|id="_part_1"] > partintro', output, 1
 3375       assert_css 'partintro > simpara', output, 2
 3376     end
 3377 
 3378     test 'should accept partintro on open block with title converted to DocBook' do
 3379       input = <<~'EOS'
 3380       = Book
 3381       :doctype: book
 3382 
 3383       = Part 1
 3384 
 3385       .Intro title
 3386       [partintro]
 3387       --
 3388       This is a part intro with a title.
 3389       --
 3390 
 3391       == Chapter 1
 3392 
 3393       content
 3394       EOS
 3395 
 3396       output = convert_string input, backend: 'docbook'
 3397       assert_css 'partintro', output, 1
 3398       assert_css 'part[xml|id="_part_1"] > partintro', output, 1
 3399       assert_css 'partintro > title', output, 1
 3400       assert_css 'partintro > title + simpara', output, 1
 3401     end
 3402 
 3403     test 'should exclude partintro if not a child of part converted to DocBook' do
 3404       input = <<~'EOS'
 3405       = Book
 3406       :doctype: book
 3407 
 3408       [partintro]
 3409       part intro paragraph
 3410       EOS
 3411 
 3412       output = convert_string input, backend: 'docbook'
 3413       assert_css 'partintro', output, 0
 3414       assert_message @logger, :ERROR, 'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
 3415     end
 3416 
 3417     test 'should not allow partintro unless doctype is book converted to DocBook' do
 3418       input = <<~'EOS'
 3419       [partintro]
 3420       part intro paragraph
 3421       EOS
 3422 
 3423       output = convert_string input, backend: 'docbook'
 3424       assert_css 'partintro', output, 0
 3425       assert_message @logger, :ERROR, 'partintro block can only be used when doctype is book and must be a child of a book part. Excluding block content.'
 3426     end
 3427   end
 3428 
 3429   context 'Substitutions' do
 3430     test 'processor should not crash if subs are empty' do
 3431       input = <<~'EOS'
 3432       [subs=","]
 3433       ....
 3434       content
 3435       ....
 3436       EOS
 3437 
 3438       doc = document_from_string input
 3439       block = doc.blocks.first
 3440       assert_equal [], block.subs
 3441     end
 3442 
 3443     test 'should be able to append subs to default block substitution list' do
 3444       input = <<~'EOS'
 3445       :application: Asciidoctor
 3446 
 3447       [subs="+attributes,+macros"]
 3448       ....
 3449       {application}
 3450       ....
 3451       EOS
 3452 
 3453       doc = document_from_string input
 3454       block = doc.blocks.first
 3455       assert_equal [:specialcharacters, :attributes, :macros], block.subs
 3456     end
 3457 
 3458     test 'should be able to prepend subs to default block substitution list' do
 3459       input = <<~'EOS'
 3460       :application: Asciidoctor
 3461 
 3462       [subs="attributes+"]
 3463       ....
 3464       {application}
 3465       ....
 3466       EOS
 3467 
 3468       doc = document_from_string input
 3469       block = doc.blocks.first
 3470       assert_equal [:attributes, :specialcharacters], block.subs
 3471     end
 3472 
 3473     test 'should be able to remove subs to default block substitution list' do
 3474       input = <<~'EOS'
 3475       [subs="-quotes,-replacements"]
 3476       content
 3477       EOS
 3478 
 3479       doc = document_from_string input
 3480       block = doc.blocks.first
 3481       assert_equal [:specialcharacters, :attributes, :macros, :post_replacements], block.subs
 3482     end
 3483 
 3484     test 'should be able to prepend, append and remove subs from default block substitution list' do
 3485       input = <<~'EOS'
 3486       :application: asciidoctor
 3487 
 3488       [subs="attributes+,-verbatim,+specialcharacters,+macros"]
 3489       ....
 3490       https://{application}.org[{gt}{gt}] <1>
 3491       ....
 3492       EOS
 3493 
 3494       doc = document_from_string input, standalone: false
 3495       block = doc.blocks.first
 3496       assert_equal [:attributes, :specialcharacters, :macros], block.subs
 3497       result = doc.convert
 3498       assert_includes result, '<pre><a href="https://asciidoctor.org">&gt;&gt;</a> &lt;1&gt;</pre>'
 3499     end
 3500 
 3501     test 'should be able to set subs then modify them' do
 3502       input = <<~'EOS'
 3503       [subs="verbatim,-callouts"]
 3504       _hey now_ <1>
 3505       EOS
 3506 
 3507       doc = document_from_string input, standalone: false
 3508       block = doc.blocks.first
 3509       assert_equal [:specialcharacters], block.subs
 3510       result = doc.convert
 3511       assert_includes result, '_hey now_ &lt;1&gt;'
 3512     end
 3513   end
 3514 
 3515   context 'References' do
 3516     test 'should not recognize block anchor with illegal id characters' do
 3517       input = <<~'EOS'
 3518       [[illegal$id,Reference Text]]
 3519       ----
 3520       content
 3521       ----
 3522       EOS
 3523 
 3524       doc = document_from_string input
 3525       block = doc.blocks.first
 3526       assert_nil block.id
 3527       assert_nil(block.attr 'reftext')
 3528       refute doc.catalog[:refs].key? 'illegal$id'
 3529     end
 3530 
 3531     test 'should not recognize block anchor that starts with digit' do
 3532       input = <<~'EOS'
 3533       [[3-blind-mice]]
 3534       --
 3535       see how they run
 3536       --
 3537       EOS
 3538 
 3539       output = convert_string_to_embedded input
 3540       assert_includes output, '[[3-blind-mice]]'
 3541       assert_xpath '/*[@id=":3-blind-mice"]', output, 0
 3542     end
 3543 
 3544     test 'should recognize block anchor that starts with colon' do
 3545       input = <<~'EOS'
 3546       [[:idname]]
 3547       --
 3548       content
 3549       --
 3550       EOS
 3551 
 3552       output = convert_string_to_embedded input
 3553       assert_xpath '/*[@id=":idname"]', output, 1
 3554     end
 3555 
 3556     test 'should use specified id and reftext when registering block reference' do
 3557       input = <<~'EOS'
 3558       [[debian,Debian Install]]
 3559       .Installation on Debian
 3560       ----
 3561       $ apt-get install asciidoctor
 3562       ----
 3563       EOS
 3564 
 3565       doc = document_from_string input
 3566       ref = doc.catalog[:refs]['debian']
 3567       refute_nil ref
 3568       assert_equal 'Debian Install', ref.reftext
 3569       assert_equal 'debian', (doc.resolve_id 'Debian Install')
 3570     end
 3571 
 3572     test 'should allow square brackets in block reference text' do
 3573       input = <<~'EOS'
 3574       [[debian,[Debian] Install]]
 3575       .Installation on Debian
 3576       ----
 3577       $ apt-get install asciidoctor
 3578       ----
 3579       EOS
 3580 
 3581       doc = document_from_string input
 3582       ref = doc.catalog[:refs]['debian']
 3583       refute_nil ref
 3584       assert_equal '[Debian] Install', ref.reftext
 3585       assert_equal 'debian', (doc.resolve_id '[Debian] Install')
 3586     end
 3587 
 3588     test 'should allow comma in block reference text' do
 3589       input = <<~'EOS'
 3590       [[debian, Debian, Ubuntu]]
 3591       .Installation on Debian
 3592       ----
 3593       $ apt-get install asciidoctor
 3594       ----
 3595       EOS
 3596 
 3597       doc = document_from_string input
 3598       ref = doc.catalog[:refs]['debian']
 3599       refute_nil ref
 3600       assert_equal 'Debian, Ubuntu', ref.reftext
 3601       assert_equal 'debian', (doc.resolve_id 'Debian, Ubuntu')
 3602     end
 3603 
 3604     test 'should resolve attribute reference in title using attribute defined at location of block' do
 3605       input = <<~'EOS'
 3606       = Document Title
 3607       :foo: baz
 3608 
 3609       intro paragraph. see <<free-standing>>.
 3610 
 3611       :foo: bar
 3612 
 3613       .foo is {foo}
 3614       [#formal-para]
 3615       paragraph with title
 3616 
 3617       [discrete#free-standing]
 3618       == foo is still {foo}
 3619       EOS
 3620 
 3621       doc = document_from_string input
 3622       ref = doc.catalog[:refs]['formal-para']
 3623       refute_nil ref
 3624       assert_equal 'foo is bar', ref.title
 3625       assert_equal 'formal-para', (doc.resolve_id 'foo is bar')
 3626       output = doc.convert standalone: false
 3627       assert_include '<a href="#free-standing">foo is still bar</a>', output
 3628       assert_include '<h2 id="free-standing" class="discrete">foo is still bar</h2>', output
 3629     end
 3630 
 3631     test 'should substitute attribute references in reftext when registering block reference' do
 3632       input = <<~'EOS'
 3633       :label-tiger: Tiger
 3634 
 3635       [[tiger-evolution,Evolution of the {label-tiger}]]
 3636       ****
 3637       Information about the evolution of the tiger.
 3638       ****
 3639       EOS
 3640 
 3641       doc = document_from_string input
 3642       ref = doc.catalog[:refs]['tiger-evolution']
 3643       refute_nil ref
 3644       assert_equal 'Evolution of the Tiger', ref.attributes['reftext']
 3645       assert_equal 'tiger-evolution', (doc.resolve_id 'Evolution of the Tiger')
 3646     end
 3647 
 3648     test 'should use specified reftext when registering block reference' do
 3649       input = <<~'EOS'
 3650       [[debian]]
 3651       [reftext="Debian Install"]
 3652       .Installation on Debian
 3653       ----
 3654       $ apt-get install asciidoctor
 3655       ----
 3656       EOS
 3657 
 3658       doc = document_from_string input
 3659       ref = doc.catalog[:refs]['debian']
 3660       refute_nil ref
 3661       assert_equal 'Debian Install', ref.reftext
 3662       assert_equal 'debian', (doc.resolve_id 'Debian Install')
 3663     end
 3664   end
 3665 end