require 'spec_helper'

describe Belajar::Terminal::Output do
  subject do
    require 'thor'
    class Test < Thor
      include Belajar::Terminal::Output
    end

    Test.new
  end

  before do
    allow($stdout).to receive(:puts) { |string| string }
    allow($stdout).to receive(:print) { |string| string }
  end

  [
    :say,
    :empty_line,
    :get,
    :say_info,
    :say_warning,
    :get_command,
    :get_confirm
  ].each do |method|
    it "has the private method #{method}" do
      expect(subject.private_methods.include?(method)).to be_truthy
    end
  end

  describe '.say' do
    it 'prints the prescribed output to the $stdout' do
      line = 'line'
      expect($stdout).to receive(:puts).with("\t#{line}")
      subject.send(:say, line)
    end

    it 'adds the line start in case of multiline inputs' do
      lines     = "first line\nsecond line\nthird line\n"
      out_lines = lines.split("\n").map { |l| "\t#{l}" }.join("\n")

      expect($stdout).to receive(:puts).with(out_lines)
      subject.send(:say, lines)
    end
  end

  describe '.empty_line' do
    it 'prints an empty line to the $stdout' do
      expect($stdout).to receive(:puts).with('')
      subject.send(:empty_line)
    end
  end

  describe '.get' do
    let(:text) { 'printed' }

    it 'prints a string to $stdout to get a line on $stdin' do
      allow($stdin).to receive(:gets).and_return('received')

      expect($stdout).to receive(:print).with("\n\t#{text} ")
      expect($stdin).to receive(:gets)
      subject.send(:get, text)
    end
  end

  describe '.say_info' do
    let(:line) { 'line' }

    it 'prints the prescribed output to the $stdout' do
      expect($stdout).to receive(:puts).exactly(4).times.with('')
      expect($stdout).to receive(:puts).with("\t" + " ℹ #{line}".light_blue)
      expect($stdout).to receive(:puts).twice.with("\t" + ('-' * 70).light_blue)
      subject.send(:say_info, line)
    end
  end

  describe '.say_warning' do
    let(:line) { 'line' }

    it 'prints the prescribed output to the $stdout' do
      expect($stdout).to receive(:puts).exactly(4).times.with('')
      expect($stdout).to receive(:puts).with("\t" + "⚠  #{line}".light_red)
      expect($stdout).to receive(:puts).twice.with("\t" + ('-' * 70).light_red)
      subject.send(:say_warning, line)
    end
  end

  describe '.get_command' do
    before do
      @correct_command = 'correct command'
      @description = 'description'
      allow($stdin).to receive(:gets).and_return(@correct_command)
      allow(Kernel).to receive(:system) { '' }
    end

    it 'prints a description' do
      expect($stdout).to receive(:puts).once.with("\t#{@description}")
      subject.send(:get_command, @correct_command, @description)
    end

    it 'gets a command from the $stdin' do
      expect($stdin).to receive(:gets)
      subject.send(:get_command, @correct_command, @description)
    end

    context 'with the right command typed in' do
      it 'gets a specified command from the user' do
        subject.send(:get_command, @correct_command, @description)
      end
    end

    context 'with a wrong command typed in' do
      it 'writes a hint' do
        wrong_command = 'wrong command'
        error = "This was something else. Try \"#{@correct_command}\"."

        allow($stdin)
          .to receive(:gets)
          .and_return(wrong_command, @correct_command)

        expect($stdout).to receive(:puts).once.with("\t#{error}")
        subject.send(:get_command, @correct_command, @description)
      end
    end
  end

  describe '.get_confirm' do
    before do
      @description = 'description'
      allow($stdin).to receive(:gets).and_return('yes')
    end

    it 'prints a warning with the given description' do
      expect(subject).to receive(:say_warning).once.with(@description)
      subject.send(:get_confirm, @description)
    end

    it 'gets a command from the $stdin' do
      expect($stdin).to receive(:gets)
      subject.send(:get_confirm, @description)
    end

    it 'takes a block to run when confirmed' do
      allow(subject).to receive(:mocked_method).and_return('mocked method')
      expect(subject).to receive(:mocked_method)
      subject.send(:get_confirm, @description) { subject.mocked_method }
    end

    it 'does not run the given block if not confirmed' do
      allow(subject).to receive(:get).and_return('no')
      expect(subject).not_to receive(:mocked_method)
      subject.send(:get_confirm, @description) { subject.mocked_method }
    end
  end
end