# frozen_string_literal: true
require File.dirname(__FILE__) + "/shared_signature_examples"

RSpec.describe YARD::Templates::Helpers::HtmlHelper do
  include YARD::Templates::Helpers::BaseHelper
  include YARD::Templates::Helpers::HtmlHelper
  include YARD::Templates::Helpers::MethodHelper

  def options
    Templates::TemplateOptions.new.tap do |o|
      o.reset_defaults
      o.default_return = nil
    end
  end

  describe "#h" do
    it "uses #h to escape HTML" do
      expect(h('Usage: foo "bar" <baz>')).to eq "Usage: foo &quot;bar&quot; &lt;baz&gt;"
    end
  end

  describe "#charset" do
    after { Encoding.default_external = 'utf-8' if defined?(Encoding) }

    it "returns foo if LANG=foo" do
      expect(ENV).to receive(:[]).with('LANG').and_return('shift_jis') if YARD.ruby18?
      Encoding.default_external = 'shift_jis' if defined?(Encoding)
      expect(charset).to eq 'shift_jis'
    end

    ['US-ASCII', 'binary', 'ASCII-8BIT'].each do |type|
      it "converts #{type} to iso-8859-1" do
        expect(ENV).to receive(:[]).with('LANG').and_return(type) if YARD.ruby18?
        Encoding.default_external = type if defined?(Encoding)
        expect(charset).to eq 'iso-8859-1'
      end
    end

    it "takes file encoding if there is a file" do
      @file = OpenStruct.new(:contents => String.new('foo').force_encoding('sjis'))
      # not the correct charset name, but good enough
      expect(['Shift_JIS', 'Windows-31J']).to include(charset)
    end if YARD.ruby19?

    it "takes file encoding if there is a file" do
      allow(ENV).to receive(:[]).with('LANG').and_return('utf-8') if YARD.ruby18?
      @file = OpenStruct.new(:contents => 'foo')
      expect(charset).to eq 'utf-8'
    end if YARD.ruby18?

    if YARD.ruby18?
      it "returns utf-8 if no LANG env is set" do
        expect(ENV).to receive(:[]).with('LANG').and_return(nil)
        expect(charset).to eq 'utf-8'
      end

      it "only returns charset part of lang" do
        expect(ENV).to receive(:[]).with('LANG').and_return('en_US.UTF-8')
        expect(charset).to eq 'utf-8'
      end
    end
  end

  describe "#format_types" do
    it "includes brackets by default" do
      text = ["String"]
      expect(self).to receive(:linkify).at_least(1).times.with("String", "String").and_return("String")
      expect(format_types(text)).to eq format_types(text, true)
      expect(format_types(text)).to eq "(<tt>String</tt>)"
    end

    it "avoids brackets if brackets=false" do
      expect(self).to receive(:linkify).with("String", "String").and_return("String")
      expect(self).to receive(:linkify).with("Symbol", "Symbol").and_return("Symbol")
      expect(format_types(["String", "Symbol"], false)).to eq "<tt>String</tt>, <tt>Symbol</tt>"
    end

    {"String" => [["String"],
        "<tt><a href=''>String</a></tt>"],
      "A::B::C" => [["A::B::C"],
        "<tt><a href=''>A::B::C</a></tt>"],
      "Array<String>" => [["Array", "String"],
        "<tt><a href=''>Array</a>&lt;<a href=''>String</a>&gt;</tt>"],
      "Array<String, Symbol>" => [["Array", "String", "Symbol"],
        "<tt><a href=''>Array</a>&lt;<a href=''>String</a>, <a href=''>Symbol</a>&gt;</tt>"],
      "Array<{String => Array<Symbol>}>" => [["Array", "String", "Array", "Symbol"],
        "<tt><a href=''>Array</a>&lt;{<a href=''>String</a> =&gt; " \
          "<a href=''>Array</a>&lt;<a href=''>Symbol</a>&gt;}&gt;</tt>"]}.each do |text, values|
      it "links all classes in #{text}" do
        if text.count('<') > 0
          expect(self).to receive(:h).with('<').at_least(text.count('<')).times.and_return("&lt;")
        end
        if text.count('>') > 0
          expect(self).to receive(:h).with('>').at_least(text.count('>')).times.and_return("&gt;")
        end
        values[0].each {|v| expect(self).to receive(:linkify).with(v, v).and_return("<a href=''>#{v}</a>") }
        expect(format_types([text], false)).to eq values[1]
      end
    end
  end

  describe "#htmlify" do
    it "handles various encodings" do
      allow(self).to receive(:object).and_return(Registry.root)
      text = String.new("\xB0\xB1")
      if defined?(Encoding)
        utf8 = Encoding.find('utf-8')

        Encoding.default_internal = utf8 unless Encoding.default_internal == utf8
        text = text.force_encoding('binary')
      end
      htmlify(text, :text)
      # TODO: add more encoding tests
    end

    it "returns pre-formatted text with :pre markup" do
      expect(htmlify("fo\no\n\nbar<>", :pre)).to eq "<pre>fo\no\n\nbar&lt;&gt;</pre>"
    end

    it "returns regular text with :text markup" do
      expect(htmlify("fo\no\n\nbar<>", :text)).to eq "fo<br>o<br><br>bar&lt;&gt;"
    end

    it "returns unmodified text with :none markup" do
      expect(htmlify("fo\no\n\nbar<>", :none)).to eq "fo\no\n\nbar&lt;&gt;"
    end

    it "highlights ruby if markup is :ruby" do
      expect(htmlify("class Foo; end", :ruby)).to match(/\A<pre class="code ruby"><span/)
    end

    it "includes file and htmlifies it" do
      load_markup_provider(:rdoc)
      expect(File).to receive(:file?).with('foo.rdoc').and_return(true)
      expect(File).to receive(:read).with('foo.rdoc').and_return('HI')
      expect(htmlify("{include:file:foo.rdoc}", :rdoc).gsub(/\s+/, '')).to eq "<p>HI</p>"
    end

    it "allows inline includes for {include:} in the middle of a line" do
      load_markup_provider(:rdoc)
      expect(File).to receive(:file?).with('foo.rdoc').and_return(true)
      expect(File).to receive(:read).with('foo.rdoc').and_return('HI')
      expect(htmlify("test {include:file:foo.rdoc}", :rdoc).gsub(/[\r?\n]+/, '')).to eq '<p>test HI</p>'
    end

    it "does not autolink URLs inside of {} (markdown specific)" do
      log.enter_level(Logger::FATAL) do
        pending 'This test depends on markdown' unless markup_class(:markdown)
      end
      expect(htmlify('{https://example.com Title}', :markdown).chomp).to match(
        %r{<p><a href="https://example.com".*>Title</a></p>}
      )
      expect(htmlify('{https://example.com}', :markdown).chomp).to match(
        %r{<p><a href="https://example.com".*>https://example.com</a></p>}
      )
    end

    it "creates tables (markdown specific)" do
      markdown = <<-EOF.strip
        City    | State | Country
        --------|-------|--------
        Raleigh | NC    | US
        Seattle | WA    | US
      EOF

      html = htmlify(markdown, :markdown)
      expect(html).to match(/<table>/)
      expect(html).to match %r{<th>City</th>}
      expect(html).to match %r{<td>NC</td>}
    end

    it "handles fenced code blocks" do
      markdown = "Introduction:\n```ruby\nputs\n\nputs\n```"
      html = htmlify(markdown, :markdown)
      expect(html).to match %r{^<p>Introduction:</p>.*<code class="ruby">}m
    end

    it "sets env and env-yard attributes (AsciiDoc specific)" do
      skip "Missing asciidoctor gem" if markup_class(:asciidoc).nil?

      adoc = <<-EOF.strip.gsub(/^ +/, "") # strip and unindent
        ifdef::env[]
        Attribute "env" is set, and its value is "{env}".
        endif::[]

        ifdef::env-yard[]
        Attribute "env-yard" is set, and its value is "{env-yard}".
        endif::[]
      EOF
      html = htmlify(adoc, :asciidoc)
      expect(html).to match(/"env" is set, and its value is "yard"./)
      expect(html).to match(/"env-yard" is set, and its value is ""./)
    end

    it "should not include the document title from the AsciiDoc header" do
      skip "Missing asciidoctor gem" if markup_class(:asciidoc).nil?

      adoc = <<-EOF.strip.gsub(/^ +/, "") # strip and unindent
        = Project Name

        Introduction.

        == Installation

        Installation instructions.

        == Usage

        Usage instructions.
      EOF
      html = htmlify(adoc, :asciidoc)
      expect(html).to_not match(/Project Name/)
      expect(html).to include(%(<h2 id="_installation">Installation</h2>))
    end
  end

  describe "#link_object" do
    let(:object) { CodeObjects::NamespaceObject.new(nil, :YARD) }

    it "returns the object path if there's no serializer and no title" do
      allow(self).to receive(:serializer).and_return(nil)
      expect(link_object(CodeObjects::NamespaceObject.new(nil, :YARD))).to eq "YARD"
    end

    it "returns the title if there's a title but no serializer" do
      allow(self).to receive(:serializer).and_return(nil)
      expect(link_object(CodeObjects::NamespaceObject.new(nil, :YARD), 'title')).to eq "title"
    end

    it "links objects from overload tag" do
      YARD.parse_string <<-'eof'
        module Foo
          class Bar; def a; end end
          class Baz
            # @overload a
            def a; end
          end
        end
      eof
      obj = Registry.at('Foo::Baz#a').tag(:overload)
      allow(self).to receive(:serializer).and_return(Serializers::FileSystemSerializer.new)
      allow(self).to receive(:object).and_return(obj)
      expect(link_object("Bar#a")).to match(/href="Bar.html#a-instance_method"/)
    end

    it "uses relative path in title" do
      CodeObjects::ModuleObject.new(:root, :YARD)
      CodeObjects::ClassObject.new(P('YARD'), :Bar)
      allow(self).to receive(:object).and_return(CodeObjects::ModuleObject.new(P('YARD'), :Foo))
      allow(self).to receive(:serializer).and_return(Serializers::FileSystemSerializer.new)
      expect(link_object("Bar")).to match %r{>Bar</a>}
    end

    it "uses #title if overridden" do
      CodeObjects::ModuleObject.new(:root, :YARD)
      CodeObjects::ClassObject.new(P('YARD'), :Bar)
      allow(Registry.at('YARD::Bar')).to receive(:title).and_return('TITLE!')
      allow(self).to receive(:object).and_return(Registry.at('YARD::Bar'))
      allow(self).to receive(:serializer).and_return(Serializers::FileSystemSerializer.new)
      expect(link_object("Bar")).to match %r{>TITLE!</a>}
    end

    it "uses relative path to parent class in title" do
      root = CodeObjects::ModuleObject.new(:root, :YARD)
      obj = CodeObjects::ModuleObject.new(root, :SubModule)
      allow(self).to receive(:object).and_return(obj)
      allow(self).to receive(:serializer).and_return(Serializers::FileSystemSerializer.new)
      expect(link_object("YARD")).to match %r{>YARD</a>}
    end

    it "uses Klass.foo when linking to class method in current namespace" do
      root = CodeObjects::ModuleObject.new(:root, :Klass)
      CodeObjects::MethodObject.new(root, :foo, :class)
      allow(self).to receive(:object).and_return(root)
      allow(self).to receive(:serializer).and_return(Serializers::FileSystemSerializer.new)
      expect(link_object("foo")).to match %r{>Klass.foo</a>}
    end

    it "escapes method name in title" do
      YARD.parse_string <<-'eof'
        class Array
          def &(other)
          end
        end
      eof
      obj = Registry.at('Array#&')
      allow(self).to receive(:serializer).and_return(Serializers::FileSystemSerializer.new)
      allow(self).to receive(:object).and_return(obj)
      expect(link_object("Array#&")).to match(/title="Array#&amp; \(method\)"/)
    end
  end

  describe "#url_for" do
    before { Registry.clear }

    it "returns nil if serializer is nil" do
      allow(self).to receive(:serializer).and_return nil
      allow(self).to receive(:object).and_return Registry.root
      expect(url_for(P("Mod::Class#meth"))).to be nil
    end

    it "returns nil if object is hidden" do
      yard = CodeObjects::ModuleObject.new(:root, :YARD)

      allow(self).to receive(:serializer).and_return(Serializers::FileSystemSerializer.new)
      allow(self).to receive(:object).and_return Registry.root
      allow(self).to receive(:options).and_return OpenStruct.new(:verifier => Verifier.new('false'))

      expect(url_for(yard)).to be nil
    end

    it "returns nil if serializer does not implement #serialized_path" do
      allow(self).to receive(:serializer).and_return Serializers::Base.new
      allow(self).to receive(:object).and_return Registry.root
      expect(url_for(P("Mod::Class#meth"))).to be nil
    end

    it "links to a path/file for a namespace object" do
      allow(self).to receive(:serializer).and_return Serializers::FileSystemSerializer.new
      allow(self).to receive(:object).and_return Registry.root

      yard = CodeObjects::ModuleObject.new(:root, :YARD)
      expect(url_for(yard)).to eq 'YARD.html'
    end

    it "links to the object's namespace path/file and use the object as the anchor" do
      allow(self).to receive(:serializer).and_return Serializers::FileSystemSerializer.new
      allow(self).to receive(:object).and_return Registry.root

      yard = CodeObjects::ModuleObject.new(:root, :YARD)
      meth = CodeObjects::MethodObject.new(yard, :meth)
      expect(url_for(meth)).to eq 'YARD.html#meth-instance_method'
    end

    it "properly urlencodes methods with punctuation in links" do
      obj = CodeObjects::MethodObject.new(nil, :/)
      serializer = double(:serializer, :serialized_path => "file.html")
      allow(self).to receive(:serializer).and_return serializer
      allow(self).to receive(:object).and_return obj
      expect(url_for(obj)).to eq "#%2F-instance_method"
    end
  end

  describe "#anchor_for" do
    it "does not urlencode data when called directly" do
      obj = CodeObjects::MethodObject.new(nil, :/)
      expect(anchor_for(obj)).to eq "/-instance_method"
    end
  end

  describe "#resolve_links" do
    def parse_link(link)
      results = {}
      link =~ %r{<a (.+?)>(.+?)</a>}m
      params = $1
      results[:inner_text] = $2
      params.scan(/\s*(\S+?)=['"](.+?)['"]\s*/).each do |key, value|
        results[key.to_sym] = value.gsub(/^["'](.+)["']$/, '\1')
      end
      results
    end

    it "escapes {} syntax with backslash (\\{foo bar})" do
      input  = '\{foo bar} \{XYZ} \{file:FOO} $\{N-M}'
      output = '{foo bar} {XYZ} {file:FOO} ${N-M}'
      expect(resolve_links(input)).to eq output
    end

    it "escapes {} syntax with ! (!{foo bar})" do
      input  = '!{foo bar} !{XYZ} !{file:FOO} $!{N-M}'
      output = '{foo bar} {XYZ} {file:FOO} ${N-M}'
      expect(resolve_links(input)).to eq output
    end

    it "links static files with file: prefix" do
      allow(self).to receive(:serializer).and_return Serializers::FileSystemSerializer.new
      allow(self).to receive(:object).and_return Registry.root

      expect(parse_link(resolve_links("{file:TEST.txt#abc}"))).to eq(
        :inner_text => "TEST",
        :title => "TEST",
        :href => "file.TEST.html#abc"
      )
      expect(parse_link(resolve_links("{file:TEST.txt title}"))).to eq(
        :inner_text => "title",
        :title => "title",
        :href => "file.TEST.html"
      )
    end

    it "creates regular links with https:// prefix" do
      expect(parse_link(resolve_links("{https://example.com}"))).to eq(
        :inner_text => "https://example.com",
        :target => "_parent",
        :href => "https://example.com",
        :title => "https://example.com"
      )
      expect(parse_link(resolve_links("{https://example.com title}"))).to eq(
        :inner_text => "title",
        :target => "_parent",
        :href => "https://example.com",
        :title => "title"
      )
    end

    it "creates mailto links with mailto: prefixes" do
      expect(parse_link(resolve_links('{mailto:joanna@example.com}'))).to eq(
        :inner_text => 'mailto:joanna@example.com',
        :target => '_parent',
        :href => 'mailto:joanna@example.com',
        :title => 'mailto:joanna@example.com'
      )
      expect(parse_link(resolve_links('{mailto:steve@example.com Steve}'))).to eq(
        :inner_text => 'Steve',
        :target => '_parent',
        :href => 'mailto:steve@example.com',
        :title => 'Steve'
      )
    end

    it "ignores {links} that begin with |...|" do
      expect(resolve_links("{|x|x == 1}")).to eq "{|x|x == 1}"
    end

    it "gracefully ignores {} in links" do
      allow(self).to receive(:linkify).with('Foo', 'Foo').and_return('FOO')
      expect(resolve_links("{} {} {Foo Foo}")).to eq '{} {} FOO'
    end

    %w(tt code pre).each do |tag|
      it "ignores links in <#{tag}>" do
        text = "<#{tag}>{Foo}</#{tag}>"
        expect(resolve_links(text)).to eq text
      end
    end

    it "resolves {Name}" do
      expect(self).to receive(:link_file).with('TEST', nil, nil).and_return('')
      resolve_links("{file:TEST}")
    end

    it "resolves ({Name})" do
      expect(self).to receive(:link_file).with('TEST', nil, nil).and_return('')
      resolve_links("({file:TEST})")
    end

    it "resolves link with newline in title-part" do
      expect(parse_link(resolve_links("{https://example.com foo\nbar}"))).to eq(
        :inner_text => "foo bar",
        :target => "_parent",
        :href => "https://example.com",
        :title => "foo bar"
      )
    end

    it "resolves links to methods whose names have been escaped" do
      expect(self).to receive(:linkify).with('Object#<<', nil).and_return('')
      resolve_links("{Object#&lt;&lt;}")
    end

    it "warns about missing reference at right file location for object" do
      YARD.parse_string <<-eof
        # Comments here
        # And a reference to {InvalidObject}
        class MyObject; end
      eof
      logger = double(:log)
      expect(logger).to receive(:warn).ordered.with(
        "In file `(stdin)':2: Cannot resolve link to InvalidObject from text:\n\t...{InvalidObject}"
      )
      allow(self).to receive(:log).and_return(logger)
      allow(self).to receive(:object).and_return(Registry.at('MyObject'))
      resolve_links(object.docstring)
    end

    it "shows ellipsis on either side if there is more on the line in a reference warning" do
      YARD.parse_string <<-eof
        # {InvalidObject1} beginning of line
        # end of line {InvalidObject2}
        # Middle of {InvalidObject3} line
        # {InvalidObject4}
        class MyObject; end
      eof
      logger = double(:log)
      expect(logger).to receive(:warn).ordered.with("In file `(stdin)':1: Cannot resolve link to InvalidObject1 from text:\n\t{InvalidObject1}...")
      expect(logger).to receive(:warn).ordered.with("In file `(stdin)':2: Cannot resolve link to InvalidObject2 from text:\n\t...{InvalidObject2}")
      expect(logger).to receive(:warn).ordered.with("In file `(stdin)':3: Cannot resolve link to InvalidObject3 from text:\n\t...{InvalidObject3}...")
      expect(logger).to receive(:warn).ordered.with("In file `(stdin)':4: Cannot resolve link to InvalidObject4 from text:\n\t{InvalidObject4}")
      allow(self).to receive(:log).and_return(logger)
      allow(self).to receive(:object).and_return(Registry.at('MyObject'))
      resolve_links(object.docstring)
    end

    it "warns about missing reference for file template (no object)" do
      @file = CodeObjects::ExtraFileObject.new('myfile.txt', '')
      logger = double(:log)
      expect(logger).to receive(:warn).ordered.with("In file `myfile.txt':3: Cannot resolve link to InvalidObject from text:\n\t...{InvalidObject Some Title}")
      allow(self).to receive(:log).and_return(logger)
      allow(self).to receive(:object).and_return(Registry.root)
      resolve_links(<<-eof)
        Hello world
        This is a line
        And {InvalidObject Some Title}
        And more.
      eof
    end

    it "warns if you link a constant namespace as if it were a class/module" do
      @file = CodeObjects::ExtraFileObject.new('myfile.txt', '')
      YARD.parse_string('CONST = 1')

      logger = double(:log)
      allow(self).to receive(:log).and_return(logger)
      expect(logger).to receive(:warn).ordered.with("The namespace of link \"::CONST#meth\" is a constant or invalid.")
      expect(logger).to receive(:warn).ordered.with("In file `myfile.txt':1: Cannot resolve link to ::CONST#meth from text:\n\t{::CONST#meth}")
      expect(resolve_links("{::CONST#meth}")).to eq('::CONST#meth')
    end
  end

  describe "#signature" do
    before do
      arrow = "&#x21d2;"
      @results = {
        :regular => "#<strong>foo</strong> #{arrow} Object",
        :default_return => "#<strong>foo</strong> #{arrow} Hello",
        :no_default_return => "#<strong>foo</strong>",
        :private_class => ".<strong>foo</strong> #{arrow} Object <span class=\"extras\">(private)</span>",
        :single => "#<strong>foo</strong> #{arrow} String",
        :two_types => "#<strong>foo</strong> #{arrow} String, Symbol",
        :two_types_multitag => "#<strong>foo</strong> #{arrow} String, Symbol",
        :type_nil => "#<strong>foo</strong> #{arrow} Type<sup>?</sup>",
        :type_array => "#<strong>foo</strong> #{arrow} Type<sup>+</sup>",
        :multitype => "#<strong>foo</strong> #{arrow} Type, ...",
        :void => "#<strong>foo</strong> #{arrow} void",
        :hide_void => "#<strong>foo</strong>",
        :block => "#<strong>foo</strong> {|a, b, c| ... } #{arrow} Object",
        :empty_overload => "#<strong>foobar</strong> #{arrow} String"
      }
    end

    def format_types(types, _brackets = false) types.join(", ") end
    def signature(obj, link = false) super(obj, link).strip end

    it_should_behave_like "signature"

    it "links to regular method if overload name does not have the same method name" do
      YARD.parse_string <<-eof
        class Foo
          # @overload bar(a, b, c)
          def foo; end
        end
      eof
      serializer = double(:serializer)
      allow(serializer).to receive(:serialized_path).with(Registry.at('Foo')).and_return('')
      allow(self).to receive(:serializer).and_return(serializer)
      allow(self).to receive(:object).and_return(Registry.at('Foo'))
      expect(signature(Registry.at('Foo#foo').tag(:overload), true)).to eq(
        "<a href=\"#foo-instance_method\" title=\"#bar (instance method)\">#<strong>bar</strong>(a, b, c)  </a>"
      )
    end
  end

  describe "#html_syntax_highlight" do
    subject do
      obj = OpenStruct.new
      obj.options = options
      obj.object = Registry.root
      obj.extend(Templates::Helpers::HtmlHelper)
      obj
    end

    it "returns empty string on nil input" do
      expect(subject.html_syntax_highlight(nil)).to eq ''
    end

    it "calls #html_syntax_highlight_ruby by default" do
      Registry.root.source_type = nil
      expect(subject).to receive(:html_syntax_highlight_ruby).with('def x; end')
      subject.html_syntax_highlight('def x; end')
    end

    it "calls #html_syntax_highlight_NAME if there's an object with a #source_type" do
      subject.object = OpenStruct.new(:source_type => :NAME)
      expect(subject).to receive(:html_markup_html) {|text| text }
      expect(subject).to receive(:html_syntax_highlight_NAME).and_return("foobar")
      expect(subject.htmlify('<pre><code>def x; end</code></pre>', :html)).to eq(
        '<pre class="code NAME"><code class="NAME">foobar</code></pre>'
      )
    end

    it "adds !!!LANG to className in outputted pre tag" do
      subject.object = OpenStruct.new(:source_type => :LANG)
      expect(subject).to receive(:html_markup_html) {|text| text }
      expect(subject).to receive(:html_syntax_highlight_LANG).and_return("foobar")
      expect(subject.htmlify("<pre><code>!!!LANG\ndef x; end</code></pre>", :html)).to eq(
        '<pre class="code LANG"><code class="LANG">foobar</code></pre>'
      )
    end

    it "calls html_syntax_highlight_NAME if source starts with !!!NAME" do
      expect(subject).to receive(:html_syntax_highlight_NAME).and_return("foobar")
      expect(subject.html_syntax_highlight(<<-eof
        !!!NAME
        def x; end
      eof
                                          )).to eq "foobar"
    end

    it "does not highlight if highlight option is false" do
      subject.options.highlight = false
      expect(subject).not_to receive(:html_syntax_highlight_ruby)
      expect(subject.html_syntax_highlight('def x; end')).to eq 'def x; end'
    end

    it "does not highlight if there is no highlight method specified by !!!NAME" do
      def subject.respond_to?(method, include_all = false)
        return false if method == 'html_syntax_highlight_NAME'
        super
      end
      expect(subject).not_to receive(:html_syntax_highlight_NAME)
      expect(subject.html_syntax_highlight("!!!NAME\ndef x; end")).to eq "def x; end"
    end

    it "highlights as ruby if htmlify(text, :ruby) is called" do
      expect(subject).to receive(:html_syntax_highlight_ruby).with('def x; end').and_return('x')
      expect(subject.htmlify('def x; end', :ruby)).to eq '<pre class="code ruby">x</pre>'
    end

    it "does not prioritize object source type when called directly" do
      expect(subject).to receive(:html_syntax_highlight_ruby).with('def x; end').and_return('x')
      subject.object = OpenStruct.new(:source_type => :c)
      expect(subject.html_syntax_highlight("def x; end")).to eq "x"
    end

    it "doesn't escape code snippets twice" do
      expect(subject.htmlify('<pre lang="foo"><code>{"foo" => 1}</code></pre>', :html)).to eq(
        '<pre class="code foo"><code class="foo">{&quot;foo&quot; =&gt; 1}</code></pre>'
      )
    end

    it "highlights source when matching a pre lang= tag" do
      expect(subject.htmlify('<pre lang="foo"><code>x = 1</code></pre>', :html)).to eq(
        '<pre class="code foo"><code class="foo">x = 1</code></pre>'
      )
    end

    it "highlights source when matching a code class= tag" do
      expect(subject.htmlify('<pre><code class="foo">x = 1</code></pre>', :html)).to eq(
        '<pre class="code foo"><code class="foo">x = 1</code></pre>'
      )
    end

    it "does not re-escape already-highlighted code blocks embedded via {yard:include_tags}" do
      # parse_codeblocks double-processing: when {yard:include_tags} embeds already-highlighted
      # <pre class="example code"><code>HIGHLIGHTED</code></pre> blocks into a page, a second
      # call to parse_codeblocks must not HTML-escape the span tags.
      # Regression test for https://github.com/lsegal/yard/issues/1661
      highlighted = "<span class='kw'>def</span> <span class='id identifier rubyid_foo'>foo</span>"
      html = %(<pre class="example code"><code>#{highlighted}</code></pre>)
      result = subject.htmlify(html, :html)
      expect(result).not_to include('&lt;span')
      expect(result).to include("<span class='kw'>def</span>")
    end

    it "does not re-escape already-highlighted code with leading whitespace (indented blocks)" do
      # The rescue check in html_syntax_highlight_ruby_ripper was anchored with ^<span, which
      # only matches when spans start at the beginning of a line. Leading spaces (e.g., from
      # indented Ruby code like '  def foo') caused the check to fail and h(source) to be called.
      # Regression test for https://github.com/lsegal/yard/issues/1661
      highlighted = "  <span class='kw'>def</span> <span class='id identifier rubyid_foo'>foo</span>"
      html = %(<pre class="example code"><code>#{highlighted}</code></pre>)
      result = subject.htmlify(html, :html)
      expect(result).not_to include('&lt;span')
      expect(result).to include("<span class='kw'>def</span>")
    end
  end

  describe "#link_url" do
    it "adds target if scheme is provided" do
      expect(link_url("https://url.com")).to include(" target=\"_parent\"")
      expect(link_url("http://url.com")).to include(" target=\"_parent\"")
      expect(link_url("irc://url.com")).to include(" target=\"_parent\"")
      expect(link_url("../not/scheme")).not_to include("target")
    end
  end
end
