# frozen_string_literal: true require_relative 'test_helper' context 'Syntax Highlighter' do test 'should set syntax_highlighter property on document if source highlighter is set and basebackend is html' do input = <<~'EOS' :source-highlighter: coderay [source, ruby] ---- puts 'Hello, World!' ---- EOS doc = document_from_string input, safe: :safe, parse: true assert doc.basebackend? 'html' refute_nil doc.syntax_highlighter assert_kind_of Asciidoctor::SyntaxHighlighter, doc.syntax_highlighter end test 'should not set syntax_highlighter property on document if source highlighter is set and basebackend is not html' do input = <<~'EOS' :source-highlighter: coderay [source, ruby] ---- puts 'Hello, World!' ---- EOS doc = document_from_string input, safe: :safe, backend: 'docbook', parse: true refute doc.basebackend? 'html' assert_nil doc.syntax_highlighter end test 'should not set syntax_highlighter property on document if source highlighter is not set' do input = <<~'EOS' [source, ruby] ---- puts 'Hello, World!' ---- EOS doc = document_from_string input, safe: :safe, parse: true assert_nil doc.syntax_highlighter end test 'should not set syntax_highlighter property on document if syntax highlighter cannot be resolved' do input = <<~'EOS' :source-highlighter: unknown [source, ruby] ---- puts 'Hello, World!' ---- EOS doc = document_from_string input, safe: :safe, parse: true assert_nil doc.syntax_highlighter end test 'should not allow document to enable syntax highlighter if safe mode is at least SERVER' do input = ':source-highlighter: coderay' doc = document_from_string input, safe: Asciidoctor::SafeMode::SERVER, parse: true assert_nil doc.attributes['source-highlighter'] assert_nil doc.syntax_highlighter end test 'should not invoke highlight method on syntax highlighter if highlight? is false' do Class.new Asciidoctor::SyntaxHighlighter::Base do register_for 'unavailable' def format node, language, opts %(
#{node.content})
end
def highlight?
false
end
end
input = <<~'EOS'
[source,ruby]
----
puts 'Hello, World!'
----
EOS
doc = document_from_string input, attributes: { 'source-highlighter' => 'unavailable' }
output = doc.convert
assert_css 'pre.highlight > code.language-ruby', output, 1
source_block = (doc.find_by {|candidate| candidate.style == 'source' })[0]
assert_raises NotImplementedError do
doc.syntax_highlighter.highlight source_block, source_block.source, (source_block.attr 'language'), {}
end
end
test 'should be able to register syntax highlighter from syntax highlighter class itself' do
syntax_highlighter = Class.new Asciidoctor::SyntaxHighlighter::Base do
def format node, language, opts
%(#{node.content})
end
def highlight?
false
end
end
syntax_highlighter.register_for 'foobar'
assert_equal syntax_highlighter, (Asciidoctor::SyntaxHighlighter.for 'foobar')
end
test 'should be able to register syntax highlighter using symbol' do
syntax_highlighter = Class.new Asciidoctor::SyntaxHighlighter::Base do
register_for :foobaz
def format node, language, opts
%(#{node.content})
end
def highlight?
false
end
end
assert_equal syntax_highlighter, (Asciidoctor::SyntaxHighlighter.for 'foobaz')
end
test 'should set language on output of source block when source-highlighter attribute is not set' do
input = <<~'EOS'
[source, ruby]
----
puts 'Hello, World!'
----
EOS
output = convert_string input, safe: Asciidoctor::SafeMode::SAFE
assert_css 'pre.highlight', output, 1
assert_css 'pre.highlight > code.language-ruby', output, 1
assert_css 'pre.highlight > code.language-ruby[data-lang="ruby"]', output, 1
end
test 'should set language on output of source block when source-highlighter attribute is not recognized' do
input = <<~'EOS'
:source-highlighter: unknown
[source, ruby]
----
puts 'Hello, World!'
----
EOS
output = convert_string input, safe: Asciidoctor::SafeMode::SAFE
assert_css 'pre.highlight', output, 1
assert_css 'pre.highlight > code.language-ruby', output, 1
assert_css 'pre.highlight > code.language-ruby[data-lang="ruby"]', output, 1
end
test 'should highlight source if source highlighter is set even if language is not set' do
input = <<~'EOS'
:source-highlighter: coderay
[source]
----
[numbers]
one
two
three
----
EOS
output = convert_string input, safe: :safe
assert_css 'pre.CodeRay.highlight', output, 1
assert_includes output, ''
end
test 'should not crash if source block has no lines and source highlighter is set' do
input = <<~'EOS'
:source-highlighter: coderay
[source,text]
----
----
EOS
output = convert_string_to_embedded input, safe: :safe
assert_css 'pre.CodeRay', output, 1
assert_css 'pre.CodeRay > code', output, 1
assert_css 'pre.CodeRay > code:empty', output, 1
end
test 'should highlight source inside AsciiDoc table cell if source-highlighter attribute is set' do
input = <<~'EOS'
:source-highlighter: coderay
|===
a|
[source, ruby]
----
require 'coderay'
html = CodeRay.scan("puts 'Hello, world!'", :ruby).div line_numbers: :table
----
|===
EOS
output = convert_string_to_embedded input, safe: :safe
assert_xpath '/table//pre[@class="CodeRay highlight"]/code[@data-lang="ruby"]//span[@class = "constant"][text() = "CodeRay"]', output, 1
end
test 'should set starting line number in DocBook output if linenums option is enabled and start attribute is set' do
input = <<~'EOS'
[source%linenums,java,start=3]
----
public class HelloWorld {
public static void main(String[] args) {
out.println("Hello, World!");
}
}
----
EOS
output = convert_string_to_embedded input, backend: :docbook, safe: Asciidoctor::SafeMode::SAFE
assert_css 'programlisting[startinglinenumber]', output, 1
assert_css 'programlisting[startinglinenumber="3"]', output, 1
end
test 'should read source language from source-language document attribute if not specified on source block' do
input = <<~'EOS'
:source-highlighter: coderay
:source-language: ruby
[source]
----
require 'coderay'
html = CodeRay.scan("puts 'Hello, world!'", :ruby).div line_numbers: :table
----
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE, linkcss_default: true
assert_xpath '//pre[@class="CodeRay highlight"]/code[@data-lang="ruby"]//span[@class = "constant"][text() = "CodeRay"]', output, 1
end
test 'should rename document attribute named language to source-language when compat-mode is enabled' do
input = <<~'EOS'
:language: ruby
{source-language}
EOS
assert_equal 'ruby', (convert_inline_string input, attributes: { 'compat-mode' => '' })
input = <<~'EOS'
:language: ruby
{source-language}
EOS
assert_equal '{source-language}', (convert_inline_string input)
end
context 'CodeRay' do
test 'should highlight source if source-highlighter attribute is set' do
input = <<~'EOS'
:source-highlighter: coderay
[source, ruby]
----
require 'coderay'
html = CodeRay.scan("puts 'Hello, world!'", :ruby).div line_numbers: :table
----
EOS
output = convert_string input, safe: Asciidoctor::SafeMode::SAFE, linkcss_default: true
assert_xpath '//pre[@class="CodeRay highlight"]/code[@data-lang="ruby"]//span[@class = "constant"][text() = "CodeRay"]', output, 1
assert_match(/\.CodeRay *\{/, output)
style_node = xmlnodes_at_xpath '//style[contains(text(), ".CodeRay")]', output, 1
refute_nil style_node
assert_equal 'head', style_node.parent.name
end
test 'should not fail if source language is invalid' do
input = <<~'EOS'
:source-highlighter: coderay
[source, n/a]
----
PRINT 'yo'
----
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
assert_css 'code[data-lang="n/a"]', output, 1
end
test 'should number lines if third positional attribute is set' do
input = <<~'EOS'
:source-highlighter: coderay
[source,ruby,linenums]
----
puts 'Hello, World!'
----
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
assert_xpath '//td[@class="line-numbers"]', output, 1
end
test 'should number lines if linenums option is set on source block' do
input = <<~'EOS'
:source-highlighter: coderay
[source%linenums,ruby]
----
puts 'Hello, World!'
----
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
assert_xpath '//td[@class="line-numbers"]', output, 1
end
test 'should number lines of source block if source-linenums-option document attribute is set' do
input = <<~'EOS'
:source-highlighter: coderay
:source-linenums-option:
[source,ruby]
----
puts 'Hello, World!'
----
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
assert_xpath '//td[@class="line-numbers"]', output, 1
end
test 'should set starting line number in HTML output if linenums option is enabled and start attribute is set' do
input = <<~'EOS'
:source-highlighter: coderay
:coderay-linenums-mode: inline
[source%linenums,ruby,start=10]
----
puts 'Hello, World!'
----
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
assert_xpath '//span[@class="line-numbers"]', output, 1
assert_xpath '//span[@class="line-numbers"][text()="10"]', output, 1
end
test 'should highlight lines specified in highlight attribute if linenums is set and source-highlighter is coderay' do
%w(highlight="1,4-6" highlight="4-6,1" highlight="5-6,1,4,5" highlight=1;4..6 highlight=1;4..;!7).each do |highlight_attr|
input = <<~EOS
:source-highlighter: coderay
[source%linenums,java,#{highlight_attr}]
----
import static java.lang.System.out;
public class HelloWorld {
public static void main(String[] args) {
out.println("Hello, World!");
}
}
----
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
assert_css 'strong.highlighted', output, 4
assert_xpath '//strong[@class="highlighted"][text()="1"]', output, 1
assert_xpath '//strong[@class="highlighted"][text()="2"]', output, 0
assert_xpath '//strong[@class="highlighted"][text()="3"]', output, 0
assert_xpath '//strong[@class="highlighted"][text()="4"]', output, 1
assert_xpath '//strong[@class="highlighted"][text()="5"]', output, 1
assert_xpath '//strong[@class="highlighted"][text()="6"]', output, 1
assert_xpath '//strong[@class="highlighted"][text()="7"]', output, 0
end
end
test 'should replace callout marks but not highlight them if source-highlighter attribute is coderay' do
input = <<~'EOS'
:source-highlighter: coderay
[source, ruby]
----
require 'coderay' # <1>
html = CodeRay.scan("puts 'Hello, world!'", :ruby).div line_numbers: :table # <2>
puts html # <3> <4>
exit 0 # <5><6>
----
<1> Load library
<2> Highlight source
<3> Print to stdout
<4> Redirect to a file to capture output
<5> Exit program
<6> Reports success
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
assert_match(/coderay<\/span>.* # \(1\)<\/b>$/, output)
assert_match(/puts 'Hello, world!'<\/span>.* # \(2\)<\/b>$/, output)
assert_match(/puts html.* # \(3\)<\/b> \(4\)<\/b>$/, output)
assert_match(/exit.* # \(5\)<\/b> \(6\)<\/b><\/code>/, output)
end
test 'should support autonumbered callout marks if source-highlighter attribute is coderay' do
input = <<~'EOS'
:source-highlighter: coderay
[source, ruby]
----
require 'coderay' # <.><.>
html = CodeRay.scan("puts 'Hello, world!'", :ruby).div line_numbers: :table # <.>
puts html # <.>
----
<.> Load library
<.> Gem must be installed
<.> Highlight source
<.> Print to stdout
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
assert_match(/coderay<\/span>.* # \(1\)<\/b> \(2\)<\/b>$/, output)
assert_match(/puts 'Hello, world!'<\/span>.* # \(3\)<\/b>$/, output)
assert_match(/puts html.* # \(4\)<\/b><\/code>/, output)
assert_css '.colist ol', output, 1
assert_css '.colist ol li', output, 4
end
test 'should restore callout marks to correct lines if source highlighter is coderay and table line numbering is enabled' do
input = <<~'EOS'
:source-highlighter: coderay
:coderay-linenums-mode: table
[source, ruby, numbered]
----
require 'coderay' # <1>
html = CodeRay.scan("puts 'Hello, world!'", :ruby).div line_numbers: :table # <2>
puts html # <3> <4>
exit 0 # <5><6>
----
<1> Load library
<2> Highlight source
<3> Print to stdout
<4> Redirect to a file to capture output
<5> Exit program
<6> Reports success
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
assert_match(/coderay<\/span>.* # \(1\)<\/b>$/, output)
assert_match(/puts 'Hello, world!'<\/span>.* # \(2\)<\/b>$/, output)
assert_match(/puts html.* # \(3\)<\/b> \(4\)<\/b>$/, output)
# NOTE notice there's a newline before the closing tag
assert_match(/exit.* # \(5\)<\/b> \(6\)<\/b>\n<\/pre>/, output)
end
test 'should restore isolated callout mark on last line of source when source highlighter is coderay' do
input = <<~'EOS'
:source-highlighter: coderay
[source,ruby,linenums]
----
require 'app'
launch_app
# <1>
----
<1> Profit.
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
# NOTE notice there's a newline before the closing tag
assert_match(/\n# \(1\)<\/b>\n<\/pre>/, output)
end
test 'should preserve space before callout on final line' do
inputs = []
inputs << <<~'EOS'
[source,yaml]
----
a: 'a'
key: 'value' #<1>
----
<1> key-value pair
EOS
inputs << <<~'EOS'
[source,ruby]
----
puts 'hi'
puts 'value' #<1>
----
<1> print to stdout
EOS
inputs << <<~'EOS'
[source,python]
----
print 'hi'
print 'value' #<1>
----
<1> print to stdout
EOS
inputs.each do |input|
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE, attributes: { 'source-highlighter' => 'coderay' }
output = output.gsub(/<\/?span.*?>/, '')
assert_includes output, '\'value\' #(1)'
end
end
test 'should preserve passthrough placeholders when highlighting source using coderay' do
input = <<~'EOS'
:source-highlighter: coderay
[source,java]
[subs="specialcharacters,macros,callouts"]
----
public class Printer {
public static void main(String[] args) {
System.pass:quotes[_out_].println("*asterisks* make text pass:quotes[*bold*]");
}
}
----
EOS
output = convert_string input, safe: Asciidoctor::SafeMode::SAFE
assert_match(/\.out<\/em>\./, output, 1)
assert_match(/\*asterisks\*/, output, 1)
assert_match(/bold<\/strong>/, output, 1)
refute_includes output, Asciidoctor::Substitutors::PASS_START
end
test 'should link to CodeRay stylesheet if source-highlighter is coderay and linkcss is set' do
input = <<~'EOS'
:source-highlighter: coderay
[source, ruby]
----
require 'coderay'
html = CodeRay.scan("puts 'Hello, world!'", :ruby).div line_numbers: :table
----
EOS
output = convert_string input, safe: Asciidoctor::SafeMode::SAFE, attributes: { 'linkcss' => '' }
assert_xpath '//pre[@class="CodeRay highlight"]/code[@data-lang="ruby"]//span[@class = "constant"][text() = "CodeRay"]', output, 1
link_node = xmlnodes_at_xpath '//link[@rel="stylesheet"][@href="./coderay-asciidoctor.css"]', output, 1
refute_nil link_node
assert_equal 'head', link_node.parent.name
end
test 'should highlight source inline if source-highlighter attribute is coderay and coderay-css is style' do
input = <<~'EOS'
:source-highlighter: coderay
:coderay-css: style
[source, ruby]
----
require 'coderay'
html = CodeRay.scan("puts 'Hello, world!'", :ruby).div line_numbers: :table
----
EOS
output = convert_string input, safe: Asciidoctor::SafeMode::SAFE, linkcss_default: true
assert_xpath '//pre[@class="CodeRay highlight"]/code[@data-lang="ruby"]//span[@style = "color:#036;font-weight:bold"][text() = "CodeRay"]', output, 1
refute_match(/\.CodeRay \{/, output)
end
test 'should read stylesheet' do
css = (Asciidoctor::SyntaxHighlighter.for 'coderay').read_stylesheet
refute_nil css
assert_includes css, 'pre.CodeRay{background:#f7f7f8}'
end
end
context 'Highlight.js' do
test 'should add data-lang as last attribute on code tag when source-highlighter is highlight.js' do
input = <<~'EOS'
:source-highlighter: highlight.js
[source,ruby]
----
puts 'Hello, World!'
----
EOS
output = convert_string_to_embedded input, safe: :safe
assert_includes output, ''
end
test 'should include remote highlight.js assets if source-highlighter attribute is highlight.js' do
input = <<~'EOS'
:source-highlighter: highlight.js
[source,html]
----
Highlight me!
----
EOS
output = convert_string input, safe: Asciidoctor::SafeMode::SAFE
assert_css 'pre.highlightjs.highlight', output, 1
assert_css 'pre.highlightjs.highlight > code.language-html.hljs[data-lang="html"]', output, 1
assert_includes output, '<p>Highlight me!</p>'
assert_css 'head > link[href*="highlight.js"]', output, 1
assert_css '#footer ~ script[src*="highlight.min.js"]', output, 1
assert_xpath '//script[contains(text(), "hljs.highlightBlock(el)")]', output, 1
end
test 'should add language-none class to source block when source-highlighter is highlight.js and language is not set' do
input = <<~'EOS'
:source-highlighter: highlight.js
[source]
----
[numbers]
one
two
three
----
EOS
output = convert_string input, safe: :safe
assert_css 'code.language-none', output, 1
end
test 'should load additional languages specified by highlightjs-languages' do
input = <<~'EOS'
:source-highlighter: highlight.js
:highlightjs-languages: yaml, scilab
[source,yaml]
----
key: value
----
EOS
output = convert_string input, safe: Asciidoctor::SafeMode::SAFE
assert_css '#footer ~ script[src*="languages/yaml.min.js"]', output, 1
assert_css '#footer ~ script[src*="languages/scilab.min.js"]', output, 1
end
end
context 'Prettify' do
test 'should add language classes to child code element when source-highlighter is prettify' do
input = <<~'EOS'
[source,ruby]
----
puts "foo"
----
EOS
output = convert_string_to_embedded input, attributes: { 'source-highlighter' => 'prettify' }
assert_css 'pre[class="prettyprint highlight"]', output, 1
assert_css 'pre > code[data-lang="ruby"]', output, 1
end
test 'should set linenums start if linenums are enabled and start attribute is set when source-highlighter is prettify' do
input = <<~'EOS'
[source%linenums,ruby,start=5]
----
puts "foo"
----
EOS
output = convert_string_to_embedded input, attributes: { 'source-highlighter' => 'prettify' }
assert_css 'pre[class="prettyprint highlight linenums:5"]', output, 1
assert_css 'pre > code[data-lang="ruby"]', output, 1
end
end
context 'HTML Pipeline' do
test 'should set lang attribute on pre when source-highlighter is html-pipeline' do
input = <<~'EOS'
[source,ruby]
----
filters = [
HTML::Pipeline::AsciiDocFilter,
HTML::Pipeline::SanitizationFilter,
HTML::Pipeline::SyntaxHighlightFilter
]
puts HTML::Pipeline.new(filters, {}).call(input)[:output]
----
EOS
output = convert_string input, attributes: { 'source-highlighter' => 'html-pipeline' }
assert_css 'pre[lang="ruby"]', output, 1
assert_css 'pre[lang="ruby"] > code', output, 1
assert_css 'pre[class]', output, 0
assert_css 'code[class]', output, 0
end
end
context 'Rouge' do
test 'should syntax highlight source if source-highlighter attribute is set' do
input = <<~'EOS'
:source-highlighter: rouge
[source,ruby]
----
require 'rouge'
html = Rouge::Formatters::HTML.format(Rouge::Lexers::Ruby.lex('puts "Hello, world!"'))
----
EOS
output = convert_string input, safe: :safe, linkcss_default: true
assert_xpath '//pre[@class="rouge highlight"]/code[@data-lang="ruby"]/span[@class="no"][text()="Rouge"]', output, 2
assert_includes output, 'pre.rouge .no {'
style_node = xmlnodes_at_xpath '//style[contains(text(), "pre.rouge")]', output, 1
refute_nil style_node
assert_equal 'head', style_node.parent.name
end
test 'should highlight source using a mixed lexer (HTML + JavaScript)' do
input = <<~'EOS'
[,html]
----
----
EOS
output = convert_string_to_embedded input, safe: :safe, attributes: { 'source-highlighter' => 'rouge' }
assert_css 'pre.rouge > code[data-lang="html"]', output, 1
end
test 'should enable start_inline for PHP by default' do
input = <<~'EOS'
[,php]
----
echo " 'rouge' }
assert_css 'pre.rouge > code[data-lang="php"]', output, 1
assert_include 'echo', output
end
test 'should not enable start_inline for PHP if disabled using cgi-style option on language' do
input = <<~'EOS'
[,php?start_inline=0]
----
echo " 'rouge' }
assert_css 'pre.rouge > code[data-lang="php"]', output, 1
refute_include 'echo', output
assert_include '<?php', output
end
test 'should not enable start_inline for PHP if mixed option is set' do
input = <<~'EOS'
[%mixed,php]
----
echo " 'rouge' }
assert_css 'pre.rouge > code[data-lang="php"]', output, 1
refute_include 'echo', output
assert_include '<?php', output
end
test 'should preserve cgi-style options on language when setting start_inline option for PHP', if: (Rouge.version >= '2.1.0') do
input = <<~'EOS'
[,php?funcnamehighlighting=0]
----
cal_days_in_month(CAL_GREGORIAN, 6, 2019)
----
EOS
output = convert_string_to_embedded input, safe: :safe, attributes: { 'source-highlighter' => 'rouge' }
assert_css 'pre.rouge > code[data-lang="php"]', output, 1
# if class is "nb", then the funcnamehighlighting option is not honored
refute_include 'cal_days_in_month', output
assert_include '2019', output
end
test 'should not crash if source-highlighter attribute is set and source block does not define a language' do
input = <<~'EOS'
:source-highlighter: rouge
[source]
----
require 'rouge'
html = Rouge::Formatters::HTML.format(Rouge::Lexers::Ruby.lex('puts "Hello, world!"'))
----
EOS
output = convert_string_to_embedded input, safe: :safe
assert_css 'pre > code:not([data-lang])', output, 1
end
test 'should default to plain text lexer if lexer cannot be resolved for language' do
input = <<~'EOS'
:source-highlighter: rouge
[source,lolcode]
----
CAN HAS STDIO?
PLZ OPEN FILE "LOLCATS.TXT"?
KTHXBYE
----
EOS
output = convert_string_to_embedded input, safe: :safe
assert_css 'code[data-lang=lolcode]', output, 1
assert_css 'code span', output, 0
assert_xpath %(//code[text()='CAN HAS STDIO?\nPLZ OPEN FILE "LOLCATS.TXT"?\nKTHXBYE']), output, 1
end
test 'should honor cgi-style options on language', if: (Rouge.version >= '2.1.0') do
input = <<~'EOS'
:source-highlighter: rouge
[source,console?prompt=$>]
----
$> asciidoctor --version
----
EOS
output = convert_string_to_embedded input, safe: :safe
assert_css 'code[data-lang=console]', output, 1
assert_css 'code span.gp', output, 1
end
test 'should number lines using table layout if linenums option is enabled and linenums mode is not set' do
input = <<~'EOS'
[source%linenums,ruby]
----
puts 'Hello, world!'
puts 'Goodbye, world!'
----
EOS
output = convert_string_to_embedded input, attributes: { 'source-highlighter' => 'rouge' }
assert_css 'table.linenotable', output, 1
assert_css 'table.linenotable td.linenos', output, 2
assert_css 'table.linenotable td.linenos pre', output, 2
assert_xpath '(//td[@class="linenos"])[1]/pre[text()="1"]', output, 1
assert_xpath '(//td[@class="linenos"])[2]/pre[text()="2"]', output, 1
assert_css 'table.linenotable td.code', output, 2
assert_css 'table.linenotable td.code pre:not([class])', output, 2
assert_includes output, %(puts 'Hello, world!'\n
)
assert_includes output, %(puts 'Goodbye, world!'\n
)
end
test 'should number lines using inline element if linenums option is enabled and linenums mode is inline' do
input = <<~'EOS'
:rouge-linenums-mode: inline
[source%linenums,ruby]
----
puts 'Hello, world!'
puts 'Goodbye, world!'
----
EOS
expected = <<~EOS.chop
1puts 'Hello, world!'
2puts 'Goodbye, world!'
EOS
output = convert_string_to_embedded input, attributes: { 'source-highlighter' => 'rouge' }
assert_css 'table.linenotable', output, 0
assert_includes output, expected
end
test 'should set starting line number in HTML output if linenums option is enabled and start attribute is set' do
input = <<~'EOS'
[source%linenums,ruby,start=9]
----
puts 'Hello, World!'
puts 'Goodbye, World!'
----
EOS
output = convert_string_to_embedded input, attributes: { 'source-highlighter' => 'rouge' }
assert_css 'table.linenotable', output, 1
assert_css 'table.linenotable td.linenos', output, 2
assert_css 'table.linenotable td.linenos pre', output, 2
assert_css 'table.linenotable td.code', output, 2
assert_css 'table.linenotable td.code pre:not([class])', output, 2
assert_xpath '(//td[@class="linenos"])[1]/pre[text()="9"]', output, 1
assert_xpath '(//td[@class="linenos"])[2]/pre[text()="10"]', output, 1
end
test 'should restore callout marks to correct lines' do
['', '%linenums'].each do |opts|
input = <<~EOS
:source-highlighter: rouge
:rouge-linenums-mode: inline
[source#{opts},ruby]
----
require 'rouge' # <1>
html = Rouge::Formatters::HTML.format(Rouge::Lexers::Ruby.lex('puts "Hello, world!"')) # <2>
puts html # <3> <4>
exit 0 # <5><6>
----
<1> Load library
<2> Highlight source
<3> Print to stdout
<4> Redirect to a file to capture output
<5> Exit program
<6> Reports success
EOS
output = convert_string_to_embedded input, safe: :safe
assert_match(/'rouge'<\/span>.* # \(1\)<\/b>$/, output)
assert_match(/'puts "Hello, world!"'<\/span>.* # \(2\)<\/b>$/, output)
assert_match(/html<\/span>.* # \(3\)<\/b> \(4\)<\/b>$/, output)
assert_match(/0<\/span>.* # \(5\)<\/b> \(6\)<\/b><\/code><\/pre>/, output)
end
end
test 'should line highlight specified lines when last line is not highlighted' do
['', '%linenums'].each do |opts|
input = <<~EOS
:source-highlighter: rouge
:rouge-linenums-mode: inline
[source#{opts},ruby,highlight=1]
----
puts 'Hello, world!'
puts 'Goodbye, world!'
----
EOS
expected = <<~EOS.chop
puts 'Hello, world!'
#{opts == '%linenums' ? '2' : ''}puts 'Goodbye, world!'
EOS
output = convert_string_to_embedded input, safe: :safe
assert_includes output, expected
end
end
test 'should line highlight specified lines when last line is highlighted' do
['', '%linenums'].each do |opts|
input = <<~EOS
:source-highlighter: rouge
:rouge-linenums-mode: inline
[source#{opts},ruby,highlight=2]
----
puts 'Hello, world!'
puts 'Goodbye, world!'
----
EOS
expected = <<~EOS.chop
puts 'Hello, world!'
#{opts == '%linenums' ? '2' : ''}puts 'Goodbye, world!'
EOS
output = convert_string_to_embedded input, safe: :safe
assert_includes output, expected
end
end
test 'should line highlight specified lines relative to start value when linenums mode is table' do
input = <<~EOS
:source-highlighter: rouge
[source%linenums,ruby,start=5,highlight=6]
----
get {
render "Hello, World!"
}
----
EOS
output = convert_string_to_embedded input, safe: :safe
assert_css 'table.linenotable td.linenos', output, 3
assert_css 'table.linenotable td.linenos pre', output, 3
assert_xpath '(//td[@class="linenos"])[1]/pre[text()="5"]', output, 1
assert_xpath '(//td[@class="linenos"])[2]/pre[text()="6"]', output, 1
assert_xpath '(//td[@class="linenos"])[3]/pre[text()="7"]', output, 1
end
test 'should line highlight specified lines relative to start value when linenums mode is inline' do
input = <<~EOS
:source-highlighter: rouge
:rouge-linenums-mode: inline
[source%linenums,ruby,start=5,highlight=6]
----
get {
render "Hello, World!"
}
----
EOS
expected = <<~EOS.chop
5get {
6 render "Hello, World!"
7}
EOS
output = convert_string_to_embedded input, safe: :safe
assert_includes output, expected
end
test 'should ignore start attribute when the value is 0' do
input = <<~EOS
:source-highlighter: rouge
:rouge-linenums-mode: inline
[source%linenums,ruby,start=0,highlight=6]
----
get {
render "Hello, World!"
}
----
EOS
expected = <<~EOS.chop
1get {
2 render "Hello, World!"
3}
EOS
output = convert_string_to_embedded input, safe: :safe
assert_includes output, expected
end
test 'should not line highlight when the start attribute is greater than highlight' do
input = <<~EOS
:source-highlighter: rouge
:rouge-linenums-mode: inline
[source%linenums,ruby,start=7,highlight=6]
----
get {
render "Hello, World!"
}
----
EOS
expected = <<~EOS.chop
7get {
8 render "Hello, World!"
9}
EOS
output = convert_string_to_embedded input, safe: :safe
assert_includes output, expected
end
test 'should restore callout marks to correct lines if line numbering and line highlighting are enabled' do
[1, 2].each do |highlight|
input = <<~EOS
:source-highlighter: rouge
[source%linenums,ruby,highlight=#{highlight}]
----
require 'rouge' # <1>
exit 0 # <2>
----
<1> Load library
<2> Exit program
EOS
output = convert_string_to_embedded input, safe: :safe
assert_match(/'rouge'<\/span>.* # \(1\)<\/b>$/, output)
# NOTE notice there's a newline before the closing tag
assert_match(/0<\/span>.* # \(2\)<\/b>\n#{highlight == 2 ? '' : ''}<\/pre>/, output)
end
end
test 'should gracefully fallback to default style if specified style not recognized' do
input = <<~'EOS'
:source-highlighter: rouge
:rouge-style: unknown
[source,ruby]
----
puts 'Hello, world!'
----
EOS
output = convert_string input, safe: :safe, linkcss_default: true
assert_css 'pre.rouge', output, 1
assert_includes output, 'pre.rouge .no {'
assert_match %r/pre\.rouge \{\s*background-color: #f8f8f8;/m, output
end
test 'should restore isolated callout mark on last line of source' do
input = <<~'EOS'
:source-highlighter: rouge
[source%linenums,ruby]
----
require 'app'
launch_app
# <1>
----
<1> Profit.
EOS
output = convert_string_to_embedded input, safe: :safe
# NOTE notice there's a newline before the closing tag, but not before the closing tag
assert_match(/# \(1\)<\/b>\n<\/pre><\/td>/, output)
end
test 'should number all lines when isolated callout mark is on last line of source and starting line number is set' do
input = <<~'EOS'
:source-highlighter: rouge
[source%linenums,ruby,start=5]
----
require 'app'
launch_app
# <1>
----
<1> Profit.
EOS
output = convert_string_to_embedded input, safe: :safe
assert_xpath '//td[@class="linenos"]', output, 4
assert_xpath '(//td[@class="linenos"])[1]/pre[text()="5"]', output, 1
assert_xpath '(//td[@class="linenos"])[2]/pre[text()="6"]', output, 1
assert_xpath '(//td[@class="linenos"])[3]/pre[text()="7"]', output, 1
assert_xpath '(//td[@class="linenos"])[4]/pre[text()="8"]', output, 1
# NOTE notice there's a newline before the closing tag, but not before the closing tag
assert_match(/# \(1\)<\/b>\n<\/pre><\/td>/, output)
end
test 'should preserve guard in front of callout if icons are not enabled' do
input = <<~'EOS'
[,ruby]
----
puts 'Hello, World!' # <1>
puts 'Goodbye, World ;(' # <2>
----
EOS
result = convert_string_to_embedded input
assert_include ' # (1)', result
assert_include ' # (2)', result
end
test 'should preserve guard around callout if icons are not enabled' do
input = <<~'EOS'
----
----
EOS
result = convert_string_to_embedded input
assert_include ' <!--(1)-->', result
assert_include ' <!--(2)-->', result
end
test 'should read stylesheet for specified style' do
css = (Asciidoctor::SyntaxHighlighter.for 'rouge').read_stylesheet 'monokai'
refute_nil css
assert_includes css, 'pre.rouge {'
assert_includes css, 'background-color: #49483e;'
end
test 'should not fail to load rouge if the Asciidoctor module is included into the global namespace', unless: jruby_9_1_windows? do
result = run_command(asciidoctor_cmd, '-r', (fixture_path 'include-asciidoctor.rb'), '-s', '-o', '-', '-a', 'source-highlighter=rouge', (fixture_path 'source-block.adoc'), use_bundler: true) {|out| out.read }
assert_xpath '//pre[@class="rouge highlight"]', result, 1
end
end
context 'Pygments', if: ENV['PYGMENTS_VERSION'] do
test 'should syntax highlight source if source-highlighter attribute is set' do
input = <<~'EOS'
:source-highlighter: pygments
:pygments-style: monokai
[source,python]
----
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
source = 'print "Hello World"'
print(highlight(source, PythonLexer(), HtmlFormatter()))
----
EOS
output = convert_string input, safe: :safe, linkcss_default: true
assert_xpath '//pre[@class="pygments highlight"]/code[@data-lang="python"]/span[@class="tok-kn"][text()="import"]', output, 3
assert_includes output, 'pre.pygments '
style_node = xmlnodes_at_xpath '//style[contains(text(), "pre.pygments")]', output, 1
refute_nil style_node
assert_equal 'head', style_node.parent.name
end
test 'should embed stylesheet for pygments style' do
input = <<~'EOS'
:source-highlighter: pygments
:pygments-style: monokai
[source,python]
----
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
source = 'print "Hello World"'
print(highlight(source, PythonLexer(), HtmlFormatter()))
----
EOS
output = convert_string input, safe: :safe, linkcss_default: false
assert_css 'style', output, 2
style_nodes = xmlnodes_at_xpath '//style[contains(text(), "pre.pygments")]', output
assert_equal 2, style_nodes.size
pygments_styles = style_nodes[1].to_s
assert_match %r/^pre\.pygments *\{/, pygments_styles
refute_match %r/^pre *\{/, pygments_styles
end
test 'should gracefully fallback to default style if specified style not recognized' do
input = <<~'EOS'
:source-highlighter: pygments
:pygments-style: unknown
[source,python]
----
from pygments import highlight
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
source = 'print "Hello World"'
print(highlight(source, PythonLexer(), HtmlFormatter()))
----
EOS
output = convert_string input, safe: :safe, linkcss_default: true
assert_css 'pre.pygments', output, 1
assert_includes output, 'pre.pygments '
assert_includes output, '.tok-c { color: #408080;'
end
test 'should number lines if linenums option is set on source block' do
input = <<~'EOS'
:source-highlighter: pygments
[source%linenums,ruby]
----
puts 'Hello, World!'
puts 'Goodbye, World!'
----
EOS
output = convert_string_to_embedded input, safe: Asciidoctor::SafeMode::SAFE
assert_css 'table.linenotable', output, 1
assert_css 'table.linenotable td.linenos', output, 1
assert_css 'table.linenotable td.linenos .linenodiv', output, 1
assert_css 'table.linenotable td.linenos .linenodiv pre:not([class])', output, 1
assert_css 'table.linenotable td.code', output, 1
assert_css 'table.linenotable td.code pre:not([class])', output, 1
# NOTE new versions of Pygments wrap the numbers in span
linenos_node = xmlnodes_at_xpath %(//*[@class="linenodiv"]/pre), output, 1
linenos = linenos_node.content.gsub %r(\d+), '\1'
assert_equal %(1\n2), linenos
end
test 'should restore callout marks to correct lines if table line numbering is enabled' do
input = <<~'EOS'
:source-highlighter: pygments
:pygments-linenums-mode: table
[source%linenums,ruby]
----
from pygments import highlight # <1>
from pygments.lexers import PythonLexer
from pygments.formatters import HtmlFormatter
code = 'print "Hello World"'
print(highlight(code, PythonLexer(), HtmlFormatter())) # <2><3>
----
<1> Load library
<2> Highlight source
<3> Print to stdout
EOS
output = convert_string_to_embedded input, safe: :safe
assert_match(/highlight<\/span> # \(1\)<\/b>$/, output)
# NOTE notice there's a newline before the closing tag
assert_match(/\(\)\)\).*<\/span> # \(2\)<\/b> \(3\)<\/b>$/, output)
end
test 'should restore isolated callout mark on last line of source' do
input = <<~'EOS'
:source-highlighter: pygments
[source,ruby,linenums]
----
require 'app'
launch_app
# <1>
----
<1> Profit.
EOS
output = convert_string_to_embedded input, safe: :safe
# NOTE notice there's a newline before the closing tag, but not before the closing tag
assert_match(/\n# \(1\)<\/b>\n<\/pre><\/td>/, output)
end
test 'should not hardcode inline styles on lineno div and pre elements when linenums are enabled in table mode' do
input = <<~'EOS'
:source-highlighter: pygments
:pygments-css: inline
[source%linenums,ruby]
----
puts 'Hello, World!'
----
EOS
output = convert_string_to_embedded input, safe: :safe
assert_css 'td.linenos', output, 1
assert_css 'div.linenodiv:not([style])', output, 1
assert_includes output, ''
assert_css 'pre:not([style])', output, 2
end
test 'should add lineno spans with class and trim trailing space when linenums are enabled and source-highlighter is pygments' do
%w(class style).each do |css_mode|
input = <<~EOS
:source-highlighter: pygments
:pygments-css: #{css_mode}
:pygments-linenums-mode: inline
[source%linenums,ruby]
----
puts 'Hello, World!'
puts 'Hello, World!'
puts 'Hello, World!'
puts 'Hello, World!'
puts 'Hello, World!'
puts 'Hello, World!'
puts 'Hello, World!'
puts 'Hello, World!'
puts 'Hello, World!'
exit 0
----
EOS
output = convert_string_to_embedded input, safe: :safe
assert_css 'table.linenotable', output, 0
assert_css 'pre', output, 1
assert_includes output, ' 1'
assert_includes output, '10'
end
end
test 'should line highlight specified lines' do
input = <<~'EOS'
:source-highlighter: pygments
[source,ruby,highlight=1..2]
----
puts 'Hello, world!'
puts 'Goodbye, world!'
----
EOS
# NOTE notice the newline is inside the closing of the highlight span
expected = <<~'EOS'.chop
puts 'Hello, world!'
puts 'Goodbye, world!'
EOS
output = convert_string_to_embedded input, safe: :safe
assert_includes output, expected
end
end
end