require File.expand_path(File.dirname(__FILE__) + '/spec_helper')
require 'thor/base'

class Amazing
  desc "hello", "say hello"
  def hello
    puts "Hello"
  end
end

describe Thor::Base do
  describe "#initialize" do
    it "sets arguments array" do
      base = MyCounter.new [1, 2]
      base.first.should == 1
      base.second.should == 2
    end

    it "sets arguments default values" do
      base = MyCounter.new [1]
      base.second.should == 2
    end

    it "sets options default values" do
      base = MyCounter.new [1, 2]
      base.options[:third].should == 3
    end

    it "allows options to be given as symbols or strings" do
      base = MyCounter.new [1, 2], :third => 4
      base.options[:third].should == 4

      base = MyCounter.new [1, 2], "third" => 4
      base.options[:third].should == 4
    end

    it "creates options with indifferent access" do
      base = MyCounter.new [1, 2], :third => 3
      base.options['third'].should == 3
    end

    it "creates options with magic predicates" do
      base = MyCounter.new [1, 2], :third => 3
      base.options.third.should == 3
    end
  end

  describe "#no_tasks" do
    it "avoids methods being added as tasks" do
      MyScript.tasks.keys.should include("animal")
      MyScript.tasks.keys.should_not include("this_is_not_a_task")
    end
  end

  describe "#argument" do
    it "sets a value as required and creates an accessor for it" do
      MyCounter.start(["1", "2", "--third", "3"])[0].should == 1
      Scripts::MyScript.start(["zoo", "my_special_param", "--param=normal_param"]).should == "my_special_param"
    end

    it "does not set a value in the options hash" do
      BrokenCounter.start(["1", "2", "--third", "3"])[0].should be_nil
    end
  end

  describe "#arguments" do
    it "returns the arguments for the class" do
      MyCounter.arguments.should have(2).items
    end
  end

  describe "#class_option" do
    it "sets options class wise" do
      MyCounter.start(["1", "2", "--third", "3"])[2].should == 3
    end

    it "does not create an accessor for it" do
      BrokenCounter.start(["1", "2", "--third", "3"])[3].should be_false
    end
  end

  describe "#class_options" do
    it "sets default options overwriting superclass definitions" do
      options = Scripts::MyScript.class_options
      options[:force].should_not be_required
    end
  end

  describe "#remove_argument" do
    it "removes previous defined arguments from class" do
      ClearCounter.arguments.should be_empty
    end

    it "undefine accessors if required" do
      ClearCounter.new.should_not respond_to(:first)
      ClearCounter.new.should_not respond_to(:second)
    end
  end

  describe "#remove_class_option" do
    it "removes previous defined class option" do
      ClearCounter.class_options[:third].should be_nil
    end
  end

  describe "#class_options_help" do
    before do
      @content = capture(:stdout) { MyCounter.help(Thor::Base.shell.new) }
    end

    it "shows options description" do
      @content.should =~ /# The third argument/
    end

    it "shows usage with banner content" do
      @content.should =~ /\[\-\-third=THREE\]/
    end

    it "shows default values below description" do
      @content.should =~ /# Default: 3/
    end

    it "shows options in different groups" do
      @content.should =~ /Options\:/
      @content.should =~ /Runtime options\:/
      @content.should =~ /\-p, \[\-\-pretend\]/
    end

    it "use padding in options that does not have aliases" do
      @content.should =~ /^  -t, \[--third/
      @content.should =~ /^      \[--fourth/
    end

    it "allows extra options to be given" do
      hash = { "Foo" => B.class_options.values }

      content = capture(:stdout) { MyCounter.send(:class_options_help, Thor::Base.shell.new, hash) }
      content.should =~ /Foo options\:/
      content.should =~ /--last-name=LAST_NAME/
    end
  end

  describe "#namespace" do
    it "returns the default class namespace" do
      Scripts::MyScript.namespace.should == "scripts:my_script"
    end

    it "sets a namespace to the class" do
      Scripts::MyDefaults.namespace.should == "default"
    end
  end

  describe "#group" do
    it "sets a group" do
      MyScript.group.should == "script"
    end

    it "inherits the group from parent" do
      MyChildScript.group.should == "script"
    end

    it "defaults to standard if no group is given" do
      Amazing.group.should == "standard"
    end
  end

  describe "#subclasses" do
    it "tracks its subclasses in an Array" do
      Thor::Base.subclasses.should include(MyScript)
      Thor::Base.subclasses.should include(MyChildScript)
      Thor::Base.subclasses.should include(Scripts::MyScript)
    end
  end

  describe "#subclass_files" do
    it "returns tracked subclasses, grouped by the files they come from" do
      thorfile = File.join(File.dirname(__FILE__), "fixtures", "script.thor")
      Thor::Base.subclass_files[File.expand_path(thorfile)].should == [
        MyScript, MyScript::AnotherScript, MyChildScript, Barn,
        Scripts::MyScript, Scripts::MyDefaults, Scripts::ChildDefault
      ]
    end

    it "tracks a single subclass across multiple files" do
      thorfile = File.join(File.dirname(__FILE__), "fixtures", "task.thor")
      Thor::Base.subclass_files[File.expand_path(thorfile)].should include(Amazing)
      Thor::Base.subclass_files[File.expand_path(__FILE__)].should include(Amazing)
    end
  end

  describe "#tasks" do
    it "returns a list with all tasks defined in this class" do
      MyChildScript.new.should respond_to("animal")
      MyChildScript.tasks.keys.should include("animal")
    end

    it "raises an error if a task with reserved word is defined" do
      lambda {
        klass = Class.new(Thor::Group)
        klass.class_eval "def shell; end"
      }.should raise_error(RuntimeError, /"shell" is a Thor reserved word and cannot be defined as task/)
    end
  end

  describe "#all_tasks" do
    it "returns a list with all tasks defined in this class plus superclasses" do
      MyChildScript.new.should respond_to("foo")
      MyChildScript.all_tasks.keys.should include("foo")
    end
  end

  describe "#remove_task" do
    it "removes the task from its tasks hash" do
      MyChildScript.tasks.keys.should_not include("bar")
      MyChildScript.tasks.keys.should_not include("boom")
    end

    it "undefines the method if desired" do
      MyChildScript.new.should_not respond_to("boom")
    end
  end

  describe "#from_superclass" do
    it "does not send a method to the superclass if the superclass does not respond to it" do
      MyCounter.get_from_super.should == 13
    end
  end

  describe "#start" do
    it "raises an error instead of rescueing if THOR_DEBUG=1 is given" do
      begin
        ENV["THOR_DEBUG"] = 1
        lambda {
          MyScript.start ["what", "--debug"]
        }.should raise_error(Thor::UndefinedTaskError, 'Could not find task "what" in "my_script" namespace.')
      rescue
        ENV["THOR_DEBUG"] = nil
      end
    end

    it "does not steal args" do
      args = ["foo", "bar", "--force", "true"]
      MyScript.start(args)
      args.should == ["foo", "bar", "--force", "true"]
    end

    it "checks unknown options" do
      capture(:stderr) {
        MyScript.start(["foo", "bar", "--force", "true", "--unknown", "baz"])
      }.strip.should == "Unknown switches '--unknown'"
    end

    it "checks unknown options except specified" do
      capture(:stderr) {
        MyScript.start(["with_optional", "NAME", "--omg", "--invalid"]).should == ["NAME", {}, ["--omg", "--invalid"]]
      }.strip.should be_empty
    end
  end

  describe "attr_*" do
    it "should not add attr_reader as a task" do
      capture(:stderr){ MyScript.start(["another_attribute"]) }.should =~ /Could not find/
    end

    it "should not add attr_writer as a task" do
      capture(:stderr){ MyScript.start(["another_attribute=", "foo"]) }.should =~ /Could not find/
    end

    it "should not add attr_accessor as a task" do
      capture(:stderr){ MyScript.start(["some_attribute"]) }.should =~ /Could not find/
      capture(:stderr){ MyScript.start(["some_attribute=", "foo"]) }.should =~ /Could not find/
    end
  end
end
