# frozen_string_literal: true

RSpec.describe Pry::ClassCommand do
  describe ".inherited" do
    context "when match is defined" do
      subject do
        Class.new(described_class) do
          match('match')
        end
      end

      it "sets match on the subclass" do
        subclass = Class.new(subject)
        expect(subclass.match).to eq('match')
      end
    end

    context "when description is defined" do
      subject do
        Class.new(described_class) do
          description('description')
        end
      end

      it "sets description on the subclass" do
        subclass = Class.new(subject)
        expect(subclass.description).to eq('description')
      end
    end

    context "when command_options is defined" do
      subject do
        Class.new(described_class) do
          command_options(listing: 'listing')
        end
      end

      it "sets command_options on the subclass" do
        subclass = Class.new(subject)
        expect(subclass.command_options)
          .to match(hash_including(listing: 'listing'))
      end
    end
  end

  describe ".source" do
    subject { Class.new(described_class) }

    it "returns source code for the process method" do
      expect(subject.source).to match(/\Adef process\n.+\nend\n\z/)
    end
  end

  describe ".doc" do
    subject do
      Class.new(described_class) { banner('banner') }
    end

    it "returns source code for the process method" do
      expect(subject.doc).to eq("banner\n    -h, --help      Show this message.")
    end
  end

  describe ".source_location" do
    subject { Class.new(described_class) }

    it "returns source location" do
      expect(subject.source_location.take(2))
        .to match([/class_command.rb/, be_kind_of(Integer)])
    end
  end

  describe ".source_file" do
    subject { Class.new(described_class) }

    it "returns source file" do
      expect(subject.source_file).to match(/class_command.rb/)
    end
  end

  describe ".source_line" do
    subject { Class.new(described_class) }

    it "returns source file" do
      expect(subject.source_line).to be_kind_of(Integer)
    end
  end

  describe "#call" do
    subject do
      command = Class.new(described_class) do
        def process; end
      end
      command.new
    end

    before { subject.class.banner('banner') }

    it "invokes setup" do
      expect(subject).to receive(:setup)
      expect(subject.call)
    end

    it "sets command's opts" do
      expect { subject.call }.to change { subject.opts }
        .from(nil).to(an_instance_of(Pry::Slop))
    end

    it "sets command's args" do
      expect { subject.call('foo', 'bar') }.to change { subject.args }
        .from(nil).to(%w[foo bar])
    end

    context "when help is invoked" do
      let(:output) { StringIO.new }

      before { subject.output = output }

      it "outputs help info" do
        subject.call('--help')
        expect(subject.output.string)
          .to eq("banner\n    -h, --help      Show this message.\n")
      end

      it "returns void value" do
        expect(subject.call('--help')).to eql(Pry::Command::VOID_VALUE)
      end
    end

    context "when help is not involved" do
      context "when #process accepts no arguments" do
        subject do
          command = Class.new(described_class) do
            def process; end
          end
          command.new
        end

        it "calls the command despite passed arguments" do
          expect { subject.call('foo') }.not_to raise_error
        end
      end

      context "when #process accepts some arguments" do
        subject do
          command = Class.new(described_class) do
            def process(arg, other); end
          end
          command.new
        end

        it "calls the command even if there's not enough arguments" do
          expect { subject.call('foo') }.not_to raise_error
        end

        it "calls the command even if there are more arguments than needed" do
          expect { subject.call('1', '2', '3') }.not_to raise_error
        end
      end

      context "when passed a variable-length array" do
        subject do
          command = Class.new(described_class) do
            def process(arg, other); end
          end
          command.new
        end

        it "calls the command without arguments" do
          expect { subject.call }.not_to raise_error
        end

        it "calls the command with some arguments" do
          expect { subject.call('1', '2', '3') }.not_to raise_error
        end
      end
    end
  end

  describe "#help" do
    subject { Class.new(described_class).new }

    before { subject.class.banner('banner') }

    it "returns help output" do
      expect(subject.help)
        .to eq("banner\n    -h, --help      Show this message.")
    end
  end

  describe "#slop" do
    subject { Class.new(described_class).new }

    before { subject.class.banner('    banner') }

    it "returns a Slop instance" do
      expect(subject.slop).to be_a(Pry::Slop)
    end

    it "makes Slop's banner unindented" do
      slop = subject.slop
      expect(slop.banner).to eq('banner')
    end

    it "defines the help option" do
      expect(subject.slop.fetch_option(:help)).not_to be_nil
    end

    context "when there are subcommands" do
      subject do
        command = Class.new(described_class) do
          def subcommands(cmd)
            cmd.command(:download)
          end
        end
        command.new
      end

      it "adds subcommands to Slop" do
        expect(subject.slop.fetch_command(:download)).not_to be_nil
      end
    end

    context "when there are options" do
      subject do
        command = Class.new(described_class) do
          def options(opt)
            opt.on(:test)
          end
        end
        command.new
      end

      it "adds subcommands to Slop" do
        expect(subject.slop.fetch_option(:test)).not_to be_nil
      end
    end
  end

  describe "#complete" do
    subject do
      command = Class.new(described_class) do
        def options(opt)
          opt.on(:d, :download)
          opt.on(:u, :upload)
          opt.on(:x)
        end
      end
      command.new
    end

    before { subject.class.banner('') }

    it "generates option completions" do
      expect(subject.complete(''))
        .to match(array_including('--download ', '--upload ', '-x'))
    end
  end

  describe "#process" do
    it "raises CommandError" do
      expect { subject.process }
        .to raise_error(Pry::CommandError, /not implemented/)
    end
  end
end
