diff --git a/app/assets/javascripts/pipeline_new/components/refs_dropdown.vue b/app/assets/javascripts/pipeline_new/components/refs_dropdown.vue
index d35d20101509441f75dd4cd41c67bb8a7514b523..d0a955a1db3f110d8e340ad0bca071b29e6caa8b 100644
--- a/app/assets/javascripts/pipeline_new/components/refs_dropdown.vue
+++ b/app/assets/javascripts/pipeline_new/components/refs_dropdown.vue
@@ -1,16 +1,13 @@
-
-
- {{ __('Branches') }}
-
- {{ branch.shortName }}
-
- {{ __('Tags') }}
-
- {{ tag.shortName }}
-
-
+
diff --git a/app/assets/javascripts/pipeline_new/utils/format_refs.js b/app/assets/javascripts/pipeline_new/utils/format_refs.js
index f0fbc5ed7b6e577853cca2935ac4543cec39a9f2..e6d26b32d4721c13397c555a6d3ef9cc6831cd45 100644
--- a/app/assets/javascripts/pipeline_new/utils/format_refs.js
+++ b/app/assets/javascripts/pipeline_new/utils/format_refs.js
@@ -1,6 +1,11 @@
+import { __ } from '~/locale';
import { BRANCH_REF_TYPE, TAG_REF_TYPE } from '../constants';
-export default (refs, type) => {
+function convertToListBoxItems(items) {
+ return items.map(({ shortName, fullName }) => ({ text: shortName, value: fullName }));
+}
+
+export function formatRefs(refs, type) {
let fullName;
return refs.map((ref) => {
@@ -15,4 +20,36 @@ export default (refs, type) => {
fullName,
};
});
+}
+
+export const formatListBoxItems = (branches, tags) => {
+ const finalResults = [];
+
+ if (branches.length > 0) {
+ finalResults.push({
+ text: __('Branches'),
+ options: convertToListBoxItems(formatRefs(branches, BRANCH_REF_TYPE)),
+ });
+ }
+
+ if (tags.length > 0) {
+ finalResults.push({
+ text: __('Tags'),
+ options: convertToListBoxItems(formatRefs(tags, TAG_REF_TYPE)),
+ });
+ }
+
+ return finalResults;
+};
+
+export const searchByFullNameInListboxOptions = (fullName, listBox) => {
+ const optionsToSearch =
+ listBox.length > 1 ? listBox[0].options.concat(listBox[1].options) : listBox[0]?.options;
+
+ const foundOption = optionsToSearch.find(({ value }) => value === fullName);
+
+ return {
+ shortName: foundOption.text,
+ fullName: foundOption.value,
+ };
};
diff --git a/spec/features/projects/pipelines/legacy_pipelines_spec.rb b/spec/features/projects/pipelines/legacy_pipelines_spec.rb
new file mode 100644
index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb
index 26ef0cd52f7374b224a2790dcc80f916f464aa36..1241fe9c87c6638adc57e7827c9dc4cd94be9004 100644
--- a/spec/features/projects/pipelines/pipelines_spec.rb
+++ b/spec/features/projects/pipelines/pipelines_spec.rb
@@ -674,7 +674,7 @@ def create_build(stage, stage_idx, name, status)
click_button project.default_branch
wait_for_requests
- find('p', text: 'master').click
+ find('.gl-dropdown-item', text: 'master').click
wait_for_requests
end
@@ -789,7 +789,7 @@ def create_build(stage, stage_idx, name, status)
click_button project.default_branch
page.within '[data-testid="ref-select"]' do
- find('[data-testid="search-refs"]').native.send_keys('fix')
+ find('[data-testid="listbox-search-input"] input').native.send_keys('fix')
page.within '.gl-dropdown-contents' do
expect(page).to have_content('fix')
diff --git a/spec/frontend/pipeline_new/components/refs_dropdown_spec.js b/spec/frontend/pipeline_new/components/refs_dropdown_spec.js
index 8cba876c688d39aad793da0ff1fa0b600908ab8f..6fae970aaf0f6086c5847ddcb07adde74ebaffcd 100644
--- a/spec/frontend/pipeline_new/components/refs_dropdown_spec.js
+++ b/spec/frontend/pipeline_new/components/refs_dropdown_spec.js
@@ -1,13 +1,13 @@
-import { GlDropdown, GlDropdownItem, GlSearchBoxByType } from '@gitlab/ui';
-import { shallowMount } from '@vue/test-utils';
+import { GlListbox, GlListboxItem } from '@gitlab/ui';
import MockAdapter from 'axios-mock-adapter';
+import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper';
import waitForPromises from 'helpers/wait_for_promises';
import axios from '~/lib/utils/axios_utils';
import httpStatusCodes from '~/lib/utils/http_status';
import RefsDropdown from '~/pipeline_new/components/refs_dropdown.vue';
-import { mockRefs, mockFilteredRefs } from '../mock_data';
+import { mockBranches, mockRefs, mockFilteredRefs, mockTags } from '../mock_data';
const projectRefsEndpoint = '/root/project/refs';
const refShortName = 'main';
@@ -19,11 +19,12 @@ describe('Pipeline New Form', () => {
let wrapper;
let mock;
- const findDropdown = () => wrapper.findComponent(GlDropdown);
- const findRefsDropdownItems = () => wrapper.findAllComponents(GlDropdownItem);
- const findSearchBox = () => wrapper.findComponent(GlSearchBoxByType);
+ const findDropdown = () => wrapper.findComponent(GlListbox);
+ const findRefsDropdownItems = () => wrapper.findAllComponents(GlListboxItem);
+ const findSearchBox = () => wrapper.findByTestId('listbox-search-input');
+ const findListboxGroups = () => wrapper.findAll('ul[role="group"]');
- const createComponent = (props = {}, mountFn = shallowMount) => {
+ const createComponent = (props = {}, mountFn = shallowMountExtended) => {
wrapper = mountFn(RefsDropdown, {
provide: {
projectRefsEndpoint,
@@ -43,19 +44,12 @@ describe('Pipeline New Form', () => {
mock.onGet(projectRefsEndpoint, { params: { search: '' } }).reply(httpStatusCodes.OK, mockRefs);
});
- afterEach(() => {
- wrapper.destroy();
- wrapper = null;
-
- mock.restore();
- });
-
beforeEach(() => {
createComponent();
});
- it('displays empty dropdown initially', async () => {
- await findDropdown().vm.$emit('show');
+ it('displays empty dropdown initially', () => {
+ findDropdown().vm.$emit('shown');
expect(findRefsDropdownItems()).toHaveLength(0);
});
@@ -66,19 +60,19 @@ describe('Pipeline New Form', () => {
describe('when user opens dropdown', () => {
beforeEach(async () => {
- await findDropdown().vm.$emit('show');
+ createComponent({}, mountExtended);
+ findDropdown().vm.$emit('shown');
await waitForPromises();
});
- it('requests unfiltered tags and branches', async () => {
+ it('requests unfiltered tags and branches', () => {
expect(mock.history.get).toHaveLength(1);
expect(mock.history.get[0].url).toBe(projectRefsEndpoint);
expect(mock.history.get[0].params).toEqual({ search: '' });
});
- it('displays dropdown with branches and tags', async () => {
+ it('displays dropdown with branches and tags', () => {
const refLength = mockRefs.Tags.length + mockRefs.Branches.length;
-
expect(findRefsDropdownItems()).toHaveLength(refLength);
});
@@ -99,7 +93,8 @@ describe('Pipeline New Form', () => {
const selectedIndex = 1;
beforeEach(async () => {
- await findRefsDropdownItems().at(selectedIndex).vm.$emit('click');
+ findRefsDropdownItems().at(selectedIndex).vm.$emit('select', 'refs/heads/branch-1');
+ await waitForPromises();
});
it('component emits @input', () => {
@@ -149,18 +144,21 @@ describe('Pipeline New Form', () => {
})
.reply(httpStatusCodes.OK, mockRefs);
- createComponent({
- value: {
- shortName: mockShortName,
- fullName: mockFullName,
+ createComponent(
+ {
+ value: {
+ shortName: mockShortName,
+ fullName: mockFullName,
+ },
},
- });
- await findDropdown().vm.$emit('show');
+ mountExtended,
+ );
+ findDropdown().vm.$emit('shown');
await waitForPromises();
});
it('branch is checked', () => {
- expect(findRefsDropdownItems().at(selectedIndex).props('isChecked')).toBe(true);
+ expect(findRefsDropdownItems().at(selectedIndex).props('isSelected')).toBe(true);
});
});
@@ -170,7 +168,7 @@ describe('Pipeline New Form', () => {
.onGet(projectRefsEndpoint, { params: { search: '' } })
.reply(httpStatusCodes.INTERNAL_SERVER_ERROR);
- await findDropdown().vm.$emit('show');
+ findDropdown().vm.$emit('shown');
await waitForPromises();
});
@@ -179,4 +177,27 @@ describe('Pipeline New Form', () => {
expect(wrapper.emitted('loadingError')[0]).toEqual([expect.any(Error)]);
});
});
+
+ describe('should display branches and tags based on its length', () => {
+ it.each`
+ mockData | expectedGroupLength | expectedListboxItemsLength
+ ${{ ...mockBranches, Tags: [] }} | ${1} | ${mockBranches.Branches.length}
+ ${{ Branches: [], ...mockTags }} | ${1} | ${mockTags.Tags.length}
+ ${{ ...mockRefs }} | ${2} | ${mockBranches.Branches.length + mockTags.Tags.length}
+ ${{ Branches: undefined, Tags: undefined }} | ${0} | ${0}
+ `(
+ 'should render branches and tags based on presence',
+ async ({ mockData, expectedGroupLength, expectedListboxItemsLength }) => {
+ mock
+ .onGet(projectRefsEndpoint, { params: { search: '' } })
+ .reply(httpStatusCodes.OK, mockData);
+ createComponent({}, mountExtended);
+ findDropdown().vm.$emit('shown');
+ await waitForPromises();
+
+ expect(findListboxGroups()).toHaveLength(expectedGroupLength);
+ expect(findRefsDropdownItems()).toHaveLength(expectedListboxItemsLength);
+ },
+ );
+ });
});
diff --git a/spec/frontend/pipeline_new/mock_data.js b/spec/frontend/pipeline_new/mock_data.js
index 2af0ef4d7c46aa2b15ceb0d157615c3f3522617f..dfb643a0ba4258afe92f300351de07ecf4b302b9 100644
--- a/spec/frontend/pipeline_new/mock_data.js
+++ b/spec/frontend/pipeline_new/mock_data.js
@@ -1,8 +1,16 @@
-export const mockRefs = {
+export const mockBranches = {
Branches: ['main', 'branch-1', 'branch-2'],
+};
+
+export const mockTags = {
Tags: ['1.0.0', '1.1.0', '1.2.0'],
};
+export const mockRefs = {
+ ...mockBranches,
+ ...mockTags,
+};
+
export const mockFilteredRefs = {
Branches: ['branch-1'],
Tags: ['1.0.0', '1.1.0'],
diff --git a/spec/frontend/pipeline_new/utils/format_refs_spec.js b/spec/frontend/pipeline_new/utils/format_refs_spec.js
index 71190f55c166e9f54ca08df8c43ea50ed61e22e2..26dfa002a91be9fb701289d8acca1c914e2ad84d 100644
--- a/spec/frontend/pipeline_new/utils/format_refs_spec.js
+++ b/spec/frontend/pipeline_new/utils/format_refs_spec.js
@@ -1,5 +1,9 @@
import { BRANCH_REF_TYPE, TAG_REF_TYPE } from '~/pipeline_new/constants';
-import formatRefs from '~/pipeline_new/utils/format_refs';
+import {
+ formatRefs,
+ formatListBoxItems,
+ searchByFullNameInListboxOptions,
+} from '~/pipeline_new/utils/format_refs';
import { mockBranchRefs, mockTagRefs } from '../mock_data';
describe('Format refs util', () => {
@@ -19,3 +23,60 @@ describe('Format refs util', () => {
]);
});
});
+
+describe('formatListBoxItems', () => {
+ it('formats branches and tags to listbox items correctly', () => {
+ expect(formatListBoxItems(mockBranchRefs, mockTagRefs)).toEqual([
+ {
+ text: 'Branches',
+ options: [
+ { value: 'refs/heads/main', text: 'main' },
+ { value: 'refs/heads/dev', text: 'dev' },
+ { value: 'refs/heads/release', text: 'release' },
+ ],
+ },
+ {
+ text: 'Tags',
+ options: [
+ { value: 'refs/tags/1.0.0', text: '1.0.0' },
+ { value: 'refs/tags/1.1.0', text: '1.1.0' },
+ { value: 'refs/tags/1.2.0', text: '1.2.0' },
+ ],
+ },
+ ]);
+
+ expect(formatListBoxItems(mockBranchRefs, [])).toEqual([
+ {
+ text: 'Branches',
+ options: [
+ { value: 'refs/heads/main', text: 'main' },
+ { value: 'refs/heads/dev', text: 'dev' },
+ { value: 'refs/heads/release', text: 'release' },
+ ],
+ },
+ ]);
+
+ expect(formatListBoxItems([], mockTagRefs)).toEqual([
+ {
+ text: 'Tags',
+ options: [
+ { value: 'refs/tags/1.0.0', text: '1.0.0' },
+ { value: 'refs/tags/1.1.0', text: '1.1.0' },
+ { value: 'refs/tags/1.2.0', text: '1.2.0' },
+ ],
+ },
+ ]);
+ });
+});
+
+describe('searchByFullNameInListboxOptions', () => {
+ const listbox = formatListBoxItems(mockBranchRefs, mockTagRefs);
+
+ it.each`
+ fullName | expectedResult
+ ${'refs/heads/main'} | ${{ fullName: 'refs/heads/main', shortName: 'main' }}
+ ${'refs/tags/1.0.0'} | ${{ fullName: 'refs/tags/1.0.0', shortName: '1.0.0' }}
+ `('should search item in listbox correctly', ({ fullName, expectedResult }) => {
+ expect(searchByFullNameInListboxOptions(fullName, listbox)).toEqual(expectedResult);
+ });
+});