use crate::tests::sdd::*;
track_file!("ref/asciidoctor/test/extensions_test.rb");
non_normative!(
r##"
# 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'
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
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, %(<script src="http://example.com/#{target}.js?_mode=#{attributes['mode']}"></script>), {}, content_model: :raw
end
end
class LegacyPosAttrsBlockMacro < Asciidoctor::Extensions::BlockMacroProcessor
option :pos_attrs, ['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
'<meta name="robots" content="index,follow">'
end
end
class MetaAppDocinfoProcessor < Asciidoctor::Extensions::DocinfoProcessor; use_dsl
at_location :head
def process document
'<meta name="application-name" content="Asciidoctor App">'
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
"##
);
non_normative!(
r##"
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 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
empty_document
flunk 'Expecting RuntimeError to be raised'
rescue NameError => e
assert_match %r/^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
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
"##
);
non_normative!(
r##"
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
"##
);
non_normative!(
r##"
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
refute extensions.first.instance.frozen?
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
refute extensions.first.instance.frozen?
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
refute extensions.first.instance.frozen?
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
refute extensions.first.instance.frozen?
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
refute extensions.first.instance.frozen?
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
refute extension.instance.frozen?
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
"##
);
non_normative!(
r##"
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 not activate global registry if :extensions option is false' do
begin
Asciidoctor::Extensions.register :sample do
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|
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
while reader.has_more_lines?
lines << reader.read_line
end
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(/<div class="ulist">/, 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]), {}
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, '<script src="http://example.com/12345.js?_mode=edit"></script>'
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, '<script src="http://example.com/12345.js?_mode=edit"></script>'
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|
result = convert_string_to_embedded input
assert_message logger, :DEBUG, '<stdin>: line 1: unknown name for block macro: unknown', Hash
assert_xpath %(/*[@class="paragraph"]/p[text()="#{input}"]), result, 1
end
end
test 'should log debug message if custom block macro is unknown when custom block macros are registered' do
begin
input = 'unknown::[]'
Asciidoctor::Extensions.register do
block_macro SampleBlockMacro, :sample
end
using_memory_logger Logger::Severity::DEBUG do |logger|
result = convert_string_to_embedded input
assert_message logger, :DEBUG, '<stdin>: line 1: unknown name for block macro: unknown', Hash
assert_xpath %(/*[@class="paragraph"]/p[text()="#{input}"]), result, 1
end
ensure
Asciidoctor::Extensions.unregister_all
end
end
test 'should not log debug message if line is not a custom block macro and block macros are registered' do
begin
input = '* xref:component::page.adoc[link text]'
Asciidoctor::Extensions.register do
block_macro SampleBlockMacro, :sample
end
using_memory_logger Logger::Severity::DEBUG do |logger|
result = convert_string_to_embedded input
assert_empty logger.messages
assert_css 'ul li a[href="component::page.html"]', result, 1
end
ensure
Asciidoctor::Extensions.unregister_all
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, '<!-- custom toc goes here -->', {}, content_model: :raw
end
end
end
output = convert_string_to_embedded input
assert_equal '<!-- custom toc goes here -->', 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, ['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 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, %(<label>#{attrs['text']}</label>)
end
end
end
output = convert_string_to_embedded 'label:[Checkbox]'
assert_includes output, '<label>Checkbox</label>'
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, %(<label>#{target}</label>)
end
end
end
output = convert_string_to_embedded 'label:[Checkbox]'
assert_includes output, '<label>Checkbox</label>'
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|
if target == 'json'
create_inline_pass parent, %({ #{attrs.map {|k, v| %["#{k}": "#{v}"] }.join ', '} })
else
nil
end
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).gsub ' => ', '=>').gsub ', ', ','
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, '<a href="https://github.com/mojavelinux">Dan</a>'
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 '<em>*yo*</em>', 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 '<strong>yo</strong>', 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 '<strong><message></strong>', 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 '<strong><message></strong>', 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 that 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 coerce names of positional attributes to strings' do
begin
Asciidoctor::Extensions.register do
inline_macro :attrs do
match_format :short
positional_attributes :a, 'b'
process do |parent, _, attrs|
create_inline_pass parent, %(a=#{attrs['a']},b=#{attrs['b']})
end
end
end
output = convert_string_to_embedded 'attrs:[A,B]', doctype: :inline
assert_equal 'a=A,b=B', 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 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 '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 '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: ['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 '<meta name="robots" content="index,follow">', 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|
'<script><!-- analytics code --></script>'
end
end
end
doc = document_from_string input, safe: :server
assert_equal %(<meta name="robots" content="index,follow">\n<meta name="application-name" content="Asciidoctor App">), doc.docinfo
assert_equal '<script><!-- analytics code --></script>', 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, '<img src="cat-in-sink-day-25.png" alt="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, '<img src="cat-in-sink-day-30.png" alt="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 '<img src="picture.jpg" alt="">', 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 '<span class="image"><img src="picture.jpg" alt=""></span>', 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
"##
);