require 'spec_helper'

describe EnumerateIt do
  let :test_class do
    Class.new do
      extend EnumerateIt

      attr_accessor :foobar

      has_enumeration_for :foobar, with: TestEnumeration

      def initialize(foobar)
        @foobar = foobar
      end

      I18n.locale = :en
    end
  end

  let(:target) { test_class.new(TestEnumeration::VALUE_2) }

  context 'associating an enumeration with a class attribute' do
    it "creates an humanized description for the attribute's value" do
      expect(target.foobar_humanize).to eq('Hey, I am 2!')
    end

    it 'if the attribute is blank, the humanize description is nil' do
      target.foobar = nil
      expect(target.foobar_humanize).to be_nil
    end

    it 'creates the key method' do
      expect(target.foobar_key).to eq(:value_2)
    end

    it 'if the attribute is blank, the key method returns nil' do
      target.foobar = nil
      expect(target.foobar_key).to be_nil
    end

    it 'defaults to not creating helper methods' do
      expect(target).not_to respond_to(:value_1?)
    end

    it 'stores the enumeration class in a class-level hash' do
      expect(test_class.enumerations[:foobar]).to eq(TestEnumeration)
    end

    context 'use the same enumeration from an inherited class' do
      let(:some_class_without_enum) { Class.new(BaseClass) }
      let(:target) { some_class_without_enum.new }
      let(:base) { BaseClass.new }

      it 'has use the correct class' do
        expect(base.class.enumerations[:foobar]).to eq(TestEnumeration)
        expect(target.class.enumerations[:foobar]).to eq(TestEnumeration)
      end
    end

    context 'declaring a simple enum on an inherited class' do
      let(:some_class) { Class.new(BaseClass) { has_enumeration_for :foobar } }
      let(:target) { some_class.new }
      let(:base) { BaseClass.new }

      it 'has use the corret class' do
        expect(base.class.enumerations[:foobar]).to eq(TestEnumeration)
        expect(target.class.enumerations[:foobar]).to eq(Foobar)
      end
    end

    context 'passing options values without the human string (just the value, without an array)' do
      let :test_class_for_enumeration_without_array do
        Class.new do
          extend EnumerateIt

          attr_accessor :foobar

          has_enumeration_for :foobar, with: TestEnumerationWithoutArray

          def initialize(foobar)
            @foobar = foobar
          end
        end
      end

      let :target do
        test_class_for_enumeration_without_array.new(TestEnumerationWithoutArray::VALUE_TWO)
      end

      it 'humanizes the respective hash key' do
        expect(target.foobar_humanize).to eq('Value Two')
      end

      it 'translates the respective hash key when a translation is found' do
        target.foobar = TestEnumerationWithoutArray::VALUE_ONE
        expect(target.foobar_humanize).to eq('First Value')
        I18n.locale = :pt

        expect(target.foobar_humanize).to eq('Primeiro Valor')
      end
    end

    context 'without passing the enumeration class' do
      let :foo_bar_class do
        Class.new do
          extend EnumerateIt

          attr_accessor :test_enumeration

          has_enumeration_for :test_enumeration

          def initialize(test_enumeration_value)
            @test_enumeration = test_enumeration_value
          end
        end
      end

      let(:target) { foo_bar_class.new(TestEnumeration::VALUE_1) }

      it 'finds out which enumeration class to use' do
        expect(target.test_enumeration_humanize).to eq('Hey, I am 1!')
      end

      context 'when using a nested class as the enumeration' do
        let :class_with_nested_enum do
          Class.new do
            # rubocop:disable Lint/ConstantDefinitionInBlock
            # rubocop:disable RSpec/LeakyConstantDeclaration
            class NestedEnum < EnumerateIt::Base
              associate_values foo: %w[1 Blerrgh], bar: ['2' => 'Blarghhh']
            end
            # rubocop:enable RSpec/LeakyConstantDeclaration
            # rubocop:enable Lint/ConstantDefinitionInBlock

            extend EnumerateIt

            attr_accessor :nested_enum

            has_enumeration_for :nested_enum

            def initialize(nested_enum_value)
              @nested_enum = nested_enum_value
            end
          end
        end

        it 'uses the inner class as the enumeration class' do
          expect(class_with_nested_enum.new('1').nested_enum_humanize).to eq('Blerrgh')
        end
      end
    end
  end

  context 'using the :create_helpers option' do
    let :test_class_with_helper do
      Class.new do
        extend EnumerateIt

        attr_accessor :foobar

        has_enumeration_for :foobar, with: TestEnumeration, create_helpers: true

        def initialize(foobar)
          @foobar = foobar
        end
      end
    end

    it 'creates helpers methods with question marks for each enumeration option' do
      target = test_class_with_helper.new(TestEnumeration::VALUE_2)
      expect(target).to be_value_2
      expect(target).not_to be_value_1
    end

    it 'creates a mutator method for each enumeration value' do
      %i[value_1 value_2 value_3].each do |value|
        expect(test_class_with_helper.new(TestEnumeration::VALUE_1)).to respond_to(:"#{value}!")
      end
    end

    it "changes the attribute's value through mutator methods" do
      target = test_class_with_helper.new(TestEnumeration::VALUE_2)
      target.value_3!
      expect(target.foobar).to eq(TestEnumeration::VALUE_3)
    end

    context 'when class responds to save! method' do
      it 'calls save!' do
        target = test_class_with_helper.new(TestEnumeration::VALUE_2)
        allow(target).to receive(:save!)
        target.value_3!
        expect(target).to have_received(:save!)
      end
    end

    context 'with :prefix option' do
      let :test_class_with_prefixed_helper do
        Class.new do
          extend EnumerateIt

          attr_accessor :foobar

          has_enumeration_for :foobar, with: TestEnumeration, create_helpers: { prefix: true }

          def initialize(foobar)
            @foobar = foobar
          end
        end
      end

      it 'creates helpers methods with question marks and prefixes for each enumeration option' do
        target = test_class_with_prefixed_helper.new(TestEnumeration::VALUE_2)
        expect(target).to be_foobar_value_2
      end

      it 'creates a mutator method for each enumeration value' do
        %i[value_1 value_2 value_3].each do |value|
          expect(test_class_with_prefixed_helper.new(TestEnumeration::VALUE_1))
            .to respond_to(:"foobar_#{value}!")
        end
      end

      it "changes the attribute's value through mutator methods" do
        target = test_class_with_prefixed_helper.new(TestEnumeration::VALUE_2)
        target.foobar_value_3!
        expect(target.foobar).to eq(TestEnumeration::VALUE_3)
      end

      context 'when class responds to save! method' do
        it 'calls save!' do
          target = test_class_with_prefixed_helper.new(TestEnumeration::VALUE_2)
          allow(target).to receive(:save!)
          target.foobar_value_3!
          expect(target).to have_received(:save!)
        end
      end
    end

    context 'with :polymorphic option' do
      let :polymorphic_class do
        Class.new do
          extend EnumerateIt

          attr_accessor :foo

          has_enumeration_for :foo, with: PolymorphicEnum, create_helpers: { polymorphic: true }
        end
      end

      let(:target) { polymorphic_class.new }

      it "calls methods on the enum constants' objects" do
        target.foo = PolymorphicEnum::NORMAL

        expect(target.foo_object.print('Gol')).to eq("I'm Normal: Gol")

        target.foo = PolymorphicEnum::CRAZY

        expect(target.foo_object.print('Gol')).to eq('Whoa!: Gol')
      end

      it 'returns nil if foo is not set' do
        expect(target.foo_object).to be_nil
      end

      context 'and :suffix' do
        let :polymorphic_class_with_suffix do
          Class.new do
            extend EnumerateIt

            attr_accessor :foo

            has_enumeration_for :foo, with: PolymorphicEnum,
              create_helpers: { polymorphic: { suffix: '_strategy' } }
          end
        end

        let(:target) { polymorphic_class_with_suffix.new }

        it "calls methods on the enum constants' objects" do
          target.foo = PolymorphicEnum::NORMAL

          expect(target.foo_strategy.print('Gol')).to eq("I'm Normal: Gol")

          target.foo = PolymorphicEnum::CRAZY

          expect(target.foo_strategy.print('Gol')).to eq('Whoa!: Gol')
        end
      end
    end
  end

  describe 'using the :create_scopes option' do
    context 'if the hosting class responds to :scope' do
      let :test_class_with_scope do
        Class.new(ActiveRecord::Base) do
          self.table_name = 'test_class_with_scopes'
          has_enumeration_for :foobar, with: TestEnumeration, create_scopes: true
        end
      end

      it 'creates a scope for each enumeration value' do
        TestEnumeration.enumeration.each_key do |symbol|
          expect(test_class_with_scope).to respond_to(symbol)
        end
      end

      it 'when called, the scopes create the correct query', :sqlite do
        ActiveRecord::Schema.define { create_table :test_class_with_scopes }

        TestEnumeration.enumeration.each do |symbol, pair|
          expect(test_class_with_scope.public_send(symbol).to_sql)
            .to match(/WHERE "test_class_with_scopes"."foobar" = '#{pair.first}'/)
        end
      end
    end

    context 'when the hosting class does not respond to :scope' do
      let(:generic_class) { Class.new { extend EnumerateIt } }

      it 'raises no errors' do
        expect do
          generic_class.has_enumeration_for(:foobar, with: TestEnumeration, create_scopes: true)
        end.not_to raise_error
      end
    end

    context 'with :prefix option' do
      let :other_test_class do
        Class.new(ActiveRecord::Base) do
          has_enumeration_for :foobar, with: TestEnumerationWithReservedWords,
            create_scopes: { prefix: true }
        end
      end

      it 'creates a scope with prefix for each enumeration value' do
        TestEnumerationWithReservedWords.enumeration.each_key do |symbol|
          expect(other_test_class).to respond_to(:"foobar_#{symbol}")
        end
      end
    end
  end
end
