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 5337d0da80c88cea9aec0fb51134633dab1e7f81..4c4c0ce24f21ea8e59d52c72fd6ea2689a967457 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 060527f266275311edc4d53f732cdf8bbea892a2..9b57f135a404fdbc3af3abcbe192e116e522eddb 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 e6d26b32d4721c13397c555a6d3ef9cc6831cd45..228702fbc71987ee66afab62830a864a0817e081 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 637f1843e860a492086a4168b1193b87b4ee101f..c0e41ba88aaf851d97052734a5f56ed6b40ed5ce 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,9 +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')
+ 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 9015031b6c8227ecfa5d65d71639ab7226cc00ee..a08a01009e20377682313632aceb083cf4cdae15 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();
});
@@ -460,7 +458,7 @@ describe('Pipeline New Form', () => {
describe('when the refs cannot be loaded', () => {
beforeEach(() => {
mock
- .onGet(projectRefsEndpoint, { params: { search: '' } })
+ .onGet('/api/v4/projects/8/repository/branches', { params: { search: '' } })
.reply(HTTP_STATUS_INTERNAL_SERVER_ERROR);
findRefsDropdown().vm.$emit('loadingError');
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 82dac1358c5064f9a7e65a0b2a84ea570d2f2826..01c7dd7eb84e1f4dae06f24d91a6f01fda2fa549 100644
--- a/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js
+++ b/spec/frontend/ci/pipeline_new/components/refs_dropdown_spec.js
@@ -1,35 +1,22 @@
-import { GlCollapsibleListbox, 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 { HTTP_STATUS_INTERNAL_SERVER_ERROR, HTTP_STATUS_OK } from '~/lib/utils/http_status';
+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';
-import { mockBranches, mockRefs, mockFilteredRefs, mockTags } from '../mock_data';
-
-const projectRefsEndpoint = '/root/project/refs';
+const projectId = '8';
const refShortName = 'main';
const refFullName = 'refs/heads/main';
-jest.mock('~/alert');
-
describe('Pipeline New Form', () => {
let wrapper;
- let mock;
- 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 findRefSelector = () => wrapper.findComponent(RefSelector);
- const createComponent = (props = {}, mountFn = shallowMountExtended) => {
- wrapper = mountFn(RefsDropdown, {
- provide: {
- projectRefsEndpoint,
- },
+ const createComponent = (props = {}) => {
+ wrapper = shallowMount(RefsDropdown, {
propsData: {
+ projectId,
value: {
shortName: refShortName,
fullName: refFullName,
@@ -39,163 +26,54 @@ describe('Pipeline New Form', () => {
});
};
- beforeEach(() => {
- mock = new MockAdapter(axios);
- mock.onGet(projectRefsEndpoint, { params: { search: '' } }).reply(HTTP_STATUS_OK, mockRefs);
- });
-
- 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 () => {
- createComponent({}, mountExtended);
- findDropdown().vm.$emit('shown');
- await waitForPromises();
+ beforeEach(() => {
+ createComponent();
});
- 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('has default selected branch', () => {
+ expect(findRefSelector().props('value')).toBe('main');
});
- it('displays dropdown with branches and tags', () => {
- const refLength = mockRefs.Tags.length + mockRefs.Branches.length;
- expect(findRefsDropdownItems()).toHaveLength(refLength);
- });
-
- it('displays the names of refs', () => {
- // Branches
- expect(findRefsDropdownItems().at(0).text()).toBe(mockRefs.Branches[0]);
-
- // 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);
+ 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 () => {
- findRefsDropdownItems().at(selectedIndex).vm.$emit('select', 'refs/heads/branch-1');
- await waitForPromises();
- });
+ 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);
- 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);
+ expect(inputs[0]).toEqual([
+ {
+ shortName: 'conflict-contains-conflict-markers',
+ fullName: 'refs/heads/conflict-contains-conflict-markers',
+ },
+ ]);
});
});
});
describe('when user has selected a value', () => {
- const selectedIndex = 1;
- const mockShortName = mockRefs.Branches[selectedIndex];
+ const mockShortName = 'conflict-contains-conflict-markers';
const mockFullName = `refs/heads/${mockShortName}`;
- beforeEach(async () => {
- mock
- .onGet(projectRefsEndpoint, {
- params: { ref: mockFullName },
- })
- .reply(HTTP_STATUS_OK, mockRefs);
-
- 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();
- });
+ createComponent({
+ value: {
+ shortName: mockShortName,
+ fullName: mockFullName,
+ },
+ });
- it('loading error event is emitted', () => {
- expect(wrapper.emitted('loadingError')).toHaveLength(1);
- expect(wrapper.emitted('loadingError')[0]).toEqual([expect.any(Error)]);
+ expect(findRefSelector().props('value')).toBe(mockShortName);
});
});
-
- 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 175f513217bf890608306b317df40a48816afe8e..76a88f632981d81cb2a9bd87e803e97e5df11d5b 100644
--- a/spec/frontend/ci/pipeline_new/mock_data.js
+++ b/spec/frontend/ci/pipeline_new/mock_data.js
@@ -1,16 +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'],