# frozen_string_literal: true

require 'rubocop_spec_helper'
require 'rspec-parameterized'

require_relative '../../../../../rubocop/cop/rspec/factory_bot/inline_association'

RSpec.describe RuboCop::Cop::RSpec::FactoryBot::InlineAssociation do
  shared_examples 'offense' do |code_snippet, autocorrected|
    # We allow `create` or `FactoryBot.create` or `::FactoryBot.create`
    let(:type) { code_snippet[/^(?:::)?(?:FactoryBot\.)?(\w+)/, 1] }
    let(:offense_marker) { '^' * code_snippet.size }
    let(:offense_msg) { msg(type) }
    let(:offense) { "#{offense_marker} #{offense_msg}" }
    let(:source) do
      <<~RUBY
        FactoryBot.define do
          factory :project do
            attribute { #{code_snippet} }
                        #{offense}
          end
        end
      RUBY
    end

    let(:corrected_source) do
      <<~RUBY
        FactoryBot.define do
          factory :project do
            attribute { #{autocorrected} }
          end
        end
      RUBY
    end

    it 'registers an offense and corrects', :aggregate_failures do
      expect_offense(source)

      expect_correction(corrected_source)
    end
  end

  shared_examples 'no offense' do |code_snippet|
    first_line = code_snippet.lines.first.chomp

    context "for `#{first_line}`" do
      it 'does not register any offenses' do
        expect_no_offenses <<~RUBY
          FactoryBot.define do
            factory :project do
              #{code_snippet}
            end
          end
        RUBY
      end
    end
  end

  context 'offenses' do
    using RSpec::Parameterized::TableSyntax

    where(:code_snippet, :autocorrected) do
      # create
      'create(:user)'              | 'association(:user)'
      'FactoryBot.create(:user)'   | 'association(:user)'
      '::FactoryBot.create(:user)' | 'association(:user)'
      'create(:user, :admin)'      | 'association(:user, :admin)'
      'create(:user, name: "any")' | 'association(:user, name: "any")'
      # build
      'build(:user)'               | 'association(:user)'
      'FactoryBot.build(:user)'    | 'association(:user)'
      '::FactoryBot.build(:user)'  | 'association(:user)'
      'build(:user, :admin)'       | 'association(:user, :admin)'
      'build(:user, name: "any")'  | 'association(:user, name: "any")'
    end

    with_them do
      include_examples 'offense', params[:code_snippet], params[:autocorrected]
    end

    it 'recognizes `add_attribute`' do
      expect_offense <<~RUBY
        FactoryBot.define do
          factory :project, class: 'Project' do
            add_attribute(:method) { create(:user) }
                                     ^^^^^^^^^^^^^ #{msg(:create)}
          end
        end
      RUBY
    end

    it 'recognizes `transient` attributes' do
      expect_offense <<~RUBY
        FactoryBot.define do
          factory :project, class: 'Project' do
            transient do
              creator { create(:user) }
                        ^^^^^^^^^^^^^ #{msg(:create)}
            end
          end
        end
      RUBY
    end
  end

  context 'no offenses' do
    include_examples 'no offense', 'association(:user)'
    include_examples 'no offense', 'association(:user, :admin)'
    include_examples 'no offense', 'association(:user, name: "any")'

    include_examples 'no offense', <<~RUBY
      after(:build) do |object|
        object.user = create(:user)
      end
    RUBY

    include_examples 'no offense', <<~RUBY
      initialize_with do
        create(:user)
      end
    RUBY

    include_examples 'no offense', <<~RUBY
      user_id { create(:user).id }
    RUBY
  end

  def msg(type)
    format(described_class::MSG, type: type)
  end
end
