diff options
Diffstat (limited to 'spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js')
-rw-r--r-- | spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js | 981 |
1 files changed, 981 insertions, 0 deletions
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 new file mode 100644 index 00000000000..1f0d6a7378c --- /dev/null +++ b/spec/frontend/vue_mr_widget/components/states/mr_widget_ready_to_merge_spec.js @@ -0,0 +1,981 @@ +import Vue from 'vue'; +import { createLocalVue, shallowMount } from '@vue/test-utils'; +import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue'; +import SquashBeforeMerge from '~/vue_merge_request_widget/components/states/squash_before_merge.vue'; +import CommitsHeader from '~/vue_merge_request_widget/components/states/commits_header.vue'; +import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit.vue'; +import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue'; +import eventHub from '~/vue_merge_request_widget/event_hub'; +import { MWPS_MERGE_STRATEGY, MTWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants'; +import { refreshUserMergeRequestCounts } from '~/commons/nav/user_merge_requests'; +import simplePoll from '~/lib/utils/simple_poll'; + +jest.mock('~/lib/utils/simple_poll', () => + jest.fn().mockImplementation(jest.requireActual('~/lib/utils/simple_poll').default), +); +jest.mock('~/commons/nav/user_merge_requests', () => ({ + refreshUserMergeRequestCounts: jest.fn(), +})); + +const commitMessage = 'This is the commit message'; +const squashCommitMessage = 'This is the squash commit message'; +const commitMessageWithDescription = 'This is the commit message description'; +const createTestMr = customConfig => { + const mr = { + isPipelineActive: false, + pipeline: null, + isPipelineFailed: false, + isPipelinePassing: false, + isMergeAllowed: true, + isApproved: true, + onlyAllowMergeIfPipelineSucceeds: false, + ffOnlyEnabled: false, + hasCI: false, + ciStatus: null, + sha: '12345678', + squash: false, + commitMessage, + squashCommitMessage, + commitMessageWithDescription, + shouldRemoveSourceBranch: true, + canRemoveSourceBranch: false, + targetBranch: 'master', + preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY, + availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY], + mergeImmediatelyDocsPath: 'path/to/merge/immediately/docs', + }; + + Object.assign(mr, customConfig.mr); + + return mr; +}; + +const createTestService = () => ({ + merge: jest.fn(), + poll: jest.fn().mockResolvedValue(), +}); + +const createComponent = (customConfig = {}) => { + const Component = Vue.extend(ReadyToMerge); + + return new Component({ + el: document.createElement('div'), + propsData: { + mr: createTestMr(customConfig), + service: createTestService(), + }, + }); +}; + +describe('ReadyToMerge', () => { + let vm; + + beforeEach(() => { + vm = createComponent(); + }); + + afterEach(() => { + vm.$destroy(); + }); + + describe('props', () => { + it('should have props', () => { + const { mr, service } = ReadyToMerge.props; + + expect(mr.type instanceof Object).toBeTruthy(); + expect(mr.required).toBeTruthy(); + + expect(service.type instanceof Object).toBeTruthy(); + expect(service.required).toBeTruthy(); + }); + }); + + describe('data', () => { + it('should have default data', () => { + expect(vm.mergeWhenBuildSucceeds).toBeFalsy(); + expect(vm.useCommitMessageWithDescription).toBeFalsy(); + expect(vm.showCommitMessageEditor).toBeFalsy(); + expect(vm.isMakingRequest).toBeFalsy(); + expect(vm.isMergingImmediately).toBeFalsy(); + expect(vm.commitMessage).toBe(vm.mr.commitMessage); + expect(vm.successSvg).toBeDefined(); + expect(vm.warningSvg).toBeDefined(); + }); + }); + + describe('computed', () => { + describe('isAutoMergeAvailable', () => { + it('should return true when at least one merge strategy is available', () => { + vm.mr.availableAutoMergeStrategies = [MWPS_MERGE_STRATEGY]; + + expect(vm.isAutoMergeAvailable).toBe(true); + }); + + it('should return false when no merge strategies are available', () => { + vm.mr.availableAutoMergeStrategies = []; + + expect(vm.isAutoMergeAvailable).toBe(false); + }); + }); + + describe('status', () => { + it('defaults to success', () => { + Vue.set(vm.mr, 'pipeline', true); + Vue.set(vm.mr, 'availableAutoMergeStrategies', []); + + expect(vm.status).toEqual('success'); + }); + + it('returns failed when MR has CI but also has an unknown status', () => { + Vue.set(vm.mr, 'hasCI', true); + + expect(vm.status).toEqual('failed'); + }); + + it('returns default when MR has no pipeline', () => { + Vue.set(vm.mr, 'availableAutoMergeStrategies', []); + + expect(vm.status).toEqual('success'); + }); + + it('returns pending when pipeline is active', () => { + Vue.set(vm.mr, 'pipeline', {}); + Vue.set(vm.mr, 'isPipelineActive', true); + + expect(vm.status).toEqual('pending'); + }); + + it('returns failed when pipeline is failed', () => { + Vue.set(vm.mr, 'pipeline', {}); + Vue.set(vm.mr, 'isPipelineFailed', true); + Vue.set(vm.mr, 'availableAutoMergeStrategies', []); + + expect(vm.status).toEqual('failed'); + }); + }); + + describe('mergeButtonVariant', () => { + it('defaults to success class', () => { + Vue.set(vm.mr, 'availableAutoMergeStrategies', []); + + expect(vm.mergeButtonVariant).toEqual('success'); + }); + + it('returns success class for success status', () => { + Vue.set(vm.mr, 'availableAutoMergeStrategies', []); + Vue.set(vm.mr, 'pipeline', true); + + expect(vm.mergeButtonVariant).toEqual('success'); + }); + + it('returns info class for pending status', () => { + Vue.set(vm.mr, 'availableAutoMergeStrategies', [MTWPS_MERGE_STRATEGY]); + + expect(vm.mergeButtonVariant).toEqual('info'); + }); + + it('returns danger class for failed status', () => { + vm.mr.hasCI = true; + + expect(vm.mergeButtonVariant).toEqual('danger'); + }); + }); + + describe('status icon', () => { + it('defaults to tick icon', () => { + expect(vm.iconClass).toEqual('success'); + }); + + it('shows tick for success status', () => { + vm.mr.pipeline = true; + + expect(vm.iconClass).toEqual('success'); + }); + + it('shows tick for pending status', () => { + vm.mr.pipeline = {}; + vm.mr.isPipelineActive = true; + + expect(vm.iconClass).toEqual('success'); + }); + + it('shows warning icon for failed status', () => { + vm.mr.hasCI = true; + + expect(vm.iconClass).toEqual('warning'); + }); + + it('shows warning icon for merge not allowed', () => { + vm.mr.hasCI = true; + + expect(vm.iconClass).toEqual('warning'); + }); + }); + + describe('mergeButtonText', () => { + it('should return "Merge" when no auto merge strategies are available', () => { + Vue.set(vm.mr, 'availableAutoMergeStrategies', []); + + expect(vm.mergeButtonText).toEqual('Merge'); + }); + + it('should return "Merge in progress"', () => { + Vue.set(vm, 'isMergingImmediately', true); + + expect(vm.mergeButtonText).toEqual('Merge in progress'); + }); + + it('should return "Merge when pipeline succeeds" when the MWPS auto merge strategy is available', () => { + Vue.set(vm, 'isMergingImmediately', false); + Vue.set(vm.mr, 'preferredAutoMergeStrategy', MWPS_MERGE_STRATEGY); + + expect(vm.mergeButtonText).toEqual('Merge when pipeline succeeds'); + }); + }); + + describe('autoMergeText', () => { + it('should return Merge when pipeline succeeds', () => { + Vue.set(vm.mr, 'preferredAutoMergeStrategy', MWPS_MERGE_STRATEGY); + + expect(vm.autoMergeText).toEqual('Merge when pipeline succeeds'); + }); + }); + + describe('shouldShowMergeImmediatelyDropdown', () => { + it('should return false if no pipeline is active', () => { + Vue.set(vm.mr, 'isPipelineActive', false); + Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false); + + expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false); + }); + + it('should return false if "Pipelines must succeed" is enabled for the current project', () => { + Vue.set(vm.mr, 'isPipelineActive', true); + Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true); + + expect(vm.shouldShowMergeImmediatelyDropdown).toBe(false); + }); + + it('should return true if the MR\'s pipeline is active and "Pipelines must succeed" is not enabled for the current project', () => { + Vue.set(vm.mr, 'isPipelineActive', true); + Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', false); + + expect(vm.shouldShowMergeImmediatelyDropdown).toBe(true); + }); + }); + + describe('isMergeButtonDisabled', () => { + it('should return false with initial data', () => { + Vue.set(vm.mr, 'isMergeAllowed', true); + + expect(vm.isMergeButtonDisabled).toBe(false); + }); + + it('should return true when there is no commit message', () => { + Vue.set(vm.mr, 'isMergeAllowed', true); + Vue.set(vm, 'commitMessage', ''); + + expect(vm.isMergeButtonDisabled).toBe(true); + }); + + it('should return true if merge is not allowed', () => { + Vue.set(vm.mr, 'isMergeAllowed', false); + Vue.set(vm.mr, 'availableAutoMergeStrategies', []); + Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true); + + expect(vm.isMergeButtonDisabled).toBe(true); + }); + + it('should return true when the vm instance is making request', () => { + Vue.set(vm.mr, 'isMergeAllowed', true); + Vue.set(vm, 'isMakingRequest', true); + + expect(vm.isMergeButtonDisabled).toBe(true); + }); + }); + + describe('isMergeImmediatelyDangerous', () => { + it('should always return false in CE', () => { + expect(vm.isMergeImmediatelyDangerous).toBe(false); + }); + }); + }); + + describe('methods', () => { + describe('shouldShowMergeControls', () => { + it('should return false when an external pipeline is running and required to succeed', () => { + Vue.set(vm.mr, 'isMergeAllowed', false); + Vue.set(vm.mr, 'availableAutoMergeStrategies', []); + + expect(vm.shouldShowMergeControls).toBe(false); + }); + + it('should return true when the build succeeded or build not required to succeed', () => { + Vue.set(vm.mr, 'isMergeAllowed', true); + Vue.set(vm.mr, 'availableAutoMergeStrategies', []); + + expect(vm.shouldShowMergeControls).toBe(true); + }); + + it('should return true when showing the MWPS button and a pipeline is running that needs to be successful', () => { + Vue.set(vm.mr, 'isMergeAllowed', false); + Vue.set(vm.mr, 'availableAutoMergeStrategies', [MWPS_MERGE_STRATEGY]); + + expect(vm.shouldShowMergeControls).toBe(true); + }); + + it('should return true when showing the MWPS button but not required for the pipeline to succeed', () => { + Vue.set(vm.mr, 'isMergeAllowed', true); + Vue.set(vm.mr, 'availableAutoMergeStrategies', [MWPS_MERGE_STRATEGY]); + + expect(vm.shouldShowMergeControls).toBe(true); + }); + }); + + describe('updateMergeCommitMessage', () => { + it('should revert flag and change commitMessage', () => { + expect(vm.commitMessage).toEqual(commitMessage); + vm.updateMergeCommitMessage(true); + + expect(vm.commitMessage).toEqual(commitMessageWithDescription); + vm.updateMergeCommitMessage(false); + + expect(vm.commitMessage).toEqual(commitMessage); + }); + }); + + describe('handleMergeButtonClick', () => { + const returnPromise = status => + new Promise(resolve => { + resolve({ + data: { + status, + }, + }); + }); + + it('should handle merge when pipeline succeeds', done => { + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + jest + .spyOn(vm.service, 'merge') + .mockReturnValue(returnPromise('merge_when_pipeline_succeeds')); + vm.removeSourceBranch = false; + vm.handleMergeButtonClick(true); + + setImmediate(() => { + expect(vm.isMakingRequest).toBeTruthy(); + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + + const params = vm.service.merge.mock.calls[0][0]; + + expect(params).toEqual( + expect.objectContaining({ + sha: vm.mr.sha, + commit_message: vm.mr.commitMessage, + should_remove_source_branch: false, + auto_merge_strategy: 'merge_when_pipeline_succeeds', + }), + ); + done(); + }); + }); + + it('should handle merge failed', done => { + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + jest.spyOn(vm.service, 'merge').mockReturnValue(returnPromise('failed')); + vm.handleMergeButtonClick(false, true); + + setImmediate(() => { + expect(vm.isMakingRequest).toBeTruthy(); + expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined); + + const params = vm.service.merge.mock.calls[0][0]; + + expect(params.should_remove_source_branch).toBeTruthy(); + expect(params.auto_merge_strategy).toBeUndefined(); + done(); + }); + }); + + it('should handle merge action accepted case', done => { + jest.spyOn(vm.service, 'merge').mockReturnValue(returnPromise('success')); + jest.spyOn(vm, 'initiateMergePolling').mockImplementation(() => {}); + vm.handleMergeButtonClick(); + + setImmediate(() => { + expect(vm.isMakingRequest).toBeTruthy(); + expect(vm.initiateMergePolling).toHaveBeenCalled(); + + const params = vm.service.merge.mock.calls[0][0]; + + expect(params.should_remove_source_branch).toBeTruthy(); + expect(params.auto_merge_strategy).toBeUndefined(); + done(); + }); + }); + }); + + describe('initiateMergePolling', () => { + it('should call simplePoll', () => { + vm.initiateMergePolling(); + + expect(simplePoll).toHaveBeenCalledWith(expect.any(Function), { timeout: 0 }); + }); + + it('should call handleMergePolling', () => { + jest.spyOn(vm, 'handleMergePolling').mockImplementation(() => {}); + + vm.initiateMergePolling(); + + expect(vm.handleMergePolling).toHaveBeenCalled(); + }); + }); + + describe('handleMergePolling', () => { + const returnPromise = state => + new Promise(resolve => { + resolve({ + data: { + state, + source_branch_exists: true, + }, + }); + }); + + beforeEach(() => { + loadFixtures('merge_requests/merge_request_of_current_user.html'); + }); + + it('should call start and stop polling when MR merged', done => { + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('merged')); + jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {}); + + let cpc = false; // continuePollingCalled + let spc = false; // stopPollingCalled + + vm.handleMergePolling( + () => { + cpc = true; + }, + () => { + spc = true; + }, + ); + setImmediate(() => { + expect(vm.service.poll).toHaveBeenCalled(); + expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested'); + expect(eventHub.$emit).toHaveBeenCalledWith('FetchActionsContent'); + expect(vm.initiateRemoveSourceBranchPolling).toHaveBeenCalled(); + expect(refreshUserMergeRequestCounts).toHaveBeenCalled(); + expect(cpc).toBeFalsy(); + expect(spc).toBeTruthy(); + + done(); + }); + }); + + it('updates status box', done => { + jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('merged')); + jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {}); + + vm.handleMergePolling(() => {}, () => {}); + + setImmediate(() => { + const statusBox = document.querySelector('.status-box'); + + expect(statusBox.classList.contains('status-box-mr-merged')).toBeTruthy(); + expect(statusBox.textContent).toContain('Merged'); + + done(); + }); + }); + + it('hides close button', done => { + jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('merged')); + jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {}); + + vm.handleMergePolling(() => {}, () => {}); + + setImmediate(() => { + expect(document.querySelector('.btn-close').classList.contains('hidden')).toBeTruthy(); + + done(); + }); + }); + + it('updates merge request count badge', done => { + jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('merged')); + jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {}); + + vm.handleMergePolling(() => {}, () => {}); + + setImmediate(() => { + expect(document.querySelector('.js-merge-counter').textContent).toBe('0'); + + done(); + }); + }); + + it('should continue polling until MR is merged', done => { + jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise('some_other_state')); + jest.spyOn(vm, 'initiateRemoveSourceBranchPolling').mockImplementation(() => {}); + + let cpc = false; // continuePollingCalled + let spc = false; // stopPollingCalled + + vm.handleMergePolling( + () => { + cpc = true; + }, + () => { + spc = true; + }, + ); + setImmediate(() => { + expect(cpc).toBeTruthy(); + expect(spc).toBeFalsy(); + + done(); + }); + }); + }); + + describe('initiateRemoveSourceBranchPolling', () => { + it('should emit event and call simplePoll', () => { + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + + vm.initiateRemoveSourceBranchPolling(); + + expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [true]); + expect(simplePoll).toHaveBeenCalled(); + }); + }); + + describe('handleRemoveBranchPolling', () => { + const returnPromise = state => + new Promise(resolve => { + resolve({ + data: { + source_branch_exists: state, + }, + }); + }); + + it('should call start and stop polling when MR merged', done => { + jest.spyOn(eventHub, '$emit').mockImplementation(() => {}); + jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise(false)); + + let cpc = false; // continuePollingCalled + let spc = false; // stopPollingCalled + + vm.handleRemoveBranchPolling( + () => { + cpc = true; + }, + () => { + spc = true; + }, + ); + setImmediate(() => { + expect(vm.service.poll).toHaveBeenCalled(); + + const args = eventHub.$emit.mock.calls[0]; + + expect(args[0]).toEqual('MRWidgetUpdateRequested'); + expect(args[1]).toBeDefined(); + args[1](); + + expect(eventHub.$emit).toHaveBeenCalledWith('SetBranchRemoveFlag', [false]); + + expect(cpc).toBeFalsy(); + expect(spc).toBeTruthy(); + + done(); + }); + }); + + it('should continue polling until MR is merged', done => { + jest.spyOn(vm.service, 'poll').mockReturnValue(returnPromise(true)); + + let cpc = false; // continuePollingCalled + let spc = false; // stopPollingCalled + + vm.handleRemoveBranchPolling( + () => { + cpc = true; + }, + () => { + spc = true; + }, + ); + setImmediate(() => { + expect(cpc).toBeTruthy(); + expect(spc).toBeFalsy(); + + done(); + }); + }); + }); + }); + + describe('Remove source branch checkbox', () => { + describe('when user can merge but cannot delete branch', () => { + it('should be disabled in the rendered output', () => { + const checkboxElement = vm.$el.querySelector('#remove-source-branch-input'); + + expect(checkboxElement).toBeNull(); + }); + }); + + describe('when user can merge and can delete branch', () => { + beforeEach(() => { + vm = createComponent({ + mr: { canRemoveSourceBranch: true }, + }); + }); + + it('isRemoveSourceBranchButtonDisabled should be false', () => { + expect(vm.isRemoveSourceBranchButtonDisabled).toBe(false); + }); + + it('removed source branch should be enabled in rendered output', () => { + const checkboxElement = vm.$el.querySelector('#remove-source-branch-input'); + + expect(checkboxElement).not.toBeNull(); + }); + }); + }); + + describe('render children components', () => { + let wrapper; + const localVue = createLocalVue(); + + const createLocalComponent = (customConfig = {}) => { + wrapper = shallowMount(localVue.extend(ReadyToMerge), { + localVue, + propsData: { + mr: createTestMr(customConfig), + service: createTestService(), + }, + }); + }; + + afterEach(() => { + wrapper.destroy(); + }); + + const findCheckboxElement = () => wrapper.find(SquashBeforeMerge); + const findCommitsHeaderElement = () => wrapper.find(CommitsHeader); + const findCommitEditElements = () => wrapper.findAll(CommitEdit); + const findCommitDropdownElement = () => wrapper.find(CommitMessageDropdown); + const findFirstCommitEditLabel = () => + findCommitEditElements() + .at(0) + .props('label'); + + describe('squash checkbox', () => { + it('should be rendered when squash before merge is enabled and there is more than 1 commit', () => { + createLocalComponent({ + mr: { commitsCount: 2, enableSquashBeforeMerge: true }, + }); + + expect(findCheckboxElement().exists()).toBeTruthy(); + }); + + it('should not be rendered when squash before merge is disabled', () => { + createLocalComponent({ mr: { commitsCount: 2, enableSquashBeforeMerge: false } }); + + expect(findCheckboxElement().exists()).toBeFalsy(); + }); + + it('should not be rendered when there is only 1 commit', () => { + createLocalComponent({ mr: { commitsCount: 1, enableSquashBeforeMerge: true } }); + + expect(findCheckboxElement().exists()).toBeFalsy(); + }); + }); + + describe('commits count collapsible header', () => { + it('should be rendered when fast-forward is disabled', () => { + createLocalComponent(); + + expect(findCommitsHeaderElement().exists()).toBeTruthy(); + }); + + describe('when fast-forward is enabled', () => { + it('should be rendered if squash and squash before are enabled and there is more than 1 commit', () => { + createLocalComponent({ + mr: { + ffOnlyEnabled: true, + enableSquashBeforeMerge: true, + squash: true, + commitsCount: 2, + }, + }); + + expect(findCommitsHeaderElement().exists()).toBeTruthy(); + }); + + it('should not be rendered if squash before merge is disabled', () => { + createLocalComponent({ + mr: { + ffOnlyEnabled: true, + enableSquashBeforeMerge: false, + squash: true, + commitsCount: 2, + }, + }); + + expect(findCommitsHeaderElement().exists()).toBeFalsy(); + }); + + it('should not be rendered if squash is disabled', () => { + createLocalComponent({ + mr: { + ffOnlyEnabled: true, + squash: false, + enableSquashBeforeMerge: true, + commitsCount: 2, + }, + }); + + expect(findCommitsHeaderElement().exists()).toBeFalsy(); + }); + + it('should not be rendered if commits count is 1', () => { + createLocalComponent({ + mr: { + ffOnlyEnabled: true, + squash: true, + enableSquashBeforeMerge: true, + commitsCount: 1, + }, + }); + + expect(findCommitsHeaderElement().exists()).toBeFalsy(); + }); + }); + }); + + describe('commits edit components', () => { + describe('when fast-forward merge is enabled', () => { + it('should not be rendered if squash is disabled', () => { + createLocalComponent({ + mr: { + ffOnlyEnabled: true, + squash: false, + enableSquashBeforeMerge: true, + commitsCount: 2, + }, + }); + + expect(findCommitEditElements().length).toBe(0); + }); + + it('should not be rendered if squash before merge is disabled', () => { + createLocalComponent({ + mr: { + ffOnlyEnabled: true, + squash: true, + enableSquashBeforeMerge: false, + commitsCount: 2, + }, + }); + + expect(findCommitEditElements().length).toBe(0); + }); + + it('should not be rendered if there is only one commit', () => { + createLocalComponent({ + mr: { + ffOnlyEnabled: true, + squash: true, + enableSquashBeforeMerge: true, + commitsCount: 1, + }, + }); + + expect(findCommitEditElements().length).toBe(0); + }); + + it('should have one edit component if squash is enabled and there is more than 1 commit', () => { + createLocalComponent({ + mr: { + ffOnlyEnabled: true, + squash: true, + enableSquashBeforeMerge: true, + commitsCount: 2, + }, + }); + + expect(findCommitEditElements().length).toBe(1); + expect(findFirstCommitEditLabel()).toBe('Squash commit message'); + }); + }); + + it('should have one edit component when squash is disabled', () => { + createLocalComponent(); + + expect(findCommitEditElements().length).toBe(1); + }); + + it('should have two edit components when squash is enabled and there is more than 1 commit', () => { + createLocalComponent({ + mr: { + commitsCount: 2, + squash: true, + enableSquashBeforeMerge: true, + }, + }); + + expect(findCommitEditElements().length).toBe(2); + }); + + it('should have one edit components when squash is enabled and there is 1 commit only', () => { + createLocalComponent({ + mr: { + commitsCount: 1, + squash: true, + enableSquashBeforeMerge: true, + }, + }); + + expect(findCommitEditElements().length).toBe(1); + }); + + it('should have correct edit merge commit label', () => { + createLocalComponent(); + + expect(findFirstCommitEditLabel()).toBe('Merge commit message'); + }); + + it('should have correct edit squash commit label', () => { + createLocalComponent({ + mr: { + commitsCount: 2, + squash: true, + enableSquashBeforeMerge: true, + }, + }); + + expect(findFirstCommitEditLabel()).toBe('Squash commit message'); + }); + }); + + describe('commits dropdown', () => { + it('should not be rendered if squash is disabled', () => { + createLocalComponent(); + + expect(findCommitDropdownElement().exists()).toBeFalsy(); + }); + + it('should be rendered if squash is enabled and there is more than 1 commit', () => { + createLocalComponent({ + mr: { enableSquashBeforeMerge: true, squash: true, commitsCount: 2 }, + }); + + expect(findCommitDropdownElement().exists()).toBeTruthy(); + }); + }); + }); + + describe('Merge controls', () => { + describe('when allowed to merge', () => { + beforeEach(() => { + vm = createComponent({ + mr: { isMergeAllowed: true, canRemoveSourceBranch: true }, + }); + }); + + it('shows remove source branch checkbox', () => { + expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).not.toBeNull(); + }); + + it('shows modify commit message button', () => { + expect(vm.$el.querySelector('.js-modify-commit-message-button')).toBeDefined(); + }); + + it('does not show message about needing to resolve items', () => { + expect(vm.$el.querySelector('.js-resolve-mr-widget-items-message')).toBeNull(); + }); + }); + + describe('when not allowed to merge', () => { + beforeEach(() => { + vm = createComponent({ + mr: { isMergeAllowed: false }, + }); + }); + + it('does not show remove source branch checkbox', () => { + expect(vm.$el.querySelector('.js-remove-source-branch-checkbox')).toBeNull(); + }); + + it('shows message to resolve all items before being allowed to merge', () => { + expect(vm.$el.querySelector('.js-resolve-mr-widget-items-message')).toBeDefined(); + }); + }); + }); + + describe('Merge request project settings', () => { + describe('when the merge commit merge method is enabled', () => { + beforeEach(() => { + vm = createComponent({ + mr: { ffOnlyEnabled: false }, + }); + }); + + it('should not show fast forward message', () => { + expect(vm.$el.querySelector('.mr-fast-forward-message')).toBeNull(); + }); + + it('should show "Modify commit message" button', () => { + expect(vm.$el.querySelector('.js-modify-commit-message-button')).toBeDefined(); + }); + }); + + describe('when the fast-forward merge method is enabled', () => { + beforeEach(() => { + vm = createComponent({ + mr: { ffOnlyEnabled: true }, + }); + }); + + it('should show fast forward message', () => { + expect(vm.$el.querySelector('.mr-fast-forward-message')).toBeDefined(); + }); + + it('should not show "Modify commit message" button', () => { + expect(vm.$el.querySelector('.js-modify-commit-message-button')).toBeNull(); + }); + }); + }); + + describe('with a mismatched SHA', () => { + const findMismatchShaBlock = () => vm.$el.querySelector('.js-sha-mismatch'); + + beforeEach(() => { + vm = createComponent({ + mr: { + isSHAMismatch: true, + mergeRequestDiffsPath: '/merge_requests/1/diffs', + }, + }); + }); + + it('displays a warning message', () => { + expect(findMismatchShaBlock()).toExist(); + }); + + it('warns the user to refresh to review', () => { + expect(findMismatchShaBlock().textContent.trim()).toBe( + 'New changes were added. Reload the page to review them', + ); + }); + + it('displays link to the diffs tab', () => { + expect(findMismatchShaBlock().querySelector('a').href).toContain(vm.mr.mergeRequestDiffsPath); + }); + }); +}); |