RSpec.shared_examples "a basic configuration" do
  subject(:config) do
    Class.new(described_class) do
      option_accessor :use_test, type: Contracts::Bool,
                                 default: false
    end.new
  end

  it { expect(config).not_to be_nil }

  describe ".option_reader" do
    subject(:config) { config_klass.new }

    let(:config_klass) { Class.new(described_class) }

    before do
      config_klass.option_reader :new_opt, type: Contracts::Num,
                                           default: 1
    end

    context "when value is read" do
      it { expect(config.new_opt).to eq 1 }
    end

    context "when one tries to write a value" do
      it { expect { config.new_opt = 1 }.to raise_error NoMethodError }
    end

    context "when block is defined" do
      before do
        config_klass.option_reader(
          :new_opt2,
          type: Contracts::Num
        ) { |c| c.new_opt.value + 1 }
      end

      it "uses the block to set the value" do
        expect(config.new_opt2).to eq 2
      end
    end

    context "when block and default value is defined" do
      it "complains that only one or the other can be specified" do
        expect do
          config_klass
            .option_accessor(:new_opt2, type: Contracts::Num,
                                        default: 2) { |c| c.new_opt.value + 1 }
        end.to raise_error ArgumentError, "Either use block or default value"
      end
    end
  end

  describe ".option_accessor" do
    subject(:config) { config_klass.new }

    let(:config_klass) { Class.new(described_class) }

    before do
      config_klass.option_accessor :new_opt, type: Contracts::Num,
                                             default: 1
    end

    context "when default is used" do
      it { expect(config.new_opt).to eq 1 }
    end

    context "when is modified" do
      before { config.new_opt = 2 }

      it { expect(config.new_opt).to eq 2 }
    end

    context "when block is defined" do
      before do
        config_klass.option_accessor(
          :new_opt2, type: Contracts::Num
        ) do |c|
          c.new_opt.value + 1
        end
      end

      it "uses the block to set the default value" do
        expect(config.new_opt2).to eq 2
      end
    end

    context "when block and default value is defined" do
      it "complains that only one or the other can be specified" do
        expect do
          config_klass.option_accessor(:new_opt2,
                                       type: Contracts::Num,
                                       default: 2) { |c| c.new_opt1 + 1 }
        end.to raise_error ArgumentError, "Either use block or default value"
      end
    end
  end

  describe "option?" do
    let(:name) { :use_test }

    it "returns true when passed a valid option name" do
      expect(config.option?(:use_test)).to be_truthy
    end

    it "returns false when passed an invalid option name" do
      expect(config.option?(:blub)).to be_falsy
    end
  end

  describe "set_if_option" do
    let(:name) { :use_test }
    let(:value) { true }

    it "sets the option to the given value if a valid option name is passed" do
      expect { config.set_if_option(:use_test, true) }
        .to change(config, :use_test).from(false).to(true)
    end

    it "raises no error if an invalid option name is passed" do
      expect { config.set_if_option(:blub, true) }.not_to raise_error
    end
  end
end
