require 'spec_helper'

describe RelativePositioning do
  let(:project) { create(:project) }
  let(:issue) { create(:issue, project: project) }
  let(:issue1) { create(:issue, project: project) }
  let(:new_issue) { create(:issue, project: project) }

  describe '.move_to_end' do
    it 'moves the object to the end' do
      Issue.move_to_end([issue, issue1])

      expect(issue1.prev_relative_position).to eq issue.relative_position
      expect(issue.prev_relative_position).to eq nil
      expect(issue1.next_relative_position).to eq nil
    end

    it 'does not perform any moves if all issues have their relative_position set' do
      issue.update!(relative_position: 1)

      expect(issue).not_to receive(:save)

      Issue.move_to_end([issue])
    end
  end

  describe '#max_relative_position' do
    it 'returns maximum position' do
      expect(issue.max_relative_position).to eq issue1.relative_position
    end
  end

  describe '#prev_relative_position' do
    it 'returns previous position if there is an issue above' do
      expect(issue1.prev_relative_position).to eq issue.relative_position
    end

    it 'returns nil if there is no issue above' do
      expect(issue.prev_relative_position).to eq nil
    end
  end

  describe '#next_relative_position' do
    it 'returns next position if there is an issue below' do
      expect(issue.next_relative_position).to eq issue1.relative_position
    end

    it 'returns nil if there is no issue below' do
      expect(issue1.next_relative_position).to eq nil
    end
  end

  describe '#move_before' do
    it 'moves issue before' do
      [issue1, issue].each(&:move_to_end)

      issue.move_before(issue1)

      expect(issue.relative_position).to be < issue1.relative_position
    end
  end

  describe '#move_after' do
    it 'moves issue after' do
      [issue, issue1].each(&:move_to_end)

      issue.move_after(issue1)

      expect(issue.relative_position).to be > issue1.relative_position
    end
  end

  describe '#move_to_end' do
    before do
      [issue, issue1].each do |issue|
        issue.move_to_end && issue.save
      end
    end

    it 'moves issue to the end' do
      new_issue.move_to_end

      expect(new_issue.relative_position).to be > issue1.relative_position
    end
  end

  describe '#shift_after?' do
    before do
      [issue, issue1].each do |issue|
        issue.move_to_end && issue.save
      end
    end

    it 'returns true' do
      issue.update(relative_position: issue1.relative_position - 1)

      expect(issue.shift_after?).to be_truthy
    end

    it 'returns false' do
      issue.update(relative_position: issue1.relative_position - 2)

      expect(issue.shift_after?).to be_falsey
    end
  end

  describe '#shift_before?' do
    before do
      [issue, issue1].each do |issue|
        issue.move_to_end && issue.save
      end
    end

    it 'returns true' do
      issue.update(relative_position: issue1.relative_position + 1)

      expect(issue.shift_before?).to be_truthy
    end

    it 'returns false' do
      issue.update(relative_position: issue1.relative_position + 2)

      expect(issue.shift_before?).to be_falsey
    end
  end

  describe '#move_between' do
    before do
      [issue, issue1].each do |issue|
        issue.move_to_end && issue.save
      end
    end

    it 'positions issue between two other' do
      new_issue.move_between(issue, issue1)

      expect(new_issue.relative_position).to be > issue.relative_position
      expect(new_issue.relative_position).to be < issue1.relative_position
    end

    it 'positions issue between on top' do
      new_issue.move_between(nil, issue)

      expect(new_issue.relative_position).to be < issue.relative_position
    end

    it 'positions issue between to end' do
      new_issue.move_between(issue1, nil)

      expect(new_issue.relative_position).to be > issue1.relative_position
    end

    it 'positions issues even when after and before positions are the same' do
      issue1.update relative_position: issue.relative_position

      new_issue.move_between(issue, issue1)

      expect(new_issue.relative_position).to be > issue.relative_position
      expect(issue.relative_position).to be < issue1.relative_position
    end

    it 'positions issues between other two if distance is 1' do
      issue1.update relative_position: issue.relative_position + 1

      new_issue.move_between(issue, issue1)

      expect(new_issue.relative_position).to be > issue.relative_position
      expect(issue.relative_position).to be < issue1.relative_position
    end

    it 'positions issue in the middle of other two if distance is big enough' do
      issue.update relative_position: 6000
      issue1.update relative_position: 10000

      new_issue.move_between(issue, issue1)

      expect(new_issue.relative_position).to eq(8000)
    end

    it 'positions issue closer to the middle if we are at the very top' do
      issue1.update relative_position: 6000

      new_issue.move_between(nil, issue1)

      expect(new_issue.relative_position).to eq(6000 - RelativePositioning::IDEAL_DISTANCE)
    end

    it 'positions issue closer to the middle if we are at the very bottom' do
      issue.update relative_position: 6000
      issue1.update relative_position: nil

      new_issue.move_between(issue, nil)

      expect(new_issue.relative_position).to eq(6000 + RelativePositioning::IDEAL_DISTANCE)
    end

    it 'positions issue in the middle of other two if distance is not big enough' do
      issue.update relative_position: 100
      issue1.update relative_position: 400

      new_issue.move_between(issue, issue1)

      expect(new_issue.relative_position).to eq(250)
    end

    it 'positions issue in the middle of other two is there is no place' do
      issue.update relative_position: 100
      issue1.update relative_position: 101

      new_issue.move_between(issue, issue1)

      expect(new_issue.relative_position).to be_between(issue.relative_position, issue1.relative_position)
    end

    it 'uses rebalancing if there is no place' do
      issue.update relative_position: 100
      issue1.update relative_position: 101
      issue2 = create(:issue, relative_position: 102, project: project)
      new_issue.update relative_position: 103

      new_issue.move_between(issue1, issue2)
      new_issue.save!

      expect(new_issue.relative_position).to be_between(issue1.relative_position, issue2.relative_position)
      expect(issue.reload.relative_position).not_to eq(100)
    end

    it 'positions issue right if we pass none-sequential parameters' do
      issue.update relative_position: 99
      issue1.update relative_position: 101
      issue2 = create(:issue, relative_position: 102, project: project)
      new_issue.update relative_position: 103

      new_issue.move_between(issue, issue2)
      new_issue.save!

      expect(new_issue.relative_position).to be(100)
    end
  end
end
