diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 04:45:44 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-09-19 04:45:44 +0300 |
commit | 85dc423f7090da0a52c73eb66faf22ddb20efff9 (patch) | |
tree | 9160f299afd8c80c038f08e1545be119f5e3f1e1 /spec/lib/gitlab/relative_positioning | |
parent | 15c2c8c66dbe422588e5411eee7e68f1fa440bb8 (diff) |
Add latest changes from gitlab-org/gitlab@13-4-stable-ee
Diffstat (limited to 'spec/lib/gitlab/relative_positioning')
-rw-r--r-- | spec/lib/gitlab/relative_positioning/item_context_spec.rb | 215 | ||||
-rw-r--r-- | spec/lib/gitlab/relative_positioning/mover_spec.rb | 487 | ||||
-rw-r--r-- | spec/lib/gitlab/relative_positioning/range_spec.rb | 162 |
3 files changed, 864 insertions, 0 deletions
diff --git a/spec/lib/gitlab/relative_positioning/item_context_spec.rb b/spec/lib/gitlab/relative_positioning/item_context_spec.rb new file mode 100644 index 00000000000..daea8d8470d --- /dev/null +++ b/spec/lib/gitlab/relative_positioning/item_context_spec.rb @@ -0,0 +1,215 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::RelativePositioning::ItemContext do + let_it_be(:default_user) { create_default(:user) } + let_it_be(:project, reload: true) { create(:project) } + + def create_issue(pos) + create(:issue, project: project, relative_position: pos) + end + + range = (101..107) # A deliberately small range, so we can test everything + indices = (0..).take(range.size) + + let(:start) { ((range.first + range.last) / 2.0).floor } + let(:subjects) { issues.map { |i| described_class.new(i.reset, range) } } + + # This allows us to refer to range in methods and examples + let_it_be(:full_range) { range } + + context 'there are gaps at the start and end' do + let_it_be(:issues) { (range.first.succ..range.last.pred).map { |pos| create_issue(pos) } } + + it 'is always possible to find a gap' do + expect(subjects) + .to all(have_attributes(find_next_gap_before: be_present, find_next_gap_after: be_present)) + end + + where(:index) { indices.reverse.drop(2) } + + with_them do + subject { subjects[index] } + + let(:positions) { subject.scoped_items.map(&:relative_position) } + + it 'is possible to shift_right, which will consume the gap at the end' do + subject.shift_right + + expect(subject.find_next_gap_after).not_to be_present + + expect(positions).to all(be_between(range.first, range.last)) + expect(positions).to eq(positions.uniq) + end + + it 'is possible to create_space_right, which will move the gap to immediately after' do + subject.create_space_right + + expect(subject.find_next_gap_after).to have_attributes(start_pos: subject.relative_position) + expect(positions).to all(be_between(range.first, range.last)) + expect(positions).to eq(positions.uniq) + end + + it 'is possible to shift_left, which will consume the gap at the start' do + subject.shift_left + + expect(subject.find_next_gap_before).not_to be_present + expect(positions).to all(be_between(range.first, range.last)) + expect(positions).to eq(positions.uniq) + end + + it 'is possible to create_space_left, which will move the gap to immediately before' do + subject.create_space_left + + expect(subject.find_next_gap_before).to have_attributes(start_pos: subject.relative_position) + expect(positions).to all(be_between(range.first, range.last)) + expect(positions).to eq(positions.uniq) + end + end + end + + context 'there is a gap of multiple spaces' do + let_it_be(:issues) { [range.first, range.last].map { |pos| create_issue(pos) } } + + it 'is impossible to move the last element to the right' do + expect { subjects.last.shift_right }.to raise_error(Gitlab::RelativePositioning::NoSpaceLeft) + end + + it 'is impossible to move the first element to the left' do + expect { subjects.first.shift_left }.to raise_error(Gitlab::RelativePositioning::NoSpaceLeft) + end + + it 'is possible to move the last element to the left' do + subject = subjects.last + + expect { subject.shift_left }.to change { subject.relative_position }.by(be < 0) + end + + it 'is possible to move the first element to the right' do + subject = subjects.first + + expect { subject.shift_right }.to change { subject.relative_position }.by(be > 0) + end + + it 'is possible to find the gap from the right' do + gap = Gitlab::RelativePositioning::Gap.new(range.last, range.first) + + expect(subjects.last).to have_attributes( + find_next_gap_before: eq(gap), + find_next_gap_after: be_nil + ) + end + + it 'is possible to find the gap from the left' do + gap = Gitlab::RelativePositioning::Gap.new(range.first, range.last) + + expect(subjects.first).to have_attributes( + find_next_gap_before: be_nil, + find_next_gap_after: eq(gap) + ) + end + end + + context 'there are several free spaces' do + let_it_be(:issues) { range.select(&:even?).map { |pos| create_issue(pos) } } + let_it_be(:gaps) do + range.select(&:odd?).map do |pos| + rhs = pos.succ.clamp(range.first, range.last) + lhs = pos.pred.clamp(range.first, range.last) + + { + before: Gitlab::RelativePositioning::Gap.new(rhs, lhs), + after: Gitlab::RelativePositioning::Gap.new(lhs, rhs) + } + end + end + + def issue_at(position) + issues.find { |i| i.relative_position == position } + end + + where(:current_pos) { range.select(&:even?) } + + with_them do + let(:subject) { subjects.find { |s| s.relative_position == current_pos } } + let(:siblings) { subjects.reject { |s| s.relative_position == current_pos } } + + def covered_by_range(pos) + full_range.cover?(pos) ? pos : nil + end + + it 'finds the closest gap' do + closest_gap_before = gaps + .map { |gap| gap[:before] } + .select { |gap| gap.start_pos <= subject.relative_position } + .max_by { |gap| gap.start_pos } + closest_gap_after = gaps + .map { |gap| gap[:after] } + .select { |gap| gap.start_pos >= subject.relative_position } + .min_by { |gap| gap.start_pos } + + expect(subject).to have_attributes( + find_next_gap_before: closest_gap_before, + find_next_gap_after: closest_gap_after + ) + end + + it 'finds the neighbours' do + expect(subject).to have_attributes( + lhs_neighbour: subject.neighbour(issue_at(subject.relative_position - 2)), + rhs_neighbour: subject.neighbour(issue_at(subject.relative_position + 2)) + ) + end + + it 'finds the next relative_positions' do + expect(subject).to have_attributes( + prev_relative_position: covered_by_range(subject.relative_position - 2), + next_relative_position: covered_by_range(subject.relative_position + 2) + ) + end + + it 'finds the min/max positions' do + expect(subject).to have_attributes( + min_relative_position: issues.first.relative_position, + max_relative_position: issues.last.relative_position + ) + end + + it 'finds the min/max siblings' do + expect(subject).to have_attributes( + min_sibling: siblings.first, + max_sibling: siblings.last + ) + end + end + end + + context 'there is at least one free space' do + where(:free_space) { range.to_a } + + with_them do + let(:issues) { range.reject { |x| x == free_space }.map { |p| create_issue(p) } } + let(:gap_rhs) { free_space.succ.clamp(range.first, range.last) } + let(:gap_lhs) { free_space.pred.clamp(range.first, range.last) } + + it 'can always find a gap before if there is space to the left' do + expected_gap = Gitlab::RelativePositioning::Gap.new(gap_rhs, gap_lhs) + + to_the_right_of_gap = subjects.select { |s| free_space < s.relative_position } + + expect(to_the_right_of_gap) + .to all(have_attributes(find_next_gap_before: eq(expected_gap), find_next_gap_after: be_nil)) + end + + it 'can always find a gap after if there is space to the right' do + expected_gap = Gitlab::RelativePositioning::Gap.new(gap_lhs, gap_rhs) + + to_the_left_of_gap = subjects.select { |s| s.relative_position < free_space } + + expect(to_the_left_of_gap) + .to all(have_attributes(find_next_gap_before: be_nil, find_next_gap_after: eq(expected_gap))) + end + end + end +end diff --git a/spec/lib/gitlab/relative_positioning/mover_spec.rb b/spec/lib/gitlab/relative_positioning/mover_spec.rb new file mode 100644 index 00000000000..c49230c2415 --- /dev/null +++ b/spec/lib/gitlab/relative_positioning/mover_spec.rb @@ -0,0 +1,487 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe RelativePositioning::Mover do + let_it_be(:user) { create(:user) } + let_it_be(:one_sibling, reload: true) { create(:project, creator: user, namespace: user.namespace) } + let_it_be(:one_free_space, reload: true) { create(:project, creator: user, namespace: user.namespace) } + let_it_be(:fully_occupied, reload: true) { create(:project, creator: user, namespace: user.namespace) } + let_it_be(:no_issues, reload: true) { create(:project, creator: user, namespace: user.namespace) } + let_it_be(:three_sibs, reload: true) { create(:project, creator: user, namespace: user.namespace) } + + def create_issue(pos, parent = project) + create(:issue, author: user, project: parent, relative_position: pos) + end + + range = (101..105) + indices = (0..).take(range.size) + + let(:start) { ((range.first + range.last) / 2.0).floor } + + subject { described_class.new(start, range) } + + let_it_be(:full_set) do + range.each_with_index.map do |pos, i| + create(:issue, iid: i.succ, project: fully_occupied, relative_position: pos) + end + end + + let_it_be(:sole_sibling) { create(:issue, iid: 1, project: one_sibling, relative_position: nil) } + let_it_be(:one_sibling_set) { [sole_sibling] } + let_it_be(:one_free_space_set) do + indices.drop(1).map { |iid| create(:issue, project: one_free_space, iid: iid.succ) } + end + let_it_be(:three_sibs_set) do + [1, 2, 3].map { |iid| create(:issue, iid: iid, project: three_sibs) } + end + + def set_positions(positions) + vals = issues.zip(positions).map do |issue, pos| + issue.relative_position = pos + "(#{issue.id}, #{pos})" + end.join(', ') + + Issue.connection.exec_query(<<~SQL, 'set-positions') + WITH cte(cte_id, new_pos) AS ( + SELECT * FROM (VALUES #{vals}) as t (id, pos) + ) + UPDATE issues SET relative_position = new_pos FROM cte WHERE id = cte_id + ; + SQL + end + + def ids_in_position_order + project.issues.reorder(:relative_position).pluck(:id) + end + + def relative_positions + project.issues.pluck(:relative_position) + end + + describe '#move_to_end' do + def max_position + project.issues.maximum(:relative_position) + end + + def move_to_end(issue) + subject.move_to_end(issue) + issue.save! + end + + shared_examples 'able to place a new item at the end' do + it 'can place any new item' do + existing_issues = ids_in_position_order + new_item = create_issue(nil) + + expect do + move_to_end(new_item) + end.to change { project.issues.pluck(:id, :relative_position) } + + expect(new_item.relative_position).to eq(max_position) + expect(relative_positions).to all(be_between(range.first, range.last)) + expect(ids_in_position_order).to eq(existing_issues + [new_item.id]) + end + end + + shared_examples 'able to move existing items to the end' do + it 'can move any existing item' do + issues = project.issues.reorder(:relative_position).to_a + issue = issues[index] + other_issues = issues.reject { |i| i == issue } + expect(relative_positions).to all(be_between(range.first, range.last)) + + if issues.last == issue + move_to_end(issue) # May not change the positions + else + expect do + move_to_end(issue) + end.to change { project.issues.pluck(:id, :relative_position) } + end + + project.reset + + expect(relative_positions).to all(be_between(range.first, range.last)) + expect(issue.relative_position).to eq(max_position) + expect(ids_in_position_order).to eq(other_issues.map(&:id) + [issue.id]) + end + end + + context 'all positions are taken' do + let(:issues) { full_set } + let(:project) { fully_occupied } + + it 'raises an error when placing a new item' do + new_item = create_issue(nil) + + expect { subject.move_to_end(new_item) }.to raise_error(RelativePositioning::NoSpaceLeft) + end + + where(:index) { indices } + + with_them do + it_behaves_like 'able to move existing items to the end' + end + end + + context 'there are no siblings' do + let(:issues) { [] } + let(:project) { no_issues } + + it_behaves_like 'able to place a new item at the end' + end + + context 'there is only one sibling' do + where(:pos) { range.to_a } + + with_them do + let(:issues) { one_sibling_set } + let(:project) { one_sibling } + let(:index) { 0 } + + before do + sole_sibling.reset.update!(relative_position: pos) + end + + it_behaves_like 'able to place a new item at the end' + + it_behaves_like 'able to move existing items to the end' + end + end + + context 'at least one position is free' do + where(:free_space, :index) do + is = indices.take(range.size - 1) + + range.to_a.product(is) + end + + with_them do + let(:issues) { one_free_space_set } + let(:project) { one_free_space } + + before do + positions = range.reject { |x| x == free_space } + set_positions(positions) + end + + it_behaves_like 'able to place a new item at the end' + + it_behaves_like 'able to move existing items to the end' + end + end + end + + describe '#move_to_start' do + def min_position + project.issues.minimum(:relative_position) + end + + def move_to_start(issue) + subject.move_to_start(issue) + issue.save! + end + + shared_examples 'able to place a new item at the start' do + it 'can place any new item' do + existing_issues = ids_in_position_order + new_item = create_issue(nil) + + expect do + move_to_start(new_item) + end.to change { project.issues.pluck(:id, :relative_position) } + + expect(relative_positions).to all(be_between(range.first, range.last)) + expect(new_item.relative_position).to eq(min_position) + expect(ids_in_position_order).to eq([new_item.id] + existing_issues) + end + end + + shared_examples 'able to move existing items to the start' do + it 'can move any existing item' do + issues = project.issues.reorder(:relative_position).to_a + issue = issues[index] + other_issues = issues.reject { |i| i == issue } + expect(relative_positions).to all(be_between(range.first, range.last)) + + if issues.first == issue + move_to_start(issue) # May not change the positions + else + expect do + move_to_start(issue) + end.to change { project.issues.pluck(:id, :relative_position) } + end + + project.reset + + expect(relative_positions).to all(be_between(range.first, range.last)) + expect(issue.relative_position).to eq(min_position) + expect(ids_in_position_order).to eq([issue.id] + other_issues.map(&:id)) + end + end + + context 'all positions are taken' do + let(:issues) { full_set } + let(:project) { fully_occupied } + + it 'raises an error when placing a new item' do + new_item = create(:issue, project: project, relative_position: nil) + + expect { subject.move_to_start(new_item) }.to raise_error(RelativePositioning::NoSpaceLeft) + end + + where(:index) { indices } + + with_them do + it_behaves_like 'able to move existing items to the start' + end + end + + context 'there are no siblings' do + let(:project) { no_issues } + let(:issues) { [] } + + it_behaves_like 'able to place a new item at the start' + end + + context 'there is only one sibling' do + where(:pos) { range.to_a } + + with_them do + let(:issues) { one_sibling_set } + let(:project) { one_sibling } + let(:index) { 0 } + + before do + sole_sibling.reset.update!(relative_position: pos) + end + + it_behaves_like 'able to place a new item at the start' + + it_behaves_like 'able to move existing items to the start' + end + end + + context 'at least one position is free' do + where(:free_space, :index) do + range.to_a.product((0..).take(range.size - 1).to_a) + end + + with_them do + let(:issues) { one_free_space_set } + let(:project) { one_free_space } + + before do + set_positions(range.reject { |x| x == free_space }) + end + + it_behaves_like 'able to place a new item at the start' + + it_behaves_like 'able to move existing items to the start' + end + end + end + + describe '#move' do + shared_examples 'able to move a new item' do + let(:other_issues) { project.issues.reorder(relative_position: :asc).to_a } + let!(:previous_order) { other_issues.map(&:id) } + + it 'can place any new item betwen two others' do + new_item = create_issue(nil) + + subject.move(new_item, lhs, rhs) + new_item.save! + lhs.reset + rhs.reset + + expect(new_item.relative_position).to be_between(range.first, range.last) + expect(new_item.relative_position).to be_between(lhs.relative_position, rhs.relative_position) + + ids = project.issues.reorder(:relative_position).pluck(:id).reject { |id| id == new_item.id } + expect(ids).to eq(previous_order) + end + + it 'can place any new item after another' do + new_item = create_issue(nil) + + subject.move(new_item, lhs, nil) + new_item.save! + lhs.reset + + expect(new_item.relative_position).to be_between(range.first, range.last) + expect(new_item.relative_position).to be > lhs.relative_position + + ids = project.issues.reorder(:relative_position).pluck(:id).reject { |id| id == new_item.id } + expect(ids).to eq(previous_order) + end + + it 'can place any new item before another' do + new_item = create_issue(nil) + + subject.move(new_item, nil, rhs) + new_item.save! + rhs.reset + + expect(new_item.relative_position).to be_between(range.first, range.last) + expect(new_item.relative_position).to be < rhs.relative_position + + ids = project.issues.reorder(:relative_position).pluck(:id).reject { |id| id == new_item.id } + expect(ids).to eq(previous_order) + end + end + + shared_examples 'able to move an existing item' do + let(:all_issues) { project.issues.reorder(:relative_position).to_a } + let(:item) { all_issues[index] } + let(:positions) { project.reset.issues.pluck(:relative_position) } + let(:other_issues) { all_issues.reject { |i| i == item } } + let!(:previous_order) { other_issues.map(&:id) } + let(:new_order) do + project.issues.where.not(id: item.id).reorder(:relative_position).pluck(:id) + end + + it 'can place any item betwen two others' do + subject.move(item, lhs, rhs) + item.save! + lhs.reset + rhs.reset + + expect(positions).to all(be_between(range.first, range.last)) + expect(positions).to match_array(positions.uniq) + expect(item.relative_position).to be_between(lhs.relative_position, rhs.relative_position) + + expect(new_order).to eq(previous_order) + end + + def sequence(expected_sequence) + range = (expected_sequence.first.relative_position..expected_sequence.last.relative_position) + + project.issues.reorder(:relative_position).where(relative_position: range) + end + + it 'can place any item after another' do + subject.move(item, lhs, nil) + item.save! + lhs.reset + + expect(positions).to all(be_between(range.first, range.last)) + expect(positions).to match_array(positions.uniq) + expect(item.relative_position).to be >= lhs.relative_position + + expected_sequence = [lhs, item].uniq + + expect(sequence(expected_sequence)).to eq(expected_sequence) + + expect(new_order).to eq(previous_order) + end + + it 'can place any item before another' do + subject.move(item, nil, rhs) + item.save! + rhs.reset + + expect(positions).to all(be_between(range.first, range.last)) + expect(positions).to match_array(positions.uniq) + expect(item.relative_position).to be <= rhs.relative_position + + expected_sequence = [item, rhs].uniq + + expect(sequence(expected_sequence)).to eq(expected_sequence) + + expect(new_order).to eq(previous_order) + end + end + + context 'all positions are taken' do + let(:issues) { full_set } + let(:project) { fully_occupied } + + where(:idx_a, :idx_b) do + indices.product(indices).select { |a, b| a < b } + end + + with_them do + let(:lhs) { issues[idx_a].reset } + let(:rhs) { issues[idx_b].reset } + + it 'raises an error when placing a new item anywhere' do + new_item = create_issue(nil) + + expect { subject.move(new_item, lhs, rhs) } + .to raise_error(Gitlab::RelativePositioning::NoSpaceLeft) + + expect { subject.move(new_item, nil, rhs) } + .to raise_error(Gitlab::RelativePositioning::NoSpaceLeft) + + expect { subject.move(new_item, lhs, nil) } + .to raise_error(Gitlab::RelativePositioning::NoSpaceLeft) + end + + where(:index) { indices } + + with_them do + it_behaves_like 'able to move an existing item' + end + end + end + + context 'there are no siblings' do + let(:project) { no_issues } + + it 'raises an ArgumentError when both first and last are nil' do + new_item = create_issue(nil) + + expect { subject.move(new_item, nil, nil) }.to raise_error(ArgumentError) + end + end + + context 'there are a couple of siblings' do + where(:pos_movable, :pos_a, :pos_b) do + xs = range.to_a + + xs.product(xs).product(xs).map(&:flatten) + .select { |vals| vals == vals.uniq && vals[1] < vals[2] } + end + + with_them do + let(:issues) { three_sibs_set } + let(:project) { three_sibs } + let(:index) { 0 } + let(:lhs) { issues[1] } + let(:rhs) { issues[2] } + + before do + set_positions([pos_movable, pos_a, pos_b]) + end + + it_behaves_like 'able to move a new item' + it_behaves_like 'able to move an existing item' + end + end + + context 'at least one position is free' do + where(:free_space, :index, :pos_a, :pos_b) do + is = indices.reverse.drop(1) + + range.to_a.product(is).product(is).product(is) + .map(&:flatten) + .select { |_, _, a, b| a < b } + end + + with_them do + let(:issues) { one_free_space_set } + let(:project) { one_free_space } + let(:lhs) { issues[pos_a] } + let(:rhs) { issues[pos_b] } + + before do + set_positions(range.reject { |x| x == free_space }) + end + + it_behaves_like 'able to move a new item' + it_behaves_like 'able to move an existing item' + end + end + end +end diff --git a/spec/lib/gitlab/relative_positioning/range_spec.rb b/spec/lib/gitlab/relative_positioning/range_spec.rb new file mode 100644 index 00000000000..c3386336493 --- /dev/null +++ b/spec/lib/gitlab/relative_positioning/range_spec.rb @@ -0,0 +1,162 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Gitlab::RelativePositioning::Range do + item_a = OpenStruct.new(relative_position: 100, object: :x, positioned?: true) + item_b = OpenStruct.new(relative_position: 200, object: :y, positioned?: true) + + before do + allow(item_a).to receive(:lhs_neighbour) { nil } + allow(item_a).to receive(:rhs_neighbour) { item_b } + + allow(item_b).to receive(:lhs_neighbour) { item_a } + allow(item_b).to receive(:rhs_neighbour) { nil } + end + + describe 'RelativePositioning.range' do + it 'raises if lhs and rhs are nil' do + expect { Gitlab::RelativePositioning.range(nil, nil) }.to raise_error(ArgumentError) + end + + it 'raises an error if there is no extent' do + expect { Gitlab::RelativePositioning.range(item_a, item_a) }.to raise_error(ArgumentError) + end + + it 'constructs a closed range when both termini are provided' do + range = Gitlab::RelativePositioning.range(item_a, item_b) + expect(range).to be_a_kind_of(Gitlab::RelativePositioning::Range) + expect(range).to be_a_kind_of(Gitlab::RelativePositioning::ClosedRange) + end + + it 'constructs a starting-from range when only the LHS is provided' do + range = Gitlab::RelativePositioning.range(item_a, nil) + expect(range).to be_a_kind_of(Gitlab::RelativePositioning::Range) + expect(range).to be_a_kind_of(Gitlab::RelativePositioning::StartingFrom) + end + + it 'constructs an ending-at range when only the RHS is provided' do + range = Gitlab::RelativePositioning.range(nil, item_b) + expect(range).to be_a_kind_of(Gitlab::RelativePositioning::Range) + expect(range).to be_a_kind_of(Gitlab::RelativePositioning::EndingAt) + end + end + + it 'infers neighbours correctly' do + starting_at_a = Gitlab::RelativePositioning.range(item_a, nil) + ending_at_b = Gitlab::RelativePositioning.range(nil, item_b) + + expect(starting_at_a).to eq(ending_at_b) + end + + describe '#open_on_left?' do + where(:lhs, :rhs, :expected_result) do + [ + [item_a, item_b, false], + [item_a, nil, false], + [nil, item_b, false], + [item_b, nil, false], + [nil, item_a, true] + ] + end + + with_them do + it 'is true if there is no LHS terminus' do + range = Gitlab::RelativePositioning.range(lhs, rhs) + + expect(range.open_on_left?).to be(expected_result) + end + end + end + + describe '#open_on_right?' do + where(:lhs, :rhs, :expected_result) do + [ + [item_a, item_b, false], + [item_a, nil, false], + [nil, item_b, false], + [item_b, nil, true], + [nil, item_a, false] + ] + end + + with_them do + it 'is true if there is no RHS terminus' do + range = Gitlab::RelativePositioning.range(lhs, rhs) + + expect(range.open_on_right?).to be(expected_result) + end + end + end + + describe '#cover?' do + item_c = OpenStruct.new(relative_position: 150, object: :z, positioned?: true) + item_d = OpenStruct.new(relative_position: 050, object: :w, positioned?: true) + item_e = OpenStruct.new(relative_position: 250, object: :r, positioned?: true) + item_f = OpenStruct.new(positioned?: false) + item_ax = OpenStruct.new(relative_position: 100, object: :not_x, positioned?: true) + item_bx = OpenStruct.new(relative_position: 200, object: :not_y, positioned?: true) + + where(:lhs, :rhs, :item, :expected_result) do + [ + [item_a, item_b, item_a, true], + [item_a, item_b, item_b, true], + [item_a, item_b, item_c, true], + [item_a, item_b, item_d, false], + [item_a, item_b, item_e, false], + [item_a, item_b, item_ax, false], + [item_a, item_b, item_bx, false], + [item_a, item_b, item_f, false], + [item_a, item_b, nil, false], + + [nil, item_b, item_a, true], + [nil, item_b, item_b, true], + [nil, item_b, item_c, true], + [nil, item_b, item_d, false], + [nil, item_b, item_e, false], + [nil, item_b, item_ax, false], + [nil, item_b, item_bx, false], + [nil, item_b, item_f, false], + [nil, item_b, nil, false], + + [item_a, nil, item_a, true], + [item_a, nil, item_b, true], + [item_a, nil, item_c, true], + [item_a, nil, item_d, false], + [item_a, nil, item_e, false], + [item_a, nil, item_ax, false], + [item_a, nil, item_bx, false], + [item_a, nil, item_f, false], + [item_a, nil, nil, false], + + [nil, item_a, item_a, true], + [nil, item_a, item_b, false], + [nil, item_a, item_c, false], + [nil, item_a, item_d, true], + [nil, item_a, item_e, false], + [nil, item_a, item_ax, false], + [nil, item_a, item_bx, false], + [nil, item_a, item_f, false], + [nil, item_a, nil, false], + + [item_b, nil, item_a, false], + [item_b, nil, item_b, true], + [item_b, nil, item_c, false], + [item_b, nil, item_d, false], + [item_b, nil, item_e, true], + [item_b, nil, item_ax, false], + [item_b, nil, item_bx, false], + [item_b, nil, item_f, false], + [item_b, nil, nil, false] + ] + end + + with_them do + it 'is true when the object is within the bounds of the range' do + range = Gitlab::RelativePositioning.range(lhs, rhs) + + expect(range.cover?(item)).to be(expected_result) + end + end + end +end |