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 @@ 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); + }); +});