Add merge train support to MR merge button (CE)

This commit updates the merge request widget's "Merge" button to
support merge trains.
This commit is contained in:
Nathan Friend 2019-06-11 09:19:22 -05:00
parent fc27c93e4b
commit 35ae9d8a74
No known key found for this signature in database
GPG key ID: E010A0869C9F35D9
14 changed files with 273 additions and 104 deletions

View file

@ -1,15 +1,19 @@
<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">

View file

@ -1,4 +1,5 @@
<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>

View file

@ -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,
];

View file

@ -0,0 +1,15 @@
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');
},
},
};

View file

@ -1,3 +1,5 @@
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');
},
},
};

View file

@ -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,

View file

@ -1,7 +1,9 @@
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;
}
}

View file

@ -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',

View file

@ -0,0 +1,5 @@
---
title: Update the merge request widget's "Merge" button to support merge trains
merge_request: 27594
author:
type: added

View file

@ -6045,6 +6045,9 @@ msgstr ""
msgid "Members of <strong>%{project_name}</strong>"
msgstr ""
msgid "Merge"
msgstr ""
msgid "Merge Request"
msgstr ""

View file

@ -1,17 +1,19 @@
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();
});
});
});
});

View file

@ -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);
});

View file

@ -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 = {

View file

@ -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();
});
});
});
});