require 'spec_helper'

describe Bogus::CopiesClasses do
  module SampleMethods
    def foo
    end

    def bar(x)
    end

    def baz(x, *y)
    end

    def bam(opts = {})
    end

    def baa(x, &block)
    end
  end

  shared_examples_for 'the copied class' do
    it "copies methods with no arguments" do
      expect(subject).to respond_to(:foo)
      subject.foo
    end

    it "copies methods with explicit arguments" do
      expect(subject).to respond_to(:bar)

      expect(subject.method(:bar).arity).to eq 1

      subject.bar('hello')
    end

    it "copies methods with variable arguments" do
      expect(subject).to respond_to(:baz)

      subject.baz('hello', 'foo', 'bar', 'baz')
    end

    it "copies methods with default arguments" do
      expect(subject).to respond_to(:bam)

      subject.bam
      subject.bam(hello: 'world')
    end

    it "copies methods with block arguments" do
      expect(subject).to respond_to(:baa)

      subject.baa('hello')
      subject.baa('hello') {}
    end
  end

  let(:copies_classes) { Bogus.inject.copies_classes }
  let(:fake_class) { copies_classes.copy(klass) }
  let(:fake) { fake_class.new }

  class FooWithInstanceMethods
    CONST = "the const"
    include SampleMethods
  end

  context "nested constants" do
    let(:klass) { FooWithInstanceMethods }

    it "does not overwrite nested constants" do
      expect(fake_class::CONST).to eq "the const"
    end
  end

  context "instance methods" do
    let(:klass) { FooWithInstanceMethods }
    subject{ fake }

    it_behaves_like 'the copied class'
  end

  context "constructors" do
    let(:klass) {
      Class.new do
        def initialize(hello)
        end

        def foo
        end
      end
    }

    it "adds a no-arg constructor" do
      instance = fake_class.__create__

      expect(instance).to respond_to(:foo)
    end

    it "adds a constructor that allows passing the correct number of arguments" do
      instance = fake_class.new('hello')

      expect(instance).to respond_to(:foo)
    end
  end

  class ClassWithClassMethods
    extend SampleMethods
  end

  context "class methods" do
    let(:klass) { ClassWithClassMethods }
    subject{ fake_class }

    it_behaves_like 'the copied class'
  end

  context "identification" do
    module SomeModule
      class SomeClass
      end
    end

    let(:klass) { SomeModule::SomeClass }

    it "should copy the class name" do
      expect(fake.class.name).to eq 'SomeModule::SomeClass'
    end

    it "should override kind_of?" do
      expect(fake.kind_of?(SomeModule::SomeClass)).to be_true
    end

    it "should override instance_of?" do
      expect(fake.instance_of?(SomeModule::SomeClass)).to be_true
    end

    it "should override is_a?" do
      expect(fake.is_a?(SomeModule::SomeClass)).to be_true
    end

    it "should include class name in the output of fake's class #to_s" do
      expect(fake.class.to_s).to include(klass.name)
    end

    it "should include class name in the output of fake's #to_s" do
      expect(fake.to_s).to include(klass.name)
    end

    it 'should override ===' do
      expect(SomeModule::SomeClass === fake).to be_true
    end
  end

  shared_examples_for 'spying' do
    def should_record(method, *args)
      mock(subject).__record__(method, *args)

      subject.send(method, *args)
    end

    it "records method calls with no arguments" do
      should_record(:foo)
    end

    it "records method calls with explicit arguments" do
      should_record(:bar, 'hello')
    end

    it "records method calls with variable arguments" do
      should_record(:baz, 'hello', 'foo', 'bar', 'baz')
    end

    it "records method calls with default arguments" do
      should_record(:bam, hello: 'world')
    end
  end

  context "spying on an instance" do
    let(:klass) { FooWithInstanceMethods }
    subject{ fake }

    include_examples 'spying'
  end

  context "spying on copied class" do
    let(:klass) { ClassWithClassMethods }
    subject { fake_class }

    include_examples 'spying'
  end

  class SomeModel
    def save(*)
      # ignores arguments
    end
  end

  context "copying classes with methods with nameless parameters" do
    let(:klass) { SomeModel }

    it "copies those methods" do
      expect(fake).to respond_to(:save)
    end
  end
end

