Commit 35ae9d8a authored by Nathan Friend's avatar Nathan Friend

Add merge train support to MR merge button (CE)

This commit updates the merge request widget's "Merge" button to
support merge trains.
parent fc27c93e
<script>
import _ from 'underscore';
import autoMergeMixin from 'ee_else_ce/vue_merge_request_widget/mixins/auto_merge';
import Flash from '../../../flash';
import statusIcon from '../mr_widget_status_icon.vue';
import MrWidgetAuthor from '../../components/mr_widget_author.vue';
import eventHub from '../../event_hub';
import { AUTO_MERGE_STRATEGIES } from '../../constants';
export default {
name: 'MRWidgetMergeWhenPipelineSucceeds',
name: 'MRWidgetAutoMergeEnabled',
components: {
MrWidgetAuthor,
statusIcon,
},
mixins: [autoMergeMixin],
props: {
mr: {
type: Object,
......@@ -57,7 +61,7 @@ export default {
removeSourceBranch() {
const options = {
sha: this.mr.sha,
auto_merge_strategy: 'merge_when_pipeline_succeeds',
auto_merge_strategy: this.mr.autoMergeStrategy,
should_remove_source_branch: true,
};
......@@ -66,7 +70,7 @@ export default {
.merge(options)
.then(res => res.data)
.then(data => {
if (data.status === 'merge_when_pipeline_succeeds') {
if (_.includes(AUTO_MERGE_STRATEGIES, data.status)) {
eventHub.$emit('MRWidgetUpdateRequested');
}
})
......@@ -84,9 +88,9 @@ export default {
<div class="media-body">
<h4 class="d-flex align-items-start">
<span class="append-right-10">
{{ s__('mrWidget|Set by') }}
<span class="js-status-text-before-author">{{ statusTextBeforeAuthor }}</span>
<mr-widget-author :author="mr.setToAutoMergeBy" />
{{ s__('mrWidget|to be merged automatically when the pipeline succeeds') }}
<span class="js-status-text-after-author">{{ statusTextAfterAuthor }}</span>
</span>
<a
v-if="mr.canCancelAutomaticMerge"
......@@ -97,7 +101,7 @@ export default {
@click.prevent="cancelAutomaticMerge"
>
<i v-if="isCancellingAutoMerge" class="fa fa-spinner fa-spin" aria-hidden="true"> </i>
{{ s__('mrWidget|Cancel automatic merge') }}
{{ cancelButtonText }}
</a>
</h4>
<section class="mr-info-list">
......
<script>
import _ from 'underscore';
import successSvg from 'icons/_icon_status_success.svg';
import warningSvg from 'icons/_icon_status_warning.svg';
import simplePoll from '~/lib/utils/simple_poll';
......@@ -12,6 +13,7 @@ import SquashBeforeMerge from './squash_before_merge.vue';
import CommitsHeader from './commits_header.vue';
import CommitEdit from './commit_edit.vue';
import CommitMessageDropdown from './commit_message_dropdown.vue';
import { AUTO_MERGE_STRATEGIES } from '../../constants';
export default {
name: 'ReadyToMerge',
......@@ -30,8 +32,6 @@ export default {
data() {
return {
removeSourceBranch: this.mr.shouldRemoveSourceBranch,
mergeWhenBuildSucceeds: false,
autoMergeStrategy: undefined,
isMakingRequest: false,
isMergingImmediately: false,
commitMessage: this.mr.commitMessage,
......@@ -42,18 +42,18 @@ export default {
};
},
computed: {
shouldShowAutoMergeText() {
return this.mr.isPipelineActive;
isAutoMergeAvailable() {
return !_.isEmpty(this.mr.availableAutoMergeStrategies);
},
status() {
const { pipeline, isPipelineActive, isPipelineFailed, hasCI, ciStatus } = this.mr;
const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr;
if (hasCI && !ciStatus) {
return 'failed';
} else if (this.isAutoMergeAvailable) {
return 'pending';
} else if (!pipeline) {
return 'success';
} else if (isPipelineActive) {
return 'pending';
} else if (isPipelineFailed) {
return 'failed';
}
......@@ -87,14 +87,14 @@ export default {
mergeButtonText() {
if (this.isMergingImmediately) {
return __('Merge in progress');
} else if (this.shouldShowAutoMergeText) {
return __('Merge when pipeline succeeds');
} else if (this.isAutoMergeAvailable) {
return this.autoMergeText;
}
return 'Merge';
return __('Merge');
},
shouldShowMergeOptionsDropdown() {
return this.mr.isPipelineActive && !this.mr.onlyAllowMergeIfPipelineSucceeds;
return this.isAutoMergeAvailable && !this.mr.onlyAllowMergeIfPipelineSucceeds;
},
isRemoveSourceBranchButtonDisabled() {
return this.isMergeButtonDisabled;
......@@ -104,7 +104,7 @@ export default {
return enableSquashBeforeMerge && commitsCount > 1;
},
shouldShowMergeControls() {
return this.mr.isMergeAllowed || this.shouldShowAutoMergeText;
return this.mr.isMergeAllowed || this.isAutoMergeAvailable;
},
shouldShowSquashEdit() {
return this.squashBeforeMerge && this.shouldShowSquashBeforeMerge;
......@@ -118,20 +118,15 @@ export default {
const { commitMessageWithDescription, commitMessage } = this.mr;
this.commitMessage = includeDescription ? commitMessageWithDescription : commitMessage;
},
handleMergeButtonClick(mergeWhenBuildSucceeds, mergeImmediately) {
// TODO: Remove no-param-reassign
if (mergeWhenBuildSucceeds === undefined) {
mergeWhenBuildSucceeds = this.mr.isPipelineActive; // eslint-disable-line no-param-reassign
} else if (mergeImmediately) {
handleMergeButtonClick(useAutoMerge, mergeImmediately = false) {
if (mergeImmediately) {
this.isMergingImmediately = true;
}
this.autoMergeStrategy = mergeWhenBuildSucceeds ? 'merge_when_pipeline_succeeds' : undefined;
const options = {
sha: this.mr.sha,
commit_message: this.commitMessage,
auto_merge_strategy: this.autoMergeStrategy,
auto_merge_strategy: useAutoMerge ? this.mr.preferredAutoMergeStrategy : undefined,
should_remove_source_branch: this.removeSourceBranch === true,
squash: this.squashBeforeMerge,
squash_commit_message: this.squashCommitMessage,
......@@ -144,7 +139,7 @@ export default {
.then(data => {
const hasError = data.status === 'failed' || data.status === 'hook_validation_error';
if (data.status === 'merge_when_pipeline_succeeds') {
if (_.includes(AUTO_MERGE_STRATEGIES, data.status)) {
eventHub.$emit('MRWidgetUpdateRequested');
} else if (data.status === 'success') {
this.initiateMergePolling();
......@@ -242,13 +237,13 @@ export default {
:class="mergeButtonClass"
type="button"
class="qa-merge-button"
@click="handleMergeButtonClick()"
@click="handleMergeButtonClick(isAutoMergeAvailable)"
>
<i v-if="isMakingRequest" class="fa fa-spinner fa-spin" aria-hidden="true"></i>
{{ mergeButtonText }}
</button>
<button
v-if="shouldShowMergeOptionsDropdown"
v-if="isAutoMergeAvailable"
:disabled="isMergeButtonDisabled"
type="button"
class="btn btn-sm btn-info dropdown-toggle js-merge-moment"
......@@ -264,15 +259,13 @@ export default {
>
<li>
<a
class="merge_when_pipeline_succeeds qa-merge-when-pipeline-succeeds-option"
class="auto_merge_enabled qa-merge-when-pipeline-succeeds-option"
href="#"
@click.prevent="handleMergeButtonClick(true)"
>
<span class="media">
<span class="merge-opt-icon" aria-hidden="true" v-html="successSvg"></span>
<span class="media-body merge-opt-title">{{
__('Merge when pipeline succeeds')
}}</span>
<span class="media-body merge-opt-title">{{ autoMergeText }}</span>
</span>
</a>
</li>
......
......@@ -3,3 +3,13 @@ export const DANGER = 'danger';
export const WARNING_MESSAGE_CLASS = 'warning_message';
export const DANGER_MESSAGE_CLASS = 'danger_message';
export const MWPS_MERGE_STRATEGY = 'merge_when_pipeline_succeeds';
export const ATMTWPS_MERGE_STRATEGY = 'add_to_merge_train_when_pipeline_succeeds';
export const MT_MERGE_STRATEGY = 'merge_train';
export const AUTO_MERGE_STRATEGIES = [
MWPS_MERGE_STRATEGY,
ATMTWPS_MERGE_STRATEGY,
MT_MERGE_STRATEGY,
];
import { s__ } from '~/locale';
export default {
computed: {
statusTextBeforeAuthor() {
return s__('mrWidget|Set by');
},
statusTextAfterAuthor() {
return s__('mrWidget|to be merged automatically when the pipeline succeeds');
},
cancelButtonText() {
return s__('mrWidget|Cancel automatic merge');
},
},
};
import { __ } from '~/locale';
export default {
computed: {
isMergeButtonDisabled() {
......@@ -9,5 +11,9 @@ export default {
this.mr.preventMerge,
);
},
autoMergeText() {
// MWPS is currently the only auto merge strategy available in CE
return __('Merge when pipeline succeeds');
},
},
};
......@@ -29,7 +29,7 @@ import UnresolvedDiscussionsState from './components/states/unresolved_discussio
import PipelineBlockedState from './components/states/mr_widget_pipeline_blocked.vue';
import PipelineFailedState from './components/states/pipeline_failed.vue';
import FailedToMerge from './components/states/mr_widget_failed_to_merge.vue';
import MergeWhenPipelineSucceedsState from './components/states/mr_widget_merge_when_pipeline_succeeds.vue';
import MrWidgetAutoMergeEnabled from './components/states/mr_widget_auto_merge_enabled.vue';
import AutoMergeFailed from './components/states/mr_widget_auto_merge_failed.vue';
import CheckingState from './components/states/mr_widget_checking.vue';
import eventHub from './event_hub';
......@@ -64,7 +64,7 @@ export default {
'mr-widget-unresolved-discussions': UnresolvedDiscussionsState,
'mr-widget-pipeline-blocked': PipelineBlockedState,
'mr-widget-pipeline-failed': PipelineFailedState,
'mr-widget-merge-when-pipeline-succeeds': MergeWhenPipelineSucceedsState,
MrWidgetAutoMergeEnabled,
'mr-widget-auto-merge-failed': AutoMergeFailed,
'mr-widget-rebase': RebaseState,
SourceBranchRemovalStatus,
......
import Timeago from 'timeago.js';
import _ from 'underscore';
import getStateKey from 'ee_else_ce/vue_merge_request_widget/stores/get_state_key';
import { stateKey } from './state_maps';
import { formatDate } from '../../lib/utils/datetime_utility';
import { ATMTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY, MWPS_MERGE_STRATEGY } from '../constants';
export default class MergeRequestStore {
constructor(data) {
......@@ -77,6 +79,10 @@ export default class MergeRequestStore {
this.onlyAllowMergeIfPipelineSucceeds = data.only_allow_merge_if_pipeline_succeeds || false;
this.autoMergeEnabled = Boolean(data.auto_merge_enabled);
this.autoMergeStrategy = data.auto_merge_strategy;
this.availableAutoMergeStrategies = data.available_auto_merge_strategies;
this.preferredAutoMergeStrategy = MergeRequestStore.getPreferredAutoMergeStrategy(
this.availableAutoMergeStrategies,
);
this.mergePath = data.merge_path;
this.ffOnlyEnabled = data.ff_only_enabled;
this.shouldBeRebased = Boolean(data.should_be_rebased);
......@@ -104,7 +110,9 @@ export default class MergeRequestStore {
this.sourceProjectFullPath = data.source_project_full_path;
this.sourceProjectId = data.source_project_id;
this.targetProjectId = data.target_project_id;
this.mergePipelinesEnabled = data.merge_pipelines_enabled;
this.mergePipelinesEnabled = Boolean(data.merge_pipelines_enabled);
this.mergeTrainsCount = data.merge_trains_count || 0;
this.mergeTrainIndex = data.merge_train_index;
// Cherry-pick and Revert actions related
this.canCherryPickInCurrentMR = currentUser.can_cherry_pick_on_current_merge_request || false;
......@@ -204,4 +212,16 @@ export default class MergeRequestStore {
return timeagoInstance.format(date);
}
static getPreferredAutoMergeStrategy(availableAutoMergeStrategies) {
if (_.includes(availableAutoMergeStrategies, ATMTWPS_MERGE_STRATEGY)) {
return ATMTWPS_MERGE_STRATEGY;
} else if (_.includes(availableAutoMergeStrategies, MT_MERGE_STRATEGY)) {
return MT_MERGE_STRATEGY;
} else if (_.includes(availableAutoMergeStrategies, MWPS_MERGE_STRATEGY)) {
return MWPS_MERGE_STRATEGY;
}
return undefined;
}
}
......@@ -13,7 +13,7 @@ const stateToComponentMap = {
unresolvedDiscussions: 'mr-widget-unresolved-discussions',
pipelineBlocked: 'mr-widget-pipeline-blocked',
pipelineFailed: 'mr-widget-pipeline-failed',
autoMergeEnabled: 'mr-widget-merge-when-pipeline-succeeds',
autoMergeEnabled: 'mr-widget-auto-merge-enabled',
failedToMerge: 'mr-widget-failed-to-merge',
autoMergeFailed: 'mr-widget-auto-merge-failed',
shaMismatch: 'sha-mismatch',
......
---
title: Update the merge request widget's "Merge" button to support merge trains
merge_request: 27594
author:
type: added
......@@ -6045,6 +6045,9 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
msgid "Merge"
msgstr ""
msgid "Merge Request"
msgstr ""
......
import Vue from 'vue';
import mwpsComponent from '~/vue_merge_request_widget/components/states/mr_widget_merge_when_pipeline_succeeds.vue';
import autoMergeEnabledComponent from '~/vue_merge_request_widget/components/states/mr_widget_auto_merge_enabled.vue';
import MRWidgetService from '~/vue_merge_request_widget/services/mr_widget_service';
import eventHub from '~/vue_merge_request_widget/event_hub';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { trimText } from 'spec/helpers/text_helper';
import { MWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
describe('MRWidgetMergeWhenPipelineSucceeds', () => {
describe('MRWidgetAutoMergeEnabled', () => {
let vm;
const targetBranchPath = '/foo/bar';
const targetBranch = 'foo';
const sha = '1EA2EZ34';
beforeEach(() => {
const Component = Vue.extend(mwpsComponent);
const Component = Vue.extend(autoMergeEnabledComponent);
spyOn(eventHub, '$emit');
vm = mountComponent(Component, {
......@@ -25,6 +27,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
sha,
targetBranchPath,
targetBranch,
autoMergeStrategy: MWPS_MERGE_STRATEGY,
},
service: new MRWidgetService({}),
});
......@@ -66,6 +69,32 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
expect(vm.canRemoveSourceBranch).toBeFalsy();
});
});
describe('statusTextBeforeAuthor', () => {
it('should return "Set by" if the MWPS is selected', () => {
Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY);
expect(vm.statusTextBeforeAuthor).toBe('Set by');
});
});
describe('statusTextAfterAuthor', () => {
it('should return "to be merged automatically..." if MWPS is selected', () => {
Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY);
expect(vm.statusTextAfterAuthor).toBe(
'to be merged automatically when the pipeline succeeds',
);
});
});
describe('cancelButtonText', () => {
it('should return "Cancel automatic merge" if MWPS is selected', () => {
Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY);
expect(vm.cancelButtonText).toBe('Cancel automatic merge');
});
});
});
describe('methods', () => {
......@@ -96,7 +125,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
spyOn(vm.service, 'merge').and.returnValue(
Promise.resolve({
data: {
status: 'merge_when_pipeline_succeeds',
status: MWPS_MERGE_STRATEGY,
},
}),
);
......@@ -106,7 +135,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
expect(vm.service.merge).toHaveBeenCalledWith({
sha,
auto_merge_strategy: 'merge_when_pipeline_succeeds',
auto_merge_strategy: MWPS_MERGE_STRATEGY,
should_remove_source_branch: true,
});
done();
......@@ -119,6 +148,7 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
it('should have correct elements', () => {
expect(vm.$el.classList.contains('mr-widget-body')).toBeTruthy();
expect(vm.$el.innerText).toContain('to be merged automatically when the pipeline succeeds');
expect(vm.$el.innerText).toContain('The changes will be merged into');
expect(vm.$el.innerText).toContain(targetBranch);
expect(vm.$el.innerText).toContain('The source branch will not be deleted');
......@@ -174,5 +204,27 @@ describe('MRWidgetMergeWhenPipelineSucceeds', () => {
done();
});
});
it('should render the status text as "...to merged automatically" if MWPS is selected', done => {
Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY);
Vue.nextTick(() => {
const statusText = trimText(vm.$el.querySelector('.js-status-text-after-author').innerText);
expect(statusText).toBe('to be merged automatically when the pipeline succeeds');
done();
});
});
it('should render the cancel button as "Cancel automatic merge" if MWPS is selected', done => {
Vue.set(vm.mr, 'autoMergeStrategy', MWPS_MERGE_STRATEGY);
Vue.nextTick(() => {
const cancelButtonText = trimText(vm.$el.querySelector('.js-cancel-auto-merge').innerText);
expect(cancelButtonText).toBe('Cancel automatic merge');
done();
});
});
});
});
......@@ -6,6 +6,7 @@ import CommitEdit from '~/vue_merge_request_widget/components/states/commit_edit
import CommitMessageDropdown from '~/vue_merge_request_widget/components/states/commit_message_dropdown.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
import { createLocalVue, shallowMount } from '@vue/test-utils';
import { MWPS_MERGE_STRATEGY, ATMTWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
const commitMessage = 'This is the commit message';
const squashCommitMessage = 'This is the squash commit message';
......@@ -29,6 +30,8 @@ const createTestMr = customConfig => {
shouldRemoveSourceBranch: true,
canRemoveSourceBranch: false,
targetBranch: 'master',
preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY,
availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY],
};
Object.assign(mr, customConfig.mr);
......@@ -80,7 +83,6 @@ describe('ReadyToMerge', () => {
it('should have default data', () => {
expect(vm.mergeWhenBuildSucceeds).toBeFalsy();
expect(vm.useCommitMessageWithDescription).toBeFalsy();
expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.showCommitMessageEditor).toBeFalsy();
expect(vm.isMakingRequest).toBeFalsy();
expect(vm.isMergingImmediately).toBeFalsy();
......@@ -91,47 +93,51 @@ describe('ReadyToMerge', () => {
});
describe('computed', () => {
describe('shouldShowAutoMergeText', () => {
it('should return true with active pipeline', () => {
vm.mr.isPipelineActive = true;
describe('isAutoMergeAvailable', () => {
it('should return true when at least one merge strategy is available', () => {
vm.mr.availableAutoMergeStrategies = [MWPS_MERGE_STRATEGY];
expect(vm.shouldShowAutoMergeText).toBeTruthy();
expect(vm.isAutoMergeAvailable).toBe(true);
});
it('should return false with inactive pipeline', () => {
vm.mr.isPipelineActive = false;
it('should return false when no merge strategies are available', () => {
vm.mr.availableAutoMergeStrategies = [];
expect(vm.shouldShowAutoMergeText).toBeFalsy();
expect(vm.isAutoMergeAvailable).toBe(false);
});
});
describe('status', () => {
it('defaults to success', () => {
vm.mr.pipeline = true;
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', () => {
vm.mr.hasCI = true;
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', () => {
vm.mr.pipeline = {};
vm.mr.isPipelineActive = true;
Vue.set(vm.mr, 'pipeline', {});
Vue.set(vm.mr, 'isPipelineActive', true);
expect(vm.status).toEqual('pending');
});
it('returns failed when pipeline is failed', () => {
vm.mr.pipeline = {};
vm.mr.isPipelineFailed = true;
Vue.set(vm.mr, 'pipeline', {});
Vue.set(vm.mr, 'isPipelineFailed', true);
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.status).toEqual('failed');
});
......@@ -143,18 +149,20 @@ describe('ReadyToMerge', () => {
const inActionClass = `${defaultClass} btn-info`;
it('defaults to success class', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.mergeButtonClass).toEqual(defaultClass);
});
it('returns success class for success status', () => {
vm.mr.pipeline = true;
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
Vue.set(vm.mr, 'pipeline', true);
expect(vm.mergeButtonClass).toEqual(defaultClass);
});
it('returns info class for pending status', () => {
vm.mr.pipeline = {};
vm.mr.isPipelineActive = true;
Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
expect(vm.mergeButtonClass).toEqual(inActionClass);
});
......@@ -198,69 +206,82 @@ describe('ReadyToMerge', () => {
});
describe('mergeButtonText', () => {
it('should return Merge', () => {
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', () => {
vm.isMergingImmediately = true;
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', () => {
vm.isMergingImmediately = false;
vm.mr.isPipelineActive = true;
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('shouldShowMergeOptionsDropdown', () => {
it('should return false with initial data', () => {
expect(vm.shouldShowMergeOptionsDropdown).toBeFalsy();
it('should return false when no auto merge strategies are available', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
});
it('should return true when pipeline active', () => {
vm.mr.isPipelineActive = true;
it('should return true when at least one auto merge strategy is available', () => {
Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
expect(vm.shouldShowMergeOptionsDropdown).toBeTruthy();
expect(vm.shouldShowMergeOptionsDropdown).toBe(true);
});
it('should return false when pipeline active but only merge when pipeline succeeds set in project options', () => {
vm.mr.isPipelineActive = true;
vm.mr.onlyAllowMergeIfPipelineSucceeds = true;
Vue.set(vm.mr, 'availableAutoMergeStrategies', [ATMTWPS_MERGE_STRATEGY]);
Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
expect(vm.shouldShowMergeOptionsDropdown).toBeFalsy();
expect(vm.shouldShowMergeOptionsDropdown).toBe(false);
});
});
describe('isMergeButtonDisabled', () => {
it('should return false with initial data', () => {
vm.mr.isMergeAllowed = true;
Vue.set(vm.mr, 'isMergeAllowed', true);
expect(vm.isMergeButtonDisabled).toBeFalsy();
expect(vm.isMergeButtonDisabled).toBe(false);
});
it('should return true when there is no commit message', () => {
vm.mr.isMergeAllowed = true;
vm.commitMessage = '';
Vue.set(vm.mr, 'isMergeAllowed', true);
Vue.set(vm, 'commitMessage', '');
expect(vm.isMergeButtonDisabled).toBeTruthy();
expect(vm.isMergeButtonDisabled).toBe(true);
});
it('should return true if merge is not allowed', () => {
vm.mr.isMergeAllowed = false;
vm.mr.onlyAllowMergeIfPipelineSucceeds = true;
Vue.set(vm.mr, 'isMergeAllowed', false);
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
Vue.set(vm.mr, 'onlyAllowMergeIfPipelineSucceeds', true);
expect(vm.isMergeButtonDisabled).toBeTruthy();
expect(vm.isMergeButtonDisabled).toBe(true);
});
it('should return true when the vm instance is making request', () => {
vm.mr.isMergeAllowed = true;
vm.isMakingRequest = true;
Vue.set(vm.mr, 'isMergeAllowed', true);
Vue.set(vm, 'isMakingRequest', true);
expect(vm.isMergeButtonDisabled).toBeTruthy();
expect(vm.isMergeButtonDisabled).toBe(true);
});
});
});
......@@ -268,31 +289,31 @@ describe('ReadyToMerge', () => {
describe('methods', () => {
describe('shouldShowMergeControls', () => {
it('should return false when an external pipeline is running and required to succeed', () => {
vm.mr.isMergeAllowed = false;
vm.mr.isPipelineActive = false;
Vue.set(vm.mr, 'isMergeAllowed', false);
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.shouldShowMergeControls).toBeFalsy();
expect(vm.shouldShowMergeControls).toBe(false);
});
it('should return true when the build succeeded or build not required to succeed', () => {
vm.mr.isMergeAllowed = true;
vm.mr.isPipelineActive = false;
Vue.set(vm.mr, 'isMergeAllowed', true);
Vue.set(vm.mr, 'availableAutoMergeStrategies', []);
expect(vm.shouldShowMergeControls).toBeTruthy();
expect(vm.shouldShowMergeControls).toBe(true);
});
it('should return true when showing the MWPS button and a pipeline is running that needs to be successful', () => {
vm.mr.isMergeAllowed = false;
vm.mr.isPipelineActive = true;
Vue.set(vm.mr, 'isMergeAllowed', false);
Vue.set(vm.mr, 'availableAutoMergeStrategies', [MWPS_MERGE_STRATEGY]);
expect(vm.shouldShowMergeControls).toBeTruthy();
expect(vm.shouldShowMergeControls).toBe(true);
});
it('should return true when showing the MWPS button but not required for the pipeline to succeed', () => {
vm.mr.isMergeAllowed = true;
vm.mr.isPipelineActive = true;
Vue.set(vm.mr, 'isMergeAllowed', true);
Vue.set(vm.mr, 'availableAutoMergeStrategies', [MWPS_MERGE_STRATEGY]);
expect(vm.shouldShowMergeControls).toBeTruthy();
expect(vm.shouldShowMergeControls).toBe(true);
});
});
......@@ -325,7 +346,6 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick(true);
setTimeout(() => {
expect(vm.autoMergeStrategy).toBe('merge_when_pipeline_succeeds');
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('MRWidgetUpdateRequested');
......@@ -349,14 +369,13 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick(false, true);
setTimeout(() => {
expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.isMakingRequest).toBeTruthy();
expect(eventHub.$emit).toHaveBeenCalledWith('FailedToMerge', undefined);
const params = vm.service.merge.calls.argsFor(0)[0];
expect(params.should_remove_source_branch).toBeTruthy();
expect(params.merge_when_pipeline_succeeds).toBeFalsy();
expect(params.auto_merge_strategy).toBeUndefined();
done();
}, 333);
});
......@@ -367,14 +386,13 @@ describe('ReadyToMerge', () => {
vm.handleMergeButtonClick();
setTimeout(() => {
expect(vm.autoMergeStrategy).toBeUndefined();
expect(vm.isMakingRequest).toBeTruthy();
expect(vm.initiateMergePolling).toHaveBeenCalled();
const params = vm.service.merge.calls.argsFor(0)[0];
expect(params.should_remove_source_branch).toBeTruthy();
expect(params.merge_when_pipeline_succeeds).toBeFalsy();
expect(params.auto_merge_strategy).toBeUndefined();
done();
}, 333);
});
......
......@@ -25,7 +25,6 @@ export default {
},
merge_status: 'can_be_merged',
merge_user_id: null,
merge_when_pipeline_succeeds: false,
source_branch: 'daaaa',
source_branch_link: 'daaaa',
source_project_id: 19,
......@@ -210,8 +209,7 @@ export default {
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_merge_when_pipeline_succeeds_path:
'/root/acets-app/merge_requests/22/cancel_merge_when_pipeline_succeeds',
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',
......@@ -237,6 +235,9 @@ export default {
merge_request_pipelines_docs_path: '/help/ci/merge_request_pipelines/index.md',
squash: true,
visual_review_app_available: true,
merge_trains_enabled: true,
merge_trains_count: 3,
merge_train_index: 1,
};
export const mockStore = {
......
......@@ -82,5 +82,47 @@ describe('MergeRequestStore', () => {
expect(store.isNothingToMergeState).toEqual(false);
});
});
describe('mergePipelinesEnabled', () => {
it('should set mergePipelinesEnabled = true when merge_pipelines_enabled is true', () => {
store.setData({ ...mockData, merge_pipelines_enabled: true });
expect(store.mergePipelinesEnabled).toBe(true);
});
it('should set mergePipelinesEnabled = false when merge_pipelines_enabled is not provided', () => {
store.setData({ ...mockData, merge_pipelines_enabled: undefined });
expect(store.mergePipelinesEnabled).toBe(false);
});
});
describe('mergeTrainsCount', () => {
it('should set mergeTrainsCount when merge_trains_count is provided', () => {
store.setData({ ...mockData, merge_trains_count: 3 });
expect(store.mergeTrainsCount).toBe(3);
});
it('should set mergeTrainsCount = 0 when merge_trains_count is not provided', () => {
store.setData({ ...mockData, merge_trains_count: undefined });
expect(store.mergeTrainsCount).toBe(0);
});
});
describe('mergeTrainIndex', () => {
it('should set mergeTrainIndex when merge_train_index is provided', () => {
store.setData({ ...mockData, merge_train_index: 3 });
expect(store.mergeTrainIndex).toBe(3);
});
it('should not set mergeTrainIndex when merge_train_index is not provided', () => {
store.setData({ ...mockData, merge_train_index: undefined });
expect(store.mergeTrainIndex).toBeUndefined();
});
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment