# encoding: UTF-8 unless defined? ASCIIDOCTOR_PROJECT_DIR $: << File.dirname(__FILE__); $:.uniq! require 'test_helper' end class ExtensionsInitTest < Minitest::Test def test_autoload doc = empty_document refute doc.extensions?, 'Extensions should not be enabled by default' begin # NOTE trigger extensions to autoload by registering empty group Asciidoctor::Extensions.register do end rescue; 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 else nil 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(/<(\w+).*?>/m, "<\\1>") end end class UppercaseBlock < Asciidoctor::Extensions::BlockProcessor; use_dsl named :yell bound_to :paragraph parses_content_as :simple def process parent, reader, attributes create_paragraph parent, reader.lines.map(&:upcase), attributes end end class SnippetMacro < Asciidoctor::Extensions::BlockMacroProcessor def process parent, target, attributes create_pass_block parent, %(), {}, :content_model => :raw end end class TemperatureMacro < Asciidoctor::Extensions::InlineMacroProcessor; use_dsl named :degrees resolves_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' %(#{round_with_precision c, precision} °C) when 'F' %(#{round_with_precision c * 1.8 + 32, precision} °F) else raise ::ArgumentError, %(Unknown temperature units: #{units}) end end if (::Numeric.instance_method :round).arity == 0 def round_with_precision value, precision = 0 if precision == 0 value.round else factor = 10 ** precision if precision < 0 (value * factor).round.div factor else (value * factor).round.fdiv factor end end end else def round_with_precision value, precision = 0 value.round precision 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 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 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 get class for top-level class name' do clazz = Asciidoctor::Extensions.class_for_name 'String' refute_nil clazz assert_equal String, clazz end test 'should get class for class name in module' do clazz = Asciidoctor::Extensions.class_for_name 'Asciidoctor::Document' refute_nil clazz assert_equal Asciidoctor::Document, clazz end test 'should get class for class name resolved from root' do clazz = Asciidoctor::Extensions.class_for_name '::Asciidoctor::Document' refute_nil clazz assert_equal Asciidoctor::Document, clazz end test 'should raise exception if cannot find class for name' do begin Asciidoctor::Extensions.class_for_name 'InvalidModule::InvalidClass' flunk 'Expecting RuntimeError to be raised' rescue NameError => e assert_equal 'Could not resolve class for name: InvalidModule::InvalidClass', e.message end end test 'should raise exception if constant name is invalid' do begin Asciidoctor::Extensions.class_for_name 'foobar' flunk 'Expecting RuntimeError to be raised' rescue NameError => e assert_equal 'Could not resolve class for name: foobar', e.message end end test 'should raise exception if class not found in scope' do begin Asciidoctor::Extensions.class_for_name 'Asciidoctor::Extensions::String' flunk 'Expecting RuntimeError to be raised' rescue NameError => e assert_equal 'Could not resolve class for name: Asciidoctor::Extensions::String', e.message end end test 'should raise exception if name resolves to module' do begin Asciidoctor::Extensions.class_for_name 'Asciidoctor::Extensions' flunk 'Expecting RuntimeError to be raised' rescue NameError => e assert_equal 'Could not resolve class for name: Asciidoctor::Extensions', e.message end end test 'should resolve class if class is given' do clazz = Asciidoctor::Extensions.resolve_class Asciidoctor::Document refute_nil clazz assert_equal Asciidoctor::Document, clazz end test 'should resolve class if class from string' do clazz = Asciidoctor::Extensions.resolve_class 'Asciidoctor::Document' refute_nil clazz assert_equal Asciidoctor::Document, clazz end test 'should not resolve class if not in scope' do begin Asciidoctor::Extensions.resolve_class 'Asciidoctor::Extensions::String' flunk 'Expecting RuntimeError to be raised' rescue NameError => e assert_equal 'Could not resolve class for name: Asciidoctor::Extensions::String', e.message end end test 'should raise NameError if extension class cannot be resolved from string' do begin Asciidoctor::Extensions.register do block 'foobar' end empty_document flunk 'Expecting RuntimeError to be raised' rescue NameError => e assert_equal 'Could not resolve class for name: foobar', e.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 bound_to :paragraph parses_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 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 '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 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 macro' do input = <<-EOS before include::lorem-ipsum.txt[] after EOS begin Asciidoctor::Extensions.register do include_processor BoilerplateTextIncludeProcessor end result = convert_string input, :safe => :server 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 call include processor to process include directive' do input = <<-EOS first line include::include-file.asciidoc[] last line EOS registry = Asciidoctor::Extensions.create do include_processor do handles? do |target| target == 'include-file.asciidoc' end process do |doc, reader, target, attributes| # demonstrate that push_include normalizes endlines content = ["include target:: #{target}\n", "\n", "middle line\n"] reader.push_include content, target, target, 1, attributes end end end # Safe Mode is not required here document = empty_document :base_dir => testdir, :extension_registry => registry reader = Asciidoctor::PreprocessorReader.new document, input, nil, :normalize => true lines = [] lines << reader.read_line lines << reader.read_line lines << reader.read_line assert_equal 'include target:: include-file.asciidoc', lines.last assert_equal 'include-file.asciidoc: line 2', reader.line_info while reader.has_more_lines? lines << reader.read_line end source = lines * ::Asciidoctor::LF assert_match(/^include target:: include-file.asciidoc$/, source) assert_match(/^middle 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.asciidoc' doc = Asciidoctor.load_file sample_doc, :sourcemap => true assert_includes doc.convert, 'file: sample.asciidoc, 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 { process do |doc| doc << (create_paragraph doc, 'a', {}) nil end } prefer :tree_processor, SelfSigningTreeProcessor end (doc = empty_document).convert assert_equal %w(SelfSigningTreeProcessor a b c d), doc.blocks.map {|b| b.lines[0] } 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(/
'
end
test 'should create an image block if mandatory attributes are provided' do
input = <<-EOS
cat_in_sink::30[cat in sink (yes)]
EOS
doc = document_from_string input, :header_footer => 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, '
'
end
test 'should not assign caption on image block if title is not set on custom block macro' do
input = <<-EOS
cat_in_sink::30[]
EOS
doc = document_from_string input, :header_footer => 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, :header_footer => 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
end
end