#!/usr/bin/env ruby

#---
# Copyright 2003-2012 by Jim Weirich (jim.weirich@gmail.com).
# All rights reserved.

# Permission is granted for use, copying, modification, distribution,
# and distribution of modified versions of this work as long as the
# above copyright notice is included.
#+++

require 'test/test_setup'

class TestNewInstances < Test::Unit::TestCase
  include FlexMock::TestCase
  include FlexMock::RedirectError

  class Dog
    def bark
      :woof
    end
    def wag
      :tail
    end
    def self.make
      new
    end
  end

  class Cat
    attr_reader :name
    def initialize(name, &block)
      @name = name
      block.call(self) if block_given?
    end
  end

  class Connection
    def initialize(*args)
      yield(self) if block_given?
    end
    def send(args)
      post(args)
    end
    def post(args)
      :unstubbed
    end
  end

  def test_new_instances_allows_stubbing_of_existing_methods
    flexstub(Dog).new_instances do |obj|
      obj.should_receive(:bark).and_return(:whimper)
    end
    m = Dog.new
    assert_equal :whimper,  m.bark
  end

  def test_new_instances_stubs_still_have_existing_methods
    flexstub(Dog).new_instances do |obj|
      obj.should_receive(:bark).and_return(:whimper)
    end
    m = Dog.new
    assert_equal :tail,  m.wag
  end

  def test_new_instances_will_pass_args_to_new
    flexstub(Cat).new_instances do |obj|
      obj.should_receive(:meow).and_return(:scratch)
    end
    x = :not_called
    m = Cat.new("Fido") { x = :called }
    assert_equal :scratch,  m.meow
    assert_equal "Fido", m.name
    assert_equal :called, x
  end

  # Some versions of the software had problems invoking the block after a
  # second stubbing.
  def test_new_gets_block_after_restubbing
    flexstub(Cat).new_instances { }
    x = :not_called
    m = Cat.new("Fido") { x = :called }
    assert_equal :called, x
    flexmock_teardown

    flexstub(Cat).new_instances { }
    x = :not_called
    m = Cat.new("Fido") { x = :called }
    assert_equal :called, x
  end

  def test_new_instances_stub_verification_happens_on_teardown
    flexstub(Dog).new_instances do |obj|
      obj.should_receive(:bark).once.and_return(nil)
    end

    Dog.new
    ex = assert_raise(assertion_failed_error) { flexmock_teardown }
    assert_match(/method 'bark\(.*\)' called incorrect number of times/i, ex.message)
  end

  def test_new_instances_reports_error_on_non_classes
    ex = assert_raise(ArgumentError) {
      flexstub(Dog.new).new_instances do |obj|
        obj.should_receive(:hi)
      end
    }
    assert_match(/Class/, ex.message)
    assert_match(/new_instances/, ex.message)
  end

  def test_does_not_by_default_stub_objects_created_with_allocate
    flexstub(Dog).new_instances do |obj|
      obj.should_receive(:bark).and_return(:whimper)
    end
    m = Dog.allocate
    assert_equal :woof,  m.bark
  end

  if RUBY_VERSION >= "1.9"
    def test_explicitly_mocking_allocation_in_new_instances_fails_in_ruby_19
      assert_raise FlexMock::UsageError do
        flexstub(Dog).new_instances(:allocate) do |obj|
          obj.should_receive(:bark).and_return(:whimper)
        end
      end
    end
  else
    def test_can_explicitly_stub_objects_created_with_allocate
      flexstub(Dog).new_instances(:allocate) do |obj|
        obj.should_receive(:bark).and_return(:whimper)
      end
      m = Dog.allocate
      assert_equal :whimper,  m.bark
    end
  end

  def test_can_stub_objects_created_with_arbitrary_class_methods
    flexstub(Dog).new_instances(:make) do |obj|
      obj.should_receive(:bark).and_return(:whimper)
    end
    assert_equal :whimper,  Dog.make.bark
  end

  def test_stubbing_arbitrary_class_methods_leaves_new_alone
    flexstub(Dog).new_instances(:make) do |obj|
      obj.should_receive(:bark).and_return(:whimper)
    end
    assert_equal :woof,  Dog.new.bark
  end

  def test_stubbing_new_and_allocate_doesnt_double_stub_objects_on_new
    counter = 0
    flexstub(Dog).new_instances do |obj|
      counter += 1
    end
    Dog.new
    assert_equal 1, counter
  end

  # Current behavior does not install stubs into the block passed to new.
  # This is rather difficult to achieve, although it would be nice.  For the
  # moment, we assure that they are not stubbed, but I am willing to change
  # this in the future.
  def test_blocks_on_new_do_not_have_stubs_installed
    flexstub(Connection).new_instances do |new_con|
      new_con.should_receive(:post).and_return {
        :stubbed
      }
    end
    block_run = false
    Connection.new do |c|
      assert_equal :unstubbed, c.send("hi")
      block_run = true
    end
    assert block_run
  end

  def test_new_instances_accept_chained_expectations
    flexmock(Dog).new_instances.
      should_receive(:growl).and_return(:grr).
      should_receive(:roll_over).and_return(:flip)
    assert_equal :grr, Dog.new.growl
    assert_equal :flip, Dog.new.roll_over
  end

  def test_fancy_use_of_chained_should_received
    flexmock(Dog).new_instances.should_receive(:woof => :grrr)
    assert_equal :grrr, Dog.new.woof
  end

  def test_writable_accessors
    flexmock(Dog).new_instances.should_receive(:name=).with("fido")
    dog = Dog.new
    dog.name = 'fido'
  end

  def test_ordering_can_be_specified
    dog = Dog.new
    flexmock(dog).should_receive(:bark).once.ordered
    flexmock(dog).should_receive(:bite).once.ordered
    dog.bark
    dog.bite
  end

  def test_ordering_can_be_specified_in_groups
    dog = Dog.new
    flexmock(dog).should_receive(:wag).once.ordered(:safe)
    flexmock(dog).should_receive(:bark).once.ordered(:danger)
    flexmock(dog).should_receive(:bite).once.ordered(:danger)
    dog.wag
    dog.bite
    dog.bark
  end
end
