summaryrefslogtreecommitdiff
path: root/spec/frontend/vue_mr_widget
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2020-03-02 18:07:42 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2020-03-02 18:07:42 +0000
commit7b52c7cb634ef7047d30b0337fe477bcdcedf41d (patch)
tree374ca9e908204488422046f10e340d1500780362 /spec/frontend/vue_mr_widget
parentb375c6c05fbd03aea33a9ee9f82e678bdaa8c3cc (diff)
downloadgitlab-ce-7b52c7cb634ef7047d30b0337fe477bcdcedf41d.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/frontend/vue_mr_widget')
-rw-r--r--spec/frontend/vue_mr_widget/mock_data.js318
-rw-r--r--spec/frontend/vue_mr_widget/mr_widget_options_spec.js834
2 files changed, 1152 insertions, 0 deletions
diff --git a/spec/frontend/vue_mr_widget/mock_data.js b/spec/frontend/vue_mr_widget/mock_data.js
new file mode 100644
index 00000000000..d11756d712a
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/mock_data.js
@@ -0,0 +1,318 @@
+import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
+
+export default {
+ id: 132,
+ iid: 22,
+ assignee_id: null,
+ author_id: 1,
+ description: '',
+ lock_version: null,
+ milestone_id: null,
+ position: 0,
+ state: 'merged',
+ title: 'Update README.md',
+ updated_by_id: null,
+ created_at: '2017-04-07T12:27:26.718Z',
+ updated_at: '2017-04-07T15:39:25.852Z',
+ time_estimate: 0,
+ total_time_spent: 0,
+ human_access: 'Maintainer',
+ human_time_estimate: null,
+ human_total_time_spent: null,
+ in_progress_merge_commit_sha: null,
+ merge_commit_sha: '53027d060246c8f47e4a9310fb332aa52f221775',
+ short_merge_commit_sha: '53027d06',
+ merge_error: null,
+ merge_params: {
+ force_remove_source_branch: null,
+ },
+ merge_status: 'can_be_merged',
+ merge_user_id: null,
+ pipelines_empty_svg_path: '/path/to/svg',
+ source_branch: 'daaaa',
+ source_branch_link: 'daaaa',
+ source_project_id: 19,
+ source_project_full_path: '/group1/project1',
+ target_branch: 'master',
+ target_project_id: 19,
+ target_project_full_path: '/group2/project2',
+ merge_request_add_ci_config_path: '/group2/project2/new/pipeline',
+ metrics: {
+ merged_by: {
+ name: 'Administrator',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ merged_at: '2017-04-07T15:39:25.696Z',
+ closed_by: null,
+ closed_at: null,
+ },
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ merge_user: null,
+ diff_head_sha: '104096c51715e12e7ae41f9333e9fa35b73f385d',
+ diff_head_commit_short_id: '104096c5',
+ default_merge_commit_message:
+ "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
+ pipeline: {
+ id: 172,
+ user: {
+ name: 'Administrator',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ active: false,
+ coverage: '92.16',
+ path: '/root/acets-app/pipelines/172',
+ details: {
+ status: {
+ icon: 'status_success',
+ favicon: 'favicon_status_success',
+ text: 'passed',
+ label: 'passed',
+ group: 'success',
+ has_details: true,
+ details_path: '/root/acets-app/pipelines/172',
+ },
+ duration: null,
+ finished_at: '2017-04-07T14:00:14.256Z',
+ stages: [
+ {
+ name: 'build',
+ title: 'build: failed',
+ status: {
+ icon: 'status_failed',
+ favicon: 'favicon_status_failed',
+ text: 'failed',
+ label: 'failed',
+ group: 'failed',
+ has_details: true,
+ details_path: '/root/acets-app/pipelines/172#build',
+ },
+ path: '/root/acets-app/pipelines/172#build',
+ dropdown_path: '/root/acets-app/pipelines/172/stage.json?stage=build',
+ },
+ {
+ name: 'review',
+ title: 'review: skipped',
+ status: {
+ icon: 'status_skipped',
+ favicon: 'favicon_status_skipped',
+ text: 'skipped',
+ label: 'skipped',
+ group: 'skipped',
+ has_details: true,
+ details_path: '/root/acets-app/pipelines/172#review',
+ },
+ path: '/root/acets-app/pipelines/172#review',
+ dropdown_path: '/root/acets-app/pipelines/172/stage.json?stage=review',
+ },
+ ],
+ artifacts: [],
+ manual_actions: [
+ {
+ name: 'stop_review',
+ path: '/root/acets-app/builds/1427/play',
+ playable: false,
+ },
+ ],
+ },
+ flags: {
+ latest: false,
+ triggered: false,
+ stuck: false,
+ yaml_errors: false,
+ retryable: true,
+ cancelable: false,
+ merge_request_pipeline: false,
+ detached_merge_request_pipeline: true,
+ },
+ ref: {
+ name: 'daaaa',
+ path: '/root/acets-app/tree/daaaa',
+ tag: false,
+ branch: true,
+ },
+ merge_request: {
+ iid: 1,
+ path: '/root/detached-merge-request-pipelines/-/merge_requests/1',
+ title: 'Update README.md',
+ source_branch: 'feature-1',
+ source_branch_path: '/root/detached-merge-request-pipelines/branches/feature-1',
+ target_branch: 'master',
+ target_branch_path: '/root/detached-merge-request-pipelines/branches/master',
+ },
+ commit: {
+ id: '104096c51715e12e7ae41f9333e9fa35b73f385d',
+ short_id: '104096c5',
+ title: 'Update README.md',
+ created_at: '2017-04-07T15:27:18.000+03:00',
+ parent_ids: ['2396536178668d8930c29d904e53bd4d06228b32'],
+ message: 'Update README.md',
+ author_name: 'Administrator',
+ author_email: 'admin@example.com',
+ authored_date: '2017-04-07T15:27:18.000+03:00',
+ committer_name: 'Administrator',
+ committer_email: 'admin@example.com',
+ committed_date: '2017-04-07T15:27:18.000+03:00',
+ author: {
+ name: 'Administrator',
+ username: 'root',
+ id: 1,
+ state: 'active',
+ avatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ web_url: 'http://localhost:3000/root',
+ },
+ author_gravatar_url:
+ 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
+ commit_url:
+ 'http://localhost:3000/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d',
+ commit_path: '/root/acets-app/commit/104096c51715e12e7ae41f9333e9fa35b73f385d',
+ },
+ retry_path: '/root/acets-app/pipelines/172/retry',
+ created_at: '2017-04-07T12:27:19.520Z',
+ updated_at: '2017-04-07T15:28:44.800Z',
+ },
+ pipelineCoverageDelta: '15.25',
+ work_in_progress: false,
+ source_branch_exists: false,
+ mergeable_discussions_state: true,
+ conflicts_can_be_resolved_in_ui: false,
+ branch_missing: true,
+ commits_count: 1,
+ has_conflicts: false,
+ can_be_merged: true,
+ has_ci: true,
+ ci_status: 'success',
+ pipeline_status_path: '/root/acets-app/-/merge_requests/22/pipeline_status',
+ issues_links: {
+ closing: '',
+ mentioned_but_not_closing: '',
+ },
+ current_user: {
+ can_resolve_conflicts: true,
+ can_remove_source_branch: false,
+ can_revert_on_current_merge_request: true,
+ can_cherry_pick_on_current_merge_request: true,
+ },
+ target_branch_path: '/root/acets-app/branches/master',
+ source_branch_path: '/root/acets-app/branches/daaaa',
+ conflict_resolution_ui_path: '/root/acets-app/-/merge_requests/22/conflicts',
+ remove_wip_path: '/root/acets-app/-/merge_requests/22/remove_wip',
+ cancel_auto_merge_path: '/root/acets-app/-/merge_requests/22/cancel_auto_merge',
+ create_issue_to_resolve_discussions_path:
+ '/root/acets-app/-/issues/new?merge_request_to_resolve_discussions_of=22',
+ merge_path: '/root/acets-app/-/merge_requests/22/merge',
+ cherry_pick_in_fork_path:
+ '/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+revert+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
+ revert_in_fork_path:
+ '/root/acets-app/forks?continue%5Bnotice%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+has+been+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.+Try+to+cherry-pick+this+commit+again.&continue%5Bnotice_now%5D=You%27re+not+allowed+to+make+changes+to+this+project+directly.+A+fork+of+this+project+is+being+created+that+you+can+make+changes+in%2C+so+you+can+submit+a+merge+request.&continue%5Bto%5D=%2Froot%2Facets-app%2Fmerge_requests%2F22&namespace_key=1',
+ email_patches_path: '/root/acets-app/-/merge_requests/22.patch',
+ plain_diff_path: '/root/acets-app/-/merge_requests/22.diff',
+ merge_request_basic_path: '/root/acets-app/-/merge_requests/22.json?serializer=basic',
+ merge_request_widget_path: '/root/acets-app/-/merge_requests/22/widget.json',
+ merge_request_cached_widget_path: '/cached.json',
+ merge_check_path: '/root/acets-app/-/merge_requests/22/merge_check',
+ ci_environments_status_url: '/root/acets-app/-/merge_requests/22/ci_environments_status',
+ project_archived: false,
+ default_merge_commit_message_with_description:
+ "Merge branch 'daaaa' into 'master'\n\nUpdate README.md\n\nSee merge request !22",
+ default_squash_commit_message: 'Test squash commit message',
+ diverged_commits_count: 0,
+ only_allow_merge_if_pipeline_succeeds: false,
+ commit_change_content_path: '/root/acets-app/-/merge_requests/22/commit_change_content',
+ merge_commit_path:
+ 'http://localhost:3000/root/acets-app/commit/53027d060246c8f47e4a9310fb332aa52f221775',
+ troubleshooting_docs_path: 'help',
+ merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md',
+ merge_train_when_pipeline_succeeds_docs_path:
+ '/help/ci/merge_request_pipelines/pipelines_for_merged_results/merge_trains/#startadd-to-merge-train-when-pipeline-succeeds',
+ squash: true,
+ visual_review_app_available: true,
+ merge_trains_enabled: true,
+ merge_trains_count: 3,
+ merge_train_index: 1,
+};
+
+export const mockStore = {
+ pipeline: {
+ id: 0,
+ details: {
+ status: {
+ details_path: '/root/review-app-tester/pipelines/66',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2. png',
+ group: 'success-with-warnings',
+ has_details: true,
+ icon: 'status_warning',
+ illustration: null,
+ label: 'passed with warnings',
+ text: 'passed',
+ tooltip: 'passed',
+ },
+ },
+ flags: {},
+ ref: {},
+ },
+ mergePipeline: {
+ id: 1,
+ details: {
+ status: {
+ details_path: '/root/review-app-tester/pipelines/66',
+ favicon:
+ '/assets/ci_favicons/favicon_status_success-8451333011eee8ce9f2ab25dc487fe24a8758c694827a582f17f42b0a90446a2. png',
+ group: 'success-with-warnings',
+ has_details: true,
+ icon: 'status_warning',
+ illustration: null,
+ label: 'passed with warnings',
+ text: 'passed',
+ tooltip: 'passed',
+ },
+ },
+ flags: {},
+ ref: {},
+ },
+ targetBranch: 'target-branch',
+ sourceBranch: 'source-branch',
+ sourceBranchLink: 'source-branch-link',
+ deployments: [
+ {
+ id: 0,
+ name: 'bogus',
+ external_url: 'https://fake.com',
+ external_url_formatted: 'https://fake.com',
+ status: SUCCESS,
+ },
+ {
+ id: 1,
+ name: 'bogus-docs',
+ external_url: 'https://fake.com',
+ external_url_formatted: 'https://fake.com',
+ status: SUCCESS,
+ },
+ ],
+ postMergeDeployments: [
+ { id: 0, name: 'prod', status: SUCCESS },
+ { id: 1, name: 'prod-docs', status: SUCCESS },
+ ],
+ troubleshootingDocsPath: 'troubleshooting-docs-path',
+ ciStatus: 'ci-status',
+ hasCI: true,
+ exposedArtifactsPath: 'exposed_artifacts.json',
+};
diff --git a/spec/frontend/vue_mr_widget/mr_widget_options_spec.js b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
new file mode 100644
index 00000000000..5edf41b1ec6
--- /dev/null
+++ b/spec/frontend/vue_mr_widget/mr_widget_options_spec.js
@@ -0,0 +1,834 @@
+import Vue from 'vue';
+import MockAdapter from 'axios-mock-adapter';
+import mountComponent from 'helpers/vue_mount_component_helper';
+import axios from '~/lib/utils/axios_utils';
+import mrWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
+import eventHub from '~/vue_merge_request_widget/event_hub';
+import notify from '~/lib/utils/notify';
+import SmartInterval from '~/smart_interval';
+import { stateKey } from '~/vue_merge_request_widget/stores/state_maps';
+import mockData from './mock_data';
+import { faviconDataUrl, overlayDataUrl } from '../lib/utils/mock_data';
+import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
+
+jest.mock('~/smart_interval');
+
+const returnPromise = data =>
+ new Promise(resolve => {
+ resolve({
+ data,
+ });
+ });
+
+describe('mrWidgetOptions', () => {
+ let vm;
+ let mock;
+ let MrWidgetOptions;
+
+ const COLLABORATION_MESSAGE = 'Allows commits from members who can merge to the target branch';
+
+ beforeEach(() => {
+ // Prevent component mounting
+ delete mrWidgetOptions.el;
+
+ gl.mrWidgetData = { ...mockData };
+ gon.features = { asyncMrWidget: true };
+
+ mock = new MockAdapter(axios);
+ mock.onGet(mockData.merge_request_widget_path).reply(() => [200, { ...mockData }]);
+ mock.onGet(mockData.merge_request_cached_widget_path).reply(() => [200, { ...mockData }]);
+
+ MrWidgetOptions = Vue.extend(mrWidgetOptions);
+ });
+
+ afterEach(() => {
+ mock.restore();
+ vm.$destroy();
+ vm = null;
+
+ gl.mrWidgetData = {};
+ gon.features = {};
+ });
+
+ const createComponent = () => {
+ if (vm) {
+ vm.$destroy();
+ }
+
+ vm = mountComponent(MrWidgetOptions, {
+ mrData: { ...mockData },
+ });
+
+ return axios.waitForAll();
+ };
+
+ describe('default', () => {
+ beforeEach(() => {
+ return createComponent();
+ });
+
+ describe('data', () => {
+ it('should instantiate Store and Service', () => {
+ expect(vm.mr).toBeDefined();
+ expect(vm.service).toBeDefined();
+ });
+ });
+
+ describe('computed', () => {
+ describe('componentName', () => {
+ it('should return merged component', () => {
+ expect(vm.componentName).toEqual('mr-widget-merged');
+ });
+
+ it('should return conflicts component', () => {
+ vm.mr.state = 'conflicts';
+
+ expect(vm.componentName).toEqual('mr-widget-conflicts');
+ });
+ });
+
+ describe('shouldRenderMergeHelp', () => {
+ it('should return false for the initial merged state', () => {
+ expect(vm.shouldRenderMergeHelp).toBeFalsy();
+ });
+
+ it('should return true for a state which requires help widget', () => {
+ vm.mr.state = 'conflicts';
+
+ expect(vm.shouldRenderMergeHelp).toBeTruthy();
+ });
+ });
+
+ describe('shouldRenderPipelines', () => {
+ it('should return true when hasCI is true', () => {
+ vm.mr.hasCI = true;
+
+ expect(vm.shouldRenderPipelines).toBeTruthy();
+ });
+
+ it('should return false when hasCI is false', () => {
+ vm.mr.hasCI = false;
+
+ expect(vm.shouldRenderPipelines).toBeFalsy();
+ });
+ });
+
+ describe('shouldRenderRelatedLinks', () => {
+ it('should return false for the initial data', () => {
+ expect(vm.shouldRenderRelatedLinks).toBeFalsy();
+ });
+
+ it('should return true if there is relatedLinks in MR', () => {
+ Vue.set(vm.mr, 'relatedLinks', {});
+
+ expect(vm.shouldRenderRelatedLinks).toBeTruthy();
+ });
+ });
+
+ describe('shouldRenderSourceBranchRemovalStatus', () => {
+ beforeEach(() => {
+ vm.mr.state = 'readyToMerge';
+ });
+
+ it('should return true when cannot remove source branch and branch will be removed', () => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = true;
+
+ expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(true);
+ });
+
+ it('should return false when can remove source branch and branch will be removed', () => {
+ vm.mr.canRemoveSourceBranch = true;
+ vm.mr.shouldRemoveSourceBranch = true;
+
+ expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+ });
+
+ it('should return false when cannot remove source branch and branch will not be removed', () => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = false;
+
+ expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+ });
+
+ it('should return false when in merged state', () => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = true;
+ vm.mr.state = 'merged';
+
+ expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+ });
+
+ it('should return false when in nothing to merge state', () => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = true;
+ vm.mr.state = 'nothingToMerge';
+
+ expect(vm.shouldRenderSourceBranchRemovalStatus).toEqual(false);
+ });
+ });
+
+ describe('shouldRenderCollaborationStatus', () => {
+ describe('when collaboration is allowed', () => {
+ beforeEach(() => {
+ vm.mr.allowCollaboration = true;
+ });
+
+ describe('when merge request is opened', () => {
+ beforeEach(done => {
+ vm.mr.isOpen = true;
+ vm.$nextTick(done);
+ });
+
+ it('should render collaboration status', () => {
+ expect(vm.$el.textContent).toContain(COLLABORATION_MESSAGE);
+ });
+ });
+
+ describe('when merge request is not opened', () => {
+ beforeEach(done => {
+ vm.mr.isOpen = false;
+ vm.$nextTick(done);
+ });
+
+ it('should not render collaboration status', () => {
+ expect(vm.$el.textContent).not.toContain(COLLABORATION_MESSAGE);
+ });
+ });
+ });
+
+ describe('when collaboration is not allowed', () => {
+ beforeEach(() => {
+ vm.mr.allowCollaboration = false;
+ });
+
+ describe('when merge request is opened', () => {
+ beforeEach(done => {
+ vm.mr.isOpen = true;
+ vm.$nextTick(done);
+ });
+
+ it('should not render collaboration status', () => {
+ expect(vm.$el.textContent).not.toContain(COLLABORATION_MESSAGE);
+ });
+ });
+ });
+ });
+
+ describe('showMergePipelineForkWarning', () => {
+ describe('when the source project and target project are the same', () => {
+ beforeEach(done => {
+ Vue.set(vm.mr, 'mergePipelinesEnabled', true);
+ Vue.set(vm.mr, 'sourceProjectId', 1);
+ Vue.set(vm.mr, 'targetProjectId', 1);
+ vm.$nextTick(done);
+ });
+
+ it('should be false', () => {
+ expect(vm.showMergePipelineForkWarning).toEqual(false);
+ });
+ });
+
+ describe('when merge pipelines are not enabled', () => {
+ beforeEach(done => {
+ Vue.set(vm.mr, 'mergePipelinesEnabled', false);
+ Vue.set(vm.mr, 'sourceProjectId', 1);
+ Vue.set(vm.mr, 'targetProjectId', 2);
+ vm.$nextTick(done);
+ });
+
+ it('should be false', () => {
+ expect(vm.showMergePipelineForkWarning).toEqual(false);
+ });
+ });
+
+ describe('when merge pipelines are enabled _and_ the source project and target project are different', () => {
+ beforeEach(done => {
+ Vue.set(vm.mr, 'mergePipelinesEnabled', true);
+ Vue.set(vm.mr, 'sourceProjectId', 1);
+ Vue.set(vm.mr, 'targetProjectId', 2);
+ vm.$nextTick(done);
+ });
+
+ it('should be true', () => {
+ expect(vm.showMergePipelineForkWarning).toEqual(true);
+ });
+ });
+ });
+ });
+
+ describe('methods', () => {
+ describe('checkStatus', () => {
+ it('should tell service to check status', () => {
+ jest.spyOn(vm.service, 'checkStatus').mockReturnValue(returnPromise(mockData));
+ jest.spyOn(vm.mr, 'setData').mockImplementation(() => {});
+ jest.spyOn(vm, 'handleNotification').mockImplementation(() => {});
+
+ let isCbExecuted = false;
+ const cb = () => {
+ isCbExecuted = true;
+ };
+
+ vm.checkStatus(cb);
+
+ return vm.$nextTick().then(() => {
+ expect(vm.service.checkStatus).toHaveBeenCalled();
+ expect(vm.mr.setData).toHaveBeenCalled();
+ expect(vm.handleNotification).toHaveBeenCalledWith(mockData);
+ expect(isCbExecuted).toBeTruthy();
+ });
+ });
+ });
+
+ describe('initPolling', () => {
+ it('should call SmartInterval', () => {
+ vm.initPolling();
+
+ expect(SmartInterval).toHaveBeenCalledWith(
+ expect.objectContaining({
+ callback: vm.checkStatus,
+ }),
+ );
+ });
+ });
+
+ describe('initDeploymentsPolling', () => {
+ it('should call SmartInterval', () => {
+ vm.initDeploymentsPolling();
+
+ expect(SmartInterval).toHaveBeenCalledWith(
+ expect.objectContaining({
+ callback: vm.fetchPreMergeDeployments,
+ }),
+ );
+ });
+ });
+
+ describe('fetchDeployments', () => {
+ it('should fetch deployments', () => {
+ jest
+ .spyOn(vm.service, 'fetchDeployments')
+ .mockReturnValue(returnPromise([{ id: 1, status: SUCCESS }]));
+
+ vm.fetchPreMergeDeployments();
+
+ return vm.$nextTick().then(() => {
+ expect(vm.service.fetchDeployments).toHaveBeenCalled();
+ expect(vm.mr.deployments.length).toEqual(1);
+ expect(vm.mr.deployments[0].id).toBe(1);
+ });
+ });
+ });
+
+ describe('fetchActionsContent', () => {
+ it('should fetch content of Cherry Pick and Revert modals', () => {
+ jest
+ .spyOn(vm.service, 'fetchMergeActionsContent')
+ .mockReturnValue(returnPromise('hello world'));
+
+ vm.fetchActionsContent();
+
+ return vm.$nextTick().then(() => {
+ expect(vm.service.fetchMergeActionsContent).toHaveBeenCalled();
+ expect(document.body.textContent).toContain('hello world');
+ });
+ });
+ });
+
+ describe('bindEventHubListeners', () => {
+ it.each`
+ event | method | methodArgs
+ ${'MRWidgetUpdateRequested'} | ${'checkStatus'} | ${x => [x]}
+ ${'MRWidgetRebaseSuccess'} | ${'checkStatus'} | ${x => [x, true]}
+ ${'FetchActionsContent'} | ${'fetchActionsContent'} | ${() => []}
+ ${'EnablePolling'} | ${'resumePolling'} | ${() => []}
+ ${'DisablePolling'} | ${'stopPolling'} | ${() => []}
+ `('should bind to $event', ({ event, method, methodArgs }) => {
+ jest.spyOn(vm, method).mockImplementation();
+
+ const eventArg = {};
+ eventHub.$emit(event, eventArg);
+
+ expect(vm[method]).toHaveBeenCalledWith(...methodArgs(eventArg));
+ });
+
+ it('should bind to SetBranchRemoveFlag', () => {
+ expect(vm.mr.isRemovingSourceBranch).toBe(false);
+
+ eventHub.$emit('SetBranchRemoveFlag', [true]);
+
+ expect(vm.mr.isRemovingSourceBranch).toBe(true);
+ });
+
+ it('should bind to FailedToMerge', () => {
+ vm.mr.state = '';
+ vm.mr.mergeError = '';
+
+ const mergeError = 'Something bad happened!';
+ eventHub.$emit('FailedToMerge', mergeError);
+
+ expect(vm.mr.state).toBe('failedToMerge');
+ expect(vm.mr.mergeError).toBe(mergeError);
+ });
+
+ it('should bind to UpdateWidgetData', () => {
+ jest.spyOn(vm.mr, 'setData').mockImplementation();
+
+ const data = { ...mockData };
+ eventHub.$emit('UpdateWidgetData', data);
+
+ expect(vm.mr.setData).toHaveBeenCalledWith(data);
+ });
+ });
+
+ describe('setFavicon', () => {
+ let faviconElement;
+
+ beforeEach(() => {
+ const favicon = document.createElement('link');
+ favicon.setAttribute('id', 'favicon');
+ favicon.setAttribute('data-original-href', faviconDataUrl);
+ document.body.appendChild(favicon);
+
+ faviconElement = document.getElementById('favicon');
+ });
+
+ afterEach(() => {
+ document.body.removeChild(document.getElementById('favicon'));
+ });
+
+ it('should call setFavicon method', done => {
+ vm.mr.ciStatusFaviconPath = overlayDataUrl;
+ vm.setFaviconHelper()
+ .then(() => {
+ /*
+ It would be better if we'd could mock commonUtils.setFaviconURL
+ with a spy and test that it was called. We are doing the following
+ tests as a proxy to show that the function has been called
+ */
+ expect(faviconElement.getAttribute('href')).not.toEqual(null);
+ expect(faviconElement.getAttribute('href')).not.toEqual(overlayDataUrl);
+ expect(faviconElement.getAttribute('href')).not.toEqual(faviconDataUrl);
+ })
+ .then(done)
+ .catch(done.fail);
+ });
+
+ it('should not call setFavicon when there is no ciStatusFaviconPath', done => {
+ vm.mr.ciStatusFaviconPath = null;
+ vm.setFaviconHelper()
+ .then(() => {
+ expect(faviconElement.getAttribute('href')).toEqual(null);
+ done();
+ })
+ .catch(done.fail);
+ });
+ });
+
+ describe('handleNotification', () => {
+ const data = {
+ ci_status: 'running',
+ title: 'title',
+ pipeline: { details: { status: { label: 'running-label' } } },
+ };
+
+ beforeEach(() => {
+ jest.spyOn(notify, 'notifyMe').mockImplementation(() => {});
+
+ vm.mr.ciStatus = 'failed';
+ vm.mr.gitlabLogo = 'logo.png';
+ });
+
+ it('should call notifyMe', () => {
+ vm.handleNotification(data);
+
+ expect(notify.notifyMe).toHaveBeenCalledWith(
+ 'Pipeline running-label',
+ 'Pipeline running-label for "title"',
+ 'logo.png',
+ );
+ });
+
+ it('should not call notifyMe if the status has not changed', () => {
+ vm.mr.ciStatus = data.ci_status;
+
+ vm.handleNotification(data);
+
+ expect(notify.notifyMe).not.toHaveBeenCalled();
+ });
+
+ it('should not notify if no pipeline provided', () => {
+ vm.handleNotification({
+ ...data,
+ pipeline: undefined,
+ });
+
+ expect(notify.notifyMe).not.toHaveBeenCalled();
+ });
+ });
+
+ describe('resumePolling', () => {
+ it('should call stopTimer on pollingInterval', () => {
+ jest.spyOn(vm.pollingInterval, 'resume').mockImplementation(() => {});
+
+ vm.resumePolling();
+
+ expect(vm.pollingInterval.resume).toHaveBeenCalled();
+ });
+ });
+
+ describe('stopPolling', () => {
+ it('should call stopTimer on pollingInterval', () => {
+ jest.spyOn(vm.pollingInterval, 'stopTimer').mockImplementation(() => {});
+
+ vm.stopPolling();
+
+ expect(vm.pollingInterval.stopTimer).toHaveBeenCalled();
+ });
+ });
+ });
+
+ describe('rendering relatedLinks', () => {
+ beforeEach(done => {
+ vm.mr.relatedLinks = {
+ assignToMe: null,
+ closing: `
+ <a class="close-related-link" href="#">
+ Close
+ </a>
+ `,
+ mentioned: '',
+ };
+ Vue.nextTick(done);
+ });
+
+ it('renders if there are relatedLinks', () => {
+ expect(vm.$el.querySelector('.close-related-link')).toBeDefined();
+ });
+
+ it('does not render if state is nothingToMerge', done => {
+ vm.mr.state = stateKey.nothingToMerge;
+ Vue.nextTick(() => {
+ expect(vm.$el.querySelector('.close-related-link')).toBeNull();
+ done();
+ });
+ });
+ });
+
+ describe('rendering source branch removal status', () => {
+ it('renders when user cannot remove branch and branch should be removed', done => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = true;
+ vm.mr.state = 'readyToMerge';
+
+ vm.$nextTick(() => {
+ const tooltip = vm.$el.querySelector('.fa-question-circle');
+
+ expect(vm.$el.textContent).toContain('Deletes source branch');
+ expect(tooltip.getAttribute('data-original-title')).toBe(
+ 'A user with write access to the source branch selected this option',
+ );
+
+ done();
+ });
+ });
+
+ it('does not render in merged state', done => {
+ vm.mr.canRemoveSourceBranch = false;
+ vm.mr.shouldRemoveSourceBranch = true;
+ vm.mr.state = 'merged';
+
+ vm.$nextTick(() => {
+ expect(vm.$el.textContent).toContain('The source branch has been deleted');
+ expect(vm.$el.textContent).not.toContain('Deletes source branch');
+
+ done();
+ });
+ });
+ });
+
+ describe('rendering deployments', () => {
+ const changes = [
+ {
+ path: 'index.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ];
+ const deploymentMockData = {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/acets-review-apps/environments/15',
+ stop_url: '/root/acets-review-apps/environments/15/stop',
+ metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+ external_url: 'http://diplo.',
+ external_url_formatted: 'diplo.',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ changes,
+ status: SUCCESS,
+ };
+
+ beforeEach(done => {
+ vm.mr.deployments.push(
+ {
+ ...deploymentMockData,
+ },
+ {
+ ...deploymentMockData,
+ id: deploymentMockData.id + 1,
+ },
+ );
+
+ vm.$nextTick(done);
+ });
+
+ it('renders multiple deployments', () => {
+ expect(vm.$el.querySelectorAll('.deploy-heading').length).toBe(2);
+ });
+
+ it('renders dropdpown with multiple file changes', () => {
+ expect(
+ vm.$el
+ .querySelector('.js-mr-wigdet-deployment-dropdown')
+ .querySelectorAll('.js-filtered-dropdown-result').length,
+ ).toEqual(changes.length);
+ });
+ });
+
+ describe('pipeline for target branch after merge', () => {
+ describe('with information for target branch pipeline', () => {
+ beforeEach(done => {
+ vm.mr.state = 'merged';
+ vm.mr.mergePipeline = {
+ id: 127,
+ user: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ web_url: 'http://localhost:3000/root',
+ status_tooltip_html: null,
+ path: '/root',
+ },
+ active: true,
+ coverage: null,
+ source: 'push',
+ created_at: '2018-10-22T11:41:35.186Z',
+ updated_at: '2018-10-22T11:41:35.433Z',
+ path: '/root/ci-web-terminal/pipelines/127',
+ flags: {
+ latest: true,
+ stuck: true,
+ auto_devops: false,
+ yaml_errors: false,
+ retryable: false,
+ cancelable: true,
+ failure_reason: false,
+ },
+ details: {
+ status: {
+ icon: 'status_pending',
+ text: 'pending',
+ label: 'pending',
+ group: 'pending',
+ tooltip: 'pending',
+ has_details: true,
+ details_path: '/root/ci-web-terminal/pipelines/127',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
+ },
+ duration: null,
+ finished_at: null,
+ stages: [
+ {
+ name: 'test',
+ title: 'test: pending',
+ status: {
+ icon: 'status_pending',
+ text: 'pending',
+ label: 'pending',
+ group: 'pending',
+ tooltip: 'pending',
+ has_details: true,
+ details_path: '/root/ci-web-terminal/pipelines/127#test',
+ illustration: null,
+ favicon:
+ '/assets/ci_favicons/favicon_status_pending-5bdf338420e5221ca24353b6bff1c9367189588750632e9a871b7af09ff6a2ae.png',
+ },
+ path: '/root/ci-web-terminal/pipelines/127#test',
+ dropdown_path: '/root/ci-web-terminal/pipelines/127/stage.json?stage=test',
+ },
+ ],
+ artifacts: [],
+ manual_actions: [],
+ scheduled_actions: [],
+ },
+ ref: {
+ name: 'master',
+ path: '/root/ci-web-terminal/commits/master',
+ tag: false,
+ branch: true,
+ },
+ commit: {
+ id: 'aa1939133d373c94879becb79d91828a892ee319',
+ short_id: 'aa193913',
+ title: "Merge branch 'master-test' into 'master'",
+ created_at: '2018-10-22T11:41:33.000Z',
+ parent_ids: [
+ '4622f4dd792468993003caf2e3be978798cbe096',
+ '76598df914cdfe87132d0c3c40f80db9fa9396a4',
+ ],
+ message:
+ "Merge branch 'master-test' into 'master'\n\nUpdate .gitlab-ci.yml\n\nSee merge request root/ci-web-terminal!1",
+ author_name: 'Administrator',
+ author_email: 'admin@example.com',
+ authored_date: '2018-10-22T11:41:33.000Z',
+ committer_name: 'Administrator',
+ committer_email: 'admin@example.com',
+ committed_date: '2018-10-22T11:41:33.000Z',
+ author: {
+ id: 1,
+ name: 'Administrator',
+ username: 'root',
+ state: 'active',
+ avatar_url: null,
+ web_url: 'http://localhost:3000/root',
+ status_tooltip_html: null,
+ path: '/root',
+ },
+ author_gravatar_url: null,
+ commit_url:
+ 'http://localhost:3000/root/ci-web-terminal/commit/aa1939133d373c94879becb79d91828a892ee319',
+ commit_path: '/root/ci-web-terminal/commit/aa1939133d373c94879becb79d91828a892ee319',
+ },
+ cancel_path: '/root/ci-web-terminal/pipelines/127/cancel',
+ };
+ vm.$nextTick(done);
+ });
+
+ it('renders pipeline block', () => {
+ expect(vm.$el.querySelector('.js-post-merge-pipeline')).not.toBeNull();
+ });
+
+ describe('with post merge deployments', () => {
+ beforeEach(done => {
+ vm.mr.postMergeDeployments = [
+ {
+ id: 15,
+ name: 'review/diplo',
+ url: '/root/acets-review-apps/environments/15',
+ stop_url: '/root/acets-review-apps/environments/15/stop',
+ metrics_url: '/root/acets-review-apps/environments/15/deployments/1/metrics',
+ metrics_monitoring_url: '/root/acets-review-apps/environments/15/metrics',
+ external_url: 'http://diplo.',
+ external_url_formatted: 'diplo.',
+ deployed_at: '2017-03-22T22:44:42.258Z',
+ deployed_at_formatted: 'Mar 22, 2017 10:44pm',
+ changes: [
+ {
+ path: 'index.html',
+ external_url:
+ 'http://root-master-patch-91341.volatile-watch.surge.sh/index.html',
+ },
+ {
+ path: 'imgs/gallery.html',
+ external_url:
+ 'http://root-master-patch-91341.volatile-watch.surge.sh/imgs/gallery.html',
+ },
+ {
+ path: 'about/',
+ external_url: 'http://root-master-patch-91341.volatile-watch.surge.sh/about/',
+ },
+ ],
+ status: 'success',
+ },
+ ];
+
+ vm.$nextTick(done);
+ });
+
+ it('renders post deployment information', () => {
+ expect(vm.$el.querySelector('.js-post-deployment')).not.toBeNull();
+ });
+ });
+ });
+
+ describe('without information for target branch pipeline', () => {
+ beforeEach(done => {
+ vm.mr.state = 'merged';
+
+ vm.$nextTick(done);
+ });
+
+ it('does not render pipeline block', () => {
+ expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull();
+ });
+ });
+
+ describe('when state is not merged', () => {
+ beforeEach(done => {
+ vm.mr.state = 'archived';
+
+ vm.$nextTick(done);
+ });
+
+ it('does not render pipeline block', () => {
+ expect(vm.$el.querySelector('.js-post-merge-pipeline')).toBeNull();
+ });
+
+ it('does not render post deployment information', () => {
+ expect(vm.$el.querySelector('.js-post-deployment')).toBeNull();
+ });
+ });
+ });
+
+ it('should not suggest pipelines', () => {
+ vm.mr.mergeRequestAddCiConfigPath = null;
+
+ expect(vm.shouldSuggestPipelines).toBeFalsy();
+ });
+ });
+
+ describe('given suggestPipeline feature flag is enabled', () => {
+ beforeEach(() => {
+ // This is needed because some grandchildren Bootstrap components throw warnings
+ // https://gitlab.com/gitlab-org/gitlab/issues/208458
+ jest.spyOn(console, 'warn').mockImplementation();
+
+ gon.features = { suggestPipeline: true };
+ return createComponent();
+ });
+
+ it('should suggest pipelines when none exist', () => {
+ vm.mr.mergeRequestAddCiConfigPath = 'some/path';
+ vm.mr.hasCI = false;
+
+ expect(vm.shouldSuggestPipelines).toBeTruthy();
+ });
+
+ it('should not suggest pipelines when they exist', () => {
+ vm.mr.mergeRequestAddCiConfigPath = null;
+ vm.mr.hasCI = false;
+
+ expect(vm.shouldSuggestPipelines).toBeFalsy();
+ });
+
+ it('should not suggest pipelines hasCI is true', () => {
+ vm.mr.mergeRequestAddCiConfigPath = 'some/path';
+ vm.mr.hasCI = true;
+
+ expect(vm.shouldSuggestPipelines).toBeFalsy();
+ });
+ });
+});