require "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]
      expect(base.first).to eq(1)
      expect(base.second).to eq(2)
    end

    it "sets arguments default values" do
      base = MyCounter.new [1]
      expect(base.second).to eq(2)
    end

    it "sets options default values" do
      base = MyCounter.new [1, 2]
      expect(base.options[:third]).to eq(3)
    end

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

      base = MyCounter.new [1, 2], "third" => 4
      expect(base.options[:third]).to eq(4)
    end

    it "creates options with indifferent access" do
      base = MyCounter.new [1, 2], third: 3
      expect(base.options["third"]).to eq(3)
    end

    it "creates options with magic predicates" do
      base = MyCounter.new [1, 2], third: 3
      expect(base.options.third).to eq(3)
    end
  end

  describe "#no_commands" do
    it "avoids methods being added as commands" do
      expect(MyScript.commands.keys).to include("animal")
      expect(MyScript.commands.keys).not_to include("this_is_not_a_command")
      expect(MyScript.commands.keys).not_to include("neither_is_this")
    end
  end

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

    it "does not set a value in the options hash" do
      expect(BrokenCounter.start(%w(1 2 --third 3))[0]).to be nil
    end
  end

  describe "#arguments" do
    it "returns the arguments for the class" do
      expect(MyCounter.arguments.size).to be(2)
    end
  end

  describe "#class_exclusive_option_names" do
    it "returns the exclusive option names for the class" do
      expect(MyClassOptionScript.class_exclusive_option_names.size).to be(1)
      expect(MyClassOptionScript.class_exclusive_option_names.first.size).to be(2)
    end
  end

  describe "#class_at_least_one_option_names" do
    it "returns the at least one of option names for the class" do
      expect(MyClassOptionScript.class_at_least_one_option_names.size).to be(1)
      expect(MyClassOptionScript.class_at_least_one_option_names.first.size).to be(2)
    end
  end

  describe "#class_exclusive" do
    it "raise error when exclusive options are given" do
      begin
        ENV["THOR_DEBUG"] = "1"
        expect do
          MyClassOptionScript.start %w[mix --one --two --three --five]
        end.to raise_error(Thor::ExclusiveArgumentError, "Found exclusive options '--one', '--two'")

        expect do
          MyClassOptionScript.start %w[mix --one --three --five --six]
        end.to raise_error(Thor::ExclusiveArgumentError,  "Found exclusive options '--five', '--six'")
      ensure
        ENV["THOR_DEBUG"] = nil
      end
    end
  end

  describe "#class_at_least_one" do
    it "raise error when at least one of required options are not given" do
      begin
        ENV["THOR_DEBUG"] = "1"

        expect do
          MyClassOptionScript.start %w[mix --five]
        end.to raise_error(Thor::AtLeastOneRequiredArgumentError, "Not found at least one of required options '--three', '--four'")

        expect do
          MyClassOptionScript.start %w[mix --one --three]
        end.to raise_error(Thor::AtLeastOneRequiredArgumentError, "Not found at least one of required options '--five', '--six', '--seven'")
      ensure
        ENV["THOR_DEBUG"] = nil
      end
    end
  end

  describe ":aliases" do
    it "supports string aliases without a dash prefix" do
      expect(MyCounter.start(%w(1 2 -z 3))[4]).to eq(3)
    end

    it "supports symbol aliases" do
      expect(MyCounter.start(%w(1 2 -y 3))[5]).to eq(3)
      expect(MyCounter.start(%w(1 2 -r 3))[5]).to eq(3)
    end
  end

  describe "#class_option" do
    it "sets options class wise" do
      expect(MyCounter.start(%w(1 2 --third 3))[2]).to eq(3)
    end

    it "does not create an accessor for it" do
      expect(BrokenCounter.start(%w(1 2 --third 3))[3]).to be false
    end
  end

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

  describe "#remove_argument" do
    it "removes previously defined arguments from class" do
      expect(ClearCounter.arguments).to be_empty
    end

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

  describe "#remove_class_option" do
    it "removes previous defined class option" do
      expect(ClearCounter.class_options[:third]).to be nil
    end
  end

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

    it "shows option's description" do
      expect(@content).to match(/# The third argument/)
    end

    it "shows usage with banner content" do
      expect(@content).to match(/\[\-\-third=THREE\]/)
    end

    it "shows default values below descriptions" do
      expect(@content).to match(/# Default: 3/)
    end

    it "prints arrays as copy pasteables" do
      expect(@content).to match(/Default: "foo" "bar"/)
    end

    it "shows options in different groups" do
      expect(@content).to match(/Options\:/)
      expect(@content).to match(/Runtime options\:/)
      expect(@content).to match(/\-p, \[\-\-pretend\]/)
    end

    it "use padding in options that do not have aliases" do
      expect(@content).to match(/^  -t,     \[--third/)
      expect(@content).to match(/^          \[--fourth/)
      expect(@content).to match(/^  -y, -r, \[--symbolic/)
    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) }
      expect(content).to match(/Foo options\:/)
      expect(content).to match(/--last-name=LAST_NAME/)
    end

    it "displays choices for enums" do
      content = capture(:stdout) { Enum.help(Thor::Base.shell.new) }
      expect(content).to match(/Possible values\: apple, banana/)
    end
  end

  describe "#namespace" do
    it "returns the default class namespace" do
      expect(Scripts::MyScript.namespace).to eq("scripts:my_script")
    end

    it "sets a namespace to the class" do
      expect(Scripts::MyDefaults.namespace).to eq("default")
    end
  end

  describe "#group" do
    it "sets a group" do
      expect(MyScript.group).to eq("script")
    end

    it "inherits the group from parent" do
      expect(MyChildScript.group).to eq("script")
    end

    it "defaults to standard if no group is given" do
      expect(Amazing.group).to eq("standard")
    end
  end

  describe "#subclasses" do
    it "tracks its subclasses in an Array" do
      expect(Thor::Base.subclasses).to include(MyScript)
      expect(Thor::Base.subclasses).to include(MyChildScript)
      expect(Thor::Base.subclasses).to 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")
      expect(Thor::Base.subclass_files[File.expand_path(thorfile)]).to eq([
        MyScript, MyScript::AnotherScript, MyChildScript, Barn,
        PackageNameScript, Scripts::MyScript, Scripts::MyDefaults,
        Scripts::ChildDefault, Scripts::Arities, Apple, Pear, MyClassOptionScript, MyOptionScript
      ])
    end

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

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

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

  describe "#all_commands" do
    it "returns a list with all commands defined in this class plus superclasses" do
      expect(MyChildScript.new).to respond_to("foo")
      expect(MyChildScript.all_commands.keys).to include("foo")
    end
  end

  describe "#remove_command" do
    it "removes the command from its commands hash" do
      expect(MyChildScript.all_commands.keys).not_to include("name_with_dashes")
      expect(MyChildScript.commands.keys).not_to include("boom")
    end

    it "undefines the method if desired" do
      expect(MyChildScript.new).not_to 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
      expect(MyCounter.get_from_super).to eq(13)
    end
  end

  describe "#start" do
    it "raises an error instead of rescuing if THOR_DEBUG=1 is given" do
      begin
        ENV["THOR_DEBUG"] = "1"

        expect do
          MyScript.start %w(what --debug)
        end.to raise_error(Thor::UndefinedCommandError, 'Could not find command "what" in "my_script" namespace.')
      ensure
        ENV["THOR_DEBUG"] = nil
      end
    end

    it "raises an error instead of rescuing if :debug option is given" do
      expect do
        MyScript.start %w(what), debug: true
      end.to raise_error(Thor::UndefinedCommandError, 'Could not find command "what" in "my_script" namespace.')
    end

    it "suggests commands that are similar if there is a typo" do
      expected = "Could not find command \"paintz\" in \"barn\" namespace.\n".dup
      expected << "Did you mean?  \"paint\"\n" if Thor::Correctable

      expect(capture(:stderr) { Barn.start(%w(paintz)) }).to eq(expected)
    end

    it "does not steal args" do
      args = %w(foo bar --force true)
      MyScript.start(args)
      expect(args).to eq(%w(foo bar --force true))
    end

    it "checks unknown options" do
      expect(capture(:stderr) do
        MyScript.start(%w(foo bar --force true --unknown baz))
      end.strip).to eq("Unknown switches \"--unknown\"")
    end

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

  describe "attr_*" do
    it "does not add attr_reader as a command" do
      expect(capture(:stderr) { MyScript.start(%w(another_attribute)) }).to match(/Could not find/)
    end

    it "does not add attr_writer as a command" do
      expect(capture(:stderr) { MyScript.start(%w(another_attribute= foo)) }).to match(/Could not find/)
    end

    it "does not add attr_accessor as a command" do
      expect(capture(:stderr) { MyScript.start(["some_attribute"]) }).to match(/Could not find/)
      expect(capture(:stderr) { MyScript.start(["some_attribute=", "foo"]) }).to match(/Could not find/)
    end
  end
end
