diff options
Diffstat (limited to 'spec/frontend/vue_mr_widget')
13 files changed, 218 insertions, 26 deletions
diff --git a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js index ef712ec23a6..c9dea4394f9 100644 --- a/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js +++ b/spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js @@ -61,9 +61,7 @@ describe('MRWidget approvals summary', () => { it('render message', () => { const names = toNounSeriesText(testRulesLeft()); - expect(wrapper.text()).toContain( - `Requires ${TEST_APPROVALS_LEFT} more approvals from ${names}.`, - ); + expect(wrapper.text()).toContain(`Requires ${TEST_APPROVALS_LEFT} approvals from ${names}.`); }); }); @@ -75,7 +73,9 @@ describe('MRWidget approvals summary', () => { }); it('renders message', () => { - expect(wrapper.text()).toContain(`Requires ${TEST_APPROVALS_LEFT} more approvals.`); + expect(wrapper.text()).toContain( + `Requires ${TEST_APPROVALS_LEFT} approvals from eligible users`, + ); }); }); diff --git a/spec/frontend/vue_mr_widget/components/extensions/actions_spec.js b/spec/frontend/vue_mr_widget/components/extensions/actions_spec.js new file mode 100644 index 00000000000..d5d779d7a34 --- /dev/null +++ b/spec/frontend/vue_mr_widget/components/extensions/actions_spec.js @@ -0,0 +1,35 @@ +import { GlButton, GlDropdownItem } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import Actions from '~/vue_merge_request_widget/components/extensions/actions.vue'; + +let wrapper; + +function factory(propsData = {}) { + wrapper = shallowMount(Actions, { + propsData: { ...propsData, widget: 'test' }, + }); +} + +describe('MR widget extension actions', () => { + afterEach(() => { + wrapper.destroy(); + }); + + describe('tertiaryButtons', () => { + it('renders buttons', () => { + factory({ + tertiaryButtons: [{ text: 'hello world', href: 'https://gitlab.com', target: '_blank' }], + }); + + expect(wrapper.findAllComponents(GlButton)).toHaveLength(1); + }); + + it('renders tertiary actions in dropdown', () => { + factory({ + tertiaryButtons: [{ text: 'hello world', href: 'https://gitlab.com', target: '_blank' }], + }); + + expect(wrapper.findAllComponents(GlDropdownItem)).toHaveLength(1); + }); + }); +}); diff --git a/spec/frontend/vue_mr_widget/components/extensions/index_spec.js b/spec/frontend/vue_mr_widget/components/extensions/index_spec.js index 8f6fe3cd37a..63df63a9b00 100644 --- a/spec/frontend/vue_mr_widget/components/extensions/index_spec.js +++ b/spec/frontend/vue_mr_widget/components/extensions/index_spec.js @@ -1,4 +1,7 @@ -import { registerExtension, extensions } from '~/vue_merge_request_widget/components/extensions'; +import { + registerExtension, + registeredExtensions, +} from '~/vue_merge_request_widget/components/extensions'; import ExtensionBase from '~/vue_merge_request_widget/components/extensions/base.vue'; describe('MR widget extension registering', () => { @@ -14,7 +17,7 @@ describe('MR widget extension registering', () => { }, }); - expect(extensions[0]).toEqual( + expect(registeredExtensions.extensions[0]).toEqual( expect.objectContaining({ extends: ExtensionBase, name: 'Test', diff --git a/spec/frontend/vue_mr_widget/components/extensions/status_icon_spec.js b/spec/frontend/vue_mr_widget/components/extensions/status_icon_spec.js new file mode 100644 index 00000000000..f3aa5bb774f --- /dev/null +++ b/spec/frontend/vue_mr_widget/components/extensions/status_icon_spec.js @@ -0,0 +1,36 @@ +import { GlIcon, GlLoadingIcon } from '@gitlab/ui'; +import { shallowMount } from '@vue/test-utils'; +import StatusIcon from '~/vue_merge_request_widget/components/extensions/status_icon.vue'; + +let wrapper; + +function factory(propsData = {}) { + wrapper = shallowMount(StatusIcon, { + propsData, + }); +} + +describe('MR widget extensions status icon', () => { + afterEach(() => { + wrapper.destroy(); + }); + + it('renders loading icon', () => { + factory({ name: 'test', isLoading: true, iconName: 'failed' }); + + expect(wrapper.findComponent(GlLoadingIcon).exists()).toBe(true); + }); + + it('renders status icon', () => { + factory({ name: 'test', isLoading: false, iconName: 'failed' }); + + expect(wrapper.findComponent(GlIcon).exists()).toBe(true); + expect(wrapper.findComponent(GlIcon).props('name')).toBe('status-failed'); + }); + + it('sets aria-label for status icon', () => { + factory({ name: 'test', isLoading: false, iconName: 'failed' }); + + expect(wrapper.findComponent(GlIcon).props('ariaLabel')).toBe('Failed test'); + }); +}); diff --git a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js index 5ec719b17d6..efe2bf75c3f 100644 --- a/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js +++ b/spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js @@ -1,5 +1,6 @@ import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; +import { extendedWrapper } from 'helpers/vue_test_utils_helper'; import axios from '~/lib/utils/axios_utils'; import ArtifactsApp from '~/vue_merge_request_widget/components/artifacts_list_app.vue'; import DeploymentList from '~/vue_merge_request_widget/components/deployment/deployment_list.vue'; @@ -12,12 +13,14 @@ describe('MrWidgetPipelineContainer', () => { let mock; const factory = (props = {}) => { - wrapper = mount(MrWidgetPipelineContainer, { - propsData: { - mr: { ...mockStore }, - ...props, - }, - }); + wrapper = extendedWrapper( + mount(MrWidgetPipelineContainer, { + propsData: { + mr: { ...mockStore }, + ...props, + }, + }), + ); }; beforeEach(() => { @@ -30,6 +33,7 @@ describe('MrWidgetPipelineContainer', () => { }); const findDeploymentList = () => wrapper.findComponent(DeploymentList); + const findCIErrorMessage = () => wrapper.findByTestId('ci-error-message'); describe('when pre merge', () => { beforeEach(() => { @@ -69,15 +73,21 @@ describe('MrWidgetPipelineContainer', () => { beforeEach(() => { factory({ isPostMerge: true, + mr: { + ...mockStore, + pipeline: {}, + ciStatus: undefined, + }, }); }); it('renders pipeline', () => { expect(wrapper.find(MrWidgetPipeline).exists()).toBe(true); + expect(findCIErrorMessage().exists()).toBe(false); expect(wrapper.find(MrWidgetPipeline).props()).toMatchObject({ pipeline: mockStore.mergePipeline, pipelineCoverageDelta: mockStore.pipelineCoverageDelta, - ciStatus: mockStore.ciStatus, + ciStatus: mockStore.mergePipeline.details.status.text, hasCi: mockStore.hasCI, sourceBranch: mockStore.targetBranch, sourceBranchLink: mockStore.targetBranch, @@ -92,7 +102,6 @@ describe('MrWidgetPipelineContainer', () => { targetBranch: 'Foo<script>alert("XSS")</script>', }, }); - expect(wrapper.find(MrWidgetPipeline).props().sourceBranchLink).toBe('Foo'); }); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js index b31a75f30d3..2ff94a547f4 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js @@ -1,4 +1,5 @@ import { shallowMount } from '@vue/test-utils'; +import { GlSprintf } from '@gitlab/ui'; import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue'; describe('Commits header component', () => { @@ -6,6 +7,9 @@ describe('Commits header component', () => { const createComponent = (props) => { wrapper = shallowMount(CommitsHeader, { + stubs: { + GlSprintf, + }, propsData: { isSquashEnabled: false, targetBranch: 'main', diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js index e41fb815c8d..f0fbb1d5851 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -45,6 +45,8 @@ const createTestMr = (customConfig) => { preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY, availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY], mergeImmediatelyDocsPath: 'path/to/merge/immediately/docs', + transitionStateMachine: (transition) => eventHub.$emit('StateMachineValueChanged', transition), + translateStateToMachine: () => this.transitionStateMachine(), }; Object.assign(mr, customConfig.mr); @@ -304,6 +306,9 @@ describe('ReadyToMerge', () => { setImmediate(() => { expect(wrapper.vm.isMakingRequest).toBeTruthy(); expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', { + transition: 'start-auto-merge', + }); const params = wrapper.vm.service.merge.mock.calls[0][0]; @@ -341,10 +346,15 @@ describe('ReadyToMerge', () => { it('should handle merge action accepted case', (done) => { createComponent(); + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); jest.spyOn(wrapper.vm.service, 'merge').mockReturnValue(returnPromise('success')); jest.spyOn(wrapper.vm, 'initiateMergePolling').mockImplementation(() => {}); wrapper.vm.handleMergeButtonClick(); + expect(eventHub.$emit).toHaveBeenCalledWith('StateMachineValueChanged', { + transition: 'start-merge', + }); + setImmediate(() => { expect(wrapper.vm.isMakingRequest).toBeTruthy(); expect(wrapper.vm.initiateMergePolling).toHaveBeenCalled(); diff --git a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js index 61e44140efc..be15e4df66d 100644 --- a/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js @@ -1,9 +1,9 @@ import Vue from 'vue'; -import createFlash from '~/flash'; import WorkInProgress from '~/vue_merge_request_widget/components/states/work_in_progress.vue'; +import toast from '~/vue_shared/plugins/global_toast'; import eventHub from '~/vue_merge_request_widget/event_hub'; -jest.mock('~/flash'); +jest.mock('~/vue_shared/plugins/global_toast'); const createComponent = () => { const Component = Vue.extend(WorkInProgress); @@ -63,10 +63,7 @@ describe('Wip', () => { setImmediate(() => { expect(vm.isMakingRequest).toBeTruthy(); expect(eventHub.$emit).toHaveBeenCalledWith('UpdateWidgetData', mrObj); - expect(createFlash).toHaveBeenCalledWith({ - message: 'Marked as ready. Merging is now allowed.', - type: 'notice', - }); + expect(toast).toHaveBeenCalledWith('Marked as ready. Merging is now allowed.'); done(); }); }); diff --git a/spec/frontend/vue_mr_widget/mock_data.js b/spec/frontend/vue_mr_widget/mock_data.js index f356f6fb5bf..34a741cf8f2 100644 --- a/spec/frontend/vue_mr_widget/mock_data.js +++ b/spec/frontend/vue_mr_widget/mock_data.js @@ -280,7 +280,7 @@ export default { merge_train_index: 1, security_reports_docs_path: 'security-reports-docs-path', sast_comparison_path: '/sast_comparison_path', - secret_scanning_comparison_path: '/secret_scanning_comparison_path', + secret_detection_comparison_path: '/secret_detection_comparison_path', gitpod_enabled: true, show_gitpod_button: true, gitpod_url: 'http://gitpod.localhost', diff --git a/spec/frontend/vue_mr_widget/mr_widget_how_to_merge_modal_spec.js b/spec/frontend/vue_mr_widget/mr_widget_how_to_merge_modal_spec.js index bd22183cbea..913d5860b48 100644 --- a/spec/frontend/vue_mr_widget/mr_widget_how_to_merge_modal_spec.js +++ b/spec/frontend/vue_mr_widget/mr_widget_how_to_merge_modal_spec.js @@ -8,11 +8,9 @@ describe('MRWidgetHowToMerge', () => { function mountComponent({ data = {}, props = {} } = {}) { wrapper = shallowMount(MrWidgetHowToMergeModal, { data() { - return { ...data }; - }, - propsData: { - ...props, + return data; }, + propsData: props, stubs: {}, }); } @@ -57,4 +55,16 @@ describe('MRWidgetHowToMerge', () => { mountComponent({ props: { isFork: true } }); expect(findInstructionsFields().at(0).text()).toContain('FETCH_HEAD'); }); + + it('escapes the target branch name shell-secure', () => { + mountComponent({ props: { targetBranch: '";echo$IFS"you_shouldnt_run_this' } }); + + expect(findInstructionsFields().at(1).text()).toContain('\'";echo$IFS"you_shouldnt_run_this\''); + }); + + it('escapes the source branch name shell-secure', () => { + mountComponent({ props: { sourceBranch: 'branch-of-$USER' } }); + + expect(findInstructionsFields().at(0).text()).toContain("'branch-of-$USER'"); + }); }); diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js index c50cf7cb076..5aba6982886 100644 --- a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js +++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js @@ -1,13 +1,19 @@ +import { GlBadge, GlLink, GlIcon } from '@gitlab/ui'; import { mount } from '@vue/test-utils'; import MockAdapter from 'axios-mock-adapter'; import Vue, { nextTick } from 'vue'; import VueApollo from 'vue-apollo'; import createMockApollo from 'helpers/mock_apollo_helper'; +import waitForPromises from 'helpers/wait_for_promises'; import { securityReportMergeRequestDownloadPathsQueryResponse } from 'jest/vue_shared/security_reports/mock_data'; import axios from '~/lib/utils/axios_utils'; import { setFaviconOverlay } from '~/lib/utils/favicon'; import notify from '~/lib/utils/notify'; import SmartInterval from '~/smart_interval'; +import { + registerExtension, + registeredExtensions, +} from '~/vue_merge_request_widget/components/extensions'; import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants'; import eventHub from '~/vue_merge_request_widget/event_hub'; import MrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue'; @@ -15,6 +21,7 @@ import { stateKey } from '~/vue_merge_request_widget/stores/state_maps'; import securityReportMergeRequestDownloadPathsQuery from '~/vue_shared/security_reports/graphql/queries/security_report_merge_request_download_paths.query.graphql'; import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data'; import mockData from './mock_data'; +import testExtension from './test_extension'; jest.mock('~/smart_interval'); @@ -879,4 +886,48 @@ describe('MrWidgetOptions', () => { }); }); }); + + describe('mock extension', () => { + beforeEach(() => { + registerExtension(testExtension); + + createComponent(); + }); + + afterEach(() => { + registeredExtensions.extensions = []; + }); + + it('renders collapsed data', async () => { + await waitForPromises(); + + expect(wrapper.text()).toContain('Test extension summary count: 1'); + }); + + it('renders full data', async () => { + await waitForPromises(); + + wrapper + .find('[data-testid="widget-extension"] [data-testid="toggle-button"]') + .trigger('click'); + + await Vue.nextTick(); + + const collapsedSection = wrapper.find('[data-testid="widget-extension-collapsed-section"]'); + expect(collapsedSection.exists()).toBe(true); + expect(collapsedSection.text()).toContain('Hello world'); + + // Renders icon in the row + expect(collapsedSection.find(GlIcon).exists()).toBe(true); + expect(collapsedSection.find(GlIcon).props('name')).toBe('status-failed'); + + // Renders badge in the row + expect(collapsedSection.find(GlBadge).exists()).toBe(true); + expect(collapsedSection.find(GlBadge).text()).toBe('Closed'); + + // Renders a link in the row + expect(collapsedSection.find(GlLink).exists()).toBe(true); + expect(collapsedSection.find(GlLink).text()).toBe('GitLab.com'); + }); + }); }); diff --git a/spec/frontend/vue_mr_widget/stores/mr_widget_store_spec.js b/spec/frontend/vue_mr_widget/stores/mr_widget_store_spec.js index bf0179aa425..febcfcd4019 100644 --- a/spec/frontend/vue_mr_widget/stores/mr_widget_store_spec.js +++ b/spec/frontend/vue_mr_widget/stores/mr_widget_store_spec.js @@ -162,7 +162,7 @@ describe('MergeRequestStore', () => { expect(store.securityReportsDocsPath).toBe('security-reports-docs-path'); }); - it.each(['sast_comparison_path', 'secret_scanning_comparison_path'])( + it.each(['sast_comparison_path', 'secret_detection_comparison_path'])( 'should set %s path', (property) => { // Ensure something is set in the mock data diff --git a/spec/frontend/vue_mr_widget/test_extension.js b/spec/frontend/vue_mr_widget/test_extension.js new file mode 100644 index 00000000000..a29a4d2fb46 --- /dev/null +++ b/spec/frontend/vue_mr_widget/test_extension.js @@ -0,0 +1,37 @@ +import { EXTENSION_ICONS } from '~/vue_merge_request_widget/constants'; + +export default { + name: 'WidgetTestExtension', + props: ['targetProjectFullPath'], + computed: { + summary({ count, targetProjectFullPath }) { + return `Test extension summary count: ${count} & ${targetProjectFullPath}`; + }, + statusIcon({ count }) { + return count > 0 ? EXTENSION_ICONS.warning : EXTENSION_ICONS.success; + }, + }, + methods: { + fetchCollapsedData({ targetProjectFullPath }) { + return Promise.resolve({ targetProjectFullPath, count: 1 }); + }, + fetchFullData() { + return Promise.resolve([ + { + id: 1, + text: 'Hello world', + icon: { + name: EXTENSION_ICONS.failed, + }, + badge: { + text: 'Closed', + }, + link: { + href: 'https://gitlab.com', + text: 'GitLab.com', + }, + }, + ]); + }, + }, +}; |