require 'test_helper' context 'Attributes' do 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_equal nil, doc.attributes['foo'] 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 'deletes an attribute' do doc = document_from_string(":frog: Tanglefoot\n:frog!:") assert_equal nil, doc.attributes['frog'] end test "doesn't choke when deleting a non-existing attribute" do doc = document_from_string(':frog!:') assert_equal 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 doc = document_from_string(":release: Asciidoctor {version}") assert_equal '', doc.attributes['release'] end test "assigns multi-line attribute to empty string if substitution fails to resolve attribute" do doc = document_from_string(":release: Asciidoctor +\n {version}") assert_equal '', doc.attributes['release'] 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 "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 = render_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 # Validates requirement: "Header attributes are overridden by command-line attributes." test 'attribute defined in document options overrides attribute in document' do doc = document_from_string(':cash: money', :attributes => {'cash' => 'heroes'}) assert_equal 'heroes', doc.attributes['cash'] end test 'attribute defined in document options cannot be unassigned in document' do doc = document_from_string(':cash!:', :attributes => {'cash' => 'heroes'}) assert_equal 'heroes', doc.attributes['cash'] end test 'attribute undefined in document options cannot be assigned in document' do doc = document_from_string(':cash: money', :attributes => {'cash!' => '' }) assert_equal nil, doc.attributes['cash'] doc = document_from_string(':cash: money', :attributes => {'cash' => nil }) assert_equal nil, doc.attributes['cash'] end test 'backend attributes are updated if backend attribute is defined in document and safe mode is less than SERVER' do doc = document_from_string(':backend: docbook45', :safe => Asciidoctor::SafeMode::SAFE) assert_equal 'docbook45', doc.attributes['backend'] assert doc.attributes.has_key? 'backend-docbook45' assert_equal 'docbook', doc.attributes['basebackend'] assert doc.attributes.has_key? 'basebackend-docbook' 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 end context 'Interpolation' do test "render properly with simple names" do html = render_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 result = render_embedded_string(":He-Man: The most powerful man in the universe\n\n{He-Man}") assert_xpath '//p[text()="The most powerful man in the universe"]', result, 1 end test "render properly with single character name" do html = render_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 "convert multi-word names and render" do input = <<-EOS Main Header =========== :My frog: Tanglefoot Yo, {myfrog}! EOS output = render_string input assert_xpath '(//p)[1][text()="Yo, Tanglefoot!"]', output, 1 end test "ignores lines with bad attributes" do html = render_string("This is\nblah blah {foobarbaz}\nall there is.") result = Nokogiri::HTML(html) assert_no_match(/blah blah/m, result.css("p").first.content.strip) end test "attribute value gets interpretted when rendering" do doc = document_from_string(":google: http://google.com[Google]\n\n{google}") assert_equal 'http://google.com[Google]', doc.attributes['google'] output = doc.render assert_xpath '//a[@href="http://google.com"][text() = "Google"]', output, 1 end # See above - AsciiDoc says we're supposed to delete lines with bad # attribute refs in them. AsciiDoc is strange. # # test "Unknowns" do # html = render_string("Look, a {gobbledygook}") # result = Nokogiri::HTML(html) # assert_equal("Look, a {gobbledygook}", result.css("p").first.content.strip) # end test "substitutes inside unordered list items" do html = render_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 = render_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.render assert output.include?('value == value') assert output.include?('2010-01-01 == 2010-01-01') end test 'substitutes inside block title' do input = <<-EOS :gem_name: asciidoctor .Require the +{gem_name}+ gem To use {gem_name}, the first thing to do is to import it in your Ruby source file. EOS output = render_embedded_string input assert_xpath '//*[@class="title"]/code[text()="asciidoctor"]', output, 1 end test 'renders attribute until it is deleted' do input = <<-EOS :foo: bar Crossing the {foo}. :foo!: Belly up to the {foo}. EOS output = render_embedded_string input assert_xpath '//p[text()="Crossing the bar."]', output, 1 assert_xpath '//p[text()="Belly up to the bar."]', output, 0 end test 'does not disturb attribute-looking things escaped with backslash' do html = render_string(":foo: bar\nThis is a \\{foo} day.") result = Nokogiri::HTML(html) assert_equal 'This is a {foo} day.', result.css('p').first.content.strip end test 'does not disturb attribute-looking things escaped with literals' do html = render_string(":foo: bar\nThis is a +++{foo}+++ day.") result = Nokogiri::HTML(html) assert_equal 'This is a {foo} day.', result.css('p').first.content.strip end test 'does not substitute attributes inside listing blocks' do input = <<-EOS :forecast: snow ---- puts 'The forecast for today is {forecast}' ---- EOS output = render_string(input) assert_match(/\{forecast\}/, output) end test 'does not substitute attributes inside literal blocks' do input = <<-EOS :foo: bar .... You insert the text {foo} to expand the value of the attribute named foo in your document. .... EOS output = render_string(input) assert_match(/\{foo\}/, output) end test 'does not show docdir and shows relative docfile if safe mode is SERVER or greater' do input = <<-EOS * docdir: {docdir} * docfile: {docfile} EOS docdir = Dir.pwd docfile = File.join(docdir, 'sample.asciidoc') output = render_embedded_string input, :safe => Asciidoctor::SafeMode::SERVER, :attributes => {'docdir' => docdir, 'docfile' => docfile} assert_xpath '//li[1]/p[text()="docdir: "]', output, 1 assert_xpath '//li[2]/p[text()="docfile: sample.asciidoc"]', output, 1 end test 'shows absolute docdir and docfile paths if safe mode is less than SERVER' do input = <<-EOS * docdir: {docdir} * docfile: {docfile} EOS docdir = Dir.pwd docfile = File.join(docdir, 'sample.asciidoc') output = render_embedded_string input, :safe => Asciidoctor::SafeMode::SAFE, :attributes => {'docdir' => docdir, 'docfile' => docfile} assert_xpath %(//li[1]/p[text()="docdir: #{docdir}"]), output, 1 assert_xpath %(//li[2]/p[text()="docfile: #{docfile}"]), output, 1 end end context "Intrinsic attributes" do test "substitute intrinsics" do Asciidoctor::INTRINSICS.each_pair do |key, value| html = render_string("Look, a {#{key}} is here") # can't use Nokogiri because it interprets the HTML entities and we can't match them assert_match(/Look, a #{Regexp.escape(value)} is here/, html) end end test "don't escape intrinsic substitutions" do html = render_string('happy{nbsp}together') assert_match(/happy together/, html) end test "escape special characters" do html = render_string('<node>&</node>') assert_match(/<node>&<\/node>/, html) end test 'creates counter' do input = <<-EOS {counter:mycounter} EOS doc = document_from_string input output = doc.render assert_equal 1, doc.attributes['mycounter'] assert_xpath '//p[text()="1"]', output, 1 end test 'creates counter silently' do input = <<-EOS {counter2:mycounter} EOS doc = document_from_string input output = doc.render assert_equal 1, doc.attributes['mycounter'] assert_xpath '//p[text()="1"]', output, 0 end test 'creates counter with numeric seed value' do input = <<-EOS {counter2:mycounter:10} EOS doc = document_from_string input doc.render assert_equal 10, doc.attributes['mycounter'] end test 'creates counter with character seed value' do input = <<-EOS {counter2:mycounter:A} EOS doc = document_from_string input doc.render assert_equal 'A', doc.attributes['mycounter'] end test 'increments counter with numeric value' do input = <<-EOS :mycounter: 1 {counter:mycounter} {mycounter} EOS doc = document_from_string input output = doc.render assert_equal 2, doc.attributes['mycounter'] assert_xpath '//p[text()="2"]', output, 2 end test 'increments counter with character value' do input = <<-EOS :mycounter: @ {counter:mycounter} {mycounter} EOS doc = document_from_string input output = doc.render assert_equal 'A', doc.attributes['mycounter'] assert_xpath '//p[text()="A"]', output, 2 end end context 'Block attributes' do test 'Positional attributes assigned to block' do input = <<-EOS [quote, author, source] ____ A famous quote. ____ EOS doc = document_from_string(input) qb = doc.blocks.first assert_equal 'quote', qb.attributes['style'] assert_equal 'quote', qb.attr(:style) assert_equal 'author', qb.attributes['attribution'] assert_equal 'source', qb.attributes['citetitle'] end test 'Normal substitutions are performed on single-quoted attributes' do input = <<-EOS [quote, author, 'http://wikipedia.org[source]'] ____ A famous quote. ____ EOS doc = document_from_string(input) qb = doc.blocks.first assert_equal 'quote', qb.attributes['style'] assert_equal 'quote', qb.attr(:style) assert_equal 'author', qb.attributes['attribution'] assert_equal '<a href="http://wikipedia.org">source</a>', qb.attributes['citetitle'] end test 'attribute list may begin with space' do input = <<-EOS [ quote] ____ A famous quote. ____ EOS doc = document_from_string input qb = doc.blocks.first assert_equal 'quote', qb.attributes['style'] end test 'attribute list may begin with comma' do input = <<-EOS [, author, source] ____ A famous quote. ____ EOS doc = document_from_string input qb = doc.blocks.first assert_equal 'quote', qb.attributes['style'] assert_equal 'author', qb.attributes['attribution'] assert_equal 'source', qb.attributes['citetitle'] end test 'first attribute in list may be double quoted' do input = <<-EOS ["quote", "author", "source", role="famous"] ____ A famous quote. ____ EOS doc = document_from_string input qb = doc.blocks.first assert_equal 'quote', qb.attributes['style'] assert_equal 'author', qb.attributes['attribution'] assert_equal 'source', qb.attributes['citetitle'] assert_equal 'famous', qb.attributes['role'] end test 'first attribute in list may be single quoted' do input = <<-EOS ['quote', 'author', 'source', role='famous'] ____ A famous quote. ____ EOS doc = document_from_string input qb = doc.blocks.first assert_equal 'quote', qb.attributes['style'] assert_equal 'author', qb.attributes['attribution'] assert_equal 'source', qb.attributes['citetitle'] assert_equal 'famous', qb.attributes['role'] end test "Attribute substitutions are performed on attribute list before parsing attributes" do input = <<-EOS :lead: role="lead" [{lead}] A paragraph EOS doc = document_from_string(input) para = doc.blocks.first assert_equal 'lead', para.attributes['role'] end test "Block attributes are additive" do input = <<-EOS [id='foo'] [role='lead'] A paragraph. EOS doc = document_from_string(input) para = doc.blocks.first assert_equal 'foo', para.id assert_equal 'lead', para.attributes['role'] end test "Last wins for id attribute" do input = <<-EOS [[bar]] [[foo]] == Section paragraph [[baz]] [id='coolio'] === Section EOS doc = document_from_string(input) sec = doc.first_section assert_equal 'foo', sec.id subsec = sec.blocks.last assert_equal 'coolio', subsec.id end test 'block id above document title sets id on document' do input = <<-EOS [[reference]] Reference Manual ================ :css-signature: refguide preamble EOS doc = document_from_string input assert_equal 'reference', doc.id assert_equal 'refguide', doc.attr('css-signature') output = doc.render assert_xpath '//body[@id="reference"]', output, 1 end test "trailing block attributes tranfer to the following section" do input = <<-EOS [[one]] == Section One paragraph [[sub]] // try to mess this up! === Sub-section paragraph [role='classy'] //// block comment //// == Section Two content EOS doc = document_from_string(input) section_one = doc.blocks.first assert_equal 'one', section_one.id subsection = section_one.blocks.last assert_equal 'sub', subsection.id section_two = doc.blocks.last assert_equal 'classy', section_two.attr(:role) end end end