From 3f2d1aa8cea08d5befea4f435af9e9105ba99137 Mon Sep 17 00:00:00 2001 From: Artur Fedorov Date: Mon, 23 Jan 2023 01:43:15 +0100 Subject: [PATCH 1/2] This MR migrates Dropdown to RefSelector Ref Dropdown can use ref selector instead of legacy dropdown and reduce code duplication Changelog: changed --- .../components/pipeline_new_form.vue | 6 +- .../pipeline_new/components/refs_dropdown.vue | 95 +++++--------- .../ci/pipeline_new/utils/format_refs.js | 4 + .../projects/pipelines/pipelines_spec.rb | 1 + .../components/pipeline_new_form_spec.js | 89 ++++++------- .../components/refs_dropdown_spec.js | 122 ++++++------------ spec/frontend/ci/pipeline_new/mock_data.js | 18 --- 7 files changed, 118 insertions(+), 217 deletions(-) diff --git a/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue b/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue index 5337d0da80c88c..4c4c0ce24f21ea 100644 --- a/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue +++ b/app/assets/javascripts/ci/pipeline_new/components/pipeline_new_form.vue @@ -424,7 +424,11 @@ export default { - + diff --git a/app/assets/javascripts/ci/pipeline_new/components/refs_dropdown.vue b/app/assets/javascripts/ci/pipeline_new/components/refs_dropdown.vue index 060527f2662753..9b57f135a404fd 100644 --- a/app/assets/javascripts/ci/pipeline_new/components/refs_dropdown.vue +++ b/app/assets/javascripts/ci/pipeline_new/components/refs_dropdown.vue @@ -1,86 +1,59 @@ diff --git a/app/assets/javascripts/ci/pipeline_new/utils/format_refs.js b/app/assets/javascripts/ci/pipeline_new/utils/format_refs.js index e6d26b32d4721c..228702fbc71987 100644 --- a/app/assets/javascripts/ci/pipeline_new/utils/format_refs.js +++ b/app/assets/javascripts/ci/pipeline_new/utils/format_refs.js @@ -5,6 +5,10 @@ function convertToListBoxItems(items) { return items.map(({ shortName, fullName }) => ({ text: shortName, value: fullName })); } +export function formatToShortName(ref) { + return ref.replace(/^refs\/(tags|heads)\//, ''); +} + export function formatRefs(refs, type) { let fullName; diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index 637f1843e860a4..af5e81fe88cafe 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -799,6 +799,7 @@ def create_build(stage, stage_idx, name, status) it 'shows filtered pipelines', :js do click_button project.default_branch send_keys('fix') + wait_for_requests expect_listbox_item('fix') end diff --git a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js index 9015031b6c8227..0f381def1519e1 100644 --- a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js +++ b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js @@ -4,7 +4,7 @@ import { GlForm, GlDropdownItem, GlSprintf, GlLoadingIcon } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; import CreditCardValidationRequiredAlert from 'ee_component/billings/components/cc_validation_required_alert.vue'; import createMockApollo from 'helpers/mock_apollo_helper'; -import { shallowMountExtended, mountExtended } from 'helpers/vue_test_utils_helper'; +import { shallowMountExtended } from 'helpers/vue_test_utils_helper'; import { TEST_HOST } from 'helpers/test_constants'; import waitForPromises from 'helpers/wait_for_promises'; import axios from '~/lib/utils/axios_utils'; @@ -30,7 +30,6 @@ import { mockQueryParams, mockPostParams, mockProjectId, - mockRefs, mockYamlVariables, mockPipelineConfigButtonText, } from '../mock_data'; @@ -41,7 +40,6 @@ jest.mock('~/lib/utils/url_utility', () => ({ redirectTo: jest.fn(), })); -const projectRefsEndpoint = '/root/project/refs'; const pipelinesPath = '/root/project/-/pipelines'; const pipelinesEditorPath = '/root/project/-/ci/editor'; const projectPath = '/root/project/-/pipelines/config_variables'; @@ -91,21 +89,18 @@ describe('Pipeline New Form', () => { const changeKeyInputValue = async (keyInputIndex, value) => { const input = findKeyInputs().at(keyInputIndex); - input.element.value = value; - input.trigger('change'); + input.vm.$emit('input', value); + input.vm.$emit('change'); await nextTick(); }; - const createComponentWithApollo = ({ method = shallowMountExtended, props = {} } = {}) => { + const createComponentWithApollo = ({ props = {} } = {}) => { const handlers = [[ciConfigVariablesQuery, mockCiConfigVariables]]; mockApollo = createMockApollo(handlers, resolvers); - wrapper = method(PipelineNewForm, { + wrapper = shallowMountExtended(PipelineNewForm, { apolloProvider: mockApollo, - provide: { - projectRefsEndpoint, - }, propsData: { projectId: mockProjectId, pipelinesPath, @@ -124,7 +119,6 @@ describe('Pipeline New Form', () => { beforeEach(() => { mock = new MockAdapter(axios); mockCiConfigVariables = jest.fn(); - mock.onGet(projectRefsEndpoint).reply(HTTP_STATUS_OK, mockRefs); dummySubmitEvent = { preventDefault: jest.fn(), @@ -138,7 +132,7 @@ describe('Pipeline New Form', () => { describe('Form', () => { beforeEach(async () => { mockCiConfigVariables.mockResolvedValue(mockEmptyCiConfigVariablesResponse); - createComponentWithApollo({ props: mockQueryParams, method: mountExtended }); + createComponentWithApollo({ props: mockQueryParams }); await waitForPromises(); }); @@ -150,13 +144,13 @@ describe('Pipeline New Form', () => { }); it('displays a variable from provided query params', () => { - expect(findKeyInputs().at(0).element.value).toBe('test_var'); - expect(findValueInputs().at(0).element.value).toBe('test_var_val'); + expect(findKeyInputs().at(0).attributes('value')).toBe('test_var'); + expect(findValueInputs().at(0).attributes('value')).toBe('test_var_val'); }); it('displays an empty variable for the user to fill out', () => { - expect(findKeyInputs().at(2).element.value).toBe(''); - expect(findValueInputs().at(2).element.value).toBe(''); + expect(findKeyInputs().at(2).attributes('value')).toBe(''); + expect(findValueInputs().at(2).attributes('value')).toBe(''); expect(findVariableTypes().at(2).props('text')).toBe('Variable'); }); @@ -165,7 +159,7 @@ describe('Pipeline New Form', () => { }); it('removes ci variable row on remove icon button click', async () => { - findRemoveIcons().at(1).trigger('click'); + findRemoveIcons().at(1).vm.$emit('click'); await nextTick(); @@ -174,14 +168,15 @@ describe('Pipeline New Form', () => { it('creates blank variable on input change event', async () => { const input = findKeyInputs().at(2); - input.element.value = 'test_var_2'; - input.trigger('change'); + + input.vm.$emit('input', 'test_var_2'); + input.vm.$emit('change'); await nextTick(); expect(findVariableRows()).toHaveLength(4); - expect(findKeyInputs().at(3).element.value).toBe(''); - expect(findValueInputs().at(3).element.value).toBe(''); + expect(findKeyInputs().at(3).attributes('value')).toBe(''); + expect(findValueInputs().at(3).attributes('value')).toBe(''); }); }); @@ -237,7 +232,7 @@ describe('Pipeline New Form', () => { describe('When the ref has been changed', () => { beforeEach(async () => { mockCiConfigVariables.mockResolvedValue(mockEmptyCiConfigVariablesResponse); - createComponentWithApollo({ method: mountExtended }); + createComponentWithApollo(); await waitForPromises(); }); @@ -251,12 +246,12 @@ describe('Pipeline New Form', () => { await selectBranch('main'); - expect(findKeyInputs().at(0).element.value).toBe('build_var'); + expect(findKeyInputs().at(0).attributes('value')).toBe('build_var'); expect(findVariableRows().length).toBe(2); await selectBranch('branch-1'); - expect(findKeyInputs().at(0).element.value).toBe('deploy_var'); + expect(findKeyInputs().at(0).attributes('value')).toBe('deploy_var'); expect(findVariableRows().length).toBe(2); }); @@ -280,7 +275,7 @@ describe('Pipeline New Form', () => { describe('When there are no variables in the API cache', () => { beforeEach(async () => { mockCiConfigVariables.mockResolvedValue(mockNoCachedCiConfigVariablesResponse); - createComponentWithApollo({ method: mountExtended }); + createComponentWithApollo(); await waitForPromises(); }); @@ -330,7 +325,7 @@ describe('Pipeline New Form', () => { const testBehaviorWhenCacheIsPopulated = (queryResponse) => { beforeEach(() => { mockCiConfigVariables.mockResolvedValue(queryResponse); - createComponentWithApollo({ method: mountExtended }); + createComponentWithApollo(); }); it('does not poll for new values', async () => { @@ -345,6 +340,9 @@ describe('Pipeline New Form', () => { }); it('loading icon is shown when content is requested and hidden when received', async () => { + mockCiConfigVariables.mockResolvedValue(mockEmptyCiConfigVariablesResponse); + createComponentWithApollo({ props: mockQueryParams }); + expect(findLoadingIcon().exists()).toBe(true); await waitForPromises(); @@ -358,11 +356,11 @@ describe('Pipeline New Form', () => { it('displays an empty form', async () => { mockCiConfigVariables.mockResolvedValue(mockEmptyCiConfigVariablesResponse); - createComponentWithApollo({ method: mountExtended }); + createComponentWithApollo(); await waitForPromises(); - expect(findKeyInputs().at(0).element.value).toBe(''); - expect(findValueInputs().at(0).element.value).toBe(''); + expect(findKeyInputs().at(0).attributes('value')).toBe(''); + expect(findValueInputs().at(0).attributes('value')).toBe(''); expect(findVariableTypes().at(0).props('text')).toBe('Variable'); }); }); @@ -373,12 +371,12 @@ describe('Pipeline New Form', () => { describe('with different predefined values', () => { beforeEach(async () => { mockCiConfigVariables.mockResolvedValue(mockCiConfigVariablesResponse); - createComponentWithApollo({ method: mountExtended }); + createComponentWithApollo(); await waitForPromises(); }); it('multi-line strings are added to the value field without removing line breaks', () => { - expect(findValueInputs().at(1).element.value).toBe(mockYamlVariables[1].value); + expect(findValueInputs().at(1).attributes('value')).toBe(mockYamlVariables[1].value); }); it('multiple predefined values are rendered as a dropdown', () => { @@ -402,7 +400,7 @@ describe('Pipeline New Form', () => { describe('with description', () => { beforeEach(async () => { mockCiConfigVariables.mockResolvedValue(mockCiConfigVariablesResponse); - createComponentWithApollo({ props: mockQueryParams, method: mountExtended }); + createComponentWithApollo({ props: mockQueryParams }); await waitForPromises(); }); @@ -411,15 +409,15 @@ describe('Pipeline New Form', () => { }); it('displays a variable from yml', () => { - expect(findKeyInputs().at(0).element.value).toBe(mockYamlVariables[0].key); - expect(findValueInputs().at(0).element.value).toBe(mockYamlVariables[0].value); + expect(findKeyInputs().at(0).attributes('value')).toBe(mockYamlVariables[0].key); + expect(findValueInputs().at(0).attributes('value')).toBe(mockYamlVariables[0].value); }); it('displays a variable from provided query params', () => { - expect(findKeyInputs().at(3).element.value).toBe( + expect(findKeyInputs().at(3).attributes('value')).toBe( Object.keys(mockQueryParams.variableParams)[0], ); - expect(findValueInputs().at(3).element.value).toBe( + expect(findValueInputs().at(3).attributes('value')).toBe( Object.values(mockQueryParams.fileParams)[0], ); }); @@ -429,7 +427,7 @@ describe('Pipeline New Form', () => { }); it('removes the description when a variable key changes', async () => { - findKeyInputs().at(0).element.value = 'yml_var_modified'; + findKeyInputs().at(0).vm.$emit('input', 'yml_var_modified'); findKeyInputs().at(0).trigger('change'); await nextTick(); @@ -441,7 +439,7 @@ describe('Pipeline New Form', () => { describe('without description', () => { beforeEach(async () => { mockCiConfigVariables.mockResolvedValue(mockCiConfigVariablesResponseWithoutDesc); - createComponentWithApollo({ method: mountExtended }); + createComponentWithApollo(); await waitForPromises(); }); @@ -457,21 +455,6 @@ describe('Pipeline New Form', () => { createComponentWithApollo(); }); - describe('when the refs cannot be loaded', () => { - beforeEach(() => { - mock - .onGet(projectRefsEndpoint, { params: { search: '' } }) - .reply(HTTP_STATUS_INTERNAL_SERVER_ERROR); - - findRefsDropdown().vm.$emit('loadingError'); - }); - - it('shows both an error alert', () => { - expect(findErrorAlert().exists()).toBe(true); - expect(findWarningAlert().exists()).toBe(false); - }); - }); - describe('when the error response can be handled', () => { beforeEach(async () => { mock.onPost(pipelinesPath).reply(HTTP_STATUS_BAD_REQUEST, mockError); diff --git a/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js b/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js index 82dac1358c5064..82e170b3a86d75 100644 --- a/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js +++ b/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js @@ -1,15 +1,16 @@ import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui'; import MockAdapter from 'axios-mock-adapter'; -import { mountExtended, shallowMountExtended } from 'helpers/vue_test_utils_helper'; +import commit from 'test_fixtures/api/commits/commit.json'; +import branches from 'test_fixtures/api/branches/branches.json'; +import tags from 'test_fixtures/api/tags/tags.json'; +import { mountExtended } from 'helpers/vue_test_utils_helper'; import waitForPromises from 'helpers/wait_for_promises'; +import { X_TOTAL_HEADER } from '~/ref/constants'; import axios from '~/lib/utils/axios_utils'; -import { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status'; import RefsDropdown from '~/ci/pipeline_new/components/refs_dropdown.vue'; -import { mockBranches, mockRefs, mockFilteredRefs, mockTags } from '../mock_data'; - -const projectRefsEndpoint = '/root/project/refs'; +const projectId = '8'; const refShortName = 'main'; const refFullName = 'refs/heads/main'; @@ -18,18 +19,15 @@ jest.mock('~/alert'); describe('Pipeline New Form', () => { let wrapper; let mock; + const fixtures = { branches, tags, commit }; const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox); const findRefsDropdownItems = () => wrapper.findAllComponents(GlListboxItem); - const findSearchBox = () => wrapper.findByTestId('listbox-search-input'); - const findListboxGroups = () => wrapper.findAll('ul[role="group"]'); - const createComponent = (props = {}, mountFn = shallowMountExtended) => { + const createComponent = (props = {}, mountFn = mountExtended) => { wrapper = mountFn(RefsDropdown, { - provide: { - projectRefsEndpoint, - }, propsData: { + projectId, value: { shortName: refShortName, fullName: refFullName, @@ -40,8 +38,15 @@ describe('Pipeline New Form', () => { }; beforeEach(() => { + gon.api_version = 'v4'; mock = new MockAdapter(axios); - mock.onGet(projectRefsEndpoint, { params: { search: '' } }).reply(HTTP_STATUS_OK, mockRefs); + + mock + .onGet(`/api/v4/projects/${projectId}/repository/branches`) + .reply(200, fixtures.branches, { [X_TOTAL_HEADER]: '123' }); + mock + .onGet(`/api/v4/projects/${projectId}/repository/tags`) + .reply(200, fixtures.tags, { [X_TOTAL_HEADER]: '123' }); }); beforeEach(() => { @@ -60,47 +65,44 @@ describe('Pipeline New Form', () => { describe('when user opens dropdown', () => { beforeEach(async () => { - createComponent({}, mountExtended); + createComponent(); findDropdown().vm.$emit('shown'); await waitForPromises(); }); - 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', () => { - const refLength = mockRefs.Tags.length + mockRefs.Branches.length; + const refLength = fixtures.branches.length + fixtures.tags.length; expect(findRefsDropdownItems()).toHaveLength(refLength); }); it('displays the names of refs', () => { // Branches - expect(findRefsDropdownItems().at(0).text()).toBe(mockRefs.Branches[0]); + expect(findRefsDropdownItems().at(0).text()).toBe(fixtures.branches[0].name); // Tags (appear after branches) - const firstTag = mockRefs.Branches.length; - expect(findRefsDropdownItems().at(firstTag).text()).toBe(mockRefs.Tags[0]); - }); - - it('when user shows dropdown a second time, only one request is done', () => { - expect(mock.history.get).toHaveLength(1); + const firstTag = fixtures.branches.length; + expect(findRefsDropdownItems().at(firstTag).text()).toBe(fixtures.tags[0].name); }); describe('when user selects a value', () => { const selectedIndex = 1; beforeEach(async () => { - findRefsDropdownItems().at(selectedIndex).vm.$emit('select', 'refs/heads/branch-1'); + createComponent(); await waitForPromises(); + findRefsDropdownItems().at(selectedIndex).trigger('click'); }); it('component emits @input', () => { const inputs = wrapper.emitted('input'); expect(inputs).toHaveLength(1); + expect(inputs[0]).toEqual([ + { + shortName: 'conflict-contains-conflict-markers', + fullName: 'refs/heads/conflict-contains-conflict-markers', + }, + ]); expect(inputs[0]).toEqual([{ shortName: 'branch-1', fullName: 'refs/heads/branch-1' }]); }); }); @@ -134,68 +136,20 @@ describe('Pipeline New Form', () => { describe('when user has selected a value', () => { const selectedIndex = 1; - const mockShortName = mockRefs.Branches[selectedIndex]; + const mockShortName = fixtures.branches[selectedIndex].name; const mockFullName = `refs/heads/${mockShortName}`; - beforeEach(async () => { - mock - .onGet(projectRefsEndpoint, { - params: { ref: mockFullName }, - }) - .reply(HTTP_STATUS_OK, mockRefs); - - createComponent( - { - value: { - shortName: mockShortName, - fullName: mockFullName, - }, + it('branch is checked', async () => { + createComponent({ + value: { + shortName: mockShortName, + fullName: mockFullName, }, - mountExtended, - ); - findDropdown().vm.$emit('shown'); + }); await waitForPromises(); - }); - - it('branch is checked', () => { - expect(findRefsDropdownItems().at(selectedIndex).props('isSelected')).toBe(true); - }); - }); - - describe('when server returns an error', () => { - beforeEach(async () => { - mock - .onGet(projectRefsEndpoint, { params: { search: '' } }) - .reply(HTTP_STATUS_INTERNAL_SERVER_ERROR); findDropdown().vm.$emit('shown'); - await waitForPromises(); + expect(findDropdown().props('selected')).toBe(mockShortName); }); - - it('loading error event is emitted', () => { - expect(wrapper.emitted('loadingError')).toHaveLength(1); - 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(HTTP_STATUS_OK, mockData); - createComponent({}, mountExtended); - findDropdown().vm.$emit('shown'); - await waitForPromises(); - - expect(findListboxGroups()).toHaveLength(expectedGroupLength); - expect(findRefsDropdownItems()).toHaveLength(expectedListboxItemsLength); - }, - ); }); }); diff --git a/spec/frontend/ci/pipeline_new/mock_data.js b/spec/frontend/ci/pipeline_new/mock_data.js index 175f513217bf89..39dd6ffdde2d30 100644 --- a/spec/frontend/ci/pipeline_new/mock_data.js +++ b/spec/frontend/ci/pipeline_new/mock_data.js @@ -1,21 +1,3 @@ -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'], -}; - export const mockQueryParams = { refParam: 'tag-1', variableParams: { -- GitLab From 387e3ef4dc16d8766ca98851652958dc52f2dfea Mon Sep 17 00:00:00 2001 From: Artur Fedorov Date: Mon, 20 Mar 2023 01:20:42 +0100 Subject: [PATCH 2/2] Applied review changes --- .../projects/pipelines/pipelines_spec.rb | 7 +- .../components/pipeline_new_form_spec.js | 15 +++ .../components/refs_dropdown_spec.js | 116 +++--------------- spec/frontend/ci/pipeline_new/mock_data.js | 5 + 4 files changed, 43 insertions(+), 100 deletions(-) diff --git a/spec/features/projects/pipelines/pipelines_spec.rb b/spec/features/projects/pipelines/pipelines_spec.rb index af5e81fe88cafe..c0e41ba88aaf85 100644 --- a/spec/features/projects/pipelines/pipelines_spec.rb +++ b/spec/features/projects/pipelines/pipelines_spec.rb @@ -686,7 +686,7 @@ def create_build(stage, stage_idx, name, status) click_button project.default_branch wait_for_requests - find('.gl-new-dropdown-item', text: 'master').click + find('.gl-new-dropdown-item', text: '2-mb-file').click wait_for_requests end @@ -798,10 +798,9 @@ def create_build(stage, stage_idx, name, status) describe 'find pipelines' do it 'shows filtered pipelines', :js do click_button project.default_branch - send_keys('fix') - wait_for_requests + send_keys('2-mb-file') - expect_listbox_item('fix') + expect_listbox_item('2-mb-file') end end end diff --git a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js index 0f381def1519e1..a08a01009e2037 100644 --- a/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js +++ b/spec/frontend/ci/pipeline_new/components/pipeline_new_form_spec.js @@ -455,6 +455,21 @@ describe('Pipeline New Form', () => { createComponentWithApollo(); }); + describe('when the refs cannot be loaded', () => { + beforeEach(() => { + mock + .onGet('/api/v4/projects/8/repository/branches', { params: { search: '' } }) + .reply(HTTP_STATUS_INTERNAL_SERVER_ERROR); + + findRefsDropdown().vm.$emit('loadingError'); + }); + + it('shows both an error alert', () => { + expect(findErrorAlert().exists()).toBe(true); + expect(findWarningAlert().exists()).toBe(false); + }); + }); + describe('when the error response can be handled', () => { beforeEach(async () => { mock.onPost(pipelinesPath).reply(HTTP_STATUS_BAD_REQUEST, mockError); diff --git a/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js b/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js index 82e170b3a86d75..01c7dd7eb84e1f 100644 --- a/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js +++ b/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js @@ -1,31 +1,20 @@ -import { GlCollapsibleListbox, GlListboxItem } from '@gitlab/ui'; -import MockAdapter from 'axios-mock-adapter'; -import commit from 'test_fixtures/api/commits/commit.json'; -import branches from 'test_fixtures/api/branches/branches.json'; -import tags from 'test_fixtures/api/tags/tags.json'; -import { mountExtended } from 'helpers/vue_test_utils_helper'; -import waitForPromises from 'helpers/wait_for_promises'; -import { X_TOTAL_HEADER } from '~/ref/constants'; -import axios from '~/lib/utils/axios_utils'; +import { shallowMount } from '@vue/test-utils'; +import RefSelector from '~/ref/components/ref_selector.vue'; import RefsDropdown from '~/ci/pipeline_new/components/refs_dropdown.vue'; +import { REF_TYPE_BRANCHES, REF_TYPE_TAGS } from '~/ref/constants'; const projectId = '8'; const refShortName = 'main'; const refFullName = 'refs/heads/main'; -jest.mock('~/alert'); - describe('Pipeline New Form', () => { let wrapper; - let mock; - const fixtures = { branches, tags, commit }; - const findDropdown = () => wrapper.findComponent(GlCollapsibleListbox); - const findRefsDropdownItems = () => wrapper.findAllComponents(GlListboxItem); + const findRefSelector = () => wrapper.findComponent(RefSelector); - const createComponent = (props = {}, mountFn = mountExtended) => { - wrapper = mountFn(RefsDropdown, { + const createComponent = (props = {}) => { + wrapper = shallowMount(RefsDropdown, { propsData: { projectId, value: { @@ -37,63 +26,28 @@ describe('Pipeline New Form', () => { }); }; - beforeEach(() => { - gon.api_version = 'v4'; - mock = new MockAdapter(axios); - - mock - .onGet(`/api/v4/projects/${projectId}/repository/branches`) - .reply(200, fixtures.branches, { [X_TOTAL_HEADER]: '123' }); - mock - .onGet(`/api/v4/projects/${projectId}/repository/tags`) - .reply(200, fixtures.tags, { [X_TOTAL_HEADER]: '123' }); - }); - - beforeEach(() => { - createComponent(); - }); - - it('displays empty dropdown initially', () => { - findDropdown().vm.$emit('shown'); - - expect(findRefsDropdownItems()).toHaveLength(0); - }); - - it('does not make requests immediately', () => { - expect(mock.history.get).toHaveLength(0); - }); - describe('when user opens dropdown', () => { - beforeEach(async () => { + beforeEach(() => { createComponent(); - findDropdown().vm.$emit('shown'); - await waitForPromises(); }); - it('displays dropdown with branches and tags', () => { - const refLength = fixtures.branches.length + fixtures.tags.length; - expect(findRefsDropdownItems()).toHaveLength(refLength); + it('has default selected branch', () => { + expect(findRefSelector().props('value')).toBe('main'); }); - it('displays the names of refs', () => { - // Branches - expect(findRefsDropdownItems().at(0).text()).toBe(fixtures.branches[0].name); - - // Tags (appear after branches) - const firstTag = fixtures.branches.length; - expect(findRefsDropdownItems().at(firstTag).text()).toBe(fixtures.tags[0].name); + it('has ref selector for branches and tags', () => { + expect(findRefSelector().props('enabledRefTypes')).toEqual([ + REF_TYPE_BRANCHES, + REF_TYPE_TAGS, + ]); }); describe('when user selects a value', () => { - const selectedIndex = 1; - - beforeEach(async () => { - createComponent(); - await waitForPromises(); - findRefsDropdownItems().at(selectedIndex).trigger('click'); - }); + const fullName = `refs/heads/conflict-contains-conflict-markers`; it('component emits @input', () => { + findRefSelector().vm.$emit('input', fullName); + const inputs = wrapper.emitted('input'); expect(inputs).toHaveLength(1); @@ -103,53 +57,23 @@ describe('Pipeline New Form', () => { fullName: 'refs/heads/conflict-contains-conflict-markers', }, ]); - expect(inputs[0]).toEqual([{ shortName: 'branch-1', fullName: 'refs/heads/branch-1' }]); - }); - }); - - describe('when user types searches for a tag', () => { - const mockSearchTerm = 'my-search'; - - beforeEach(async () => { - mock - .onGet(projectRefsEndpoint, { params: { search: mockSearchTerm } }) - .reply(HTTP_STATUS_OK, mockFilteredRefs); - - await findSearchBox().vm.$emit('input', mockSearchTerm); - await waitForPromises(); - }); - - it('requests filtered tags and branches', () => { - expect(mock.history.get).toHaveLength(2); - expect(mock.history.get[1].params).toEqual({ - search: mockSearchTerm, - }); - }); - - it('displays dropdown with branches and tags', () => { - const filteredRefLength = mockFilteredRefs.Tags.length + mockFilteredRefs.Branches.length; - - expect(findRefsDropdownItems()).toHaveLength(filteredRefLength); }); }); }); describe('when user has selected a value', () => { - const selectedIndex = 1; - const mockShortName = fixtures.branches[selectedIndex].name; + const mockShortName = 'conflict-contains-conflict-markers'; const mockFullName = `refs/heads/${mockShortName}`; - it('branch is checked', async () => { + it('branch is checked', () => { createComponent({ value: { shortName: mockShortName, fullName: mockFullName, }, }); - await waitForPromises(); - findDropdown().vm.$emit('shown'); - expect(findDropdown().props('selected')).toBe(mockShortName); + expect(findRefSelector().props('value')).toBe(mockShortName); }); }); }); diff --git a/spec/frontend/ci/pipeline_new/mock_data.js b/spec/frontend/ci/pipeline_new/mock_data.js index 39dd6ffdde2d30..76a88f632981d8 100644 --- a/spec/frontend/ci/pipeline_new/mock_data.js +++ b/spec/frontend/ci/pipeline_new/mock_data.js @@ -1,3 +1,8 @@ +export const mockFilteredRefs = { + Branches: ['branch-1'], + Tags: ['1.0.0', '1.1.0'], +}; + export const mockQueryParams = { refParam: 'tag-1', variableParams: { -- GitLab