diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-12 00:09:14 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-08-12 00:09:14 +0300 |
commit | 0f079aa28d93f40ad7fda398fb2280c3e358098d (patch) | |
tree | bc67abda12fd05a9712cd0bd97f82e33eb6c927f /spec | |
parent | 603ee53dbdbd3adaced752e1a119eb40d64e9979 (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
28 files changed, 474 insertions, 122 deletions
diff --git a/spec/controllers/admin/topics_controller_spec.rb b/spec/controllers/admin/topics_controller_spec.rb index ee36d5f1def..87093e0263b 100644 --- a/spec/controllers/admin/topics_controller_spec.rb +++ b/spec/controllers/admin/topics_controller_spec.rb @@ -173,4 +173,44 @@ RSpec.describe Admin::TopicsController do end end end + + describe 'POST #merge' do + let_it_be(:source_topic) { create(:topic, name: 'source_topic') } + let_it_be(:project) { create(:project, topic_list: source_topic.name ) } + + it 'merges source topic into target topic' do + post :merge, params: { source_topic_id: source_topic.id, target_topic_id: topic.id } + + expect(response).to redirect_to(admin_topics_path) + expect(topic.projects).to contain_exactly(project) + expect { source_topic.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'renders a 404 error for non-existing id' do + post :merge, params: { source_topic_id: non_existing_record_id, target_topic_id: topic.id } + + expect(response).to have_gitlab_http_status(:not_found) + expect { topic.reload }.not_to raise_error + end + + it 'renders a 400 error for identical topic ids' do + post :merge, params: { source_topic_id: topic, target_topic_id: topic.id } + + expect(response).to have_gitlab_http_status(:bad_request) + expect { topic.reload }.not_to raise_error + end + + context 'as a normal user' do + before do + sign_in(user) + end + + it 'renders a 404 error' do + post :merge, params: { source_topic_id: source_topic.id, target_topic_id: topic.id } + + expect(response).to have_gitlab_http_status(:not_found) + expect { source_topic.reload }.not_to raise_error + end + end + end end diff --git a/spec/fixtures/api/schemas/external_validation.json b/spec/fixtures/api/schemas/external_validation.json index ddcabd4c61e..4a2538a020e 100644 --- a/spec/fixtures/api/schemas/external_validation.json +++ b/spec/fixtures/api/schemas/external_validation.json @@ -4,7 +4,8 @@ "project", "user", "pipeline", - "builds" + "builds", + "total_builds_count" ], "properties" : { "project": { @@ -80,6 +81,7 @@ } } } - } + }, + "total_builds_count": { "type": "integer" } } } diff --git a/spec/frontend/blob/components/blob_content_spec.js b/spec/frontend/blob/components/blob_content_spec.js index 8450c6b9332..788ee0a86ab 100644 --- a/spec/frontend/blob/components/blob_content_spec.js +++ b/spec/frontend/blob/components/blob_content_spec.js @@ -36,20 +36,20 @@ describe('Blob Content component', () => { describe('rendering', () => { it('renders loader if `loading: true`', () => { createComponent({ loading: true }); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(true); - expect(wrapper.find(BlobContentError).exists()).toBe(false); - expect(wrapper.find(RichViewer).exists()).toBe(false); - expect(wrapper.find(SimpleViewer).exists()).toBe(false); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); + expect(wrapper.findComponent(BlobContentError).exists()).toBe(false); + expect(wrapper.findComponent(RichViewer).exists()).toBe(false); + expect(wrapper.findComponent(SimpleViewer).exists()).toBe(false); }); it('renders error if there is any in the viewer', () => { const renderError = 'Oops'; const viewer = { ...SimpleViewerMock, renderError }; createComponent({}, viewer); - expect(wrapper.find(GlLoadingIcon).exists()).toBe(false); - expect(wrapper.find(BlobContentError).exists()).toBe(true); - expect(wrapper.find(RichViewer).exists()).toBe(false); - expect(wrapper.find(SimpleViewer).exists()).toBe(false); + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(false); + expect(wrapper.findComponent(BlobContentError).exists()).toBe(true); + expect(wrapper.findComponent(RichViewer).exists()).toBe(false); + expect(wrapper.findComponent(SimpleViewer).exists()).toBe(false); }); it.each` @@ -60,7 +60,7 @@ describe('Blob Content component', () => { 'renders $type viewer when activeViewer is $type and no loading or error detected', ({ mock, viewer }) => { createComponent({}, mock); - expect(wrapper.find(viewer).exists()).toBe(true); + expect(wrapper.findComponent(viewer).exists()).toBe(true); }, ); @@ -70,13 +70,13 @@ describe('Blob Content component', () => { ${RichBlobContentMock.richData} | ${RichViewerMock} | ${RichViewer} `('renders correct content that is passed to the component', ({ content, mock, viewer }) => { createComponent({ content }, mock); - expect(wrapper.find(viewer).html()).toContain(content); + expect(wrapper.findComponent(viewer).html()).toContain(content); }); }); describe('functionality', () => { describe('render error', () => { - const findErrorEl = () => wrapper.find(BlobContentError); + const findErrorEl = () => wrapper.findComponent(BlobContentError); const renderError = BLOB_RENDER_ERRORS.REASONS.COLLAPSED.id; const viewer = { ...SimpleViewerMock, renderError }; diff --git a/spec/frontend/blob/components/blob_edit_content_spec.js b/spec/frontend/blob/components/blob_edit_content_spec.js index 9fc2356c018..5017b624292 100644 --- a/spec/frontend/blob/components/blob_edit_content_spec.js +++ b/spec/frontend/blob/components/blob_edit_content_spec.js @@ -69,7 +69,7 @@ describe('Blob Header Editing', () => { }); it('initialises Source Editor', () => { - const el = wrapper.find({ ref: 'editor' }).element; + const el = wrapper.findComponent({ ref: 'editor' }).element; expect(utils.initSourceEditor).toHaveBeenCalledWith({ el, blobPath: fileName, diff --git a/spec/frontend/blob/components/blob_edit_header_spec.js b/spec/frontend/blob/components/blob_edit_header_spec.js index b1ce0e9a4c5..c84b5896348 100644 --- a/spec/frontend/blob/components/blob_edit_header_spec.js +++ b/spec/frontend/blob/components/blob_edit_header_spec.js @@ -16,7 +16,7 @@ describe('Blob Header Editing', () => { }); }; const findDeleteButton = () => - wrapper.findAll(GlButton).wrappers.find((x) => x.text() === 'Delete file'); + wrapper.findAllComponents(GlButton).wrappers.find((x) => x.text() === 'Delete file'); beforeEach(() => { createComponent(); @@ -32,7 +32,7 @@ describe('Blob Header Editing', () => { }); it('contains a form input field', () => { - expect(wrapper.find(GlFormInput).exists()).toBe(true); + expect(wrapper.findComponent(GlFormInput).exists()).toBe(true); }); it('does not show delete button', () => { @@ -42,7 +42,7 @@ describe('Blob Header Editing', () => { describe('functionality', () => { it('emits input event when the blob name is changed', async () => { - const inputComponent = wrapper.find(GlFormInput); + const inputComponent = wrapper.findComponent(GlFormInput); const newValue = 'bar.txt'; // setData usage is discouraged. See https://gitlab.com/groups/gitlab-org/-/epics/7330 for details diff --git a/spec/frontend/blob/components/blob_header_default_actions_spec.js b/spec/frontend/blob/components/blob_header_default_actions_spec.js index aa538facae2..2382ac15f40 100644 --- a/spec/frontend/blob/components/blob_header_default_actions_spec.js +++ b/spec/frontend/blob/components/blob_header_default_actions_spec.js @@ -30,8 +30,8 @@ describe('Blob Header Default Actions', () => { beforeEach(() => { createComponent(); - btnGroup = wrapper.find(GlButtonGroup); - buttons = wrapper.findAll(GlButton); + btnGroup = wrapper.findComponent(GlButtonGroup); + buttons = wrapper.findAllComponents(GlButton); }); afterEach(() => { @@ -69,7 +69,7 @@ describe('Blob Header Default Actions', () => { createComponent({ activeViewer: RICH_BLOB_VIEWER, }); - buttons = wrapper.findAll(GlButton); + buttons = wrapper.findAllComponents(GlButton); expect(buttons.at(0).attributes('disabled')).toBeTruthy(); }); diff --git a/spec/frontend/blob/components/blob_header_filepath_spec.js b/spec/frontend/blob/components/blob_header_filepath_spec.js index 8220b598ff6..8c32cba1ba4 100644 --- a/spec/frontend/blob/components/blob_header_filepath_spec.js +++ b/spec/frontend/blob/components/blob_header_filepath_spec.js @@ -25,7 +25,7 @@ describe('Blob Header Filepath', () => { wrapper.destroy(); }); - const findBadge = () => wrapper.find(GlBadge); + const findBadge = () => wrapper.findComponent(GlBadge); describe('rendering', () => { it('matches the snapshot', () => { @@ -46,7 +46,7 @@ describe('Blob Header Filepath', () => { it('renders copy-to-clipboard icon that copies path of the Blob', () => { createComponent(); - const btn = wrapper.find(ClipboardButton); + const btn = wrapper.findComponent(ClipboardButton); expect(btn.exists()).toBe(true); expect(btn.vm.text).toBe(MockBlob.path); }); diff --git a/spec/frontend/blob/components/blob_header_spec.js b/spec/frontend/blob/components/blob_header_spec.js index ee42c2387ae..46740958090 100644 --- a/spec/frontend/blob/components/blob_header_spec.js +++ b/spec/frontend/blob/components/blob_header_spec.js @@ -31,7 +31,7 @@ describe('Blob Header Default Actions', () => { }); describe('rendering', () => { - const findDefaultActions = () => wrapper.find(DefaultActions); + const findDefaultActions = () => wrapper.findComponent(DefaultActions); const slots = { prepend: 'Foo Prepend', @@ -45,17 +45,17 @@ describe('Blob Header Default Actions', () => { it('renders all components', () => { createComponent(); - expect(wrapper.find(TableContents).exists()).toBe(true); - expect(wrapper.find(ViewerSwitcher).exists()).toBe(true); + expect(wrapper.findComponent(TableContents).exists()).toBe(true); + expect(wrapper.findComponent(ViewerSwitcher).exists()).toBe(true); expect(findDefaultActions().exists()).toBe(true); - expect(wrapper.find(BlobFilepath).exists()).toBe(true); + expect(wrapper.findComponent(BlobFilepath).exists()).toBe(true); }); it('does not render viewer switcher if the blob has only the simple viewer', () => { createComponent({ richViewer: null, }); - expect(wrapper.find(ViewerSwitcher).exists()).toBe(false); + expect(wrapper.findComponent(ViewerSwitcher).exists()).toBe(false); }); it('does not render viewer switcher if a corresponding prop is passed', () => { @@ -66,7 +66,7 @@ describe('Blob Header Default Actions', () => { hideViewerSwitcher: true, }, ); - expect(wrapper.find(ViewerSwitcher).exists()).toBe(false); + expect(wrapper.findComponent(ViewerSwitcher).exists()).toBe(false); }); it('does not render default actions is corresponding prop is passed', () => { @@ -77,7 +77,7 @@ describe('Blob Header Default Actions', () => { hideDefaultActions: true, }, ); - expect(wrapper.find(DefaultActions).exists()).toBe(false); + expect(wrapper.findComponent(DefaultActions).exists()).toBe(false); }); Object.keys(slots).forEach((slot) => { diff --git a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js index 91baaf3ea69..1eac0733646 100644 --- a/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js +++ b/spec/frontend/blob/components/blob_header_viewer_switcher_spec.js @@ -35,8 +35,8 @@ describe('Blob Header Viewer Switcher', () => { beforeEach(() => { createComponent(); - btnGroup = wrapper.find(GlButtonGroup); - buttons = wrapper.findAll(GlButton); + btnGroup = wrapper.findComponent(GlButtonGroup); + buttons = wrapper.findAllComponents(GlButton); }); it('renders gl-button-group component', () => { @@ -58,7 +58,7 @@ describe('Blob Header Viewer Switcher', () => { function factory(propsData = {}) { createComponent(propsData); - buttons = wrapper.findAll(GlButton); + buttons = wrapper.findAllComponents(GlButton); simpleBtn = buttons.at(0); richBtn = buttons.at(1); diff --git a/spec/frontend/blob/notebook/notebook_viever_spec.js b/spec/frontend/blob/notebook/notebook_viever_spec.js index 93406db2675..ea4badc03fb 100644 --- a/spec/frontend/blob/notebook/notebook_viever_spec.js +++ b/spec/frontend/blob/notebook/notebook_viever_spec.js @@ -31,10 +31,10 @@ describe('iPython notebook renderer', () => { wrapper = shallowMount(component, { propsData: { endpoint, relativeRawPath } }); }; - const findLoading = () => wrapper.find(GlLoadingIcon); - const findNotebookLab = () => wrapper.find(NotebookLab); - const findLoadErrorMessage = () => wrapper.find({ ref: 'loadErrorMessage' }); - const findParseErrorMessage = () => wrapper.find({ ref: 'parsingErrorMessage' }); + const findLoading = () => wrapper.findComponent(GlLoadingIcon); + const findNotebookLab = () => wrapper.findComponent(NotebookLab); + const findLoadErrorMessage = () => wrapper.findComponent({ ref: 'loadErrorMessage' }); + const findParseErrorMessage = () => wrapper.findComponent({ ref: 'parsingErrorMessage' }); beforeEach(() => { mock = new MockAdapter(axios); diff --git a/spec/frontend/blob/pdf/pdf_viewer_spec.js b/spec/frontend/blob/pdf/pdf_viewer_spec.js index e332ea49fa6..23227df6357 100644 --- a/spec/frontend/blob/pdf/pdf_viewer_spec.js +++ b/spec/frontend/blob/pdf/pdf_viewer_spec.js @@ -18,9 +18,9 @@ describe('PDF renderer', () => { }); }; - const findLoading = () => wrapper.find(GlLoadingIcon); - const findPdfLab = () => wrapper.find(PdfLab); - const findLoadError = () => wrapper.find({ ref: 'loadError' }); + const findLoading = () => wrapper.findComponent(GlLoadingIcon); + const findPdfLab = () => wrapper.findComponent(PdfLab); + const findLoadError = () => wrapper.findComponent({ ref: 'loadError' }); beforeEach(() => { mountComponent(); diff --git a/spec/frontend/blob/pipeline_tour_success_modal_spec.js b/spec/frontend/blob/pipeline_tour_success_modal_spec.js index 750dd8f0a72..81b38cfc278 100644 --- a/spec/frontend/blob/pipeline_tour_success_modal_spec.js +++ b/spec/frontend/blob/pipeline_tour_success_modal_spec.js @@ -52,7 +52,7 @@ describe('PipelineTourSuccessModal', () => { }); it('renders the path from the commit cookie for back to the merge request button', () => { - const goToMrBtn = wrapper.find({ ref: 'goToMergeRequest' }); + const goToMrBtn = wrapper.findComponent({ ref: 'goToMergeRequest' }); expect(goToMrBtn.attributes('href')).toBe(expectedMrPath); }); @@ -67,16 +67,16 @@ describe('PipelineTourSuccessModal', () => { }); it('renders the path from projectMergeRequestsPath for back to the merge request button', () => { - const goToMrBtn = wrapper.find({ ref: 'goToMergeRequest' }); + const goToMrBtn = wrapper.findComponent({ ref: 'goToMergeRequest' }); expect(goToMrBtn.attributes('href')).toBe(expectedMrPath); }); }); it('has expected structure', () => { - const modal = wrapper.find(GlModal); - const sprintf = modal.find(GlSprintf); - const emoji = modal.find(GlEmoji); + const modal = wrapper.findComponent(GlModal); + const sprintf = modal.findComponent(GlSprintf); + const emoji = modal.findComponent(GlEmoji); expect(wrapper.text()).toContain("That's it, well done!"); expect(sprintf.exists()).toBe(true); @@ -84,7 +84,7 @@ describe('PipelineTourSuccessModal', () => { }); it('renders the link for codeQualityLink', () => { - expect(wrapper.find(GlLink).attributes('href')).toBe('/code-quality-link'); + expect(wrapper.findComponent(GlLink).attributes('href')).toBe('/code-quality-link'); }); it('calls to remove cookie', () => { @@ -103,7 +103,7 @@ describe('PipelineTourSuccessModal', () => { it('send an event when go to pipelines is clicked', () => { trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn); - const goToBtn = wrapper.find({ ref: 'goToPipelines' }); + const goToBtn = wrapper.findComponent({ ref: 'goToPipelines' }); triggerEvent(goToBtn.element); expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', { @@ -115,7 +115,7 @@ describe('PipelineTourSuccessModal', () => { it('sends an event when back to the merge request is clicked', () => { trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn); - const goToBtn = wrapper.find({ ref: 'goToMergeRequest' }); + const goToBtn = wrapper.findComponent({ ref: 'goToMergeRequest' }); triggerEvent(goToBtn.element); expect(trackingSpy).toHaveBeenCalledWith('_category_', 'click_button', { diff --git a/spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js b/spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js index 7e13994f2b7..6b329dc078a 100644 --- a/spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js +++ b/spec/frontend/blob/suggest_gitlab_ci_yml/components/popover_spec.js @@ -98,7 +98,7 @@ describe('Suggest gitlab-ci.yml Popover', () => { const expectedAction = 'click_button'; const expectedProperty = 'owner'; const expectedValue = '10'; - const dismissButton = wrapper.find(GlButton); + const dismissButton = wrapper.findComponent(GlButton); trackingSpy = mockTracking('_category_', wrapper.element, jest.spyOn); triggerEvent(dismissButton.element); diff --git a/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js new file mode 100644 index 00000000000..fb79611229c --- /dev/null +++ b/spec/frontend/ci_variable_list/components/ci_variable_settings_spec.js @@ -0,0 +1,123 @@ +import { nextTick } from 'vue'; +import { shallowMount } from '@vue/test-utils'; +import CiVariableSettings from '~/ci_variable_list/components/ci_variable_settings.vue'; +import ciVariableModal from '~/ci_variable_list/components/ci_variable_modal.vue'; +import ciVariableTable from '~/ci_variable_list/components/ci_variable_table.vue'; +import { ADD_VARIABLE_ACTION, EDIT_VARIABLE_ACTION } from '~/ci_variable_list/constants'; +import { createJoinedEnvironments, mapEnvironmentNames } from '~/ci_variable_list/utils'; + +import { mockEnvs, mockVariablesWithScopes, newVariable } from '../mocks'; + +describe('Ci variable table', () => { + let wrapper; + + const defaultProps = { + areScopedVariablesAvailable: true, + environments: mapEnvironmentNames(mockEnvs), + isLoading: false, + variables: mockVariablesWithScopes, + }; + + const findCiVariableTable = () => wrapper.findComponent(ciVariableTable); + const findCiVariableModal = () => wrapper.findComponent(ciVariableModal); + + const createComponent = () => { + wrapper = shallowMount(CiVariableSettings, { + propsData: { + ...defaultProps, + }, + }); + }; + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('props passing', () => { + it('passes props down correctly to the ci table', () => { + expect(findCiVariableTable().props()).toEqual({ + isLoading: defaultProps.isLoading, + variables: defaultProps.variables, + }); + }); + + it('passes props down correctly to the ci modal', async () => { + findCiVariableTable().vm.$emit('set-selected-variable'); + await nextTick(); + + expect(findCiVariableModal().props()).toEqual({ + areScopedVariablesAvailable: defaultProps.areScopedVariablesAvailable, + environments: createJoinedEnvironments(defaultProps.variables, defaultProps.environments), + mode: ADD_VARIABLE_ACTION, + selectedVariable: {}, + }); + }); + }); + + describe('modal mode', () => { + it('passes down ADD mode when receiving an empty variable', async () => { + findCiVariableTable().vm.$emit('set-selected-variable'); + await nextTick(); + + expect(findCiVariableModal().props('mode')).toBe(ADD_VARIABLE_ACTION); + }); + + it('passes down EDIT mode when receiving a variable', async () => { + findCiVariableTable().vm.$emit('set-selected-variable', newVariable); + await nextTick(); + + expect(findCiVariableModal().props('mode')).toBe(EDIT_VARIABLE_ACTION); + }); + }); + + describe('variable modal', () => { + it('is hidden by default', () => { + expect(findCiVariableModal().exists()).toBe(false); + }); + + it('shows modal when adding a new variable', async () => { + findCiVariableTable().vm.$emit('set-selected-variable'); + await nextTick(); + + expect(findCiVariableModal().exists()).toBe(true); + }); + + it('shows modal when updating a variable', async () => { + findCiVariableTable().vm.$emit('set-selected-variable', newVariable); + await nextTick(); + + expect(findCiVariableModal().exists()).toBe(true); + }); + + it('hides modal when receiving the event from the modal', async () => { + findCiVariableTable().vm.$emit('set-selected-variable'); + await nextTick(); + + findCiVariableModal().vm.$emit('hideModal'); + await nextTick(); + + expect(findCiVariableModal().exists()).toBe(false); + }); + }); + + describe('variable events', () => { + it.each` + eventName + ${'add-variable'} + ${'update-variable'} + ${'delete-variable'} + `('bubbles up the $eventName event', async ({ eventName }) => { + findCiVariableTable().vm.$emit('set-selected-variable'); + await nextTick(); + + findCiVariableModal().vm.$emit(eventName, newVariable); + await nextTick(); + + expect(wrapper.emitted(eventName)).toEqual([[newVariable]]); + }); + }); +}); diff --git a/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js b/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js new file mode 100644 index 00000000000..b5b4881aa44 --- /dev/null +++ b/spec/frontend/ci_variable_list/components/ci_variable_table_spec.js @@ -0,0 +1,97 @@ +import { mountExtended } from 'helpers/vue_test_utils_helper'; +import CiVariableTable from '~/ci_variable_list/components/ci_variable_table.vue'; +import { mockVariables } from '../mocks'; + +describe('Ci variable table', () => { + let wrapper; + + const defaultProps = { + isLoading: false, + variables: mockVariables, + }; + + const createComponent = ({ props = {} } = {}) => { + wrapper = mountExtended(CiVariableTable, { + attachTo: document.body, + propsData: { + ...defaultProps, + ...props, + }, + }); + }; + + const findRevealButton = () => wrapper.findByText('Reveal values'); + const findAddButton = () => wrapper.findByLabelText('Add'); + const findEditButton = () => wrapper.findByLabelText('Edit'); + const findEmptyVariablesPlaceholder = () => wrapper.findByText('There are no variables yet.'); + const findHiddenValues = () => wrapper.findAll('[data-testid="hiddenValue"]'); + const findRevealedValues = () => wrapper.findAll('[data-testid="revealedValue"]'); + + beforeEach(() => { + createComponent(); + }); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('When table is empty', () => { + beforeEach(() => { + createComponent({ props: { variables: [] } }); + }); + + it('displays empty message', () => { + expect(findEmptyVariablesPlaceholder().exists()).toBe(true); + }); + + it('hides the reveal button', () => { + expect(findRevealButton().exists()).toBe(false); + }); + }); + + describe('When table has variables', () => { + beforeEach(() => { + createComponent(); + }); + + it('does not display the empty message', () => { + expect(findEmptyVariablesPlaceholder().exists()).toBe(false); + }); + + it('displays the reveal button', () => { + expect(findRevealButton().exists()).toBe(true); + }); + + it('displays the correct amount of variables', async () => { + expect(wrapper.findAll('.js-ci-variable-row')).toHaveLength(defaultProps.variables.length); + }); + }); + + describe('Table click actions', () => { + beforeEach(() => { + createComponent(); + }); + + it('reveals secret values when button is clicked', async () => { + expect(findHiddenValues()).toHaveLength(defaultProps.variables.length); + expect(findRevealedValues()).toHaveLength(0); + + await findRevealButton().trigger('click'); + + expect(findHiddenValues()).toHaveLength(0); + expect(findRevealedValues()).toHaveLength(defaultProps.variables.length); + }); + + it('dispatches `setSelectedVariable` with correct variable to edit', async () => { + await findEditButton().trigger('click'); + + expect(wrapper.emitted('set-selected-variable')).toEqual([[defaultProps.variables[0]]]); + }); + + it('dispatches `setSelectedVariable` with no variable when adding a new one', async () => { + await findAddButton().trigger('click'); + + expect(wrapper.emitted('set-selected-variable')).toEqual([[null]]); + }); + }); +}); diff --git a/spec/frontend/editor/schema/ci/ci_schema_spec.js b/spec/frontend/editor/schema/ci/ci_schema_spec.js index c59806a5d60..b9d2426c33d 100644 --- a/spec/frontend/editor/schema/ci/ci_schema_spec.js +++ b/spec/frontend/editor/schema/ci/ci_schema_spec.js @@ -24,12 +24,14 @@ import ReleaseAssetsLinksMissingJson from './json_tests/negative_tests/release_a import RetryUnknownWhenJson from './json_tests/negative_tests/retry_unknown_when.json'; // YAML POSITIVE TEST +import ArtifactsYaml from './yaml_tests/positive_tests/artifacts.yml'; import CacheYaml from './yaml_tests/positive_tests/cache.yml'; import FilterYaml from './yaml_tests/positive_tests/filter.yml'; import IncludeYaml from './yaml_tests/positive_tests/include.yml'; import RulesYaml from './yaml_tests/positive_tests/rules.yml'; // YAML NEGATIVE TEST +import ArtifactsNegativeYaml from './yaml_tests/negative_tests/artifacts.yml'; import CacheNegativeYaml from './yaml_tests/negative_tests/cache.yml'; import IncludeNegativeYaml from './yaml_tests/negative_tests/include.yml'; @@ -63,6 +65,7 @@ describe('positive tests', () => { FilterYaml, IncludeYaml, RulesYaml, + ArtifactsYaml, }), )('schema validates %s', (_, input) => { expect(input).toValidateJsonSchema(schema); @@ -84,6 +87,7 @@ describe('negative tests', () => { // YAML CacheNegativeYaml, IncludeNegativeYaml, + ArtifactsNegativeYaml, }), )('schema validates %s', (_, input) => { expect(input).not.toValidateJsonSchema(schema); diff --git a/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/artifacts.yml b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/artifacts.yml new file mode 100644 index 00000000000..f5670376efc --- /dev/null +++ b/spec/frontend/editor/schema/ci/yaml_tests/negative_tests/artifacts.yml @@ -0,0 +1,18 @@ +# invalid artifact:reports:cyclonedx + +cyclonedx no paths: + artifacts: + reports: + cyclonedx: + +cyclonedx not a report: + artifacts: + cyclonedx: foo + +cyclonedx not an array or string: + artifacts: + reports: + cyclonedx: + paths: + - foo + - bar diff --git a/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/artifacts.yml b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/artifacts.yml new file mode 100644 index 00000000000..20c1fc2c50f --- /dev/null +++ b/spec/frontend/editor/schema/ci/yaml_tests/positive_tests/artifacts.yml @@ -0,0 +1,25 @@ +# valid artifact:reports:cyclonedx + +cyclonedx string path: + artifacts: + reports: + cyclonedx: foo + +cyclonedx glob path: + artifacts: + reports: + cyclonedx: "*.foo" + +cylonedx list of string paths: + artifacts: + reports: + cyclonedx: + - foo + - ./bar/baz + +cylonedx mixed list of string paths and globs: + artifacts: + reports: + cyclonedx: + - ./foo + - "bar/*.baz" diff --git a/spec/frontend/ide/stores/modules/commit/getters_spec.js b/spec/frontend/ide/stores/modules/commit/getters_spec.js index 1e34087b290..38ebe36c2c5 100644 --- a/spec/frontend/ide/stores/modules/commit/getters_spec.js +++ b/spec/frontend/ide/stores/modules/commit/getters_spec.js @@ -14,21 +14,21 @@ describe('IDE commit module getters', () => { describe('discardDraftButtonDisabled', () => { it('returns true when commitMessage is empty', () => { - expect(getters.discardDraftButtonDisabled(state)).toBeTruthy(); + expect(getters.discardDraftButtonDisabled(state)).toBe(true); }); it('returns false when commitMessage is not empty & loading is false', () => { state.commitMessage = 'test'; state.submitCommitLoading = false; - expect(getters.discardDraftButtonDisabled(state)).toBeFalsy(); + expect(getters.discardDraftButtonDisabled(state)).toBe(false); }); it('returns true when commitMessage is not empty & loading is true', () => { state.commitMessage = 'test'; state.submitCommitLoading = true; - expect(getters.discardDraftButtonDisabled(state)).toBeTruthy(); + expect(getters.discardDraftButtonDisabled(state)).toBe(true); }); }); @@ -152,13 +152,13 @@ describe('IDE commit module getters', () => { it('returns false if NOT creating a new branch', () => { state.commitAction = COMMIT_TO_CURRENT_BRANCH; - expect(getters.isCreatingNewBranch(state)).toBeFalsy(); + expect(getters.isCreatingNewBranch(state)).toBe(false); }); it('returns true if creating a new branch', () => { state.commitAction = COMMIT_TO_NEW_BRANCH; - expect(getters.isCreatingNewBranch(state)).toBeTruthy(); + expect(getters.isCreatingNewBranch(state)).toBe(true); }); }); @@ -183,7 +183,7 @@ describe('IDE commit module getters', () => { }); it('should never hide "New MR" option', () => { - expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeNull(); }); }); @@ -195,13 +195,13 @@ describe('IDE commit module getters', () => { it('should NOT hide "New MR" option if user can NOT push to the current branch', () => { rootGetters.canPushToBranch = false; - expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBe(false); }); it('should hide "New MR" option if user can push to the current branch', () => { rootGetters.canPushToBranch = true; - expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeTruthy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBe(true); }); }); @@ -211,7 +211,7 @@ describe('IDE commit module getters', () => { }); it('should never hide "New MR" option', () => { - expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeNull(); }); }); @@ -223,13 +223,13 @@ describe('IDE commit module getters', () => { it('should NOT hide "New MR" option if there is NO existing MR for the current branch', () => { rootGetters.hasMergeRequest = false; - expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeNull(); }); it('should hide "New MR" option if there is existing MR for the current branch', () => { rootGetters.hasMergeRequest = true; - expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeTruthy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBe(true); }); }); @@ -247,17 +247,13 @@ describe('IDE commit module getters', () => { it('should hide "New MR" when there is an existing MR', () => { rootGetters.hasMergeRequest = true; - expect( - getters.shouldHideNewMrOption(state, localGetters, null, rootGetters), - ).toBeTruthy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBe(true); }); it('should hide "New MR" when there is no existing MR', () => { rootGetters.hasMergeRequest = false; - expect( - getters.shouldHideNewMrOption(state, localGetters, null, rootGetters), - ).toBeTruthy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBe(true); }); }); @@ -270,17 +266,17 @@ describe('IDE commit module getters', () => { rootGetters.hasMergeRequest = false; rootGetters.canPushToBranch = true; - expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBe(false); rootGetters.hasMergeRequest = true; rootGetters.canPushToBranch = true; - expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBe(false); rootGetters.hasMergeRequest = false; rootGetters.canPushToBranch = false; - expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBe(false); }); }); }); @@ -292,7 +288,7 @@ describe('IDE commit module getters', () => { rootGetters.hasMergeRequest = true; rootGetters.canPushToBranch = true; - expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBeFalsy(); + expect(getters.shouldHideNewMrOption(state, localGetters, null, rootGetters)).toBe(false); }); }); diff --git a/spec/frontend/pipelines/test_reports/test_reports_spec.js b/spec/frontend/pipelines/test_reports/test_reports_spec.js index 3c3143b1865..9b9ee4172f9 100644 --- a/spec/frontend/pipelines/test_reports/test_reports_spec.js +++ b/spec/frontend/pipelines/test_reports/test_reports_spec.js @@ -94,8 +94,8 @@ describe('Test reports app', () => { beforeEach(() => createComponent()); it('sets testReports and shows tests', () => { - expect(wrapper.vm.testReports).toBeTruthy(); - expect(wrapper.vm.showTests).toBeTruthy(); + expect(wrapper.vm.testReports).toEqual(expect.any(Object)); + expect(wrapper.vm.showTests).toBe(true); }); it('shows tests details', () => { diff --git a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js index 02148e044ec..6e89cd41559 100644 --- a/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/frontend/vue_merge_request_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -369,7 +369,7 @@ describe('ReadyToMerge', () => { const params = wrapper.vm.service.merge.mock.calls[0][0]; - expect(params.should_remove_source_branch).toBeTruthy(); + expect(params.should_remove_source_branch).toBe(true); expect(params.auto_merge_strategy).toBeUndefined(); }); @@ -393,7 +393,7 @@ describe('ReadyToMerge', () => { const params = wrapper.vm.service.merge.mock.calls[0][0]; - expect(params.should_remove_source_branch).toBeTruthy(); + expect(params.should_remove_source_branch).toBe(true); expect(params.auto_merge_strategy).toBeUndefined(); }); @@ -469,8 +469,8 @@ describe('ReadyToMerge', () => { expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]); - expect(cpc).toBeFalsy(); - expect(spc).toBeTruthy(); + expect(cpc).toBe(false); + expect(spc).toBe(true); }); it('should continue polling until MR is merged', async () => { @@ -492,8 +492,8 @@ describe('ReadyToMerge', () => { await waitForPromises(); - expect(cpc).toBeTruthy(); - expect(spc).toBeFalsy(); + expect(cpc).toBe(true); + expect(spc).toBe(false); }); }); }); @@ -527,13 +527,13 @@ describe('ReadyToMerge', () => { mr: { commitsCount: 2, enableSquashBeforeMerge: true }, }); - expect(findCheckboxElement().exists()).toBeTruthy(); + expect(findCheckboxElement().exists()).toBe(true); }); it('should not be rendered when squash before merge is disabled', () => { createComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: false } }); - expect(findCheckboxElement().exists()).toBeFalsy(); + expect(findCheckboxElement().exists()).toBe(false); }); it('should be rendered when there is only 1 commit', () => { @@ -710,7 +710,7 @@ describe('ReadyToMerge', () => { it('should not be rendered if squash is disabled', () => { createComponent(); - expect(findCommitDropdownElement().exists()).toBeFalsy(); + expect(findCommitDropdownElement().exists()).toBe(false); }); it('should be rendered if squash is enabled and there is more than 1 commit', async () => { @@ -720,7 +720,7 @@ describe('ReadyToMerge', () => { await wrapper.find('[data-testid="widget_edit_commit_message"]').vm.$emit('input', true); - expect(findCommitDropdownElement().exists()).toBeTruthy(); + expect(findCommitDropdownElement().exists()).toBe(true); }); }); diff --git a/spec/frontend/vue_shared/components/dismissible_container_spec.js b/spec/frontend/vue_shared/components/dismissible_container_spec.js index b8aeea38e77..f7030f38709 100644 --- a/spec/frontend/vue_shared/components/dismissible_container_spec.js +++ b/spec/frontend/vue_shared/components/dismissible_container_spec.js @@ -33,7 +33,7 @@ describe('DismissibleContainer', () => { button.trigger('click'); - expect(wrapper.emitted().dismiss).toBeTruthy(); + expect(wrapper.emitted().dismiss).toEqual(expect.any(Array)); }); }); diff --git a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js index 7bac9f8235b..b7ce3e47cef 100644 --- a/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js +++ b/spec/frontend/vue_shared/components/user_popover/user_popover_spec.js @@ -351,7 +351,7 @@ describe('User Popover Component', () => { await axios.waitForAll(); expect(wrapper.emitted().follow.length).toBe(1); - expect(wrapper.emitted().unfollow).toBeFalsy(); + expect(wrapper.emitted().unfollow).toBeUndefined(); }); itTracksToggleFollowButtonClick('follow_from_user_popover'); @@ -376,8 +376,8 @@ describe('User Popover Component', () => { it('emits no events', async () => { await axios.waitForAll(); - expect(wrapper.emitted().follow).toBe(undefined); - expect(wrapper.emitted().unfollow).toBe(undefined); + expect(wrapper.emitted().follow).toBeUndefined(); + expect(wrapper.emitted().unfollow).toBeUndefined(); }); }); }); @@ -423,8 +423,8 @@ describe('User Popover Component', () => { }); it('emits no events', () => { - expect(wrapper.emitted().follow).toBe(undefined); - expect(wrapper.emitted().unfollow).toBe(undefined); + expect(wrapper.emitted().follow).toBeUndefined(); + expect(wrapper.emitted().unfollow).toBeUndefined(); }); }); }); diff --git a/spec/lib/gitlab/audit/target_spec.rb b/spec/lib/gitlab/audit/target_spec.rb index 4748b4599fe..5c06cd117a9 100644 --- a/spec/lib/gitlab/audit/target_spec.rb +++ b/spec/lib/gitlab/audit/target_spec.rb @@ -3,7 +3,7 @@ require 'spec_helper' RSpec.describe Gitlab::Audit::Target do - let(:object) { double('object') } # rubocop:disable Rspec/VerifiedDoubles + let(:object) { double('object') } # rubocop:disable RSpec/VerifiedDoubles subject { described_class.new(object) } diff --git a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb index c19756c4783..fb1a360a4b7 100644 --- a/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb +++ b/spec/lib/gitlab/ci/pipeline/chain/validate/external_spec.rb @@ -148,6 +148,8 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do expect(::Gitlab::HTTP).to receive(:post) do |_url, params| payload = Gitlab::Json.parse(params[:body]) + expect(payload['total_builds_count']).to eq(0) + builds = payload['builds'] expect(builds.count).to eq(2) expect(builds[0]['services']).to be_nil @@ -160,6 +162,23 @@ RSpec.describe Gitlab::Ci::Pipeline::Chain::Validate::External do perform! end + + context "with existing jobs from other project's alive pipelines" do + before do + create(:ci_pipeline, :with_job, user: user) + create(:ci_pipeline, :with_job) + end + + it 'returns the expected total_builds_count' do + expect(::Gitlab::HTTP).to receive(:post) do |_url, params| + payload = Gitlab::Json.parse(params[:body]) + + expect(payload['total_builds_count']).to eq(1) + end + + perform! + end + end end context 'when EXTERNAL_VALIDATION_SERVICE_TOKEN is set' do diff --git a/spec/models/concerns/cross_database_modification_spec.rb b/spec/models/concerns/cross_database_modification_spec.rb index 72544536953..c3831b654cf 100644 --- a/spec/models/concerns/cross_database_modification_spec.rb +++ b/spec/models/concerns/cross_database_modification_spec.rb @@ -4,38 +4,6 @@ require 'spec_helper' RSpec.describe CrossDatabaseModification do describe '.transaction' do - context 'feature flag disabled' do - before do - stub_feature_flags(track_gitlab_schema_in_current_transaction: false) - end - - it 'does not add to gitlab_transactions_stack' do - ApplicationRecord.transaction do - expect(ApplicationRecord.gitlab_transactions_stack).to be_empty - - Project.first - end - - expect(ApplicationRecord.gitlab_transactions_stack).to be_empty - end - end - - context 'feature flag is not yet setup' do - before do - allow(Feature::FlipperFeature).to receive(:table_exists?).and_raise(ActiveRecord::NoDatabaseError) - end - - it 'does not add to gitlab_transactions_stack' do - ApplicationRecord.transaction do - expect(ApplicationRecord.gitlab_transactions_stack).to be_empty - - Project.first - end - - expect(ApplicationRecord.gitlab_transactions_stack).to be_empty - end - end - it 'adds the current gitlab schema to gitlab_transactions_stack', :aggregate_failures do ApplicationRecord.transaction do expect(ApplicationRecord.gitlab_transactions_stack).to contain_exactly(:gitlab_main) diff --git a/spec/models/concerns/database_event_tracking_spec.rb b/spec/models/concerns/database_event_tracking_spec.rb index 79be8654498..976462b4174 100644 --- a/spec/models/concerns/database_event_tracking_spec.rb +++ b/spec/models/concerns/database_event_tracking_spec.rb @@ -9,7 +9,7 @@ RSpec.describe DatabaseEventTracking, :snowplow do self.table_name = 'application_setting_terms' - self::SNOWPLOW_ATTRIBUTES = %w[id].freeze # rubocop:disable Rspec/LeakyConstantDeclaration + self::SNOWPLOW_ATTRIBUTES = %w[id].freeze # rubocop:disable RSpec/LeakyConstantDeclaration end end @@ -17,7 +17,7 @@ RSpec.describe DatabaseEventTracking, :snowplow do context 'if event emmiter failed' do before do - allow(Gitlab::Tracking).to receive(:event).and_raise(StandardError) # rubocop:disable Rspec/ExpectGitlabTracking + allow(Gitlab::Tracking).to receive(:event).and_raise(StandardError) # rubocop:disable RSpec/ExpectGitlabTracking end it 'tracks the exception' do diff --git a/spec/services/topics/merge_service_spec.rb b/spec/services/topics/merge_service_spec.rb new file mode 100644 index 00000000000..971917eb8e9 --- /dev/null +++ b/spec/services/topics/merge_service_spec.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Topics::MergeService do + let_it_be(:source_topic) { create(:topic, name: 'source_topic') } + let_it_be(:target_topic) { create(:topic, name: 'target_topic') } + let_it_be(:project_1) { create(:project, :public, topic_list: source_topic.name ) } + let_it_be(:project_2) { create(:project, :private, topic_list: source_topic.name ) } + let_it_be(:project_3) { create(:project, :public, topic_list: target_topic.name ) } + let_it_be(:project_4) { create(:project, :public, topic_list: [source_topic.name, target_topic.name] ) } + + subject { described_class.new(source_topic, target_topic).execute } + + describe '#execute' do + it 'merges source topic into target topic' do + subject + + expect(target_topic.projects).to contain_exactly(project_1, project_2, project_3, project_4) + expect { source_topic.reload }.to raise_error(ActiveRecord::RecordNotFound) + end + + it 'refreshes counters of target topic' do + expect { subject } + .to change { target_topic.reload.total_projects_count }.by(2) + .and change { target_topic.reload.non_private_projects_count }.by(1) + end + + context 'when source topic fails to delete' do + it 'reverts previous changes' do + allow(source_topic.reload).to receive(:destroy!).and_raise(ActiveRecord::RecordNotDestroyed) + + expect { subject }.to raise_error(ActiveRecord::RecordNotDestroyed) + + expect(source_topic.projects).to contain_exactly(project_1, project_2, project_4) + expect(target_topic.projects).to contain_exactly(project_3, project_4) + end + end + + context 'for parameter validation' do + using RSpec::Parameterized::TableSyntax + + subject { described_class.new(source_topic_parameter, target_topic_parameter).execute } + + where(:source_topic_parameter, :target_topic_parameter, :expected_message) do + nil | ref(:target_topic) | 'The source topic is not a topic.' + ref(:source_topic) | nil | 'The target topic is not a topic.' + ref(:target_topic) | ref(:target_topic) | 'The source topic and the target topic are identical.' # rubocop:disable Lint/BinaryOperatorWithIdenticalOperands + end + + with_them do + it 'raises correct error' do + expect { subject }.to raise_error(ArgumentError) do |error| + expect(error.message).to eq(expected_message) + end + end + end + end + end +end |