require 'fileutils'

describe "Fortitude widget-class-from-(file|source) support", :type => :system do
  def tempdir
    @tempdir ||= begin
      out = File.join(File.dirname(File.dirname(File.dirname(__FILE__))), 'tmp', 'spec', 'widget_class_from_spec')
      FileUtils.rm_rf(out)
      FileUtils.mkdir_p(out)
      out
    end
  end

  def splat!(filename, text)
    full_path = File.join(tempdir, filename)
    FileUtils.mkdir_p(File.dirname(full_path))
    File.open(full_path, 'w') { |f| f << text }
    full_path
  end

  def wcfs(*args)
    ::Fortitude::Widget.widget_class_from_source(*args)
  end

  def wcff(filename, *args)
    filename = File.join(tempdir, filename)
    ::Fortitude::Widget.widget_class_from_file(filename, *args)
  end

  context "with no hints" do
    it "should return a widget subclass that's already been evaluated" do
      text = "class WidgetFromClass1 < Fortitude::Widget; end"
      ::Object.class_eval(text)
      expect(wcfs(text)).to eq(::WidgetFromClass1)
    end

    it "should automatically evaluate the source, if needed" do
      text = "class WidgetFromClass2 < Fortitude::Widget; end"
      expect(wcfs(text)).to eq(::WidgetFromClass2)
    end

    it "should prefer the longest class name to a shorter name" do
      eval("module WidgetFromClass25; class Widget1 < Fortitude::Widget; end; end")
      text = "module WidgetFromClass25; module Foo; class Widget1 < Fortitude::Widget; end; end; end"
      eval(text)
      expect(wcfs(text)).to eq(::WidgetFromClass25::Foo::Widget1)
    end

    it "should fail if given source code it can't guess the class name from, returning any :class_names_to_try in the exception" do
      cdne = capture_exception(::Fortitude::Widget::Files::CannotDetermineWidgetClassNameError) do
        wcfs("cname = 'WidgetFromCla' + 'ss3'; eval('class ' + cname + ' < ::Fortitude::Widget; end')",
          :class_names_to_try => [ 'WidgetFromClass3::Foo', 'WidgetFromClass3::Baz' ])
      end
      expect(cdne.class_names_to_try).to eq([ 'WidgetFromClass3::Foo', 'WidgetFromClass3::Baz' ])
    end

    it "should fail if the source code contains something that isn't a widget class" do
      cdne = capture_exception(::Fortitude::Widget::Files::CannotDetermineWidgetClassNameError) do
        wcfs("class WidgetFromClass11; end")
      end
      expect(cdne.tried_class_names).to be_include("WidgetFromClass11")
    end

    it "should allow specifying additional widget base classes, and return those if needed" do
      eval("module WidgetFromClass26; class SomeOtherBase; end; end")
      text = "class WidgetFromClass27 < WidgetFromClass26::SomeOtherBase; end"
      eval(text)
      expect(wcfs(text, :valid_base_classes => [ ::Fortitude::Widget, ::WidgetFromClass26::SomeOtherBase ])).to eq(::WidgetFromClass27)
    end

    it "should allow excluding Fortitude::Widget" do
      eval("module WidgetFromClass28; class SomeOtherBase; end; end")
      text = "class WidgetFromClass29 < ::Fortitude::Widget; end"
      eval(text)

      cdne = capture_exception(::Fortitude::Widget::Files::CannotDetermineWidgetClassNameError) do
        wcfs(text, :valid_base_classes => [ ::WidgetFromClass28::SomeOtherBase ])
      end
      expect(cdne.tried_class_names).to eq([ 'WidgetFromClass29' ])
    end

    it "should fail, but return the object, if the source code contains an object of that class that just isn't a widget class" do
      cdne = capture_exception(::Fortitude::Widget::Files::CannotDetermineWidgetClassNameError) do
        wcfs("module WidgetFromClass24\nend")
      end
      expect(cdne.resulting_objects).to eq([ ::WidgetFromClass24 ])
    end

    it "should work if given something that's a grandchild of Fortitude::Widget, not a direct child" do
      ::Object.class_eval("class WidgetFromClass12Parent < ::Fortitude::Widget; end")
      expect(wcfs("class WidgetFromClass12 < WidgetFromClass12Parent; end")).to eq(WidgetFromClass12)
    end

    it "should work if given a class name with a leading :: prefix" do
      expect(wcfs("class ::WidgetFromClassWithColons < Fortitude::Widget; end")).to eq(::WidgetFromClassWithColons)
    end

    it "should be able to guess the class name of a class namespaced in a module" do
      module ::Wcfs1; end
      expect(wcfs("class Wcfs1::WidgetFromClass6 < ::Fortitude::Widget; end")).to eq(Wcfs1::WidgetFromClass6)
    end

    it "should be able to guess the class name of a class nested in a module" do
      expect(wcfs("module Wcfs2; class WidgetFromClass7 < ::Fortitude::Widget; end; end")).to eq(Wcfs2::WidgetFromClass7)
    end

    it "should be able to guess the class name of a class nested in a module several levels deep" do
      expect(wcfs("module Wcfs3; module Wcfs4; class WidgetFromClass8 < ::Fortitude::Widget; end; end; end")).to eq(Wcfs3::Wcfs4::WidgetFromClass8)
    end

    it "should be able to guess the class name of a class nested in a module several levels deep, using newlines" do
      expect(wcfs(%{module Wcfs5
  module Wcfs6
    class WidgetFromClass9 < ::Fortitude::Widget
    end
  end
end})).to eq(Wcfs5::Wcfs6::WidgetFromClass9)
    end

    it "should be able to guess the class name of a class with both nesting and namespacing" do
      ::Object.class_eval %{module Wcfs7; module Wcfs8; module Wcfs9; end; end; end}

      expect(wcfs(%{module Wcfs7
  module Wcfs8
    class Wcfs9::WidgetFromClass10 < ::Fortitude::Widget
    end
  end
end})).to eq(Wcfs7::Wcfs8::Wcfs9::WidgetFromClass10)
    end
  end

  context "with an explicit class name provided" do
    it "should be able to get a widget class, even if it can't get it by parsing the source code" do
      result = wcfs("cname = 'WidgetFromCla' + 'ss4'; eval('class ' + cname + ' < ::Fortitude::Widget; end')",
        :class_names_to_try => [ 'WidgetFromClass4' ])
      expect(result).to eq(WidgetFromClass4)
    end

    it "should be able to get a widget class, even if it can't get it by parsing the source code, if passed in among a bunch of other crap" do
      result = wcfs("cname = 'WidgetFromCla' + 'ss5'; eval('class ' + cname + ' < ::Fortitude::Widget; end')",
        :class_names_to_try => [ 'String', 'Integer', 'Foo::Bar::Baz', 'WidgetFromClass5', 'Baz::Quux' ])
      expect(result).to eq(WidgetFromClass5)
    end
  end

  context "with a magic comment provided" do
    it "should be able to get a widget class, even if it can't get it by parsing the source code" do
      result = wcfs(%{#!fortitude_class: WidgetFromClass13
        cname = 'WidgetFromCla' + 'ss13'; eval('class ' + cname + ' < ::Fortitude::Widget; end')})
      expect(result).to eq(WidgetFromClass13)
    end

    it "should be able to get a widget class, even if it can't get it by parsing the source code, using an alternate magic comment text" do
      result = wcfs(%{#!foo_bar_baz: WidgetFromClass14
        cname = 'WidgetFromCla' + 'ss14'; eval('class ' + cname + ' < ::Fortitude::Widget; end')},
        :magic_comment_text => %w{bar_baz_quux foo_bar_baz foo_Bar})
      expect(result).to eq(WidgetFromClass14)
    end

    it "should be able to get a widget class, even if it can't get it by parsing the source code, using a standard magic comment text, even if an alternate is provided" do
      result = wcfs(%{#!fortitude_class: WidgetFromClass20
        cname = 'WidgetFromCla' + 'ss20'; eval('class ' + cname + ' < ::Fortitude::Widget; end')},
        :magic_comment_text => %w{bar_baz_quux foo_bar_baz foo_Bar})
      expect(result).to eq(WidgetFromClass20)
    end

    it "should be able to get a widget class, even if it can't get it by parsing the source code, even at the end" do
      result = wcfs(%{
        cname = 'WidgetFromCla' + 'ss15'; eval('class ' + cname + ' < ::Fortitude::Widget; end')
#!fortitude_class: WidgetFromClass15})
      expect(result).to eq(WidgetFromClass15)
    end

    it "should not fail if it's wrong (something that doesn't exist)" do
      text = %{#!fortitude_class: Whatever
class WidgetFromClass16 < Fortitude::Widget; end}
      expect(wcfs(text)).to eq(::WidgetFromClass16)
    end

    it "should not fail if it's wrong (something that isn't a widget class)" do
      text = %{#!fortitude_class: String
class WidgetFromClass17 < Fortitude::Widget; end}
      expect(wcfs(text)).to eq(::WidgetFromClass17)
    end

    it "should tell you the magic comment texts it was looking for in the error if it can't find a class" do
      cdne = capture_exception(::Fortitude::Widget::Files::CannotDetermineWidgetClassNameError) do
        wcfs(%{class WidgetFromClass18; end})
      end
      expect(cdne.magic_comment_texts).to eq(%w{fortitude_class})
    end

    it "should tell you the magic comment texts it was looking for in the error if it can't find a class and custom magic texts were provided" do
      cdne = capture_exception(::Fortitude::Widget::Files::CannotDetermineWidgetClassNameError) do
        wcfs(%{class WidgetFromClass19; end}, :magic_comment_text => %w{foo_bar bar_baz})
      end
      expect(cdne.magic_comment_texts).to be_include('fortitude_class')
      expect(cdne.magic_comment_texts).to be_include('foo_bar')
      expect(cdne.magic_comment_texts).to be_include('bar_baz')
    end
  end

  context "when given a file" do
    it "should still be able to guess the class name correctly" do
      splat!('widget_from_file_1.rb', %{class WidgetFromClass21 < Fortitude::Widget; end})
      expect(wcff('widget_from_file_1.rb')).to eq(WidgetFromClass21)
    end

    it "should still be able to use magic comments" do
      splat!('widget_from_file_2.rb', %{#!fortitude_class: WidgetFromClass22\ncname = 'WidgetFromCla' + 'ss22'; eval('class ' + cname + ' < ::Fortitude::Widget; end')})
      expect(wcff('widget_from_file_2.rb')).to eq(WidgetFromClass22)
    end

    it "should be able to use a root directory to infer a class name" do
      ::Object.class_eval("module Wcfs10; module Wcfs11; end; end")
      splat!('wcfs10/wcfs_11/widget_from_class_23.rb', %{cname = 'WidgetFromCla' + 'ss23'; eval('class Wcfs10::Wcfs11::' + cname + ' < ::Fortitude::Widget; end')})
      expect(wcff('wcfs10/wcfs_11/widget_from_class_23.rb', :root_dirs => tempdir)).to eq(Wcfs10::Wcfs11::WidgetFromClass23)
    end

    it "should be able to use a root directory to infer a class name, even if it the class name ends in other extensions before .rb" do
      ::Object.class_eval("module Wcfs12; end")
      splat!('wcfs12/widget_from_class_30.html.rb', %{cname = 'WidgetFromCla' + 'ss30'; eval('class Wcfs12::' + cname + ' < ::Fortitude::Widget; end')})
      expect(wcff('wcfs12/widget_from_class_30.html.rb', :root_dirs => tempdir)).to eq(Wcfs12::WidgetFromClass30)
    end

    it "should be able to use an array of root directories to infer a class name, and find the right one" do
      ::Object.class_eval("module Wcfs10; module Wcfs11; end; end")
      splat!('bar/wcfs10/wcfs_11/widget_from_class_23.rb', %{cname = 'WidgetFromCla' + 'ss23'; eval('class Wcfs10::Wcfs11::' + cname + ' < ::Fortitude::Widget; end')})
      expect(wcff('bar/wcfs10/wcfs_11/widget_from_class_23.rb', :root_dirs => [ "#{tempdir}/foo", "#{tempdir}/bar" ])).to eq(Wcfs10::Wcfs11::WidgetFromClass23)
    end

    it "should be able to have an additional base class and use it" do
      eval("module Wcff12; class OtherBaseClass; end; end")
      splat!("wcff12/widget_from_file_12.rb", %{class WidgetFromFile12 < Wcff12::OtherBaseClass; end})
      expect(wcff('wcff12/widget_from_file_12.rb', :valid_base_classes => [ ::Fortitude::Widget, ::Wcff12::OtherBaseClass ])).to eq(WidgetFromFile12)
    end

    it "should be able to exclude ::Fortitude::Widget successfully" do
      eval("module Wcff13; class OtherBaseClass; end; end")
      splat!("wcff13/widget_from_file_13.rb", %{class WidgetFromFile13 < ::Fortitude::Widget; end})
      cdne = capture_exception(::Fortitude::Widget::Files::CannotDetermineWidgetClassNameError) do
        wcff('wcff13/widget_from_file_13.rb', :valid_base_classes => [ ::Wcff13::OtherBaseClass ])
      end
      expect(cdne.tried_class_names).to eq([ "WidgetFromFile13" ])
    end
  end

  it "should prioritize a magic comment above class names you told it to try or directory-based names or scanned source text" do
    splat!('widget_baz.rb', %{
#!fortitude_class: WidgetFoo

eval('class WidgetB' +
'az < Fortitud' +
'e::Widget; end')
eval('class WidgetB' +
'ar < Fortitud' +
'e::Widget; end')
class WidgetQuux < Fortitude::Widget; end
eval('class WidgetF' +
'oo < Fortitud' +
'e::Widget; end')
    })

    expect(wcff('widget_baz.rb', :root_dirs => tempdir, :class_names_to_try => %w{WidgetBar})).to eq(WidgetFoo)
  end

  it "should prioritize class names you told it to try above directory-based names or scanned source text" do
    splat!('widget_baz.rb', %{
eval('class WidgetB' +
'az < Fortitud' +
'e::Widget; end')
eval('class WidgetB' +
'ar < Fortitud' +
'e::Widget; end')
class WidgetQuux < Fortitude::Widget; end
eval('class WidgetF' +
'oo < Fortitud' +
'e::Widget; end')
    })

    expect(wcff('widget_baz.rb', :root_dirs => tempdir, :class_names_to_try => %w{WidgetBar})).to eq(WidgetBar)
  end

  it "should prioritize directory-based names above scanned source text" do
    splat!('widget_baz.rb', %{
eval('class WidgetB' +
'az < Fortitud' +
'e::Widget; end')
eval('class WidgetB' +
'ar < Fortitud' +
'e::Widget; end')
class WidgetQuux < Fortitude::Widget; end
eval('class WidgetF' +
'oo < Fortitud' +
'e::Widget; end')
    })

    expect(wcff('widget_baz.rb', :root_dirs => tempdir)).to eq(WidgetBaz)
  end
end
