require 'spec_helper'
require 'oregano/interface'

describe Oregano::Interface::Option do
  let :face do Oregano::Interface.new(:option_testing, '0.0.1') end

  describe "#optparse_to_name" do
    ["", "=BAR", " BAR", "=bar", " bar"].each do |postfix|
      { "--foo" => :foo, "-f" => :f }.each do |base, expect|
        input = base + postfix
        it "should map #{input.inspect} to #{expect.inspect}" do
          option = Oregano::Interface::Option.new(face, input)
          expect(option.name).to eq(expect)
        end
      end
    end

    [:foo, 12, nil, {}, []].each do |input|
      it "should fail sensible when given #{input.inspect}" do
        expect {
          Oregano::Interface::Option.new(face, input)
        }.to raise_error ArgumentError, /is not valid for an option argument/
      end
    end

    ["-foo", "-foo=BAR", "-foo BAR"].each do |input|
      it "should fail with a single dash for long option #{input.inspect}" do
        expect {
          Oregano::Interface::Option.new(face, input)
        }.to raise_error ArgumentError, /long options need two dashes \(--\)/
      end
    end
  end

  it "requires a face when created" do
    expect {
      Oregano::Interface::Option.new
    }.to raise_error ArgumentError, /wrong number of arguments/
  end

  it "also requires some declaration arguments when created" do
    expect {
      Oregano::Interface::Option.new(face)
    }.to raise_error ArgumentError, /No option declarations found/
  end

  it "should infer the name from an optparse string" do
    option = Oregano::Interface::Option.new(face, "--foo")
    expect(option.name).to eq(:foo)
  end

  it "should infer the name when multiple optparse string are given" do
    option = Oregano::Interface::Option.new(face, "--foo", "-f")
    expect(option.name).to eq(:foo)
  end

  it "should prefer the first long option name over a short option name" do
    option = Oregano::Interface::Option.new(face, "-f", "--foo")
    expect(option.name).to eq(:foo)
  end

  it "should create an instance when given a face and name" do
    expect(Oregano::Interface::Option.new(face, "--foo")).
      to be_instance_of Oregano::Interface::Option
  end

  Oregano.settings.each do |name, value|
    it "should fail when option #{name.inspect} already exists in oregano core" do
      expect do
        Oregano::Interface::Option.new(face, "--#{name}")
      end.to raise_error ArgumentError, /already defined/
    end
  end

  describe "#to_s" do
    it "should transform a symbol into a string" do
      option = Oregano::Interface::Option.new(face, "--foo")
      expect(option.name).to eq(:foo)
      expect(option.to_s).to eq("foo")
    end

    it "should use - rather than _ to separate words in strings but not symbols" do
      option = Oregano::Interface::Option.new(face, "--foo-bar")
      expect(option.name).to eq(:foo_bar)
      expect(option.to_s).to eq("foo-bar")
    end
  end

  %w{before after}.each do |side|
    describe "#{side} hooks" do
      subject { Oregano::Interface::Option.new(face, "--foo") }
      let :proc do Proc.new do :from_proc end end

      it { is_expected.to respond_to "#{side}_action" }
      it { is_expected.to respond_to "#{side}_action=" }

      it "should set the #{side}_action hook" do
        expect(subject.send("#{side}_action")).to be_nil
        subject.send("#{side}_action=", proc)
        expect(subject.send("#{side}_action")).to be_an_instance_of UnboundMethod
      end

      data = [1, "foo", :foo, Object.new, method(:hash), method(:hash).unbind]
      data.each do |input|
        it "should fail if a #{input.class} is added to the #{side} hooks" do
          expect { subject.send("#{side}_action=", input) }.
            to raise_error ArgumentError, /not a proc/
        end
      end
    end
  end

  context "defaults" do
    subject { Oregano::Interface::Option.new(face, "--foo") }

    it "should work sanely if member variables are used for state" do
      subject.default = proc { @foo ||= 0; @foo += 1 }
      expect(subject.default).to eq(1)
      expect(subject.default).to eq(2)
      expect(subject.default).to eq(3)
    end

    context "with no default" do
      it { is_expected.not_to be_has_default }
      its :default do should be_nil end

      it "should set a proc as default" do
        expect { subject.default = proc { 12 } }.to_not raise_error
      end

      [1, {}, [], Object.new, "foo"].each do |input|
        it "should reject anything but a proc (#{input.class})" do
          expect { subject.default = input }.to raise_error ArgumentError, /not a proc/
        end
      end
    end

    context "with a default" do
      before :each do subject.default = proc { [:foo] } end

      it { is_expected.to be_has_default }
      its :default do should == [:foo] end

      it "should invoke the block every time" do
        expect(subject.default.object_id).not_to eq(subject.default.object_id)
        expect(subject.default).to eq(subject.default)
      end

      it "should allow replacing the default proc" do
        expect(subject.default).to eq([:foo])
        subject.default = proc { :bar }
        expect(subject.default).to eq(:bar)
      end
    end
  end
end
