summaryrefslogtreecommitdiff
path: root/spec/frontend/vue_mr_widget
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/vue_mr_widget')
-rw-r--r--spec/frontend/vue_mr_widget/components/approvals/approvals_summary_spec.js8
-rw-r--r--spec/frontend/vue_mr_widget/components/extensions/actions_spec.js35
-rw-r--r--spec/frontend/vue_mr_widget/components/extensions/index_spec.js7
-rw-r--r--spec/frontend/vue_mr_widget/components/extensions/status_icon_spec.js36
-rw-r--r--spec/frontend/vue_mr_widget/components/mr_widget_pipeline_container_spec.js25
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_commits_header_spec.js4
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js10
-rw-r--r--spec/frontend/vue_mr_widget/components/states/mr_widget_wip_spec.js9
-rw-r--r--spec/frontend/vue_mr_widget/mock_data.js2
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_how_to_merge_modal_spec.js18
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_options_spec.js51
-rw-r--r--spec/frontend/vue_mr_widget/stores/mr_widget_store_spec.js2
-rw-r--r--spec/frontend/vue_mr_widget/test_extension.js37
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',
+ },
+ },
+ ]);
+ },
+ },
+};