# frozen_string_literal: true

RSpec.describe RuboCop::Cop::Lint::SafeNavigationChain, :config do
  let(:cop_config) do
    { 'AcceptedMethods' => %w[present? blank? try presence] }
  end

  shared_examples 'accepts' do |name, code|
    it "accepts usages of #{name}" do
      expect_no_offenses(code)
    end
  end

  [
    ['ordinary method chain', 'x.foo.bar.baz'],
    ['ordinary method chain with argument', 'x.foo(x).bar(y).baz(z)'],
    ['method chain with safe navigation only', 'x&.foo&.bar&.baz'],
    ['method chain with safe navigation only with argument',
     'x&.foo(x)&.bar(y)&.baz(z)'],
    ['safe navigation at last only', 'x.foo.bar&.baz'],
    ['safe navigation at last only with argument', 'x.foo(x).bar(y)&.baz(z)'],
    ['safe navigation with == operator', 'x&.foo == bar'],
    ['safe navigation with === operator', 'x&.foo === bar'],
    ['safe navigation with || operator', 'x&.foo || bar'],
    ['safe navigation with && operator', 'x&.foo && bar'],
    ['safe navigation with | operator', 'x&.foo | bar'],
    ['safe navigation with & operator', 'x&.foo & bar'],
    ['safe navigation with `nil?` method', 'x&.foo.nil?'],
    ['safe navigation with `present?` method', 'x&.foo.present?'],
    ['safe navigation with `blank?` method', 'x&.foo.blank?'],
    ['safe navigation with `try` method', 'a&.b.try(:c)'],
    ['safe navigation with assignment method', 'x&.foo = bar'],
    ['safe navigation with self assignment method', 'x&.foo += bar'],
    ['safe navigation with `to_d` method', 'x&.foo.to_d']
  ].each do |name, code|
    include_examples 'accepts', name, code
  end

  it 'registers an offense for ordinary method call exists after ' \
     'safe navigation method call' do
    expect_offense(<<~RUBY)
      x&.foo.bar
            ^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  it 'registers an offense for ordinary method call exists after ' \
     'safe navigation method call with an argument' do
    expect_offense(<<~RUBY)
      x&.foo(x).bar(y)
               ^^^^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  it 'registers an offense for ordinary method chain exists after ' \
     'safe navigation method call' do
    expect_offense(<<~RUBY)
      something
      x&.foo.bar.baz
            ^^^^^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  it 'registers an offense for ordinary method chain exists after ' \
     'safe navigation method call with an argument' do
    expect_offense(<<~RUBY)
      x&.foo(x).bar(y).baz(z)
               ^^^^^^^^^^^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  it 'registers an offense for ordinary method chain exists after ' \
     'safe navigation method call with a block-pass' do
    expect_offense(<<~RUBY)
      something
      x&.select(&:foo).bar
                      ^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  it 'registers an offense for ordinary method chain exists after ' \
     'safe navigation method call with a block' do
    expect_offense(<<~RUBY)
      something
      x&.select { |x| foo(x) }.bar
                              ^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  context '>= Ruby 2.7', :ruby27 do
    it 'registers an offense for ordinary method chain exists after ' \
       'safe navigation method call with a block using numbered parameter' do
      expect_offense(<<~RUBY)
        something
        x&.select { foo(_1) }.bar
                             ^^^^ Do not chain ordinary method call after safe navigation operator.
      RUBY
    end
  end

  it 'registers an offense for safe navigation with < operator' do
    expect_offense(<<~RUBY)
      x&.foo < bar
            ^^^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  it 'registers an offense for safe navigation with > operator' do
    expect_offense(<<~RUBY)
      x&.foo > bar
            ^^^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  it 'registers an offense for safe navigation with <= operator' do
    expect_offense(<<~RUBY)
      x&.foo <= bar
            ^^^^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  it 'registers an offense for safe navigation with >= operator' do
    expect_offense(<<~RUBY)
      x&.foo >= bar
            ^^^^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  it 'registers an offense for safe navigation with + operator' do
    expect_offense(<<~RUBY)
      x&.foo + bar
            ^^^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  it 'registers an offense for safe navigation with [] operator' do
    expect_offense(<<~RUBY)
      x&.foo[bar]
            ^^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  it 'registers an offense for safe navigation with []= operator' do
    expect_offense(<<~RUBY)
      x&.foo[bar] = baz
            ^^^^^^^^^^^ Do not chain ordinary method call after safe navigation operator.
    RUBY
  end

  context 'proper highlighting' do
    it 'when there are methods before' do
      expect_offense(<<~RUBY)
        something
        x&.foo.bar.baz
              ^^^^^^^^ Do not chain ordinary method call after safe navigation operator.
      RUBY
    end

    it 'when there are methods after' do
      expect_offense(<<~RUBY)
        x&.foo.bar.baz
              ^^^^^^^^ Do not chain ordinary method call after safe navigation operator.
        something
      RUBY
    end

    it 'when in a method' do
      expect_offense(<<~RUBY)
        def something
          x&.foo.bar.baz
                ^^^^^^^^ Do not chain ordinary method call after safe navigation operator.
        end
      RUBY
    end

    it 'when in a begin' do
      expect_offense(<<~RUBY)
        begin
          x&.foo.bar.baz
                ^^^^^^^^ Do not chain ordinary method call after safe navigation operator.
        end
      RUBY
    end

    it 'when used with a modifier if' do
      expect_offense(<<~RUBY)
        x&.foo.bar.baz if something
              ^^^^^^^^ Do not chain ordinary method call after safe navigation operator.
      RUBY
    end
  end
end
