require 'unit_spec_helper'

describe Shoulda::Matchers::ActiveModel::ValidateAcceptanceOfMatcher, type: :model do
  context 'a model with an acceptance validation' do
    context 'passing multiple attributes' do
      it 'accepts when every attribute has the validation' do
        model = define_active_model_class(
          'Example',
          accessors: [:attr1, :attr2],
        ) do
          validates_acceptance_of :attr1, :attr2
        end

        expect(model.new).to validate_acceptance_of(:attr1, :attr2)
      end

      it 'rejects when one attribute has the validation and one does not' do
        model = define_active_model_class(
          'Example',
          accessors: [:attr1, :attr2],
        ) do
          validates_acceptance_of :attr1
        end

        assertion = lambda do
          expect(model.new).to validate_acceptance_of(:attr1, :attr2)
        end

        message = <<-MESSAGE
Expected Example to validate that :attr2 has been set to "1", but this
could not be proved.
  After setting :attr2 to ‹false›, the matcher expected the Example to
  be invalid, but it was valid instead.
        MESSAGE

        expect(&assertion).to fail_with_message(message)
      end

      it 'rejects when no attribute has the validation' do
        model = define_active_model_class(
          'Example',
          accessors: [:attr1, :attr2],
        )

        assertion = lambda do
          expect(model.new).to validate_acceptance_of(:attr1, :attr2)
        end

        message = <<-MESSAGE
Expected Example to validate that :attr1 has been set to "1" and
validate that :attr2 has been set to "1", but this could not be proved.
  After setting :attr1 to ‹false›, the matcher expected the Example to
  be invalid, but it was valid instead.
  After setting :attr2 to ‹false›, the matcher expected the Example to
  be invalid, but it was valid instead.
        MESSAGE

        expect(&assertion).to fail_with_message(message)
      end
    end

    it 'accepts when the attributes match' do
      expect(record_validating_acceptance).to matcher
    end

    it 'does not overwrite the default message with nil' do
      expect(record_validating_acceptance).to matcher.with_message(nil)
    end

    it_supports(
      'ignoring_interference_by_writer',
      tests: {
        accept_if_qualified_but_changing_value_does_not_interfere: {
          changing_values_with: :never_falsy,
        },
        reject_if_qualified_but_changing_value_interferes: {
          model_name: 'Example',
          attribute_name: :attr,
          changing_values_with: :always_nil,
          expected_message: <<-MESSAGE.strip,
Expected Example to validate that :attr has been set to "1", but this
could not be proved.
  After setting :attr to ‹false› -- which was read back as ‹nil› -- the
  matcher expected the Example to be invalid, but it was valid instead.

  As indicated in the message above, :attr seems to be changing certain
  values as they are set, and this could have something to do with why
  this test is failing. If you've overridden the writer method for this
  attribute, then you may need to change it to make this test pass, or
  do something else entirely.
          MESSAGE
        },
      },
      model_creator: :active_model,
    )

    it 'fails when used in the negative' do
      assertion = lambda do
        expect(record_validating_acceptance).not_to matcher
      end

      message = <<-MESSAGE
Expected Example not to validate that :attr has been set to "1", but
this could not be proved.
  After setting :attr to ‹false›, the matcher expected the Example to be
  valid, but it was invalid instead, producing these validation errors:

  * attr: ["must be accepted"]
      MESSAGE

      expect(&assertion).to fail_with_message(message)
    end
  end

  context 'a model without an acceptance validation' do
    it 'rejects' do
      expect(record_validating_nothing).not_to matcher
    end
  end

  context 'an attribute which must be accepted with a custom message' do
    it 'accepts when the message matches' do
      expect(record_validating_acceptance(message: 'custom')).
        to matcher.with_message(/custom/)
    end

    it 'rejects when the message does not match' do
      expect(record_validating_acceptance(message: 'custom')).
        not_to matcher.with_message(/wrong/)
    end
  end

  def matcher
    validate_acceptance_of(:attr)
  end

  def model_validating_nothing(options = {}, &block)
    attribute_name = options.fetch(:attribute_name, :attr)
    define_active_model_class(:example, accessors: [attribute_name], &block)
  end

  def record_validating_nothing
    model_validating_nothing.new
  end

  def model_validating_acceptance(options = {})
    attribute_name = options.fetch(:attribute_name, :attr)

    model_validating_nothing(attribute_name:) do
      validates_acceptance_of attribute_name, options
    end
  end

  alias_method :define_model_validating_acceptance, :model_validating_acceptance

  def record_validating_acceptance(options = {})
    model_validating_acceptance(options).new
  end

  alias_method :build_record_validating_acceptance,
    :record_validating_acceptance

  def validation_matcher_scenario_args
    { matcher_name: :validate_acceptance_of }
  end
end
