require 'spec_helper'
require 'command_mapper/option'
require 'command_mapper/types/list'
require 'command_mapper/types/key_value'

describe CommandMapper::Option do
  describe "#initialize" do
    let(:flag) { '--foo' }

    subject { described_class.new(flag) }

    it "must set #flag" do
      expect(subject.flag).to eq(flag)
    end

    context "when a name: keyword argument is given" do
      let(:name) { :bar }

      subject { described_class.new(flag, name: name) }

      it "must set #name" do
        expect(subject.name).to eq(name)
      end
    end

    context "when no name: keyword argument is given" do
      it "must default #name to .infer_name_from_flag" do
        expect(subject.name).to eq(described_class.infer_name_from_flag(flag))
      end
    end

    it "must default #repeats? to false" do
      expect(subject.repeats?).to be(false)
    end

    context "when equals: true is given" do
      subject { described_class.new(flag, equals: true) }

      it "#equals? must return true" do
        expect(subject.equals?).to be(true)
      end
    end

    context "when value_in_flag: true is given" do
      subject { described_class.new(flag, value_in_flag: true) }

      it "#value_in_flag? must return true" do
        expect(subject.value_in_flag?).to be(true)
      end
    end

    context "when given the repeats: true keyword argument" do
      subject { described_class.new(flag, repeats: true) }

      it "#repeats? must be true" do
        expect(subject.repeats?).to be(true)
      end
    end

    context "when value: is given" do
      context "and when value: is true" do
        subject { described_class.new(flag, value: true) }

        it "must initialize #value as an OptionValue" do
          expect(subject.value).to be_kind_of(OptionValue)
        end

        it "must set #value.required? to true" do
          expect(subject.value.required?).to be(true)
        end
      end

      context "and when value: is a Hash" do
        let(:value_required) { true }
        let(:value_type)     { Types::KeyValue.new }
        let(:value_kwargs) do
          {
            required: value_required,
            type:     value_type
          }
        end

        subject { described_class.new(flag, value: value_kwargs) }

        it "must initialize #value as an OptionValue" do
          expect(subject.value).to be_kind_of(OptionValue)
        end

        it "must initialize #value with the value: ... Hash" do
          expect(subject.value.required?).to be(value_required)
          expect(subject.value.type).to eq(value_type)
        end
      end
    end
  end

  describe ".infer_name_from_flag" do
    subject { described_class.infer_name_from_flag(flag) }

    context "when given a long flag" do
      let(:name) { "foo"       }
      let(:flag) { "--#{name}" }

      it "must return the flag name as a Symbol" do
        expect(subject).to eq(name.to_sym)
      end

      context "when the flag contains a '-'" do
        let(:flag) { '--foo-bar' }

        it "must convert all '-' characters to '_'" do
          expect(subject).to eq(:foo_bar)
        end
      end

      context "when the flag contains multiple '-'" do
        let(:flag) { '--foo--bar' }

        it "must replace multiple '-' characters with a single '_'" do
          expect(subject).to eq(:foo_bar)
        end
      end

      context "when the flag contains multiple '_'" do
        let(:flag) { '--foo__bar' }

        it "must replace multiple '_' characters with a single '_'" do
          expect(subject).to eq(:foo_bar)
        end
      end
    end

    context "when given a short flag" do
      context "when the flag length is 1" do
        let(:flag) { '-x' }

        it "must raise an ArgumentError" do
          expect {
            described_class.infer_name_from_flag(flag)
          }.to raise_error(ArgumentError,"cannot infer a name from short option flag: #{flag.inspect}")
        end
      end

      context "when the flag length is 2" do
        let(:flag) { '-ip' }

        it "must return the flag name without the '-'" do
          expect(subject).to eq(:ip)
        end
      end

      context "when the flag contains uppercase characters" do
        let(:flag) { '-Ip' }

        it "must convert all uppercase characters to lowercase" do
          expect(subject).to eq(:ip)
        end
      end

      context "when the flag length is > 2" do
        context "when the flag contains a '-'" do
          let(:flag) { '-foo-bar' }

          it "must convert all '-' characters to '_'" do
            expect(subject).to eq(:foo_bar)
          end
        end

        context "when the flag contains multiple '-'" do
          let(:flag) { '-foo--bar' }

          it "must replace multiple '-' characters with a single '_'" do
            expect(subject).to eq(:foo_bar)
          end
        end

        context "when the flag contains multiple '_'" do
          let(:flag) { '-foo__bar' }

          it "must replace multiple '_' characters with a single '_'" do
            expect(subject).to eq(:foo_bar)
          end
        end
      end
    end
  end

  describe "#accepts_value?" do
    context "when initialized with the value: keyword argument" do
      context "and when value: is true" do
        subject { described_class.new(flag, value: true) }

        it "must return true" do
          expect(subject.accepts_value?).to be(true)
        end
      end

      context "and when value: is a Hash" do
        let(:value_required) { true }
        let(:value_type)     { Types::KeyValue.new }
        let(:value_kwargs) do
          {
            required: value_required,
            type:     value_type
          }
        end

        subject { described_class.new(flag, value: value_kwargs) }

        it "must return true" do
          expect(subject.accepts_value?).to be(true)
        end
      end
    end

    context "when not initialied with the value: keyword argument" do
      subject { described_class.new(flag) }

      it "must return false" do
        expect(subject.accepts_value?).to be(false)
      end
    end
  end

  describe "#equals?" do
    context "when initialized with equals: true" do
      subject { described_class.new(flag, value: true, equals: true) }

      it "must return true" do
        expect(subject.equals?).to be(true)
      end
    end

    context "when not initialized with equals: true" do
      subject { described_class.new(flag, value: true) }

      it "must return nil" do
        expect(subject.equals?).to be(nil)
      end
    end
  end

  describe "#repeats?" do
    context "when initialized with repeats: true" do
      subject { described_class.new(flag, value: true, repeats: true) }

      it "must return true" do
        expect(subject.repeats?).to be(true)
      end
    end

    context "when not initialized with repeats: true" do
      subject { described_class.new(flag, value: true) }

      it "must return false" do
        expect(subject.repeats?).to be(false)
      end
    end
  end

  describe "#value_in_flag?" do
    context "when initialized with value_in_flag: true" do
      subject { described_class.new(flag, value: true, value_in_flag: true) }

      it "must return true" do
        expect(subject.value_in_flag?).to be(true)
      end
    end

    context "when not initialized with value_in_flag: true" do
      subject { described_class.new(flag, value: true) }

      it "must return nil" do
        expect(subject.value_in_flag?).to be(nil)
      end
    end
  end

  let(:flag) { "--opt" }
  let(:name) { "opt" }

  let(:repeats) { false }
  let(:accepts_value) { false }

  let(:value_required) { true }
  let(:value_allows_empty) { false }
  let(:value_allows_blank) { false }
  let(:value_type) do
    {
      allow_empty: value_allows_empty,
      allow_blank: value_allows_blank
    }
  end
  let(:value_kwargs) do
    {
      required: value_required,
      type:     value_type
    }
  end

  let(:equals) { nil }
  let(:value_in_flag) { nil }

  subject do
    if accepts_value
      described_class.new(flag, name:    name,
                                value:   value_kwargs,
                                repeats: repeats,
                                # formatting options
                                equals:        equals,
                                value_in_flag: value_in_flag)
    else
      described_class.new(flag, name: name, repeats: repeats)
    end
  end

  describe "#validate" do
    context "when the option accepts a value" do
      let(:accepts_value)  { true }
      let(:value_required) { true }

      context "when the option can be specified multiple times" do
        let(:repeats) { true }

        context "and is given an Array" do
          context "and all elements of the Array are Strings" do
            let(:values) { %w[foo bar baz] }

            it "must return true" do
              expect(subject.validate(values)).to be(true)
            end
          end

          context "but one of the Array's elements is nil" do
            let(:values) { ["foo", nil, "bar"] }

            it "must return [false, \"does not accept a nil value\"]" do
              expect(subject.validate(values)).to eq(
                [false, "does not accept a nil value"]
              )
            end

            context "but #value.required? is false" do
              let(:value_required) { false }

              it "must return true" do
                expect(subject.validate(values)).to be(true)
              end
            end
          end

          context "but the Array contains Hashes" do
            let(:values) do
              [{"foo" => 1}, {"bar" => 2 }]
            end

            it "must return [false, \"cannot convert a Hash into a String (...)\"]" do
              expect(subject.validate(values)).to eq(
                [false, "cannot convert a Hash into a String (#{values[0].inspect})"]
              )
            end

            context "but #value.type is a Types::KeyValue object" do
              let(:value_type) { Types::KeyValue.new }

              let(:values) do
                [{"foo" => 1}, {"bar" => 2 }]
              end

              it "must return true" do
                expect(subject.validate(values)).to be(true)
              end

              context "but one of the Hashes is empty" do
                let(:values) do
                  [{"foo" => 1}, {}]
                end

                it "must return [false, \"requires at least one value\"]" do
                  expect(subject.validate(values)).to eq(
                    [false, "cannot be empty"]
                  )
                end
              end
            end
          end

          context "but it's empty" do
            let(:value) { [] }

            it "must return [false, \"requires at least one value\"]" do
              expect(subject.validate(value)).to eq(
                [false, "requires at least one value"]
              )
            end
          end
        end

        context "and it's only given one value" do
          context "and it's a String" do
            let(:value) { "foo" }

            it "must return true" do
              expect(subject.validate(value)).to be(true)
            end
          end

          context "and it's a Hash" do
            let(:value) do
              {"foo" => "bar"}
            end

            it "must return [false, \"cannot convert a Hash into a String (...)\"]" do
              expect(subject.validate(value)).to eq(
                [false, "cannot convert a Hash into a String (#{value.inspect})"]
              )
            end

            context "but #value.type is a Types::KeyValue object" do
              let(:value_type) { Types::KeyValue.new }

              it "must return true" do
                expect(subject.validate(value)).to be(true)
              end

              context "but it's empty" do
                let(:value) { {} }

                it "must return [false, \"cannot be empty\"]" do
                  expect(subject.validate(value)).to eq(
                    [false, "cannot be empty"]
                  )
                end
              end
            end
          end
        end
      end

      context "when the option can only be specified once" do
        let(:repeats) { false }

        context "and is given a String" do
          let(:value) { "foo" }

          it "must return true" do
            expect(subject.validate(value)).to be(true)
          end
        end

        context "and is given an Array" do
          let(:value) { [1,2,3,4] }

          it "must return [false, \"cannot convert a Array into a String (...)\"]" do
            expect(subject.validate(value)).to eq(
              [false, "cannot convert a Array into a String (#{value.inspect})"]
            )
          end

          context "when #value.type is a Types::List object" do
            let(:value_type) { Types::List.new }

            it "must return true" do
              expect(subject.validate(value)).to be(true)
            end

            context "but one of the Array elements is nil" do
              let(:value) { [1,2,nil,4] }

              it "must return [false, \"element cannot be nil\"]" do
                expect(subject.validate(value)).to eq(
                  [false, "element cannot be nil"]
                )
              end
            end

            context "but it's empty" do
              let(:value) { [] }

              it "must return [false, \"cannot be empty\"]" do
                expect(subject.validate(value)).to eq(
                  [false, "cannot be empty"]
                )
              end
            end
          end
        end

        context "and it's a Hash" do
          let(:value) do
            {"foo" => "bar"}
          end

          it "must return [false, \"cannot convert a Hash into a String (...)\"]" do
            expect(subject.validate(value)).to eq(
              [false, "cannot convert a Hash into a String (#{value.inspect})"]
            )
          end

          context "but #value.type is a Types::KeyValue object" do
            let(:value_type) { Types::KeyValue.new }

            it "must return true" do
              expect(subject.validate(value)).to be(true)
            end

            context "but it's empty" do
              let(:value) { {} }

              it "must return [false, \"cannot be empty\"]" do
                expect(subject.validate(value)).to eq(
                  [false, "cannot be empty"]
                )
              end
            end
          end
        end
      end
    end

    context "when the option does not accept a value" do
      let(:allows_value) { false }

      shared_examples "and a Boolean is given" do
        context "and is given true" do
          let(:value) { true }

          it "must return true" do
            expect(subject.validate(value)).to be(true)
          end
        end

        context "and is given false" do
          let(:value) { false }

          it "must return true" do
            expect(subject.validate(value)).to be(true)
          end
        end

        context "and is given nil" do
          let(:value) { nil }

          it "must return true" do
            expect(subject.validate(value)).to be(true)
          end
        end

      end

      context "and when the option can be repeated" do
        let(:repeats) { true }

        context "and is given true" do
          let(:value) { true }

          it "must return true" do
            expect(subject.validate(value)).to be(true)
          end
        end

        context "and is given false" do
          let(:value) { false }

          it "must return true" do
            expect(subject.validate(value)).to be(true)
          end
        end

        context "and is given nil" do
          let(:value) { nil }

          it "must return true" do
            expect(subject.validate(value)).to be(true)
          end
        end

        context "and is given an Integer" do
          let(:value) { 3 }

          it "must return true" do
            expect(subject.validate(value)).to be(true)
          end
        end
      end

      context "but the option can only be specified once" do
        let(:repeats) { false }

        context "is given an Integer" do
          let(:value) { 3 }

          it "must return false and a validation error message" do
            expect(subject.validate(value)).to eq(
              [false, "only repeating options may accept Integers"]
            )
          end
        end
      end
    end
  end

  describe "#argv" do
    context "when the option accepts a value" do
      let(:accepts_value) { true }

      context "when the option can be specified multiple times" do
        let(:repeats) { true }

        context "and it's given an Array" do
          context "and all elements of the Array are Strings" do
            let(:values) { %w[foo bar baz] }

            it "must return an argv of the option flags followed by values" do
              expect(subject.argv(values)).to eq(
                [
                  flag, values[0],
                  flag, values[1],
                  flag, values[2]
                ]
              )
            end

            context "and #equals? is true" do
              let(:equals) { true }

              it "must return an argv of option flag=value Strings" do
                expect(subject.argv(values)).to eq(
                  [
                    "#{flag}=#{values[0]}",
                    "#{flag}=#{values[1]}",
                    "#{flag}=#{values[2]}"
                  ]
                )
              end
            end

            context "and #value_in_flag? is true" do
              let(:value_in_flag) { true }

              it "must return an argv of option flag + value Strings" do
                expect(subject.argv(values)).to eq(
                  [
                    "#{flag}#{values[0]}",
                    "#{flag}#{values[1]}",
                    "#{flag}#{values[2]}"
                  ]
                )
              end
            end

            context "but one of the Array's elements is invalid" do
              let(:value) { ["foo", " ", "baz"] }

              it do
                expect {
                  subject.argv(value)
                }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{value.inspect}): does not allow a blank value (#{value[1].inspect})")
              end
            end

            context "but one of the Array's elements starts with a '-'" do
              let(:value) { ["foo", "-bar", "baz"] }

              it do
                expect {
                  subject.argv(value)
                }.to raise_error(ValidationError,"option #{name} formatted value (#{value[1].inspect}) cannot start with a '-'")
              end
            end
          end

          context "but one of the Array's elements is true" do
            let(:values) { ["foo", true, "bar"] }

            context "but #value.required? is false" do
              let(:value_required) { false }

              it "must only emit the option's flag for true values" do
                expect(subject.argv(values)).to eq(
                  [
                    flag, values[0],
                    flag,
                    flag, values[2]
                  ]
                )
              end
            end
          end

          context "but the Array contains Hashes" do
            let(:values) do
              [{"foo" => 1}, {"bar" => 2 }]
            end

            it do
              expect {
                subject.argv(values)
              }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{values.inspect}): cannot convert a Hash into a String (#{values[0].inspect})")
            end

            context "but #value.type is a Types::KeyValue object" do
              let(:value_type) { Types::KeyValue.new }

              it "must format each value using #value.format" do
                expect(subject.argv(values)).to eq(
                  [
                    flag, subject.value.format(values[0]),
                    flag, subject.value.format(values[1])
                  ]
                )
              end

              context "and #equals? is true" do
                let(:equals) { true }

                it "must return an argv of option flag=value Strings" do
                  expect(subject.argv(values)).to eq(
                    [
                      "#{flag}=#{subject.value.format(values[0])}",
                      "#{flag}=#{subject.value.format(values[1])}"
                    ]
                  )
                end
              end

              context "and #value_in_flag? is true" do
                let(:value_in_flag) { true }

                it "must return an argv of option flag + value Strings" do
                  expect(subject.argv(values)).to eq(
                    [
                      "#{flag}#{subject.value.format(values[0])}",
                      "#{flag}#{subject.value.format(values[1])}"
                    ]
                  )
                end
              end

              context "but one of the Hashes is empty" do
                let(:values) do
                  [{"foo" => 1}, {}]
                end

                it do
                  expect {
                    subject.argv(values)
                  }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{values.inspect}): cannot be empty")
                end
              end

              context "but one of the Hash's keys starts with a '-'" do
                let(:value) { [{"foo" => 1}, {"-bar" => 2 }] }

                it do
                  expect {
                    subject.argv(value)
                  }.to raise_error(ValidationError,"option #{name} formatted value (\"-bar=2\") cannot start with a '-'")
                end
              end
            end
          end

          context "but it's empty" do
            let(:value) { [] }

            it do
              expect {
                subject.argv(value)
              }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{value.inspect}): requires at least one value")
            end
          end
        end

        context "and it's only given one value" do
          context "and it's a String" do
            let(:value) { "foo" }

            it "must return an argv only containing the option flag and value" do
              expect(subject.argv(value)).to eq([flag, value])
            end

            context "and #equals? is true" do
              let(:equals) { true }

              it "must return an argv containing an option flag=value String" do
                expect(subject.argv(value)).to eq(["#{flag}=#{value}"])
              end
            end

            context "and #value_in_flag? is true" do
              let(:value_in_flag) { true }

              it "must return an argv containing an option flag + value String" do
                expect(subject.argv(value)).to eq(["#{flag}#{value}"])
              end
            end

            context "but it's invalid" do
              let(:value) { " " }

              it do
                expect {
                  subject.argv(value)
                }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{value.inspect}): does not allow a blank value (#{value.inspect})")
              end
            end

            context "but it starts with a '-'" do
              let(:value) { "-foo" }

              it do
                expect {
                  subject.argv(value)
                }.to raise_error(ValidationError,"option #{name} formatted value (#{value.inspect}) cannot start with a '-'")
              end
            end
          end

          context "and it's a Hash" do
            let(:value) do
              {"foo" => "bar"}
            end

            context "but #value.type is a Types::KeyValue object" do
              let(:value_type) { Types::KeyValue.new }

              it "must format the value using #value.format" do
                expect(subject.argv(value)).to eq(
                  [flag, subject.value.format(value)]
                )
              end

              context "and #equals? is true" do
                let(:equals) { true }

                it "must return an argv containing an option flag=value String" do
                  expect(subject.argv(value)).to eq(
                    ["#{flag}=#{subject.value.format(value)}"]
                  )
                end
              end

              context "and #value_in_flag? is true" do
                let(:value_in_flag) { true }

                it "must return an argv of option flag + value Strings" do
                  expect(subject.argv(value)).to eq(
                    ["#{flag}#{subject.value.format(value)}"]
                  )
                end
              end

              context "but it's empty" do
                let(:value) { {} }

                it do
                  expect {
                    subject.argv(value)
                  }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{value.inspect}): cannot be empty")
                end
              end

              context "but the key starts with a '-'" do
                let(:value) do
                  {"-foo" => "bar"}
                end

                it do
                  expect {
                    subject.argv(value)
                  }.to raise_error(ValidationError,"option #{name} formatted value (\"-foo=bar\") cannot start with a '-'")
                end
              end
            end
          end
        end
      end

      context "when the option can only be specified once" do
        let(:repeats) { false }

        context "and it's true" do
          let(:value) { true }

          context "but #value.required? is false" do
            let(:value_required) { false }

            it "must only emit the option's flag for true values" do
              expect(subject.argv(value)).to eq([flag])
            end
          end
        end

        context "and it's a String" do
          let(:value) { "foo" }

          it "must return an argv only containing the option flag and value" do
            expect(subject.argv(value)).to eq([flag, value])
          end

          context "and #equals? is true" do
            let(:equals) { true }

            it "must return an argv containing an option flag=value String" do
              expect(subject.argv(value)).to eq(["#{flag}=#{value}"])
            end
          end

          context "and #value_in_flag? is true" do
            let(:value_in_flag) { true }

            it "must return an argv containing an option flag + value String" do
              expect(subject.argv(value)).to eq(["#{flag}#{value}"])
            end
          end

          context "but it's invalid" do
            let(:value) { " " }

            it do
              expect {
                subject.argv(value)
              }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{value.inspect}): does not allow a blank value (#{value.inspect})")
            end
          end

          context "but it starts with a '-'" do
            let(:value) { "-foo" }

            it do
              expect {
                subject.argv(value)
              }.to raise_error(ValidationError,"option #{name} formatted value (#{value.inspect}) cannot start with a '-'")
            end
          end
        end

        context "and it's an Array" do
          let(:value) { %w[foo bar baz] }

          it do
            expect {
              subject.argv(value)
            }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{value.inspect}): cannot convert a Array into a String (#{value.inspect})")
          end

          context "but #value.type is a Types::List object" do
            let(:value_type) { Types::List.new }

            it "must format the value using #value.format" do
              expect(subject.argv(value)).to eq(
                [flag, subject.value.format(value)]
              )
            end

            context "and #equals? is true" do
              let(:equals) { true }

              it "must return an argv containing an option flag=value String" do
                expect(subject.argv(value)).to eq(
                  ["#{flag}=#{subject.value.format(value)}"]
                )
              end
            end

            context "and #value_in_flag? is true" do
              let(:value_in_flag) { true }

              it "must return an argv containing an option flag + value String" do
                expect(subject.argv(value)).to eq(
                  ["#{flag}#{subject.value.format(value)}"]
                )
              end
            end

            context "but one of the Array's elements is nil" do
              let(:value) { ["foo", nil, "baz"] }

              it do
                expect {
                  subject.argv(value)
                }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{value.inspect}): element cannot be nil")
              end
            end

            context "but it's empty" do
              let(:value) { [] }

              it do
                expect {
                  subject.argv(value)
                }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{value.inspect}): cannot be empty")
              end
            end

            context "but the first element starts with a '-'" do
              let(:value) { ["-foo", "bar", "baz"] }

              it do
                expect {
                  subject.argv(value)
                }.to raise_error(ValidationError,"option #{name} formatted value (#{value.join(',').inspect}) cannot start with a '-'")
              end
            end
          end
        end

        context "and it's a Hash" do
          let(:value) do
            {"foo" => "bar"}
          end

          it do
            expect {
              subject.argv(value)
            }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{value.inspect}): cannot convert a Hash into a String (#{value.inspect})")
          end

          context "but #value.type is a Types::KeyValue object" do
            let(:value_type) { Types::KeyValue.new }

            it "must format the value using #value.format" do
              expect(subject.argv(value)).to eq(
                [flag, subject.value.format(value)]
              )
            end

            context "and #equals? is true" do
              let(:equals) { true }

              it "must return an argv containing an option flag=value String" do
                expect(subject.argv(value)).to eq(
                  ["#{flag}=#{subject.value.format(value)}"]
                )
              end
            end

            context "and #value_in_flag? is true" do
              let(:value_in_flag) { true }

              it "must return an argv of option flag + value Strings" do
                expect(subject.argv(value)).to eq(
                  ["#{flag}#{subject.value.format(value)}"]
                )
              end
            end

            context "but it's empty" do
              let(:value) { {} }

              it do
                expect {
                  subject.argv(value)
                }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{value.inspect}): cannot be empty")
              end
            end

            context "but the first key starts with a '-'" do
              let(:value) do
                {"-foo" => "bar"}
              end

              it do
                expect {
                  subject.argv(value)
                }.to raise_error(ValidationError,"option #{name} formatted value (\"-foo=bar\") cannot start with a '-'")
              end
            end
          end
        end
      end
    end

    context "when the option does not accept a value" do
      let(:accepts_value) { false }

      context "and is given true" do
        it "must return an argv only containing the option's flag" do
          expect(subject.argv(true)).to eq([flag])
        end
      end

      context "and is given false" do
        it "must return an empty argv" do
          expect(subject.argv(false)).to eq([])
        end
      end

      context "and is given nil" do
        it "must return an empty argv" do
          expect(subject.argv(nil)).to eq([])
        end
      end

      context "and it's given a non-Boolean value" do
        let(:value) { "foo" }
        let(:message) { "only accepts true, false, or nil" }

        it do
          expect {
            subject.argv(value)
          }.to raise_error(ValidationError,"option #{name} was given an invalid value (#{value.inspect}): #{message}")
        end
      end

      context "and when the option can be specified multiple times" do
        let(:repeats) { true }

        context "and is given an Integer" do
          let(:value) { 3 }

          it "must return an argv containing multiple instances of the flag" do
            expect(subject.argv(value)).to eq([flag] * value)
          end
        end
      end
    end

    context "when given an argv array and a value" do
      let(:accepts_value) { true  }
      let(:value)         { "foo" }

      let(:argv) { [] }

      before { subject.argv(argv,value) }

      it "must concat the args to the argv array" do
        expect(argv).to eq([flag, value])
      end
    end
  end
end