"Fossies" - the Fresh Open Source Software Archive

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


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

    1 # frozen_string_literal: true
    2 require_relative 'test_helper'
    3 
    4 class ExtensionsInitTest < Minitest::Test
    5   def test_autoload
    6     doc = empty_document
    7     refute doc.extensions?, 'Extensions should not be enabled by default'
    8 
    9     begin
   10       # NOTE trigger extensions to autoload by registering empty group
   11       Asciidoctor::Extensions.register do
   12       end
   13     rescue; end
   14 
   15     doc = empty_document
   16     assert doc.extensions?, 'Extensions should be enabled after being autoloaded'
   17 
   18     self.class.remove_tests self.class
   19   ensure
   20     Asciidoctor::Extensions.unregister_all
   21   end
   22   self
   23 end.new(nil).test_autoload
   24 
   25 class SamplePreprocessor < Asciidoctor::Extensions::Preprocessor
   26   def process doc, reader
   27     nil
   28   end
   29 end
   30 
   31 class SampleIncludeProcessor < Asciidoctor::Extensions::IncludeProcessor
   32 end
   33 
   34 class SampleDocinfoProcessor < Asciidoctor::Extensions::DocinfoProcessor
   35 end
   36 
   37 # NOTE intentionally using the deprecated name
   38 class SampleTreeprocessor < Asciidoctor::Extensions::Treeprocessor
   39   def process document
   40     nil
   41   end
   42 end
   43 SampleTreeProcessor = SampleTreeprocessor
   44 
   45 class SamplePostprocessor < Asciidoctor::Extensions::Postprocessor
   46 end
   47 
   48 class SampleBlock < Asciidoctor::Extensions::BlockProcessor
   49 end
   50 
   51 class SampleBlockMacro < Asciidoctor::Extensions::BlockMacroProcessor
   52 end
   53 
   54 class SampleInlineMacro < Asciidoctor::Extensions::InlineMacroProcessor
   55 end
   56 
   57 class ScrubHeaderPreprocessor < Asciidoctor::Extensions::Preprocessor
   58   def process doc, reader
   59     lines = reader.lines
   60     skipped = []
   61     while !lines.empty? && !lines.first.start_with?('=')
   62       skipped << lines.shift
   63       reader.advance
   64     end
   65     doc.set_attr 'skipped', (skipped * "\n")
   66     reader
   67   end
   68 end
   69 
   70 class BoilerplateTextIncludeProcessor < Asciidoctor::Extensions::IncludeProcessor
   71   def handles? target
   72     target.end_with? '.txt'
   73   end
   74 
   75   def process document, reader, target, attributes
   76     case target
   77     when 'lorem-ipsum.txt'
   78       content = ["Lorem ipsum dolor sit amet...\n"]
   79       reader.push_include content, target, target, 1, attributes
   80     else
   81       nil
   82     end
   83   end
   84 end
   85 
   86 class ReplaceAuthorTreeProcessor < Asciidoctor::Extensions::TreeProcessor
   87   def process document
   88     document.attributes['firstname'] = 'Ghost'
   89     document.attributes['author'] = 'Ghost Writer'
   90     document
   91   end
   92 end
   93 
   94 class ReplaceTreeTreeProcessor < Asciidoctor::Extensions::TreeProcessor
   95   def process document
   96     if document.doctitle == 'Original Document'
   97       Asciidoctor.load %(== Replacement Document\nReplacement Author\n\ncontent)
   98     else
   99       document
  100     end
  101   end
  102 end
  103 
  104 class SelfSigningTreeProcessor < Asciidoctor::Extensions::TreeProcessor
  105   def process document
  106     document << (create_paragraph document, self.class.name, {})
  107     nil
  108   end
  109 end
  110 
  111 class StripAttributesPostprocessor < Asciidoctor::Extensions::Postprocessor
  112   def process document, output
  113     output.gsub(/<(\w+).*?>/m, "<\\1>")
  114   end
  115 end
  116 
  117 class UppercaseBlock < Asciidoctor::Extensions::BlockProcessor; use_dsl
  118   named :yell
  119   on_context :paragraph
  120   name_positional_attributes 'chars'
  121   parse_content_as :simple
  122   def process parent, reader, attributes
  123     if (chars = attributes['chars'])
  124       upcase_chars = chars.upcase
  125       create_paragraph parent, reader.lines.map {|l| l.downcase.tr chars, upcase_chars }, attributes
  126     else
  127       create_paragraph parent, reader.lines.map(&:upcase), attributes
  128     end
  129   end
  130 end
  131 
  132 class SnippetMacro < Asciidoctor::Extensions::BlockMacroProcessor
  133   def process parent, target, attributes
  134     create_pass_block parent, %(<script src="http://example.com/#{target}.js?_mode=#{attributes['mode']}"></script>), {}, content_model: :raw
  135   end
  136 end
  137 
  138 class LegacyPosAttrsBlockMacro < Asciidoctor::Extensions::BlockMacroProcessor
  139   option :pos_attrs, ['target', 'format']
  140   def process parent, _, attrs
  141     create_image_block parent, { 'target' => %(#{attrs['target']}.#{attrs['format']}) }
  142   end
  143 end
  144 
  145 class TemperatureMacro < Asciidoctor::Extensions::InlineMacroProcessor; use_dsl
  146   named :degrees
  147   resolve_attributes '1:units', 'precision=1'
  148   def process parent, target, attributes
  149     units = attributes['units'] || (parent.document.attr 'temperature-unit', 'C')
  150     precision = attributes['precision'].to_i
  151     c = target.to_f
  152     case units
  153     when 'C'
  154       create_inline parent, :quoted, %(#{c.round precision} &#176;C), type: :unquoted
  155     when 'F'
  156       create_inline parent, :quoted, %(#{(c * 1.8 + 32).round precision} &#176;F), type: :unquoted
  157     else
  158       raise ::ArgumentError, %(Unknown temperature units: #{units})
  159     end
  160   end
  161 end
  162 
  163 class MetaRobotsDocinfoProcessor < Asciidoctor::Extensions::DocinfoProcessor
  164   def process document
  165     '<meta name="robots" content="index,follow">'
  166   end
  167 end
  168 
  169 class MetaAppDocinfoProcessor < Asciidoctor::Extensions::DocinfoProcessor; use_dsl
  170   at_location :head
  171 
  172   def process document
  173     '<meta name="application-name" content="Asciidoctor App">'
  174   end
  175 end
  176 
  177 class SampleExtensionGroup < Asciidoctor::Extensions::Group
  178   def activate registry
  179     registry.document.attributes['activate-method-called'] = ''
  180     registry.preprocessor SamplePreprocessor
  181   end
  182 end
  183 
  184 def create_cat_in_sink_block_macro
  185   Asciidoctor::Extensions.create do
  186     block_macro do
  187       named :cat_in_sink
  188       process do |parent, target, attrs|
  189         image_attrs = {}
  190         unless target.nil_or_empty?
  191           image_attrs['target'] = %(cat-in-sink-day-#{target}.png)
  192         end
  193         if (title = attrs.delete 'title')
  194           image_attrs['title'] = title
  195         end
  196         if (alt = attrs.delete 1)
  197           image_attrs['alt'] = alt
  198         end
  199         create_image_block parent, image_attrs
  200       end
  201     end
  202   end
  203 end
  204 
  205 def create_santa_list_block_macro
  206   Asciidoctor::Extensions.create do
  207     block_macro do
  208       named :santa_list
  209       process do |parent, target|
  210         list = create_list parent, target
  211         guillaume = (create_list_item list, 'Guillaume')
  212         guillaume.add_role('friendly')
  213         guillaume.id = 'santa-list-guillaume'
  214         list << guillaume
  215         robert = (create_list_item list, 'Robert')
  216         robert.add_role('kind')
  217         robert.add_role('contributor')
  218         robert.add_role('java')
  219         list << robert
  220         pepijn = (create_list_item list, 'Pepijn')
  221         pepijn.id = 'santa-list-pepijn'
  222         list << pepijn
  223         dan = (create_list_item list, 'Dan')
  224         dan.add_role('naughty')
  225         dan.id = 'santa-list-dan'
  226         list << dan
  227         sarah = (create_list_item list, 'Sarah')
  228         list << sarah
  229         list
  230       end
  231     end
  232   end
  233 end
  234 
  235 context 'Extensions' do
  236   context 'Register' do
  237     test 'should not activate registry if no extension groups are registered' do
  238       assert defined? Asciidoctor::Extensions
  239       doc = empty_document
  240       refute doc.extensions?, 'Extensions should not be enabled if not groups are registered'
  241     end
  242 
  243     test 'should register extension group class' do
  244       begin
  245         Asciidoctor::Extensions.register :sample, SampleExtensionGroup
  246         refute_nil Asciidoctor::Extensions.groups
  247         assert_equal 1, Asciidoctor::Extensions.groups.size
  248         assert_equal SampleExtensionGroup, Asciidoctor::Extensions.groups[:sample]
  249       ensure
  250         Asciidoctor::Extensions.unregister_all
  251       end
  252     end
  253 
  254     test 'should self register extension group class' do
  255       begin
  256         SampleExtensionGroup.register :sample
  257         refute_nil Asciidoctor::Extensions.groups
  258         assert_equal 1, Asciidoctor::Extensions.groups.size
  259         assert_equal SampleExtensionGroup, Asciidoctor::Extensions.groups[:sample]
  260       ensure
  261         Asciidoctor::Extensions.unregister_all
  262       end
  263     end
  264 
  265     test 'should register extension group from class name' do
  266       begin
  267         Asciidoctor::Extensions.register :sample, 'SampleExtensionGroup'
  268         refute_nil Asciidoctor::Extensions.groups
  269         assert_equal 1, Asciidoctor::Extensions.groups.size
  270         assert_equal SampleExtensionGroup, Asciidoctor::Extensions.groups[:sample]
  271       ensure
  272         Asciidoctor::Extensions.unregister_all
  273       end
  274     end
  275 
  276     test 'should register extension group from instance' do
  277       begin
  278         Asciidoctor::Extensions.register :sample, SampleExtensionGroup.new
  279         refute_nil Asciidoctor::Extensions.groups
  280         assert_equal 1, Asciidoctor::Extensions.groups.size
  281         assert_kind_of SampleExtensionGroup, Asciidoctor::Extensions.groups[:sample]
  282       ensure
  283         Asciidoctor::Extensions.unregister_all
  284       end
  285     end
  286 
  287     test 'should register extension block' do
  288       begin
  289         Asciidoctor::Extensions.register :sample do
  290         end
  291         refute_nil Asciidoctor::Extensions.groups
  292         assert_equal 1, Asciidoctor::Extensions.groups.size
  293         assert_kind_of Proc, Asciidoctor::Extensions.groups[:sample]
  294       ensure
  295         Asciidoctor::Extensions.unregister_all
  296       end
  297     end
  298 
  299     test 'should coerce group name to symbol when registering' do
  300       begin
  301         Asciidoctor::Extensions.register 'sample', SampleExtensionGroup
  302         refute_nil Asciidoctor::Extensions.groups
  303         assert_equal 1, Asciidoctor::Extensions.groups.size
  304         assert_equal SampleExtensionGroup, Asciidoctor::Extensions.groups[:sample]
  305       ensure
  306         Asciidoctor::Extensions.unregister_all
  307       end
  308     end
  309 
  310     test 'should unregister extension group by symbol name' do
  311       begin
  312         Asciidoctor::Extensions.register :sample, SampleExtensionGroup
  313         refute_nil Asciidoctor::Extensions.groups
  314         assert_equal 1, Asciidoctor::Extensions.groups.size
  315         Asciidoctor::Extensions.unregister :sample
  316         assert_equal 0, Asciidoctor::Extensions.groups.size
  317       ensure
  318         Asciidoctor::Extensions.unregister_all
  319       end
  320     end
  321 
  322     test 'should unregister extension group by string name' do
  323       begin
  324         Asciidoctor::Extensions.register :sample, SampleExtensionGroup
  325         refute_nil Asciidoctor::Extensions.groups
  326         assert_equal 1, Asciidoctor::Extensions.groups.size
  327         Asciidoctor::Extensions.unregister 'sample'
  328         assert_equal 0, Asciidoctor::Extensions.groups.size
  329       ensure
  330         Asciidoctor::Extensions.unregister_all
  331       end
  332     end
  333 
  334     test 'should unregister multiple extension groups by name' do
  335       begin
  336         Asciidoctor::Extensions.register :sample1, SampleExtensionGroup
  337         Asciidoctor::Extensions.register :sample2, SampleExtensionGroup
  338         refute_nil Asciidoctor::Extensions.groups
  339         assert_equal 2, Asciidoctor::Extensions.groups.size
  340         Asciidoctor::Extensions.unregister :sample1, :sample2
  341         assert_equal 0, Asciidoctor::Extensions.groups.size
  342       ensure
  343         Asciidoctor::Extensions.unregister_all
  344       end
  345     end
  346 
  347     test 'should raise NameError if extension class cannot be resolved from string' do
  348       begin
  349         Asciidoctor::Extensions.register do
  350           block 'foobar'
  351         end
  352         empty_document
  353         flunk 'Expecting RuntimeError to be raised'
  354       rescue NameError => e
  355         assert_equal 'Could not resolve class for name: foobar', e.message
  356       ensure
  357         Asciidoctor::Extensions.unregister_all
  358       end
  359     end
  360 
  361     test 'should allow standalone registry to be created but not registered' do
  362       registry = Asciidoctor::Extensions.create 'sample' do
  363         block do
  364           named :whisper
  365           on_context :paragraph
  366           parse_content_as :simple
  367           def process parent, reader, attributes
  368             create_paragraph parent, reader.lines.map(&:downcase), attributes
  369           end
  370         end
  371       end
  372 
  373       assert_instance_of Asciidoctor::Extensions::Registry, registry
  374       refute_nil registry.groups
  375       assert_equal 1, registry.groups.size
  376       assert_equal 'sample', registry.groups.keys.first
  377       assert_equal 0, Asciidoctor::Extensions.groups.size
  378     end
  379   end
  380 
  381   context 'Activate' do
  382     test 'should call activate on extension group class' do
  383       begin
  384         doc = Asciidoctor::Document.new
  385         Asciidoctor::Extensions.register :sample, SampleExtensionGroup
  386         registry = Asciidoctor::Extensions::Registry.new
  387         registry.activate doc
  388         assert doc.attr? 'activate-method-called'
  389         assert registry.preprocessors?
  390       ensure
  391         Asciidoctor::Extensions.unregister_all
  392       end
  393     end
  394 
  395     test 'should invoke extension block' do
  396       begin
  397         doc = Asciidoctor::Document.new
  398         Asciidoctor::Extensions.register do
  399           @document.attributes['block-called'] = ''
  400           preprocessor SamplePreprocessor
  401         end
  402         registry = Asciidoctor::Extensions::Registry.new
  403         registry.activate doc
  404         assert doc.attr? 'block-called'
  405         assert registry.preprocessors?
  406       ensure
  407         Asciidoctor::Extensions.unregister_all
  408       end
  409     end
  410 
  411     test 'should create registry in Document if extensions are loaded' do
  412       begin
  413         SampleExtensionGroup.register
  414         doc = Asciidoctor::Document.new
  415         assert doc.extensions?
  416         assert_kind_of Asciidoctor::Extensions::Registry, doc.extensions
  417       ensure
  418         Asciidoctor::Extensions.unregister_all
  419       end
  420 
  421     end
  422   end
  423 
  424   context 'Instantiate' do
  425     test 'should instantiate preprocessors' do
  426       registry = Asciidoctor::Extensions::Registry.new
  427       registry.preprocessor SamplePreprocessor
  428       registry.activate Asciidoctor::Document.new
  429       assert registry.preprocessors?
  430       extensions = registry.preprocessors
  431       assert_equal 1, extensions.size
  432       assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first
  433       assert_kind_of SamplePreprocessor, extensions.first.instance
  434       assert_kind_of Method, extensions.first.process_method
  435     end
  436 
  437     test 'should instantiate include processors' do
  438       registry = Asciidoctor::Extensions::Registry.new
  439       registry.include_processor SampleIncludeProcessor
  440       registry.activate Asciidoctor::Document.new
  441       assert registry.include_processors?
  442       extensions = registry.include_processors
  443       assert_equal 1, extensions.size
  444       assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first
  445       assert_kind_of SampleIncludeProcessor, extensions.first.instance
  446       assert_kind_of Method, extensions.first.process_method
  447     end
  448 
  449     test 'should instantiate docinfo processors' do
  450       registry = Asciidoctor::Extensions::Registry.new
  451       registry.docinfo_processor SampleDocinfoProcessor
  452       registry.activate Asciidoctor::Document.new
  453       assert registry.docinfo_processors?
  454       assert registry.docinfo_processors?(:head)
  455       extensions = registry.docinfo_processors
  456       assert_equal 1, extensions.size
  457       assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first
  458       assert_kind_of SampleDocinfoProcessor, extensions.first.instance
  459       assert_kind_of Method, extensions.first.process_method
  460     end
  461 
  462     # NOTE intentionally using the legacy names
  463     test 'should instantiate tree processors' do
  464       registry = Asciidoctor::Extensions::Registry.new
  465       registry.treeprocessor SampleTreeprocessor
  466       registry.activate Asciidoctor::Document.new
  467       assert registry.treeprocessors?
  468       extensions = registry.treeprocessors
  469       assert_equal 1, extensions.size
  470       assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first
  471       assert_kind_of SampleTreeprocessor, extensions.first.instance
  472       assert_kind_of Method, extensions.first.process_method
  473     end
  474 
  475     test 'should instantiate postprocessors' do
  476       registry = Asciidoctor::Extensions::Registry.new
  477       registry.postprocessor SamplePostprocessor
  478       registry.activate Asciidoctor::Document.new
  479       assert registry.postprocessors?
  480       extensions = registry.postprocessors
  481       assert_equal 1, extensions.size
  482       assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first
  483       assert_kind_of SamplePostprocessor, extensions.first.instance
  484       assert_kind_of Method, extensions.first.process_method
  485     end
  486 
  487     test 'should instantiate block processor' do
  488       registry = Asciidoctor::Extensions::Registry.new
  489       registry.block SampleBlock, :sample
  490       registry.activate Asciidoctor::Document.new
  491       assert registry.blocks?
  492       assert registry.registered_for_block? :sample, :paragraph
  493       extension = registry.find_block_extension :sample
  494       assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extension
  495       assert_kind_of SampleBlock, extension.instance
  496       assert_kind_of Method, extension.process_method
  497     end
  498 
  499     test 'should not match block processor for unsupported context' do
  500       registry = Asciidoctor::Extensions::Registry.new
  501       registry.block SampleBlock, :sample
  502       registry.activate Asciidoctor::Document.new
  503       refute registry.registered_for_block? :sample, :sidebar
  504     end
  505 
  506     test 'should instantiate block macro processor' do
  507       registry = Asciidoctor::Extensions::Registry.new
  508       registry.block_macro SampleBlockMacro, 'sample'
  509       registry.activate Asciidoctor::Document.new
  510       assert registry.block_macros?
  511       assert registry.registered_for_block_macro? 'sample'
  512       extension = registry.find_block_macro_extension 'sample'
  513       assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extension
  514       assert_kind_of SampleBlockMacro, extension.instance
  515       assert_kind_of Method, extension.process_method
  516     end
  517 
  518     test 'should instantiate inline macro processor' do
  519       registry = Asciidoctor::Extensions::Registry.new
  520       registry.inline_macro SampleInlineMacro, 'sample'
  521       registry.activate Asciidoctor::Document.new
  522       assert registry.inline_macros?
  523       assert registry.registered_for_inline_macro? 'sample'
  524       extension = registry.find_inline_macro_extension 'sample'
  525       assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extension
  526       assert_kind_of SampleInlineMacro, extension.instance
  527       assert_kind_of Method, extension.process_method
  528     end
  529 
  530     test 'should allow processors to be registered by a string name' do
  531       registry = Asciidoctor::Extensions::Registry.new
  532       registry.preprocessor 'SamplePreprocessor'
  533       registry.activate Asciidoctor::Document.new
  534       assert registry.preprocessors?
  535       extensions = registry.preprocessors
  536       assert_equal 1, extensions.size
  537       assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first
  538     end
  539   end
  540 
  541   context 'Integration' do
  542     test 'can provide extension registry as an option' do
  543       registry = Asciidoctor::Extensions.create do
  544         tree_processor SampleTreeProcessor
  545       end
  546 
  547       doc = document_from_string %(= Document Title\n\ncontent), extension_registry: registry
  548       refute_nil doc.extensions
  549       assert_equal 1, doc.extensions.groups.size
  550       assert doc.extensions.tree_processors?
  551       assert_equal 1, doc.extensions.tree_processors.size
  552       assert_equal 0, Asciidoctor::Extensions.groups.size
  553     end
  554 
  555     # NOTE I'm not convinced we want to continue to support this use case
  556     test 'can provide extension registry created without any groups as option' do
  557       registry = Asciidoctor::Extensions.create
  558       registry.tree_processor SampleTreeProcessor
  559 
  560       doc = document_from_string %(= Document Title\n\ncontent), extension_registry: registry
  561       refute_nil doc.extensions
  562       assert_equal 0, doc.extensions.groups.size
  563       assert doc.extensions.tree_processors?
  564       assert_equal 1, doc.extensions.tree_processors.size
  565       assert_equal 0, Asciidoctor::Extensions.groups.size
  566     end
  567 
  568     test 'can provide extensions proc as option' do
  569       doc = document_from_string %(= Document Title\n\ncontent), extensions: proc { tree_processor SampleTreeProcessor }
  570       refute_nil doc.extensions
  571       assert_equal 1, doc.extensions.groups.size
  572       assert doc.extensions.tree_processors?
  573       assert_equal 1, doc.extensions.tree_processors.size
  574       assert_equal 0, Asciidoctor::Extensions.groups.size
  575     end
  576 
  577     test 'should invoke preprocessors before parsing document' do
  578       input = <<~'EOS'
  579       junk line
  580 
  581       = Document Title
  582 
  583       sample content
  584       EOS
  585 
  586       begin
  587         Asciidoctor::Extensions.register do
  588           preprocessor ScrubHeaderPreprocessor
  589         end
  590 
  591         doc = document_from_string input
  592         assert doc.attr? 'skipped'
  593         assert_equal 'junk line', (doc.attr 'skipped').strip
  594         assert doc.has_header?
  595         assert_equal 'Document Title', doc.doctitle
  596       ensure
  597         Asciidoctor::Extensions.unregister_all
  598       end
  599     end
  600 
  601     test 'should invoke include processor to process include directive' do
  602       input = <<~'EOS'
  603       before
  604 
  605       include::lorem-ipsum.txt[]
  606 
  607       after
  608       EOS
  609 
  610       begin
  611         Asciidoctor::Extensions.register do
  612           include_processor BoilerplateTextIncludeProcessor
  613         end
  614 
  615         # a custom include processor is not affected by the safe mode
  616         result = convert_string input, safe: :secure
  617         assert_css '.paragraph > p', result, 3
  618         assert_includes result, 'before'
  619         assert_includes result, 'Lorem ipsum'
  620         assert_includes result, 'after'
  621       ensure
  622         Asciidoctor::Extensions.unregister_all
  623       end
  624     end
  625 
  626     test 'should invoke include processor if it offers to handle include directive' do
  627       input = <<~'EOS'
  628       include::skip-me.adoc[]
  629       line after skip
  630 
  631       include::include-file.adoc[]
  632 
  633       include::fixtures/grandchild-include.adoc[]
  634 
  635       last line
  636       EOS
  637 
  638       registry = Asciidoctor::Extensions.create do
  639         include_processor do
  640           handles? do |target|
  641             target == 'skip-me.adoc'
  642           end
  643 
  644           process do |doc, reader, target, attributes|
  645           end
  646         end
  647 
  648         include_processor do
  649           handles? do |target|
  650             target == 'include-file.adoc'
  651           end
  652 
  653           process do |doc, reader, target, attributes|
  654             # demonstrates that push_include normalizes newlines
  655             content = [
  656               %(found include target '#{target}' at line #{reader.cursor_at_prev_line.lineno}\r\n),
  657               %(\r\n),
  658               %(middle line\r\n)
  659             ]
  660             reader.push_include content, target, target, 1, attributes
  661           end
  662         end
  663       end
  664       # safe mode only required for built-in include processor
  665       document = empty_document base_dir: testdir, extension_registry: registry, safe: :safe
  666       reader = Asciidoctor::PreprocessorReader.new document, input, nil, normalize: true
  667       lines = []
  668       lines << reader.read_line
  669       assert_equal 'line after skip', lines.last
  670       lines << reader.read_line
  671       lines << reader.read_line
  672       assert_equal 'found include target \'include-file.adoc\' at line 4', lines.last
  673       assert_equal 'include-file.adoc: line 2', reader.line_info
  674       while reader.has_more_lines?
  675         lines << reader.read_line
  676       end
  677       source = lines * ::Asciidoctor::LF
  678       assert_match(/^found include target 'include-file.adoc' at line 4$/, source)
  679       assert_match(/^middle line$/, source)
  680       assert_match(/^last line of grandchild$/, source)
  681       assert_match(/^last line$/, source)
  682     end
  683 
  684     test 'should invoke tree processors after parsing document' do
  685       input = <<~'EOS'
  686       = Document Title
  687       Doc Writer
  688 
  689       content
  690       EOS
  691 
  692       begin
  693         Asciidoctor::Extensions.register do
  694           tree_processor ReplaceAuthorTreeProcessor
  695         end
  696 
  697         doc = document_from_string input
  698         assert_equal 'Ghost Writer', doc.author
  699       ensure
  700         Asciidoctor::Extensions.unregister_all
  701       end
  702     end
  703 
  704     test 'should set source_location on document before invoking tree processors' do
  705       begin
  706         Asciidoctor::Extensions.register do
  707           tree_processor do
  708             process do |doc|
  709               para = create_paragraph doc.blocks.last.parent, %(file: #{doc.file}, lineno: #{doc.lineno}), {}
  710               doc << para
  711             end
  712           end
  713         end
  714 
  715         sample_doc = fixture_path 'sample.adoc'
  716         doc = Asciidoctor.load_file sample_doc, sourcemap: true
  717         assert_includes doc.convert, 'file: sample.adoc, lineno: 1'
  718       ensure
  719         Asciidoctor::Extensions.unregister_all
  720       end
  721     end
  722 
  723     test 'should allow tree processor to replace tree' do
  724       input = <<~'EOS'
  725       = Original Document
  726       Doc Writer
  727 
  728       content
  729       EOS
  730 
  731       begin
  732         Asciidoctor::Extensions.register do
  733           tree_processor ReplaceTreeTreeProcessor
  734         end
  735 
  736         doc = document_from_string input
  737         assert_equal 'Replacement Document', doc.doctitle
  738       ensure
  739         Asciidoctor::Extensions.unregister_all
  740       end
  741     end
  742 
  743     test 'should honor block title assigned in tree processor' do
  744       input = <<~'EOS'
  745       = Document Title
  746       :!example-caption:
  747 
  748       .Old block title
  749       ====
  750       example block content
  751       ====
  752       EOS
  753 
  754       old_title = nil
  755       begin
  756         Asciidoctor::Extensions.register do
  757           tree_processor do
  758             process do |doc|
  759               ex = (doc.find_by context: :example)[0]
  760               old_title = ex.title
  761               ex.title = 'New block title'
  762             end
  763           end
  764         end
  765 
  766         doc = document_from_string input
  767         assert_equal 'Old block title', old_title
  768         assert_equal 'New block title', (doc.find_by context: :example)[0].title
  769       ensure
  770         Asciidoctor::Extensions.unregister_all
  771       end
  772     end
  773 
  774     test 'should be able to register preferred tree processor' do
  775       begin
  776         Asciidoctor::Extensions.register do
  777           tree_processor do
  778             process do |doc|
  779               doc << (create_paragraph doc, 'd', {})
  780               nil
  781             end
  782           end
  783 
  784           tree_processor do
  785             prefer
  786             process do |doc|
  787               doc << (create_paragraph doc, 'c', {})
  788               nil
  789             end
  790           end
  791 
  792           prefer :tree_processor do
  793             process do |doc|
  794               doc << (create_paragraph doc, 'b', {})
  795               nil
  796             end
  797           end
  798 
  799           prefer (tree_processor do
  800             process do |doc|
  801               doc << (create_paragraph doc, 'a', {})
  802               nil
  803             end
  804           end)
  805 
  806           prefer :tree_processor, SelfSigningTreeProcessor
  807         end
  808 
  809         (doc = empty_document).convert
  810         assert_equal %w(SelfSigningTreeProcessor a b c d), doc.blocks.map(&:lines).map(&:first)
  811       ensure
  812         Asciidoctor::Extensions.unregister_all
  813       end
  814     end
  815 
  816     test 'should invoke postprocessors after converting document' do
  817       input = <<~'EOS'
  818       * one
  819       * two
  820       * three
  821       EOS
  822 
  823       begin
  824         Asciidoctor::Extensions.register do
  825           postprocessor StripAttributesPostprocessor
  826         end
  827 
  828         output = convert_string input
  829         refute_match(/<div class="ulist">/, output)
  830       ensure
  831         Asciidoctor::Extensions.unregister_all
  832       end
  833     end
  834 
  835     test 'should yield to document processor block if block has non-zero arity' do
  836       input = <<~'EOS'
  837       hi!
  838       EOS
  839 
  840       begin
  841         Asciidoctor::Extensions.register do
  842           tree_processor do |processor|
  843             processor.process do |doc|
  844               doc << (create_paragraph doc, 'bye!', {})
  845             end
  846           end
  847         end
  848 
  849         output = convert_string_to_embedded input
  850         assert_xpath '//p', output, 2
  851         assert_xpath '//p[text()="hi!"]', output, 1
  852         assert_xpath '//p[text()="bye!"]', output, 1
  853       ensure
  854         Asciidoctor::Extensions.unregister_all
  855       end
  856     end
  857 
  858     test 'should invoke processor for custom block' do
  859       input = <<~'EOS'
  860       [yell]
  861       Hi there!
  862 
  863       [yell,chars=aeiou]
  864       Hi there!
  865       EOS
  866 
  867       begin
  868         Asciidoctor::Extensions.register do
  869           block UppercaseBlock
  870         end
  871 
  872         output = convert_string_to_embedded input
  873         assert_xpath '//p', output, 2
  874         assert_xpath '(//p)[1][text()="HI THERE!"]', output, 1
  875         assert_xpath '(//p)[2][text()="hI thErE!"]', output, 1
  876       ensure
  877         Asciidoctor::Extensions.unregister_all
  878       end
  879     end
  880 
  881     test 'should invoke processor for custom block in an AsciiDoc table cell' do
  882       input = <<~'EOS'
  883       |===
  884       a|
  885       [yell]
  886       Hi there!
  887       |===
  888       EOS
  889 
  890       begin
  891         Asciidoctor::Extensions.register do
  892           block UppercaseBlock
  893         end
  894 
  895         output = convert_string_to_embedded input
  896         assert_xpath '/table//p', output, 1
  897         assert_xpath '/table//p[text()="HI THERE!"]', output, 1
  898       ensure
  899         Asciidoctor::Extensions.unregister_all
  900       end
  901     end
  902 
  903     test 'should yield to syntax processor block if block has non-zero arity' do
  904       input = <<~'EOS'
  905       [eval]
  906       ....
  907       'yolo' * 5
  908       ....
  909       EOS
  910 
  911       begin
  912         Asciidoctor::Extensions.register do
  913           block :eval do |processor|
  914             processor.on_context :literal
  915             processor.process do |parent, reader, attrs|
  916               create_paragraph parent, (eval reader.read_lines[0]), {}
  917             end
  918           end
  919         end
  920 
  921         output = convert_string_to_embedded input
  922         assert_xpath '//p[text()="yoloyoloyoloyoloyolo"]', output, 1
  923       ensure
  924         Asciidoctor::Extensions.unregister_all
  925       end
  926     end
  927 
  928     test 'should pass cloaked context in attributes passed to process method of custom block' do
  929       input = <<~'EOS'
  930       [custom]
  931       ****
  932       sidebar
  933       ****
  934       EOS
  935 
  936       cloaked_context = nil
  937       begin
  938         Asciidoctor::Extensions.register do
  939           block :custom do
  940             on_context :sidebar
  941             process do |doc, reader, attrs|
  942               cloaked_context = attrs['cloaked-context']
  943               nil
  944             end
  945           end
  946         end
  947 
  948         convert_string_to_embedded input
  949         assert_equal :sidebar, cloaked_context
  950       ensure
  951         Asciidoctor::Extensions.unregister_all
  952       end
  953     end
  954 
  955     test 'should invoke processor for custom block macro' do
  956       input = 'snippet::12345[mode=edit]'
  957 
  958       begin
  959         Asciidoctor::Extensions.register do
  960           block_macro SnippetMacro, :snippet
  961         end
  962 
  963         output = convert_string_to_embedded input
  964         assert_includes output, '<script src="http://example.com/12345.js?_mode=edit"></script>'
  965       ensure
  966         Asciidoctor::Extensions.unregister_all
  967       end
  968     end
  969 
  970     test 'should substitute attributes in target of custom block macro' do
  971       input = 'snippet::{gist-id}[mode=edit]'
  972 
  973       begin
  974         Asciidoctor::Extensions.register do
  975           block_macro SnippetMacro, :snippet
  976         end
  977 
  978         output = convert_string_to_embedded input, attributes: { 'gist-id' => '12345' }
  979         assert_includes output, '<script src="http://example.com/12345.js?_mode=edit"></script>'
  980       ensure
  981         Asciidoctor::Extensions.unregister_all
  982       end
  983     end
  984 
  985     test 'should log debug message if custom block macro is unknown' do
  986       input = 'unknown::[]'
  987       using_memory_logger Logger::Severity::DEBUG do |logger|
  988         convert_string_to_embedded input
  989         assert_message logger, :DEBUG, '<stdin>: line 1: unknown name for block macro: unknown', Hash
  990       end
  991     end
  992 
  993     test 'should drop block macro line if target references missing attribute and attribute-missing is drop-line' do
  994       input = <<~'EOS'
  995       [.rolename]
  996       snippet::{gist-ns}12345[mode=edit]
  997 
  998       following paragraph
  999       EOS
 1000 
 1001       begin
 1002         Asciidoctor::Extensions.register do
 1003           block_macro SnippetMacro, :snippet
 1004         end
 1005 
 1006         doc, output = nil, nil
 1007         using_memory_logger do |logger|
 1008           doc = document_from_string input, attributes: { 'attribute-missing' => 'drop-line' }
 1009           assert_equal 1, doc.blocks.size
 1010           assert_equal :paragraph, doc.blocks[0].context
 1011           output = doc.convert
 1012           assert_message logger, :INFO, 'dropping line containing reference to missing attribute: gist-ns'
 1013         end
 1014         assert_css '.paragraph', output, 1
 1015         assert_css '.rolename', output, 0
 1016       ensure
 1017         Asciidoctor::Extensions.unregister_all
 1018       end
 1019     end
 1020 
 1021     test 'should invoke processor for custom block macro in an AsciiDoc table cell' do
 1022       input = <<~'EOS'
 1023       |===
 1024       a|message::hi[]
 1025       |===
 1026       EOS
 1027 
 1028       begin
 1029         Asciidoctor::Extensions.register do
 1030           block_macro :message do
 1031             process do |parent, target, attrs|
 1032               create_paragraph parent, target.upcase, {}
 1033             end
 1034           end
 1035         end
 1036 
 1037         output = convert_string_to_embedded input
 1038         assert_xpath '/table//p[text()="HI"]', output, 1
 1039       ensure
 1040         Asciidoctor::Extensions.unregister_all
 1041       end
 1042     end
 1043 
 1044     test 'should match short form of block macro' do
 1045       input = 'custom-toc::[]'
 1046 
 1047       resolved_target = nil
 1048 
 1049       begin
 1050         Asciidoctor::Extensions.register do
 1051           block_macro do
 1052             named 'custom-toc'
 1053             process do |parent, target, attrs|
 1054               resolved_target = target
 1055               create_pass_block parent, '<!-- custom toc goes here -->', {}, content_model: :raw
 1056             end
 1057           end
 1058         end
 1059 
 1060         output = convert_string_to_embedded input
 1061         assert_equal '<!-- custom toc goes here -->', output
 1062         assert_equal '', resolved_target
 1063       ensure
 1064         Asciidoctor::Extensions.unregister_all
 1065       end
 1066     end
 1067 
 1068     test 'should fail to convert if name of block macro is illegal' do
 1069       input = 'illegal name::target[]'
 1070 
 1071       begin
 1072         Asciidoctor::Extensions.register do
 1073           block_macro do
 1074             named 'illegal name'
 1075             process do |parent, target, attrs|
 1076               nil
 1077             end
 1078           end
 1079         end
 1080 
 1081         assert_raises ArgumentError do
 1082           convert_string_to_embedded input
 1083         end
 1084       ensure
 1085         Asciidoctor::Extensions.unregister_all
 1086       end
 1087     end
 1088 
 1089     test 'should honor legacy :pos_attrs option set via static method' do
 1090       begin
 1091         Asciidoctor::Extensions.register do
 1092           block_macro LegacyPosAttrsBlockMacro, :diag
 1093         end
 1094 
 1095         result = convert_string_to_embedded 'diag::[filename,png]'
 1096         assert_css 'img[src="filename.png"]', result, 1
 1097       ensure
 1098         Asciidoctor::Extensions.unregister_all
 1099       end
 1100     end
 1101 
 1102     test 'should honor legacy :pos_attrs option set via DSL' do
 1103       begin
 1104         Asciidoctor::Extensions.register do
 1105           block_macro :diag do
 1106             option :pos_attrs, ['target', 'format']
 1107             process do |parent, _, attrs|
 1108               create_image_block parent, { 'target' => %(#{attrs['target']}.#{attrs['format']}) }
 1109             end
 1110           end
 1111         end
 1112 
 1113         result = convert_string_to_embedded 'diag::[filename,png]'
 1114         assert_css 'img[src="filename.png"]', result, 1
 1115       ensure
 1116         Asciidoctor::Extensions.unregister_all
 1117       end
 1118     end
 1119 
 1120     test 'should be able to set header attribute in block macro processor' do
 1121       begin
 1122         Asciidoctor::Extensions.register do
 1123           block_macro do
 1124             named :attribute
 1125             resolves_attributes '1:value'
 1126             process do |parent, target, attrs|
 1127               parent.document.set_attr target, attrs['value']
 1128               nil
 1129             end
 1130           end
 1131           block_macro do
 1132             named :header_attribute
 1133             resolves_attributes '1:value'
 1134             process do |parent, target, attrs|
 1135               parent.document.set_header_attribute target, attrs['value']
 1136               nil
 1137             end
 1138           end
 1139         end
 1140         input = <<~'EOS'
 1141         attribute::yin[yang]
 1142 
 1143         header_attribute::foo[bar]
 1144         EOS
 1145         doc = document_from_string input
 1146         assert_nil doc.attr 'yin'
 1147         assert_equal 'bar', (doc.attr 'foo')
 1148       ensure
 1149         Asciidoctor::Extensions.unregister_all
 1150       end
 1151     end
 1152 
 1153     test 'should invoke processor for custom inline macro' do
 1154       begin
 1155         Asciidoctor::Extensions.register do
 1156           inline_macro TemperatureMacro, :deg
 1157         end
 1158 
 1159         output = convert_string_to_embedded 'Room temperature is deg:25[C,precision=0].', attributes: { 'temperature-unit' => 'F' }
 1160         assert_includes output, 'Room temperature is 25 &#176;C.'
 1161 
 1162         output = convert_string_to_embedded 'Normal body temperature is deg:37[].', attributes: { 'temperature-unit' => 'F' }
 1163         assert_includes output, 'Normal body temperature is 98.6 &#176;F.'
 1164       ensure
 1165         Asciidoctor::Extensions.unregister_all
 1166       end
 1167     end
 1168 
 1169     test 'should resolve regexp for inline macro lazily' do
 1170       begin
 1171         Asciidoctor::Extensions.register do
 1172           inline_macro do
 1173             named :label
 1174             match_format :short
 1175             parse_content_as :text
 1176             process do |parent, _, attrs|
 1177               create_inline_pass parent, %(<label>#{attrs['text']}</label>)
 1178             end
 1179           end
 1180         end
 1181 
 1182         output = convert_string_to_embedded 'label:[Checkbox]'
 1183         assert_includes output, '<label>Checkbox</label>'
 1184       ensure
 1185         Asciidoctor::Extensions.unregister_all
 1186       end
 1187     end
 1188 
 1189     test 'should map unparsed attrlist to target when format is short' do
 1190       begin
 1191         Asciidoctor::Extensions.register do
 1192           inline_macro do
 1193             named :label
 1194             match_format :short
 1195             process do |parent, target|
 1196               create_inline_pass parent, %(<label>#{target}</label>)
 1197             end
 1198           end
 1199         end
 1200 
 1201         output = convert_string_to_embedded 'label:[Checkbox]'
 1202         assert_includes output, '<label>Checkbox</label>'
 1203       ensure
 1204         Asciidoctor::Extensions.unregister_all
 1205       end
 1206     end
 1207 
 1208     test 'should parse text in square brackets as attrlist by default' do
 1209       begin
 1210         Asciidoctor::Extensions.register do
 1211           inline_macro do
 1212             named :json
 1213             match_format :short
 1214             process do |parent, _, attrs|
 1215               create_inline_pass parent, %({ #{attrs.map {|k, v| %["#{k}": "#{v}"] }.join ', '} })
 1216             end
 1217           end
 1218 
 1219           inline_macro do
 1220             named :data
 1221             process do |parent, target, attrs|
 1222               if target == 'json'
 1223                 create_inline_pass parent, %({ #{attrs.map {|k, v| %["#{k}": "#{v}"] }.join ', '} })
 1224               else
 1225                 nil
 1226               end
 1227             end
 1228           end
 1229         end
 1230 
 1231         output = convert_string_to_embedded 'json:[a=A,b=B,c=C]', doctype: :inline
 1232         assert_equal '{ "a": "A", "b": "B", "c": "C" }', output
 1233         output = convert_string_to_embedded 'data:json[a=A,b=B,c=C]', doctype: :inline
 1234         assert_equal '{ "a": "A", "b": "B", "c": "C" }', output
 1235       ensure
 1236         Asciidoctor::Extensions.unregister_all
 1237       end
 1238     end
 1239 
 1240     test 'should assign captures correctly for inline macros' do
 1241       begin
 1242         Asciidoctor::Extensions.register do
 1243           inline_macro do
 1244             named :short_attributes
 1245             match_format :short
 1246             resolve_attributes '1:name'
 1247             process do |parent, target, attrs|
 1248               create_inline_pass parent, %(target=#{target.inspect}, attributes=#{attrs.sort_by {|(k)| k.to_s }.to_h})
 1249             end
 1250           end
 1251 
 1252           inline_macro do
 1253             named :short_text
 1254             match_format :short
 1255             resolve_attributes false
 1256             process do |parent, target, attrs|
 1257               create_inline_pass parent, %(target=#{target.inspect}, attributes=#{attrs.sort_by {|(k)| k.to_s }.to_h})
 1258             end
 1259           end
 1260 
 1261           inline_macro do
 1262             named :'full-attributes'
 1263             resolve_attributes '1:name' => nil
 1264             process do |parent, target, attrs|
 1265               create_inline_pass parent, %(target=#{target.inspect}, attributes=#{attrs.sort_by {|(k)| k.to_s }.to_h})
 1266             end
 1267           end
 1268 
 1269           inline_macro do
 1270             named :'full-text'
 1271             resolve_attributes false
 1272             process do |parent, target, attrs|
 1273               create_inline_pass parent, %(target=#{target.inspect}, attributes=#{attrs.sort_by {|(k)| k.to_s }.to_h})
 1274             end
 1275           end
 1276 
 1277           inline_macro do
 1278             named :@short_match
 1279             match %r/@(\w+)/
 1280             resolve_attributes false
 1281             process do |parent, target, attrs|
 1282               create_inline_pass parent, %(target=#{target.inspect}, attributes=#{attrs.sort_by {|(k)| k.to_s }.to_h})
 1283             end
 1284           end
 1285         end
 1286 
 1287         input = <<~'EOS'
 1288         [subs=normal]
 1289         ++++
 1290         short_attributes:[]
 1291         short_attributes:[value,key=val]
 1292         short_text:[]
 1293         short_text:[[text\]]
 1294         full-attributes:target[]
 1295         full-attributes:target[value,key=val]
 1296         full-text:target[]
 1297         full-text:target[[text\]]
 1298         @target
 1299         ++++
 1300         EOS
 1301         expected = <<~'EOS'.chop
 1302         target="", attributes={}
 1303         target="value,key=val", attributes={1=>"value", "key"=>"val", "name"=>"value"}
 1304         target="", attributes={"text"=>""}
 1305         target="[text]", attributes={"text"=>"[text]"}
 1306         target="target", attributes={}
 1307         target="target", attributes={1=>"value", "key"=>"val", "name"=>"value"}
 1308         target="target", attributes={"text"=>""}
 1309         target="target", attributes={"text"=>"[text]"}
 1310         target="target", attributes={}
 1311         EOS
 1312         output = convert_string_to_embedded input
 1313         assert_equal expected, output
 1314       ensure
 1315         Asciidoctor::Extensions.unregister_all
 1316       end
 1317     end
 1318 
 1319     test 'should invoke convert on return value if value is an inline node' do
 1320       begin
 1321         Asciidoctor::Extensions.register do
 1322           inline_macro do
 1323             named :mention
 1324             resolve_attributes false
 1325             process do |parent, target, attrs|
 1326               if (text = attrs['text']).empty?
 1327                 text = %(@#{target})
 1328               end
 1329               create_anchor parent, text, type: :link, target: %(https://github.com/#{target})
 1330             end
 1331           end
 1332         end
 1333 
 1334         output = convert_string_to_embedded 'mention:mojavelinux[Dan]'
 1335         assert_includes output, '<a href="https://github.com/mojavelinux">Dan</a>'
 1336       ensure
 1337         Asciidoctor::Extensions.unregister_all
 1338       end
 1339     end
 1340 
 1341     test 'should allow return value of inline macro to be nil' do
 1342       begin
 1343         Asciidoctor::Extensions.register do
 1344           inline_macro do
 1345             named :skipme
 1346             match_format :short
 1347             process do
 1348               nil
 1349             end
 1350           end
 1351         end
 1352 
 1353         using_memory_logger do |logger|
 1354           output = convert_string_to_embedded '-skipme:[]-', doctype: :inline
 1355           assert_equal '--', output
 1356           assert_empty logger
 1357         end
 1358       ensure
 1359         Asciidoctor::Extensions.unregister_all
 1360       end
 1361     end
 1362 
 1363     test 'should warn if return value of inline macro is a string' do
 1364       begin
 1365         Asciidoctor::Extensions.register do
 1366           inline_macro do
 1367             named :say
 1368             process do |parent, target, attrs|
 1369               target
 1370             end
 1371           end
 1372         end
 1373 
 1374         using_memory_logger do |logger|
 1375           output = convert_string_to_embedded 'say:yo[]', doctype: :inline
 1376           assert_equal 'yo', output
 1377           assert_message logger, :INFO, 'expected substitution value for custom inline macro to be of type Inline; got String: say:yo[]'
 1378         end
 1379       ensure
 1380         Asciidoctor::Extensions.unregister_all
 1381       end
 1382     end
 1383 
 1384     test 'should not apply subs to inline node returned by process method by default' do
 1385       begin
 1386         Asciidoctor::Extensions.register do
 1387           inline_macro do
 1388             named :say
 1389             process do |parent, target, attrs|
 1390               create_inline parent, :quoted, %(*#{target}*), type: :emphasis
 1391             end
 1392           end
 1393         end
 1394 
 1395         output = convert_string_to_embedded 'say:yo[]', doctype: :inline
 1396         assert_equal '<em>*yo*</em>', output
 1397       ensure
 1398         Asciidoctor::Extensions.unregister_all
 1399       end
 1400     end
 1401 
 1402     test 'should apply specified subs to inline node returned by process method' do
 1403       begin
 1404         Asciidoctor::Extensions.register do
 1405           inline_macro do
 1406             named :say
 1407             process do |parent, target, attrs|
 1408               create_inline_pass parent, %(*#{target}*), attributes: { 'subs' => :normal }
 1409             end
 1410           end
 1411         end
 1412 
 1413         output = convert_string_to_embedded 'say:yo[]', doctype: :inline
 1414         assert_equal '<strong>yo</strong>', output
 1415       ensure
 1416         Asciidoctor::Extensions.unregister_all
 1417       end
 1418     end
 1419 
 1420     test 'should prefer attributes parsed from inline macro over default attributes' do
 1421       begin
 1422         Asciidoctor::Extensions.register do
 1423           inline_macro :attrs do
 1424             match_format :short
 1425             default_attributes 1 => 'a', 2 => 'b', 'foo' => 'baz'
 1426             positional_attributes 'a', 'b'
 1427             process do |parent, _, attrs|
 1428               create_inline_pass parent, %(a=#{attrs['a']},2=#{attrs[2]},b=#{attrs['b'] || 'nil'},foo=#{attrs['foo']})
 1429             end
 1430           end
 1431         end
 1432 
 1433         output = convert_string_to_embedded 'attrs:[A,foo=bar]', doctype: :inline
 1434         # note that default attributes aren't considered when mapping positional attributes
 1435         assert_equal 'a=A,2=b,b=nil,foo=bar', output
 1436       ensure
 1437         Asciidoctor::Extensions.unregister_all
 1438       end
 1439     end
 1440 
 1441     test 'should not carry over attributes if block processor returns nil' do
 1442       begin
 1443         Asciidoctor::Extensions.register do
 1444           block do
 1445             named 'skip-me'
 1446             on_context :paragraph
 1447             parse_content_as :raw
 1448             process do |parent, reader, attrs|
 1449               nil
 1450             end
 1451           end
 1452         end
 1453         input = <<~'EOS'
 1454         .unused title
 1455         [skip-me]
 1456         not shown
 1457 
 1458         --
 1459         shown
 1460         --
 1461         EOS
 1462         doc = document_from_string input
 1463         assert_equal 1, doc.blocks.size
 1464         assert_nil doc.blocks[0].attributes['title']
 1465       ensure
 1466         Asciidoctor::Extensions.unregister_all
 1467       end
 1468     end
 1469 
 1470     test 'should not invoke process method or carry over attributes if block processor declares skip content model' do
 1471       begin
 1472         process_method_called = false
 1473         Asciidoctor::Extensions.register do
 1474           block do
 1475             named :ignore
 1476             on_context :paragraph
 1477             parse_content_as :skip
 1478             process do |parent, reader, attrs|
 1479               process_method_called = true
 1480               nil
 1481             end
 1482           end
 1483         end
 1484         input = <<~'EOS'
 1485         .unused title
 1486         [ignore]
 1487         not shown
 1488 
 1489         --
 1490         shown
 1491         --
 1492         EOS
 1493         doc = document_from_string input
 1494         refute process_method_called
 1495         assert_equal 1, doc.blocks.size
 1496         assert_nil doc.blocks[0].attributes['title']
 1497       ensure
 1498         Asciidoctor::Extensions.unregister_all
 1499       end
 1500     end
 1501 
 1502     test 'should pass attributes by value to block processor' do
 1503       begin
 1504         Asciidoctor::Extensions.register do
 1505           block do
 1506             named :foo
 1507             on_context :paragraph
 1508             parse_content_as :raw
 1509             process do |parent, reader, attrs|
 1510               original_attrs = attrs.dup
 1511               attrs.delete('title')
 1512               create_paragraph parent, reader.read_lines, original_attrs.merge('id' => 'value')
 1513             end
 1514           end
 1515         end
 1516         input = <<~'EOS'
 1517         .title
 1518         [foo]
 1519         content
 1520         EOS
 1521         doc = document_from_string input
 1522         assert_equal 1, doc.blocks.size
 1523         assert_equal 'title', doc.blocks[0].attributes['title']
 1524         assert_equal 'value', doc.blocks[0].id
 1525       ensure
 1526         Asciidoctor::Extensions.unregister_all
 1527       end
 1528     end
 1529 
 1530     test 'parse_content should not share attributes between parsed blocks' do
 1531       begin
 1532         Asciidoctor::Extensions.register do
 1533           block do
 1534             named :wrap
 1535             on_context :open
 1536             process do |parent, reader, attrs|
 1537               wrap = create_open_block parent, nil, attrs
 1538               parse_content wrap, reader.read_lines
 1539             end
 1540           end
 1541         end
 1542         input = <<~'EOS'
 1543         [wrap]
 1544         --
 1545         [foo=bar]
 1546         ====
 1547         content
 1548         ====
 1549 
 1550         [baz=qux]
 1551         ====
 1552         content
 1553         ====
 1554         --
 1555         EOS
 1556         doc = document_from_string input
 1557         assert_equal 1, doc.blocks.size
 1558         wrap = doc.blocks[0]
 1559         assert_equal 2, wrap.blocks.size
 1560         assert_equal 2, wrap.blocks[0].attributes.size
 1561         assert_equal 2, wrap.blocks[1].attributes.size
 1562         assert_nil wrap.blocks[1].attributes['foo']
 1563       ensure
 1564         Asciidoctor::Extensions.unregister_all
 1565       end
 1566     end
 1567 
 1568     test 'can use parse_attributes to parse attrlist' do
 1569       begin
 1570         parsed_attrs = nil
 1571         Asciidoctor::Extensions.register do
 1572           block do
 1573             named :attrs
 1574             on_context :open
 1575             process do |parent, reader, attrs|
 1576               parsed_attrs = parse_attributes parent, reader.read_line, positional_attributes: ['a', 'b']
 1577               parsed_attrs.update parse_attributes parent, 'foo={foo}', sub_attributes: true
 1578               nil
 1579             end
 1580           end
 1581         end
 1582         input = <<~'EOS'
 1583         :foo: bar
 1584 
 1585         [attrs]
 1586         --
 1587         a,b,c,key=val
 1588         --
 1589         EOS
 1590         convert_string_to_embedded input
 1591         assert_equal 'a', parsed_attrs['a']
 1592         assert_equal 'b', parsed_attrs['b']
 1593         assert_equal 'val', parsed_attrs['key']
 1594         assert_equal 'bar', parsed_attrs['foo']
 1595       ensure
 1596         Asciidoctor::Extensions.unregister_all
 1597       end
 1598     end
 1599 
 1600     test 'create_section should set up all section properties' do
 1601       begin
 1602         sect = nil
 1603         Asciidoctor::Extensions.register do
 1604           block_macro do
 1605             named :sect
 1606             process do |parent, target, attrs|
 1607               opts = (level = attrs.delete 'level') ? { level: level.to_i } : {}
 1608               attrs['id'] = false if attrs['id'] == 'false'
 1609               parent = parent.parent if parent.context == :preamble
 1610               sect = create_section parent, 'Section Title', attrs, opts
 1611               nil
 1612             end
 1613           end
 1614         end
 1615 
 1616         input_tpl = <<~'EOS'
 1617         = Document Title
 1618         :doctype: book
 1619         :sectnums:
 1620 
 1621         sect::[%s]
 1622         EOS
 1623 
 1624         {
 1625           ''                       => ['chapter',  1, false, true, '_section_title'],
 1626           'level=0'                => ['part',     0, false, false, '_section_title'],
 1627           'level=0,alt'            => ['part',     0, false, true, '_section_title', { 'partnums' => '' }],
 1628           'level=0,style=appendix' => ['appendix', 1, true,  true, '_section_title'],
 1629           'style=appendix'         => ['appendix', 1, true,  true, '_section_title'],
 1630           'style=glossary'         => ['glossary', 1, true,  false, '_section_title'],
 1631           'style=glossary,alt'     => ['glossary', 1, true,  :chapter, '_section_title', { 'sectnums' => 'all' }],
 1632           'style=abstract'         => ['chapter',  1, false, true, '_section_title'],
 1633           'id=section-title'       => ['chapter',  1, false, true, 'section-title'],
 1634           'id=false'               => ['chapter',  1, false, true, nil]
 1635         }.each do |attrlist, (expect_sectname, expect_level, expect_special, expect_numbered, expect_id, extra_attrs)|
 1636           input = input_tpl % attrlist
 1637           document_from_string input, safe: :server, attributes: extra_attrs
 1638           assert_equal expect_sectname, sect.sectname
 1639           assert_equal expect_level, sect.level
 1640           assert_equal expect_special, sect.special
 1641           assert_equal expect_numbered, sect.numbered
 1642           if expect_id
 1643             assert_equal expect_id, sect.id
 1644           else
 1645             assert_nil sect.id
 1646           end
 1647         end
 1648       ensure
 1649         Asciidoctor::Extensions.unregister_all
 1650       end
 1651     end
 1652 
 1653     test 'should add docinfo to document' do
 1654       input = <<~'EOS'
 1655       = Document Title
 1656 
 1657       sample content
 1658       EOS
 1659 
 1660       begin
 1661         Asciidoctor::Extensions.register do
 1662           docinfo_processor MetaRobotsDocinfoProcessor
 1663         end
 1664 
 1665         doc = document_from_string input
 1666         assert_equal Asciidoctor::SafeMode::SECURE, doc.safe
 1667         assert_equal '<meta name="robots" content="index,follow">', doc.docinfo
 1668       ensure
 1669         Asciidoctor::Extensions.unregister_all
 1670       end
 1671     end
 1672 
 1673     test 'should add multiple docinfo to document' do
 1674       input = <<~'EOS'
 1675       = Document Title
 1676 
 1677       sample content
 1678       EOS
 1679 
 1680       begin
 1681         Asciidoctor::Extensions.register do
 1682           docinfo_processor MetaAppDocinfoProcessor
 1683           docinfo_processor MetaRobotsDocinfoProcessor, position: :>>
 1684           docinfo_processor do
 1685             at_location :footer
 1686             process do |doc|
 1687               '<script><!-- analytics code --></script>'
 1688             end
 1689           end
 1690         end
 1691 
 1692         doc = document_from_string input, safe: :server
 1693         assert_equal %(<meta name="robots" content="index,follow">\n<meta name="application-name" content="Asciidoctor App">), doc.docinfo
 1694         assert_equal '<script><!-- analytics code --></script>', doc.docinfo(:footer)
 1695       ensure
 1696         Asciidoctor::Extensions.unregister_all
 1697       end
 1698     end
 1699 
 1700     test 'should append docinfo to document' do
 1701       begin
 1702         Asciidoctor::Extensions.register do
 1703           docinfo_processor MetaRobotsDocinfoProcessor
 1704         end
 1705         sample_input_path = fixture_path('basic.adoc')
 1706 
 1707         output = Asciidoctor.convert_file sample_input_path, to_file: false,
 1708                                           standalone: true,
 1709                                           safe: Asciidoctor::SafeMode::SERVER,
 1710                                           attributes: { 'docinfo' => '' }
 1711         refute_empty output
 1712         assert_css 'script[src="modernizr.js"]', output, 1
 1713         assert_css 'meta[name="robots"]', output, 1
 1714         assert_css 'meta[http-equiv="imagetoolbar"]', output, 0
 1715       ensure
 1716         Asciidoctor::Extensions.unregister_all
 1717       end
 1718     end
 1719 
 1720     test 'should return extension instance after registering' do
 1721       begin
 1722         exts = []
 1723         Asciidoctor::Extensions.register do
 1724           exts.push preprocessor SamplePreprocessor
 1725           exts.push include_processor SampleIncludeProcessor
 1726           exts.push tree_processor SampleTreeProcessor
 1727           exts.push docinfo_processor SampleDocinfoProcessor
 1728           exts.push postprocessor SamplePostprocessor
 1729         end
 1730         empty_document
 1731         exts.each do |ext|
 1732           assert_kind_of Asciidoctor::Extensions::ProcessorExtension, ext
 1733         end
 1734       ensure
 1735         Asciidoctor::Extensions.unregister_all
 1736       end
 1737     end
 1738 
 1739     test 'should raise an exception if mandatory target attribute is not provided for image block' do
 1740       input = 'cat_in_sink::[]'
 1741       exception = assert_raises ArgumentError do
 1742         convert_string_to_embedded input, extension_registry: create_cat_in_sink_block_macro
 1743       end
 1744       assert_match(/target attribute is required/, exception.message)
 1745     end
 1746 
 1747     test 'should assign alt attribute to image block if alt is not provided' do
 1748       input = 'cat_in_sink::25[]'
 1749       doc = document_from_string input, standalone: false, extension_registry: create_cat_in_sink_block_macro
 1750       image = doc.blocks[0]
 1751       assert_equal 'cat in sink day 25', (image.attr 'alt')
 1752       assert_equal 'cat in sink day 25', (image.attr 'default-alt')
 1753       output = doc.convert
 1754       assert_includes output, '<img src="cat-in-sink-day-25.png" alt="cat in sink day 25">'
 1755     end
 1756 
 1757     test 'should create an image block if mandatory attributes are provided' do
 1758       input = 'cat_in_sink::30[cat in sink (yes)]'
 1759       doc = document_from_string input, standalone: false, extension_registry: create_cat_in_sink_block_macro
 1760       image = doc.blocks[0]
 1761       assert_equal 'cat in sink (yes)', (image.attr 'alt')
 1762       refute(image.attr? 'default-alt')
 1763       output = doc.convert
 1764       assert_includes output, '<img src="cat-in-sink-day-30.png" alt="cat in sink (yes)">'
 1765     end
 1766 
 1767     test 'should not assign caption on image block if title is not set on custom block macro' do
 1768       input = 'cat_in_sink::30[]'
 1769       doc = document_from_string input, standalone: false, extension_registry: create_cat_in_sink_block_macro
 1770       output = doc.convert
 1771       assert_xpath '/*[@class="imageblock"]/*[@class="title"]', output, 0
 1772     end
 1773 
 1774     test 'should assign caption on image block if title is set on custom block macro' do
 1775       input = <<~'EOS'
 1776       .Cat in Sink?
 1777       cat_in_sink::30[]
 1778       EOS
 1779       doc = document_from_string input, standalone: false, extension_registry: create_cat_in_sink_block_macro
 1780       output = doc.convert
 1781       assert_xpath '/*[@class="imageblock"]/*[@class="title"][text()="Figure 1. Cat in Sink?"]', output, 1
 1782     end
 1783 
 1784     test 'should not fail if alt attribute is not set on block image node' do
 1785       begin
 1786         Asciidoctor::Extensions.register do
 1787           block_macro :no_alt do
 1788             process do |parent, target, attrs|
 1789               create_block parent, 'image', nil, { 'target' => 'picture.jpg' }
 1790             end
 1791           end
 1792         end
 1793 
 1794         output = Asciidoctor.convert 'no_alt::[]'
 1795         assert_include '<img src="picture.jpg" alt="">', output
 1796       ensure
 1797         Asciidoctor::Extensions.unregister_all
 1798       end
 1799     end
 1800 
 1801     test 'should not fail if alt attribute is not set on inline image node' do
 1802       begin
 1803         Asciidoctor::Extensions.register do
 1804           inline_macro :no_alt do
 1805             match_format :short
 1806             process do |parent, target, attrs|
 1807               create_inline parent, 'image', nil, target: 'picture.jpg'
 1808             end
 1809           end
 1810         end
 1811 
 1812         output = Asciidoctor.convert 'no_alt:[]'
 1813         assert_include '<span class="image"><img src="picture.jpg" alt=""></span>', output
 1814       ensure
 1815         Asciidoctor::Extensions.unregister_all
 1816       end
 1817     end
 1818 
 1819     test 'should assign id and role on list items unordered' do
 1820       input = 'santa_list::ulist[]'
 1821       doc = document_from_string input, standalone: false, extension_registry: create_santa_list_block_macro
 1822       output = doc.convert
 1823       assert_xpath '/div[@class="ulist"]/ul/li[@class="friendly"][@id="santa-list-guillaume"]', output, 1
 1824       assert_xpath '/div[@class="ulist"]/ul/li[@class="kind contributor java"]', output, 1
 1825       assert_xpath '/div[@class="ulist"]/ul/li[@class="kind contributor java"][not(@id)]', output, 1
 1826       assert_xpath '/div[@class="ulist"]/ul/li[@id="santa-list-pepijn"][not(@class)]', output, 1
 1827       assert_xpath '/div[@class="ulist"]/ul/li[@id="santa-list-dan"][@class="naughty"]', output, 1
 1828       assert_xpath '/div[@class="ulist"]/ul/li[not(@id)][not(@class)]/p[text()="Sarah"]', output, 1
 1829     end
 1830 
 1831     test 'should assign id and role on list items ordered' do
 1832       input = 'santa_list::olist[]'
 1833       doc = document_from_string input, standalone: false, extension_registry: create_santa_list_block_macro
 1834       output = doc.convert
 1835       assert_xpath '/div[@class="olist"]/ol/li[@class="friendly"][@id="santa-list-guillaume"]', output, 1
 1836       assert_xpath '/div[@class="olist"]/ol/li[@class="kind contributor java"]', output, 1
 1837       assert_xpath '/div[@class="olist"]/ol/li[@class="kind contributor java"][not(@id)]', output, 1
 1838       assert_xpath '/div[@class="olist"]/ol/li[@id="santa-list-pepijn"][not(@class)]', output, 1
 1839       assert_xpath '/div[@class="olist"]/ol/li[@id="santa-list-dan"][@class="naughty"]', output, 1
 1840       assert_xpath '/div[@class="olist"]/ol/li[not(@id)][not(@class)]/p[text()="Sarah"]', output, 1
 1841     end
 1842   end
 1843 end