diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-10 00:10:34 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-06-10 00:10:34 +0300 |
commit | f820d18e56f2bd63dd0f91b076ace59345a036a1 (patch) | |
tree | 93faf6cea37b703dc3cd01ef81b4b8d63f855582 /spec | |
parent | d40d684afaec767bb05efdbeaa4ce3620cd337bb (diff) |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec')
7 files changed, 281 insertions, 115 deletions
diff --git a/spec/frontend/branches/components/__snapshots__/delete_branch_modal_spec.js.snap b/spec/frontend/branches/components/__snapshots__/delete_branch_modal_spec.js.snap deleted file mode 100644 index 187ba4ba7f9..00000000000 --- a/spec/frontend/branches/components/__snapshots__/delete_branch_modal_spec.js.snap +++ /dev/null @@ -1,67 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Delete branch modal Deleting a protected branch (for owner or maintainer) renders the modal correctly 1`] = ` -"<div visible=\\"visible\\"> - <gl-alert-stub title=\\"\\" dismisslabel=\\"Dismiss\\" variant=\\"danger\\" primarybuttonlink=\\"\\" primarybuttontext=\\"\\" secondarybuttonlink=\\"\\" secondarybuttontext=\\"\\" class=\\"gl-mb-5\\"> - <div data-testid=\\"modal-message\\"> - <gl-sprintf-stub message=\\"You're about to permanently delete the protected branch %{strongStart}test_modal.%{strongEnd}\\"></gl-sprintf-stub> - <p class=\\"gl-mb-0 gl-mt-4\\"> - This branch hasn’t been merged into default. To avoid data loss, consider merging this branch before deleting it. - </p> - </div> - </gl-alert-stub> - <form action=\\"/path/to/branch\\" method=\\"post\\"> - <div class=\\"gl-mt-4\\"> - <p> - <gl-sprintf-stub message=\\"Once you confirm and press %{strongStart}Yes, delete protected branch,%{strongEnd} it cannot be undone or recovered.\\"></gl-sprintf-stub> - </p> - <p> - <gl-sprintf-stub message=\\"Please type the following to confirm:\\"></gl-sprintf-stub> <code class=\\"gl-white-space-pre-wrap\\"> test_modal </code> - <b-form-input-stub name=\\"delete_branch_input\\" value=\\"\\" autocomplete=\\"off\\" debounce=\\"0\\" type=\\"text\\" aria-labelledby=\\"input-label\\" class=\\"gl-form-input gl-mt-4\\"></b-form-input-stub> - </p> - </div> <input type=\\"hidden\\" name=\\"_method\\" value=\\"delete\\"> <input type=\\"hidden\\" name=\\"authenticity_token\\"> - </form> - <div class=\\"gl-display-flex gl-flex-direction-row gl-justify-content-end gl-flex-wrap gl-m-0\\"> - <b-button-stub size=\\"md\\" variant=\\"default\\" type=\\"button\\" tag=\\"button\\" class=\\"gl-button\\"> - <!----> - <!----> <span class=\\"gl-button-text\\"> - Cancel, keep branch - </span></b-button-stub> - <div class=\\"gl-mr-3\\"></div> - <b-button-stub disabled=\\"true\\" size=\\"md\\" variant=\\"danger\\" type=\\"button\\" tag=\\"button\\" data-qa-selector=\\"delete_branch_confirmation_button\\" data-testid=\\"delete_branch_confirmation_button\\" class=\\"gl-button\\"> - <!----> - <!----> <span class=\\"gl-button-text\\">Yes, delete protected branch</span></b-button-stub> - </div> -</div>" -`; - -exports[`Delete branch modal Deleting a regular branch renders the modal correctly 1`] = ` -"<div visible=\\"visible\\"> - <gl-alert-stub title=\\"\\" dismisslabel=\\"Dismiss\\" variant=\\"danger\\" primarybuttonlink=\\"\\" primarybuttontext=\\"\\" secondarybuttonlink=\\"\\" secondarybuttontext=\\"\\" class=\\"gl-mb-5\\"> - <div data-testid=\\"modal-message\\"> - <gl-sprintf-stub message=\\"You're about to permanently delete the branch %{strongStart}test_modal.%{strongEnd}\\"></gl-sprintf-stub> - <p class=\\"gl-mb-0 gl-mt-4\\"> - This branch hasn’t been merged into default. To avoid data loss, consider merging this branch before deleting it. - </p> - </div> - </gl-alert-stub> - <form action=\\"/path/to/branch\\" method=\\"post\\"> - <div> - <p class=\\"gl-mt-4\\"> - <gl-sprintf-stub message=\\"Deleting the %{strongStart}test_modal%{strongEnd} branch cannot be undone. Are you sure?\\"></gl-sprintf-stub> - </p> - </div> <input type=\\"hidden\\" name=\\"_method\\" value=\\"delete\\"> <input type=\\"hidden\\" name=\\"authenticity_token\\"> - </form> - <div class=\\"gl-display-flex gl-flex-direction-row gl-justify-content-end gl-flex-wrap gl-m-0\\"> - <b-button-stub size=\\"md\\" variant=\\"default\\" type=\\"button\\" tag=\\"button\\" class=\\"gl-button\\"> - <!----> - <!----> <span class=\\"gl-button-text\\"> - Cancel, keep branch - </span></b-button-stub> - <div class=\\"gl-mr-3\\"></div> - <b-button-stub size=\\"md\\" variant=\\"danger\\" type=\\"button\\" tag=\\"button\\" data-qa-selector=\\"delete_branch_confirmation_button\\" data-testid=\\"delete_branch_confirmation_button\\" class=\\"gl-button\\"> - <!----> - <!----> <span class=\\"gl-button-text\\">Yes, delete branch</span></b-button-stub> - </div> -</div>" -`; diff --git a/spec/frontend/branches/components/delete_branch_button_spec.js b/spec/frontend/branches/components/delete_branch_button_spec.js index 515c8f0ec6f..acbc83a9bdc 100644 --- a/spec/frontend/branches/components/delete_branch_button_spec.js +++ b/spec/frontend/branches/components/delete_branch_button_spec.js @@ -29,7 +29,7 @@ describe('Delete branch button', () => { wrapper.destroy(); }); - it('renders the button with correct tooltip, style, and icon', () => { + it('renders the button with default tooltip, style, and icon', () => { createComponent(); expect(findDeleteButton().attributes()).toMatchObject({ @@ -42,7 +42,20 @@ describe('Delete branch button', () => { it('renders a different tooltip for a protected branch', () => { createComponent({ isProtectedBranch: true }); - expect(findDeleteButton().attributes('title')).toBe('Delete protected branch'); + expect(findDeleteButton().attributes()).toMatchObject({ + title: 'Delete protected branch', + variant: 'danger', + icon: 'remove', + }); + }); + + it('renders a different protected tooltip when it is both protected and disabled', () => { + createComponent({ isProtectedBranch: true, disabled: true }); + + expect(findDeleteButton().attributes()).toMatchObject({ + title: 'Only a project maintainer or owner can delete a protected branch', + variant: 'default', + }); }); it('emits the data to eventHub when button is clicked', () => { @@ -63,14 +76,21 @@ describe('Delete branch button', () => { it('does not disable the button by default when mounted', () => { createComponent(); - expect(findDeleteButton().attributes('disabled')).not.toBe('true'); + expect(findDeleteButton().attributes()).toMatchObject({ + title: 'Delete branch', + variant: 'danger', + }); }); // Used for unallowed users and for the default branch. it('disables the button when mounted for a disabled modal', () => { - createComponent({ disabled: true }); + createComponent({ disabled: true, tooltip: 'The default branch cannot be deleted' }); - expect(findDeleteButton().attributes('disabled')).toBe('true'); + expect(findDeleteButton().attributes()).toMatchObject({ + title: 'The default branch cannot be deleted', + disabled: 'true', + variant: 'default', + }); }); }); }); diff --git a/spec/frontend/branches/components/delete_branch_modal_spec.js b/spec/frontend/branches/components/delete_branch_modal_spec.js index 62110af7e84..0c6111bda9e 100644 --- a/spec/frontend/branches/components/delete_branch_modal_spec.js +++ b/spec/frontend/branches/components/delete_branch_modal_spec.js @@ -1,86 +1,157 @@ -import { GlButton, GlModal, GlFormInput } from '@gitlab/ui'; +import { GlButton, GlModal, GlFormInput, GlSprintf } from '@gitlab/ui'; import { shallowMount } from '@vue/test-utils'; import { stubComponent } from 'helpers/stub_component'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import DeleteBranchModal from '~/branches/components/delete_branch_modal.vue'; +import eventHub from '~/branches/event_hub'; let wrapper; const branchName = 'test_modal'; +const defaultBranchName = 'default'; +const deletePath = '/path/to/branch'; +const merged = false; +const isProtectedBranch = false; const createComponent = (data = {}) => { - wrapper = shallowMount(DeleteBranchModal, { - data() { - return { - branchName, - deletePath: '/path/to/branch', - defaultBranchName: 'default', - ...data, - }; - }, - attrs: { - visible: true, - }, - stubs: { - GlModal: stubComponent(GlModal, { - template: - '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>', - }), - GlButton, - GlFormInput, - }, - }); + wrapper = extendedWrapper( + shallowMount(DeleteBranchModal, { + data() { + return { + branchName, + deletePath, + defaultBranchName, + merged, + isProtectedBranch, + ...data, + }; + }, + stubs: { + GlModal: stubComponent(GlModal, { + template: + '<div><slot name="modal-title"></slot><slot></slot><slot name="modal-footer"></slot></div>', + }), + GlButton, + GlFormInput, + GlSprintf, + }, + }), + ); }; -const findDeleteButton = () => wrapper.find('[data-testid="delete_branch_confirmation_button"]'); +const findModal = () => wrapper.findComponent(GlModal); +const findModalMessage = () => wrapper.findByTestId('modal-message'); +const findDeleteButton = () => wrapper.findByTestId('delete-branch-confirmation-button'); +const findCancelButton = () => wrapper.findByTestId('delete-branch-cancel-button'); const findFormInput = () => wrapper.findComponent(GlFormInput); +const findForm = () => wrapper.find('form'); describe('Delete branch modal', () => { + const expectedUnmergedWarning = + 'This branch hasn’t been merged into default. To avoid data loss, consider merging this branch before deleting it.'; + afterEach(() => { wrapper.destroy(); }); describe('Deleting a regular branch', () => { + const expectedTitle = 'Delete branch. Are you ABSOLUTELY SURE?'; + const expectedWarning = "You're about to permanently delete the branch test_modal."; + const expectedMessage = `${expectedWarning} ${expectedUnmergedWarning}`; + beforeEach(() => { createComponent(); }); it('renders the modal correctly', () => { - expect(wrapper.html()).toMatchSnapshot(); + expect(findModal().props('title')).toBe(expectedTitle); + expect(findModalMessage().text()).toMatchInterpolatedText(expectedMessage); + expect(findCancelButton().text()).toBe('Cancel, keep branch'); + expect(findDeleteButton().text()).toBe('Yes, delete branch'); + expect(findForm().attributes('action')).toBe(deletePath); }); - it('submits the form when clicked', () => { + it('submits the form when the delete button is clicked', () => { const submitFormSpy = jest.spyOn(wrapper.vm.$refs.form, 'submit'); - return wrapper.vm.$nextTick().then(() => { - findDeleteButton().trigger('click'); + findDeleteButton().trigger('click'); + + expect(findForm().attributes('action')).toBe(deletePath); + expect(submitFormSpy).toHaveBeenCalled(); + }); + + it('calls show on the modal when a `openModal` event is received through the event hub', async () => { + const showSpy = jest.spyOn(wrapper.vm.$refs.modal, 'show'); - expect(submitFormSpy).toHaveBeenCalled(); + eventHub.$emit('openModal', { + isProtectedBranch, + branchName, + defaultBranchName, + deletePath, + merged, }); + + expect(showSpy).toHaveBeenCalled(); + }); + + it('calls hide on the modal when cancel button is clicked', () => { + const closeModalSpy = jest.spyOn(wrapper.vm.$refs.modal, 'hide'); + + findCancelButton().trigger('click'); + + expect(closeModalSpy).toHaveBeenCalled(); }); }); describe('Deleting a protected branch (for owner or maintainer)', () => { + const expectedTitleProtected = 'Delete protected branch. Are you ABSOLUTELY SURE?'; + const expectedWarningProtected = + "You're about to permanently delete the protected branch test_modal."; + const expectedMessageProtected = `${expectedWarningProtected} ${expectedUnmergedWarning}`; + const expectedConfirmationText = + 'Once you confirm and press Yes, delete protected branch, it cannot be undone or recovered. Please type the following to confirm: test_modal'; + beforeEach(() => { createComponent({ isProtectedBranch: true }); }); - it('renders the modal correctly', () => { - expect(wrapper.html()).toMatchSnapshot(); + describe('rendering the modal correctly for a protected branch', () => { + it('sets the modal title for a protected branch', () => { + expect(findModal().props('title')).toBe(expectedTitleProtected); + }); + + it('renders the correct text in the modal message', () => { + expect(findModalMessage().text()).toMatchInterpolatedText(expectedMessageProtected); + }); + + it('renders the protected branch name confirmation form with expected text and action', () => { + expect(findForm().text()).toMatchInterpolatedText(expectedConfirmationText); + expect(findForm().attributes('action')).toBe(deletePath); + }); + + it('renders the buttons with the correct button text', () => { + expect(findCancelButton().text()).toBe('Cancel, keep branch'); + expect(findDeleteButton().text()).toBe('Yes, delete protected branch'); + }); }); - it('disables the delete button when branch name input is unconfirmed', () => { - expect(findDeleteButton().attributes('disabled')).toBe('true'); + it('opens with the delete button disabled and enables it when branch name is confirmed', async () => { + expect(findDeleteButton().props('disabled')).toBe(true); + + findFormInput().vm.$emit('input', branchName); + + await waitForPromises(); + + expect(findDeleteButton().props('disabled')).not.toBe(true); }); + }); + + describe('Deleting a merged branch', () => { + it('does not include the unmerged branch warning when merged is true', () => { + createComponent({ merged: true }); - it('enables the delete button when branch name input is confirmed', () => { - return wrapper.vm - .$nextTick() - .then(() => { - findFormInput().vm.$emit('input', branchName); - }) - .then(() => { - expect(findDeleteButton()).not.toBeDisabled(); - }); + expect(findModalMessage().html()).not.toContain(expectedUnmergedWarning); }); }); }); diff --git a/spec/frontend/pipelines/graph/linked_pipeline_spec.js b/spec/frontend/pipelines/graph/linked_pipeline_spec.js index 96f2cd1e371..c7d95526a0c 100644 --- a/spec/frontend/pipelines/graph/linked_pipeline_spec.js +++ b/spec/frontend/pipelines/graph/linked_pipeline_spec.js @@ -14,6 +14,7 @@ describe('Linked pipeline', () => { let wrapper; const findButton = () => wrapper.find(GlButton); + const findDownstreamPipelineTitle = () => wrapper.find('[data-testid="downstream-title"]'); const findPipelineLabel = () => wrapper.find('[data-testid="downstream-pipeline-label"]'); const findLinkedPipeline = () => wrapper.find({ ref: 'linkedPipeline' }); const findLoadingIcon = () => wrapper.find(GlLoadingIcon); @@ -119,6 +120,11 @@ describe('Linked pipeline', () => { expect(findPipelineLabel().exists()).toBe(true); }); + it('should have the name of the trigger job on the card when it is a child pipeline', () => { + createWrapper(downstreamProps); + expect(findDownstreamPipelineTitle().text()).toBe(mockPipeline.source_job.name); + }); + it('should display parent label when pipeline project id is the same as triggered_by pipeline project id', () => { createWrapper(upstreamProps); expect(findPipelineLabel().exists()).toBe(true); diff --git a/spec/frontend/security_configuration/components/redesigned_app_spec.js b/spec/frontend/security_configuration/components/redesigned_app_spec.js index a1da7e8584c..d55d8d183ce 100644 --- a/spec/frontend/security_configuration/components/redesigned_app_spec.js +++ b/spec/frontend/security_configuration/components/redesigned_app_spec.js @@ -36,6 +36,8 @@ describe('redesigned App component', () => { const findTabs = () => wrapper.findAllComponents(GlTab); const findByTestId = (id) => wrapper.findByTestId(id); const findFeatureCards = () => wrapper.findAllComponents(FeatureCard); + const findComplianceViewHistoryLink = () => findByTestId('compliance-view-history-link'); + const findSecurityViewHistoryLink = () => findByTestId('security-view-history-link'); const securityFeaturesMock = [ { @@ -103,6 +105,11 @@ describe('redesigned App component', () => { it('should not show latest pipeline link when latestPipelinePath is not defined', () => { expect(findByTestId('latest-pipeline-info').exists()).toBe(false); }); + + it('should not show configuration History Link when gitlabCiPresent & gitlabCiHistoryPath are not defined', () => { + expect(findComplianceViewHistoryLink().exists()).toBe(false); + expect(findSecurityViewHistoryLink().exists()).toBe(false); + }); }); describe('when given latestPipelinePath props', () => { @@ -134,4 +141,23 @@ describe('redesigned App component', () => { expect(latestPipelineInfoCompliance.find('a').attributes('href')).toBe('test/path'); }); }); + + describe('given gitlabCiPresent & gitlabCiHistoryPath props', () => { + beforeEach(() => { + createComponent({ + augmentedSecurityFeatures: securityFeaturesMock, + augmentedComplianceFeatures: complianceFeaturesMock, + gitlabCiPresent: true, + gitlabCiHistoryPath: 'test/historyPath', + }); + }); + + it('should show configuration History Link', () => { + expect(findComplianceViewHistoryLink().exists()).toBe(true); + expect(findSecurityViewHistoryLink().exists()).toBe(true); + + expect(findComplianceViewHistoryLink().attributes('href')).toBe('test/historyPath'); + expect(findSecurityViewHistoryLink().attributes('href')).toBe('test/historyPath'); + }); + }); }); diff --git a/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js b/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js new file mode 100644 index 00000000000..d58c87d66cb --- /dev/null +++ b/spec/frontend/vue_shared/components/security_reports/artifact_downloads/merge_request_artifact_download_spec.js @@ -0,0 +1,108 @@ +import { shallowMount } from '@vue/test-utils'; +import Vue from 'vue'; +import VueApollo from 'vue-apollo'; +import createMockApollo from 'helpers/mock_apollo_helper'; +import { + expectedDownloadDropdownProps, + securityReportMergeRequestDownloadPathsQueryResponse, +} from 'jest/vue_shared/security_reports/mock_data'; +import createFlash from '~/flash'; +import Component from '~/vue_shared/security_reports/components/artifact_downloads/merge_request_artifact_download.vue'; +import SecurityReportDownloadDropdown from '~/vue_shared/security_reports/components/security_report_download_dropdown.vue'; +import { + REPORT_TYPE_SAST, + REPORT_TYPE_SECRET_DETECTION, +} from '~/vue_shared/security_reports/constants'; +import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/queries/security_report_merge_request_download_paths.query.graphql'; + +jest.mock('~/flash'); + +describe('Merge request artifact Download', () => { + let wrapper; + + const defaultProps = { + reportTypes: [REPORT_TYPE_SAST, REPORT_TYPE_SECRET_DETECTION], + targetProjectFullPath: '/path', + mrIid: 123, + }; + + const createWrapper = ({ propsData, options }) => { + wrapper = shallowMount(Component, { + stubs: { + SecurityReportDownloadDropdown, + }, + propsData: { + ...defaultProps, + ...propsData, + }, + ...options, + }); + }; + + const pendingHandler = () => new Promise(() => {}); + const successHandler = () => + Promise.resolve({ data: securityReportMergeRequestDownloadPathsQueryResponse }); + const failureHandler = () => Promise.resolve({ errors: [{ message: 'some error' }] }); + const createMockApolloProvider = (handler) => { + Vue.use(VueApollo); + const requestHandlers = [[securityReportMergeRequestDownloadPathsQuery, handler]]; + + return createMockApollo(requestHandlers); + }; + + const findDownloadDropdown = () => wrapper.find(SecurityReportDownloadDropdown); + + afterEach(() => { + wrapper.destroy(); + }); + + describe('given the query is loading', () => { + beforeEach(() => { + createWrapper({ + options: { + apolloProvider: createMockApolloProvider(pendingHandler), + }, + }); + }); + + it('loading is true', () => { + expect(findDownloadDropdown().props('loading')).toBe(true); + }); + }); + + describe('given the query loads successfully', () => { + beforeEach(() => { + createWrapper({ + options: { + apolloProvider: createMockApolloProvider(successHandler), + }, + }); + }); + + it('renders the download dropdown', () => { + expect(findDownloadDropdown().props()).toEqual(expectedDownloadDropdownProps); + }); + }); + + describe('given the query fails', () => { + beforeEach(() => { + createWrapper({ + options: { + apolloProvider: createMockApolloProvider(failureHandler), + }, + }); + }); + + it('calls createFlash correctly', () => { + expect(createFlash).toHaveBeenCalledWith({ + message: Component.i18n.apiError, + captureError: true, + error: expect.any(Error), + }); + }); + + it('renders nothing', () => { + expect(findDownloadDropdown().props('artifacts')).toEqual([]); + }); + }); +}); diff --git a/spec/models/ci/build_spec.rb b/spec/models/ci/build_spec.rb index 352badf4133..ed8487cac88 100644 --- a/spec/models/ci/build_spec.rb +++ b/spec/models/ci/build_spec.rb @@ -2720,7 +2720,7 @@ RSpec.describe Ci::Build do let(:expected_variables) do predefined_variables.map { |variable| variable.fetch(:key) } + %w[YAML_VARIABLE CI_ENVIRONMENT_NAME CI_ENVIRONMENT_SLUG - CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL] + CI_ENVIRONMENT_TIER CI_ENVIRONMENT_ACTION CI_ENVIRONMENT_URL] end before do @@ -2820,7 +2820,8 @@ RSpec.describe Ci::Build do let(:environment_variables) do [ { key: 'CI_ENVIRONMENT_NAME', value: 'production', public: true, masked: false }, - { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false } + { key: 'CI_ENVIRONMENT_SLUG', value: 'prod-slug', public: true, masked: false }, + { key: 'CI_ENVIRONMENT_TIER', value: 'production', public: true, masked: false } ] end @@ -2829,6 +2830,7 @@ RSpec.describe Ci::Build do project: build.project, name: 'production', slug: 'prod-slug', + tier: 'production', external_url: '') end |