# frozen_string_literal: true require_relative 'test_helper' class ExtensionsInitTest < Minitest::Test def test_autoload doc = empty_document refute doc.extensions?, 'Extensions should not be enabled by default' Asciidoctor::Extensions.register do # trigger extensions to autoload by registering empty group end doc = empty_document assert doc.extensions?, 'Extensions should be enabled after being autoloaded' self.class.remove_tests self.class ensure Asciidoctor::Extensions.unregister_all end self end.new(nil).test_autoload class SamplePreprocessor < Asciidoctor::Extensions::Preprocessor def process doc, reader nil end end class SampleIncludeProcessor < Asciidoctor::Extensions::IncludeProcessor end class SampleDocinfoProcessor < Asciidoctor::Extensions::DocinfoProcessor end # NOTE intentionally using the deprecated name class SampleTreeprocessor < Asciidoctor::Extensions::Treeprocessor def process document nil end end SampleTreeProcessor = SampleTreeprocessor class SamplePostprocessor < Asciidoctor::Extensions::Postprocessor end class SampleBlock < Asciidoctor::Extensions::BlockProcessor end class SampleBlockMacro < Asciidoctor::Extensions::BlockMacroProcessor end class SampleInlineMacro < Asciidoctor::Extensions::InlineMacroProcessor end class ScrubHeaderPreprocessor < Asciidoctor::Extensions::Preprocessor def process doc, reader lines = reader.lines skipped = [] while !lines.empty? && !lines.first.start_with?('=') skipped << lines.shift reader.advance end doc.set_attr 'skipped', (skipped * "\n") reader end end class BoilerplateTextIncludeProcessor < Asciidoctor::Extensions::IncludeProcessor def handles? target target.end_with? '.txt' end def process document, reader, target, attributes case target when 'lorem-ipsum.txt' content = ["Lorem ipsum dolor sit amet...\n"] reader.push_include content, target, target, 1, attributes end end end class ReplaceAuthorTreeProcessor < Asciidoctor::Extensions::TreeProcessor def process document document.attributes['firstname'] = 'Ghost' document.attributes['author'] = 'Ghost Writer' document end end class ReplaceTreeTreeProcessor < Asciidoctor::Extensions::TreeProcessor def process document if document.doctitle == 'Original Document' Asciidoctor.load %(== Replacement Document\nReplacement Author\n\ncontent) else document end end end class SelfSigningTreeProcessor < Asciidoctor::Extensions::TreeProcessor def process document document << (create_paragraph document, self.class.name, {}) nil end end class StripAttributesPostprocessor < Asciidoctor::Extensions::Postprocessor def process document, output output.gsub %r/<(\w+).*?>/m, '<\\1>' end end class UppercaseBlock < Asciidoctor::Extensions::BlockProcessor use_dsl named :yell on_context :paragraph name_positional_attributes 'chars' parse_content_as :simple def process parent, reader, attributes if (chars = attributes['chars']) upcase_chars = chars.upcase create_paragraph parent, reader.lines.map {|l| l.downcase.tr chars, upcase_chars }, attributes else create_paragraph parent, reader.lines.map(&:upcase), attributes end end end class SnippetMacro < Asciidoctor::Extensions::BlockMacroProcessor def process parent, target, attributes create_pass_block parent, %(), {}, content_model: :raw end end class LegacyPosAttrsBlockMacro < Asciidoctor::Extensions::BlockMacroProcessor option :pos_attrs, %w(target format) def process parent, _, attrs create_image_block parent, { 'target' => %(#{attrs['target']}.#{attrs['format']}) } end end class TemperatureMacro < Asciidoctor::Extensions::InlineMacroProcessor use_dsl named :degrees resolve_attributes '1:units', 'precision=1' def process parent, target, attributes units = attributes['units'] || (parent.document.attr 'temperature-unit', 'C') precision = attributes['precision'].to_i c = target.to_f case units when 'C' create_inline parent, :quoted, %(#{c.round precision} °C), type: :unquoted when 'F' create_inline parent, :quoted, %(#{(c * 1.8 + 32).round precision} °F), type: :unquoted else raise ArgumentError, %(Unknown temperature units: #{units}) end end end class MetaRobotsDocinfoProcessor < Asciidoctor::Extensions::DocinfoProcessor def process document '' end end class MetaAppDocinfoProcessor < Asciidoctor::Extensions::DocinfoProcessor use_dsl at_location :head def process document '' end end class SampleExtensionGroup < Asciidoctor::Extensions::Group def activate registry registry.document.attributes['activate-method-called'] = '' registry.preprocessor SamplePreprocessor end end def create_cat_in_sink_block_macro Asciidoctor::Extensions.create do block_macro do named :cat_in_sink process do |parent, target, attrs| image_attrs = {} unless target.nil_or_empty? image_attrs['target'] = %(cat-in-sink-day-#{target}.png) end if (title = attrs.delete 'title') image_attrs['title'] = title end if (alt = attrs.delete 1) image_attrs['alt'] = alt end create_image_block parent, image_attrs end end end end def create_santa_list_block_macro Asciidoctor::Extensions.create do block_macro do named :santa_list process do |parent, target| list = create_list parent, target guillaume = (create_list_item list, 'Guillaume') guillaume.add_role 'friendly' guillaume.id = 'santa-list-guillaume' list << guillaume robert = (create_list_item list, 'Robert') robert.add_role 'kind' robert.add_role 'contributor' robert.add_role 'java' list << robert pepijn = (create_list_item list, 'Pepijn') pepijn.id = 'santa-list-pepijn' list << pepijn dan = (create_list_item list, 'Dan') dan.add_role 'naughty' dan.id = 'santa-list-dan' list << dan sarah = (create_list_item list, 'Sarah') list << sarah list end end end end context 'Extensions' do context 'Register' do test 'should not activate registry if no extension groups are registered' do assert defined? Asciidoctor::Extensions doc = empty_document refute doc.extensions?, 'Extensions should not be enabled if not groups are registered' end test 'should register extension group class' do begin Asciidoctor::Extensions.register :sample, SampleExtensionGroup refute_nil Asciidoctor::Extensions.groups assert_equal 1, Asciidoctor::Extensions.groups.size assert_equal SampleExtensionGroup, Asciidoctor::Extensions.groups[:sample] ensure Asciidoctor::Extensions.unregister_all end end test 'should self register extension group class' do begin SampleExtensionGroup.register :sample refute_nil Asciidoctor::Extensions.groups assert_equal 1, Asciidoctor::Extensions.groups.size assert_equal SampleExtensionGroup, Asciidoctor::Extensions.groups[:sample] ensure Asciidoctor::Extensions.unregister_all end end test 'should register extension group from class name' do begin Asciidoctor::Extensions.register :sample, 'SampleExtensionGroup' refute_nil Asciidoctor::Extensions.groups assert_equal 1, Asciidoctor::Extensions.groups.size assert_equal SampleExtensionGroup, Asciidoctor::Extensions.groups[:sample] ensure Asciidoctor::Extensions.unregister_all end end test 'should register extension group from instance' do begin Asciidoctor::Extensions.register :sample, SampleExtensionGroup.new refute_nil Asciidoctor::Extensions.groups assert_equal 1, Asciidoctor::Extensions.groups.size assert_kind_of SampleExtensionGroup, Asciidoctor::Extensions.groups[:sample] ensure Asciidoctor::Extensions.unregister_all end end test 'should register extension block' do begin Asciidoctor::Extensions.register :sample do # this space intentionally left blank end refute_nil Asciidoctor::Extensions.groups assert_equal 1, Asciidoctor::Extensions.groups.size assert_kind_of Proc, Asciidoctor::Extensions.groups[:sample] ensure Asciidoctor::Extensions.unregister_all end end test 'should coerce group name to symbol when registering' do begin Asciidoctor::Extensions.register 'sample', SampleExtensionGroup refute_nil Asciidoctor::Extensions.groups assert_equal 1, Asciidoctor::Extensions.groups.size assert_equal SampleExtensionGroup, Asciidoctor::Extensions.groups[:sample] ensure Asciidoctor::Extensions.unregister_all end end test 'should unregister extension group by symbol name' do begin Asciidoctor::Extensions.register :sample, SampleExtensionGroup refute_nil Asciidoctor::Extensions.groups assert_equal 1, Asciidoctor::Extensions.groups.size Asciidoctor::Extensions.unregister :sample assert_equal 0, Asciidoctor::Extensions.groups.size ensure Asciidoctor::Extensions.unregister_all end end test 'should unregister extension group by string name' do begin Asciidoctor::Extensions.register :sample, SampleExtensionGroup refute_nil Asciidoctor::Extensions.groups assert_equal 1, Asciidoctor::Extensions.groups.size Asciidoctor::Extensions.unregister 'sample' assert_equal 0, Asciidoctor::Extensions.groups.size ensure Asciidoctor::Extensions.unregister_all end end test 'should unregister multiple extension groups by name' do begin Asciidoctor::Extensions.register :sample1, SampleExtensionGroup Asciidoctor::Extensions.register :sample2, SampleExtensionGroup refute_nil Asciidoctor::Extensions.groups assert_equal 2, Asciidoctor::Extensions.groups.size Asciidoctor::Extensions.unregister :sample1, :sample2 assert_equal 0, Asciidoctor::Extensions.groups.size ensure Asciidoctor::Extensions.unregister_all end end test 'should not fail to unregister extension group if not registered' do refute_nil Asciidoctor::Extensions.groups assert_equal 0, Asciidoctor::Extensions.groups.size Asciidoctor::Extensions.unregister :sample assert_equal 0, Asciidoctor::Extensions.groups.size end test 'should not fail to unregister extension group if extension groups are not initialized' do Asciidoctor::Extensions.remove_instance_variable :@groups Asciidoctor::Extensions.unregister :sample assert_equal 0, Asciidoctor::Extensions.groups.size end test 'should raise NameError if extension class cannot be resolved from string' do begin Asciidoctor::Extensions.register do block 'foobar' end ex = assert_raises NameError do empty_document end assert_match %r/^Could not resolve class for name: foobar$/, ex.message ensure Asciidoctor::Extensions.unregister_all end end test 'should allow standalone registry to be created but not registered' do registry = Asciidoctor::Extensions.create 'sample' do block do named :whisper on_context :paragraph parse_content_as :simple def process parent, reader, attributes create_paragraph parent, reader.lines.map(&:downcase), attributes end end end assert_instance_of Asciidoctor::Extensions::Registry, registry refute_nil registry.groups assert_equal 1, registry.groups.size assert_equal 'sample', registry.groups.keys.first assert_equal 0, Asciidoctor::Extensions.groups.size end end context 'Activate' do test 'should call activate on extension group class' do begin doc = Asciidoctor::Document.new Asciidoctor::Extensions.register :sample, SampleExtensionGroup registry = Asciidoctor::Extensions::Registry.new registry.activate doc assert doc.attr? 'activate-method-called' assert registry.preprocessors? ensure Asciidoctor::Extensions.unregister_all end end test 'should reset registry if activate is called again' do begin Asciidoctor::Extensions.register :sample, SampleExtensionGroup doc = Asciidoctor::Document.new registry = Asciidoctor::Extensions::Registry.new registry.activate doc assert doc.attr? 'activate-method-called' assert registry.preprocessors? assert_equal 1, registry.preprocessors.size assert_same doc, registry.document doc = Asciidoctor::Document.new registry.activate doc assert doc.attr? 'activate-method-called' assert registry.preprocessors? assert_equal 1, registry.preprocessors.size assert_same doc, registry.document ensure Asciidoctor::Extensions.unregister_all end end test 'should invoke extension block' do begin doc = Asciidoctor::Document.new Asciidoctor::Extensions.register do @document.attributes['block-called'] = '' preprocessor SamplePreprocessor end registry = Asciidoctor::Extensions::Registry.new registry.activate doc assert doc.attr? 'block-called' assert registry.preprocessors? ensure Asciidoctor::Extensions.unregister_all end end test 'should create registry in Document if extensions are loaded' do begin SampleExtensionGroup.register doc = Asciidoctor::Document.new assert doc.extensions? assert_kind_of Asciidoctor::Extensions::Registry, doc.extensions ensure Asciidoctor::Extensions.unregister_all end end end context 'Instantiate' do test 'should instantiate preprocessors' do registry = Asciidoctor::Extensions::Registry.new registry.preprocessor SamplePreprocessor registry.activate Asciidoctor::Document.new assert registry.preprocessors? extensions = registry.preprocessors assert_equal 1, extensions.size assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first assert_kind_of SamplePreprocessor, extensions.first.instance assert_kind_of Method, extensions.first.process_method end test 'should instantiate include processors' do registry = Asciidoctor::Extensions::Registry.new registry.include_processor SampleIncludeProcessor registry.activate Asciidoctor::Document.new assert registry.include_processors? extensions = registry.include_processors assert_equal 1, extensions.size assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first assert_kind_of SampleIncludeProcessor, extensions.first.instance assert_kind_of Method, extensions.first.process_method end test 'should instantiate docinfo processors' do registry = Asciidoctor::Extensions::Registry.new registry.docinfo_processor SampleDocinfoProcessor registry.activate Asciidoctor::Document.new assert registry.docinfo_processors? assert registry.docinfo_processors?(:head) extensions = registry.docinfo_processors assert_equal 1, extensions.size assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first assert_kind_of SampleDocinfoProcessor, extensions.first.instance assert_kind_of Method, extensions.first.process_method end # NOTE intentionally using the legacy names test 'should instantiate tree processors' do registry = Asciidoctor::Extensions::Registry.new registry.treeprocessor SampleTreeprocessor registry.activate Asciidoctor::Document.new assert registry.treeprocessors? extensions = registry.treeprocessors assert_equal 1, extensions.size assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first assert_kind_of SampleTreeprocessor, extensions.first.instance assert_kind_of Method, extensions.first.process_method end test 'should instantiate postprocessors' do registry = Asciidoctor::Extensions::Registry.new registry.postprocessor SamplePostprocessor registry.activate Asciidoctor::Document.new assert registry.postprocessors? extensions = registry.postprocessors assert_equal 1, extensions.size assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first assert_kind_of SamplePostprocessor, extensions.first.instance assert_kind_of Method, extensions.first.process_method end test 'should instantiate block processor' do registry = Asciidoctor::Extensions::Registry.new registry.block SampleBlock, :sample registry.activate Asciidoctor::Document.new assert registry.blocks? assert registry.registered_for_block? :sample, :paragraph extension = registry.find_block_extension :sample assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extension assert_kind_of SampleBlock, extension.instance assert_kind_of Method, extension.process_method end test 'should not match block processor for unsupported context' do registry = Asciidoctor::Extensions::Registry.new registry.block SampleBlock, :sample registry.activate Asciidoctor::Document.new refute registry.registered_for_block? :sample, :sidebar end test 'should instantiate block macro processor' do registry = Asciidoctor::Extensions::Registry.new registry.block_macro SampleBlockMacro, 'sample' registry.activate Asciidoctor::Document.new assert registry.block_macros? assert registry.registered_for_block_macro? 'sample' extension = registry.find_block_macro_extension 'sample' assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extension assert_kind_of SampleBlockMacro, extension.instance assert_kind_of Method, extension.process_method end test 'should instantiate inline macro processor' do registry = Asciidoctor::Extensions::Registry.new registry.inline_macro SampleInlineMacro, 'sample' registry.activate Asciidoctor::Document.new assert registry.inline_macros? assert registry.registered_for_inline_macro? 'sample' extension = registry.find_inline_macro_extension 'sample' assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extension assert_kind_of SampleInlineMacro, extension.instance assert_kind_of Method, extension.process_method end test 'should allow processors to be registered by a string name' do registry = Asciidoctor::Extensions::Registry.new registry.preprocessor 'SamplePreprocessor' registry.activate Asciidoctor::Document.new assert registry.preprocessors? extensions = registry.preprocessors assert_equal 1, extensions.size assert_kind_of Asciidoctor::Extensions::ProcessorExtension, extensions.first end end context 'Integration' do test 'does not crash when querying for extensions if none are registered' do registry = Asciidoctor::Extensions.create doc = document_from_string %(= Document Title\n\ncontent), extension_registry: registry refute_nil doc.extensions assert_equal false, (doc.extensions.registered_for_block? :unknown, :paragraph) # rubocop:disable Minitest/RefuteFalse assert_nil doc.extensions.find_block_extension :unknown assert_equal false, (doc.extensions.registered_for_block_macro? :unknown) # rubocop:disable Minitest/RefuteFalse assert_nil doc.extensions.find_block_macro_extension :unknown assert_equal false, (doc.extensions.registered_for_inline_macro? :unknown) # rubocop:disable Minitest/RefuteFalse assert_nil doc.extensions.find_inline_macro_extension :unknown assert_empty doc.extensions.inline_macros end test 'can provide extension registry as an option' do registry = Asciidoctor::Extensions.create do tree_processor SampleTreeProcessor end doc = document_from_string %(= Document Title\n\ncontent), extension_registry: registry refute_nil doc.extensions assert_equal 1, doc.extensions.groups.size assert doc.extensions.tree_processors? assert_equal 1, doc.extensions.tree_processors.size assert_equal 0, Asciidoctor::Extensions.groups.size end # NOTE I'm not convinced we want to continue to support this use case test 'can provide extension registry created without any groups as option' do registry = Asciidoctor::Extensions.create registry.tree_processor SampleTreeProcessor doc = document_from_string %(= Document Title\n\ncontent), extension_registry: registry refute_nil doc.extensions assert_equal 0, doc.extensions.groups.size assert doc.extensions.tree_processors? assert_equal 1, doc.extensions.tree_processors.size assert_equal 0, Asciidoctor::Extensions.groups.size end test 'can provide extensions proc as option' do doc = document_from_string %(= Document Title\n\ncontent), extensions: proc { tree_processor SampleTreeProcessor } refute_nil doc.extensions assert_equal 1, doc.extensions.groups.size assert doc.extensions.tree_processors? assert_equal 1, doc.extensions.tree_processors.size assert_equal 0, Asciidoctor::Extensions.groups.size end test 'should not activate global registry if :extensions option is false' do begin Asciidoctor::Extensions.register :sample do # this space intentionally left blank end refute_nil Asciidoctor::Extensions.groups assert_equal 1, Asciidoctor::Extensions.groups.size doc = empty_document extensions: false refute doc.extensions?, 'Extensions should not be enabled if :extensions option is false' ensure Asciidoctor::Extensions.unregister_all end end test 'should invoke preprocessors before parsing document' do input = <<~'EOS' junk line = Document Title sample content EOS begin Asciidoctor::Extensions.register do preprocessor ScrubHeaderPreprocessor end doc = document_from_string input assert doc.attr? 'skipped' assert_equal 'junk line', (doc.attr 'skipped').strip assert doc.has_header? assert_equal 'Document Title', doc.doctitle ensure Asciidoctor::Extensions.unregister_all end end test 'should invoke include processor to process include directive' do input = <<~'EOS' before include::lorem-ipsum.txt[] after EOS begin Asciidoctor::Extensions.register do include_processor BoilerplateTextIncludeProcessor end # a custom include processor is not affected by the safe mode result = convert_string input, safe: :secure assert_css '.paragraph > p', result, 3 assert_includes result, 'before' assert_includes result, 'Lorem ipsum' assert_includes result, 'after' ensure Asciidoctor::Extensions.unregister_all end end test 'should invoke include processor if it offers to handle include directive' do input = <<~'EOS' include::skip-me.adoc[] line after skip include::include-file.adoc[] include::fixtures/grandchild-include.adoc[] last line EOS registry = Asciidoctor::Extensions.create do include_processor do handles? do |target| target == 'skip-me.adoc' end process do |_doc, _reader, _target, _attributes| nil end end include_processor do handles? do |target| target == 'include-file.adoc' end process do |_doc, reader, target, attributes| # demonstrates that push_include normalizes newlines content = [ %(found include target '#{target}' at line #{reader.cursor_at_prev_line.lineno}\r\n), %(\r\n), %(middle line\r\n), ] reader.push_include content, target, target, 1, attributes end end end # safe mode only required for built-in include processor document = empty_document base_dir: testdir, extension_registry: registry, safe: :safe reader = Asciidoctor::PreprocessorReader.new document, input, nil, normalize: true lines = [] lines << reader.read_line assert_equal 'line after skip', lines.last lines << reader.read_line lines << reader.read_line assert_equal 'found include target \'include-file.adoc\' at line 4', lines.last assert_equal 'include-file.adoc: line 2', reader.line_info lines << reader.read_line while reader.has_more_lines? source = lines * Asciidoctor::LF assert_match(/^found include target 'include-file.adoc' at line 4$/, source) assert_match(/^middle line$/, source) assert_match(/^last line of grandchild$/, source) assert_match(/^last line$/, source) end test 'should invoke tree processors after parsing document' do input = <<~'EOS' = Document Title Doc Writer content EOS begin Asciidoctor::Extensions.register do tree_processor ReplaceAuthorTreeProcessor end doc = document_from_string input assert_equal 'Ghost Writer', doc.author ensure Asciidoctor::Extensions.unregister_all end end test 'should set source_location on document before invoking tree processors' do begin Asciidoctor::Extensions.register do tree_processor do process do |doc| para = create_paragraph doc.blocks.last.parent, %(file: #{doc.file}, lineno: #{doc.lineno}), {} doc << para end end end sample_doc = fixture_path 'sample.adoc' doc = Asciidoctor.load_file sample_doc, sourcemap: true assert_includes doc.convert, 'file: sample.adoc, lineno: 1' ensure Asciidoctor::Extensions.unregister_all end end test 'should allow tree processor to replace tree' do input = <<~'EOS' = Original Document Doc Writer content EOS begin Asciidoctor::Extensions.register do tree_processor ReplaceTreeTreeProcessor end doc = document_from_string input assert_equal 'Replacement Document', doc.doctitle ensure Asciidoctor::Extensions.unregister_all end end test 'should honor block title assigned in tree processor' do input = <<~'EOS' = Document Title :!example-caption: .Old block title ==== example block content ==== EOS old_title = nil begin Asciidoctor::Extensions.register do tree_processor do process do |doc| ex = (doc.find_by context: :example)[0] old_title = ex.title ex.title = 'New block title' end end end doc = document_from_string input assert_equal 'Old block title', old_title assert_equal 'New block title', (doc.find_by context: :example)[0].title ensure Asciidoctor::Extensions.unregister_all end end test 'should be able to register preferred tree processor' do begin Asciidoctor::Extensions.register do tree_processor do process do |doc| doc << (create_paragraph doc, 'd', {}) nil end end tree_processor do prefer process do |doc| doc << (create_paragraph doc, 'c', {}) nil end end prefer :tree_processor do process do |doc| doc << (create_paragraph doc, 'b', {}) nil end end prefer (tree_processor do process do |doc| doc << (create_paragraph doc, 'a', {}) nil end end) prefer :tree_processor, SelfSigningTreeProcessor end (doc = empty_document).convert assert_equal %w(SelfSigningTreeProcessor a b c d), doc.blocks.map(&:lines).map(&:first) ensure Asciidoctor::Extensions.unregister_all end end test 'should invoke postprocessors after converting document' do input = <<~'EOS' * one * two * three EOS begin Asciidoctor::Extensions.register do postprocessor StripAttributesPostprocessor end output = convert_string input refute_match(/
/, output) ensure Asciidoctor::Extensions.unregister_all end end test 'should yield to document processor block if block has non-zero arity' do input = <<~'EOS' hi! EOS begin Asciidoctor::Extensions.register do tree_processor do |processor| processor.process do |doc| doc << (create_paragraph doc, 'bye!', {}) end end end output = convert_string_to_embedded input assert_xpath '//p', output, 2 assert_xpath '//p[text()="hi!"]', output, 1 assert_xpath '//p[text()="bye!"]', output, 1 ensure Asciidoctor::Extensions.unregister_all end end test 'should invoke processor for custom block' do input = <<~'EOS' [yell] Hi there! [yell,chars=aeiou] Hi there! EOS begin Asciidoctor::Extensions.register do block UppercaseBlock end output = convert_string_to_embedded input assert_xpath '//p', output, 2 assert_xpath '(//p)[1][text()="HI THERE!"]', output, 1 assert_xpath '(//p)[2][text()="hI thErE!"]', output, 1 ensure Asciidoctor::Extensions.unregister_all end end test 'should invoke processor for custom block in an AsciiDoc table cell' do input = <<~'EOS' |=== a| [yell] Hi there! |=== EOS begin Asciidoctor::Extensions.register do block UppercaseBlock end output = convert_string_to_embedded input assert_xpath '/table//p', output, 1 assert_xpath '/table//p[text()="HI THERE!"]', output, 1 ensure Asciidoctor::Extensions.unregister_all end end test 'should yield to syntax processor block if block has non-zero arity' do input = <<~'EOS' [eval] .... 'yolo' * 5 .... EOS begin Asciidoctor::Extensions.register do block :eval do |processor| processor.on_context :literal processor.process do |parent, reader, _attrs| create_paragraph parent, (eval reader.read_lines[0]), {} # rubocop:disable Security/Eval end end end output = convert_string_to_embedded input assert_xpath '//p[text()="yoloyoloyoloyoloyolo"]', output, 1 ensure Asciidoctor::Extensions.unregister_all end end test 'should pass cloaked context in attributes passed to process method of custom block' do input = <<~'EOS' [custom] **** sidebar **** EOS cloaked_context = nil begin Asciidoctor::Extensions.register do block :custom do on_context :sidebar process do |_doc, _reader, attrs| cloaked_context = attrs['cloaked-context'] nil end end end convert_string_to_embedded input assert_equal :sidebar, cloaked_context ensure Asciidoctor::Extensions.unregister_all end end test 'should allow extension to promote paragraph to compound block' do input = <<~'EOS' [ex] example EOS begin Asciidoctor::Extensions.register do block :ex do on_context :paragraph process do |parent, reader| create_example_block parent, reader.read_lines, {}, content_model: :compound end end end output = convert_string_to_embedded input assert_css '.exampleblock .paragraph', output, 1 ensure Asciidoctor::Extensions.unregister_all end end test 'should invoke processor for custom block macro' do input = 'snippet::12345[mode=edit]' begin Asciidoctor::Extensions.register do block_macro SnippetMacro, :snippet end output = convert_string_to_embedded input assert_includes output, '' ensure Asciidoctor::Extensions.unregister_all end end test 'should not parse attributes on custom block macro when resolve attributes is false' do input = 'log::[hello, world!]' begin Asciidoctor::Extensions.register do block_macro :log do resolve_attributes false process do |parent, _, attrs| parent.logger.info attrs['text'] nil end end end using_memory_logger :INFO do |logger| output = convert_string_to_embedded input assert_empty output assert_message logger, :INFO, 'hello, world!' end ensure Asciidoctor::Extensions.unregister_all end end test 'should not parse attributes on custom block macro when content model is :text' do input = 'log::[hello, world!]' begin Asciidoctor::Extensions.register do block_macro :log do content_model :text process do |parent, _, attrs| parent.logger.info attrs['text'] nil end end end using_memory_logger :INFO do |logger| output = convert_string_to_embedded input assert_empty output assert_message logger, :INFO, 'hello, world!' end ensure Asciidoctor::Extensions.unregister_all end end test 'should substitute attributes in target of custom block macro' do input = 'snippet::{gist-id}[mode=edit]' begin Asciidoctor::Extensions.register do block_macro SnippetMacro, :snippet end output = convert_string_to_embedded input, attributes: { 'gist-id' => '12345' } assert_includes output, '' ensure Asciidoctor::Extensions.unregister_all end end test 'should log debug message if custom block macro is unknown' do input = 'unknown::[]' using_memory_logger Logger::Severity::DEBUG do |logger| convert_string_to_embedded input assert_message logger, :DEBUG, ': line 1: unknown name for block macro: unknown', Hash end end test 'should drop block macro line if target references missing attribute and attribute-missing is drop-line' do input = <<~'EOS' [.rolename] snippet::{gist-ns}12345[mode=edit] following paragraph EOS begin Asciidoctor::Extensions.register do block_macro SnippetMacro, :snippet end doc, output = nil, nil using_memory_logger do |logger| doc = document_from_string input, attributes: { 'attribute-missing' => 'drop-line' } assert_equal 1, doc.blocks.size assert_equal :paragraph, doc.blocks[0].context output = doc.convert assert_message logger, :INFO, 'dropping line containing reference to missing attribute: gist-ns' end assert_css '.paragraph', output, 1 assert_css '.rolename', output, 0 ensure Asciidoctor::Extensions.unregister_all end end test 'should invoke processor for custom block macro in an AsciiDoc table cell' do input = <<~'EOS' |=== a|message::hi[] |=== EOS begin Asciidoctor::Extensions.register do block_macro :message do process do |parent, target, _attrs| create_paragraph parent, target.upcase, {} end end end output = convert_string_to_embedded input assert_xpath '/table//p[text()="HI"]', output, 1 ensure Asciidoctor::Extensions.unregister_all end end test 'should match short form of block macro' do input = 'custom-toc::[]' resolved_target = nil begin Asciidoctor::Extensions.register do block_macro do named 'custom-toc' process do |parent, target, _attrs| resolved_target = target create_pass_block parent, '', {}, content_model: :raw end end end output = convert_string_to_embedded input assert_equal '', output assert_equal '', resolved_target ensure Asciidoctor::Extensions.unregister_all end end test 'should fail to convert if name of block macro is illegal' do input = 'illegal name::target[]' begin Asciidoctor::Extensions.register do block_macro do named 'illegal name' process do |_parent, _target, _attrs| nil end end end assert_raises ArgumentError do convert_string_to_embedded input end ensure Asciidoctor::Extensions.unregister_all end end test 'should honor legacy :pos_attrs option set via static method' do begin Asciidoctor::Extensions.register do block_macro LegacyPosAttrsBlockMacro, :diag end result = convert_string_to_embedded 'diag::[filename,png]' assert_css 'img[src="filename.png"]', result, 1 ensure Asciidoctor::Extensions.unregister_all end end test 'should honor legacy :pos_attrs option set via DSL' do begin Asciidoctor::Extensions.register do block_macro :diag do option :pos_attrs, %w(target format) process do |parent, _, attrs| create_image_block parent, { 'target' => %(#{attrs['target']}.#{attrs['format']}) } end end end result = convert_string_to_embedded 'diag::[filename,png]' assert_css 'img[src="filename.png"]', result, 1 ensure Asciidoctor::Extensions.unregister_all end end test 'should be able to set header attribute in block macro processor' do begin Asciidoctor::Extensions.register do block_macro do named :attribute resolves_attributes '1:value' process do |parent, target, attrs| parent.document.set_attr target, attrs['value'] nil end end block_macro do named :header_attribute resolves_attributes '1:value' process do |parent, target, attrs| parent.document.set_header_attribute target, attrs['value'] nil end end end input = <<~'EOS' attribute::yin[yang] header_attribute::foo[bar] EOS doc = document_from_string input assert_nil doc.attr 'yin' assert_equal 'bar', (doc.attr 'foo') ensure Asciidoctor::Extensions.unregister_all end end test 'should invoke processor for custom inline macro' do begin Asciidoctor::Extensions.register do inline_macro TemperatureMacro, :deg end output = convert_string_to_embedded 'Room temperature is deg:25[C,precision=0].', attributes: { 'temperature-unit' => 'F' } assert_includes output, 'Room temperature is 25 °C.' output = convert_string_to_embedded 'Normal body temperature is deg:37[].', attributes: { 'temperature-unit' => 'F' } assert_includes output, 'Normal body temperature is 98.6 °F.' ensure Asciidoctor::Extensions.unregister_all end end test 'should not parse attributes on custom inline macro when resolve attributes is false' do input = 'Line is del:[good]great.' begin Asciidoctor::Extensions.register do inline_macro :del do match_format :short resolve_attributes false process do |parent, _, attrs| create_inline parent, :quoted, attrs['text'], type: :unquoted, attributes: { 'role' => 'line-through' } end end end output = convert_string_to_embedded input assert_includes output, 'good' ensure Asciidoctor::Extensions.unregister_all end end test 'should not parse attributes on custom inline macro when content model is text' do input = 'Line is del:[good]great.' begin Asciidoctor::Extensions.register do inline_macro :del do match_format :short content_model :text process do |parent, _, attrs| create_inline parent, :quoted, attrs['text'], type: :unquoted, attributes: { 'role' => 'line-through' } end end end output = convert_string_to_embedded input assert_includes output, 'good' ensure Asciidoctor::Extensions.unregister_all end end test 'should resolve regexp for inline macro lazily' do begin Asciidoctor::Extensions.register do inline_macro do named :label match_format :short parse_content_as :text process do |parent, _, attrs| create_inline_pass parent, %() end end end output = convert_string_to_embedded 'label:[Checkbox]' assert_includes output, '' ensure Asciidoctor::Extensions.unregister_all end end test 'should map unparsed attrlist to target when format is short' do begin Asciidoctor::Extensions.register do inline_macro do named :label match_format :short process do |parent, target| create_inline_pass parent, %() end end end output = convert_string_to_embedded 'label:[Checkbox]' assert_includes output, '' ensure Asciidoctor::Extensions.unregister_all end end test 'should parse text in square brackets as attrlist by default' do begin Asciidoctor::Extensions.register do inline_macro do named :json match_format :short process do |parent, _, attrs| create_inline_pass parent, %({ #{attrs.map {|k, v| %("#{k}": "#{v}") }.join ', '} }) end end inline_macro do named :data process do |parent, target, attrs| create_inline_pass parent, %({ #{attrs.map {|k, v| %("#{k}": "#{v}") }.join ', '} }) if target == 'json' end end end output = convert_string_to_embedded 'json:[a=A,b=B,c=C]', doctype: :inline assert_equal '{ "a": "A", "b": "B", "c": "C" }', output output = convert_string_to_embedded 'data:json[a=A,b=B,c=C]', doctype: :inline assert_equal '{ "a": "A", "b": "B", "c": "C" }', output ensure Asciidoctor::Extensions.unregister_all end end test 'should assign captures correctly for inline macros' do begin Asciidoctor::Extensions.register do inline_macro do named :short_attributes match_format :short resolve_attributes '1:name' process do |parent, target, attrs| create_inline_pass parent, %(target=#{target.inspect}, attributes=#{attrs.sort_by {|(k)| k.to_s }.to_h}) end end inline_macro do named :short_text match_format :short resolve_attributes false process do |parent, target, attrs| create_inline_pass parent, %(target=#{target.inspect}, attributes=#{attrs.sort_by {|(k)| k.to_s }.to_h}) end end inline_macro do named :'full-attributes' resolve_attributes '1:name' => nil process do |parent, target, attrs| create_inline_pass parent, %(target=#{target.inspect}, attributes=#{attrs.sort_by {|(k)| k.to_s }.to_h}) end end inline_macro do named :'full-text' resolve_attributes false process do |parent, target, attrs| create_inline_pass parent, %(target=#{target.inspect}, attributes=#{attrs.sort_by {|(k)| k.to_s }.to_h}) end end inline_macro do named :@short_match match %r/@(\w+)/ resolve_attributes false process do |parent, target, attrs| create_inline_pass parent, %(target=#{target.inspect}, attributes=#{attrs.sort_by {|(k)| k.to_s }.to_h}) end end end input = <<~'EOS' [subs=normal] ++++ short_attributes:[] short_attributes:[value,key=val] short_text:[] short_text:[[text\]] full-attributes:target[] full-attributes:target[value,key=val] full-text:target[] full-text:target[[text\]] @target ++++ EOS expected = <<~'EOS'.chop target="", attributes={} target="value,key=val", attributes={1=>"value", "key"=>"val", "name"=>"value"} target="", attributes={"text"=>""} target="[text]", attributes={"text"=>"[text]"} target="target", attributes={} target="target", attributes={1=>"value", "key"=>"val", "name"=>"value"} target="target", attributes={"text"=>""} target="target", attributes={"text"=>"[text]"} target="target", attributes={} EOS output = convert_string_to_embedded input assert_equal expected, output ensure Asciidoctor::Extensions.unregister_all end end test 'should invoke convert on return value if value is an inline node' do begin Asciidoctor::Extensions.register do inline_macro do named :mention resolve_attributes false process do |parent, target, attrs| if (text = attrs['text']).empty? text = %(@#{target}) end create_anchor parent, text, type: :link, target: %(https://github.com/#{target}) end end end output = convert_string_to_embedded 'mention:mojavelinux[Dan]' assert_includes output, 'Dan' ensure Asciidoctor::Extensions.unregister_all end end test 'should allow return value of inline macro to be nil' do begin Asciidoctor::Extensions.register do inline_macro do named :skipme match_format :short process do nil end end end using_memory_logger do |logger| output = convert_string_to_embedded '-skipme:[]-', doctype: :inline assert_equal '--', output assert_empty logger end ensure Asciidoctor::Extensions.unregister_all end end test 'should warn if return value of inline macro is a string' do begin Asciidoctor::Extensions.register do inline_macro do named :say process do |_parent, target, _attrs| target end end end using_memory_logger do |logger| output = convert_string_to_embedded 'say:yo[]', doctype: :inline assert_equal 'yo', output assert_message logger, :INFO, 'expected substitution value for custom inline macro to be of type Inline; got String: say:yo[]' end ensure Asciidoctor::Extensions.unregister_all end end test 'should not apply subs to inline node returned by process method by default' do begin Asciidoctor::Extensions.register do inline_macro do named :say process do |parent, target, _attrs| create_inline parent, :quoted, %(*#{target}*), type: :emphasis end end end output = convert_string_to_embedded 'say:yo[]', doctype: :inline assert_equal '*yo*', output ensure Asciidoctor::Extensions.unregister_all end end test 'should apply subs specified as symbol to inline node returned by process method' do begin Asciidoctor::Extensions.register do inline_macro do named :say process do |parent, target, _attrs| create_inline_pass parent, %(*#{target}*), attributes: { 'subs' => :normal } end end end output = convert_string_to_embedded 'say:yo[]', doctype: :inline assert_equal 'yo', output ensure Asciidoctor::Extensions.unregister_all end end test 'should apply subs specified as array to inline node returned by process method' do begin Asciidoctor::Extensions.register do inline_macro do named :say process do |parent, target, _attrs| create_inline_pass parent, %(*#{target}*), attributes: { 'subs' => [:specialchars, :quotes] } end end end output = convert_string_to_embedded 'say:{lt}message{gt}[]', doctype: :inline assert_equal '<message>', output ensure Asciidoctor::Extensions.unregister_all end end test 'should apply subs specified as string to inline node returned by process method' do begin Asciidoctor::Extensions.register do inline_macro do named :say process do |parent, target, _attrs| create_inline_pass parent, %(*#{target}*), attributes: { 'subs' => 'specialchars,quotes' } end end end output = convert_string_to_embedded 'say:{lt}message{gt}[]', doctype: :inline assert_equal '<message>', output ensure Asciidoctor::Extensions.unregister_all end end test 'should prefer attributes parsed from inline macro over default attributes' do begin Asciidoctor::Extensions.register do inline_macro :attrs do match_format :short default_attributes 1 => 'a', 2 => 'b', 'foo' => 'baz' positional_attributes 'a', 'b' process do |parent, _, attrs| create_inline_pass parent, %(a=#{attrs['a']},2=#{attrs[2]},b=#{attrs['b'] || 'nil'},foo=#{attrs['foo']}) end end end output = convert_string_to_embedded 'attrs:[A,foo=bar]', doctype: :inline # NOTE default attributes aren't considered when mapping positional attributes assert_equal 'a=A,2=b,b=nil,foo=bar', output ensure Asciidoctor::Extensions.unregister_all end end test 'should not carry over attributes if block processor returns nil' do begin Asciidoctor::Extensions.register do block do named 'skip-me' on_context :paragraph parse_content_as :raw process do |_parent, _reader, _attrs| nil end end end input = <<~'EOS' .unused title [skip-me] not shown -- shown -- EOS doc = document_from_string input assert_equal 1, doc.blocks.size assert_nil doc.blocks[0].attributes['title'] ensure Asciidoctor::Extensions.unregister_all end end test 'should not invoke process method or carry over attributes if block processor declares skip content model' do begin process_method_called = false Asciidoctor::Extensions.register do block do named :ignore on_context :paragraph parse_content_as :skip process do |_parent, _reader, _attrs| process_method_called = true nil end end end input = <<~'EOS' .unused title [ignore] not shown -- shown -- EOS doc = document_from_string input refute process_method_called assert_equal 1, doc.blocks.size assert_nil doc.blocks[0].attributes['title'] ensure Asciidoctor::Extensions.unregister_all end end test 'should pass attributes by value to block processor' do begin Asciidoctor::Extensions.register do block do named :foo on_context :paragraph parse_content_as :raw process do |parent, reader, attrs| original_attrs = attrs.dup attrs.delete 'title' create_paragraph parent, reader.read_lines, original_attrs.merge('id' => 'value') end end end input = <<~'EOS' .title [foo] content EOS doc = document_from_string input assert_equal 1, doc.blocks.size assert_equal 'title', doc.blocks[0].attributes['title'] assert_equal 'value', doc.blocks[0].id ensure Asciidoctor::Extensions.unregister_all end end test 'should allow extension to replace custom block with a list' do begin Asciidoctor::Extensions.register do block do named :lst on_context :paragraph process do |parent, reader| reader.read_lines.each_with_object create_list parent, :ulist do |line, list| list << (create_list_item list, line) end end end end input = <<~'EOS' before [lst] a b c after EOS doc = document_from_string input assert_equal 3, doc.blocks.size list = doc.blocks[1] assert_equal :ulist, list.context assert_equal 3, list.items.size assert_equal 'a', list.items[0].text assert_css 'li', doc.convert, 3 ensure Asciidoctor::Extensions.unregister_all end end test 'should allow extension to replace custom block with a section' do begin Asciidoctor::Extensions.register do block do named :sect on_context :open process do |parent, _, attrs| create_section parent, attrs['title'], {} end end end input = <<~'EOS' .Section Title [sect] -- a b -- EOS doc = document_from_string input assert_equal 1, doc.blocks.size sect = doc.blocks[0] assert_equal :section, sect.context assert_equal 'Section Title', sect.title assert_equal 2, sect.blocks.size assert_equal :paragraph, sect.blocks[0].context assert_equal :paragraph, sect.blocks[1].context assert_css 'p', doc.convert, 2 ensure Asciidoctor::Extensions.unregister_all end end test 'can use parse_content to append blocks to current parent' do begin Asciidoctor::Extensions.register do block do named :csv on_context :literal process do |parent, reader| parse_content parent, [',==='] + reader.read_lines + [',==='] nil end end end input = <<~'EOS' before [csv] .... a,b,c .... after EOS doc = document_from_string input assert_equal 3, doc.blocks.size table = doc.blocks[1] assert_equal :table, table.context assert_css 'td', doc.convert, 3 ensure Asciidoctor::Extensions.unregister_all end end test 'should ignore return value of custom block if value is parent' do begin Asciidoctor::Extensions.register do block do named :unwrap on_context :open process do |parent, reader| parse_content parent, reader.read_lines end end end input = <<~'EOS' [unwrap] -- a b c -- EOS doc = document_from_string input assert_equal 3, doc.blocks.size doc.blocks.each do |block| assert_equal :paragraph, block.context end assert_equal 'a', doc.blocks[0].source assert_css 'p', doc.convert, 3 ensure Asciidoctor::Extensions.unregister_all end end test 'should ignore return value of custom block macro if value is parent' do begin Asciidoctor::Extensions.register do block_macro :para do process do |parent, target| parse_content parent, target end end end input = <<~'EOS' para::text[] EOS doc = document_from_string input assert_equal 1, doc.blocks.size assert_equal :paragraph, doc.blocks[0].context assert_equal 'text', doc.blocks[0].source assert_css 'p', doc.convert, 1 ensure Asciidoctor::Extensions.unregister_all end end test 'parse_content should not share attributes between parsed blocks' do begin Asciidoctor::Extensions.register do block do named :wrap on_context :open process do |parent, reader, attrs| wrap = create_open_block parent, nil, attrs parse_content wrap, reader.read_lines end end end input = <<~'EOS' [wrap] -- [foo=bar] ==== content ==== [baz=qux] ==== content ==== -- EOS doc = document_from_string input assert_equal 1, doc.blocks.size wrap = doc.blocks[0] assert_equal 2, wrap.blocks.size assert_equal 2, wrap.blocks[0].attributes.size assert_equal 2, wrap.blocks[1].attributes.size assert_nil wrap.blocks[1].attributes['foo'] ensure Asciidoctor::Extensions.unregister_all end end test 'can use parse_attributes to parse attrlist' do begin parsed_attrs = nil Asciidoctor::Extensions.register do block do named :attrs on_context :open process do |parent, reader, _attrs| parsed_attrs = parse_attributes parent, reader.read_line, positional_attributes: %w(a b) parsed_attrs.update parse_attributes parent, 'foo={foo}', sub_attributes: true nil end end end input = <<~'EOS' :foo: bar [attrs] -- a,b,c,key=val -- EOS convert_string_to_embedded input assert_equal 'a', parsed_attrs['a'] assert_equal 'b', parsed_attrs['b'] assert_equal 'val', parsed_attrs['key'] assert_equal 'bar', parsed_attrs['foo'] ensure Asciidoctor::Extensions.unregister_all end end test 'create_section should set up all section properties' do begin sect = nil Asciidoctor::Extensions.register do block_macro do named :sect process do |parent, _target, attrs| opts = (level = attrs.delete 'level') ? { level: level.to_i } : {} attrs['id'] = false if attrs['id'] == 'false' parent = parent.parent if parent.context == :preamble sect = create_section parent, 'Section Title', attrs, opts nil end end end input_tpl = <<~'EOS' = Document Title :doctype: book :sectnums: sect::[%s] EOS { '' => ['chapter', 1, false, true, '_section_title'], 'level=0' => ['part', 0, false, false, '_section_title'], 'level=0,alt' => ['part', 0, false, true, '_section_title', 'partnums' => ''], 'level=0,style=appendix' => ['appendix', 1, true, true, '_section_title'], 'style=appendix' => ['appendix', 1, true, true, '_section_title'], 'style=glossary' => ['glossary', 1, true, false, '_section_title'], 'style=glossary,alt' => ['glossary', 1, true, :chapter, '_section_title', 'sectnums' => 'all'], 'style=abstract' => ['chapter', 1, false, true, '_section_title'], 'id=section-title' => ['chapter', 1, false, true, 'section-title'], 'id=false' => ['chapter', 1, false, true, nil], }.each do |attrlist, (expect_sectname, expect_level, expect_special, expect_numbered, expect_id, extra_attrs)| input = input_tpl % attrlist document_from_string input, safe: :server, attributes: extra_attrs assert_equal expect_sectname, sect.sectname assert_equal expect_level, sect.level assert_equal expect_special, sect.special assert_equal expect_numbered, sect.numbered if expect_id assert_equal expect_id, sect.id else assert_nil sect.id end end ensure Asciidoctor::Extensions.unregister_all end end test 'should add docinfo to document' do input = <<~'EOS' = Document Title sample content EOS begin Asciidoctor::Extensions.register do docinfo_processor MetaRobotsDocinfoProcessor end doc = document_from_string input assert_equal Asciidoctor::SafeMode::SECURE, doc.safe assert_equal '', doc.docinfo ensure Asciidoctor::Extensions.unregister_all end end test 'should add multiple docinfo to document' do input = <<~'EOS' = Document Title sample content EOS begin Asciidoctor::Extensions.register do docinfo_processor MetaAppDocinfoProcessor docinfo_processor MetaRobotsDocinfoProcessor, position: :>> docinfo_processor do at_location :footer process do |_doc| '' end end end doc = document_from_string input, safe: :server assert_equal %(\n), doc.docinfo assert_equal '', doc.docinfo(:footer) ensure Asciidoctor::Extensions.unregister_all end end test 'should append docinfo to document' do begin Asciidoctor::Extensions.register do docinfo_processor MetaRobotsDocinfoProcessor end sample_input_path = fixture_path 'basic.adoc' output = Asciidoctor.convert_file sample_input_path, to_file: false, standalone: true, safe: Asciidoctor::SafeMode::SERVER, attributes: { 'docinfo' => '' } refute_empty output assert_css 'script[src="modernizr.js"]', output, 1 assert_css 'meta[name="robots"]', output, 1 assert_css 'meta[http-equiv="imagetoolbar"]', output, 0 ensure Asciidoctor::Extensions.unregister_all end end test 'should return extension instance after registering' do begin exts = [] Asciidoctor::Extensions.register do exts.push preprocessor SamplePreprocessor exts.push include_processor SampleIncludeProcessor exts.push tree_processor SampleTreeProcessor exts.push docinfo_processor SampleDocinfoProcessor exts.push postprocessor SamplePostprocessor end empty_document exts.each do |ext| assert_kind_of Asciidoctor::Extensions::ProcessorExtension, ext end ensure Asciidoctor::Extensions.unregister_all end end test 'should raise exception if document processor extension does not provide process method' do extension_registry = Asciidoctor::Extensions.create do tree_processor do # rubocop:disable Lint/EmptyBlock end end exception = assert_raises NoMethodError do convert_string_to_embedded 'content', extension_registry: extension_registry end assert_match %r/No block specified to process tree processor extension at .*extensions_test\.rb:\d+/, exception.message end test 'should raise exception if syntax processor extension does not provide process method' do extension_registry = Asciidoctor::Extensions.create do block_macro :foo do # rubocop:disable Lint/EmptyBlock end end exception = assert_raises NoMethodError do convert_string_to_embedded 'foo::bar[]', extension_registry: extension_registry end assert_match %r/No block specified to process block macro extension at .*extensions_test\.rb:\d+/, exception.message end test 'should raise exception if syntax processor extension does not provide a name' do macro_name = (Class.new String do def to_sym nil end end).new 'foo' extension_registry = Asciidoctor::Extensions.create do block_macro do named macro_name process do nil end end end exception = assert_raises ArgumentError do convert_string_to_embedded 'foo::bar[]', extension_registry: extension_registry end assert_match %r/No name specified for block macro extension at .*extensions_test\.rb:\d+/, exception.message end test 'should raise an exception if mandatory target attribute is not provided for image block' do input = 'cat_in_sink::[]' exception = assert_raises ArgumentError do convert_string_to_embedded input, extension_registry: create_cat_in_sink_block_macro end assert_match %r/target attribute is required/, exception.message end test 'should assign alt attribute to image block if alt is not provided' do input = 'cat_in_sink::25[]' doc = document_from_string input, standalone: false, extension_registry: create_cat_in_sink_block_macro image = doc.blocks[0] assert_equal 'cat in sink day 25', (image.attr 'alt') assert_equal 'cat in sink day 25', (image.attr 'default-alt') output = doc.convert assert_includes output, 'cat in sink day 25' end test 'should create an image block if mandatory attributes are provided' do input = 'cat_in_sink::30[cat in sink (yes)]' doc = document_from_string input, standalone: false, extension_registry: create_cat_in_sink_block_macro image = doc.blocks[0] assert_equal 'cat in sink (yes)', (image.attr 'alt') refute image.attr? 'default-alt' output = doc.convert assert_includes output, 'cat in sink (yes)' end test 'should not assign caption on image block if title is not set on custom block macro' do input = 'cat_in_sink::30[]' doc = document_from_string input, standalone: false, extension_registry: create_cat_in_sink_block_macro output = doc.convert assert_xpath '/*[@class="imageblock"]/*[@class="title"]', output, 0 end test 'should assign caption on image block if title is set on custom block macro' do input = <<~'EOS' .Cat in Sink? cat_in_sink::30[] EOS doc = document_from_string input, standalone: false, extension_registry: create_cat_in_sink_block_macro output = doc.convert assert_xpath '/*[@class="imageblock"]/*[@class="title"][text()="Figure 1. Cat in Sink?"]', output, 1 end test 'should not fail if alt attribute is not set on block image node' do begin Asciidoctor::Extensions.register do block_macro :no_alt do process do |parent, _target, _attrs| create_block parent, 'image', nil, { 'target' => 'picture.jpg' } end end end output = Asciidoctor.convert 'no_alt::[]' assert_include '', output ensure Asciidoctor::Extensions.unregister_all end end test 'should not fail if alt attribute is not set on inline image node' do begin Asciidoctor::Extensions.register do inline_macro :no_alt do match_format :short process do |parent, _target, _attrs| create_inline parent, 'image', nil, target: 'picture.jpg' end end end output = Asciidoctor.convert 'no_alt:[]' assert_include '', output ensure Asciidoctor::Extensions.unregister_all end end test 'should assign id and role on list items unordered' do input = 'santa_list::ulist[]' doc = document_from_string input, standalone: false, extension_registry: create_santa_list_block_macro output = doc.convert assert_xpath '/div[@class="ulist"]/ul/li[@class="friendly"][@id="santa-list-guillaume"]', output, 1 assert_xpath '/div[@class="ulist"]/ul/li[@class="kind contributor java"]', output, 1 assert_xpath '/div[@class="ulist"]/ul/li[@class="kind contributor java"][not(@id)]', output, 1 assert_xpath '/div[@class="ulist"]/ul/li[@id="santa-list-pepijn"][not(@class)]', output, 1 assert_xpath '/div[@class="ulist"]/ul/li[@id="santa-list-dan"][@class="naughty"]', output, 1 assert_xpath '/div[@class="ulist"]/ul/li[not(@id)][not(@class)]/p[text()="Sarah"]', output, 1 end test 'should assign id and role on list items ordered' do input = 'santa_list::olist[]' doc = document_from_string input, standalone: false, extension_registry: create_santa_list_block_macro output = doc.convert assert_xpath '/div[@class="olist"]/ol/li[@class="friendly"][@id="santa-list-guillaume"]', output, 1 assert_xpath '/div[@class="olist"]/ol/li[@class="kind contributor java"]', output, 1 assert_xpath '/div[@class="olist"]/ol/li[@class="kind contributor java"][not(@id)]', output, 1 assert_xpath '/div[@class="olist"]/ol/li[@id="santa-list-pepijn"][not(@class)]', output, 1 assert_xpath '/div[@class="olist"]/ol/li[@id="santa-list-dan"][@class="naughty"]', output, 1 assert_xpath '/div[@class="olist"]/ol/li[not(@id)][not(@class)]/p[text()="Sarah"]', output, 1 end end end