Commit ed092037 authored by Frédéric Caplette's avatar Frédéric Caplette Committed by Miguel Rincon

Update merge train helper text

We update the merge train helper text
and we move it right under the button for
more clarity. This mean we had to move
the helper text component from the mr-widget-options
to the ready-to-merge component and update the
tests accordingly.
parent 75ce65df
...@@ -15,7 +15,16 @@ import SquashBeforeMerge from './squash_before_merge.vue'; ...@@ -15,7 +15,16 @@ import SquashBeforeMerge from './squash_before_merge.vue';
import CommitsHeader from './commits_header.vue'; import CommitsHeader from './commits_header.vue';
import CommitEdit from './commit_edit.vue'; import CommitEdit from './commit_edit.vue';
import CommitMessageDropdown from './commit_message_dropdown.vue'; import CommitMessageDropdown from './commit_message_dropdown.vue';
import { AUTO_MERGE_STRATEGIES } from '../../constants'; import { AUTO_MERGE_STRATEGIES, DANGER, INFO, WARNING } from '../../constants';
const PIPELINE_RUNNING_STATE = 'running';
const PIPELINE_FAILED_STATE = 'failed';
const PIPELINE_PENDING_STATE = 'pending';
const PIPELINE_SUCCESS_STATE = 'success';
const MERGE_FAILED_STATUS = 'failed';
const MERGE_SUCCESS_STATUS = 'success';
const MERGE_HOOK_VALIDATION_ERROR_STATUS = 'hook_validation_error';
export default { export default {
name: 'ReadyToMerge', name: 'ReadyToMerge',
...@@ -29,6 +38,8 @@ export default { ...@@ -29,6 +38,8 @@ export default {
GlSprintf, GlSprintf,
GlLink, GlLink,
GlDeprecatedButton, GlDeprecatedButton,
MergeTrainHelperText: () =>
import('ee_component/vue_merge_request_widget/components/merge_train_helper_text.vue'),
MergeImmediatelyConfirmationDialog: () => MergeImmediatelyConfirmationDialog: () =>
import( import(
'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue' 'ee_component/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue'
...@@ -60,35 +71,45 @@ export default { ...@@ -60,35 +71,45 @@ export default {
const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr; const { pipeline, isPipelineFailed, hasCI, ciStatus } = this.mr;
if ((hasCI && !ciStatus) || this.hasPipelineMustSucceedConflict) { if ((hasCI && !ciStatus) || this.hasPipelineMustSucceedConflict) {
return 'failed'; return PIPELINE_FAILED_STATE;
} else if (this.isAutoMergeAvailable) { }
return 'pending';
} else if (!pipeline) { if (this.isAutoMergeAvailable) {
return 'success'; return PIPELINE_PENDING_STATE;
} else if (isPipelineFailed) { }
return 'failed';
if (pipeline && isPipelineFailed) {
return PIPELINE_FAILED_STATE;
} }
return 'success'; return PIPELINE_SUCCESS_STATE;
}, },
mergeButtonVariant() { mergeButtonVariant() {
if (this.status === 'failed') { if (this.status === PIPELINE_FAILED_STATE) {
return 'danger'; return DANGER;
} else if (this.status === 'pending') {
return 'info';
} }
return 'success';
if (this.status === PIPELINE_PENDING_STATE) {
return INFO;
}
return PIPELINE_SUCCESS_STATE;
}, },
iconClass() { iconClass() {
if (this.shouldRenderMergeTrainHelperText && !this.mr.preventMerge) {
return PIPELINE_RUNNING_STATE;
}
if ( if (
this.status === 'failed' || this.status === PIPELINE_FAILED_STATE ||
!this.commitMessage.length || !this.commitMessage.length ||
!this.mr.isMergeAllowed || !this.mr.isMergeAllowed ||
this.mr.preventMerge this.mr.preventMerge
) { ) {
return 'warning'; return WARNING;
} }
return 'success';
return PIPELINE_SUCCESS_STATE;
}, },
mergeButtonText() { mergeButtonText() {
if (this.isMergingImmediately) { if (this.isMergingImmediately) {
...@@ -167,11 +188,13 @@ export default { ...@@ -167,11 +188,13 @@ export default {
.merge(options) .merge(options)
.then(res => res.data) .then(res => res.data)
.then(data => { .then(data => {
const hasError = data.status === 'failed' || data.status === 'hook_validation_error'; const hasError =
data.status === MERGE_FAILED_STATUS ||
data.status === MERGE_HOOK_VALIDATION_ERROR_STATUS;
if (AUTO_MERGE_STRATEGIES.includes(data.status)) { if (AUTO_MERGE_STRATEGIES.includes(data.status)) {
eventHub.$emit('MRWidgetUpdateRequested'); eventHub.$emit('MRWidgetUpdateRequested');
} else if (data.status === 'success') { } else if (data.status === MERGE_SUCCESS_STATUS) {
this.initiateMergePolling(); this.initiateMergePolling();
} else if (hasError) { } else if (hasError) {
eventHub.$emit('FailedToMerge', data.merge_error); eventHub.$emit('FailedToMerge', data.merge_error);
...@@ -269,7 +292,7 @@ export default { ...@@ -269,7 +292,7 @@ export default {
<template> <template>
<div> <div>
<div class="mr-widget-body media"> <div class="mr-widget-body media" :class="{ 'gl-pb-3': shouldRenderMergeTrainHelperText }">
<status-icon :status="iconClass" /> <status-icon :status="iconClass" />
<div class="media-body"> <div class="media-body">
<div class="mr-widget-body-controls media space-children"> <div class="mr-widget-body-controls media space-children">
...@@ -358,6 +381,7 @@ export default { ...@@ -358,6 +381,7 @@ export default {
<div <div
v-if="hasPipelineMustSucceedConflict" v-if="hasPipelineMustSucceedConflict"
class="gl-display-flex gl-align-items-center" class="gl-display-flex gl-align-items-center"
data-testid="pipeline-succeed-conflict"
> >
<gl-sprintf :message="pipelineMustSucceedConflictText" /> <gl-sprintf :message="pipelineMustSucceedConflictText" />
<gl-link <gl-link
...@@ -379,6 +403,13 @@ export default { ...@@ -379,6 +403,13 @@ export default {
</div> </div>
</div> </div>
</div> </div>
<merge-train-helper-text
v-if="shouldRenderMergeTrainHelperText"
:pipeline-id="mr.pipeline.id"
:pipeline-link="mr.pipeline.path"
:merge-train-length="mr.mergeTrainsCount"
:merge-train-when-pipeline-succeeds-docs-path="mr.mergeTrainWhenPipelineSucceedsDocsPath"
/>
<template v-if="shouldShowMergeControls"> <template v-if="shouldShowMergeControls">
<div v-if="mr.ffOnlyEnabled" class="mr-fast-forward-message"> <div v-if="mr.ffOnlyEnabled" class="mr-fast-forward-message">
{{ __('Fast-forward merge without a merge commit') }} {{ __('Fast-forward merge without a merge commit') }}
......
...@@ -3,6 +3,7 @@ import { s__ } from '~/locale'; ...@@ -3,6 +3,7 @@ import { s__ } from '~/locale';
export const SUCCESS = 'success'; export const SUCCESS = 'success';
export const WARNING = 'warning'; export const WARNING = 'warning';
export const DANGER = 'danger'; export const DANGER = 'danger';
export const INFO = 'info';
export const WARNING_MESSAGE_CLASS = 'warning_message'; export const WARNING_MESSAGE_CLASS = 'warning_message';
export const DANGER_MESSAGE_CLASS = 'danger_message'; export const DANGER_MESSAGE_CLASS = 'danger_message';
......
<script> <script>
import { escape } from 'lodash'; import { GlLink, GlSprintf } from '@gitlab/ui';
import { GlLink } from '@gitlab/ui'; import { s__ } from '~/locale';
import { s__, sprintf } from '~/locale';
export default { export default {
name: 'MergeTrainHelperText', name: 'MergeTrainHelperText',
components: { components: {
GlLink, GlLink,
GlSprintf,
}, },
props: { props: {
pipelineId: { pipelineId: {
...@@ -27,26 +27,13 @@ export default { ...@@ -27,26 +27,13 @@ export default {
}, },
}, },
computed: { computed: {
message() { helperMessage() {
const text = return this.mergeTrainLength === 0
this.mergeTrainLength === 0
? s__( ? s__(
'mrWidget|This merge request will start a merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds.', 'mrWidget|This action will start a merge train when pipeline %{pipelineLink} succeeds.',
) )
: s__( : s__(
'mrWidget|This merge request will be added to the merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds.', 'mrWidget|This action will add the merge request to the merge train when pipeline %{pipelineLink} succeeds.',
);
const sanitizedPipelineLink = escape(this.pipelineLink);
return sprintf(
text,
{
pipelineId: this.pipelineId,
linkStart: `<a class="js-pipeline-link" href="${sanitizedPipelineLink}">`,
linkEnd: '</a>',
},
false,
); );
}, },
}, },
...@@ -54,15 +41,21 @@ export default { ...@@ -54,15 +41,21 @@ export default {
</script> </script>
<template> <template>
<section class="js-merge-train-helper-text mr-widget-help border-top"> <section class="js-merge-train-helper-text gl-px-5 gl-pb-5">
<span v-html="message"></span> <div class="gl-pl-7">
<gl-sprintf :message="helperMessage">
<template #pipelineLink>
<gl-link data-testid="pipeline-link" :href="pipelineLink">#{{ pipelineId }}</gl-link>
</template>
</gl-sprintf>
<gl-link <gl-link
:href="mergeTrainWhenPipelineSucceedsDocsPath" :href="mergeTrainWhenPipelineSucceedsDocsPath"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="js-documentation-link" data-testid="documentation-link"
> >
{{ s__('mrWidget|More information') }} {{ s__('mrWidget|More information') }}
</gl-link> </gl-link>
</div>
</section> </section>
</template> </template>
import { isNumber, isString } from 'lodash';
import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants'; import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
import { __ } from '~/locale'; import { __ } from '~/locale';
import base from '~/vue_merge_request_widget/mixins/ready_to_merge'; import base from '~/vue_merge_request_widget/mixins/ready_to_merge';
...@@ -48,6 +49,15 @@ export default { ...@@ -48,6 +49,15 @@ export default {
} }
return __('Merge when pipeline succeeds'); return __('Merge when pipeline succeeds');
}, },
shouldRenderMergeTrainHelperText() {
return (
this.mr.pipeline &&
isNumber(this.mr.pipeline.id) &&
isString(this.mr.pipeline.path) &&
this.mr.preferredAutoMergeStrategy === MTWPS_MERGE_STRATEGY &&
!this.mr.autoMergeEnabled
);
},
shouldShowMergeImmediatelyDropdown() { shouldShowMergeImmediatelyDropdown() {
if (this.mr.preferredAutoMergeStrategy === MT_MERGE_STRATEGY) { if (this.mr.preferredAutoMergeStrategy === MT_MERGE_STRATEGY) {
return true; return true;
......
<script> <script>
import { isNumber, isString } from 'lodash';
import GroupedSecurityReportsApp from 'ee/vue_shared/security_reports/grouped_security_reports_app.vue'; import GroupedSecurityReportsApp from 'ee/vue_shared/security_reports/grouped_security_reports_app.vue';
import GroupedMetricsReportsApp from 'ee/vue_shared/metrics_reports/grouped_metrics_reports_app.vue'; import GroupedMetricsReportsApp from 'ee/vue_shared/metrics_reports/grouped_metrics_reports_app.vue';
import reportsMixin from 'ee/vue_shared/security_reports/mixins/reports_mixin'; import reportsMixin from 'ee/vue_shared/security_reports/mixins/reports_mixin';
...@@ -12,12 +11,9 @@ import { s__, __, sprintf } from '~/locale'; ...@@ -12,12 +11,9 @@ import { s__, __, sprintf } from '~/locale';
import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue'; import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
import MrWidgetGeoSecondaryNode from './components/states/mr_widget_secondary_geo_node.vue'; import MrWidgetGeoSecondaryNode from './components/states/mr_widget_secondary_geo_node.vue';
import MrWidgetPolicyViolation from './components/states/mr_widget_policy_violation.vue'; import MrWidgetPolicyViolation from './components/states/mr_widget_policy_violation.vue';
import MergeTrainHelperText from './components/merge_train_helper_text.vue';
import { MTWPS_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
export default { export default {
components: { components: {
MergeTrainHelperText,
MrWidgetLicenses, MrWidgetLicenses,
MrWidgetGeoSecondaryNode, MrWidgetGeoSecondaryNode,
MrWidgetPolicyViolation, MrWidgetPolicyViolation,
...@@ -156,16 +152,6 @@ export default { ...@@ -156,16 +152,6 @@ export default {
this.loadingLoadPerformanceFailed, this.loadingLoadPerformanceFailed,
); );
}, },
shouldRenderMergeTrainHelperText() {
return (
this.mr.pipeline &&
isNumber(this.mr.pipeline.id) &&
isString(this.mr.pipeline.path) &&
this.mr.preferredAutoMergeStrategy === MTWPS_MERGE_STRATEGY &&
!this.mr.autoMergeEnabled
);
},
licensesApiPath() { licensesApiPath() {
return gl?.mrWidgetData?.license_scanning_comparison_path || null; return gl?.mrWidgetData?.license_scanning_comparison_path || null;
}, },
...@@ -371,7 +357,6 @@ export default { ...@@ -371,7 +357,6 @@ export default {
<div class="mr-widget-section"> <div class="mr-widget-section">
<component :is="componentName" :mr="mr" :service="service" /> <component :is="componentName" :mr="mr" :service="service" />
<div class="mr-widget-info"> <div class="mr-widget-info">
<section v-if="mr.allowCollaboration" class="mr-info-list mr-links"> <section v-if="mr.allowCollaboration" class="mr-info-list mr-links">
<p> <p>
...@@ -404,13 +389,6 @@ export default { ...@@ -404,13 +389,6 @@ export default {
<source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" /> <source-branch-removal-status v-if="shouldRenderSourceBranchRemovalStatus" />
</div> </div>
</div> </div>
<merge-train-helper-text
v-if="shouldRenderMergeTrainHelperText"
:pipeline-id="mr.pipeline.id"
:pipeline-link="mr.pipeline.path"
:merge-train-length="mr.mergeTrainsCount"
:merge-train-when-pipeline-succeeds-docs-path="mr.mergeTrainWhenPipelineSucceedsDocsPath"
/>
<div v-if="shouldRenderMergeHelp" class="mr-widget-footer"><mr-widget-merge-help /></div> <div v-if="shouldRenderMergeHelp" class="mr-widget-footer"><mr-widget-merge-help /></div>
</div> </div>
<mr-widget-pipeline-container <mr-widget-pipeline-container
......
---
title: Update Merge Train helper text
merge_request: 38619
author:
type: changed
...@@ -30,7 +30,7 @@ RSpec.describe 'User adds to merge train when pipeline succeeds', :js do ...@@ -30,7 +30,7 @@ RSpec.describe 'User adds to merge train when pipeline succeeds', :js do
expect(page).to have_button('Start merge train when pipeline succeeds') expect(page).to have_button('Start merge train when pipeline succeeds')
within('.js-merge-train-helper-text') do within('.js-merge-train-helper-text') do
expect(page).to have_content("This merge request will start a merge train when pipeline ##{pipeline.id} succeeds.") expect(page).to have_content("This action will start a merge train when pipeline ##{pipeline.id} succeeds.")
expect(page).to have_link('More information', expect(page).to have_link('More information',
href: MergeRequestPresenter.new(merge_request).merge_train_when_pipeline_succeeds_docs_path) href: MergeRequestPresenter.new(merge_request).merge_train_when_pipeline_succeeds_docs_path)
end end
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlLink } from '@gitlab/ui'; import { GlLink, GlSprintf } from '@gitlab/ui';
import { trimText } from 'helpers/text_helper'; import { trimText } from 'helpers/text_helper';
import MergeTrainHelperText from 'ee/vue_merge_request_widget/components/merge_train_helper_text.vue'; import MergeTrainHelperText from 'ee/vue_merge_request_widget/components/merge_train_helper_text.vue';
describe('MergeTrainHelperText', () => { describe('MergeTrainHelperText', () => {
let wrapper; let wrapper;
const factory = propsData => { const defaultProps = {
pipelineId: 123,
pipelineLink: 'path/to/pipeline',
mergeTrainWhenPipelineSucceedsDocsPath: 'path/to/help',
mergeTrainLength: 2,
};
const findDocumentationLink = () => wrapper.find('[data-testid="documentation-link"]');
const findPipelineLink = () => wrapper.find('[data-testid="pipeline-link"]');
const createWrapper = propsData => {
wrapper = shallowMount(MergeTrainHelperText, { wrapper = shallowMount(MergeTrainHelperText, {
propsData, propsData: {
...defaultProps,
...propsData,
},
stubs: {
GlSprintf,
GlLink,
},
}); });
}; };
afterEach(() => { afterEach(() => {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}); });
it('should return the "start" version of the message if there is no existing merge train', () => { it('should return the "start" version of the message if there is no existing merge train', () => {
factory({ createWrapper({ mergeTrainLength: 0 });
pipelineId: 123,
pipelineLink: 'path/to/pipeline',
mergeTrainWhenPipelineSucceedsDocsPath: 'path/to/help',
mergeTrainLength: 0,
});
expect(trimText(wrapper.text())).toBe( expect(trimText(wrapper.text())).toBe(
'This merge request will start a merge train when pipeline #123 succeeds. More information', 'This action will start a merge train when pipeline #123 succeeds. More information',
); );
}); });
it('should render the correct pipeline link in the helper text', () => { it('should render the correct pipeline link in the helper text', () => {
factory({ createWrapper();
pipelineId: 123,
pipelineLink: 'path/to/pipeline',
mergeTrainWhenPipelineSucceedsDocsPath: 'path/to/help',
mergeTrainLength: 2,
});
const pipelineLink = wrapper.find('.js-pipeline-link').element; const pipelineLink = findPipelineLink();
expect(pipelineLink).toExist(); expect(pipelineLink.exists()).toBe(true);
expect(pipelineLink.textContent).toContain('#123'); expect(pipelineLink.text()).toContain('#123');
expect(pipelineLink).toHaveAttr('href', 'path/to/pipeline'); expect(pipelineLink.attributes('href')).toBe(defaultProps.pipelineLink);
});
it('should sanitize the pipeline link', () => {
factory({
pipelineId: 123,
pipelineLink: '"></a> <script>console.log("hacked!!")</script> <a href="',
mergeTrainWhenPipelineSucceedsDocsPath: 'path/to/help',
mergeTrainLength: 2,
});
const pipelineLink = wrapper.find('.js-pipeline-link').element;
expect(pipelineLink).toExist();
// The escaped characters are un-escaped when rendered by the DOM,
// so we expect the value of the "href" attr to be exactly the same
// as the input. If the link was not sanitized, the "href" attr
// would equal "".
expect(pipelineLink).toHaveAttr(
'href',
'"></a> <script>console.log("hacked!!")</script> <a href="',
);
}); });
it('should render the correct documentation link in the helper text', () => { it('should render the correct documentation link in the helper text', () => {
factory({ createWrapper();
pipelineId: 123,
pipelineLink: 'path/to/pipeline',
mergeTrainWhenPipelineSucceedsDocsPath: 'path/to/help',
mergeTrainLength: 2,
});
const docLink = wrapper.find(GlLink); expect(findDocumentationLink().exists()).toBe(true);
expect(findDocumentationLink().attributes('href')).toBe(
expect(docLink.exists()).toBe(true); defaultProps.mergeTrainWhenPipelineSucceedsDocsPath,
expect(docLink.attributes().href).toBe('path/to/help'); );
}); });
}); });
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { MERGE_DISABLED_TEXT_UNAPPROVED } from 'ee/vue_merge_request_widget/mixins/ready_to_merge'; import { MERGE_DISABLED_TEXT_UNAPPROVED } from 'ee/vue_merge_request_widget/mixins/ready_to_merge';
import MergeImmediatelyConfirmationDialog from 'ee/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue'; import MergeImmediatelyConfirmationDialog from 'ee/vue_merge_request_widget/components/merge_immediately_confirmation_dialog.vue';
import MergeTrainHelperText from 'ee/vue_merge_request_widget/components/merge_train_helper_text.vue';
import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue'; import ReadyToMerge from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
import { import {
MWPS_MERGE_STRATEGY, MWPS_MERGE_STRATEGY,
...@@ -11,7 +12,7 @@ import { ...@@ -11,7 +12,7 @@ import {
MERGE_DISABLED_TEXT, MERGE_DISABLED_TEXT,
PIPELINE_MUST_SUCCEED_CONFLICT_TEXT, PIPELINE_MUST_SUCCEED_CONFLICT_TEXT,
} from '~/vue_merge_request_widget/mixins/ready_to_merge'; } from '~/vue_merge_request_widget/mixins/ready_to_merge';
import { GlSprintf } from '@gitlab/ui'; import { GlLink, GlSprintf } from '@gitlab/ui';
describe('ReadyToMerge', () => { describe('ReadyToMerge', () => {
let wrapper; let wrapper;
...@@ -22,9 +23,15 @@ describe('ReadyToMerge', () => { ...@@ -22,9 +23,15 @@ describe('ReadyToMerge', () => {
poll: () => {}, poll: () => {},
}; };
const activePipeline = {
id: 1,
path: 'path/to/pipeline',
active: true,
};
const mr = { const mr = {
isPipelineActive: false, isPipelineActive: false,
pipeline: null, pipeline: { id: 1, path: 'path/to/pipeline' },
isPipelineFailed: false, isPipelineFailed: false,
isPipelinePassing: false, isPipelinePassing: false,
isMergeAllowed: true, isMergeAllowed: true,
...@@ -46,6 +53,8 @@ describe('ReadyToMerge', () => { ...@@ -46,6 +53,8 @@ describe('ReadyToMerge', () => {
preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY, preferredAutoMergeStrategy: MWPS_MERGE_STRATEGY,
availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY], availableAutoMergeStrategies: [MWPS_MERGE_STRATEGY],
mergeImmediatelyDocsPath: 'path/to/merge/immediately/docs', mergeImmediatelyDocsPath: 'path/to/merge/immediately/docs',
mergeTrainWhenPipelineSucceedsDocsPath: '/merge-train/docs',
mergeTrainsCount: 0,
}; };
const factory = (mrUpdates = {}) => { const factory = (mrUpdates = {}) => {
...@@ -56,6 +65,9 @@ describe('ReadyToMerge', () => { ...@@ -56,6 +65,9 @@ describe('ReadyToMerge', () => {
}, },
stubs: { stubs: {
MergeImmediatelyConfirmationDialog, MergeImmediatelyConfirmationDialog,
MergeTrainHelperText,
GlSprintf,
GlLink,
}, },
}); });
...@@ -63,12 +75,22 @@ describe('ReadyToMerge', () => { ...@@ -63,12 +75,22 @@ describe('ReadyToMerge', () => {
}; };
const findResolveItemsMessage = () => wrapper.find(GlSprintf); const findResolveItemsMessage = () => wrapper.find(GlSprintf);
const findPipelineConflictMessage = () =>
wrapper.find('[data-testid="pipeline-succeed-conflict"]');
const findMergeButton = () => wrapper.find('.qa-merge-button'); const findMergeButton = () => wrapper.find('.qa-merge-button');
const findMergeButtonDropdown = () => wrapper.find('.js-merge-moment'); const findMergeButtonDropdown = () => wrapper.find('.js-merge-moment');
const findMergeImmediatelyButton = () => wrapper.find('.js-merge-immediately-button'); const findMergeImmediatelyButton = () => wrapper.find('.js-merge-immediately-button');
const findMergeTrainHelperText = () => wrapper.find(MergeTrainHelperText);
const findMergeTrainPipelineLink = () =>
findMergeTrainHelperText().find('[data-testid="pipeline-link"]');
const findMergeTrainDocumentationLink = () =>
findMergeTrainHelperText().find('[data-testid="documentation-link"]');
afterEach(() => { afterEach(() => {
if (wrapper?.destroy) {
wrapper.destroy(); wrapper.destroy();
wrapper = null;
}
}); });
describe('computed', () => { describe('computed', () => {
...@@ -178,6 +200,87 @@ describe('ReadyToMerge', () => { ...@@ -178,6 +200,87 @@ describe('ReadyToMerge', () => {
}); });
}); });
describe('shouldRenderMergeTrainHelperText', () => {
it('should render the helper text if MTWPS is available and the user has not yet pressed the MTWPS button', () => {
factory({
onlyAllowMergeIfPipelineSucceeds: true,
preferredAutoMergeStrategy: MTWPS_MERGE_STRATEGY,
autoMergeEnabled: false,
});
expect(findMergeTrainHelperText().exists()).toBe(true);
});
});
describe('merge train helper text', () => {
it('does not render the merge train helper text if the MTWPS strategy is not available', () => {
factory({
availableAutoMergeStrategies: [MT_MERGE_STRATEGY],
pipeline: activePipeline,
});
expect(findMergeTrainHelperText().exists()).toBe(false);
});
it('renders the correct merge train helper text when there is an existing merge train', () => {
factory({
onlyAllowMergeIfPipelineSucceeds: true,
preferredAutoMergeStrategy: MTWPS_MERGE_STRATEGY,
autoMergeEnabled: false,
mergeTrainsCount: 2,
pipeline: activePipeline,
});
expect(findMergeTrainHelperText().text()).toContain(
`This action will add the merge request to the merge train when pipeline #${activePipeline.id} succeeds.`,
);
});
it('renders the correct merge train helper text when there is no existing merge train', () => {
factory({
onlyAllowMergeIfPipelineSucceeds: true,
preferredAutoMergeStrategy: MTWPS_MERGE_STRATEGY,
autoMergeEnabled: false,
mergeTrainsCount: 0,
pipeline: activePipeline,
});
expect(findMergeTrainHelperText().text()).toContain(
`This action will start a merge train when pipeline #${activePipeline.id} succeeds.`,
);
});
it('renders the correct pipeline link inside the message', () => {
factory({
onlyAllowMergeIfPipelineSucceeds: true,
preferredAutoMergeStrategy: MTWPS_MERGE_STRATEGY,
autoMergeEnabled: false,
mergeTrainsCount: 0,
pipeline: activePipeline,
});
const pipelineLink = findMergeTrainPipelineLink();
expect(pipelineLink.text()).toContain(activePipeline.id);
expect(pipelineLink.attributes('href')).toBe(activePipeline.path);
});
it('renders the documentation link inside the message', () => {
factory({
onlyAllowMergeIfPipelineSucceeds: true,
preferredAutoMergeStrategy: MTWPS_MERGE_STRATEGY,
autoMergeEnabled: false,
mergeTrainsCount: 0,
pipeline: activePipeline,
});
const pipelineLink = findMergeTrainDocumentationLink();
expect(pipelineLink.text()).toContain('More information');
expect(pipelineLink.attributes('href')).toBe(mr.mergeTrainWhenPipelineSucceedsDocsPath);
});
});
describe('shouldShowMergeImmediatelyDropdown', () => { describe('shouldShowMergeImmediatelyDropdown', () => {
it('should return false if no pipeline is active', () => { it('should return false if no pipeline is active', () => {
factory({ factory({
...@@ -275,7 +378,7 @@ describe('ReadyToMerge', () => { ...@@ -275,7 +378,7 @@ describe('ReadyToMerge', () => {
}); });
it('should show cannot merge text', () => { it('should show cannot merge text', () => {
expect(findResolveItemsMessage().attributes('message')).toBe(MERGE_DISABLED_TEXT); expect(findResolveItemsMessage().text()).toBe(MERGE_DISABLED_TEXT);
}); });
it('should show disabled merge button', () => { it('should show disabled merge button', () => {
...@@ -298,7 +401,7 @@ describe('ReadyToMerge', () => { ...@@ -298,7 +401,7 @@ describe('ReadyToMerge', () => {
}); });
it('should show approvals needed text', () => { it('should show approvals needed text', () => {
expect(findResolveItemsMessage().attributes('message')).toBe(MERGE_DISABLED_TEXT_UNAPPROVED); expect(findResolveItemsMessage().text()).toBe(MERGE_DISABLED_TEXT_UNAPPROVED);
}); });
}); });
...@@ -313,9 +416,7 @@ describe('ReadyToMerge', () => { ...@@ -313,9 +416,7 @@ describe('ReadyToMerge', () => {
}); });
it('should show a custom message that explains the conflict', () => { it('should show a custom message that explains the conflict', () => {
expect(findResolveItemsMessage().attributes('message')).toBe( expect(findPipelineConflictMessage().text()).toBe(PIPELINE_MUST_SUCCEED_CONFLICT_TEXT);
PIPELINE_MUST_SUCCEED_CONFLICT_TEXT,
);
}); });
}); });
}); });
...@@ -16,7 +16,6 @@ import mockData, { ...@@ -16,7 +16,6 @@ import mockData, {
import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants'; import { SUCCESS } from '~/vue_merge_request_widget/components/deployment/constants';
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils'; import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import axios from '~/lib/utils/axios_utils'; import axios from '~/lib/utils/axios_utils';
import { MTWPS_MERGE_STRATEGY, MT_MERGE_STRATEGY } from '~/vue_merge_request_widget/constants';
import { import {
sastDiffSuccessMock, sastDiffSuccessMock,
dastDiffSuccessMock, dastDiffSuccessMock,
...@@ -942,20 +941,6 @@ describe('ee merge request widget options', () => { ...@@ -942,20 +941,6 @@ describe('ee merge request widget options', () => {
expect(vm.shouldRenderApprovals).toBeTruthy(); expect(vm.shouldRenderApprovals).toBeTruthy();
}); });
}); });
describe('shouldRenderMergeTrainHelperText', () => {
it('should return true if MTWPS is available and the user has not yet pressed the MTWPS button', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MTWPS_MERGE_STRATEGY],
auto_merge_enabled: false,
},
});
expect(vm.shouldRenderMergeTrainHelperText).toBe(true);
});
});
}); });
describe('rendering source branch removal status', () => { describe('rendering source branch removal status', () => {
...@@ -1054,115 +1039,6 @@ describe('ee merge request widget options', () => { ...@@ -1054,115 +1039,6 @@ describe('ee merge request widget options', () => {
}); });
}); });
describe('merge train helper text', () => {
const getHelperTextElement = () => vm.$el.querySelector('.js-merge-train-helper-text');
it('does not render the merge train helpe text if the MTWPS strategy is not available', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MT_MERGE_STRATEGY],
pipeline: {
...mockData.pipeline,
active: true,
},
},
});
const helperText = getHelperTextElement();
expect(helperText).not.toExist();
});
it('renders the correct merge train helper text when there is an existing merge train', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MTWPS_MERGE_STRATEGY],
merge_trains_count: 2,
merge_train_when_pipeline_succeeds_docs_path: 'path/to/help',
pipeline: {
...mockData.pipeline,
id: 123,
active: true,
},
},
});
const helperText = getHelperTextElement();
expect(helperText).toExist();
expect(helperText.textContent).toContain(
'This merge request will be added to the merge train when pipeline #123 succeeds.',
);
});
it('renders the correct merge train helper text when there is no existing merge train', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MTWPS_MERGE_STRATEGY],
merge_trains_count: 0,
merge_train_when_pipeline_succeeds_docs_path: 'path/to/help',
pipeline: {
...mockData.pipeline,
id: 123,
active: true,
},
},
});
const helperText = getHelperTextElement();
expect(helperText).toExist();
expect(helperText.textContent).toContain(
'This merge request will start a merge train when pipeline #123 succeeds.',
);
});
it('renders the correct pipeline link inside the message', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MTWPS_MERGE_STRATEGY],
merge_train_when_pipeline_succeeds_docs_path: 'path/to/help',
pipeline: {
...mockData.pipeline,
id: 123,
path: 'path/to/pipeline',
active: true,
},
},
});
const pipelineLink = getHelperTextElement().querySelector('.js-pipeline-link');
expect(pipelineLink).toExist();
expect(pipelineLink.textContent).toContain('#123');
expect(pipelineLink).toHaveAttr('href', 'path/to/pipeline');
});
it('renders the documentation link inside the message', () => {
vm = mountComponent(Component, {
mrData: {
...mockData,
available_auto_merge_strategies: [MTWPS_MERGE_STRATEGY],
merge_train_when_pipeline_succeeds_docs_path: 'path/to/help',
pipeline: {
...mockData.pipeline,
active: true,
},
},
});
const pipelineLink = getHelperTextElement().querySelector('.js-documentation-link');
expect(pipelineLink).toExist();
expect(pipelineLink.textContent).toContain('More information');
expect(pipelineLink).toHaveAttr('href', 'path/to/help');
});
});
describe('data', () => { describe('data', () => {
it('passes approval api paths to service', () => { it('passes approval api paths to service', () => {
const paths = { const paths = {
......
...@@ -29333,19 +29333,19 @@ msgstr "" ...@@ -29333,19 +29333,19 @@ msgstr ""
msgid "mrWidget|There are merge conflicts" msgid "mrWidget|There are merge conflicts"
msgstr "" msgstr ""
msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected." msgid "mrWidget|This action will add the merge request to the merge train when pipeline %{pipelineLink} succeeds."
msgstr "" msgstr ""
msgid "mrWidget|This merge request failed to be merged automatically" msgid "mrWidget|This action will start a merge train when pipeline %{pipelineLink} succeeds."
msgstr "" msgstr ""
msgid "mrWidget|This merge request is in the process of being merged" msgid "mrWidget|This feature merges changes from the target branch to the source branch. You cannot use this feature since the source branch is protected."
msgstr "" msgstr ""
msgid "mrWidget|This merge request will be added to the merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds." msgid "mrWidget|This merge request failed to be merged automatically"
msgstr "" msgstr ""
msgid "mrWidget|This merge request will start a merge train when pipeline %{linkStart}#%{pipelineId}%{linkEnd} succeeds." msgid "mrWidget|This merge request is in the process of being merged"
msgstr "" msgstr ""
msgid "mrWidget|This project is archived, write access has been disabled" msgid "mrWidget|This project is archived, write access has been disabled"
......
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