#!/usr/bin/env ruby
# coding: utf-8

# tc_highline.rb
#
#  Created by James Edward Gray II on 2005-04-26.
#  Copyright 2005 Gray Productions. All rights reserved.
#
#  This is Free Software.  See LICENSE and COPYING for details.

require "test_helper"

require "highline"
require "stringio"
require "tempfile"

# if HighLine::CHARACTER_MODE == "Win32API"
#   class HighLine
#     # Override Windows' character reading so it's not tied to STDIN.
#     def get_character( input = STDIN )
#       input.getc
#     end
#   end
# end

class TestHighLine < Minitest::Test
  def setup
    HighLine.reset
    @input    = StringIO.new
    @output   = StringIO.new
    @terminal = HighLine.new(@input, @output)
  end

  def test_agree_valid_yes_answers
    valid_yes_answers = %w[y yes Y YES]

    valid_yes_answers.each do |user_input|
      @input << "#{user_input}\n"
      @input.rewind

      assert_equal true, @terminal.agree("Yes or no?  ")
      assert_equal "Yes or no?  ", @output.string

      @input.truncate(@input.rewind)
      @output.truncate(@output.rewind)
    end
  end

  def test_agree_valid_no_answers
    valid_no_answers = %w[n no N NO]

    valid_no_answers.each do |user_input|
      @input << "#{user_input}\n"
      @input.rewind

      assert_equal false, @terminal.agree("Yes or no?  ")
      assert_equal "Yes or no?  ", @output.string

      @input.truncate(@input.rewind)
      @output.truncate(@output.rewind)
    end
  end

  def test_agree_invalid_answers
    invalid_answers = ["ye", "yuk", "nope", "Oh yes", "Oh no", "Hell no!"]

    invalid_answers.each do |user_input|
      # Each invalid answer, should be followed by a 'y'
      # (as the question is reasked)
      @input << "#{user_input}\ny\n"
      @input.rewind

      assert_equal true, @terminal.agree("Yes or no?  ")

      # It reasks the question if the answer is invalid
      assert_equal "Yes or no?  Please enter \"yes\" or \"no\".\nYes or no?  ",
                   @output.string

      @input.truncate(@input.rewind)
      @output.truncate(@output.rewind)
    end
  end

  def test_agree_with_getc
    @input.truncate(@input.rewind)
    @input << "yellow"
    @input.rewind

    assert_equal(true, @terminal.agree("Yes or no?  ", :getc))
  end

  def test_agree_with_block
    @input << "\n\n"
    @input.rewind

    assert_equal(true, @terminal.agree("Yes or no?  ") { |q| q.default = "y" })
    assert_equal(false, @terminal.agree("Yes or no?  ") { |q| q.default = "n" })
  end

  def test_ask
    name = "James Edward Gray II"
    @input << name << "\n"
    @input.rewind

    assert_equal(name, @terminal.ask("What is your name?  "))

    assert_raises(EOFError) { @terminal.ask("Any input left?  ") }
  end

  def test_ask_string
    name = "James Edward Gray II"
    @input << name << "\n"
    @input.rewind

    assert_equal(name, @terminal.ask("What is your name?  ", String))

    assert_raises(EOFError) { @terminal.ask("Any input left?  ", String) }
  end

  def test_ask_string_converting
    name = "James Edward Gray II"
    @input << name << "\n"
    @input.rewind

    answer = @terminal.ask("What is your name?  ", String)

    assert_instance_of HighLine::String, answer

    @input.rewind

    answer = @terminal.ask("What is your name?  ", HighLine::String)

    assert_instance_of HighLine::String, answer

    assert_raises(EOFError) do
      @terminal.ask("Any input left?  ", HighLine::String)
    end
  end

  def test_indent
    text = "Testing...\n"
    @terminal.indent_level = 1
    @terminal.say(text)
    assert_equal(" " * 3 + text, @output.string)

    @output.truncate(@output.rewind)
    @terminal.indent_level = 3
    @terminal.say(text)
    assert_equal(" " * 9 + text, @output.string)

    @output.truncate(@output.rewind)
    @terminal.indent_level = 0
    @terminal.indent_size = 5
    @terminal.indent(2, text)
    assert_equal(" " * 10 + text, @output.string)

    @output.truncate(@output.rewind)
    @terminal.indent_level = 0
    @terminal.indent_size = 4
    @terminal.indent do
      @terminal.say(text)
    end
    assert_equal(" " * 4 + text, @output.string)

    @output.truncate(@output.rewind)
    @terminal.indent_size = 2
    @terminal.indent(3) do |t|
      t.say(text)
    end
    assert_equal(" " * 6 + text, @output.string)

    @output.truncate(@output.rewind)
    @terminal.indent do |t|
      t.indent do
        t.indent do
          t.indent do |tt|
            tt.say(text)
          end
        end
      end
    end
    assert_equal(" " * 8 + text, @output.string)

    text = "Multi\nLine\nIndentation\n"
    indent = " " * 4
    @terminal.indent_level = 2
    @output.truncate(@output.rewind)
    @terminal.say(text)
    assert_equal("#{indent}Multi\n#{indent}Line\n#{indent}Indentation\n",
                 @output.string)

    @output.truncate(@output.rewind)
    @terminal.multi_indent = false
    @terminal.say(text)
    assert_equal("#{indent}Multi\nLine\nIndentation\n", @output.string)

    @output.truncate(@output.rewind)
    @terminal.indent(0, text, true)
    assert_equal("#{indent}Multi\n#{indent}Line\n#{indent}Indentation\n",
                 @output.string)
  end

  def test_newline
    @terminal.newline
    @terminal.newline
    assert_equal("\n\n", @output.string)
  end

  def test_bug_fixes
    # auto-complete bug
    @input << "ruby\nRuby\n"
    @input.rewind

    languages = [:Perl, :Python, :Ruby]
    answer = @terminal.ask("What is your favorite programming language?  ",
                           languages)
    assert_equal(languages.last, answer)

    @input.truncate(@input.rewind)
    @input << "ruby\n"
    @input.rewind

    answer = @terminal.ask("What is your favorite programming language?  ",
                           languages) do |q|
      q.case = :capitalize
    end
    assert_equal(languages.last, answer)

    # poor auto-complete error message
    @input.truncate(@input.rewind)
    @input << "lisp\nruby\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("What is your favorite programming language?  ",
                           languages) do |q|
      q.case = :capitalize
    end
    assert_equal(languages.last, answer)
    assert_equal("What is your favorite programming language?  " \
                  "You must choose one of [Perl, Python, Ruby].\n" \
                  "?  ", @output.string)
  end

  def test_case_changes
    @input << "jeg2\n"
    @input.rewind

    answer = @terminal.ask("Enter your initials  ") do |q|
      q.case = :up
    end
    assert_equal("JEG2", answer)

    @input.truncate(@input.rewind)
    @input << "cRaZY\n"
    @input.rewind

    answer = @terminal.ask("Enter a search string:  ") do |q|
      q.case = :down
    end
    assert_equal("crazy", answer)
  end

  def test_ask_with_overwrite
    @input << "Yes, sure!\n"
    @input.rewind

    answer = @terminal.ask("Do you like Ruby? ") do |q|
      q.overwrite = true
      q.echo      = false
    end

    assert_equal("Yes, sure!", answer)
    erase_sequence = "\r#{HighLine.Style(:erase_line).code}"
    assert_equal("Do you like Ruby? #{erase_sequence}", @output.string)
  end

  def test_ask_with_overwrite_and_character_mode
    @input << "Y"
    @input.rewind

    answer = @terminal.ask("Do you like Ruby (Y/N)? ") do |q|
      q.overwrite = true
      q.echo      = false
      q.character = true
    end

    assert_equal("Y", answer)
    erase_sequence = "\r#{HighLine.Style(:erase_line).code}"
    assert_equal("Do you like Ruby (Y/N)? #{erase_sequence}", @output.string)
  end

  def test_character_echo
    @input << "password\r"
    @input.rewind

    answer = @terminal.ask("Please enter your password:  ") do |q|
      q.echo = "*"
    end
    assert_equal("password", answer)
    assert_equal("Please enter your password:  ********\n", @output.string)

    @input.truncate(@input.rewind)
    @input << "2"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Select an option (1, 2 or 3):  ",
                           Integer) do |q|
      q.echo      = "*"
      q.character = true
    end
    assert_equal(2, answer)
    assert_equal("Select an option (1, 2 or 3):  *\n", @output.string)
  end

  def test_backspace_does_not_enter_prompt
    @input << "\b\b"
    @input.rewind
    answer = @terminal.ask("Please enter your password: ") do |q|
      q.echo = "*"
    end
    assert_equal("", answer)
    assert_equal("Please enter your password: \n", @output.string)
  end

  def test_after_some_chars_backspace_does_not_enter_prompt_when_ascii
    @input << "apple\b\b\b\b\b\b\b\b\b\b"
    @input.rewind
    answer = @terminal.ask("Please enter your password: ") do |q|
      q.echo = "*"
    end
    assert_equal("", answer)

    # There's only enough backspaces to clear the given string
    assert_equal(5, @output.string.count("\b"))
  end

  def test_after_some_chars_backspace_does_not_enter_prompt_when_utf8
    @input << "maçã\b\b\b\b\b\b\b\b"
    @input.rewind
    answer = @terminal.ask("Please enter your password: ") do |q|
      q.echo = "*"
    end
    assert_equal("", answer)

    # There's only enough backspaces to clear the given string
    assert_equal(4, @output.string.count("\b"))
  end

  def test_erase_line_does_not_enter_prompt
    @input << "\cU\cU\cU\cU\cU\cU\cU\cU\cU"
    @input.rewind
    answer = @terminal.ask("Please enter your password: ") do |q|
      q.echo = "*"
    end
    assert_equal("", answer)
    assert_equal("Please enter your password: \n", @output.string)

    # There's no backspaces as the line is already empty
    assert_equal(0, @output.string.count("\b"))
  end

  def test_after_some_chars_erase_line_does_not_enter_prompt_when_ascii
    @input << "apple\cU\cU\cU\cU\cU\cU\cU\cU"
    @input.rewind
    answer = @terminal.ask("Please enter your password: ") do |q|
      q.echo = "*"
    end
    assert_equal("", answer)

    # There's only enough backspaces to clear the given string
    assert_equal(5, @output.string.count("\b"))
  end


  def test_after_some_chars_erase_line_does_not_enter_prompt_when_utf8
    @input << "maçã\cU\cU\cU\cU\cU\cU\cU\cU"
    @input.rewind
    answer = @terminal.ask("Please enter your password: ") do |q|
      q.echo = "*"
    end
    assert_equal("", answer)

    # There's only enough backspaces to clear the given string
    assert_equal(4, @output.string.count("\b"))
  end

  def test_character_reading
    # WARNING:  This method does NOT cover Unix and Windows savvy testing!
    @input << "12345"
    @input.rewind

    answer = @terminal.ask("Enter a single digit:  ", Integer) do |q|
      q.character = :getc
    end
    assert_equal(1, answer)
  end

  def test_frozen_statement
    @terminal.say("This is a frozen statement".freeze)
    assert_equal("This is a frozen statement\n", @output.string)
  end

  def test_color
    @terminal.say("This should be <%= BLUE %>blue<%= CLEAR %>!")
    assert_equal("This should be \e[34mblue\e[0m!\n", @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("This should be " \
                   "<%= BOLD + ON_WHITE %>bold on white<%= CLEAR %>!")
    assert_equal("This should be \e[1m\e[47mbold on white\e[0m!\n",
                 @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be \e[36mcyan\e[0m!\n", @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("This should be " \
                   "<%= color('blinking on red', :blink, :on_red) %>!")
    assert_equal("This should be \e[5m\e[41mblinking on red\e[0m!\n",
                 @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("This should be <%= NONE %>none<%= CLEAR %>!")
    assert_equal("This should be \e[38mnone\e[0m!\n", @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("This should be <%= RGB_906030 %>rgb_906030<%= CLEAR %>!")
    assert_equal("This should be \e[38;5;137mrgb_906030\e[0m!\n",
                 @output.string)

    @output.truncate(@output.rewind)

    @terminal.say(
      "This should be <%= ON_RGB_C06030 %>on_rgb_c06030<%= CLEAR %>!"
    )
    assert_equal("This should be \e[48;5;173mon_rgb_c06030\e[0m!\n",
                 @output.string)

    # Relying on const_missing
    assert_instance_of HighLine::Style, HighLine::ON_RGB_C06031_STYLE
    assert_instance_of String, HighLine::ON_RGB_C06032
    assert_raises(NameError) { HighLine::ON_RGB_ZZZZZZ }

    # Retrieving color_code from a style
    assert_equal "\e[41m", @terminal.color_code([HighLine::ON_RED_STYLE])

    @output.truncate(@output.rewind)

    # Does class method work, too?
    @terminal.say(
      "This should be <%= HighLine.color('reverse underlined magenta', " \
      ":reverse, :underline, :magenta) %>!"
    )

    assert_equal(
      "This should be \e[7m\e[4m\e[35mreverse underlined magenta\e[0m!\n",
      @output.string
    )

    @output.truncate(@output.rewind)

    # turn off color
    old_setting = HighLine.use_color?
    HighLine.use_color = false
    @terminal.use_color = false
    @terminal.say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be cyan!\n", @output.string)
    HighLine.use_color = old_setting
    @terminal.use_color = old_setting
  end

  def test_color_setting_per_instance
    require "highline/import"
    old_glob_instance = HighLine.default_instance
    old_setting = HighLine.use_color?

    gterm_input = StringIO.new
    gterm_output = StringIO.new
    HighLine.default_instance = HighLine.new(gterm_input, gterm_output)

    # It can set coloring at HighLine class
    cli_input = StringIO.new
    cli_output = StringIO.new

    cli = HighLine.new(cli_input, cli_output)

    # Testing with both use_color setted to true
    HighLine.use_color = true
    @terminal.use_color = true
    cli.use_color = true

    say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be \e[36mcyan\e[0m!\n", gterm_output.string)

    @terminal.say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be \e[36mcyan\e[0m!\n", @output.string)

    cli.say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be \e[36mcyan\e[0m!\n", cli_output.string)

    gterm_output.truncate(gterm_output.rewind)
    @output.truncate(@output.rewind)
    cli_output.truncate(cli_output.rewind)

    # Testing with both use_color setted to false
    HighLine.use_color = false
    @terminal.use_color = false
    cli.use_color = false

    say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be cyan!\n", gterm_output.string)

    @terminal.say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be cyan!\n", @output.string)

    cli.say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be cyan!\n", cli_output.string)

    gterm_output.truncate(gterm_output.rewind)
    @output.truncate(@output.rewind)
    cli_output.truncate(cli_output.rewind)

    # Now check when class and instance doesn't agree about use_color

    # Class false, instance true
    HighLine.use_color = false
    @terminal.use_color = false
    cli.use_color = true

    say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be cyan!\n", gterm_output.string)

    @terminal.say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be cyan!\n", @output.string)

    cli.say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be \e[36mcyan\e[0m!\n", cli_output.string)

    gterm_output.truncate(gterm_output.rewind)
    @output.truncate(@output.rewind)
    cli_output.truncate(cli_output.rewind)

    # Class true, instance false
    HighLine.use_color = true
    @terminal.use_color = true
    cli.use_color = false

    say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be \e[36mcyan\e[0m!\n", gterm_output.string)

    @terminal.say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be \e[36mcyan\e[0m!\n", @output.string)

    cli.say("This should be <%= color('cyan', CYAN) %>!")
    assert_equal("This should be cyan!\n", cli_output.string)

    gterm_output.truncate(gterm_output.rewind)
    @output.truncate(@output.rewind)
    cli_output.truncate(cli_output.rewind)

    HighLine.use_color = old_setting
    @terminal.use_color = old_setting
    HighLine.default_instance = old_glob_instance
  end

  def test_reset_use_color
    HighLine.use_color = false
    refute HighLine.use_color?
    HighLine.reset_use_color
    assert HighLine.use_color?
  end

  def test_reset_use_color_when_highline_reset
    HighLine.use_color = false
    refute HighLine.use_color?
    HighLine.reset
    assert HighLine.use_color?
  end

  def test_uncolor
    # instance method
    assert_equal(
      "This should be reverse underlined magenta!\n",
      @terminal.uncolor(
        "This should be \e[7m\e[4m\e[35mreverse underlined magenta\e[0m!\n"
      )
    )

    @output.truncate(@output.rewind)

    # class method
    assert_equal(
      "This should be reverse underlined magenta!\n",
      HighLine.uncolor(
        "This should be \e[7m\e[4m\e[35mreverse underlined magenta\e[0m!\n"
      )
    )

    @output.truncate(@output.rewind)

    # RGB color
    assert_equal(
      "This should be rgb_906030!\n",
      @terminal.uncolor(
        "This should be \e[38;5;137mrgb_906030\e[0m!\n"
      )
    )
  end

  def test_grey_is_the_same_of_gray
    @terminal.say("<%= GRAY %>")
    gray_code = @output.string.dup
    @output.truncate(@output.rewind)

    @terminal.say("<%= GREY %>")
    grey_code = @output.string.dup
    @output.truncate(@output.rewind)

    assert_equal gray_code, grey_code
  end

  def test_light_is_the_same_as_bright
    @terminal.say("<%= BRIGHT_BLUE %>")
    bright_blue_code = @output.string.dup
    @output.truncate(@output.rewind)

    @terminal.say("<%= LIGHT_BLUE %>")
    light_blue_code = @output.string.dup
    @output.truncate(@output.rewind)

    assert_equal bright_blue_code, light_blue_code
  end

  def test_confirm
    @input << "junk.txt\nno\nsave.txt\ny\n"
    @input.rewind

    answer = @terminal.ask("Enter a filename:  ") do |q|
      q.confirm = "Are you sure you want to overwrite <%= answer %>?  "
      q.responses[:ask_on_error] = :question
    end
    assert_equal("save.txt", answer)
    assert_equal("Enter a filename:  " \
                  "Are you sure you want to overwrite junk.txt?  " \
                  "Enter a filename:  " \
                  "Are you sure you want to overwrite save.txt?  ",
                 @output.string)

    @input.truncate(@input.rewind)
    @input << "junk.txt\nyes\nsave.txt\nn\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Enter a filename:  ") do |q|
      q.confirm = "Are you sure you want to overwrite <%= answer %>?  "
    end
    assert_equal("junk.txt", answer)
    assert_equal("Enter a filename:  " \
                  "Are you sure you want to overwrite junk.txt?  ",
                 @output.string)

    @input.truncate(@input.rewind)
    @input << "junk.txt\nyes\nsave.txt\nn\n"
    @input.rewind
    @output.truncate(@output.rewind)

    scoped_variable = { "junk.txt" => "20mb" }
    answer = @terminal.ask("Enter a filename:  ") do |q|
      q.confirm = proc do |checking_answer|
        "Are you sure you want to overwrite #{checking_answer} with size " \
          "of #{scoped_variable[checking_answer]}? "
      end
    end
    assert_equal("junk.txt", answer)
    assert_equal("Enter a filename:  " \
                  "Are you sure you want to overwrite junk.txt " \
                  "with size of 20mb? ",
                 @output.string)
  end

  def test_generic_confirm_with_true
    @input << "junk.txt\nno\nsave.txt\ny\n"
    @input.rewind

    answer = @terminal.ask("Enter a filename:  ") do |q|
      q.confirm = true
      q.responses[:ask_on_error] = :question
    end
    assert_equal("save.txt", answer)
    assert_equal("Enter a filename:  " \
                  "Are you sure?  " \
                  "Enter a filename:  " \
                  "Are you sure?  ",
                 @output.string)

    @input.truncate(@input.rewind)
    @input << "junk.txt\nyes\nsave.txt\nn\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Enter a filename:  ") do |q|
      q.confirm = true
    end
    assert_equal("junk.txt", answer)
    assert_equal("Enter a filename:  " \
                  "Are you sure?  ",
                 @output.string)
  end

  def test_defaults
    @input << "\nNo Comment\n"
    @input.rewind

    answer = @terminal.ask("Are you sexually active?  ") do |q|
      q.validate = /\Ay(?:es)?|no?|no comment\Z/i
    end
    assert_equal("No Comment", answer)

    @input.truncate(@input.rewind)
    @input << "\nYes\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Are you sexually active?  ") do |q|
      q.default  = "No Comment"
      q.validate = /\Ay(?:es)?|no?|no comment\Z/i
    end
    assert_equal("No Comment", answer)
    assert_equal("Are you sexually active?  |No Comment|  ",
                 @output.string)
  end

  def test_default_with_String
    @input << "\n"
    @input.rewind

    answer = @terminal.ask("Question:  ") do |q|
      q.default = "string"
    end

    assert_equal "string", answer
    assert_equal "Question:  |string|  ", @output.string
  end

  def test_default_with_Symbol
    # With a Symbol, it should show up the String version
    #   at prompt, but return the Symbol as answer

    @input << "\n"
    @input.rewind

    answer = @terminal.ask("Question:  ") do |q|
      q.default = :string
    end

    assert_equal :string, answer
    assert_equal "Question:  |string|  ", @output.string
  end

  def test_default_with_non_String_objects
    # With a non-string object, it should try to coerce it to String.
    # If the coercion is not possible, do not show
    #   any 'default' at prompt line. And should
    #   return the "default" object, without conversion.

    @input << "\n"
    @input.rewind

    default_integer_object = 42

    answer = @terminal.ask("Question:  ") do |q|
      q.default = default_integer_object
    end

    assert_equal default_integer_object, answer
    assert_equal "Question:  |42|  ", @output.string

    @input.truncate(@input.rewind)
    @input << "\n"
    @input.rewind
    @output.truncate(@output.rewind)

    default_non_string_object = Object.new

    answer = @terminal.ask("Question:  ") do |q|
      q.default = default_non_string_object
    end

    assert_equal default_non_string_object, answer
    assert_equal "Question:  |#{default_non_string_object}|  ", @output.string

    @input.truncate(@input.rewind)
    @input << "\n"
    @input.rewind
    @output.truncate(@output.rewind)

    default_non_string_object = Object.new

    answer = @terminal.ask("Question:  ") do |q|
      q.default = default_non_string_object
      q.default_hint_show = false
    end

    assert_equal default_non_string_object, answer
    assert_equal "Question:  ", @output.string
  end

  def test_string_preservation
    @input << "Maybe\nYes\n"
    @input.rewind

    my_string = "Is that your final answer? "

    @terminal.ask(my_string) { |q| q.default = "Possibly" }
    @terminal.ask(my_string) { |q| q.default = "Maybe" }

    assert_equal("Is that your final answer? ", my_string)
  end

  def test_empty
    @input << "\n"
    @input.rewind

    answer = @terminal.ask("") do |q|
      q.default  = "yes"
      q.validate = /\Ay(?:es)?|no?\Z/i
    end
    assert_equal("yes", answer)
  end

  def test_erb
    @terminal.say("The integers from 1 to 10 are:\n" \
                   "% (1...10).each do |n|\n" \
                   "\t<%= n %>,\n" \
                   "% end\n" \
                   "\tand 10")
    assert_equal("The integers from 1 to 10 are:\n" \
                  "\t1,\n\t2,\n\t3,\n\t4,\n\t5,\n" \
                  "\t6,\n\t7,\n\t8,\n\t9,\n\tand 10\n",
                 @output.string)
  end

  def test_files
    @input << "#{File.basename(__FILE__)[0, 7]}\n"
    @input.rewind

    assert_equal "test_hi\n", @input.read
    @input.rewind

    file = @terminal.ask("Select a file:  ", File) do |q|
      q.directory = File.expand_path(File.dirname(__FILE__))
      q.glob      = "*.rb"
    end
    assert_instance_of(File, file)
    assert_equal("#!/usr/bin/env ruby\n", file.gets)
    file.close

    @input.rewind

    pathname = @terminal.ask("Select a file:  ", Pathname) do |q|
      q.directory = File.expand_path(File.dirname(__FILE__))
      q.glob      = "*.rb"
    end
    assert_instance_of(Pathname, pathname)
    assert_equal(File.size(__FILE__), pathname.size)
  end

  def test_gather_with_integer
    @input << "James\nDana\nStorm\nGypsy\n\n"
    @input.rewind

    answers = @terminal.ask("Enter four names:") do |q|
      q.gather = 4
    end
    assert_equal(%w[James Dana Storm Gypsy], answers)
    assert_equal("\n", @input.gets)
    assert_equal("Enter four names:\n", @output.string)
  end

  def test_gather_with_an_empty_string
    @input << "James\nDana\nStorm\nGypsy\n\n"
    @input.rewind

    answers = @terminal.ask("Enter four names:") do |q|
      q.gather = ""
    end
    assert_equal(%w[James Dana Storm Gypsy], answers)
  end

  def test_gather_with_regexp
    @input << "James\nDana\nStorm\nGypsy\n\n"
    @input.rewind

    answers = @terminal.ask("Enter four names:") do |q|
      q.gather = /^\s*$/
    end
    assert_equal(%w[James Dana Storm Gypsy], answers)
  end

  def test_gather_with_hash
    @input << "29\n49\n30\n"
    @input.rewind

    answers = @terminal.ask("<%= key %>:  ", Integer) do |q|
      q.gather = { "Age" => 0, "Wife's Age" => 0, "Father's Age" => 0 }
    end
    assert_equal({ "Age" => 29, "Wife's Age" => 30, "Father's Age" => 49 },
                 answers)
    assert_equal("Age:  Father's Age:  Wife's Age:  ", @output.string)
  end

  def test_typing_verification
    @input << "all work and no play makes jack a dull boy\n" * 3
    @input.rewind

    answer = @terminal.ask("How's work? ") do |q|
      q.gather = 3
      q.verify_match = true
    end
    assert_equal("all work and no play makes jack a dull boy", answer)

    @input.truncate(@input.rewind)
    @input << "all play and no work makes jack a mere toy\n"
    @input << "all work and no play makes jack a dull boy\n" * 5
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("How are things going? ") do |q|
      q.gather = 3
      q.verify_match = true
      q.responses[:mismatch] = "Typing mismatch!"
      q.responses[:ask_on_error] = ""
    end
    assert_equal("all work and no play makes jack a dull boy", answer)

    # now try using a hash for gather

    @input.truncate(@input.rewind)
    @input << "Password\nPassword\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("<%= key %>: ") do |q|
      q.verify_match = true
      q.gather = { "Enter a password" => "", "Please type it again" => "" }
    end
    assert_equal("Password", answer)

    @input.truncate(@input.rewind)
    @input << "Password\nMistake\nPassword\nPassword\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("<%= key %>: ") do |q|
      q.verify_match = true
      q.responses[:mismatch] = "Typing mismatch!"
      q.responses[:ask_on_error] = ""
      q.gather = { "Enter a password" => "", "Please type it again" => "" }
    end

    assert_equal("Password", answer)
    assert_equal("Enter a password: " \
                  "Please type it again: " \
                  "Typing mismatch!\n" \
                  "Enter a password: " \
                  "Please type it again: ", @output.string)
  end

  def test_lists
    digits     = %w[Zero One Two Three Four Five Six Seven Eight Nine]
    erb_digits = digits.dup
    erb_digits[erb_digits.index("Five")] = "<%= color('Five', :blue) %%>"

    @terminal.say("<%= list(#{digits.inspect}) %>")
    assert_equal(digits.map { |d| "#{d}\n" }.join, @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("<%= list(#{digits.inspect}, :inline) %>")
    assert_equal(digits[0..-2].join(", ") + " or #{digits.last}\n",
                 @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("<%= list(#{digits.inspect}, :inline, ' and ') %>")
    assert_equal(digits[0..-2].join(", ") + " and #{digits.last}\n",
                 @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("<%= list(#{digits.inspect}, :columns_down, 3) %>")
    assert_equal("Zero   Four   Eight\n" \
                  "One    Five   Nine \n" \
                  "Two    Six  \n"        \
                  "Three  Seven\n",
                 @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("<%= list(#{erb_digits.inspect}, :columns_down, 3) %>")
    assert_equal("Zero   Four   Eight\n" \
                  "One    \e[34mFive\e[0m   Nine \n" \
                  "Two    Six  \n" \
                  "Three  Seven\n",
                 @output.string)

    colums_of_twenty = ["12345678901234567890"] * 5

    @output.truncate(@output.rewind)

    @terminal.say("<%= list(#{colums_of_twenty.inspect}, :columns_down) %>")
    assert_equal("12345678901234567890  12345678901234567890  " \
                  "12345678901234567890\n"                       \
                  "12345678901234567890  12345678901234567890\n",
                 @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("<%= list(#{digits.inspect}, :columns_across, 3) %>")
    assert_equal("Zero   One    Two  \n" \
                  "Three  Four   Five \n" \
                  "Six    Seven  Eight\n" \
                  "Nine \n",
                 @output.string)

    colums_of_twenty.pop

    @output.truncate(@output.rewind)

    @terminal.say("<%= list( #{colums_of_twenty.inspect}, :columns_across ) %>")
    assert_equal("12345678901234567890  12345678901234567890  " \
                  "12345678901234567890\n" \
                  "12345678901234567890\n",
                 @output.string)

    @output.truncate(@output.rewind)

    wide = %w[0123456789 a b c d e f g h i j k l m n o p q r s t u v w x y z]

    @terminal.say("<%= list( #{wide.inspect}, :uneven_columns_across ) %>")
    assert_equal("0123456789  a  b  c  d  e  f  g  h  i  j  k  l  m  n  o  " \
                  "p  q  r  s  t  u  v  w\n"                                  \
                  "x           y  z\n",
                 @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("<%= list( #{wide.inspect}, :uneven_columns_across, 10 ) %>")
    assert_equal("0123456789  a  b  c  d  e  f  g  h  i\n" \
                  "j           k  l  m  n  o  p  q  r  s\n" \
                  "t           u  v  w  x  y  z\n",
                 @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("<%= list( #{wide.inspect}, :uneven_columns_down ) %>")
    assert_equal("0123456789  b  d  f  h  j  l  n  p  r  t  v  x  z\n" \
                  "a           c  e  g  i  k  m  o  q  s  u  w  y\n",
                 @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("<%= list( #{wide.inspect}, :uneven_columns_down, 10 ) %>")
    assert_equal("0123456789  c  f  i  l  o  r  u  x\n" \
                  "a           d  g  j  m  p  s  v  y\n" \
                  "b           e  h  k  n  q  t  w  z\n",
                 @output.string)
  end

  def test_lists_with_zero_items
    modes = [nil, :rows, :inline, :columns_across, :columns_down]
    modes.each do |mode|
      result = @terminal.list([], mode)
      assert_equal("", result)
    end
  end

  def test_lists_with_nil_items
    modes = [nil]
    modes.each do |mode|
      result = @terminal.list([nil], mode)
      assert_equal("\n", result)
    end
  end

  def test_lists_with_one_item
    items = ["Zero"]
    modes = { nil => "Zero\n",
              :rows           => "Zero\n",
              :inline         => "Zero",
              :columns_across => "Zero\n",
              :columns_down   => "Zero\n" }

    modes.each do |mode, expected|
      result = @terminal.list(items, mode)
      assert_equal(expected, result)
    end
  end

  def test_lists_with_two_items
    items = %w[Zero One]
    modes = { nil => "Zero\nOne\n",
              :rows           => "Zero\nOne\n",
              :inline         => "Zero or One",
              :columns_across => "Zero  One \n",
              :columns_down   => "Zero  One \n" }

    modes.each do |mode, expected|
      result = @terminal.list(items, mode)
      assert_equal(expected, result)
    end
  end

  def test_lists_with_three_items
    items = %w[Zero One Two]
    modes = { nil => "Zero\nOne\nTwo\n",
              :rows           => "Zero\nOne\nTwo\n",
              :inline         => "Zero, One or Two",
              :columns_across => "Zero  One   Two \n",
              :columns_down   => "Zero  One   Two \n" }

    modes.each do |mode, expected|
      result = @terminal.list(items, mode)
      assert_equal(expected, result)
    end
  end

  def test_mode
    main_char_modes =
      %w[ HighLine::Terminal::IOConsole
          HighLine::Terminal::NCurses
          HighLine::Terminal::UnixStty ]

    assert(
      main_char_modes.include?(@terminal.terminal.character_mode),
      "#{@terminal.terminal.character_mode} not in list"
    )
  end

  class NameClass
    def self.parse(string)
      raise ArgumentError, "Invalid name format." unless
        string =~ /^\s*(\w+),\s*(\w+)\s+(\w+)\s*$/
      new(Regexp.last_match(2), Regexp.last_match(3), Regexp.last_match(1))
    end

    def initialize(first, middle, last)
      @first = first
      @middle = middle
      @last = last
    end

    attr_reader :first, :middle, :last
  end

  def test_my_class_conversion
    @input << "Gray, James Edward\n"
    @input.rewind

    answer = @terminal.ask("Your name?  ", NameClass) do |q|
      q.validate = lambda do |name|
        names = name.split(/,\s*/)
        return false unless names.size == 2
        return false if names.first =~ /\s/
        names.last.split.size == 2
      end
    end
    assert_instance_of(NameClass, answer)
    assert_equal("Gray", answer.last)
    assert_equal("James", answer.first)
    assert_equal("Edward", answer.middle)
  end

  def test_no_echo
    @input << "password\r"
    @input.rewind

    answer = @terminal.ask("Please enter your password:  ") do |q|
      q.echo = false
    end
    assert_equal("password", answer)
    assert_equal("Please enter your password:  \n", @output.string)

    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Pick a letter or number:  ") do |q|
      q.character = true
      q.echo      = false
    end
    assert_equal("p", answer)
    assert_equal("a", @input.getc.chr)
    assert_equal("Pick a letter or number:  \n", @output.string)
  end

  def test_correct_string_encoding_when_echo_false
    @input << "ação\r" # An UTF-8 portuguese word for 'action'
    @input.rewind

    answer = @terminal.ask("Please enter your password:  ") do |q|
      q.echo = false
    end

    assert_equal "ação", answer
    assert_equal Encoding::UTF_8, answer.encoding
  end

  def test_backspace_with_ascii_when_echo_false
    @input << "password\b\r"
    @input.rewind

    answer = @terminal.ask("Please enter your password:  ") do |q|
      q.echo = false
    end

    refute_equal("password", answer)
    assert_equal("passwor", answer)
  end

  def test_backspace_with_utf8_when_echo_false
    @input << "maçã\b\r"
    @input.rewind

    answer = @terminal.ask("Please enter your password:  ") do |q|
      q.echo = false
    end

    refute_equal("maçã", answer)
    assert_equal("maç", answer)
  end

  def test_echoing_with_utf8_when_echo_is_star
    @input << "maçã\r"
    @input.rewind

    answer = @terminal.ask("Type:  ") do |q|
      q.echo = "*"
    end

    assert_equal("Type:  ****\n", @output.string)
    assert_equal("maçã", answer)
  end

  def test_echo_false_with_ctrl_c_interrupts
    @input << "String with a ctrl-c at the end \u0003 \n"
    @input.rewind
    @answer = nil

    assert_raises(Interrupt) do
      @answer = @terminal.ask("Type:  ") do |q|
        q.echo = false
      end
    end

    assert_nil @answer
  end

  def test_range_requirements
    @input << "112\n-541\n28\n"
    @input.rewind

    answer = @terminal.ask("Tell me your age.", Integer) do |q|
      q.in = 0..105
    end
    assert_equal(28, answer)
    assert_equal("Tell me your age.\n" \
                  "Your answer isn't within the expected range " \
                  "(included in 0..105).\n" \
                  "?  " \
                  "Your answer isn't within the expected range " \
                  "(included in 0..105).\n" \
                  "?  ", @output.string)

    @input.truncate(@input.rewind)
    @input << "1\n-541\n28\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Tell me your age.", Integer) do |q|
      q.above = 3
    end
    assert_equal(28, answer)
    assert_equal("Tell me your age.\n" \
                  "Your answer isn't within the expected range " \
                  "(above 3).\n" \
                  "?  " \
                  "Your answer isn't within the expected range " \
                  "(above 3).\n" \
                  "?  ", @output.string)

    @input.truncate(@input.rewind)
    @input << "1\n28\n-541\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Lowest numer you can think of?", Integer) do |q|
      q.below = 0
    end
    assert_equal(-541, answer)
    assert_equal("Lowest numer you can think of?\n" \
                  "Your answer isn't within the expected range " \
                  "(below 0).\n" \
                  "?  " \
                  "Your answer isn't within the expected range " \
                  "(below 0).\n" \
                  "?  ", @output.string)

    @input.truncate(@input.rewind)
    @input << "-541\n11\n6\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Enter a low even number:  ", Integer) do |q|
      q.above = 0
      q.below = 10
    end
    assert_equal(6, answer)
    assert_equal("Enter a low even number:  " \
                  "Your answer isn't within the expected range " \
                  "(above 0 and below 10).\n" \
                  "?  " \
                  "Your answer isn't within the expected range " \
                  "(above 0 and below 10).\n" \
                  "?  ", @output.string)

    @input.truncate(@input.rewind)
    @input << "1\n-541\n6\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Enter a low even number:  ", Integer) do |q|
      q.above = 0
      q.below = 10
      q.in    = [2, 4, 6, 8]
    end
    assert_equal(6, answer)
    assert_equal("Enter a low even number:  " \
                  "Your answer isn't within the expected range " \
                  "(above 0, below 10, and included in [2, 4, 6, 8]).\n" \
                  "?  " \
                  "Your answer isn't within the expected range " \
                  "(above 0, below 10, and included in [2, 4, 6, 8]).\n" \
                  "?  ", @output.string)
  end

  def test_range_requirements_with_array_of_strings
    @input.truncate(@input.rewind)
    @input << "z\nx\nb\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Letter a, b, or c? ") do |q|
      q.in = %w[ a b c ]
    end
    assert_equal("b", answer)
    assert_equal("Letter a, b, or c? " \
                  "Your answer isn't within the expected range " \
                  "(included in [\"a\", \"b\", \"c\"]).\n" \
                  "?  " \
                  "Your answer isn't within the expected range " \
                  "(included in [\"a\", \"b\", \"c\"]).\n" \
                  "?  ", @output.string)
  end

  def test_reask
    number = 61_676
    @input << "Junk!\n" << number << "\n"
    @input.rewind

    answer = @terminal.ask("Favorite number?  ", Integer)
    assert_kind_of(Integer, number)
    assert_equal(number, answer)
    assert_equal("Favorite number?  " \
                  "You must enter a valid Integer.\n" \
                  "?  ", @output.string)

    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Favorite number?  ", Integer) do |q|
      q.responses[:ask_on_error] = :question
      q.responses[:invalid_type] = "Not a valid number!"
    end
    assert_kind_of(Integer, number)
    assert_equal(number, answer)
    assert_equal("Favorite number?  " \
                  "Not a valid number!\n" \
                  "Favorite number?  ", @output.string)

    @input.truncate(@input.rewind)
    @input << "gen\ngene\n"
    @input.rewind
    @output.truncate(@output.rewind)

    answer = @terminal.ask("Select a mode:  ", [:generate, :gentle])
    assert_instance_of(Symbol, answer)
    assert_equal(:generate, answer)
    assert_equal("Select a mode:  " \
                  "Ambiguous choice.  " \
                  "Please choose one of [generate, gentle].\n" \
                  "?  ", @output.string)
  end

  def test_response_embedding
    @input << "112\n-541\n28\n"
    @input.rewind

    answer = @terminal.ask("Tell me your age.", Integer) do |q|
      q.in = 0..105
      q.responses[:not_in_range] = "Need a #{q.answer_type}" \
                                   " #{q.expected_range}."
    end
    assert_equal(28, answer)
    assert_equal("Tell me your age.\n" \
                  "Need a Integer included in 0..105.\n" \
                  "?  " \
                  "Need a Integer included in 0..105.\n" \
                  "?  ", @output.string)
  end

  def test_say
    @terminal.say("This will have a newline.")
    assert_equal("This will have a newline.\n", @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("This will also have one newline.\n")
    assert_equal("This will also have one newline.\n", @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("This will not have a newline.  ")
    assert_equal("This will not have a newline.  ", @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("This will not have a newline.\t")
    assert_equal("This will not have a newline.\t", @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("This will not\n end with a newline. ")
    assert_equal("This will not\n end with a newline. ", @output.string)

    @output.truncate(@output.rewind)

    @terminal.say("This will \nend with a newline.")
    assert_equal("This will \nend with a newline.\n", @output.string)

    @output.truncate(@output.rewind)

    colorized = @terminal.color("This will not have a newline. ", :green)
    @terminal.say(colorized)
    assert_equal("\e[32mThis will not have a newline. \e[0m", @output.string)

    @output.truncate(@output.rewind)

    colorized = @terminal.color("This will have a newline.", :green)
    @terminal.say(colorized)
    assert_equal("\e[32mThis will have a newline.\e[0m\n", @output.string)

    @output.truncate(@output.rewind)

    @terminal.say(nil)
    assert_equal("", @output.string)
  end

  def test_say_handles_non_string_argument
    integer = 10
    hash    = { a: 20 }

    @terminal.say(integer)
    assert_equal String(integer), @output.string.chomp

    @output.truncate(@output.rewind)

    @terminal.say(hash)
    assert_equal String(hash), @output.string.chomp
  end

  def test_terminal_size
    assert(@terminal.terminal.terminal_size[0] > 0)
    assert(@terminal.terminal.terminal_size[1] > 0)
  end

  def test_type_conversion
    number = 61_676
    @input << number << "\n"
    @input.rewind

    answer = @terminal.ask("Favorite number?  ", Integer)
    assert_kind_of(Integer, answer)
    assert_equal(number, answer)

    @input.truncate(@input.rewind)
    number = 1_000_000_000_000_000_000_000_000_000_000
    @input << number << "\n"
    @input.rewind

    answer = @terminal.ask("Favorite number?  ", Integer)
    assert_kind_of(Integer, answer)
    assert_equal(number, answer)

    @input.truncate(@input.rewind)
    number = 10.5002
    @input << number << "\n"
    @input.rewind

    answer = @terminal.ask("Favorite number?  ",
                           ->(n) { n.to_f.abs.round })
    assert_kind_of(Integer, answer)
    assert_equal(11, answer)

    @input.truncate(@input.rewind)
    animal = :dog
    @input << animal << "\n"
    @input.rewind

    answer = @terminal.ask("Favorite animal?  ", Symbol)
    assert_instance_of(Symbol, answer)
    assert_equal(animal, answer)

    @input.truncate(@input.rewind)
    @input << "16th June 1976\n"
    @input.rewind

    answer = @terminal.ask("Enter your birthday.", Date)
    assert_instance_of(Date, answer)
    assert_equal(16, answer.day)
    assert_equal(6, answer.month)
    assert_equal(1976, answer.year)

    @input.truncate(@input.rewind)
    pattern = "^yes|no$"
    @input << pattern << "\n"
    @input.rewind

    answer = @terminal.ask("Give me a pattern to match with:  ", Regexp)
    assert_instance_of(Regexp, answer)
    assert_equal(/#{pattern}/, answer)

    @input.truncate(@input.rewind)
    @input << "gen\n"
    @input.rewind

    answer = @terminal.ask("Select a mode:  ", [:generate, :run])
    assert_instance_of(Symbol, answer)
    assert_equal(:generate, answer)
  end

  def test_validation
    @input << "system 'rm -rf /'\n105\n0b101_001\n"
    @input.rewind

    answer = @terminal.ask("Enter a binary number:  ") do |q|
      q.validate = /\A(?:0b)?[01_]+\Z/
    end
    assert_equal("0b101_001", answer)
    assert_equal("Enter a binary number:  " \
                  "Your answer isn't valid " \
                  "(must match /\\A(?:0b)?[01_]+\\Z/).\n" \
                  "?  " \
                  "Your answer isn't valid " \
                  "(must match /\\A(?:0b)?[01_]+\\Z/).\n" \
                  "?  ", @output.string)

    @input.truncate(@input.rewind)
    @input << "Gray II, James Edward\n" \
              "Gray, Dana Ann Leslie\n" \
              "Gray, James Edward\n"
    @input.rewind

    answer = @terminal.ask("Your name?  ") do |q|
      q.validate = lambda do |name|
        names = name.split(/,\s*/)
        return false unless names.size == 2
        return false if names.first =~ /\s/
        names.last.split.size == 2
      end
    end
    assert_equal("Gray, James Edward", answer)
  end

  class ZeroToTwentyFourValidator
    def self.valid?(answer)
      (0..24).include? answer.to_i
    end

    def self.inspect
      "(0..24) rule"
    end
  end

  def test_validation_with_custom_validator_class
    @input << "26\n25\n24\n"
    @input.rewind

    answer = @terminal.ask("What hour of the day is it?:  ", Integer) do |q|
      q.validate = ZeroToTwentyFourValidator
    end

    assert_equal(24, answer)
    assert_equal("What hour of the day is it?:  " \
                "Your answer isn't valid (must match (0..24) rule).\n" \
                "?  Your answer isn't valid (must match (0..24) rule).\n" \
                "?  ", @output.string)
  end

  require 'dry/types'

  module Types
    include Dry.Types
  end

  def test_validation_with_dry_types
    @input << "random string\nanother uncoercible string\n42\n"
    @input.rewind

    answer = @terminal.ask("Type a number:  ", Integer) do |q|
      q.validate = Types::Coercible::Integer
    end

    assert_equal(42, answer)
    assert_match(Regexp.new(<<~REGEXP.chomp),
      Type a number:  Your answer isn't valid .must match .*Dry.*Types.*Integer.*..
      \\\?  Your answer isn't valid .must match .*Dry.*Types.*Integer.*..
      \\\?
    REGEXP
      @output.string
    )
  end

  def test_validation_with_overriding_static_message
    @input << "Not valid answer\n" \
              "42\n"

    @input.rewind

    answer = @terminal.ask("Enter only numbers:  ") do |question|
      question.validate = ->(ans) { ans =~ /\d+/ }
      question.responses[:not_valid] = "We accept only numbers over here!"
    end

    assert_equal("42", answer)
    assert_equal(
      "Enter only numbers:  We accept only numbers over here!\n" \
      "?  ",
      @output.string
    )
  end

  def test_validation_with_overriding_dynamic_message
    @input << "Forty two\n" \
              "42\n"

    @input.rewind

    answer = @terminal.ask("Enter only numbers:  ") do |question|
      question.validate = ->(ans) { ans =~ /\d+/ }
      question.responses[:not_valid] =
        ->(ans) { "#{ans} is not a valid answer" }
    end

    assert_equal("42", answer)
    assert_equal(
      "Enter only numbers:  " \
      "Forty two is not a valid answer\n" \
      "?  ",
      @output.string
    )
  end

  def test_whitespace
    @input << "  A   lot\tof  \t  space\t  \there!   \n"
    @input.rewind

    answer = @terminal.ask("Enter a whitespace filled string:  ") do |q|
      q.whitespace = :chomp
    end
    assert_equal("  A   lot\tof  \t  space\t  \there!   ", answer)

    @input.rewind

    answer = @terminal.ask("Enter a whitespace filled string:  ") do |q|
      q.whitespace = :strip
    end
    assert_equal("A   lot\tof  \t  space\t  \there!", answer)

    @input.rewind

    answer = @terminal.ask("Enter a whitespace filled string:  ") do |q|
      q.whitespace = :collapse
    end
    assert_equal(" A lot of space here! ", answer)

    @input.rewind

    answer = @terminal.ask("Enter a whitespace filled string:  ")
    assert_equal("A   lot\tof  \t  space\t  \there!", answer)

    @input.rewind

    answer = @terminal.ask("Enter a whitespace filled string:  ") do |q|
      q.whitespace = :strip_and_collapse
    end
    assert_equal("A lot of space here!", answer)

    @input.rewind

    answer = @terminal.ask("Enter a whitespace filled string:  ") do |q|
      q.whitespace = :remove
    end
    assert_equal("Alotofspacehere!", answer)

    @input.rewind

    answer = @terminal.ask("Enter a whitespace filled string:  ") do |q|
      q.whitespace = :none
    end
    assert_equal("  A   lot\tof  \t  space\t  \there!   \n", answer)

    @input.rewind

    answer = @terminal.ask("Enter a whitespace filled string:  ") do |q|
      q.whitespace = nil
    end
    assert_equal("  A   lot\tof  \t  space\t  \there!   \n", answer)
  end

  def test_track_eof
    assert_raises(EOFError) { @terminal.ask("Any input left?  ") }

    # turn EOF tracking
    old_instance = HighLine.default_instance
    HighLine.default_instance = HighLine.new(StringIO.new, StringIO.new)
    HighLine.track_eof = false
    begin
      require "highline/import"
      # this will still blow up, nothing available
      ask("And now?  ")
    rescue StandardError
      # but HighLine's safe guards are off
      refute_equal(EOFError, $ERROR_INFO.class)
    end
    HighLine.default_instance = old_instance
  end

  def test_version
    refute_nil(HighLine::VERSION)
    assert_instance_of(String, HighLine::VERSION)
    assert(HighLine::VERSION.frozen?)
    assert_match(/\A\d+\.\d+\.\d+(-.*)?/, HighLine::VERSION)
  end
end
