diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-02 18:07:42 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-03-02 18:07:42 +0000 |
commit | 7b52c7cb634ef7047d30b0337fe477bcdcedf41d (patch) | |
tree | 374ca9e908204488422046f10e340d1500780362 /spec/frontend/vue_mr_widget | |
parent | b375c6c05fbd03aea33a9ee9f82e678bdaa8c3cc (diff) | |
download | gitlab-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.js | 318 | ||||
-rw-r--r-- | spec/frontend/vue_mr_widget/mr_widget_options_spec.js | 834 |
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(); + }); + }); +}); |