# encoding: UTF-8 unless defined? ASCIIDOCTOR_PROJECT_DIR $: << File.dirname(__FILE__); $:.uniq! require 'test_helper' end context 'Attributes' do default_logger = Asciidoctor::LoggerManager.logger setup do Asciidoctor::LoggerManager.logger = (@logger = Asciidoctor::MemoryLogger.new) end teardown do Asciidoctor::LoggerManager.logger = default_logger end context 'Assignment' do test 'creates an attribute' do doc = document_from_string(':frog: Tanglefoot') assert_equal 'Tanglefoot', doc.attributes['frog'] end test 'requires a space after colon following attribute name' do doc = document_from_string 'foo:bar' assert_nil doc.attributes['foo'] end # NOTE AsciiDoc Python recognizes this entry test 'does not recognize attribute entry if name contains colon' do input = <<-EOS.chomp :foo:bar: baz EOS doc = document_from_string input refute doc.attr?('foo:bar') assert_equal 1, doc.blocks.size assert_equal :paragraph, doc.blocks[0].context end # NOTE AsciiDoc Python recognizes this entry test 'does not recognize attribute entry if name ends with colon' do input = <<-EOS.chomp :foo:: bar EOS doc = document_from_string input refute doc.attr?('foo:') assert_equal 1, doc.blocks.size assert_equal :dlist, doc.blocks[0].context end # NOTE AsciiDoc Python does not recognize this entry test 'allows any word character defined by Unicode in an attribute name' do [['café', 'a coffee shop'], ['سمن', %(سازمان مردمنهاد)]].each do |(name, value)| str = <<-EOS :#{name}: #{value} {#{name}} EOS result = convert_string_to_embedded str assert_includes result, %(
#{value}
) end end if ::RUBY_MIN_VERSION_1_9 test 'creates an attribute by fusing a legacy multi-line value' do str = <<-EOS :description: This is the first + Ruby implementation of + AsciiDoc. EOS doc = document_from_string(str) assert_equal 'This is the first Ruby implementation of AsciiDoc.', doc.attributes['description'] end test 'creates an attribute by fusing a multi-line value' do str = <<-EOS :description: This is the first \\ Ruby implementation of \\ AsciiDoc. EOS doc = document_from_string(str) assert_equal 'This is the first Ruby implementation of AsciiDoc.', doc.attributes['description'] end test 'honors line break characters in multi-line values' do str = <<-EOS :signature: Linus Torvalds + \\ Linux Hacker + \\ linus.torvalds@example.com EOS doc = document_from_string(str) assert_equal %(Linus Torvalds +\nLinux Hacker +\nlinus.torvalds@example.com), doc.attributes['signature'] end test 'should allow pass macro to surround a multi-line value that contains line breaks' do str = <<-EOS :signature: pass:a[{author} + \\ {title} + \\ {email}] EOS doc = document_from_string str, :attributes => { 'author' => 'Linus Torvalds', 'title' => 'Linux Hacker', 'email' => 'linus.torvalds@example.com' } assert_equal %(Linus Torvalds +\nLinux Hacker +\nlinus.torvalds@example.com), (doc.attr 'signature') end test 'should delete an attribute that ends with !' do doc = document_from_string(":frog: Tanglefoot\n:frog!:") assert_nil doc.attributes['frog'] end test 'should delete an attribute that ends with ! set via API' do doc = document_from_string(":frog: Tanglefoot", :attributes => {'frog!' => ''}) assert_nil doc.attributes['frog'] end test 'should delete an attribute that begins with !' do doc = document_from_string(":frog: Tanglefoot\n:!frog:") assert_nil doc.attributes['frog'] end test 'should delete an attribute that begins with ! set via API' do doc = document_from_string(":frog: Tanglefoot", :attributes => {'!frog' => ''}) assert_nil doc.attributes['frog'] end test 'should delete an attribute set via API to nil value' do doc = document_from_string(":frog: Tanglefoot", :attributes => {'frog' => nil}) assert_nil doc.attributes['frog'] end test "doesn't choke when deleting a non-existing attribute" do doc = document_from_string(':frog!:') assert_nil doc.attributes['frog'] end test "replaces special characters in attribute value" do doc = document_from_string(":xml-busters: <>&") assert_equal '<>&', doc.attributes['xml-busters'] end test "performs attribute substitution on attribute value" do doc = document_from_string(":version: 1.0\n:release: Asciidoctor {version}") assert_equal 'Asciidoctor 1.0', doc.attributes['release'] end test 'assigns attribute to empty string if substitution fails to resolve attribute' do input = ':release: Asciidoctor {version}' document_from_string input, :attributes => { 'attribute-missing' => 'drop-line' } assert_message @logger, :WARN, 'dropping line containing reference to missing attribute: version' end test 'assigns multi-line attribute to empty string if substitution fails to resolve attribute' do input = <<-EOS :release: Asciidoctor + {version} EOS doc = document_from_string input, :attributes => { 'attribute-missing' => 'drop-line' } assert_equal '', doc.attributes['release'] assert_message @logger, :WARN, 'dropping line containing reference to missing attribute: version' end test 'resolves attributes inside attribute value within header' do input = <<-EOS = Document Title :big: big :bigfoot: {big}foot {bigfoot} EOS result = convert_string_to_embedded input assert_includes result, 'bigfoot' end test 'resolves attributes and pass macro inside attribute value outside header' do input = <<-EOS = Document Title content :big: pass:a,q[_big_] :bigfoot: {big}foot {bigfoot} EOS result = convert_string_to_embedded input assert_includes result, 'bigfoot' end test 'should limit maximum size of attribute value if safe mode is SECURE' do expected = 'a' * 4096 input = <<-EOS :name: #{'a' * 5000} {name} EOS result = convert_inline_string input assert_equal expected, result assert_equal 4096, result.bytesize end test 'should handle multibyte characters when limiting attribute value size' do expected = '日本' input = <<-EOS :name: 日本語 {name} EOS result = convert_inline_string input, :attributes => { 'max-attribute-value-size' => 6 } assert_equal expected, result assert_equal 6, result.bytesize end test 'should not mangle multibyte characters when limiting attribute value size' do expected = '日本' input = <<-EOS :name: 日本語 {name} EOS result = convert_inline_string input, :attributes => { 'max-attribute-value-size' => 8 } assert_equal expected, result assert_equal 6, result.bytesize end test 'should allow maximize size of attribute value to be disabled' do expected = 'a' * 5000 input = <<-EOS :name: #{'a' * 5000} {name} EOS result = convert_inline_string input, :attributes => { 'max-attribute-value-size' => nil } assert_equal expected, result assert_equal 5000, result.bytesize end test 'resolves user-home attribute if safe mode is less than SERVER' do input = <<-EOS :imagesdir: {user-home}/etc/images {imagesdir} EOS output = convert_inline_string input, :safe => :safe if RUBY_VERSION >= '1.9' assert_equal %(#{Dir.home}/etc/images), output else assert_equal %(#{ENV['HOME']}/etc/images), output end end test 'user-home attribute resolves to . if safe mode is SERVER or greater' do input = <<-EOS :imagesdir: {user-home}/etc/images {imagesdir} EOS output = convert_inline_string input, :safe => :server assert_equal './etc/images', output end test "apply custom substitutions to text in passthrough macro and assign to attribute" do doc = document_from_string(":xml-busters: pass:[<>&]") assert_equal '<>&', doc.attributes['xml-busters'] doc = document_from_string(":xml-busters: pass:none[<>&]") assert_equal '<>&', doc.attributes['xml-busters'] doc = document_from_string(":xml-busters: pass:specialcharacters[<>&]") assert_equal '<>&', doc.attributes['xml-busters'] end test 'should not recognize pass macro with invalid substitution list in attribute value' do [',', '42', 'a,'].each do |subs| doc = document_from_string %(:pass-fail: pass:#{subs}[whale]) assert_equal %(pass:#{subs}[whale]), doc.attributes['pass-fail'] end end test "attribute is treated as defined until it's not" do input = <<-EOS :holygrail: ifdef::holygrail[] The holy grail has been found! endif::holygrail[] :holygrail!: ifndef::holygrail[] Buggers! What happened to the grail? endif::holygrail[] EOS output = convert_string input assert_xpath '//p', output, 2 assert_xpath '(//p)[1][text() = "The holy grail has been found!"]', output, 1 assert_xpath '(//p)[2][text() = "Buggers! What happened to the grail?"]', output, 1 end test 'attribute set via API overrides attribute set in document' do doc = document_from_string(':cash: money', :attributes => {'cash' => 'heroes'}) assert_equal 'heroes', doc.attributes['cash'] end test 'attribute set via API cannot be unset by document' do doc = document_from_string(':cash!:', :attributes => {'cash' => 'heroes'}) assert_equal 'heroes', doc.attributes['cash'] end test 'attribute soft set via API using modifier on name can be overridden by document' do doc = document_from_string(':cash: money', :attributes => {'cash@' => 'heroes'}) assert_equal 'money', doc.attributes['cash'] end test 'attribute soft set via API using modifier on value can be overridden by document' do doc = document_from_string(':cash: money', :attributes => {'cash' => 'heroes@'}) assert_equal 'money', doc.attributes['cash'] end test 'attribute soft set via API using modifier on name can be unset by document' do doc = document_from_string(':cash!:', :attributes => {'cash@' => 'heroes'}) assert_nil doc.attributes['cash'] doc = document_from_string(':cash!:', :attributes => {'cash@' => true}) assert_nil doc.attributes['cash'] end test 'attribute soft set via API using modifier on value can be unset by document' do doc = document_from_string(':cash!:', :attributes => {'cash' => 'heroes@'}) assert_nil doc.attributes['cash'] end test 'attribute unset via API cannot be set by document' do [ { 'cash!' => '' }, { '!cash' => '' }, { 'cash' => nil }, ].each do |attributes| doc = document_from_string(':cash: money', :attributes => attributes) assert_nil doc.attributes['cash'] end end test 'attribute soft unset via API can be set by document' do [ { 'cash!@' => '' }, { '!cash@' => '' }, { 'cash!' => '@' }, { '!cash' => '@' }, { 'cash' => false }, ].each do |attributes| doc = document_from_string(':cash: money', :attributes => attributes) assert_equal 'money', doc.attributes['cash'] end end test 'can soft unset built-in attribute from API and still override in document' do [ { 'sectids!@' => '' }, { '!sectids@' => '' }, { 'sectids!' => '@' }, { '!sectids' => '@' }, { 'sectids' => false }, ].each do |attributes| doc = document_from_string '== Heading', :attributes => attributes refute doc.attr?('sectids') assert_css '#_heading', (doc.convert :header_footer => false), 0 doc = document_from_string %(:sectids:\n\n== Heading), :attributes => attributes assert doc.attr?('sectids') assert_css '#_heading', (doc.convert :header_footer => false), 1 end end test 'backend and doctype attributes are set by default in default configuration' do input = <<-EOS = Document Title Author Name content EOS doc = document_from_string input expect = { 'backend' => 'html5', 'backend-html5' => '', 'backend-html5-doctype-article' => '', 'outfilesuffix' => '.html', 'basebackend' => 'html', 'basebackend-html' => '', 'basebackend-html-doctype-article' => '', 'doctype' => 'article', 'doctype-article' => '', 'filetype' => 'html', 'filetype-html' => '' } expect.each do |key, val| assert doc.attributes.key? key assert_equal val, doc.attributes[key] end end test 'backend and doctype attributes are set by default in custom configuration' do input = <<-EOS = Document Title Author Name content EOS doc = document_from_string input, :doctype => 'book', :backend => 'docbook' expect = { 'backend' => 'docbook5', 'backend-docbook5' => '', 'backend-docbook5-doctype-book' => '', 'outfilesuffix' => '.xml', 'basebackend' => 'docbook', 'basebackend-docbook' => '', 'basebackend-docbook-doctype-book' => '', 'doctype' => 'book', 'doctype-book' => '', 'filetype' => 'xml', 'filetype-xml' => '' } expect.each do |key, val| assert doc.attributes.key? key assert_equal val, doc.attributes[key] end end test 'backend attributes are updated if backend attribute is defined in document and safe mode is less than SERVER' do input = <<-EOS = Document Title Author Name :backend: docbook :doctype: book content EOS doc = document_from_string input, :safe => Asciidoctor::SafeMode::SAFE expect = { 'backend' => 'docbook5', 'backend-docbook5' => '', 'backend-docbook5-doctype-book' => '', 'outfilesuffix' => '.xml', 'basebackend' => 'docbook', 'basebackend-docbook' => '', 'basebackend-docbook-doctype-book' => '', 'doctype' => 'book', 'doctype-book' => '', 'filetype' => 'xml', 'filetype-xml' => '' } expect.each do |key, val| assert doc.attributes.key?(key) assert_equal val, doc.attributes[key] end refute doc.attributes.key?('backend-html5') refute doc.attributes.key?('backend-html5-doctype-article') refute doc.attributes.key?('basebackend-html') refute doc.attributes.key?('basebackend-html-doctype-article') refute doc.attributes.key?('doctype-article') refute doc.attributes.key?('filetype-html') end test 'backend attributes defined in document options overrides backend attribute in document' do doc = document_from_string(':backend: docbook45', :safe => Asciidoctor::SafeMode::SAFE, :attributes => {'backend' => 'html5'}) assert_equal 'html5', doc.attributes['backend'] assert doc.attributes.has_key? 'backend-html5' assert_equal 'html', doc.attributes['basebackend'] assert doc.attributes.has_key? 'basebackend-html' end test 'can only access a positional attribute from the attributes hash' do node = Asciidoctor::Block.new nil, :paragraph, :attributes => { 1 => 'position 1' } assert_nil node.attr(1) refute node.attr?(1) assert_equal 'position 1', node.attributes[1] end test 'set_attr should set value to empty string if no value is specified' do node = Asciidoctor::Block.new nil, :paragraph, :attributes => {} node.set_attr 'foo' assert_equal '', (node.attr 'foo') end test 'remove_attr should remove attribute and return previous value' do doc = empty_document node = Asciidoctor::Block.new doc, :paragraph, :attributes => { 'foo' => 'bar' } assert_equal 'bar', (node.remove_attr 'foo') assert_nil node.attr('foo') end test 'set_attr should not overwrite existing key if overwrite is false' do node = Asciidoctor::Block.new nil, :paragraph, :attributes => { 'foo' => 'bar' } assert_equal 'bar', (node.attr 'foo') node.set_attr 'foo', 'baz', false assert_equal 'bar', (node.attr 'foo') end test 'set_attr should overwrite existing key by default' do node = Asciidoctor::Block.new nil, :paragraph, :attributes => { 'foo' => 'bar' } assert_equal 'bar', (node.attr 'foo') node.set_attr 'foo', 'baz' assert_equal 'baz', (node.attr 'foo') end test 'set_attr should set header attribute in loaded document' do input = <<-EOS :uri: http://example.org {uri} EOS doc = Asciidoctor.load input, :attributes => { 'uri' => 'https://github.com' } doc.set_attr 'uri', 'https://google.com' output = doc.convert assert_xpath '//a[@href="https://google.com"]', output, 1 end test 'set_attribute should set attribute if key is not locked' do doc = empty_document refute doc.attr? 'foo' res = doc.set_attribute 'foo', 'baz' assert res assert_equal 'baz', (doc.attr 'foo') end test 'set_attribute should not set key if key is locked' do doc = empty_document :attributes => { 'foo' => 'bar' } assert_equal 'bar', (doc.attr 'foo') res = doc.set_attribute 'foo', 'baz' refute res assert_equal 'bar', (doc.attr 'foo') end test 'set_attribute should update backend attributes' do doc = empty_document :attributes => { 'backend' => 'html5@' } assert_equal '', (doc.attr 'backend-html5') res = doc.set_attribute 'backend', 'docbook5' assert res refute doc.attr? 'backend-html5' assert_equal '', (doc.attr 'backend-docbook5') end test 'verify toc attribute matrix' do expected_data = <<-EOS #attributes |toc|toc-position|toc-placement|toc-class toc | |nil |auto |nil toc=header | |nil |auto |nil toc=beeboo | |nil |auto |nil toc=left | |left |auto |toc2 toc2 | |left |auto |toc2 toc=right | |right |auto |toc2 toc=preamble | |content |preamble |nil toc=macro | |content |macro |nil toc toc-placement=macro toc-position=left | |content |macro |nil toc toc-placement! | |content |macro |nil EOS expected = expected_data.strip.lines.map {|l| next if l.start_with? '#' l.split('|').map {|e| (e = e.strip) == 'nil' ? nil : e } }.compact expected.each do |expect| raw_attrs, toc, toc_position, toc_placement, toc_class = expect attrs = Hash[*raw_attrs.split.map {|e| e.include?('=') ? e.split('=', 2) : [e, ''] }.flatten] doc = document_from_string '', :attributes => attrs toc ? (assert doc.attr?('toc', toc)) : (refute doc.attr?('toc')) toc_position ? (assert doc.attr?('toc-position', toc_position)) : (refute doc.attr?('toc-position')) toc_placement ? (assert doc.attr?('toc-placement', toc_placement)) : (refute doc.attr?('toc-placement')) toc_class ? (assert doc.attr?('toc-class', toc_class)) : (refute doc.attr?('toc-class')) end end end context 'Interpolation' do test "convert properly with simple names" do html = convert_string(":frog: Tanglefoot\n:my_super-hero: Spiderman\n\nYo, {frog}!\nBeat {my_super-hero}!") result = Nokogiri::HTML(html) assert_equal "Yo, Tanglefoot!\nBeat Spiderman!", result.css("p").first.content.strip end test 'attribute lookup is not case sensitive' do input = <<-EOS :He-Man: The most powerful man in the universe He-Man: {He-Man} She-Ra: {She-Ra} EOS result = convert_string_to_embedded input, :attributes => {'She-Ra' => 'The Princess of Power'} assert_xpath '//p[text()="He-Man: The most powerful man in the universe"]', result, 1 assert_xpath '//p[text()="She-Ra: The Princess of Power"]', result, 1 end test "convert properly with single character name" do html = convert_string(":r: Ruby\n\nR is for {r}!") result = Nokogiri::HTML(html) assert_equal 'R is for Ruby!', result.css("p").first.content.strip end test "collapses spaces in attribute names" do input = <<-EOS Main Header =========== :My frog: Tanglefoot Yo, {myfrog}! EOS output = convert_string input assert_xpath '(//p)[1][text()="Yo, Tanglefoot!"]', output, 1 end test 'ignores lines with bad attributes if attribute-missing is drop-line' do input = <<-EOS :attribute-missing: drop-line This is blah blah {foobarbaz} all there is. EOS output = convert_string_to_embedded input para = xmlnodes_at_css 'p', output, 1 refute_includes 'blah blah', para.content assert_message @logger, :WARN, 'dropping line containing reference to missing attribute: foobarbaz' end test "attribute value gets interpretted when converting" do doc = document_from_string(":google: http://google.com[Google]\n\n{google}") assert_equal 'http://google.com[Google]', doc.attributes['google'] output = doc.convert assert_xpath '//a[@href="http://google.com"][text() = "Google"]', output, 1 end test 'should drop line with reference to missing attribute if attribute-missing attribute is drop-line' do input = <<-EOS :attribute-missing: drop-line Line 1: This line should appear in the output. Line 2: Oh no, a {bogus-attribute}! This line should not appear in the output. EOS output = convert_string_to_embedded input assert_match(/Line 1/, output) refute_match(/Line 2/, output) assert_message @logger, :WARN, 'dropping line containing reference to missing attribute: bogus-attribute' end test 'should not drop line with reference to missing attribute by default' do input = <<-EOS Line 1: This line should appear in the output. Line 2: A {bogus-attribute}! This time, this line should appear in the output. EOS output = convert_string_to_embedded input assert_match(/Line 1/, output) assert_match(/Line 2/, output) assert_match(/\{bogus-attribute\}/, output) end test 'should drop line with attribute unassignment by default' do input = <<-EOS :a: Line 1: This line should appear in the output. Line 2: {set:a!}This line should not appear in the output. EOS output = convert_string_to_embedded input assert_match(/Line 1/, output) refute_match(/Line 2/, output) end test 'should not drop line with attribute unassignment if attribute-undefined is drop' do input = <<-EOS :attribute-undefined: drop :a: Line 1: This line should appear in the output. Line 2: {set:a!}This line should appear in the output. EOS output = convert_string_to_embedded input assert_match(/Line 1/, output) assert_match(/Line 2/, output) refute_match(/\{set:a!\}/, output) end test 'should drop line that only contains attribute assignment' do input = <<-EOS Line 1 {set:a} Line 2 EOS output = convert_string_to_embedded input assert_xpath %(//p[text()="Line 1\nLine 2"]), output, 1 end test 'should drop line that only contains unresolved attribute when attribute-missing is drop' do input = <<-EOS Line 1 {unresolved} Line 2 EOS output = convert_string_to_embedded input, :attributes => { 'attribute-missing' => 'drop' } assert_xpath %(//p[text()="Line 1\nLine 2"]), output, 1 end test "substitutes inside unordered list items" do html = convert_string(":foo: bar\n* snort at the {foo}\n* yawn") result = Nokogiri::HTML(html) assert_match(/snort at the bar/, result.css("li").first.content.strip) end test 'substitutes inside section title' do output = convert_string(":prefix: Cool\n\n== {prefix} Title\n\ncontent") result = Nokogiri::HTML(output) assert_match(/Cool Title/, result.css('h2').first.content) assert_match(/_cool_title/, result.css('h2').first.attr('id')) end test 'interpolates attribute defined in header inside attribute entry in header' do input = <<-EOS = Title Author Name :attribute-a: value :attribute-b: {attribute-a} preamble EOS doc = document_from_string(input, :parse_header_only => true) assert_equal 'value', doc.attributes['attribute-b'] end test 'interpolates author attribute inside attribute entry in header' do input = <<-EOS = Title Author Name :name: {author} preamble EOS doc = document_from_string(input, :parse_header_only => true) assert_equal 'Author Name', doc.attributes['name'] end test 'interpolates revinfo attribute inside attribute entry in header' do input = <<-EOS = Title Author Name 2013-01-01 :date: {revdate} preamble EOS doc = document_from_string(input, :parse_header_only => true) assert_equal '2013-01-01', doc.attributes['date'] end test 'attribute entries can resolve previously defined attributes' do input = <<-EOS = Title Author Name v1.0, 2010-01-01: First release! :a: value :a2: {a} :revdate2: {revdate} {a} == {a2} {revdate} == {revdate2} EOS doc = document_from_string input assert_equal '2010-01-01', doc.attr('revdate') assert_equal '2010-01-01', doc.attr('revdate2') assert_equal 'value', doc.attr('a') assert_equal 'value', doc.attr('a2') output = doc.convert assert_includes output, 'value == value' assert_includes output, '2010-01-01 == 2010-01-01' end test 'should warn if unterminated block comment is detected in document header' do input = <<-EOS = Document Title :foo: bar //// :hey: there content EOS doc = document_from_string input assert_nil doc.attr('hey') assert_message @logger, :WARN, '