summaryrefslogtreecommitdiff
path: root/spec
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2021-03-15 21:09:16 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2021-03-15 21:09:16 +0000
commite8c01bc6a16cc4aa934ac42cccb7b287527c93f0 (patch)
tree947fa135303b20480f8b3a173fa59a796c6d0d36 /spec
parenta0213db466c75403a5a79f95af8a0a5cff13014c (diff)
downloadgitlab-ce-e8c01bc6a16cc4aa934ac42cccb7b287527c93f0.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
-rw-r--r--spec/frontend/diffs/components/inline_diff_table_row_spec.js13
-rw-r--r--spec/frontend/pipelines/graph_shared/links_inner_spec.js2
-rw-r--r--spec/frontend/projects/commit/components/form_modal_spec.js20
-rw-r--r--spec/frontend/projects/commit/components/projects_dropdown_spec.js124
-rw-r--r--spec/frontend/projects/commit/mock_data.js1
-rw-r--r--spec/frontend/projects/commit/store/actions_spec.js41
-rw-r--r--spec/frontend/projects/commit/store/getters_spec.js17
-rw-r--r--spec/frontend/projects/commit/store/mutations_spec.js20
-rw-r--r--spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js2
-rw-r--r--spec/helpers/commits_helper_spec.rb19
-rw-r--r--spec/lib/gitlab/object_hierarchy_spec.rb260
11 files changed, 391 insertions, 128 deletions
diff --git a/spec/frontend/diffs/components/inline_diff_table_row_spec.js b/spec/frontend/diffs/components/inline_diff_table_row_spec.js
index 7e6f75ad6f8..28b3055b58c 100644
--- a/spec/frontend/diffs/components/inline_diff_table_row_spec.js
+++ b/spec/frontend/diffs/components/inline_diff_table_row_spec.js
@@ -215,14 +215,14 @@ describe('InlineDiffTableRow', () => {
const TEST_LINE_NUMBER = 1;
describe.each`
- lineProps | findLineNumber | expectedHref | expectedClickArg
- ${{ line_code: TEST_LINE_CODE, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${`#${TEST_LINE_CODE}`} | ${TEST_LINE_CODE}
- ${{ line_code: undefined, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${'#'} | ${undefined}
- ${{ line_code: undefined, left: { line_code: TEST_LINE_CODE }, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${'#'} | ${TEST_LINE_CODE}
- ${{ line_code: undefined, right: { line_code: TEST_LINE_CODE }, new_line: TEST_LINE_NUMBER }} | ${findLineNumberNew} | ${'#'} | ${TEST_LINE_CODE}
+ lineProps | findLineNumber | expectedHref | expectedClickArg | expectedQaSelector
+ ${{ line_code: TEST_LINE_CODE, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${`#${TEST_LINE_CODE}`} | ${TEST_LINE_CODE} | ${undefined}
+ ${{ line_code: undefined, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${'#'} | ${undefined} | ${undefined}
+ ${{ line_code: undefined, left: { line_code: TEST_LINE_CODE }, old_line: TEST_LINE_NUMBER }} | ${findLineNumberOld} | ${'#'} | ${TEST_LINE_CODE} | ${undefined}
+ ${{ line_code: undefined, right: { line_code: TEST_LINE_CODE }, new_line: TEST_LINE_NUMBER }} | ${findLineNumberNew} | ${'#'} | ${TEST_LINE_CODE} | ${'new_diff_line_link'}
`(
'with line ($lineProps)',
- ({ lineProps, findLineNumber, expectedHref, expectedClickArg }) => {
+ ({ lineProps, findLineNumber, expectedHref, expectedClickArg, expectedQaSelector }) => {
beforeEach(() => {
jest.spyOn(store, 'dispatch').mockImplementation();
createComponent({
@@ -235,6 +235,7 @@ describe('InlineDiffTableRow', () => {
expect(findLineNumber().attributes()).toEqual({
href: expectedHref,
'data-linenumber': TEST_LINE_NUMBER.toString(),
+ 'data-qa-selector': expectedQaSelector,
});
});
diff --git a/spec/frontend/pipelines/graph_shared/links_inner_spec.js b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
index 15a5fbfeffe..6fef1c9b62e 100644
--- a/spec/frontend/pipelines/graph_shared/links_inner_spec.js
+++ b/spec/frontend/pipelines/graph_shared/links_inner_spec.js
@@ -284,7 +284,7 @@ describe('Links Inner component', () => {
const numLinks = 1;
const metricsData = {
histograms: [
- { name: PIPELINES_DETAIL_LINK_DURATION, value: duration },
+ { name: PIPELINES_DETAIL_LINK_DURATION, value: duration / 1000 },
{ name: PIPELINES_DETAIL_LINKS_TOTAL, value: numLinks },
{
name: PIPELINES_DETAIL_LINKS_JOB_RATIO,
diff --git a/spec/frontend/projects/commit/components/form_modal_spec.js b/spec/frontend/projects/commit/components/form_modal_spec.js
index 1569f5b4bbe..708644cb7ee 100644
--- a/spec/frontend/projects/commit/components/form_modal_spec.js
+++ b/spec/frontend/projects/commit/components/form_modal_spec.js
@@ -7,6 +7,7 @@ import axios from '~/lib/utils/axios_utils';
import { BV_SHOW_MODAL } from '~/lib/utils/constants';
import BranchesDropdown from '~/projects/commit/components/branches_dropdown.vue';
import CommitFormModal from '~/projects/commit/components/form_modal.vue';
+import ProjectsDropdown from '~/projects/commit/components/projects_dropdown.vue';
import eventHub from '~/projects/commit/event_hub';
import createStore from '~/projects/commit/store';
import mockData from '../mock_data';
@@ -20,7 +21,10 @@ describe('CommitFormModal', () => {
store = createStore({ ...mockData.mockModal, ...state });
wrapper = extendedWrapper(
method(CommitFormModal, {
- provide,
+ provide: {
+ ...provide,
+ glFeatures: { pickIntoProject: true },
+ },
propsData: { ...mockData.modalPropsData },
store,
attrs: {
@@ -33,7 +37,9 @@ describe('CommitFormModal', () => {
const findModal = () => wrapper.findComponent(GlModal);
const findStartBranch = () => wrapper.find('#start_branch');
- const findDropdown = () => wrapper.findComponent(BranchesDropdown);
+ const findTargetProject = () => wrapper.find('#target_project_id');
+ const findBranchesDropdown = () => wrapper.findComponent(BranchesDropdown);
+ const findProjectsDropdown = () => wrapper.findComponent(ProjectsDropdown);
const findForm = () => findModal().findComponent(GlForm);
const findCheckBox = () => findForm().findComponent(GlFormCheckbox);
const findPrependedText = () => wrapper.findByTestId('prepended-text');
@@ -146,11 +152,19 @@ describe('CommitFormModal', () => {
});
it('Changes the start_branch input value', async () => {
- findDropdown().vm.$emit('selectBranch', '_changed_branch_value_');
+ findBranchesDropdown().vm.$emit('selectBranch', '_changed_branch_value_');
await wrapper.vm.$nextTick();
expect(findStartBranch().attributes('value')).toBe('_changed_branch_value_');
});
+
+ it('Changes the target_project_id input value', async () => {
+ findProjectsDropdown().vm.$emit('selectProject', '_changed_project_value_');
+
+ await wrapper.vm.$nextTick();
+
+ expect(findTargetProject().attributes('value')).toBe('_changed_project_value_');
+ });
});
});
diff --git a/spec/frontend/projects/commit/components/projects_dropdown_spec.js b/spec/frontend/projects/commit/components/projects_dropdown_spec.js
new file mode 100644
index 00000000000..bb20918e0cd
--- /dev/null
+++ b/spec/frontend/projects/commit/components/projects_dropdown_spec.js
@@ -0,0 +1,124 @@
+import { GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
+import { shallowMount } from '@vue/test-utils';
+import Vue from 'vue';
+import Vuex from 'vuex';
+import { extendedWrapper } from 'helpers/vue_test_utils_helper';
+import ProjectsDropdown from '~/projects/commit/components/projects_dropdown.vue';
+
+Vue.use(Vuex);
+
+describe('ProjectsDropdown', () => {
+ let wrapper;
+ let store;
+ const spyFetchProjects = jest.fn();
+ const projectsMockData = [
+ { id: '1', name: '_project_1_', refsUrl: '_project_1_/refs' },
+ { id: '2', name: '_project_2_', refsUrl: '_project_2_/refs' },
+ { id: '3', name: '_project_3_', refsUrl: '_project_3_/refs' },
+ ];
+
+ const createComponent = (term, state = {}) => {
+ store = new Vuex.Store({
+ getters: {
+ sortedProjects: () => projectsMockData,
+ },
+ state,
+ });
+
+ wrapper = extendedWrapper(
+ shallowMount(ProjectsDropdown, {
+ store,
+ propsData: {
+ value: term,
+ },
+ }),
+ );
+ };
+
+ const findAllDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
+ const findSearchBoxByType = () => wrapper.findComponent(GlSearchBoxByType);
+ const findDropdownItemByIndex = (index) => wrapper.findAllComponents(GlDropdownItem).at(index);
+ const findNoResults = () => wrapper.findByTestId('empty-result-message');
+
+ afterEach(() => {
+ wrapper.destroy();
+ spyFetchProjects.mockReset();
+ });
+
+ describe('No projects found', () => {
+ beforeEach(() => {
+ createComponent('_non_existent_project_');
+ });
+
+ it('renders empty results message', () => {
+ expect(findNoResults().text()).toBe('No matching results');
+ });
+
+ it('shows GlSearchBoxByType with default attributes', () => {
+ expect(findSearchBoxByType().exists()).toBe(true);
+ expect(findSearchBoxByType().vm.$attrs).toMatchObject({
+ placeholder: 'Search projects',
+ });
+ });
+ });
+
+ describe('Search term is empty', () => {
+ beforeEach(() => {
+ createComponent('');
+ });
+
+ it('renders all projects when search term is empty', () => {
+ expect(findAllDropdownItems()).toHaveLength(3);
+ expect(findDropdownItemByIndex(0).text()).toBe('_project_1_');
+ expect(findDropdownItemByIndex(1).text()).toBe('_project_2_');
+ expect(findDropdownItemByIndex(2).text()).toBe('_project_3_');
+ });
+
+ it('should not be selected on the inactive project', () => {
+ expect(wrapper.vm.isSelected('_project_1_')).toBe(false);
+ });
+ });
+
+ describe('Projects found', () => {
+ beforeEach(() => {
+ createComponent('_project_1_', { targetProjectId: '1' });
+ });
+
+ it('renders only the project searched for', () => {
+ expect(findAllDropdownItems()).toHaveLength(1);
+ expect(findDropdownItemByIndex(0).text()).toBe('_project_1_');
+ });
+
+ it('should not display empty results message', () => {
+ expect(findNoResults().exists()).toBe(false);
+ });
+
+ it('should signify this project is selected', () => {
+ expect(findDropdownItemByIndex(0).props('isChecked')).toBe(true);
+ });
+
+ it('should signify the project is not selected', () => {
+ expect(wrapper.vm.isSelected('_not_selected_project_')).toBe(false);
+ });
+
+ describe('Custom events', () => {
+ it('should emit selectProject if a project is clicked', () => {
+ findDropdownItemByIndex(0).vm.$emit('click');
+
+ expect(wrapper.emitted('selectProject')).toEqual([['1']]);
+ expect(wrapper.vm.filterTerm).toBe('_project_1_');
+ });
+ });
+ });
+
+ describe('Case insensitive for search term', () => {
+ beforeEach(() => {
+ createComponent('_PrOjEcT_1_');
+ });
+
+ it('renders only the project searched for', () => {
+ expect(findAllDropdownItems()).toHaveLength(1);
+ expect(findDropdownItemByIndex(0).text()).toBe('_project_1_');
+ });
+ });
+});
diff --git a/spec/frontend/projects/commit/mock_data.js b/spec/frontend/projects/commit/mock_data.js
index 2b3b5a14c98..e4dcb24c4c0 100644
--- a/spec/frontend/projects/commit/mock_data.js
+++ b/spec/frontend/projects/commit/mock_data.js
@@ -24,4 +24,5 @@ export default {
openModal: '_open_modal_',
},
mockBranches: ['_branch_1', '_abc_', '_master_'],
+ mockProjects: ['_project_1', '_abc_', '_project_'],
};
diff --git a/spec/frontend/projects/commit/store/actions_spec.js b/spec/frontend/projects/commit/store/actions_spec.js
index 458372229cf..305257c9ca5 100644
--- a/spec/frontend/projects/commit/store/actions_spec.js
+++ b/spec/frontend/projects/commit/store/actions_spec.js
@@ -47,7 +47,7 @@ describe('Commit form modal store actions', () => {
it('dispatch correct actions on fetchBranches', (done) => {
jest
.spyOn(axios, 'get')
- .mockImplementation(() => Promise.resolve({ data: mockData.mockBranches }));
+ .mockImplementation(() => Promise.resolve({ data: { Branches: mockData.mockBranches } }));
testAction(
actions.fetchBranches,
@@ -108,4 +108,43 @@ describe('Commit form modal store actions', () => {
]);
});
});
+
+ describe('setBranchesEndpoint', () => {
+ it('commits SET_BRANCHES_ENDPOINT mutation', () => {
+ const endpoint = 'some/endpoint';
+
+ testAction(actions.setBranchesEndpoint, endpoint, {}, [
+ {
+ type: types.SET_BRANCHES_ENDPOINT,
+ payload: endpoint,
+ },
+ ]);
+ });
+ });
+
+ describe('setSelectedProject', () => {
+ const id = 1;
+
+ it('commits SET_SELECTED_PROJECT mutation', () => {
+ testAction(
+ actions.setSelectedProject,
+ id,
+ {},
+ [
+ {
+ type: types.SET_SELECTED_PROJECT,
+ payload: id,
+ },
+ ],
+ [
+ {
+ type: 'setBranchesEndpoint',
+ },
+ {
+ type: 'fetchBranches',
+ },
+ ],
+ );
+ });
+ });
});
diff --git a/spec/frontend/projects/commit/store/getters_spec.js b/spec/frontend/projects/commit/store/getters_spec.js
index bd0cb356854..38c45af7aa0 100644
--- a/spec/frontend/projects/commit/store/getters_spec.js
+++ b/spec/frontend/projects/commit/store/getters_spec.js
@@ -18,4 +18,21 @@ describe('Commit form modal getters', () => {
expect(getters.joinedBranches(state)).toEqual(branches.slice(1));
});
});
+
+ describe('sortedProjects', () => {
+ it('should sort projects with variable branches', () => {
+ const state = {
+ projects: mockData.mockProjects,
+ };
+
+ expect(getters.sortedProjects(state)).toEqual(mockData.mockProjects.sort());
+ });
+
+ it('should provide a uniq list of projects', () => {
+ const projects = ['_project_', '_project_', '_some_other_project'];
+ const state = { projects };
+
+ expect(getters.sortedProjects(state)).toEqual(projects.slice(1));
+ });
+ });
});
diff --git a/spec/frontend/projects/commit/store/mutations_spec.js b/spec/frontend/projects/commit/store/mutations_spec.js
index 2ea50e71772..8989e769772 100644
--- a/spec/frontend/projects/commit/store/mutations_spec.js
+++ b/spec/frontend/projects/commit/store/mutations_spec.js
@@ -35,6 +35,16 @@ describe('Commit form modal mutations', () => {
});
});
+ describe('SET_BRANCHES_ENDPOINT', () => {
+ it('should set branchesEndpoint', () => {
+ stateCopy = { branchesEndpoint: 'endpoint/1' };
+
+ mutations[types.SET_BRANCHES_ENDPOINT](stateCopy, 'endpoint/2');
+
+ expect(stateCopy.branchesEndpoint).toBe('endpoint/2');
+ });
+ });
+
describe('SET_BRANCH', () => {
it('should set branch', () => {
stateCopy = { branch: '_master_' };
@@ -54,4 +64,14 @@ describe('Commit form modal mutations', () => {
expect(stateCopy.selectedBranch).toBe('_changed_branch_');
});
});
+
+ describe('SET_SELECTED_PROJECT', () => {
+ it('should set targetProjectId', () => {
+ stateCopy = { targetProjectId: '_project_1_' };
+
+ mutations[types.SET_SELECTED_PROJECT](stateCopy, '_project_2_');
+
+ expect(stateCopy.targetProjectId).toBe('_project_2_');
+ });
+ });
});
diff --git a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
index f34a2db0851..99bf0d84d0c 100644
--- a/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
+++ b/spec/frontend/vue_shared/components/issue/related_issuable_item_spec.js
@@ -88,7 +88,7 @@ describe('RelatedIssuableItem', () => {
const stateTitle = tokenState().attributes('title');
const formattedCreateDate = formatDate(props.createdAt);
- expect(stateTitle).toContain('<span class="bold">Opened</span>');
+ expect(stateTitle).toContain('<span class="bold">Created</span>');
expect(stateTitle).toContain(`<span class="text-tertiary">${formattedCreateDate}</span>`);
});
diff --git a/spec/helpers/commits_helper_spec.rb b/spec/helpers/commits_helper_spec.rb
index 4a841fac064..397751b07af 100644
--- a/spec/helpers/commits_helper_spec.rb
+++ b/spec/helpers/commits_helper_spec.rb
@@ -3,6 +3,8 @@
require 'spec_helper'
RSpec.describe CommitsHelper do
+ include ProjectForksHelper
+
describe '#revert_commit_link' do
context 'when current_user exists' do
before do
@@ -239,4 +241,21 @@ RSpec.describe CommitsHelper do
end
end
end
+
+ describe '#cherry_pick_projects_data' do
+ let(:project) { create(:project, :repository) }
+ let(:user) { create(:user, maintainer_projects: [project]) }
+ let!(:forked_project) { fork_project(project, user, { namespace: user.namespace, repository: true }) }
+
+ before do
+ allow(helper).to receive(:current_user).and_return(user)
+ end
+
+ it 'returns data for cherry picking into a project' do
+ expect(helper.cherry_pick_projects_data(project)).to match_array([
+ { id: project.id.to_s, name: project.full_path, refsUrl: refs_project_path(project) },
+ { id: forked_project.id.to_s, name: forked_project.full_path, refsUrl: refs_project_path(forked_project) }
+ ])
+ end
+ end
end
diff --git a/spec/lib/gitlab/object_hierarchy_spec.rb b/spec/lib/gitlab/object_hierarchy_spec.rb
index ef2d4fa0cbf..08e1a5ee0a3 100644
--- a/spec/lib/gitlab/object_hierarchy_spec.rb
+++ b/spec/lib/gitlab/object_hierarchy_spec.rb
@@ -7,178 +7,206 @@ RSpec.describe Gitlab::ObjectHierarchy do
let!(:child1) { create(:group, parent: parent) }
let!(:child2) { create(:group, parent: child1) }
- describe '#base_and_ancestors' do
- let(:relation) do
- described_class.new(Group.where(id: child2.id)).base_and_ancestors
- end
-
- it 'includes the base rows' do
- expect(relation).to include(child2)
- end
+ shared_context 'Gitlab::ObjectHierarchy test cases' do
+ describe '#base_and_ancestors' do
+ let(:relation) do
+ described_class.new(Group.where(id: child2.id)).base_and_ancestors
+ end
- it 'includes all of the ancestors' do
- expect(relation).to include(parent, child1)
- end
+ it 'includes the base rows' do
+ expect(relation).to include(child2)
+ end
- it 'can find ancestors upto a certain level' do
- relation = described_class.new(Group.where(id: child2)).base_and_ancestors(upto: child1)
+ it 'includes all of the ancestors' do
+ expect(relation).to include(parent, child1)
+ end
- expect(relation).to contain_exactly(child2)
- end
+ it 'can find ancestors upto a certain level' do
+ relation = described_class.new(Group.where(id: child2)).base_and_ancestors(upto: child1)
- it 'uses ancestors_base #initialize argument' do
- relation = described_class.new(Group.where(id: child2.id), Group.none).base_and_ancestors
+ expect(relation).to contain_exactly(child2)
+ end
- expect(relation).to include(parent, child1, child2)
- end
+ it 'uses ancestors_base #initialize argument' do
+ relation = described_class.new(Group.where(id: child2.id), Group.none).base_and_ancestors
- it 'does not allow the use of #update_all' do
- expect { relation.update_all(share_with_group_lock: false) }
- .to raise_error(ActiveRecord::ReadOnlyRecord)
- end
+ expect(relation).to include(parent, child1, child2)
+ end
- describe 'hierarchy_order option' do
- let(:relation) do
- described_class.new(Group.where(id: child2.id)).base_and_ancestors(hierarchy_order: hierarchy_order)
+ it 'does not allow the use of #update_all' do
+ expect { relation.update_all(share_with_group_lock: false) }
+ .to raise_error(ActiveRecord::ReadOnlyRecord)
end
- context ':asc' do
- let(:hierarchy_order) { :asc }
+ describe 'hierarchy_order option' do
+ let(:relation) do
+ described_class.new(Group.where(id: child2.id)).base_and_ancestors(hierarchy_order: hierarchy_order)
+ end
+
+ context ':asc' do
+ let(:hierarchy_order) { :asc }
- it 'orders by child to parent' do
- expect(relation).to eq([child2, child1, parent])
+ it 'orders by child to parent' do
+ expect(relation).to eq([child2, child1, parent])
+ end
end
- end
- context ':desc' do
- let(:hierarchy_order) { :desc }
+ context ':desc' do
+ let(:hierarchy_order) { :desc }
- it 'orders by parent to child' do
- expect(relation).to eq([parent, child1, child2])
+ it 'orders by parent to child' do
+ expect(relation).to eq([parent, child1, child2])
+ end
end
end
end
- end
-
- describe '#base_and_descendants' do
- let(:relation) do
- described_class.new(Group.where(id: parent.id)).base_and_descendants
- end
- it 'includes the base rows' do
- expect(relation).to include(parent)
- end
+ describe '#base_and_descendants' do
+ let(:relation) do
+ described_class.new(Group.where(id: parent.id)).base_and_descendants
+ end
- it 'includes all the descendants' do
- expect(relation).to include(child1, child2)
- end
+ it 'includes the base rows' do
+ expect(relation).to include(parent)
+ end
- it 'uses descendants_base #initialize argument' do
- relation = described_class.new(Group.none, Group.where(id: parent.id)).base_and_descendants
+ it 'includes all the descendants' do
+ expect(relation).to include(child1, child2)
+ end
- expect(relation).to include(parent, child1, child2)
- end
+ it 'uses descendants_base #initialize argument' do
+ relation = described_class.new(Group.none, Group.where(id: parent.id)).base_and_descendants
- it 'does not allow the use of #update_all' do
- expect { relation.update_all(share_with_group_lock: false) }
- .to raise_error(ActiveRecord::ReadOnlyRecord)
- end
+ expect(relation).to include(parent, child1, child2)
+ end
- context 'when with_depth is true' do
- let(:relation) do
- described_class.new(Group.where(id: parent.id)).base_and_descendants(with_depth: true)
+ it 'does not allow the use of #update_all' do
+ expect { relation.update_all(share_with_group_lock: false) }
+ .to raise_error(ActiveRecord::ReadOnlyRecord)
end
- it 'includes depth in the results' do
- object_depths = {
- parent.id => 1,
- child1.id => 2,
- child2.id => 3
- }
+ context 'when with_depth is true' do
+ let(:relation) do
+ described_class.new(Group.where(id: parent.id)).base_and_descendants(with_depth: true)
+ end
+
+ it 'includes depth in the results' do
+ object_depths = {
+ parent.id => 1,
+ child1.id => 2,
+ child2.id => 3
+ }
- relation.each do |object|
- expect(object.depth).to eq(object_depths[object.id])
+ relation.each do |object|
+ expect(object.depth).to eq(object_depths[object.id])
+ end
end
end
end
- end
- describe '#descendants' do
- it 'includes only the descendants' do
- relation = described_class.new(Group.where(id: parent)).descendants
+ describe '#descendants' do
+ it 'includes only the descendants' do
+ relation = described_class.new(Group.where(id: parent)).descendants
- expect(relation).to contain_exactly(child1, child2)
+ expect(relation).to contain_exactly(child1, child2)
+ end
end
- end
- describe '#max_descendants_depth' do
- subject { described_class.new(base_relation).max_descendants_depth }
+ describe '#max_descendants_depth' do
+ subject { described_class.new(base_relation).max_descendants_depth }
- context 'when base relation is empty' do
- let(:base_relation) { Group.where(id: nil) }
+ context 'when base relation is empty' do
+ let(:base_relation) { Group.where(id: nil) }
- it { expect(subject).to be_nil }
- end
+ it { expect(subject).to be_nil }
+ end
- context 'when base has no children' do
- let(:base_relation) { Group.where(id: child2) }
+ context 'when base has no children' do
+ let(:base_relation) { Group.where(id: child2) }
- it { expect(subject).to eq(1) }
- end
+ it { expect(subject).to eq(1) }
+ end
- context 'when base has grandchildren' do
- let(:base_relation) { Group.where(id: parent) }
+ context 'when base has grandchildren' do
+ let(:base_relation) { Group.where(id: parent) }
- it { expect(subject).to eq(3) }
+ it { expect(subject).to eq(3) }
+ end
end
- end
- describe '#ancestors' do
- it 'includes only the ancestors' do
- relation = described_class.new(Group.where(id: child2)).ancestors
+ describe '#ancestors' do
+ it 'includes only the ancestors' do
+ relation = described_class.new(Group.where(id: child2)).ancestors
- expect(relation).to contain_exactly(child1, parent)
- end
+ expect(relation).to contain_exactly(child1, parent)
+ end
- it 'can find ancestors upto a certain level' do
- relation = described_class.new(Group.where(id: child2)).ancestors(upto: child1)
+ it 'can find ancestors upto a certain level' do
+ relation = described_class.new(Group.where(id: child2)).ancestors(upto: child1)
- expect(relation).to be_empty
+ expect(relation).to be_empty
+ end
end
- end
- describe '#all_objects' do
- let(:relation) do
- described_class.new(Group.where(id: child1.id)).all_objects
- end
+ describe '#all_objects' do
+ let(:relation) do
+ described_class.new(Group.where(id: child1.id)).all_objects
+ end
- it 'includes the base rows' do
- expect(relation).to include(child1)
- end
+ it 'includes the base rows' do
+ expect(relation).to include(child1)
+ end
+
+ it 'includes the ancestors' do
+ expect(relation).to include(parent)
+ end
+
+ it 'includes the descendants' do
+ expect(relation).to include(child2)
+ end
+
+ it 'uses ancestors_base #initialize argument for ancestors' do
+ relation = described_class.new(Group.where(id: child1.id), Group.where(id: non_existing_record_id)).all_objects
+
+ expect(relation).to include(parent)
+ end
- it 'includes the ancestors' do
- expect(relation).to include(parent)
+ it 'uses descendants_base #initialize argument for descendants' do
+ relation = described_class.new(Group.where(id: non_existing_record_id), Group.where(id: child1.id)).all_objects
+
+ expect(relation).to include(child2)
+ end
+
+ it 'does not allow the use of #update_all' do
+ expect { relation.update_all(share_with_group_lock: false) }
+ .to raise_error(ActiveRecord::ReadOnlyRecord)
+ end
end
+ end
- it 'includes the descendants' do
- expect(relation).to include(child2)
+ context 'when the use_distinct_in_object_hierarchy feature flag is enabled' do
+ before do
+ stub_feature_flags(use_distinct_in_object_hierarchy: true)
end
- it 'uses ancestors_base #initialize argument for ancestors' do
- relation = described_class.new(Group.where(id: child1.id), Group.where(id: non_existing_record_id)).all_objects
+ it_behaves_like 'Gitlab::ObjectHierarchy test cases'
- expect(relation).to include(parent)
+ it 'calls DISTINCT' do
+ expect(parent.self_and_descendants.to_sql).to include("DISTINCT")
+ expect(child2.self_and_ancestors.to_sql).to include("DISTINCT")
end
+ end
- it 'uses descendants_base #initialize argument for descendants' do
- relation = described_class.new(Group.where(id: non_existing_record_id), Group.where(id: child1.id)).all_objects
-
- expect(relation).to include(child2)
+ context 'when the use_distinct_in_object_hierarchy feature flag is disabled' do
+ before do
+ stub_feature_flags(use_distinct_in_object_hierarchy: false)
end
- it 'does not allow the use of #update_all' do
- expect { relation.update_all(share_with_group_lock: false) }
- .to raise_error(ActiveRecord::ReadOnlyRecord)
+ it_behaves_like 'Gitlab::ObjectHierarchy test cases'
+
+ it 'does not call DISTINCT' do
+ expect(parent.self_and_descendants.to_sql).not_to include("DISTINCT")
+ expect(child2.self_and_ancestors.to_sql).not_to include("DISTINCT")
end
end
end