"Fossies" - the Fresh Open Source Software Archive

Member "asciidoctor-2.0.10/test/api_test.rb" (1 Jun 2019, 60377 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 "api_test.rb": 2.0.9_vs_2.0.10.

    1 # frozen_string_literal: true
    2 require_relative 'test_helper'
    3 
    4 context 'API' do
    5   context 'Load' do
    6     test 'should load input file' do
    7       sample_input_path = fixture_path('sample.adoc')
    8       doc = File.open(sample_input_path, Asciidoctor::FILE_READ_MODE) {|file| Asciidoctor.load file, safe: Asciidoctor::SafeMode::SAFE }
    9       assert_equal 'Document Title', doc.doctitle
   10       assert_equal File.expand_path(sample_input_path), doc.attr('docfile')
   11       assert_equal File.expand_path(File.dirname(sample_input_path)), doc.attr('docdir')
   12       assert_equal '.adoc', doc.attr('docfilesuffix')
   13     end
   14 
   15     test 'should load input file from filename' do
   16       sample_input_path = fixture_path('sample.adoc')
   17       doc = Asciidoctor.load_file(sample_input_path, safe: Asciidoctor::SafeMode::SAFE)
   18       assert_equal 'Document Title', doc.doctitle
   19       assert_equal File.expand_path(sample_input_path), doc.attr('docfile')
   20       assert_equal File.expand_path(File.dirname(sample_input_path)), doc.attr('docdir')
   21       assert_equal '.adoc', doc.attr('docfilesuffix')
   22     end
   23 
   24     test 'should load input file from pathname' do
   25       sample_input_path = Pathname fixture_path 'sample.adoc'
   26       doc = Asciidoctor.load_file sample_input_path, safe: :safe
   27       assert_equal 'Document Title', doc.doctitle
   28       assert_equal sample_input_path.expand_path.to_s, (doc.attr 'docfile')
   29       assert_equal sample_input_path.expand_path.dirname.to_s, (doc.attr 'docdir')
   30       assert_equal '.adoc', (doc.attr 'docfilesuffix')
   31     end
   32 
   33     test 'should load input file with alternate file extension' do
   34       sample_input_path = fixture_path 'sample-alt-extension.asciidoc'
   35       doc = Asciidoctor.load_file sample_input_path, safe: :safe
   36       assert_equal 'Document Title', doc.doctitle
   37       assert_equal File.expand_path(sample_input_path), doc.attr('docfile')
   38       assert_equal File.expand_path(File.dirname(sample_input_path)), doc.attr('docdir')
   39       assert_equal '.asciidoc', doc.attr('docfilesuffix')
   40     end
   41 
   42     test 'should coerce encoding of file to UTF-8' do
   43       old_external = Encoding.default_external
   44       old_internal = Encoding.default_internal
   45       old_verbose = $VERBOSE
   46       begin
   47         $VERBOSE = nil # disable warnings since we have to modify constants
   48         input_path = fixture_path 'encoding.adoc'
   49         Encoding.default_external = Encoding.default_internal = Encoding::IBM437
   50         output = Asciidoctor.convert_file input_path, to_file: false, safe: :safe
   51         assert_equal Encoding::UTF_8, output.encoding
   52         assert_include 'Romé', output
   53       ensure
   54         Encoding.default_external = old_external
   55         Encoding.default_internal = old_internal
   56         $VERBOSE = old_verbose
   57       end
   58     end
   59 
   60     test 'should not load file with unrecognized encoding' do
   61       begin
   62         tmp_input = Tempfile.new %w(test- .adoc), encoding: Encoding::IBM437
   63         # NOTE using a character whose code differs between UTF-8 and IBM437
   64         tmp_input.write %(ƒ\n)
   65         tmp_input.close
   66         exception = assert_raises ArgumentError do
   67           Asciidoctor.load_file tmp_input.path, safe: :safe
   68         end
   69         expected_message = 'Failed to load AsciiDoc document - source is either binary or contains invalid Unicode data'
   70         assert_include expected_message, exception.message
   71       ensure
   72         tmp_input.close!
   73       end
   74     end
   75 
   76     test 'should not load invalid file' do
   77       sample_input_path = fixture_path('hello-asciidoctor.pdf')
   78       exception = assert_raises ArgumentError do
   79         Asciidoctor.load_file(sample_input_path, safe: Asciidoctor::SafeMode::SAFE)
   80       end
   81       expected_message = 'Failed to load AsciiDoc document - source is either binary or contains invalid Unicode data'
   82       assert_include expected_message, exception.message
   83       # verify we have the correct backtrace (should be at least in the first 5 lines)
   84       assert_match(/reader\.rb.*prepare_lines/, exception.backtrace[0..4].join(?\n))
   85     end
   86 
   87     test 'should convert filename that contains non-ASCII characters independent of default encodings' do
   88       old_external = Encoding.default_external
   89       old_internal = Encoding.default_internal
   90       old_verbose = $VERBOSE
   91       begin
   92         $VERBOSE = nil # disable warnings since we have to modify constants
   93         tmp_input = Tempfile.new %w(test-UTF8- .adoc)
   94         tmp_input.write %(UTF8\n)
   95         tmp_input.close
   96         Encoding.default_external = Encoding.default_internal = Encoding::IBM437
   97         tmp_output = tmp_input.path.sub '.adoc', '.html'
   98         Asciidoctor.convert_file tmp_input.path, safe: :safe, attributes: 'linkcss !copycss'
   99         assert File.exist? tmp_output
  100         output = File.binread tmp_output
  101         refute_empty output
  102         # force encoding to UTF-8 and we should see that the string is in fact UTF-8 encoded
  103         output = String.new output, encoding: Encoding::UTF_8
  104         assert_equal Encoding::UTF_8, output.encoding
  105         assert_include 'UTF8', output
  106       ensure
  107         tmp_input.close!
  108         FileUtils.rm_f tmp_output
  109         Encoding.default_external = old_external
  110         Encoding.default_internal = old_internal
  111         $VERBOSE = old_verbose
  112       end
  113     end
  114 
  115     test 'should load input IO' do
  116       input = StringIO.new <<~'EOS'
  117       Document Title
  118       ==============
  119 
  120       preamble
  121       EOS
  122       doc = Asciidoctor.load(input, safe: Asciidoctor::SafeMode::SAFE)
  123       assert_equal 'Document Title', doc.doctitle
  124       refute doc.attr?('docfile')
  125       assert_equal doc.base_dir, doc.attr('docdir')
  126     end
  127 
  128     test 'should load input string' do
  129       input = <<~'EOS'
  130       Document Title
  131       ==============
  132 
  133       preamble
  134       EOS
  135       doc = Asciidoctor.load(input, safe: Asciidoctor::SafeMode::SAFE)
  136       assert_equal 'Document Title', doc.doctitle
  137       refute doc.attr?('docfile')
  138       assert_equal doc.base_dir, doc.attr('docdir')
  139     end
  140 
  141     test 'should load input string array' do
  142       input = <<~'EOS'
  143       Document Title
  144       ==============
  145 
  146       preamble
  147       EOS
  148       doc = Asciidoctor.load(input.lines, safe: Asciidoctor::SafeMode::SAFE)
  149       assert_equal 'Document Title', doc.doctitle
  150       refute doc.attr?('docfile')
  151       assert_equal doc.base_dir, doc.attr('docdir')
  152     end
  153 
  154     test 'should load nil input' do
  155       doc = Asciidoctor.load nil, safe: :safe
  156       refute_nil doc
  157       assert_empty doc.blocks
  158     end
  159 
  160     test 'should accept attributes as array' do
  161       # NOTE there's a tab character before idseparator
  162       doc = Asciidoctor.load('text', attributes: %w(toc sectnums   source-highlighter=coderay idprefix  idseparator=-))
  163       assert_kind_of Hash, doc.attributes
  164       assert doc.attr?('toc')
  165       assert_equal '', doc.attr('toc')
  166       assert doc.attr?('sectnums')
  167       assert_equal '', doc.attr('sectnums')
  168       assert doc.attr?('source-highlighter')
  169       assert_equal 'coderay', doc.attr('source-highlighter')
  170       assert doc.attr?('idprefix')
  171       assert_equal '', doc.attr('idprefix')
  172       assert doc.attr?('idseparator')
  173       assert_equal '-', doc.attr('idseparator')
  174     end
  175 
  176     test 'should accept attributes as empty array' do
  177       doc = Asciidoctor.load('text', attributes: [])
  178       assert_kind_of Hash, doc.attributes
  179     end
  180 
  181     test 'should accept attributes as string' do
  182       doc = Asciidoctor.load 'text', attributes: %(toc sectnums\nsource-highlighter=coderay\nidprefix\nidseparator=-)
  183       assert_kind_of Hash, doc.attributes
  184       assert doc.attr?('toc')
  185       assert_equal '', doc.attr('toc')
  186       assert doc.attr?('sectnums')
  187       assert_equal '', doc.attr('sectnums')
  188       assert doc.attr?('source-highlighter')
  189       assert_equal 'coderay', doc.attr('source-highlighter')
  190       assert doc.attr?('idprefix')
  191       assert_equal '', doc.attr('idprefix')
  192       assert doc.attr?('idseparator')
  193       assert_equal '-', doc.attr('idseparator')
  194     end
  195 
  196     test 'should accept values containing spaces in attributes string' do
  197       doc = Asciidoctor.load('text', attributes: %(idprefix idseparator=-   note-caption=Note\\ to\\\tself toc))
  198       assert_kind_of Hash, doc.attributes
  199       assert doc.attr?('idprefix')
  200       assert_equal '', doc.attr('idprefix')
  201       assert doc.attr?('idseparator')
  202       assert_equal '-', doc.attr('idseparator')
  203       assert doc.attr?('note-caption')
  204       assert_equal "Note to\tself", doc.attr('note-caption')
  205     end
  206 
  207     test 'should accept attributes as empty string' do
  208       doc = Asciidoctor.load('text', attributes: '')
  209       assert_kind_of Hash, doc.attributes
  210     end
  211 
  212     test 'should accept attributes as nil' do
  213       doc = Asciidoctor.load('text', attributes: nil)
  214       assert_kind_of Hash, doc.attributes
  215     end
  216 
  217     test 'should accept attributes if hash like' do
  218       class Hashish
  219         def initialize
  220           @table = { 'toc' => '' }
  221         end
  222 
  223         def keys
  224           @table.keys
  225         end
  226 
  227         def [](key)
  228           @table[key]
  229         end
  230       end
  231 
  232       doc = Asciidoctor.load('text', attributes: Hashish.new)
  233       assert_kind_of Hash, doc.attributes
  234       assert doc.attributes.has_key?('toc')
  235     end
  236 
  237     test 'should not expand value of docdir attribute if specified via API' do
  238       docdir = 'virtual/directory'
  239       doc = document_from_string '', safe: :safe, attributes: { 'docdir' => docdir }
  240       assert_equal docdir, (doc.attr 'docdir')
  241       assert_equal docdir, doc.base_dir
  242     end
  243 
  244     test 'converts block to output format when convert is called' do
  245       doc = Asciidoctor.load 'paragraph text'
  246       expected = <<~'EOS'.chop
  247       <div class="paragraph">
  248       <p>paragraph text</p>
  249       </div>
  250       EOS
  251       assert_equal 1, doc.blocks.length
  252       assert_equal :paragraph, doc.blocks[0].context
  253       assert_equal expected, doc.blocks[0].convert
  254     end
  255 
  256     test 'render method on node is aliased to convert method' do
  257       input = <<~'EOS'
  258       paragraph text
  259 
  260       * list item
  261       EOS
  262       doc = Asciidoctor.load input
  263       assert_equal 2, doc.blocks.length
  264       ([doc] + doc.blocks).each do |block|
  265         assert_equal block.method(:convert), block.method(:render)
  266       end
  267       inline = Asciidoctor::Inline.new doc.blocks[0], :image, nil, type: 'image', target: 'tiger.png'
  268       assert_equal inline.method(:convert), inline.method(:render)
  269     end
  270 
  271     test 'should output timestamps by default' do
  272       doc = document_from_string 'text', backend: :html5, attributes: nil
  273       result = doc.convert
  274       assert doc.attr?('docdate')
  275       refute doc.attr? 'reproducible'
  276       assert_xpath '//div[@id="footer-text" and contains(string(.//text()), "Last updated")]', result, 1
  277     end
  278 
  279     test 'should not output timestamps if reproducible attribute is set in HTML 5' do
  280       doc = document_from_string 'text', backend: :html5, attributes: { 'reproducible' => '' }
  281       result = doc.convert
  282       assert doc.attr?('docdate')
  283       assert doc.attr?('reproducible')
  284       assert_xpath '//div[@id="footer-text" and contains(string(.//text()), "Last updated")]', result, 0
  285     end
  286 
  287     test 'should not output timestamps if reproducible attribute is set in DocBook' do
  288       doc = document_from_string 'text', backend: :docbook, attributes: { 'reproducible' => '' }
  289       result = doc.convert
  290       assert doc.attr?('docdate')
  291       assert doc.attr?('reproducible')
  292       assert_xpath '/article/info/date', result, 0
  293     end
  294 
  295     test 'should not modify options argument' do
  296       options = { safe: Asciidoctor::SafeMode::SAFE }
  297       options.freeze
  298       sample_input_path = fixture_path('sample.adoc')
  299       begin
  300         Asciidoctor.load_file sample_input_path, options
  301       rescue
  302         flunk %(options argument should not be modified)
  303       end
  304     end
  305 
  306     test 'should not modify attributes Hash argument' do
  307       attributes = {}
  308       attributes.freeze
  309       options = {
  310         safe: Asciidoctor::SafeMode::SAFE,
  311         attributes: attributes,
  312       }
  313       sample_input_path = fixture_path('sample.adoc')
  314       begin
  315         Asciidoctor.load_file sample_input_path, options
  316       rescue
  317         flunk %(attributes argument should not be modified)
  318       end
  319     end
  320 
  321     test 'should be able to restore header attributes after call to convert' do
  322       input = <<~'EOS'
  323       = Document Title
  324       :foo: bar
  325 
  326       content
  327 
  328       :foo: baz
  329 
  330       content
  331       EOS
  332       doc = Asciidoctor.load input
  333       assert_equal 'bar', (doc.attr 'foo')
  334       doc.convert
  335       assert_equal 'baz', (doc.attr 'foo')
  336       doc.restore_attributes
  337       assert_equal 'bar', (doc.attr 'foo')
  338     end
  339 
  340     test 'should track file and line information with blocks if sourcemap option is set' do
  341       doc = Asciidoctor.load_file fixture_path('sample.adoc'), sourcemap: true
  342 
  343       refute_nil doc.source_location
  344       assert_equal 'sample.adoc', doc.file
  345       assert_equal 1, doc.lineno
  346 
  347       section_1 = doc.sections[0]
  348       assert_equal 'Section A', section_1.title
  349       refute_nil section_1.source_location
  350       assert_equal 'sample.adoc', section_1.file
  351       assert_equal 10, section_1.lineno
  352 
  353       section_2 = doc.sections[1]
  354       assert_equal 'Section B', section_2.title
  355       refute_nil section_2.source_location
  356       assert_equal 'sample.adoc', section_2.file
  357       assert_equal 18, section_2.lineno
  358 
  359       table_block = section_2.blocks[1]
  360       assert_equal :table, table_block.context
  361       refute_nil table_block.source_location
  362       assert_equal 'sample.adoc', table_block.file
  363       assert_equal 22, table_block.lineno
  364       first_cell = table_block.rows.body[0][0]
  365       refute_nil first_cell.source_location
  366       assert_equal 'sample.adoc', first_cell.file
  367       assert_equal 23, first_cell.lineno
  368       second_cell = table_block.rows.body[0][1]
  369       refute_nil second_cell.source_location
  370       assert_equal 'sample.adoc', second_cell.file
  371       assert_equal 23, second_cell.lineno
  372       last_cell = table_block.rows.body[-1][-1]
  373       refute_nil last_cell.source_location
  374       assert_equal 'sample.adoc', last_cell.file
  375       assert_equal 24, last_cell.lineno
  376 
  377       last_block = section_2.blocks[-1]
  378       assert_equal :ulist, last_block.context
  379       refute_nil last_block.source_location
  380       assert_equal 'sample.adoc', last_block.file
  381       assert_equal 28, last_block.lineno
  382 
  383       list_items = last_block.blocks
  384       refute_nil list_items[0].source_location
  385       assert_equal 'sample.adoc', list_items[0].file
  386       assert_equal 28, list_items[0].lineno
  387 
  388       refute_nil list_items[1].source_location
  389       assert_equal 'sample.adoc', list_items[1].file
  390       assert_equal 29, list_items[1].lineno
  391 
  392       refute_nil list_items[2].source_location
  393       assert_equal 'sample.adoc', list_items[2].file
  394       assert_equal 30, list_items[2].lineno
  395 
  396       doc = Asciidoctor.load_file fixture_path('master.adoc'), sourcemap: true, safe: :safe
  397 
  398       section_1 = doc.sections[0]
  399       assert_equal 'Chapter A', section_1.title
  400       refute_nil section_1.source_location
  401       assert_equal fixture_path('chapter-a.adoc'), section_1.file
  402       assert_equal 1, section_1.lineno
  403     end
  404 
  405     test 'should track file and line information on list items if sourcemap option is set' do
  406       doc = Asciidoctor.load_file fixture_path('lists.adoc'), sourcemap: true
  407 
  408       first_section = doc.blocks[1]
  409 
  410       unordered_basic_list = first_section.blocks[0]
  411       assert_equal 11, unordered_basic_list.lineno
  412 
  413       unordered_basic_list_items = unordered_basic_list.find_by context: :list_item
  414       assert_equal 11, unordered_basic_list_items[0].lineno
  415       assert_equal 12, unordered_basic_list_items[1].lineno
  416       assert_equal 13, unordered_basic_list_items[2].lineno
  417 
  418       unordered_max_nesting = first_section.blocks[1]
  419       assert_equal 16, unordered_max_nesting.lineno
  420       unordered_max_nesting_items = unordered_max_nesting.find_by context: :list_item
  421       assert_equal 16, unordered_max_nesting_items[0].lineno
  422       assert_equal 17, unordered_max_nesting_items[1].lineno
  423       assert_equal 18, unordered_max_nesting_items[2].lineno
  424       assert_equal 19, unordered_max_nesting_items[3].lineno
  425       assert_equal 20, unordered_max_nesting_items[4].lineno
  426       assert_equal 21, unordered_max_nesting_items[5].lineno
  427 
  428       checklist = first_section.blocks[2]
  429       assert_equal 24, checklist.lineno
  430       checklist_list_items = checklist.find_by context: :list_item
  431       assert_equal 24, checklist_list_items[0].lineno
  432       assert_equal 25, checklist_list_items[1].lineno
  433       assert_equal 26, checklist_list_items[2].lineno
  434       assert_equal 27, checklist_list_items[3].lineno
  435 
  436       ordered_basic = first_section.blocks[3]
  437       assert_equal 30, ordered_basic.lineno
  438       ordered_basic_list_items = ordered_basic.find_by context: :list_item
  439       assert_equal 30, ordered_basic_list_items[0].lineno
  440       assert_equal 31, ordered_basic_list_items[1].lineno
  441       assert_equal 32, ordered_basic_list_items[2].lineno
  442 
  443       ordered_nested = first_section.blocks[4]
  444       assert_equal 35, ordered_nested.lineno
  445       ordered_nested_list_items = ordered_nested.find_by context: :list_item
  446       assert_equal 35, ordered_nested_list_items[0].lineno
  447       assert_equal 36, ordered_nested_list_items[1].lineno
  448       assert_equal 37, ordered_nested_list_items[2].lineno
  449       assert_equal 38, ordered_nested_list_items[3].lineno
  450       assert_equal 39, ordered_nested_list_items[4].lineno
  451 
  452       ordered_max_nesting = first_section.blocks[5]
  453       assert_equal 42, ordered_max_nesting.lineno
  454       ordered_max_nesting_items = ordered_max_nesting.find_by context: :list_item
  455       assert_equal 42, ordered_max_nesting_items[0].lineno
  456       assert_equal 43, ordered_max_nesting_items[1].lineno
  457       assert_equal 44, ordered_max_nesting_items[2].lineno
  458       assert_equal 45, ordered_max_nesting_items[3].lineno
  459       assert_equal 46, ordered_max_nesting_items[4].lineno
  460       assert_equal 47, ordered_max_nesting_items[5].lineno
  461 
  462       labeled_singleline = first_section.blocks[6]
  463       assert_equal 50, labeled_singleline.lineno
  464       labeled_singleline_items = labeled_singleline.find_by context: :list_item
  465       assert_equal 50, labeled_singleline_items[0].lineno
  466       assert_equal 50, labeled_singleline_items[1].lineno
  467       assert_equal 51, labeled_singleline_items[2].lineno
  468       assert_equal 51, labeled_singleline_items[3].lineno
  469 
  470       labeled_multiline = first_section.blocks[7]
  471       assert_equal 54, labeled_multiline.lineno
  472       labeled_multiline_items = labeled_multiline.find_by context: :list_item
  473       assert_equal 54, labeled_multiline_items[0].lineno
  474       assert_equal 55, labeled_multiline_items[1].lineno
  475       assert_equal 56, labeled_multiline_items[2].lineno
  476       assert_equal 57, labeled_multiline_items[3].lineno
  477 
  478       qanda = first_section.blocks[8]
  479       assert_equal 61, qanda.lineno
  480       qanda_items = qanda.find_by context: :list_item
  481       assert_equal 61, qanda_items[0].lineno
  482       assert_equal 62, qanda_items[1].lineno
  483       assert_equal 63, qanda_items[2].lineno
  484       assert_equal 63, qanda_items[3].lineno
  485 
  486       mixed = first_section.blocks[9]
  487       assert_equal 66, mixed.lineno
  488       mixed_items = mixed.find_by(context: :list_item) {|block| block.text? }
  489       assert_equal 66, mixed_items[0].lineno
  490       assert_equal 67, mixed_items[1].lineno
  491       assert_equal 68, mixed_items[2].lineno
  492       assert_equal 69, mixed_items[3].lineno
  493       assert_equal 70, mixed_items[4].lineno
  494       assert_equal 71, mixed_items[5].lineno
  495       assert_equal 72, mixed_items[6].lineno
  496       assert_equal 73, mixed_items[7].lineno
  497       assert_equal 74, mixed_items[8].lineno
  498       assert_equal 75, mixed_items[9].lineno
  499       assert_equal 77, mixed_items[10].lineno
  500       assert_equal 78, mixed_items[11].lineno
  501       assert_equal 79, mixed_items[12].lineno
  502       assert_equal 80, mixed_items[13].lineno
  503       assert_equal 81, mixed_items[14].lineno
  504       assert_equal 82, mixed_items[15].lineno
  505       assert_equal 83, mixed_items[16].lineno
  506 
  507       unordered_complex_list = first_section.blocks[10]
  508       assert_equal 86, unordered_complex_list.lineno
  509       unordered_complex_items = unordered_complex_list.find_by context: :list_item
  510       assert_equal 86, unordered_complex_items[0].lineno
  511       assert_equal 87, unordered_complex_items[1].lineno
  512       assert_equal 88, unordered_complex_items[2].lineno
  513       assert_equal 92, unordered_complex_items[3].lineno
  514       assert_equal 96, unordered_complex_items[4].lineno
  515     end
  516 
  517     # NOTE this does not work for a list continuation that attached to a grandparent
  518     test 'should assign correct source location to blocks that follow a detached list continuation' do
  519       input = <<~'EOS'
  520       * parent
  521        ** child
  522 
  523       +
  524       paragraph attached to parent
  525 
  526       ****
  527       sidebar outside list
  528       ****
  529       EOS
  530 
  531       doc = document_from_string input, sourcemap: true
  532       assert_equal [5, 8], (doc.find_by context: :paragraph).map(&:lineno)
  533     end
  534 
  535     test 'should assign correct source location if section occurs on last line of input' do
  536       input = <<~'EOS'
  537       = Document Title
  538 
  539       == Section A
  540 
  541       content
  542 
  543       == Section B
  544       EOS
  545 
  546       doc = document_from_string input, sourcemap: true
  547       assert_equal [1, 3, 7], (doc.find_by context: :section).map(&:lineno)
  548     end
  549 
  550     test 'should allow sourcemap option on document to be modified before document is parsed' do
  551       doc = Asciidoctor.load_file fixture_path('sample.adoc'), parse: false
  552       doc.sourcemap = true
  553       refute doc.parsed?
  554       doc = doc.parse
  555       assert doc.parsed?
  556 
  557       section_1 = doc.sections[0]
  558       assert_equal 'Section A', section_1.title
  559       refute_nil section_1.source_location
  560       assert_equal 'sample.adoc', section_1.file
  561       assert_equal 10, section_1.lineno
  562     end
  563 
  564     test 'find_by should return Array of blocks anywhere in document tree that match criteria' do
  565       input = <<~'EOS'
  566       = Document Title
  567 
  568       preamble
  569 
  570       == Section A
  571 
  572       paragraph
  573 
  574       --
  575       Exhibit A::
  576       +
  577       [#tiger.animal]
  578       image::tiger.png[Tiger]
  579       --
  580 
  581       image::shoe.png[Shoe]
  582 
  583       == Section B
  584 
  585       paragraph
  586       EOS
  587 
  588       doc = Asciidoctor.load input
  589       result = doc.find_by context: :image
  590       assert_equal 2, result.size
  591       assert_equal :image, result[0].context
  592       assert_equal 'tiger.png', result[0].attr('target')
  593       assert_equal :image, result[1].context
  594       assert_equal 'shoe.png', result[1].attr('target')
  595     end
  596 
  597     test 'find_by should return an empty Array if no matches are found' do
  598       input = 'paragraph'
  599       doc = Asciidoctor.load input
  600       result = doc.find_by context: :section
  601       refute_nil result
  602       assert_equal 0, result.size
  603     end
  604 
  605     test 'find_by should discover blocks inside AsciiDoc table cells if traverse_documents selector option is true' do
  606       input = <<~'EOS'
  607       paragraph in parent document (before)
  608 
  609       [%footer,cols=2*]
  610       |===
  611       a|
  612       paragraph in nested document (body)
  613       |normal table cell
  614 
  615       a|
  616       paragraph in nested document (foot)
  617       |normal table cell
  618       |===
  619 
  620       paragraph in parent document (after)
  621       EOS
  622 
  623       doc = Asciidoctor.load input
  624       result = doc.find_by context: :paragraph
  625       assert_equal 2, result.size
  626       result = doc.find_by context: :paragraph, traverse_documents: true
  627       assert_equal 4, result.size
  628     end
  629 
  630     test 'find_by should return inner document of AsciiDoc table cell if traverse_documents selector option is true' do
  631       input = <<~'EOS'
  632       |===
  633       a|paragraph in nested document
  634       |===
  635       EOS
  636 
  637       doc = Asciidoctor.load input
  638       inner_doc = doc.blocks[0].rows.body[0][0].inner_document
  639       result = doc.find_by traverse_documents: true
  640       assert_include inner_doc, result
  641       result = doc.find_by context: :inner_document, traverse_documents: true
  642       assert_equal 1, result.size
  643       assert_equal inner_doc, result[0]
  644     end
  645 
  646     test 'find_by should match table cells' do
  647       input = <<~'EOS'
  648       |===
  649       |a |b |c
  650 
  651       |1
  652       one
  653       a|NOTE: 2, as it goes.
  654       l|
  655       3
  656        you
  657         me
  658       |===
  659       EOS
  660 
  661       doc = document_from_string input
  662       table = doc.blocks[0]
  663       first_head_cell = table.rows.head[0][0]
  664       first_body_cell = table.rows.body[0][0]
  665       result = doc.find_by
  666       assert_include first_head_cell, result
  667       assert_include first_body_cell, result
  668       assert_equal 'a', first_head_cell.source
  669       assert_equal ['a'], first_head_cell.lines
  670       assert_equal %(1\none), first_body_cell.source
  671       assert_equal ['1', 'one'], first_body_cell.lines
  672       result = doc.find_by context: :table_cell, style: :asciidoc
  673       assert_equal 1, result.size
  674       assert_kind_of Asciidoctor::Table::Cell, result[0]
  675       assert_equal :asciidoc, result[0].style
  676       assert_equal 'NOTE: 2, as it goes.', result[0].source
  677     end
  678 
  679     test 'find_by should return Array of blocks that match style criteria' do
  680       input = <<~'EOS'
  681       [square]
  682       * one
  683       * two
  684       * three
  685 
  686       ---
  687 
  688       * apples
  689       * bananas
  690       * pears
  691       EOS
  692 
  693       doc = Asciidoctor.load input
  694       result = doc.find_by context: :ulist, style: 'square'
  695       assert_equal 1, result.size
  696       assert_equal :ulist, result[0].context
  697     end
  698 
  699     test 'find_by should return Array of blocks that match role criteria' do
  700       input = <<~'EOS'
  701       [#tiger.animal]
  702       image::tiger.png[Tiger]
  703 
  704       image::shoe.png[Shoe]
  705       EOS
  706 
  707       doc = Asciidoctor.load input
  708       result = doc.find_by context: :image, role: 'animal'
  709       assert_equal 1, result.size
  710       assert_equal :image, result[0].context
  711       assert_equal 'tiger.png', result[0].attr('target')
  712     end
  713 
  714     test 'find_by should return the document title section if context selector is :section' do
  715       input = <<~'EOS'
  716       = Document Title
  717 
  718       preamble
  719 
  720       == Section One
  721 
  722       content
  723       EOS
  724       doc = Asciidoctor.load input
  725       result = doc.find_by context: :section
  726       refute_nil result
  727       assert_equal 2, result.size
  728       assert_equal :section, result[0].context
  729       assert_equal 'Document Title', result[0].title
  730     end
  731 
  732     test 'find_by should only return results for which the block argument yields true' do
  733       input = <<~'EOS'
  734       == Section
  735 
  736       content
  737 
  738       === Subsection
  739 
  740       content
  741       EOS
  742       doc = Asciidoctor.load input
  743       result = doc.find_by(context: :section) {|sect| sect.level == 1 }
  744       refute_nil result
  745       assert_equal 1, result.size
  746       assert_equal :section, result[0].context
  747       assert_equal 'Section', result[0].title
  748     end
  749 
  750     test 'find_by should reject node and its children if block returns :reject' do
  751       input = <<~'EOS'
  752       paragraph 1
  753 
  754       ====
  755       paragraph 2
  756 
  757       term::
  758       +
  759       paragraph 3
  760       ====
  761 
  762       paragraph 4
  763       EOS
  764       doc = Asciidoctor.load input
  765       result = doc.find_by do |candidate|
  766         ctx = candidate.context
  767         if ctx == :example
  768           :reject
  769         elsif ctx == :paragraph
  770           true
  771         end
  772       end
  773       refute_nil result
  774       assert_equal 2, result.size
  775       assert_equal :paragraph, result[0].context
  776       assert_equal :paragraph, result[1].context
  777     end
  778 
  779     test 'find_by should reject node matched by ID selector if block returns :reject' do
  780       input = <<~'EOS'
  781       [.rolename]
  782       paragraph 1
  783 
  784       [.rolename#idname]
  785       paragraph 2
  786       EOS
  787       doc = Asciidoctor.load input
  788       result = doc.find_by id: 'idname', role: 'rolename'
  789       refute_nil result
  790       assert_equal 1, result.size
  791       assert_equal doc.blocks[1], result[0]
  792       result = doc.find_by(id: 'idname', role: 'rolename') { :reject }
  793       refute_nil result
  794       assert_equal 0, result.size
  795     end
  796 
  797     test 'find_by should accept node matched by ID selector if block returns :prune' do
  798       input = <<~'EOS'
  799       [.rolename]
  800       paragraph 1
  801 
  802       [.rolename#idname]
  803       ====
  804       paragraph 2
  805       ====
  806       EOS
  807       doc = Asciidoctor.load input
  808       result = doc.find_by id: 'idname', role: 'rolename'
  809       refute_nil result
  810       assert_equal 1, result.size
  811       assert_equal doc.blocks[1], result[0]
  812       result = doc.find_by(id: 'idname', role: 'rolename') { :prune }
  813       refute_nil result
  814       assert_equal 1, result.size
  815       assert_equal doc.blocks[1], result[0]
  816     end
  817 
  818     test 'find_by should accept node but reject its children if block returns :prune' do
  819       input = <<~'EOS'
  820       ====
  821       paragraph 2
  822 
  823       term::
  824       +
  825       paragraph 3
  826       ====
  827       EOS
  828       doc = Asciidoctor.load input
  829       result = doc.find_by do |candidate|
  830         if candidate.context == :example
  831           :prune
  832         end
  833       end
  834       refute_nil result
  835       assert_equal 1, result.size
  836       assert_equal :example, result[0].context
  837     end
  838 
  839     test 'find_by should stop looking for blocks when StopIteration is raised' do
  840       input = <<~'EOS'
  841       paragraph 1
  842 
  843       ====
  844       paragraph 2
  845 
  846       ****
  847       paragraph 3
  848       ****
  849       ====
  850 
  851       paragraph 4
  852 
  853       * item
  854       +
  855       paragraph 5
  856       EOS
  857       doc = Asciidoctor.load input
  858 
  859       stop_at_next = false
  860       result = doc.find_by do |candidate|
  861         raise StopIteration if stop_at_next
  862         if candidate.context == :paragraph
  863           candidate.parent.context == :sidebar ? (stop_at_next = true) : true
  864         end
  865       end
  866       refute_nil result
  867       assert_equal 3, result.size
  868       assert_equal 'paragraph 1', result[0].content
  869       assert_equal 'paragraph 2', result[1].content
  870       assert_equal 'paragraph 3', result[2].content
  871     end
  872 
  873     test 'find_by should stop looking for blocks when filter block returns :stop directive' do
  874       input = <<~'EOS'
  875       paragraph 1
  876 
  877       ====
  878       paragraph 2
  879 
  880       ****
  881       paragraph 3
  882       ****
  883       ====
  884 
  885       paragraph 4
  886 
  887       * item
  888       +
  889       paragraph 5
  890       EOS
  891       doc = Asciidoctor.load input
  892 
  893       stop_at_next = false
  894       result = doc.find_by do |candidate|
  895         next :stop if stop_at_next
  896         if candidate.context == :paragraph
  897           candidate.parent.context == :sidebar ? (stop_at_next = true) : true
  898         end
  899       end
  900       refute_nil result
  901       assert_equal 3, result.size
  902       assert_equal 'paragraph 1', result[0].content
  903       assert_equal 'paragraph 2', result[1].content
  904       assert_equal 'paragraph 3', result[2].content
  905     end
  906 
  907     test 'find_by should only return one result when matching by id' do
  908       input = <<~'EOS'
  909       == Section
  910 
  911       content
  912 
  913       [#subsection]
  914       === Subsection
  915 
  916       content
  917       EOS
  918       doc = Asciidoctor.load input
  919       result = doc.find_by(context: :section, id: 'subsection')
  920       refute_nil result
  921       assert_equal 1, result.size
  922       assert_equal :section, result[0].context
  923       assert_equal 'Subsection', result[0].title
  924     end
  925 
  926     test 'find_by should stop seeking once match is found' do
  927       input = <<~'EOS'
  928       == Section
  929 
  930       content
  931 
  932       [#subsection]
  933       === Subsection
  934 
  935       [#last]
  936       content
  937       EOS
  938       doc = Asciidoctor.load input
  939       visited_last = false
  940       result = doc.find_by(id: 'subsection') do |candidate|
  941         visited_last = true if candidate.id == 'last'
  942         true
  943       end
  944       refute_nil result
  945       assert_equal 1, result.size
  946       refute visited_last
  947     end
  948 
  949     test 'find_by should return an empty Array if the id criteria matches but the block argument yields false' do
  950       input = <<~'EOS'
  951       == Section
  952 
  953       content
  954 
  955       [#subsection]
  956       === Subsection
  957 
  958       content
  959       EOS
  960       doc = Asciidoctor.load input
  961       result = doc.find_by(context: :section, id: 'subsection') {|sect| false }
  962       refute_nil result
  963       assert_equal 0, result.size
  964     end
  965 
  966     test 'find_by should not crash if dlist entry does not have description' do
  967       input = 'term without description::'
  968       doc = Asciidoctor.load input
  969       result = doc.find_by
  970       refute_nil result
  971       assert_equal 3, result.size
  972       assert_kind_of Asciidoctor::Document, result[0]
  973       assert_kind_of Asciidoctor::List, result[1]
  974       assert_kind_of Asciidoctor::ListItem, result[2]
  975     end
  976 
  977     test 'dlist item should always have two entries for terms and desc' do
  978       [
  979         'term w/o desc::',
  980         %(term::\nalias::),
  981         %(primary:: 1\nsecondary:: 2),
  982       ].each do |input|
  983         dlist = (Asciidoctor.load input).blocks[0]
  984         dlist.items.each do |item|
  985           assert_equal 2, item.size
  986           assert_kind_of ::Array, item[0]
  987           assert_kind_of Asciidoctor::ListItem, item[1] if item[1]
  988         end
  989       end
  990     end
  991 
  992     test 'timings are recorded for each step when load and convert are called separately' do
  993       sample_input_path = fixture_path 'asciidoc_index.txt'
  994       (Asciidoctor.load_file sample_input_path, timings: (timings = Asciidoctor::Timings.new)).convert
  995       refute_equal '0.00000', '%05.5f' % timings.read_parse.to_f
  996       refute_equal '0.00000', '%05.5f' % timings.convert.to_f
  997       refute_equal timings.read_parse, timings.total
  998     end
  999 
 1000     test 'can disable syntax highlighter by setting value to nil in :syntax_highlighters option' do
 1001       doc = Asciidoctor.load '', safe: :safe, syntax_highlighters: { 'coderay' => nil }, attributes: { 'source-highlighter' => 'coderay' }
 1002       assert_nil doc.syntax_highlighter
 1003     end
 1004 
 1005     test 'can substitute a custom syntax highlighter factory instance using the :syntax_highlighter_factory option' do
 1006       input = <<~'EOS'
 1007       [source,ruby]
 1008       ----
 1009       puts 'Hello, World!'
 1010       ----
 1011       EOS
 1012       # NOTE this tests both the lazy loading and the custom factory
 1013       syntax_hl_factory = Asciidoctor::SyntaxHighlighter::CustomFactory.new 'github' => (Asciidoctor::SyntaxHighlighter.for 'html-pipeline')
 1014       doc = Asciidoctor.load input, safe: :safe, syntax_highlighter_factory: syntax_hl_factory, attributes: { 'source-highlighter' => 'github' }
 1015       refute_nil doc.syntax_highlighter
 1016       assert_kind_of Asciidoctor::SyntaxHighlighter::HtmlPipelineAdapter, doc.syntax_highlighter
 1017       assert_include '<pre lang="ruby"><code>', doc.convert
 1018     end
 1019 
 1020     test 'can substitute an extended syntax highlighter factory implementation using the :syntax_highlighters option' do
 1021       input = <<~'EOS'
 1022       [source,ruby]
 1023       ----
 1024       puts 'Hello, World!'
 1025       ----
 1026       EOS
 1027       syntax_hl_factory_class = Class.new do
 1028         include Asciidoctor::SyntaxHighlighter::DefaultFactory
 1029 
 1030         def for name
 1031           super 'highlight.js'
 1032         end
 1033       end
 1034       doc = Asciidoctor.load input, safe: :safe, syntax_highlighter_factory: syntax_hl_factory_class.new, attributes: { 'source-highlighter' => 'coderay' }
 1035       refute_nil doc.syntax_highlighter
 1036       output = doc.convert
 1037       refute_include 'CodeRay', output
 1038       assert_include 'hljs', output
 1039     end
 1040   end
 1041 
 1042   context 'Convert' do
 1043     test 'render_file is aliased to convert_file' do
 1044       assert_equal Asciidoctor.method(:convert_file), Asciidoctor.method(:render_file)
 1045     end
 1046 
 1047     test 'render is aliased to convert' do
 1048       assert_equal Asciidoctor.method(:convert), Asciidoctor.method(:render)
 1049     end
 1050 
 1051     test 'should convert source document to embedded document when header_footer is false' do
 1052       sample_input_path = fixture_path('sample.adoc')
 1053       sample_output_path = fixture_path('sample.html')
 1054 
 1055       [{ header_footer: false }, { header_footer: false, to_file: sample_output_path }].each do |opts|
 1056         begin
 1057           Asciidoctor.convert_file sample_input_path, opts
 1058           assert File.exist?(sample_output_path)
 1059           output = File.read(sample_output_path, mode: Asciidoctor::FILE_READ_MODE)
 1060           refute_empty output
 1061           assert_xpath '/html', output, 0
 1062           assert_css '#preamble', output, 1
 1063         ensure
 1064           FileUtils.rm(sample_output_path)
 1065         end
 1066       end
 1067     end
 1068 
 1069     test 'should convert source document to standalone document string when to_file is false and standalone is true' do
 1070       sample_input_path = fixture_path('sample.adoc')
 1071 
 1072       output = Asciidoctor.convert_file sample_input_path, standalone: true, to_file: false
 1073       refute_empty output
 1074       assert_xpath '/html', output, 1
 1075       assert_xpath '/html/head', output, 1
 1076       assert_xpath '/html/body', output, 1
 1077       assert_xpath '/html/head/title[text() = "Document Title"]', output, 1
 1078       assert_xpath '/html/body/*[@id="header"]/h1[text() = "Document Title"]', output, 1
 1079     end
 1080 
 1081     test 'should convert source document to standalone document string when to_file is false and header_footer is true' do
 1082       sample_input_path = fixture_path('sample.adoc')
 1083 
 1084       output = Asciidoctor.convert_file sample_input_path, header_footer: true, to_file: false
 1085       refute_empty output
 1086       assert_xpath '/html', output, 1
 1087       assert_xpath '/html/head', output, 1
 1088       assert_xpath '/html/body', output, 1
 1089       assert_xpath '/html/head/title[text() = "Document Title"]', output, 1
 1090       assert_xpath '/html/body/*[@id="header"]/h1[text() = "Document Title"]', output, 1
 1091     end
 1092 
 1093     test 'lines in output should be separated by line feed' do
 1094       sample_input_path = fixture_path('sample.adoc')
 1095 
 1096       output = Asciidoctor.convert_file sample_input_path, standalone: true, to_file: false
 1097       refute_empty output
 1098       lines = output.split("\n")
 1099       assert_equal lines.size, output.split(/\r\n|\r|\n/).size
 1100       assert_equal lines.map(&:length), lines.map(&:rstrip).map(&:length)
 1101     end
 1102 
 1103     test 'should accept attributes as array' do
 1104       sample_input_path = fixture_path('sample.adoc')
 1105       output = Asciidoctor.convert_file sample_input_path, attributes: %w(sectnums idprefix idseparator=-), to_file: false
 1106       assert_css '#section-a', output, 1
 1107     end
 1108 
 1109     test 'should accept attributes as string' do
 1110       sample_input_path = fixture_path('sample.adoc')
 1111       output = Asciidoctor.convert_file sample_input_path, attributes: 'sectnums idprefix idseparator=-', to_file: false
 1112       assert_css '#section-a', output, 1
 1113     end
 1114 
 1115     test 'should link to default stylesheet by default when safe mode is SECURE or greater' do
 1116       sample_input_path = fixture_path('basic.adoc')
 1117       output = Asciidoctor.convert_file sample_input_path, standalone: true, to_file: false
 1118       assert_css 'html:root > head > link[rel="stylesheet"][href^="https://fonts.googleapis.com"]', output, 1
 1119       assert_css 'html:root > head > link[rel="stylesheet"][href="./asciidoctor.css"]', output, 1
 1120     end
 1121 
 1122     test 'should embed default stylesheet by default if SafeMode is less than SECURE' do
 1123       input = <<~'EOS'
 1124       = Document Title
 1125 
 1126       text
 1127       EOS
 1128 
 1129       output = Asciidoctor.convert input, safe: Asciidoctor::SafeMode::SERVER, standalone: true
 1130       assert_css 'html:root > head > link[rel="stylesheet"][href^="https://fonts.googleapis.com"]', output, 1
 1131       assert_css 'html:root > head > link[rel="stylesheet"][href="./asciidoctor.css"]', output, 0
 1132       stylenode = xmlnodes_at_css 'html:root > head > style', output, 1
 1133       styles = stylenode.content
 1134       refute_nil styles
 1135       refute_empty styles.strip
 1136     end
 1137 
 1138     test 'should not allow linkcss be unset from document if SafeMode is SECURE or greater' do
 1139       input = <<~'EOS'
 1140       = Document Title
 1141       :linkcss!:
 1142 
 1143       text
 1144       EOS
 1145 
 1146       output = Asciidoctor.convert input, standalone: true
 1147       assert_css 'html:root > head > link[rel="stylesheet"][href^="https://fonts.googleapis.com"]', output, 1
 1148       assert_css 'html:root > head > link[rel="stylesheet"][href="./asciidoctor.css"]', output, 1
 1149     end
 1150 
 1151     test 'should embed default stylesheet if linkcss is unset from API and SafeMode is SECURE or greater' do
 1152       input = <<~'EOS'
 1153       = Document Title
 1154 
 1155       text
 1156       EOS
 1157 
 1158       #[{ 'linkcss!' => '' }, { 'linkcss' => nil }, { 'linkcss' => false }].each do |attrs|
 1159       [{ 'linkcss!' => '' }, { 'linkcss' => nil }].each do |attrs|
 1160         output = Asciidoctor.convert input, standalone: true, attributes: attrs
 1161         assert_css 'html:root > head > link[rel="stylesheet"][href^="https://fonts.googleapis.com"]', output, 1
 1162         assert_css 'html:root > head > link[rel="stylesheet"][href="./asciidoctor.css"]', output, 0
 1163         stylenode = xmlnodes_at_css 'html:root > head > style', output, 1
 1164         styles = stylenode.content
 1165         refute_nil styles
 1166         refute_empty styles.strip
 1167       end
 1168     end
 1169 
 1170     test 'should embed default stylesheet if safe mode is less than SECURE and linkcss is unset from API' do
 1171       sample_input_path = fixture_path('basic.adoc')
 1172       output = Asciidoctor.convert_file sample_input_path, standalone: true, to_file: false,
 1173           safe: Asciidoctor::SafeMode::SAFE, attributes: { 'linkcss!' => '' }
 1174       assert_css 'html:root > head > style', output, 1
 1175       stylenode = xmlnodes_at_css 'html:root > head > style', output, 1
 1176       styles = stylenode.content
 1177       refute_nil styles
 1178       refute_empty styles.strip
 1179     end
 1180 
 1181     test 'should not link to stylesheet if stylesheet is unset' do
 1182       input = <<~'EOS'
 1183       = Document Title
 1184 
 1185       text
 1186       EOS
 1187 
 1188       output = Asciidoctor.convert input, standalone: true, attributes: { 'stylesheet!' => '' }
 1189       assert_css 'html:root > head > link[rel="stylesheet"][href^="https://fonts.googleapis.com"]', output, 0
 1190       assert_css 'html:root > head > link[rel="stylesheet"]', output, 0
 1191     end
 1192 
 1193     test 'should link to custom stylesheet if specified in stylesheet attribute' do
 1194       input = <<~'EOS'
 1195       = Document Title
 1196 
 1197       text
 1198       EOS
 1199 
 1200       output = Asciidoctor.convert input, standalone: true, attributes: { 'stylesheet' => './custom.css' }
 1201       assert_css 'html:root > head > link[rel="stylesheet"][href^="https://fonts.googleapis.com"]', output, 0
 1202       assert_css 'html:root > head > link[rel="stylesheet"][href="./custom.css"]', output, 1
 1203 
 1204       output = Asciidoctor.convert input, standalone: true, attributes: { 'stylesheet' => 'file:///home/username/custom.css' }
 1205       assert_css 'html:root > head > link[rel="stylesheet"][href="file:///home/username/custom.css"]', output, 1
 1206     end
 1207 
 1208     test 'should resolve custom stylesheet relative to stylesdir' do
 1209       input = <<~'EOS'
 1210       = Document Title
 1211 
 1212       text
 1213       EOS
 1214 
 1215       output = Asciidoctor.convert input, standalone: true, attributes: { 'stylesheet' => 'custom.css', 'stylesdir' => './stylesheets' }
 1216       assert_css 'html:root > head > link[rel="stylesheet"][href="./stylesheets/custom.css"]', output, 1
 1217     end
 1218 
 1219     test 'should resolve custom stylesheet to embed relative to stylesdir' do
 1220       sample_input_path = fixture_path('basic.adoc')
 1221       output = Asciidoctor.convert_file sample_input_path, standalone: true, safe: Asciidoctor::SafeMode::SAFE, to_file: false,
 1222           attributes: { 'stylesheet' => 'custom.css', 'stylesdir' => './stylesheets', 'linkcss!' => '' }
 1223       stylenode = xmlnodes_at_css 'html:root > head > style', output, 1
 1224       styles = stylenode.content
 1225       refute_nil styles
 1226       refute_empty styles.strip
 1227     end
 1228 
 1229     test 'should convert source file and write result to adjacent file by default' do
 1230       sample_input_path = fixture_path('sample.adoc')
 1231       sample_output_path = fixture_path('sample.html')
 1232       begin
 1233         Asciidoctor.convert_file sample_input_path
 1234         assert File.exist?(sample_output_path)
 1235         output = File.read(sample_output_path, mode: Asciidoctor::FILE_READ_MODE)
 1236         refute_empty output
 1237         assert_xpath '/html', output, 1
 1238         assert_xpath '/html/head', output, 1
 1239         assert_xpath '/html/body', output, 1
 1240         assert_xpath '/html/head/title[text() = "Document Title"]', output, 1
 1241         assert_xpath '/html/body/*[@id="header"]/h1[text() = "Document Title"]', output, 1
 1242       ensure
 1243         FileUtils.rm(sample_output_path)
 1244       end
 1245     end
 1246 
 1247     test 'should convert source file specified by pathname and write result to adjacent file by default' do
 1248       sample_input_path = Pathname fixture_path 'sample.adoc'
 1249       sample_output_path = Pathname fixture_path 'sample.html'
 1250       begin
 1251         doc = Asciidoctor.convert_file sample_input_path, safe: :safe
 1252         assert_equal sample_output_path.expand_path.to_s, (doc.attr 'outfile')
 1253         assert sample_output_path.file?
 1254         output = sample_output_path.read mode: Asciidoctor::FILE_READ_MODE
 1255         refute_empty output
 1256         assert_xpath '/html', output, 1
 1257         assert_xpath '/html/head', output, 1
 1258         assert_xpath '/html/body', output, 1
 1259         assert_xpath '/html/head/title[text() = "Document Title"]', output, 1
 1260         assert_xpath '/html/body/*[@id="header"]/h1[text() = "Document Title"]', output, 1
 1261       ensure
 1262         sample_output_path.delete
 1263       end
 1264     end
 1265 
 1266     test 'should convert source file and write to specified file' do
 1267       sample_input_path = fixture_path('sample.adoc')
 1268       sample_output_path = fixture_path('result.html')
 1269       begin
 1270         Asciidoctor.convert_file sample_input_path, to_file: sample_output_path
 1271         assert File.exist?(sample_output_path)
 1272         output = File.read(sample_output_path, mode: Asciidoctor::FILE_READ_MODE)
 1273         refute_empty output
 1274         assert_xpath '/html', output, 1
 1275         assert_xpath '/html/head', output, 1
 1276         assert_xpath '/html/body', output, 1
 1277         assert_xpath '/html/head/title[text() = "Document Title"]', output, 1
 1278         assert_xpath '/html/body/*[@id="header"]/h1[text() = "Document Title"]', output, 1
 1279       ensure
 1280         FileUtils.rm(sample_output_path)
 1281       end
 1282     end
 1283 
 1284     test 'should convert source file and write to specified file in base_dir' do
 1285       sample_input_path = fixture_path('sample.adoc')
 1286       sample_output_path = fixture_path('result.html')
 1287       fixture_dir = fixture_path('')
 1288       begin
 1289         Asciidoctor.convert_file sample_input_path, to_file: 'result.html', base_dir: fixture_dir
 1290         assert File.exist?(sample_output_path)
 1291         output = File.read(sample_output_path, mode: Asciidoctor::FILE_READ_MODE)
 1292         refute_empty output
 1293         assert_xpath '/html', output, 1
 1294         assert_xpath '/html/head', output, 1
 1295         assert_xpath '/html/body', output, 1
 1296         assert_xpath '/html/head/title[text() = "Document Title"]', output, 1
 1297         assert_xpath '/html/body/*[@id="header"]/h1[text() = "Document Title"]', output, 1
 1298       rescue => e
 1299         flunk e.message
 1300       ensure
 1301         FileUtils.rm(sample_output_path, force: true)
 1302       end
 1303     end
 1304 
 1305     test 'in_place option is ignored when to_file is specified' do
 1306       sample_input_path = fixture_path('sample.adoc')
 1307       sample_output_path = fixture_path('result.html')
 1308       begin
 1309         Asciidoctor.convert_file sample_input_path, to_file: sample_output_path, in_place: true
 1310         assert File.exist?(sample_output_path)
 1311       ensure
 1312         FileUtils.rm(sample_output_path) if File.exist? sample_output_path
 1313       end
 1314     end
 1315 
 1316     test 'in_place option is ignored when to_dir is specified' do
 1317       sample_input_path = fixture_path('sample.adoc')
 1318       sample_output_path = fixture_path('sample.html')
 1319       begin
 1320         Asciidoctor.convert_file sample_input_path, to_dir: File.dirname(sample_output_path), in_place: true
 1321         assert File.exist?(sample_output_path)
 1322       ensure
 1323         FileUtils.rm(sample_output_path) if File.exist? sample_output_path
 1324       end
 1325     end
 1326 
 1327     test 'should set outfilesuffix to match file extension of target file' do
 1328       sample_input = '{outfilesuffix}'
 1329       sample_output_path = fixture_path('result.htm')
 1330       begin
 1331         Asciidoctor.convert sample_input, to_file: sample_output_path
 1332         assert File.exist?(sample_output_path)
 1333         output = File.read(sample_output_path, mode: Asciidoctor::FILE_READ_MODE)
 1334         refute_empty output
 1335         assert_include '<p>.htm</p>', output
 1336       ensure
 1337         FileUtils.rm(sample_output_path)
 1338       end
 1339     end
 1340 
 1341     test 'should respect outfilesuffix soft set from API' do
 1342       sample_input_path = fixture_path('sample.adoc')
 1343       sample_output_path = fixture_path('sample.htm')
 1344       begin
 1345         Asciidoctor.convert_file sample_input_path, to_dir: (File.dirname sample_input_path), attributes: { 'outfilesuffix' => '.htm@' }
 1346         assert File.exist?(sample_output_path)
 1347       ensure
 1348         FileUtils.rm(sample_output_path)
 1349       end
 1350     end
 1351 
 1352     test 'output should be relative to to_dir option' do
 1353       sample_input_path = fixture_path('sample.adoc')
 1354       output_dir = File.join(File.dirname(sample_input_path), 'test_output')
 1355       Dir.mkdir output_dir if !File.exist? output_dir
 1356       sample_output_path = File.join(output_dir, 'sample.html')
 1357       begin
 1358         Asciidoctor.convert_file sample_input_path, to_dir: output_dir
 1359         assert File.exist? sample_output_path
 1360       ensure
 1361         FileUtils.rm(sample_output_path) if File.exist? sample_output_path
 1362         FileUtils.rmdir output_dir
 1363       end
 1364     end
 1365 
 1366     test 'missing directories should be created if mkdirs is enabled' do
 1367       sample_input_path = fixture_path('sample.adoc')
 1368       output_dir = File.join(File.join(File.dirname(sample_input_path), 'test_output'), 'subdir')
 1369       sample_output_path = File.join(output_dir, 'sample.html')
 1370       begin
 1371         Asciidoctor.convert_file sample_input_path, to_dir: output_dir, mkdirs: true
 1372         assert File.exist? sample_output_path
 1373       ensure
 1374         FileUtils.rm(sample_output_path) if File.exist? sample_output_path
 1375         FileUtils.rmdir output_dir
 1376         FileUtils.rmdir File.dirname(output_dir)
 1377       end
 1378     end
 1379 
 1380     # TODO need similar test for when to_dir is specified
 1381     test 'should raise exception if an attempt is made to overwrite input file' do
 1382       sample_input_path = fixture_path('sample.adoc')
 1383 
 1384       assert_raises IOError do
 1385         Asciidoctor.convert_file sample_input_path, attributes: { 'outfilesuffix' => '.adoc' }
 1386       end
 1387     end
 1388 
 1389     test 'to_file should be relative to to_dir when both given' do
 1390       sample_input_path = fixture_path('sample.adoc')
 1391       base_dir = File.dirname(sample_input_path)
 1392       sample_rel_output_path = File.join('test_output', 'result.html')
 1393       output_dir = File.dirname(File.join(base_dir, sample_rel_output_path))
 1394       Dir.mkdir output_dir if !File.exist? output_dir
 1395       sample_output_path = File.join(base_dir, sample_rel_output_path)
 1396       begin
 1397         Asciidoctor.convert_file sample_input_path, to_dir: base_dir, to_file: sample_rel_output_path
 1398         assert File.exist? sample_output_path
 1399       ensure
 1400         FileUtils.rm(sample_output_path) if File.exist? sample_output_path
 1401         FileUtils.rmdir output_dir
 1402       end
 1403     end
 1404 
 1405     test 'should not modify options argument' do
 1406       options = {
 1407         safe: Asciidoctor::SafeMode::SAFE,
 1408         to_file: false,
 1409       }
 1410       options.freeze
 1411       sample_input_path = fixture_path('sample.adoc')
 1412       begin
 1413         Asciidoctor.convert_file sample_input_path, options
 1414       rescue
 1415         flunk %(options argument should not be modified)
 1416       end
 1417     end
 1418 
 1419     test 'should set to_dir option to parent directory of specified output file' do
 1420       sample_input_path = fixture_path 'basic.adoc'
 1421       sample_output_path = fixture_path 'basic.html'
 1422       begin
 1423         doc = Asciidoctor.convert_file sample_input_path, to_file: sample_output_path
 1424         assert_equal File.dirname(sample_output_path), doc.options[:to_dir]
 1425       ensure
 1426         FileUtils.rm(sample_output_path)
 1427       end
 1428     end
 1429 
 1430     test 'should set to_dir option to parent directory of specified output directory and file' do
 1431       sample_input_path = fixture_path 'basic.adoc'
 1432       sample_output_path = fixture_path 'basic.html'
 1433       fixture_base_path = File.dirname sample_output_path
 1434       fixture_parent_path = File.dirname fixture_base_path
 1435       sample_output_relpath = File.join 'fixtures', 'basic.html'
 1436       begin
 1437         doc = Asciidoctor.convert_file sample_input_path, to_dir: fixture_parent_path, to_file: sample_output_relpath
 1438         assert_equal fixture_base_path, doc.options[:to_dir]
 1439       ensure
 1440         FileUtils.rm(sample_output_path)
 1441       end
 1442     end
 1443 
 1444     test 'timings are recorded for each step' do
 1445       sample_input_path = fixture_path 'asciidoc_index.txt'
 1446       Asciidoctor.convert_file sample_input_path, timings: (timings = Asciidoctor::Timings.new), to_file: false
 1447       refute_equal '0.00000', '%05.5f' % timings.read_parse.to_f
 1448       refute_equal '0.00000', '%05.5f' % timings.convert.to_f
 1449       refute_equal timings.read_parse, timings.total
 1450     end
 1451 
 1452     test 'can override syntax highlighter using syntax_highlighters option' do
 1453       syntax_hl = Class.new Asciidoctor::SyntaxHighlighter::Base do
 1454         def highlight?
 1455           true
 1456         end
 1457 
 1458         def highlight node, source, lang, opts
 1459           'highlighted'
 1460         end
 1461       end
 1462       input = <<~'EOS'
 1463       [source,ruby]
 1464       ----
 1465       puts 'Hello, World!'
 1466       ----
 1467       EOS
 1468       output = Asciidoctor.convert input, safe: :safe, syntax_highlighters: { 'coderay' => syntax_hl }, attributes: { 'source-highlighter' => 'coderay' }
 1469       assert_css 'pre.highlight > code[data-lang="ruby"]', output, 1
 1470       assert_xpath '//pre[@class="coderay highlight"]/code[text()="highlighted"]', output, 1
 1471     end
 1472   end
 1473 
 1474   context 'AST' do
 1475     test 'with no author' do
 1476       input = <<~'EOS'
 1477       = Getting Real: The Smarter, Faster, Easier Way to Build a Successful Web Application
 1478 
 1479       Getting Real details the business, design, programming, and marketing principles of 37signals.
 1480       EOS
 1481 
 1482       doc = document_from_string input
 1483       assert_equal 0, doc.authors.size
 1484     end
 1485 
 1486     test 'with one author' do
 1487       input = <<~'EOS'
 1488       = Getting Real: The Smarter, Faster, Easier Way to Build a Successful Web Application
 1489       David Heinemeier Hansson <david@37signals.com>
 1490 
 1491       Getting Real details the business, design, programming, and marketing principles of 37signals.
 1492       EOS
 1493 
 1494       doc = document_from_string input
 1495       authors = doc.authors
 1496       assert_equal 1, authors.size
 1497       author_1 = authors[0]
 1498       assert_equal 'david@37signals.com', author_1.email
 1499       assert_equal 'David Heinemeier Hansson', author_1.name
 1500       assert_equal 'David', author_1.firstname
 1501       assert_equal 'Heinemeier', author_1.middlename
 1502       assert_equal 'Hansson', author_1.lastname
 1503       assert_equal 'DHH', author_1.initials
 1504     end
 1505 
 1506     test 'with two authors' do
 1507       input = <<~'EOS'
 1508       = Getting Real: The Smarter, Faster, Easier Way to Build a Successful Web Application
 1509       David Heinemeier Hansson <david@37signals.com>; Jason Fried <jason@37signals.com>
 1510 
 1511       Getting Real details the business, design, programming, and marketing principles of 37signals.
 1512       EOS
 1513 
 1514       doc = document_from_string input
 1515       authors = doc.authors
 1516       assert_equal 2, authors.size
 1517       author_1 = authors[0]
 1518       assert_equal 'david@37signals.com', author_1.email
 1519       assert_equal 'David Heinemeier Hansson', author_1.name
 1520       assert_equal 'David', author_1.firstname
 1521       assert_equal 'Heinemeier', author_1.middlename
 1522       assert_equal 'Hansson', author_1.lastname
 1523       assert_equal 'DHH', author_1.initials
 1524       author_2 = authors[1]
 1525       assert_equal 'jason@37signals.com', author_2.email
 1526       assert_equal 'Jason Fried', author_2.name
 1527       assert_equal 'Jason', author_2.firstname
 1528       assert_nil author_2.middlename
 1529       assert_equal 'Fried', author_2.lastname
 1530       assert_equal 'JF', author_2.initials
 1531     end
 1532 
 1533     test 'with authors as attributes' do
 1534       input = <<~'EOS'
 1535       = Getting Real: The Smarter, Faster, Easier Way to Build a Successful Web Application
 1536       :author_1: David Heinemeier Hansson
 1537       :email_1: david@37signals.com
 1538       :author_2: Jason Fried
 1539       :email_2: jason@37signals.com
 1540 
 1541       Getting Real details the business, design, programming, and marketing principles of 37signals.
 1542       EOS
 1543 
 1544       doc = document_from_string input
 1545       authors = doc.authors
 1546       assert_equal 2, authors.size
 1547       author_1 = authors[0]
 1548       assert_equal 'david@37signals.com', author_1.email
 1549       assert_equal 'David Heinemeier Hansson', author_1.name
 1550       assert_equal 'David', author_1.firstname
 1551       assert_equal 'Heinemeier', author_1.middlename
 1552       assert_equal 'Hansson', author_1.lastname
 1553       assert_equal 'DHH', author_1.initials
 1554       author_2 = authors[1]
 1555       assert_equal 'jason@37signals.com', author_2.email
 1556       assert_equal 'Jason Fried', author_2.name
 1557       assert_equal 'Jason', author_2.firstname
 1558       assert_nil author_2.middlename
 1559       assert_equal 'Fried', author_2.lastname
 1560       assert_equal 'JF', author_2.initials
 1561     end
 1562 
 1563     test 'should not crash if nil cell text is passed to Cell constructor' do
 1564       input = <<~'EOS'
 1565       |===
 1566       |a
 1567       |===
 1568       EOS
 1569       table = (document_from_string input).blocks[0]
 1570       cell = Asciidoctor::Table::Cell.new table.rows.body[0][0].column, nil, {}
 1571       refute cell.style
 1572       assert_same Asciidoctor::AbstractNode::NORMAL_SUBS, cell.subs
 1573       assert_equal '', cell.text
 1574     end
 1575 
 1576     test 'should set option on node when set_option is called' do
 1577       input = <<~'EOS'
 1578       . three
 1579       . two
 1580       . one
 1581       EOS
 1582 
 1583       block = (document_from_string input).blocks[0]
 1584       block.set_option('reversed')
 1585       assert block.option? 'reversed'
 1586       assert_equal '', block.attributes['reversed-option']
 1587     end
 1588 
 1589     test 'enabled_options should return all options which are set' do
 1590       input = <<~'EOS'
 1591       [%interactive]
 1592       * [x] code
 1593       * [ ] test
 1594       * [ ] profit
 1595       EOS
 1596 
 1597       block = (document_from_string input).blocks[0]
 1598       assert_equal %w(checklist interactive).to_set, block.enabled_options
 1599     end
 1600 
 1601     test 'should append option to existing options' do
 1602       input = <<~'EOS'
 1603       [%fancy]
 1604       . three
 1605       . two
 1606       . one
 1607       EOS
 1608 
 1609       block = (document_from_string input).blocks[0]
 1610       block.set_option('reversed')
 1611       assert block.option? 'fancy'
 1612       assert block.option? 'reversed'
 1613     end
 1614 
 1615     test 'should not append option if option is already set' do
 1616       input = <<~'EOS'
 1617       [%reversed]
 1618       . three
 1619       . two
 1620       . one
 1621       EOS
 1622 
 1623       block = (document_from_string input).blocks[0]
 1624       refute block.set_option('reversed')
 1625       assert_equal '', block.attributes['reversed-option']
 1626     end
 1627 
 1628     test 'should return set of option names' do
 1629       input = <<~'EOS'
 1630       [%compact%reversed]
 1631       . three
 1632       . two
 1633       . one
 1634       EOS
 1635 
 1636       block = (document_from_string input).blocks[0]
 1637       assert_equal %w(compact reversed).to_set, block.enabled_options
 1638     end
 1639 
 1640     test 'table column should not be a block or inline' do
 1641       input = <<~'EOS'
 1642       |===
 1643       |a
 1644       |===
 1645       EOS
 1646 
 1647       col = (document_from_string input).blocks[0].columns[0]
 1648       refute col.block?
 1649       refute col.inline?
 1650     end
 1651 
 1652     test 'table cell should be a block' do
 1653       input = <<~'EOS'
 1654       |===
 1655       |a
 1656       |===
 1657       EOS
 1658 
 1659       cell = (document_from_string input).blocks[0].rows.body[0][0]
 1660       assert_kind_of ::Asciidoctor::AbstractBlock, cell
 1661       assert cell.block?
 1662       refute cell.inline?
 1663     end
 1664 
 1665     test 'next_adjacent_block should return next block' do
 1666       input = <<~'EOS'
 1667       first
 1668 
 1669       second
 1670       EOS
 1671 
 1672       doc = document_from_string input
 1673       assert_equal doc.blocks[1], doc.blocks[0].next_adjacent_block
 1674     end
 1675 
 1676     test 'next_adjacent_block should return next sibling of parent if called on last sibling' do
 1677       input = <<~'EOS'
 1678       --
 1679       first
 1680       --
 1681 
 1682       second
 1683       EOS
 1684 
 1685       doc = document_from_string input
 1686       assert_equal doc.blocks[1], doc.blocks[0].blocks[0].next_adjacent_block
 1687     end
 1688 
 1689     test 'next_adjacent_block should return next sibling of list if called on last item' do
 1690       input = <<~'EOS'
 1691       * first
 1692 
 1693       second
 1694       EOS
 1695 
 1696       doc = document_from_string input
 1697       assert_equal doc.blocks[1], doc.blocks[0].blocks[0].next_adjacent_block
 1698     end
 1699 
 1700     test 'next_adjacent_block should return next item in dlist if called on last block of list item' do
 1701       input = <<~'EOS'
 1702       first::
 1703       desc
 1704       +
 1705       more desc
 1706 
 1707       second::
 1708       desc
 1709       EOS
 1710 
 1711       doc = document_from_string input
 1712       assert_equal doc.blocks[0].items[1], doc.blocks[0].items[0][1].blocks[0].next_adjacent_block
 1713     end
 1714   end
 1715 end