# frozen_string_literal: true require_relative 'test_helper' # TODO # - test negatives # - test role on every quote type context 'Substitutions' do BACKSLASH = ?\\ context 'Dispatcher' do test 'apply normal substitutions' do para = block_from_string("[blue]_http://asciidoc.org[AsciiDoc]_ & [red]*Ruby*\n§ Making +++documentation+++ together +\nsince (C) {inception_year}.") para.document.attributes['inception_year'] = '2012' result = para.apply_subs(para.source) assert_equal %{AsciiDoc & Ruby\n§ Making documentation together
\nsince © 2012.}, result end test 'apply_subs should not modify string directly' do input = ' -- the root of all web' para = block_from_string input para_source = para.source result = para.apply_subs para_source assert_equal '<html> — the root of all web', result assert_equal input, para_source end test 'should not drop trailing blank lines when performing substitutions' do para = block_from_string %([%hardbreaks]\nthis\nis\n-> {program}) para.lines << '' para.lines << '' para.document.attributes['program'] = 'Asciidoctor' result = para.apply_subs(para.lines) assert_equal ['this
', 'is
', '→ Asciidoctor
', '
', ''], result result = para.apply_subs(para.lines * "\n") assert_equal %(this
\nis
\n→ Asciidoctor
\n
\n), result end test 'should expand subs passed to expand_subs' do para = block_from_string %({program}\n*bold*\n2 > 1) para.document.attributes['program'] = 'Asciidoctor' assert_equal [:specialcharacters], (para.expand_subs [:specialchars]) refute para.expand_subs([:none]) assert_equal [:specialcharacters, :quotes, :attributes, :replacements, :macros, :post_replacements], (para.expand_subs [:normal]) end test 'apply_subs should allow the subs argument to be nil' do block = block_from_string %([pass]\n*raw*) result = block.apply_subs block.source, nil assert_equal '*raw*', result end end context 'Quotes' do test 'single-line double-quoted string' do para = block_from_string(%q{``a few quoted words''}, attributes: { 'compat-mode' => '' }) assert_equal '“a few quoted words”', para.sub_quotes(para.source) para = block_from_string(%q{"`a few quoted words`"}) assert_equal '“a few quoted words”', para.sub_quotes(para.source) para = block_from_string(%q{"`a few quoted words`"}, backend: 'docbook') assert_equal 'a few quoted words', para.sub_quotes(para.source) end test 'escaped single-line double-quoted string' do para = block_from_string %(#{BACKSLASH}``a few quoted words''), attributes: { 'compat-mode' => '' } assert_equal %q(‘`a few quoted words’'), para.sub_quotes(para.source) para = block_from_string %(#{BACKSLASH * 2}``a few quoted words''), attributes: { 'compat-mode' => '' } assert_equal %q(``a few quoted words''), para.sub_quotes(para.source) para = block_from_string(%(#{BACKSLASH}"`a few quoted words`")) assert_equal %q("`a few quoted words`"), para.sub_quotes(para.source) para = block_from_string(%(#{BACKSLASH * 2}"`a few quoted words`")) assert_equal %(#{BACKSLASH}"`a few quoted words`"), para.sub_quotes(para.source) end test 'multi-line double-quoted string' do para = block_from_string(%Q{``a few\nquoted words''}, attributes: { 'compat-mode' => '' }) assert_equal "“a few\nquoted words”", para.sub_quotes(para.source) para = block_from_string(%Q{"`a few\nquoted words`"}) assert_equal "“a few\nquoted words”", para.sub_quotes(para.source) end test 'double-quoted string with inline single quote' do para = block_from_string(%q{``Here's Johnny!''}, attributes: { 'compat-mode' => '' }) assert_equal %q{“Here's Johnny!”}, para.sub_quotes(para.source) para = block_from_string(%q{"`Here's Johnny!`"}) assert_equal %q{“Here's Johnny!”}, para.sub_quotes(para.source) end test 'double-quoted string with inline backquote' do para = block_from_string(%q{``Here`s Johnny!''}, attributes: { 'compat-mode' => '' }) assert_equal %q{“Here`s Johnny!”}, para.sub_quotes(para.source) para = block_from_string(%q{"`Here`s Johnny!`"}) assert_equal %q{“Here`s Johnny!”}, para.sub_quotes(para.source) end test 'double-quoted string around monospaced text' do para = block_from_string(%q("``E=mc^2^` is the solution!`")) assert_equal %q(“`E=mc2` is the solution!”), para.apply_subs(para.source); para = block_from_string(%q("```E=mc^2^`` is the solution!`")) assert_equal %q(“E=mc2 is the solution!”), para.apply_subs(para.source); end test 'single-line single-quoted string' do para = block_from_string(%q{`a few quoted words'}, attributes: { 'compat-mode' => '' }) assert_equal '‘a few quoted words’', para.sub_quotes(para.source) para = block_from_string(%q{'`a few quoted words`'}) assert_equal '‘a few quoted words’', para.sub_quotes(para.source) para = block_from_string(%q{'`a few quoted words`'}, backend: 'docbook') assert_equal 'a few quoted words', para.sub_quotes(para.source) end test 'escaped single-line single-quoted string' do para = block_from_string(%(#{BACKSLASH}`a few quoted words'), attributes: { 'compat-mode' => '' }) assert_equal %(`a few quoted words'), para.sub_quotes(para.source) para = block_from_string(%(#{BACKSLASH}'`a few quoted words`')) assert_equal %('`a few quoted words`'), para.sub_quotes(para.source) end test 'multi-line single-quoted string' do para = block_from_string(%Q{`a few\nquoted words'}, attributes: { 'compat-mode' => '' }) assert_equal "‘a few\nquoted words’", para.sub_quotes(para.source) para = block_from_string(%Q{'`a few\nquoted words`'}) assert_equal "‘a few\nquoted words’", para.sub_quotes(para.source) end test 'single-quoted string with inline single quote' do para = block_from_string(%q{`That isn't what I did.'}, attributes: { 'compat-mode' => '' }) assert_equal %q{‘That isn't what I did.’}, para.sub_quotes(para.source) para = block_from_string(%q{'`That isn't what I did.`'}) assert_equal %q{‘That isn't what I did.’}, para.sub_quotes(para.source) end test 'single-quoted string with inline backquote' do para = block_from_string(%q{`Here`s Johnny!'}, attributes: { 'compat-mode' => '' }) assert_equal %q{‘Here`s Johnny!’}, para.sub_quotes(para.source) para = block_from_string(%q{'`Here`s Johnny!`'}) assert_equal %q{‘Here`s Johnny!’}, para.sub_quotes(para.source) end test 'single-line constrained marked string' do #para = block_from_string(%q{#a few words#}, attributes: { 'compat-mode' => '' }) #assert_equal 'a few words', para.sub_quotes(para.source) para = block_from_string(%q{#a few words#}) assert_equal 'a few words', para.sub_quotes(para.source) end test 'escaped single-line constrained marked string' do para = block_from_string(%(#{BACKSLASH}#a few words#)) assert_equal '#a few words#', para.sub_quotes(para.source) end test 'multi-line constrained marked string' do #para = block_from_string(%Q{#a few\nwords#}, attributes: { 'compat-mode' => '' }) #assert_equal "a few\nwords", para.sub_quotes(para.source) para = block_from_string(%Q{#a few\nwords#}) assert_equal "a few\nwords", para.sub_quotes(para.source) end test 'constrained marked string should not match entity references' do para = block_from_string('111 #mark a# 222 "`quote a`" 333 #mark b# 444') assert_equal %(111 mark a 222 “quote a” 333 mark b 444), para.sub_quotes(para.source) end test 'single-line unconstrained marked string' do #para = block_from_string(%q{##--anything goes ##}, attributes: { 'compat-mode' => '' }) #assert_equal '--anything goes ', para.sub_quotes(para.source) para = block_from_string(%q{##--anything goes ##}) assert_equal '--anything goes ', para.sub_quotes(para.source) end test 'escaped single-line unconstrained marked string' do para = block_from_string(%(#{BACKSLASH}#{BACKSLASH}##--anything goes ##)) assert_equal '##--anything goes ##', para.sub_quotes(para.source) end test 'multi-line unconstrained marked string' do #para = block_from_string(%Q{##--anything\ngoes ##}, attributes: { 'compat-mode' => '' }) #assert_equal "--anything\ngoes ", para.sub_quotes(para.source) para = block_from_string(%Q{##--anything\ngoes ##}) assert_equal "--anything\ngoes ", para.sub_quotes(para.source) end test 'single-line constrained marked string with role' do para = block_from_string(%q{[statement]#a few words#}) assert_equal 'a few words', para.sub_quotes(para.source) end test 'single-line constrained strong string' do para = block_from_string(%q{*a few strong words*}) assert_equal 'a few strong words', para.sub_quotes(para.source) end test 'escaped single-line constrained strong string' do para = block_from_string(%(#{BACKSLASH}*a few strong words*)) assert_equal '*a few strong words*', para.sub_quotes(para.source) end test 'multi-line constrained strong string' do para = block_from_string(%Q{*a few\nstrong words*}) assert_equal "a few\nstrong words", para.sub_quotes(para.source) end test 'constrained strong string containing an asterisk' do para = block_from_string(%q{*bl*ck*-eye}) assert_equal 'bl*ck-eye', para.sub_quotes(para.source) end test 'constrained strong string containing an asterisk and multibyte word chars' do para = block_from_string(%q{*黑*眼圈*}) assert_equal '黑*眼圈', para.sub_quotes(para.source) end test 'single-line constrained quote variation emphasized string' do para = block_from_string(%q{_a few emphasized words_}) assert_equal 'a few emphasized words', para.sub_quotes(para.source) end test 'escaped single-line constrained quote variation emphasized string' do para = block_from_string(%(#{BACKSLASH}_a few emphasized words_)) assert_equal %q(_a few emphasized words_), para.sub_quotes(para.source) end test 'escaped single quoted string' do para = block_from_string(%(#{BACKSLASH}'a few emphasized words')) # NOTE the \' is replaced with ' by the :replacements substitution, later in the substitution pipeline assert_equal %(#{BACKSLASH}'a few emphasized words'), para.sub_quotes(para.source) end test 'multi-line constrained emphasized quote variation string' do para = block_from_string(%Q{_a few\nemphasized words_}) assert_equal "a few\nemphasized words", para.sub_quotes(para.source) end test 'single-quoted string containing an emphasized phrase' do para = block_from_string(%q{`I told him, 'Just go for it!''}, attributes: { 'compat-mode' => '' }) assert_equal '‘I told him, Just go for it!’', para.sub_quotes(para.source) para = block_from_string(%q{'`I told him, 'Just go for it!'`'}) assert_equal %q(‘I told him, 'Just go for it!'’), para.sub_quotes(para.source) end test 'escaped single-quotes inside emphasized words are restored' do para = block_from_string(%('Here#{BACKSLASH}'s Johnny!'), attributes: { 'compat-mode' => '' }) assert_equal %q(Here's Johnny!), para.apply_subs(para.source) para = block_from_string(%('Here#{BACKSLASH}'s Johnny!')) assert_equal %q('Here's Johnny!'), para.apply_subs(para.source) end test 'single-line constrained emphasized underline variation string' do para = block_from_string(%q{_a few emphasized words_}) assert_equal 'a few emphasized words', para.sub_quotes(para.source) end test 'escaped single-line constrained emphasized underline variation string' do para = block_from_string(%(#{BACKSLASH}_a few emphasized words_)) assert_equal '_a few emphasized words_', para.sub_quotes(para.source) end test 'multi-line constrained emphasized underline variation string' do para = block_from_string(%Q{_a few\nemphasized words_}) assert_equal "a few\nemphasized words", para.sub_quotes(para.source) end # NOTE must use apply_subs because constrained monospaced is handled as a passthrough test 'single-line constrained monospaced string' do para = block_from_string(%(`a few <{monospaced}> words`), attributes: { 'monospaced' => 'monospaced', 'compat-mode' => '' }) assert_equal 'a few <{monospaced}> words', para.apply_subs(para.source) para = block_from_string(%(`a few <{monospaced}> words`), attributes: { 'monospaced' => 'monospaced' }) assert_equal 'a few <monospaced> words', para.apply_subs(para.source) end # NOTE must use apply_subs because constrained monospaced is handled as a passthrough test 'single-line constrained monospaced string with role' do para = block_from_string(%([input]`a few <{monospaced}> words`), attributes: { 'monospaced' => 'monospaced', 'compat-mode' => '' }) assert_equal 'a few <{monospaced}> words', para.apply_subs(para.source) para = block_from_string(%([input]`a few <{monospaced}> words`), attributes: { 'monospaced' => 'monospaced' }) assert_equal 'a few <monospaced> words', para.apply_subs(para.source) end # NOTE must use apply_subs because constrained monospaced is handled as a passthrough test 'escaped single-line constrained monospaced string' do para = block_from_string(%(#{BACKSLASH}`a few words`), attributes: { 'compat-mode' => '' }) assert_equal '`a few <monospaced> words`', para.apply_subs(para.source) para = block_from_string(%(#{BACKSLASH}`a few words`)) assert_equal '`a few <monospaced> words`', para.apply_subs(para.source) end # NOTE must use apply_subs because constrained monospaced is handled as a passthrough test 'escaped single-line constrained monospaced string with role' do para = block_from_string(%([input]#{BACKSLASH}`a few words`), attributes: { 'compat-mode' => '' }) assert_equal '[input]`a few <monospaced> words`', para.apply_subs(para.source) para = block_from_string(%([input]#{BACKSLASH}`a few words`)) assert_equal '[input]`a few <monospaced> words`', para.apply_subs(para.source) end # NOTE must use apply_subs because constrained monospaced is handled as a passthrough test 'escaped role on single-line constrained monospaced string' do para = block_from_string(%(#{BACKSLASH}[input]`a few words`), attributes: { 'compat-mode' => '' }) assert_equal '[input]a few <monospaced> words', para.apply_subs(para.source) para = block_from_string(%(#{BACKSLASH}[input]`a few words`)) assert_equal '[input]a few <monospaced> words', para.apply_subs(para.source) end # NOTE must use apply_subs because constrained monospaced is handled as a passthrough test 'escaped role on escaped single-line constrained monospaced string' do para = block_from_string(%(#{BACKSLASH}[input]#{BACKSLASH}`a few words`), attributes: { 'compat-mode' => '' }) assert_equal %(#{BACKSLASH}[input]`a few <monospaced> words`), para.apply_subs(para.source) para = block_from_string(%(#{BACKSLASH}[input]#{BACKSLASH}`a few words`)) assert_equal %(#{BACKSLASH}[input]`a few <monospaced> words`), para.apply_subs(para.source) end # NOTE must use apply_subs because constrained monospaced is handled as a passthrough test 'multi-line constrained monospaced string' do para = block_from_string(%(`a few\n<{monospaced}> words`), attributes: { 'monospaced' => 'monospaced', 'compat-mode' => '' }) assert_equal "a few\n<{monospaced}> words", para.apply_subs(para.source) para = block_from_string(%(`a few\n<{monospaced}> words`), attributes: { 'monospaced' => 'monospaced' }) assert_equal "a few\n<monospaced> words", para.apply_subs(para.source) end test 'single-line unconstrained strong chars' do para = block_from_string(%q{**Git**Hub}) assert_equal 'GitHub', para.sub_quotes(para.source) end test 'escaped single-line unconstrained strong chars' do para = block_from_string(%(#{BACKSLASH}**Git**Hub)) assert_equal '*Git*Hub', para.sub_quotes(para.source) end test 'multi-line unconstrained strong chars' do para = block_from_string(%Q{**G\ni\nt\n**Hub}) assert_equal "G\ni\nt\nHub", para.sub_quotes(para.source) end test 'unconstrained strong chars with inline asterisk' do para = block_from_string(%q{**bl*ck**-eye}) assert_equal 'bl*ck-eye', para.sub_quotes(para.source) end test 'unconstrained strong chars with role' do para = block_from_string(%q{Git[blue]**Hub**}) assert_equal %q{GitHub}, para.sub_quotes(para.source) end # TODO this is not the same result as AsciiDoc, though I don't understand why AsciiDoc gets what it gets test 'escaped unconstrained strong chars with role' do para = block_from_string(%(Git#{BACKSLASH}[blue]**Hub**)) assert_equal %q{Git[blue]*Hub*}, para.sub_quotes(para.source) end test 'single-line unconstrained emphasized chars' do para = block_from_string(%q{__Git__Hub}) assert_equal 'GitHub', para.sub_quotes(para.source) end test 'escaped single-line unconstrained emphasized chars' do para = block_from_string(%(#{BACKSLASH}__Git__Hub)) assert_equal '__Git__Hub', para.sub_quotes(para.source) end test 'escaped single-line unconstrained emphasized chars around word' do para = block_from_string(%(#{BACKSLASH}#{BACKSLASH}__GitHub__)) assert_equal '__GitHub__', para.sub_quotes(para.source) end test 'multi-line unconstrained emphasized chars' do para = block_from_string(%Q{__G\ni\nt\n__Hub}) assert_equal "G\ni\nt\nHub", para.sub_quotes(para.source) end test 'unconstrained emphasis chars with role' do para = block_from_string(%q{[gray]__Git__Hub}) assert_equal %q{GitHub}, para.sub_quotes(para.source) end test 'escaped unconstrained emphasis chars with role' do para = block_from_string(%(#{BACKSLASH}[gray]__Git__Hub)) assert_equal %q{[gray]__Git__Hub}, para.sub_quotes(para.source) end test 'single-line constrained monospaced chars' do para = block_from_string(%q{call +save()+ to persist the changes}, attributes: { 'compat-mode' => '' }) assert_equal 'call save() to persist the changes', para.sub_quotes(para.source) para = block_from_string(%q{call [x-]+save()+ to persist the changes}) assert_equal 'call save() to persist the changes', para.apply_subs(para.source) para = block_from_string(%q{call `save()` to persist the changes}) assert_equal 'call save() to persist the changes', para.sub_quotes(para.source) end test 'single-line constrained monospaced chars with role' do para = block_from_string(%q{call [method]+save()+ to persist the changes}, attributes: { 'compat-mode' => '' }) assert_equal 'call save() to persist the changes', para.sub_quotes(para.source) para = block_from_string(%q{call [method x-]+save()+ to persist the changes}) assert_equal 'call save() to persist the changes', para.apply_subs(para.source) para = block_from_string(%q{call [method]`save()` to persist the changes}) assert_equal 'call save() to persist the changes', para.sub_quotes(para.source) end test 'escaped single-line constrained monospaced chars' do para = block_from_string(%(call #{BACKSLASH}+save()+ to persist the changes), attributes: { 'compat-mode' => '' }) assert_equal 'call +save()+ to persist the changes', para.sub_quotes(para.source) para = block_from_string(%(call #{BACKSLASH}`save()` to persist the changes)) assert_equal 'call `save()` to persist the changes', para.sub_quotes(para.source) end test 'escaped single-line constrained monospaced chars with role' do para = block_from_string(%(call [method]#{BACKSLASH}+save()+ to persist the changes), attributes: { 'compat-mode' => '' }) assert_equal 'call [method]+save()+ to persist the changes', para.sub_quotes(para.source) para = block_from_string(%(call [method]#{BACKSLASH}`save()` to persist the changes)) assert_equal 'call [method]`save()` to persist the changes', para.sub_quotes(para.source) end test 'escaped role on single-line constrained monospaced chars' do para = block_from_string(%(call #{BACKSLASH}[method]+save()+ to persist the changes), attributes: { 'compat-mode' => '' }) assert_equal 'call [method]save() to persist the changes', para.sub_quotes(para.source) para = block_from_string(%(call #{BACKSLASH}[method]`save()` to persist the changes)) assert_equal 'call [method]save() to persist the changes', para.sub_quotes(para.source) end test 'escaped role on escaped single-line constrained monospaced chars' do para = block_from_string(%(call #{BACKSLASH}[method]#{BACKSLASH}+save()+ to persist the changes), attributes: { 'compat-mode' => '' }) assert_equal %(call #{BACKSLASH}[method]+save()+ to persist the changes), para.sub_quotes(para.source) para = block_from_string(%(call #{BACKSLASH}[method]#{BACKSLASH}`save()` to persist the changes)) assert_equal %(call #{BACKSLASH}[method]`save()` to persist the changes), para.sub_quotes(para.source) end test 'single-line unconstrained monospaced chars' do para = block_from_string(%q{Git++Hub++}, attributes: { 'compat-mode' => '' }) assert_equal 'GitHub', para.sub_quotes(para.source) para = block_from_string(%q{Git[x-]++Hub++}) assert_equal 'GitHub', para.apply_subs(para.source) para = block_from_string(%q{Git``Hub``}) assert_equal 'GitHub', para.sub_quotes(para.source) end test 'escaped single-line unconstrained monospaced chars' do para = block_from_string(%(Git#{BACKSLASH}++Hub++), attributes: { 'compat-mode' => '' }) assert_equal 'Git+Hub+', para.sub_quotes(para.source) para = block_from_string(%(Git#{BACKSLASH * 2}++Hub++), attributes: { 'compat-mode' => '' }) assert_equal 'Git++Hub++', para.sub_quotes(para.source) para = block_from_string(%(Git#{BACKSLASH}``Hub``)) assert_equal 'Git``Hub``', para.sub_quotes(para.source) end test 'multi-line unconstrained monospaced chars' do para = block_from_string(%Q{Git++\nH\nu\nb++}, attributes: { 'compat-mode' => '' }) assert_equal "Git\nH\nu\nb", para.sub_quotes(para.source) para = block_from_string(%Q{Git[x-]++\nH\nu\nb++}) assert_equal %(Git\nH\nu\nb), para.apply_subs(para.source) para = block_from_string(%Q{Git``\nH\nu\nb``}) assert_equal "Git\nH\nu\nb", para.sub_quotes(para.source) end test 'single-line superscript chars' do para = block_from_string(%(x^2^ = x * x, e = mc^2^, there's a 1^st^ time for everything)) assert_equal %(x2 = x * x, e = mc2, there\'s a 1st time for everything), para.sub_quotes(para.source) end test 'escaped single-line superscript chars' do para = block_from_string(%(x#{BACKSLASH}^2^ = x * x)) assert_equal 'x^2^ = x * x', para.sub_quotes(para.source) end test 'does not match superscript across whitespace' do para = block_from_string(%Q{x^(n\n-\n1)^}) assert_equal para.source, para.sub_quotes(para.source) end test 'does not match adjacent superscript chars' do para = block_from_string 'a ^^ b' assert_equal 'a ^^ b', para.sub_quotes(para.source) end test 'does not confuse superscript and links with blank window shorthand' do para = block_from_string(%Q{http://localhost[Text^] on the 21^st^ and 22^nd^}) assert_equal 'Text on the 21st and 22nd', para.content end test 'single-line subscript chars' do para = block_from_string(%q{H~2~O}) assert_equal 'H2O', para.sub_quotes(para.source) end test 'escaped single-line subscript chars' do para = block_from_string(%(H#{BACKSLASH}~2~O)) assert_equal 'H~2~O', para.sub_quotes(para.source) end test 'does not match subscript across whitespace' do para = block_from_string(%Q{project~ view\non\nGitHub~}) assert_equal para.source, para.sub_quotes(para.source) end test 'does not match adjacent subscript chars' do para = block_from_string 'a ~~ b' assert_equal 'a ~~ b', para.sub_quotes(para.source) end test 'does not match subscript across distinct URLs' do para = block_from_string(%Q{http://www.abc.com/~def[DEF] and http://www.abc.com/~ghi[GHI]}) assert_equal para.source, para.sub_quotes(para.source) end test 'quoted text with role shorthand' do para = block_from_string(%q{[.white.red-background]#alert#}) assert_equal 'alert', para.sub_quotes(para.source) end test 'quoted text with id shorthand' do para = block_from_string(%q{[#bond]#007#}) assert_equal '007', para.sub_quotes(para.source) end test 'quoted text with id and role shorthand' do para = block_from_string(%q{[#bond.white.red-background]#007#}) assert_equal '007', para.sub_quotes(para.source) end test 'quoted text with id and role shorthand using docbook backend' do para = block_from_string(%q{[#bond.white.red-background]#007#}, backend: 'docbook') assert_equal '007', para.sub_quotes(para.source) end test 'should ignore attributes after comma' do para = block_from_string(%q{[red, foobar]#alert#}) assert_equal 'alert', para.sub_quotes(para.source) end test 'inline passthrough with id and role set using shorthand' do %w(#idname.rolename .rolename#idname).each do |attrlist| para = block_from_string %([#{attrlist}]+pass+) assert_equal 'pass', para.content end end test 'should not assign role attribute if shorthand style has no roles' do para = block_from_string '[#idname]*blah*' assert_equal 'blah', para.content end end context 'Macros' do test 'a single-line link macro should be interpreted as a link' do para = block_from_string('link:/home.html[]') assert_equal %q{/home.html}, para.sub_macros(para.source) end test 'a single-line link macro with text should be interpreted as a link' do para = block_from_string('link:/home.html[Home]') assert_equal %q{Home}, para.sub_macros(para.source) end test 'a mailto macro should be interpreted as a mailto link' do para = block_from_string('mailto:doc.writer@asciidoc.org[]') assert_equal %q{doc.writer@asciidoc.org}, para.sub_macros(para.source) end test 'a mailto macro with text should be interpreted as a mailto link' do para = block_from_string('mailto:doc.writer@asciidoc.org[Doc Writer]') assert_equal %q{Doc Writer}, para.sub_macros(para.source) end test 'a mailto macro with text and subject should be interpreted as a mailto link' do para = block_from_string('mailto:doc.writer@asciidoc.org[Doc Writer, Pull request]') assert_equal %q{Doc Writer}, para.sub_macros(para.source) end test 'a mailto macro with text, subject and body should be interpreted as a mailto link' do para = block_from_string('mailto:doc.writer@asciidoc.org[Doc Writer, Pull request, Please accept my pull request]') assert_equal %q{Doc Writer}, para.sub_macros(para.source) end test 'a mailto macro with subject and body only should use e-mail as text' do para = block_from_string('mailto:doc.writer@asciidoc.org[,Pull request,Please accept my pull request]') assert_equal %q{doc.writer@asciidoc.org}, para.sub_macros(para.source) end test 'should recognize inline email addresses' do %w( doc.writer@asciidoc.org author+website@4fs.no john@domain.uk.co name@somewhere.else.com joe_bloggs@mail_server.com joe-bloggs@mail-server.com joe.bloggs@mail.server.com FOO@BAR.COM docs@writing.ninja ).each do |input| para = block_from_string input assert_equal %(#{input}), (para.sub_macros para.source) end end test 'should recognize inline email address containing an ampersand' do para = block_from_string('bert&ernie@sesamestreet.com') assert_equal %q{bert&ernie@sesamestreet.com}, para.apply_subs(para.source) end test 'should recognize inline email address surrounded by angle brackets' do para = block_from_string('') assert_equal %q{<doc.writer@asciidoc.org>}, para.apply_subs(para.source) end test 'should ignore escaped inline email address' do para = block_from_string(%(#{BACKSLASH}doc.writer@asciidoc.org)) assert_equal %q{doc.writer@asciidoc.org}, para.sub_macros(para.source) end test 'a single-line raw url should be interpreted as a link' do para = block_from_string('http://google.com') assert_equal %q{http://google.com}, para.sub_macros(para.source) end test 'a single-line raw url with text should be interpreted as a link' do para = block_from_string('http://google.com[Google]') assert_equal %q{Google}, para.sub_macros(para.source) end test 'a multi-line raw url with text should be interpreted as a link' do para = block_from_string("http://google.com[Google\nHomepage]") assert_equal %{Google\nHomepage}, para.sub_macros(para.source) end test 'a single-line raw url with attribute as text should be interpreted as a link with resolved attribute' do para = block_from_string("http://google.com[{google_homepage}]") para.document.attributes['google_homepage'] = 'Google Homepage' assert_equal %q{Google Homepage}, para.sub_macros(para.sub_attributes(para.source)) end test 'should not resolve an escaped attribute in link text' do { 'http://google.com' => "http://google.com[#{BACKSLASH}{google_homepage}]", 'http://google.com?q=,' => "link:http://google.com?q=,[#{BACKSLASH}{google_homepage}]", }.each do |uri, macro| para = block_from_string macro para.document.attributes['google_homepage'] = 'Google Homepage' assert_equal %({google_homepage}), para.sub_macros(para.sub_attributes(para.source)) end end test 'a single-line escaped raw url should not be interpreted as a link' do para = block_from_string(%(#{BACKSLASH}http://google.com)) assert_equal %q{http://google.com}, para.sub_macros(para.source) end test 'a comma separated list of links should not include commas in links' do para = block_from_string('http://foo.com, http://bar.com, http://example.org') assert_equal %q{http://foo.com, http://bar.com, http://example.org}, para.sub_macros(para.source) end test 'a single-line image macro should be interpreted as an image' do para = block_from_string('image:tiger.png[]') assert_equal %{tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'should replace underscore and hyphen with space in generated alt text for an inline image' do para = block_from_string('image:tiger-with-family_1.png[]') assert_equal %{tiger with family 1}, para.sub_macros(para.source).gsub(/>\s+<') end test 'a single-line image macro with text should be interpreted as an image with alt text' do para = block_from_string('image:tiger.png[Tiger]') assert_equal %{Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'should encode special characters in alt text of inline image' do input = 'A tiger\'s "roar" is < a bear\'s "growl"' expected = 'A tiger’s "roar" is < a bear’s "growl"' output = (convert_inline_string %(image:tiger-roar.png[#{input}])).gsub(/>\s+<') assert_equal %(#{expected}), output end test 'an image macro with SVG image and text should be interpreted as an image with alt text' do para = block_from_string('image:tiger.svg[Tiger]') assert_equal %{Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'an image macro with an interactive SVG image and alt text should be converted to an object element' do para = block_from_string('image:tiger.svg[Tiger,opts=interactive]', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'imagesdir' => 'images' }) assert_equal %{Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'an image macro with an interactive SVG image, fallback and alt text should be converted to an object element' do para = block_from_string('image:tiger.svg[Tiger,fallback=tiger.png,opts=interactive]', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'imagesdir' => 'images' }) assert_equal %{Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'an image macro with an inline SVG image should be converted to an svg element' do para = block_from_string('image:circle.svg[Tiger,100,opts=inline]', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'imagesdir' => 'fixtures', 'docdir' => testdir }) result = para.sub_macros(para.source).gsub(/>\s+<') assert_match(/]*width="100px"[^>]*>/, result) refute_match(/]*width="500px"[^>]*>/, result) refute_match(/]*height="500px"[^>]*>/, result) refute_match(/]*style="width:500px;height:500px"[^>]*>/, result) end test 'an image macro with an inline SVG image should be converted to an svg element even when data-uri is set' do para = block_from_string('image:circle.svg[Tiger,100,opts=inline]', safe: Asciidoctor::SafeMode::SERVER, attributes: { 'data-uri' => '', 'imagesdir' => 'fixtures', 'docdir' => testdir }) assert_match(/]*width="100px">/, para.sub_macros(para.source).gsub(/>\s+<')) end test 'an image macro with an SVG image should not use an object element when safe mode is secure' do para = block_from_string('image:tiger.svg[Tiger,opts=interactive]', attributes: { 'imagesdir' => 'images' }) assert_equal %{Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'a single-line image macro with text containing escaped square bracket should be interpreted as an image with alt text' do para = block_from_string(%(image:tiger.png[[Another#{BACKSLASH}] Tiger])) assert_equal %{[Another] Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'a single-line image macro with text and dimensions should be interpreted as an image with alt text and dimensions' do para = block_from_string('image:tiger.png[Tiger, 200, 100]') assert_equal %{Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'a single-line image macro with text and dimensions should be interpreted as an image with alt text and dimensions in docbook' do para = block_from_string 'image:tiger.png[Tiger, 200, 100]', backend: 'docbook' assert_equal %{Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'a single-line image macro with text and link should be interpreted as a linked image with alt text' do para = block_from_string('image:tiger.png[Tiger, link="http://en.wikipedia.org/wiki/Tiger"]') assert_equal %{Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'rel=noopener should be added to an image with a link that targets the _blank window' do para = block_from_string 'image:tiger.png[Tiger,link=http://en.wikipedia.org/wiki/Tiger,window=_blank]' assert_equal %{Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'rel=noopener should be added to an image with a link that targets a named window when the noopener option is set' do para = block_from_string 'image:tiger.png[Tiger,link=http://en.wikipedia.org/wiki/Tiger,window=name,opts=noopener]' assert_equal %{Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'rel=nofollow should be added to an image with a link when the nofollow option is set' do para = block_from_string 'image:tiger.png[Tiger,link=http://en.wikipedia.org/wiki/Tiger,opts=nofollow]' assert_equal %{Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'a multi-line image macro with text and dimensions should be interpreted as an image with alt text and dimensions' do para = block_from_string(%(image:tiger.png[Another\nAwesome\nTiger, 200,\n100])) assert_equal %{Another Awesome Tiger}, para.sub_macros(para.source).gsub(/>\s+<') end test 'an inline image macro with a url target should be interpreted as an image' do para = block_from_string %(Beware of the image:http://example.com/images/tiger.png[tiger].) assert_equal %{Beware of the tiger.}, para.sub_macros(para.source).gsub(/>\s+<') end test 'an inline image macro with a float attribute should be interpreted as a floating image' do para = block_from_string %(image:http://example.com/images/tiger.png[tiger, float="right"] Beware of the tigers!) assert_equal %{tiger Beware of the tigers!}, para.sub_macros(para.source).gsub(/>\s+<') end test 'should prepend value of imagesdir attribute to inline image target if target is relative path' do para = block_from_string %(Beware of the image:tiger.png[tiger].), attributes: { 'imagesdir' => './images' } assert_equal %{Beware of the tiger.}, para.sub_macros(para.source).gsub(/>\s+<') end test 'should not prepend value of imagesdir attribute to inline image target if target is absolute path' do para = block_from_string %(Beware of the image:/tiger.png[tiger].), attributes: { 'imagesdir' => './images' } assert_equal %{Beware of the tiger.}, para.sub_macros(para.source).gsub(/>\s+<') end test 'should not prepend value of imagesdir attribute to inline image target if target is url' do para = block_from_string %(Beware of the image:http://example.com/images/tiger.png[tiger].), attributes: { 'imagesdir' => './images' } assert_equal %{Beware of the tiger.}, para.sub_macros(para.source).gsub(/>\s+<') end test 'should match an inline image macro if target contains a space character' do para = block_from_string(%(Beware of the image:big cats.png[] around here.)) assert_equal %(Beware of the big cats around here.), para.sub_macros(para.source).gsub(/>\s+<') end test 'should not match an inline image macro if target contains a newline character' do para = block_from_string(%(Fear not. There are no image:big\ncats.png[] around here.)) result = para.sub_macros(para.source) refute_includes result, ' '', 'iconsdir' => 'fixtures', 'docdir' => testdir }, safe: :server, catalog_assets: true assert 1, sect.document.catalog[:images].size assert_equal 'fixtures/dot.gif', sect.document.catalog[:images][0].to_s assert_nil sect.document.catalog[:images][0].imagesdir assert logger.empty? end end test 'an icon macro should be interpreted as an icon if icons are enabled' do para = block_from_string 'icon:github[]', attributes: { 'icons' => '' } assert_equal %{github}, para.sub_macros(para.source).gsub(/>\s+<') end test 'an icon macro should be interpreted as alt text if icons are disabled' do para = block_from_string 'icon:github[]' assert_equal %{[github]}, para.sub_macros(para.source).gsub(/>\s+<') end test 'an icon macro should output alt text if icons are disabled and alt is given' do para = block_from_string 'icon:github[alt="GitHub"]' assert_equal %{[GitHub]}, para.sub_macros(para.source).gsub(/>\s+<') end test 'an icon macro should be interpreted as a font-based icon when icons=font' do para = block_from_string 'icon:github[]', attributes: { 'icons' => 'font' } assert_equal %{}, para.sub_macros(para.source).gsub(/>\s+<') end test 'an icon macro with a size should be interpreted as a font-based icon with a size when icons=font' do para = block_from_string 'icon:github[4x]', attributes: { 'icons' => 'font' } assert_equal %{}, para.sub_macros(para.source).gsub(/>\s+<') end test 'an icon macro with a role and title should be interpreted as a font-based icon with a class and title when icons=font' do para = block_from_string 'icon:heart[role="red", title="Heart me"]', attributes: { 'icons' => 'font' } assert_equal %{}, para.sub_macros(para.source).gsub(/>\s+<') end test 'a single-line footnote macro should be registered and output as a footnote' do para = block_from_string('Sentence text footnote:[An example footnote.].') assert_equal %(Sentence text [1].), para.sub_macros(para.source) assert_equal 1, para.document.catalog[:footnotes].size footnote = para.document.catalog[:footnotes].first assert_equal 1, footnote.index assert_nil footnote.id assert_equal 'An example footnote.', footnote.text end test 'a multi-line footnote macro should be registered and output as a footnote without newline' do para = block_from_string("Sentence text footnote:[An example footnote\nwith wrapped text.].") assert_equal %(Sentence text [1].), para.sub_macros(para.source) assert_equal 1, para.document.catalog[:footnotes].size footnote = para.document.catalog[:footnotes].first assert_equal 1, footnote.index assert_nil footnote.id assert_equal "An example footnote with wrapped text.", footnote.text end test 'an escaped closing square bracket in a footnote should be unescaped when converted' do para = block_from_string(%(footnote:[a #{BACKSLASH}] b].)) assert_equal %([1].), para.sub_macros(para.source) assert_equal 1, para.document.catalog[:footnotes].size footnote = para.document.catalog[:footnotes].first assert_equal "a ] b", footnote.text end test 'a footnote macro can be directly adjacent to preceding word' do para = block_from_string('Sentence textfootnote:[An example footnote.].') assert_equal %(Sentence text[1].), para.sub_macros(para.source) end test 'a footnote macro may contain an escaped backslash' do para = block_from_string("footnote:[\\]]\nfootnote:[a \\] b]\nfootnote:[a \\]\\] b]") para.sub_macros(para.source) assert_equal 3, para.document.catalog[:footnotes].size footnote1 = para.document.catalog[:footnotes][0] assert_equal ']', footnote1.text footnote2 = para.document.catalog[:footnotes][1] assert_equal 'a ] b', footnote2.text footnote3 = para.document.catalog[:footnotes][2] assert_equal 'a ]] b', footnote3.text end test 'a footnote macro may contain a link macro' do para = block_from_string('Share your code. footnote:[https://github.com[GitHub]]') assert_equal %(Share your code. [1]), para.sub_macros(para.source) assert_equal 1, para.document.catalog[:footnotes].size footnote1 = para.document.catalog[:footnotes][0] assert_equal 'GitHub', footnote1.text end test 'a footnote macro may contain a plain URL' do para = block_from_string %(the JLine footnote:[https://github.com/jline/jline2]\nlibrary.) result = para.sub_macros para.source assert_equal %(the JLine [1]\nlibrary.), result assert_equal 1, para.document.catalog[:footnotes].size fn1 = para.document.catalog[:footnotes].first assert_equal 'https://github.com/jline/jline2', fn1.text end test 'a footnote macro followed by a semi-colon may contain a plain URL' do para = block_from_string %(the JLine footnote:[https://github.com/jline/jline2];\nlibrary.) result = para.sub_macros para.source assert_equal %(the JLine [1];\nlibrary.), result assert_equal 1, para.document.catalog[:footnotes].size fn1 = para.document.catalog[:footnotes].first assert_equal 'https://github.com/jline/jline2', fn1.text end test 'a footnote macro may contain a shorthand xref' do # specialcharacters escaping is simulated para = block_from_string('text footnote:[<<_install,install>>]') doc = para.document doc.register :refs, ['_install', (Asciidoctor::Inline.new doc, :anchor, 'Install', type: :ref, target: '_install'), 'Install'] catalog = doc.catalog assert_equal %(text [1]), para.sub_macros(para.source) assert_equal 1, catalog[:footnotes].size footnote1 = catalog[:footnotes][0] assert_equal 'install', footnote1.text end test 'a footnote macro may contain an xref macro' do para = block_from_string('text footnote:[xref:_install[install]]') doc = para.document doc.register :refs, ['_install', (Asciidoctor::Inline.new doc, :anchor, 'Install', type: :ref, target: '_install'), 'Install'] catalog = doc.catalog assert_equal %(text [1]), para.sub_macros(para.source) assert_equal 1, catalog[:footnotes].size footnote1 = catalog[:footnotes][0] assert_equal 'install', footnote1.text end test 'a footnote macro may contain an anchor macro' do para = block_from_string('text footnote:[a [[b]] [[c\]\] d]') assert_equal %(text [1]), para.sub_macros(para.source) assert_equal 1, para.document.catalog[:footnotes].size footnote1 = para.document.catalog[:footnotes][0] assert_equal 'a [[c]] d', footnote1.text end test 'subsequent footnote macros with escaped URLs should be restored in DocBook' do input = 'foofootnote:[+http://example.com+]barfootnote:[+http://acme.com+]baz' result = convert_string_to_embedded input, doctype: 'inline', backend: 'docbook' assert_equal 'foohttp://example.combarhttp://acme.combaz', result end test 'should increment index of subsequent footnote macros' do para = block_from_string("Sentence text footnote:[An example footnote.]. Sentence text footnote:[Another footnote.].") assert_equal %(Sentence text [1]. Sentence text [2].), para.sub_macros(para.source) assert_equal 2, para.document.catalog[:footnotes].size footnote1 = para.document.catalog[:footnotes][0] assert_equal 1, footnote1.index assert_nil footnote1.id assert_equal "An example footnote.", footnote1.text footnote2 = para.document.catalog[:footnotes][1] assert_equal 2, footnote2.index assert_nil footnote2.id assert_equal "Another footnote.", footnote2.text end test 'a footnoteref macro with id and single-line text should be registered and output as a footnote' do para = block_from_string 'Sentence text footnoteref:[ex1, An example footnote.].', attributes: { 'compat-mode' => '' } assert_equal %(Sentence text [1].), para.sub_macros(para.source) assert_equal 1, para.document.catalog[:footnotes].size footnote = para.document.catalog[:footnotes].first assert_equal 1, footnote.index assert_equal 'ex1', footnote.id assert_equal 'An example footnote.', footnote.text end test 'a footnoteref macro with id and multi-line text should be registered and output as a footnote without newlines' do para = block_from_string "Sentence text footnoteref:[ex1, An example footnote\nwith wrapped text.].", attributes: { 'compat-mode' => '' } assert_equal %(Sentence text [1].), para.sub_macros(para.source) assert_equal 1, para.document.catalog[:footnotes].size footnote = para.document.catalog[:footnotes].first assert_equal 1, footnote.index assert_equal 'ex1', footnote.id assert_equal "An example footnote with wrapped text.", footnote.text end test 'a footnoteref macro with id should refer to footnoteref with same id' do para = block_from_string 'Sentence text footnoteref:[ex1, An example footnote.]. Sentence text footnoteref:[ex1].', attributes: { 'compat-mode' => '' } assert_equal %(Sentence text [1]. Sentence text [1].), para.sub_macros(para.source) assert_equal 1, para.document.catalog[:footnotes].size footnote = para.document.catalog[:footnotes].first assert_equal 1, footnote.index assert_equal 'ex1', footnote.id assert_equal 'An example footnote.', footnote.text end test 'an unresolved footnote reference should produce a warning message' do input = 'Sentence text.footnote:ex1[]' using_memory_logger do |logger| para = block_from_string input para.sub_macros para.source assert_message logger, :WARN, 'invalid footnote reference: ex1' end end test 'using a footnoteref macro should generate a warning when compat mode is not enabled' do input = 'Sentence text.footnoteref:[fn1,Commentary on this sentence.]' using_memory_logger do |logger| para = block_from_string input para.sub_macros para.source assert_message logger, :WARN, 'found deprecated footnoteref macro: footnoteref:[fn1,Commentary on this sentence.]; use footnote macro with target instead' end end test 'inline footnote macro can be used to define and reference a footnote reference' do input = <<~'EOS' You can download the software from the product page.footnote:sub[Option only available if you have an active subscription.] You can also file a support request.footnote:sub[] If all else fails, you can give us a call.footnoteref:[sub] EOS using_memory_logger do |logger| output = convert_string_to_embedded input, attributes: { 'compat-mode' => '' } assert_css '#_footnotedef_1', output, 1 assert_css 'p a[href="#_footnotedef_1"]', output, 3 assert_css '#footnotes .footnote', output, 1 assert logger.empty? end end test 'should parse multiple footnote references in a single line' do input = 'notable text.footnote:id[about this [text\]], footnote:id[], footnote:id[]' output = convert_string_to_embedded input assert_xpath '(//p)[1]/sup[starts-with(@class,"footnote")]', output, 3 assert_xpath '(//p)[1]/sup[@class="footnote"]', output, 1 assert_xpath '(//p)[1]/sup[@class="footnoteref"]', output, 2 assert_xpath '(//p)[1]/sup[starts-with(@class,"footnote")]/a[@class="footnote"][text()="1"]', output, 3 assert_css '#footnotes .footnote', output, 1 end test 'should not resolve an inline footnote macro missing both id and text' do input = <<~'EOS' The footnote:[] macro can be used for defining and referencing footnotes. The footnoteref:[] macro is now deprecated. EOS output = convert_string_to_embedded input assert_includes output, 'The footnote:[] macro' assert_includes output, 'The footnoteref:[] macro' end test 'inline footnote macro can define a numeric id without conflicting with auto-generated ID' do input = 'You can download the software from the product page.footnote:1[Option only available if you have an active subscription.]' output = convert_string_to_embedded input assert_css '#_footnote_1', output, 1 assert_css 'p sup#_footnote_1', output, 1 assert_css 'p a#_footnoteref_1', output, 1 assert_css 'p a[href="#_footnotedef_1"]', output, 1 assert_css '#footnotes #_footnotedef_1', output, 1 end test 'inline footnote macro can define an id that uses any word characters in Unicode' do input = <<~'EOS' L'origine du mot forêt{blank}footnote:forêt[un massif forestier] est complexe. Qu'est-ce qu'une forêt ?{blank}footnote:forêt[] EOS output = convert_string_to_embedded input assert_css '#_footnote_forêt', output, 1 assert_css '#_footnotedef_1', output, 1 assert_xpath '//a[@class="footnote"][text()="1"]', output, 2 end test 'a single-line index term macro with a primary term should be registered as an index reference' do sentence = "The tiger (Panthera tigris) is the largest cat species.\n" macros = ['indexterm:[Tigers]', '(((Tigers)))'] macros.each do |macro| para = block_from_string("#{sentence}#{macro}") output = para.sub_macros(para.source) assert_equal sentence, output #assert_equal 1, para.document.catalog[:indexterms].size #assert_equal ['Tigers'], para.document.catalog[:indexterms].first end end test 'a single-line index term macro with primary and secondary terms should be registered as an index reference' do sentence = "The tiger (Panthera tigris) is the largest cat species.\n" macros = ['indexterm:[Big cats, Tigers]', '(((Big cats, Tigers)))'] macros.each do |macro| para = block_from_string("#{sentence}#{macro}") output = para.sub_macros(para.source) assert_equal sentence, output #assert_equal 1, para.document.catalog[:indexterms].size #assert_equal ['Big cats', 'Tigers'], para.document.catalog[:indexterms].first end end test 'a single-line index term macro with primary, secondary and tertiary terms should be registered as an index reference' do sentence = "The tiger (Panthera tigris) is the largest cat species.\n" macros = ['indexterm:[Big cats,Tigers , Panthera tigris]', '(((Big cats,Tigers , Panthera tigris)))'] macros.each do |macro| para = block_from_string("#{sentence}#{macro}") output = para.sub_macros(para.source) assert_equal sentence, output #assert_equal 1, para.document.catalog[:indexterms].size #assert_equal ['Big cats', 'Tigers', 'Panthera tigris'], para.document.catalog[:indexterms].first end end test 'a multi-line index term macro should be compacted and registered as an index reference' do sentence = "The tiger (Panthera tigris) is the largest cat species.\n" macros = ["indexterm:[Panthera\ntigris]", "(((Panthera\ntigris)))"] macros.each do |macro| para = block_from_string("#{sentence}#{macro}") output = para.sub_macros(para.source) assert_equal sentence, output #assert_equal 1, para.document.catalog[:indexterms].size #assert_equal ['Panthera tigris'], para.document.catalog[:indexterms].first end end test 'should escape concealed index term if second bracket is preceded by a backslash' do input = %[National Institute of Science and Technology (#{BACKSLASH}((NIST)))] doc = document_from_string input, standalone: false output = doc.convert assert_xpath '//p[text()="National Institute of Science and Technology (((NIST)))"]', output, 1 #assert doc.catalog[:indexterms].empty? end test 'should only escape enclosing brackets if concealed index term is preceded by a backslash' do input = %[National Institute of Science and Technology #{BACKSLASH}(((NIST)))] doc = document_from_string input, standalone: false output = doc.convert assert_xpath '//p[text()="National Institute of Science and Technology (NIST)"]', output, 1 #term = doc.catalog[:indexterms].first #assert_equal 1, term.size #assert_equal 'NIST', term.first end test 'should not split index terms on commas inside of quoted terms' do inputs = [] inputs.push <<~'EOS' Tigers are big, scary cats. indexterm:[Tigers, "[Big\], scary cats"] EOS inputs.push <<~'EOS' Tigers are big, scary cats. (((Tigers, "[Big], scary cats"))) EOS inputs.each do |input| para = block_from_string input output = para.sub_macros(para.source) assert_equal input.lines.first, output #assert_equal 1, para.document.catalog[:indexterms].size #terms = para.document.catalog[:indexterms].first #assert_equal 2, terms.size #assert_equal 'Tigers', terms.first #assert_equal '[Big], scary cats', terms.last end end test 'normal substitutions are performed on an index term macro' do sentence = "The tiger (Panthera tigris) is the largest cat species.\n" macros = ['indexterm:[*Tigers*]', '(((*Tigers*)))'] macros.each do |macro| para = block_from_string("#{sentence}#{macro}") output = para.apply_subs(para.source) assert_equal sentence, output #assert_equal 1, para.document.catalog[:indexterms].size #assert_equal ['Tigers'], para.document.catalog[:indexterms].first end end test 'registers multiple index term macros' do sentence = "The tiger (Panthera tigris) is the largest cat species." macros = "(((Tigers)))\n(((Animals,Cats)))" para = block_from_string("#{sentence}\n#{macros}") output = para.sub_macros(para.source) assert_equal sentence, output.rstrip #assert_equal 2, para.document.catalog[:indexterms].size #assert_equal ['Tigers'], para.document.catalog[:indexterms][0] #assert_equal ['Animals', 'Cats'], para.document.catalog[:indexterms][1] end test 'an index term macro with round bracket syntax may contain round brackets in term' do sentence = "The tiger (Panthera tigris) is the largest cat species.\n" macro = '(((Tiger (Panthera tigris))))' para = block_from_string("#{sentence}#{macro}") output = para.sub_macros(para.source) assert_equal sentence, output #assert_equal 1, para.document.catalog[:indexterms].size #assert_equal ['Tiger (Panthera tigris)'], para.document.catalog[:indexterms].first end test 'visible shorthand index term macro should not consume trailing round bracket' do input = '(text with ((index term)))' expected = <<~'EOS'.chop (text with index term index term) EOS #expected_term = ['index term'] para = block_from_string input, backend: :docbook output = para.sub_macros para.source assert_equal expected, output #indexterms_table = para.document.catalog[:indexterms] #assert_equal 1, indexterms_table.size #assert_equal expected_term, indexterms_table[0] end test 'visible shorthand index term macro should not consume leading round bracket' do input = '(((index term)) for text)' expected = <<~'EOS'.chop ( index term index term for text) EOS #expected_term = ['index term'] para = block_from_string input, backend: :docbook output = para.sub_macros para.source assert_equal expected, output #indexterms_table = para.document.catalog[:indexterms] #assert_equal 1, indexterms_table.size #assert_equal expected_term, indexterms_table[0] end test 'an index term macro with square bracket syntax may contain square brackets in term' do sentence = "The tiger (Panthera tigris) is the largest cat species.\n" macro = 'indexterm:[Tiger [Panthera tigris\\]]' para = block_from_string("#{sentence}#{macro}") output = para.sub_macros(para.source) assert_equal sentence, output #assert_equal 1, para.document.catalog[:indexterms].size #assert_equal ['Tiger [Panthera tigris]'], para.document.catalog[:indexterms].first end test 'a single-line index term 2 macro should be registered as an index reference and retain term inline' do sentence = 'The tiger (Panthera tigris) is the largest cat species.' macros = ['The indexterm2:[tiger] (Panthera tigris) is the largest cat species.', 'The ((tiger)) (Panthera tigris) is the largest cat species.'] macros.each do |macro| para = block_from_string(macro) output = para.sub_macros(para.source) assert_equal sentence, output #assert_equal 1, para.document.catalog[:indexterms].size #assert_equal ['tiger'], para.document.catalog[:indexterms].first end end test 'a multi-line index term 2 macro should be compacted and registered as an index reference and retain term inline' do sentence = 'The panthera tigris is the largest cat species.' macros = ["The indexterm2:[ panthera\ntigris ] is the largest cat species.", "The (( panthera\ntigris )) is the largest cat species."] macros.each do |macro| para = block_from_string(macro) output = para.sub_macros(para.source) assert_equal sentence, output #assert_equal 1, para.document.catalog[:indexterms].size #assert_equal ['panthera tigris'], para.document.catalog[:indexterms].first end end test 'registers multiple index term 2 macros' do sentence = "The ((tiger)) (Panthera tigris) is the largest ((cat)) species." para = block_from_string(sentence) output = para.sub_macros(para.source) assert_equal 'The tiger (Panthera tigris) is the largest cat species.', output #assert_equal 2, para.document.catalog[:indexterms].size #assert_equal ['tiger'], para.document.catalog[:indexterms][0] #assert_equal ['cat'], para.document.catalog[:indexterms][1] end test 'should escape visible index term if preceded by a backslash' do sentence = "The #{BACKSLASH}((tiger)) (Panthera tigris) is the largest #{BACKSLASH}((cat)) species." para = block_from_string(sentence) output = para.sub_macros(para.source) assert_equal 'The ((tiger)) (Panthera tigris) is the largest ((cat)) species.', output #assert para.document.catalog[:indexterms].empty? end test 'normal substitutions are performed on an index term 2 macro' do sentence = 'The ((*tiger*)) (Panthera tigris) is the largest cat species.' para = block_from_string sentence output = para.apply_subs(para.source) assert_equal 'The tiger (Panthera tigris) is the largest cat species.', output #assert_equal 1, para.document.catalog[:indexterms].size #assert_equal ['tiger'], para.document.catalog[:indexterms].first end test 'index term 2 macro with round bracket syntex should not interfer with index term macro with round bracket syntax' do sentence = "The ((panthera tigris)) is the largest cat species.\n(((Big cats,Tigers)))" para = block_from_string sentence output = para.sub_macros(para.source) assert_equal "The panthera tigris is the largest cat species.\n", output #terms = para.document.catalog[:indexterms] #assert_equal 2, terms.size #assert_equal ['panthera tigris'], terms[0] #assert_equal ['Big cats', 'Tigers'], terms[1] end test 'should parse visible shorthand index term with see and seealso' do sentence = '((Flash >> HTML 5)) has been supplanted by ((HTML 5 &> CSS 3 &> SVG)).' output = convert_string_to_embedded sentence, backend: 'docbook' indexterm_flash = <<~'EOS'.chop Flash HTML 5 EOS indexterm_html5 = <<~'EOS'.chop HTML 5 CSS 3 SVG EOS assert_includes output, indexterm_flash assert_includes output, indexterm_html5 end test 'should parse concealed shorthand index term with see and seealso' do sentence = 'Flash(((Flash >> HTML 5))) has been supplanted by HTML 5(((HTML 5 &> CSS 3 &> SVG))).' output = convert_string_to_embedded sentence, backend: 'docbook' indexterm_flash = <<~'EOS'.chop Flash HTML 5 EOS indexterm_html5 = <<~'EOS'.chop HTML 5 CSS 3 SVG EOS assert_includes output, indexterm_flash assert_includes output, indexterm_html5 end test 'should parse visible index term macro with see and seealso' do sentence = 'indexterm2:[Flash,see=HTML 5] has been supplanted by indexterm2:[HTML 5,see-also="CSS 3, SVG"].' output = convert_string_to_embedded sentence, backend: 'docbook' indexterm_flash = <<~'EOS'.chop Flash HTML 5 EOS indexterm_html5 = <<~'EOS'.chop HTML 5 CSS 3 SVG EOS assert_includes output, indexterm_flash assert_includes output, indexterm_html5 end test 'should parse concealed index term macro with see and seealso' do sentence = 'Flashindexterm:[Flash,see=HTML 5] has been supplanted by HTML 5indexterm:[HTML 5,see-also="CSS 3, SVG"].' output = convert_string_to_embedded sentence, backend: 'docbook' indexterm_flash = <<~'EOS'.chop Flash HTML 5 EOS indexterm_html5 = <<~'EOS'.chop HTML 5 CSS 3 SVG EOS assert_includes output, indexterm_flash assert_includes output, indexterm_html5 end context 'Button macro' do test 'btn macro' do para = block_from_string('btn:[Save]', attributes: { 'experimental' => '' }) assert_equal %q{Save}, para.sub_macros(para.source) end test 'btn macro that spans multiple lines' do para = block_from_string(%(btn:[Rebase and\nmerge]), attributes: { 'experimental' => '' }) assert_equal %q{Rebase and merge}, para.sub_macros(para.source) end test 'btn macro for docbook backend' do para = block_from_string('btn:[Save]', backend: 'docbook', attributes: { 'experimental' => '' }) assert_equal %q{Save}, para.sub_macros(para.source) end end context 'Keyboard macro' do test 'kbd macro with single key' do para = block_from_string('kbd:[F3]', attributes: { 'experimental' => '' }) assert_equal %q{F3}, para.sub_macros(para.source) end test 'kbd macro with single backslash key' do para = block_from_string("kbd:[#{BACKSLASH} ]", attributes: { 'experimental' => '' }) assert_equal %q(\), para.sub_macros(para.source) end test 'kbd macro with single key, docbook backend' do para = block_from_string('kbd:[F3]', backend: 'docbook', attributes: { 'experimental' => '' }) assert_equal %q{F3}, para.sub_macros(para.source) end test 'kbd macro with key combination' do para = block_from_string('kbd:[Ctrl+Shift+T]', attributes: { 'experimental' => '' }) assert_equal %q{Ctrl+Shift+T}, para.sub_macros(para.source) end test 'kbd macro with key combination that spans multiple lines' do para = block_from_string(%(kbd:[Ctrl +\nT]), attributes: { 'experimental' => '' }) assert_equal %q{Ctrl+T}, para.sub_macros(para.source) end test 'kbd macro with key combination, docbook backend' do para = block_from_string('kbd:[Ctrl+Shift+T]', backend: 'docbook', attributes: { 'experimental' => '' }) assert_equal %q{CtrlShiftT}, para.sub_macros(para.source) end test 'kbd macro with key combination delimited by pluses with spaces' do para = block_from_string('kbd:[Ctrl + Shift + T]', attributes: { 'experimental' => '' }) assert_equal %q{Ctrl+Shift+T}, para.sub_macros(para.source) end test 'kbd macro with key combination delimited by commas' do para = block_from_string('kbd:[Ctrl,Shift,T]', attributes: { 'experimental' => '' }) assert_equal %q{Ctrl+Shift+T}, para.sub_macros(para.source) end test 'kbd macro with key combination delimited by commas with spaces' do para = block_from_string('kbd:[Ctrl, Shift, T]', attributes: { 'experimental' => '' }) assert_equal %q{Ctrl+Shift+T}, para.sub_macros(para.source) end test 'kbd macro with key combination delimited by plus containing a comma key' do para = block_from_string('kbd:[Ctrl+,]', attributes: { 'experimental' => '' }) assert_equal %q{Ctrl+,}, para.sub_macros(para.source) end test 'kbd macro with key combination delimited by commas containing a plus key' do para = block_from_string('kbd:[Ctrl, +, Shift]', attributes: { 'experimental' => '' }) assert_equal %q{Ctrl+++Shift}, para.sub_macros(para.source) end test 'kbd macro with key combination where last key matches plus delimiter' do para = block_from_string('kbd:[Ctrl + +]', attributes: { 'experimental' => '' }) assert_equal %q{Ctrl++}, para.sub_macros(para.source) end test 'kbd macro with key combination where last key matches comma delimiter' do para = block_from_string('kbd:[Ctrl, ,]', attributes: { 'experimental' => '' }) assert_equal %q{Ctrl+,}, para.sub_macros(para.source) end test 'kbd macro with key combination containing escaped bracket' do para = block_from_string('kbd:[Ctrl + \]]', attributes: { 'experimental' => '' }) assert_equal %q{Ctrl+]}, para.sub_macros(para.source) end test 'kbd macro with key combination ending in backslash' do para = block_from_string("kbd:[Ctrl + #{BACKSLASH} ]", attributes: { 'experimental' => '' }) assert_equal %q(Ctrl+\\), para.sub_macros(para.source) end test 'kbd macro looks for delimiter beyond first character' do para = block_from_string('kbd:[,te]', attributes: { 'experimental' => '' }) assert_equal %q(,te), para.sub_macros(para.source) end test 'kbd macro restores trailing delimiter as key value' do para = block_from_string('kbd:[te,]', attributes: { 'experimental' => '' }) assert_equal %q(te,), para.sub_macros(para.source) end end context 'Menu macro' do test 'should process menu using macro sytnax' do para = block_from_string('menu:File[]', attributes: { 'experimental' => '' }) assert_equal %q{File}, para.sub_macros(para.source) end test 'should process menu for docbook backend' do para = block_from_string('menu:File[]', backend: 'docbook', attributes: { 'experimental' => '' }) assert_equal %q{File}, para.sub_macros(para.source) end test 'should process multiple menu macros in same line' do para = block_from_string('menu:File[] and menu:Edit[]', attributes: { 'experimental' => '' }) assert_equal 'File and Edit', para.sub_macros(para.source) end test 'should process menu with menu item using macro syntax' do para = block_from_string('menu:File[Save As…]', attributes: { 'experimental' => '' }) assert_equal %q{File  Save As…}, para.sub_macros(para.source) end test 'should process menu macro that spans multiple lines' do input = %(menu:Preferences[Compile\non\nSave]) para = block_from_string input, attributes: { 'experimental' => '' } assert_equal %(Preferences  Compile\non\nSave), para.sub_macros(para.source) end test 'should unescape escaped closing bracket in menu macro' do input = 'menu:Preferences[Compile [on\\] Save]' para = block_from_string input, attributes: { 'experimental' => '' } assert_equal %q(Preferences  Compile [on] Save), para.sub_macros(para.source) end test 'should process menu with menu item using macro syntax when fonts icons are enabled' do para = block_from_string('menu:Tools[More Tools > Extensions]', attributes: { 'experimental' => '', 'icons' => 'font' }) assert_equal %q{Tools  More Tools  Extensions}, para.sub_macros(para.source) end test 'should process menu with menu item for docbook backend' do para = block_from_string('menu:File[Save As…]', backend: 'docbook', attributes: { 'experimental' => '' }) assert_equal %q{File Save As…}, para.sub_macros(para.source) end test 'should process menu with menu item in submenu using macro syntax' do para = block_from_string('menu:Tools[Project > Build]', attributes: { 'experimental' => '' }) assert_equal %q{Tools  Project  Build}, para.sub_macros(para.source) end test 'should process menu with menu item in submenu for docbook backend' do para = block_from_string('menu:Tools[Project > Build]', backend: 'docbook', attributes: { 'experimental' => '' }) assert_equal %q{Tools Project Build}, para.sub_macros(para.source) end test 'should process menu with menu item in submenu using macro syntax and comma delimiter' do para = block_from_string('menu:Tools[Project, Build]', attributes: { 'experimental' => '' }) assert_equal %q{Tools  Project  Build}, para.sub_macros(para.source) end test 'should process menu with menu item using inline syntax' do para = block_from_string('"File > Save As…"', attributes: { 'experimental' => '' }) assert_equal %q{File  Save As…}, para.sub_macros(para.source) end test 'should process menu with menu item in submenu using inline syntax' do para = block_from_string('"Tools > Project > Build"', attributes: { 'experimental' => '' }) assert_equal %q{Tools  Project  Build}, para.sub_macros(para.source) end test 'inline menu syntax should not match closing quote of XML attribute' do para = block_from_string('<node>r', attributes: { 'experimental' => '' }) assert_equal %q{<node>r}, para.sub_macros(para.source) end test 'should process menu macro with items containing multibyte characters' do para = block_from_string('menu:视图[放大, 重置]', attributes: { 'experimental' => '' }) assert_equal %q{视图  放大  重置}, para.sub_macros(para.source) end test 'should process inline menu with items containing multibyte characters' do para = block_from_string('"视图 > 放大 > 重置"', attributes: { 'experimental' => '' }) assert_equal %q{视图  放大  重置}, para.sub_macros(para.source) end test 'should process a menu macro with a target that begins with a character reference' do para = block_from_string('menu:⋮[More Tools, Extensions]', attributes: { 'experimental' => '' }) assert_equal %q{  More Tools  Extensions}, para.sub_macros(para.source) end test 'should not process a menu macro with a target that ends with a space' do input = 'menu:foo [bar] menu:File[Save]' para = block_from_string input, attributes: { 'experimental' => '' } result = para.sub_macros para.source assert_xpath '/span[@class="menuseq"]', result, 1 assert_xpath '//b[@class="menu"][text()="File"]', result, 1 end test 'should process an inline menu that begins with a character reference' do para = block_from_string('"⋮ > More Tools > Extensions"', attributes: { 'experimental' => '' }) assert_equal %q{  More Tools  Extensions}, para.sub_macros(para.source) end end end context 'Passthroughs' do test 'collect inline triple plus passthroughs' do para = block_from_string('+++inline code+++') result = para.extract_passthroughs(para.source) passthroughs = para.instance_variable_get :@passthroughs assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, passthroughs.size assert_equal 'inline code', passthroughs[0][:text] assert_empty passthroughs[0][:subs] end test 'collect multi-line inline triple plus passthroughs' do para = block_from_string("+++inline\ncode+++") result = para.extract_passthroughs(para.source) passthroughs = para.instance_variable_get :@passthroughs assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, passthroughs.size assert_equal "inline\ncode", passthroughs[0][:text] assert_empty passthroughs[0][:subs] end test 'collect inline double dollar passthroughs' do para = block_from_string('$${code}$$') result = para.extract_passthroughs(para.source) passthroughs = para.instance_variable_get :@passthroughs assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, passthroughs.size assert_equal '{code}', passthroughs[0][:text] assert_equal [:specialcharacters], passthroughs[0][:subs] end test 'collect inline double plus passthroughs' do para = block_from_string('++{code}++') result = para.extract_passthroughs(para.source) passthroughs = para.instance_variable_get :@passthroughs assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, passthroughs.size assert_equal '{code}', passthroughs[0][:text] assert_equal [:specialcharacters], passthroughs[0][:subs] end test 'should not crash if role on passthrough is enclosed in quotes' do %W( ['role']#{BACKSLASH}++This++++++++++++ ['role']#{BACKSLASH}+++++++++This++++++++++++ ).each do |input| para = block_from_string input assert_includes para.content, %() end end test 'should allow inline double plus passthrough to be escaped using backslash' do para = block_from_string("you need to replace `int a = n#{BACKSLASH}++;` with `int a = ++n;`!") result = para.apply_subs para.source assert_equal 'you need to replace int a = n++; with int a = ++n;!', result end test 'should allow inline double plus passthrough with attributes to be escaped using backslash' do para = block_from_string("=[attrs]#{BACKSLASH}#{BACKSLASH}++text++") result = para.apply_subs para.source assert_equal '=[attrs]++text++', result end test 'collect multi-line inline double dollar passthroughs' do para = block_from_string("$$\n{code}\n$$") result = para.extract_passthroughs(para.source) passthroughs = para.instance_variable_get :@passthroughs assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, passthroughs.size assert_equal "\n{code}\n", passthroughs[0][:text] assert_equal [:specialcharacters], passthroughs[0][:subs] end test 'collect multi-line inline double plus passthroughs' do para = block_from_string("++\n{code}\n++") result = para.extract_passthroughs(para.source) passthroughs = para.instance_variable_get :@passthroughs assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, passthroughs.size assert_equal "\n{code}\n", passthroughs[0][:text] assert_equal [:specialcharacters], passthroughs[0][:subs] end test 'collect passthroughs from inline pass macro' do para = block_from_string(%Q{pass:specialcharacters,quotes[['code'\\]]}) result = para.extract_passthroughs(para.source) passthroughs = para.instance_variable_get :@passthroughs assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, passthroughs.size assert_equal %q{['code']}, passthroughs[0][:text] assert_equal [:specialcharacters, :quotes], passthroughs[0][:subs] end test 'collect multi-line passthroughs from inline pass macro' do para = block_from_string(%Q{pass:specialcharacters,quotes[['more\ncode'\\]]}) result = para.extract_passthroughs(para.source) passthroughs = para.instance_variable_get :@passthroughs assert_equal Asciidoctor::Substitutors::PASS_START + '0' + Asciidoctor::Substitutors::PASS_END, result assert_equal 1, passthroughs.size assert_equal %Q{['more\ncode']}, passthroughs[0][:text] assert_equal [:specialcharacters, :quotes], passthroughs[0][:subs] end test 'should find and replace placeholder duplicated by substitution' do input = %q(+first passthrough+ followed by link:$$http://example.com/__u_no_format_me__$$[] with passthrough) result = convert_inline_string input assert_equal 'first passthrough followed by http://example.com/__u_no_format_me__ with passthrough', result end test 'resolves sub shorthands on inline pass macro' do para = block_from_string 'pass:q,a[*<{backend}>*]' result = para.extract_passthroughs para.source passthroughs = para.instance_variable_get :@passthroughs assert_equal 1, passthroughs.size assert_equal [:quotes, :attributes], passthroughs[0][:subs] result = para.restore_passthroughs result assert_equal '', result end test 'inline pass macro supports incremental subs' do para = block_from_string 'pass:n,-a[<{backend}>]' result = para.extract_passthroughs para.source passthroughs = para.instance_variable_get :@passthroughs assert_equal 1, passthroughs.size result = para.restore_passthroughs result assert_equal '<{backend}>', result end test 'should not recognize pass macro with invalid subsitution list' do [',', '42', 'a,'].each do |subs| para = block_from_string %(pass:#{subs}[foobar]) result = para.extract_passthroughs para.source assert_equal %(pass:#{subs}[foobar]), result end end test 'should allow content of inline pass macro to be empty' do para = block_from_string 'pass:[]' result = para.extract_passthroughs para.source passthroughs = para.instance_variable_get :@passthroughs assert_equal 1, passthroughs.size assert_equal '', para.restore_passthroughs(result) end # NOTE placeholder is surrounded by text to prevent reader from stripping trailing boundary char (unique to test scenario) test 'restore inline passthroughs without subs' do para = block_from_string("some #{Asciidoctor::Substitutors::PASS_START}" + '0' + "#{Asciidoctor::Substitutors::PASS_END} to study") para.extract_passthroughs '' passthroughs = para.instance_variable_get :@passthroughs passthroughs[0] = { text: 'inline code', subs: [] } result = para.restore_passthroughs(para.source) assert_equal "some inline code to study", result end # NOTE placeholder is surrounded by text to prevent reader from stripping trailing boundary char (unique to test scenario) test 'restore inline passthroughs with subs' do para = block_from_string("some #{Asciidoctor::Substitutors::PASS_START}" + '0' + "#{Asciidoctor::Substitutors::PASS_END} to study in the #{Asciidoctor::Substitutors::PASS_START}" + '1' + "#{Asciidoctor::Substitutors::PASS_END} programming language") para.extract_passthroughs '' passthroughs = para.instance_variable_get :@passthroughs passthroughs[0] = { text: '{code}', subs: [:specialcharacters] } passthroughs[1] = { text: '{language}', subs: [:specialcharacters] } result = para.restore_passthroughs(para.source) assert_equal 'some <code>{code}</code> to study in the {language} programming language', result end test 'should restore nested passthroughs' do result = convert_inline_string %q(+Sometimes you feel pass:q[`mono`].+ Sometimes you +$$don't$$+.) assert_equal %q(Sometimes you feel mono. Sometimes you don't.), result end test 'should not fail to restore remaining passthroughs after processing inline passthrough with macro substitution' do input = 'pass:m[.] pass:[.]' assert_equal '. .', (convert_inline_string input) end test 'should honor role on double plus passthrough' do result = convert_inline_string 'Print the version using [var]++{asciidoctor-version}++.' assert_equal 'Print the version using {asciidoctor-version}.', result end test 'complex inline passthrough macro' do text_to_escape = %q{[(] <'basic form'> <'logical operator'> <'basic form'> [)]} para = block_from_string %($$#{text_to_escape}$$) para.extract_passthroughs(para.source) passthroughs = para.instance_variable_get :@passthroughs assert_equal 1, passthroughs.size assert_equal text_to_escape, passthroughs[0][:text] text_to_escape_escaped = %q{[(\] <'basic form'> <'logical operator'> <'basic form'> [)\]} para = block_from_string %(pass:specialcharacters[#{text_to_escape_escaped}]) para.extract_passthroughs(para.source) passthroughs = para.instance_variable_get :@passthroughs assert_equal 1, passthroughs.size assert_equal text_to_escape, passthroughs[0][:text] end test 'inline pass macro with a composite sub' do para = block_from_string %(pass:verbatim[<{backend}>]) assert_equal '<{backend}>', para.content end context 'Math macros' do test 'should passthrough text in asciimath macro and surround with AsciiMath delimiters' do input = 'asciimath:[x/x={(1,if x!=0),(text{undefined},if x=0):}]' para = block_from_string input assert_equal '\$x/x={(1,if x!=0),(text{undefined},if x=0):}\$', para.content end test 'should not recognize asciimath macro with no content' do input = 'asciimath:[]' para = block_from_string input assert_equal 'asciimath:[]', para.content end test 'should perform specialcharacters subs on asciimath macro content in html backend by default' do input = 'asciimath:[a < b]' para = block_from_string input assert_equal '\$a < b\$', para.content end test 'should convert contents of asciimath macro to MathML in DocBook output if asciimath gem is available' do asciimath_available = !(Asciidoctor::Helpers.require_library 'asciimath', true, :ignore).nil? input = 'asciimath:[a < b]' expected = 'a<b' using_memory_logger do |logger| para = block_from_string input, backend: :docbook actual = para.content if asciimath_available assert_equal expected, actual assert_equal :loaded, para.document.converter.instance_variable_get(:@asciimath_status) else assert_message logger, :WARN, 'optional gem \'asciimath\' is not available. Functionality disabled.' assert_equal :unavailable, para.document.converter.instance_variable_get(:@asciimath_status) end end end test 'should not perform specialcharacters subs on asciimath macro content in Docbook output if asciimath gem not available' do asciimath_available = !(Asciidoctor::Helpers.require_library 'asciimath', true, :ignore).nil? input = 'asciimath:[a < b]' para = block_from_string input, backend: :docbook para.document.converter.instance_variable_set :@asciimath_status, :unavailable if asciimath_available old_asciimath = ::AsciiMath Object.send :remove_const, 'AsciiMath' end assert_equal '', para.content ::AsciiMath = old_asciimath if asciimath_available end test 'should honor explicit subslist on asciimath macro' do input = 'asciimath:attributes[{expr}]' para = block_from_string input, attributes: { 'expr' => 'x != 0' } assert_equal '\$x != 0\$', para.content end test 'should passthrough text in latexmath macro and surround with LaTeX math delimiters' do input = 'latexmath:[C = \alpha + \beta Y^{\gamma} + \epsilon]' para = block_from_string input assert_equal '\(C = \alpha + \beta Y^{\gamma} + \epsilon\)', para.content end test 'should strip legacy LaTeX math delimiters around latexmath content if present' do input = 'latexmath:[$C = \alpha + \beta Y^{\gamma} + \epsilon$]' para = block_from_string input assert_equal '\(C = \alpha + \beta Y^{\gamma} + \epsilon\)', para.content end test 'should not recognize latexmath macro with no content' do input = 'latexmath:[]' para = block_from_string input assert_equal 'latexmath:[]', para.content end test 'should unescape escaped square bracket in equation' do input = 'latexmath:[\sqrt[3\]{x}]' para = block_from_string input assert_equal '\(\sqrt[3]{x}\)', para.content end test 'should perform specialcharacters subs on latexmath macro in html backend by default' do input = 'latexmath:[a < b]' para = block_from_string input assert_equal '\(a < b\)', para.content end test 'should not perform specialcharacters subs on latexmath macro content in docbook backend by default' do input = 'latexmath:[a < b]' para = block_from_string input, backend: :docbook assert_equal '', para.content end test 'should honor explicit subslist on latexmath macro' do input = 'latexmath:attributes[{expr}]' para = block_from_string input, attributes: { 'expr' => '\sqrt{4} = 2' } assert_equal '\(\sqrt{4} = 2\)', para.content end test 'should passthrough math macro inside another passthrough' do input = 'the text `asciimath:[x = y]` should be passed through as +literal+ text' para = block_from_string input, attributes: { 'compat-mode' => '' } assert_equal 'the text asciimath:[x = y] should be passed through as literal text', para.content input = 'the text [x-]`asciimath:[x = y]` should be passed through as `literal` text' para = block_from_string input assert_equal 'the text asciimath:[x = y] should be passed through as literal text', para.content input = 'the text `+asciimath:[x = y]+` should be passed through as `literal` text' para = block_from_string input assert_equal 'the text asciimath:[x = y] should be passed through as literal text', para.content end test 'should support attrlist on a literal monospace phrase' do input = '[.baz]`+foo--bar+`' para = block_from_string input assert_equal 'foo--bar', para.content end test 'should not process an escaped passthrough macro inside a monospaced phrase' do input = 'use the `\pass:c[]` macro' para = block_from_string input assert_equal 'use the pass:c[] macro', para.content end test 'should not process an escaped passthrough macro inside a monospaced phrase with attributes' do input = 'use the [syntax]`\pass:c[]` macro' para = block_from_string input assert_equal 'use the pass:c[] macro', para.content end test 'should honor an escaped single plus passthrough inside a monospaced phrase' do input = 'use `\+{author}+` to show an attribute reference' para = block_from_string input assert_equal 'use +{author}+ to show an attribute reference', para.content end test 'should not recognize stem macro with no content' do input = 'stem:[]' para = block_from_string input assert_equal input, para.content end test 'should passthrough text in stem macro and surround with AsciiMath delimiters if stem attribute is asciimath, empty, or not set' do [ {}, { 'stem' => '' }, { 'stem' => 'asciimath' }, { 'stem' => 'bogus' }, ].each do |attributes| input = 'stem:[x/x={(1,if x!=0),(text{undefined},if x=0):}]' para = block_from_string input, attributes: attributes assert_equal '\$x/x={(1,if x!=0),(text{undefined},if x=0):}\$', para.content end end test 'should passthrough text in stem macro and surround with LaTeX math delimiters if stem attribute is latexmath, latex, or tex' do [ { 'stem' => 'latexmath' }, { 'stem' => 'latex' }, { 'stem' => 'tex' }, ].each do |attributes| input = 'stem:[C = \alpha + \beta Y^{\gamma} + \epsilon]' para = block_from_string input, attributes: attributes assert_equal '\(C = \alpha + \beta Y^{\gamma} + \epsilon\)', para.content end end test 'should apply substitutions specified on stem macro' do ['stem:c,a[sqrt(x) <=> {solve-for-x}]', 'stem:n,-r[sqrt(x) <=> {solve-for-x}]'].each do |input| para = block_from_string input, attributes: { 'stem' => 'asciimath', 'solve-for-x' => '13' } assert_equal '\$sqrt(x) <=> 13\$', para.content end end test 'should not recognize stem macro with invalid substitution list' do [',', '42', 'a,'].each do |subs| input = %(stem:#{subs}[x^2]) para = block_from_string input, attributes: { 'stem' => 'asciimath' } assert_equal %(stem:#{subs}[x^2]), para.content end end end end context 'Replacements' do test 'unescapes XML entities' do para = block_from_string '< " ∴ " " >' assert_equal '< " ∴ " " >', para.apply_subs(para.source) end test 'replaces arrows' do para = block_from_string '<- -> <= => \<- \-> \<= \=>' assert_equal '← → ⇐ ⇒ <- -> <= =>', para.apply_subs(para.source) end test 'replaces dashes' do input = <<~'EOS' -- foo foo--bar foo\--bar foo -- bar foo \-- bar stuff in between -- foo stuff in between foo -- stuff in between foo -- EOS expected = <<~'EOS'.chop  — foo foo—​bar foo--bar foo — bar foo -- bar stuff in between — foo stuff in between foo — stuff in between foo —  EOS para = block_from_string input assert_equal expected, para.sub_replacements(para.source) end test 'replaces dashes between multibyte word characters' do para = block_from_string %(富--巴) expected = '富—​巴' assert_equal expected, para.sub_replacements(para.source) end test 'replaces marks' do para = block_from_string '(C) (R) (TM) \(C) \(R) \(TM)' assert_equal '© ® ™ (C) (R) (TM)', para.sub_replacements(para.source) end test 'preserves entity references' do input = '& © ✔ 😀 • 😀' result = convert_inline_string input assert_equal input, result end test 'only preserves named entities with two or more letters' do input = '& &a; >' result = convert_inline_string input assert_equal '& &a; >', result end test 'replaces punctuation' do para = block_from_string %(John's Hideout is the Whites`' place... foo\\'bar) assert_equal "John’s Hideout is the Whites’ place…​ foo'bar", para.sub_replacements(para.source) end test 'should replace right single quote marks' do given = [ %(`'Twas the night), %(a `'57 Chevy!), %(the whites`' place), %(the whites`'.), %(the whites`'--where the wild things are), %(the whites`'\nhave), %(It's Mary`'s little lamb.), %(consecutive single quotes '' are not modified), %(he is 6' tall), %(\\`') ] expected = [ %(’Twas the night), %(a ’57 Chevy!), %(the whites’ place), %(the whites’.), %(the whites’--where the wild things are), %(the whites’\nhave), %(It’s Mary’s little lamb.), %(consecutive single quotes '' are not modified), %(he is 6' tall), %(`') ] given.size.times do |i| para = block_from_string given[i] assert_equal expected[i], para.sub_replacements(para.source) end end end context 'Post replacements' do test 'line break inserted after line with line break character' do para = block_from_string("First line +\nSecond line") result = para.apply_subs para.lines, (para.expand_subs :post_replacements) assert_equal 'First line
', result.first end test 'line break inserted after line wrap with hardbreaks enabled' do para = block_from_string("First line\nSecond line", attributes: { 'hardbreaks' => '' }) result = para.apply_subs para.lines, (para.expand_subs :post_replacements) assert_equal 'First line
', result.first end test 'line break character stripped from end of line with hardbreaks enabled' do para = block_from_string("First line +\nSecond line", attributes: { 'hardbreaks' => '' }) result = para.apply_subs para.lines, (para.expand_subs :post_replacements) assert_equal 'First line
', result.first end test 'line break not inserted for single line with hardbreaks enabled' do para = block_from_string('First line', attributes: { 'hardbreaks' => '' }) result = para.apply_subs para.lines, (para.expand_subs :post_replacements) assert_equal 'First line', result.first end end context 'Resolve subs' do test 'should resolve subs for block' do doc = empty_document parse: true block = Asciidoctor::Block.new doc, :paragraph block.attributes['subs'] = 'quotes,normal' block.commit_subs assert_equal [:quotes, :specialcharacters, :attributes, :replacements, :macros, :post_replacements], block.subs end test 'should resolve specialcharacters sub as highlight for source block when source highlighter is coderay' do doc = empty_document attributes: { 'source-highlighter' => 'coderay' }, parse: true block = Asciidoctor::Block.new doc, :listing, content_model: :verbatim block.style = 'source' block.attributes['subs'] = 'specialcharacters' block.attributes['language'] = 'ruby' block.commit_subs assert_equal [:highlight], block.subs end test 'should resolve specialcharacters sub as highlight for source block when source highlighter is pygments' do doc = empty_document attributes: { 'source-highlighter' => 'pygments' }, parse: true block = Asciidoctor::Block.new doc, :listing, content_model: :verbatim block.style = 'source' block.attributes['subs'] = 'specialcharacters' block.attributes['language'] = 'ruby' block.commit_subs assert_equal [:highlight], block.subs end if ENV['PYGMENTS'] test 'should not replace specialcharacters sub with highlight for source block when source highlighter is not set' do doc = empty_document parse: true block = Asciidoctor::Block.new doc, :listing, content_model: :verbatim block.style = 'source' block.attributes['subs'] = 'specialcharacters' block.attributes['language'] = 'ruby' block.commit_subs assert_equal [:specialcharacters], block.subs end test 'should not use subs if subs option passed to block constructor is nil' do doc = empty_document parse: true block = Asciidoctor::Block.new doc, :paragraph, source: '*bold* _italic_', subs: nil, attributes: { 'subs' => 'quotes' } assert_empty block.subs block.commit_subs assert_empty block.subs end test 'should not use subs if subs option passed to block constructor is empty array' do doc = empty_document parse: true block = Asciidoctor::Block.new doc, :paragraph, source: '*bold* _italic_', subs: [], attributes: { 'subs' => 'quotes' } assert_empty block.subs block.commit_subs assert_empty block.subs end test 'should use subs from subs option passed to block constructor' do doc = empty_document parse: true block = Asciidoctor::Block.new doc, :paragraph, source: '*bold* _italic_', subs: [:specialcharacters], attributes: { 'subs' => 'quotes' } assert_equal [:specialcharacters], block.subs block.commit_subs assert_equal [:specialcharacters], block.subs end test 'should use subs from subs attribute if subs option is not passed to block constructor' do doc = empty_document parse: true block = Asciidoctor::Block.new doc, :paragraph, source: '*bold* _italic_', attributes: { 'subs' => 'quotes' } assert_empty block.subs # in this case, we have to call commit_subs to resolve the subs block.commit_subs assert_equal [:quotes], block.subs end test 'should use subs from subs attribute if subs option passed to block constructor is :default' do doc = empty_document parse: true block = Asciidoctor::Block.new doc, :paragraph, source: '*bold* _italic_', subs: :default, attributes: { 'subs' => 'quotes' } assert_equal [:quotes], block.subs block.commit_subs assert_equal [:quotes], block.subs end test 'should use built-in subs if subs option passed to block constructor is :default and subs attribute is absent' do doc = empty_document parse: true block = Asciidoctor::Block.new doc, :paragraph, source: '*bold* _italic_', subs: :default assert_equal [:specialcharacters, :quotes, :attributes, :replacements, :macros, :post_replacements], block.subs block.commit_subs assert_equal [:specialcharacters, :quotes, :attributes, :replacements, :macros, :post_replacements], block.subs end end end