Commit 4ed54d02 authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch 'prettier-vue-mr-widgets' into 'master'

Migrates EE-only js components to single file components

See merge request gitlab-org/gitlab-ee!5729
parents 26263a1c beb4c23e
......@@ -26,7 +26,7 @@ export { default as ConflictsState } from './components/states/mr_widget_conflic
export { default as NothingToMergeState } from './components/states/nothing_to_merge.vue';
export { default as MissingBranchState } from './components/states/mr_widget_missing_branch.vue';
export { default as NotAllowedState } from './components/states/mr_widget_not_allowed.vue';
export { default as ReadyToMergeState } from 'ee/vue_merge_request_widget/components/states/mr_widget_ready_to_merge';
export { default as ReadyToMergeState } from 'ee/vue_merge_request_widget/components/states/mr_widget_ready_to_merge.vue';
export { default as ShaMismatchState } from './components/states/sha_mismatch.vue';
export { default as UnresolvedDiscussionsState } from './components/states/unresolved_discussions.vue';
export { default as PipelineBlockedState } from './components/states/mr_widget_pipeline_blocked.vue';
......
<script>
import { n__, s__, sprintf } from '~/locale';
import Flash from '~/flash';
import MRWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue';
import MrWidgetAuthor from '~/vue_merge_request_widget/components/mr_widget_author.vue';
import eventHub from '~/vue_merge_request_widget/event_hub';
export default {
name: 'approvals-body',
name: 'ApprovalsBody',
components: {
MrWidgetAuthor,
},
props: {
mr: {
type: Object,
......@@ -17,27 +21,29 @@ export default {
approvedBy: {
type: Array,
required: false,
default: () => [],
},
approvalsLeft: {
type: Number,
required: false,
default: 0,
},
userCanApprove: {
type: Boolean,
required: false,
default: false,
},
userHasApproved: {
type: Boolean,
required: false,
default: false,
},
suggestedApprovers: {
type: Array,
required: false,
default: () => [],
},
},
components: {
'mr-widget-author': MRWidgetAuthor,
},
data() {
return {
approving: false,
......@@ -102,32 +108,39 @@ export default {
});
},
},
template: `
<div class="approvals-body space-children">
<span v-if="showApproveButton" class="approvals-approve-button-wrap">
<button
:disabled="approving"
@click="approveMergeRequest"
class="btn btn-primary btn-sm approve-btn"
:class="approveButtonClass">
<i
v-if="approving"
class="fa fa-spinner fa-spin"
aria-hidden="true" />
{{approveButtonText}}
</button>
</span>
<span class="approvals-required-text bold">
{{approvalsRequiredStringified}}
<span v-if="showSuggestedApprovers">
<mr-widget-author
v-for="approver in suggestedApprovers"
:key="approver.username"
:author="approver"
:show-author-name="false"
:show-author-tooltip="true" />
</span>
</span>
</div>
`,
};
</script>
<template>
<div class="approvals-body space-children">
<span
v-if="showApproveButton"
class="approvals-approve-button-wrap"
>
<button
:disabled="approving"
@click="approveMergeRequest"
class="btn btn-primary btn-sm approve-btn"
:class="approveButtonClass"
>
<i
v-if="approving"
class="fa fa-spinner fa-spin"
aria-hidden="true"
></i>
{{ approveButtonText }}
</button>
</span>
<span class="approvals-required-text bold">
{{ approvalsRequiredStringified }}
<span v-if="showSuggestedApprovers">
<mr-widget-author
v-for="approver in suggestedApprovers"
:key="approver.username"
:author="approver"
:show-author-name="false"
:show-author-tooltip="true"
/>
</span>
</span>
</div>
</template>
<script>
import Flash from '~/flash';
import LinkToMemberAvatar from 'ee/vue_shared/components/link_to_member_avatar';
import LinkToMemberAvatar from 'ee/vue_shared/components/link_to_member_avatar.vue';
import { s__ } from '~/locale';
import eventHub from '~/vue_merge_request_widget/event_hub';
export default {
name: 'approvals-footer',
name: 'ApprovalsFooter',
components: {
LinkToMemberAvatar,
},
props: {
mr: {
type: Object,
......@@ -17,22 +21,27 @@ export default {
approvedBy: {
type: Array,
required: false,
default: () => [],
},
approvalsLeft: {
type: Number,
required: false,
default: 0,
},
userCanApprove: {
type: Boolean,
required: false,
default: false,
},
userHasApproved: {
type: Boolean,
required: false,
default: false,
},
suggestedApprovers: {
type: Array,
required: false,
default: () => [],
},
},
data() {
......@@ -40,9 +49,6 @@ export default {
unapproving: false,
};
},
components: {
'link-to-member-avatar': LinkToMemberAvatar,
},
computed: {
showUnapproveButton() {
const isMerged = this.mr.state === 'merged';
......@@ -58,8 +64,9 @@ export default {
methods: {
unapproveMergeRequest() {
this.unapproving = true;
this.service.unapproveMergeRequest()
.then((data) => {
this.service
.unapproveMergeRequest()
.then(data => {
this.mr.setApprovals(data);
eventHub.$emit('MRWidgetUpdateRequested');
this.unapproving = false;
......@@ -70,45 +77,49 @@ export default {
});
},
},
template: `
<div
v-if="approvedBy.length"
class="approved-by-users approvals-footer clearfix mr-info-list">
<div class="approvers-prefix">
<p>{{approvedByText}}</p>
<div class="approvers-list">
<link-to-member-avatar
v-for="(approver, index) in approvedBy"
:key="index"
:avatar-size="20"
:avatar-url="approver.user.avatar_url"
extra-link-class="approver-avatar js-approver-list-member"
:display-name="approver.user.name"
:profile-url="approver.user.web_url"
:show-tooltip="true"
/>
<link-to-member-avatar
v-for="n in approvalsLeft"
:key="n"
:avatar-size="20"
:clickable="false"
:show-tooltip="false"
/>
</div>
<button
v-if="showUnapproveButton"
type="button"
:disabled="unapproving"
@click="unapproveMergeRequest"
class="btn btn-sm unapprove-btn-wrap">
<i
v-if="unapproving"
class="fa fa-spinner fa-spin"
aria-hidden="true">
</i>
{{removeApprovalText}}
</button>
};
</script>
<template>
<div
v-if="approvedBy.length"
class="approved-by-users approvals-footer clearfix mr-info-list"
>
<div class="approvers-prefix">
<p>{{ approvedByText }}</p>
<div class="approvers-list">
<link-to-member-avatar
v-for="(approver, index) in approvedBy"
:key="index"
:avatar-size="20"
:avatar-url="approver.user.avatar_url"
extra-link-class="approver-avatar js-approver-list-member"
:display-name="approver.user.name"
:profile-url="approver.user.web_url"
:show-tooltip="true"
/>
<link-to-member-avatar
v-for="n in approvalsLeft"
:key="n"
:avatar-size="20"
:clickable="false"
:show-tooltip="false"
/>
</div>
<button
v-if="showUnapproveButton"
type="button"
:disabled="unapproving"
@click="unapproveMergeRequest"
class="btn btn-sm unapprove-btn-wrap"
>
<i
v-if="unapproving"
class="fa fa-spinner fa-spin"
aria-hidden="true"
>
</i>
{{ removeApprovalText }}
</button>
</div>
`,
};
</div>
</template>
<script>
import Flash from '~/flash';
import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
import { s__ } from '~/locale';
import ApprovalsBody from './approvals_body';
import ApprovalsFooter from './approvals_footer';
import ApprovalsBody from './approvals_body.vue';
import ApprovalsFooter from './approvals_footer.vue';
export default {
name: 'MRWidgetApprovals',
components: {
ApprovalsBody,
ApprovalsFooter,
statusIcon,
},
props: {
mr: {
type: Object,
......@@ -21,11 +27,7 @@ export default {
fetchingApprovals: true,
};
},
components: {
'approvals-body': ApprovalsBody,
'approvals-footer': ApprovalsFooter,
statusIcon,
},
computed: {
status() {
if (this.mr.approvals.approvals_left > 0) {
......@@ -35,51 +37,64 @@ export default {
},
},
created() {
const flashErrorMessage = s__('mrWidget|An error occured while retrieving approval data for this merge request.');
const flashErrorMessage = s__(
'mrWidget|An error occured while retrieving approval data for this merge request.',
);
this.service.fetchApprovals()
.then((data) => {
this.service
.fetchApprovals()
.then(data => {
this.mr.setApprovals(data);
this.fetchingApprovals = false;
})
.catch(() => new Flash(flashErrorMessage));
},
template: `
};
</script>
<template>
<div
v-if="mr.approvalsRequired"
class="mr-widget-approvals-container mr-widget-body mr-widget-section media"
>
<div
v-if="mr.approvalsRequired"
class="mr-widget-approvals-container mr-widget-body mr-widget-section media">
<div
v-if="fetchingApprovals"
class="mr-widget-icon">
<i class="fa fa-spinner fa-spin" />
</div>
<status-icon v-else :status="status" />
<div
v-show="fetchingApprovals"
class="mr-approvals-loading-state media-body">
<span class="approvals-loading-text">
Checking approval status
</span>
</div>
<div
v-if="!fetchingApprovals"
class="approvals-components media-body">
<approvals-body
:mr="mr"
:service="service"
:user-can-approve="mr.approvals.user_can_approve"
:user-has-approved="mr.approvals.user_has_approved"
:approved-by="mr.approvals.approved_by"
:approvals-left="mr.approvals.approvals_left"
:suggested-approvers="mr.approvals.suggested_approvers" />
<approvals-footer
:mr="mr"
:service="service"
:user-can-approve="mr.approvals.user_can_approve"
:user-has-approved="mr.approvals.user_has_approved"
:approved-by="mr.approvals.approved_by"
:approvals-left="mr.approvals.approvals_left" />
</div>
v-if="fetchingApprovals"
class="mr-widget-icon"
>
<i class="fa fa-spinner fa-spin"></i>
</div>
`,
};
<status-icon
v-else
:status="status"
/>
<div
v-show="fetchingApprovals"
class="mr-approvals-loading-state media-body"
>
<span class="approvals-loading-text">
Checking approval status
</span>
</div>
<div
v-if="!fetchingApprovals"
class="approvals-components media-body"
>
<approvals-body
:mr="mr"
:service="service"
:user-can-approve="mr.approvals.user_can_approve"
:user-has-approved="mr.approvals.user_has_approved"
:approved-by="mr.approvals.approved_by"
:approvals-left="mr.approvals.approvals_left"
:suggested-approvers="mr.approvals.suggested_approvers"
/>
<approvals-footer
:mr="mr"
:service="service"
:user-can-approve="mr.approvals.user_can_approve"
:user-has-approved="mr.approvals.user_has_approved"
:approved-by="mr.approvals.approved_by"
:approvals-left="mr.approvals.approvals_left"
/>
</div>
</div>
</template>
<script>
/**
* Renders Code quality body text
* Fixed: [name] in [link]:[line]
*/
import ReportLink from 'ee/vue_shared/security_reports/components/report_link.vue';
/**
* Renders Code quality body text
* Fixed: [name] in [link]:[line]
*/
import ReportLink from 'ee/vue_shared/security_reports/components/report_link.vue';
export default {
name: 'CodequalityIssueBody',
export default {
name: 'CodequalityIssueBody',
components: {
ReportLink,
},
components: {
ReportLink,
},
props: {
isStatusSuccess: {
type: Boolean,
required: true,
},
issue: {
type: Object,
required: true,
},
props: {
isStatusSuccess: {
type: Boolean,
required: true,
},
issue: {
type: Object,
required: true,
},
};
},
};
</script>
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
......
<script>
/**
* Renders Perfomance issue body text
* [name] :[score] [symbol] [delta] in [link]
*/
import ReportLink from 'ee/vue_shared/security_reports/components/report_link.vue';
/**
* Renders Perfomance issue body text
* [name] :[score] [symbol] [delta] in [link]
*/
import ReportLink from 'ee/vue_shared/security_reports/components/report_link.vue';
export default {
name: 'PerformanceIssueBody',
export default {
name: 'PerformanceIssueBody',
components: {
ReportLink,
},
components: {
ReportLink,
},
props: {
issue: {
type: Object,
required: true,
},
props: {
issue: {
type: Object,
required: true,
},
},
methods: {
formatScore(value) {
if (Math.floor(value) !== value) {
return parseFloat(value).toFixed(2);
}
return value;
},
methods: {
formatScore(value) {
if (Math.floor(value) !== value) {
return parseFloat(value).toFixed(2);
}
return value;
},
};
},
};
</script>
<template>
<div class="report-block-list-issue-description prepend-top-5 append-bottom-5">
......
<script>
import eventHub from '~/vue_merge_request_widget/event_hub';
import ReadyToMergeState from '~/vue_merge_request_widget/components/states/ready_to_merge.vue';
import SquashBeforeMerge from './mr_widget_squash_before_merge.vue';
export default {
extends: ReadyToMergeState,
name: 'ReadyToMerge',
components: {
'squash-before-merge': SquashBeforeMerge,
SquashBeforeMerge,
},
extends: ReadyToMergeState,
data() {
return {
additionalParams: {
......@@ -15,6 +16,11 @@ export default {
},
};
},
created() {
eventHub.$on('MRWidgetUpdateSquash', val => {
this.additionalParams.squash = val;
});
},
methods: {
// called in CE super component before form submission
setAdditionalParams(options) {
......@@ -23,9 +29,5 @@ export default {
}
},
},
created() {
eventHub.$on('MRWidgetUpdateSquash', (val) => {
this.additionalParams.squash = val;
});
},
};
</script>
import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
export default {
props: {
mr: {
type: Object,
required: true,
},
},
components: {
statusIcon,
},
template: `
<div class="media">
<status-icon status="warning" showDisabledButton />
<div class="media-body">
<span class="bold">
Merge requests are read-only in a secondary Geo node
</span>
<a
:href="mr.geoSecondaryHelpPath"
data-title="About this feature"
data-toggle="tooltip"
data-placement="bottom"
target="_blank"
rel="noopener noreferrer nofollow"
data-container="body">
<i class="fa fa-question-circle"></i>
</a>
</div>
</div>
`,
};
<script>
import statusIcon from '~/vue_merge_request_widget/components/mr_widget_status_icon.vue';
export default {
components: {
statusIcon,
},
props: {
mr: {
type: Object,
required: true,
},
},
};
</script>
<template>
<div class="media">
<status-icon
status="warning"
show-disabled-button
/>
<div class="media-body">
<span class="bold">
Merge requests are read-only in a secondary Geo node
</span>
<a
:href="mr.geoSecondaryHelpPath"
data-title="About this feature"
data-toggle="tooltip"
data-placement="bottom"
target="_blank"
rel="noopener noreferrer nofollow"
data-container="body"
>
<i class="fa fa-question-circle"></i>
</a>
</div>
</div>
</template>
......@@ -51,7 +51,8 @@ export default {
>
<i
class="fa fa-question-circle"
aria-hidden="true">
aria-hidden="true"
>
</i>
</a>
</div>
......
<script>
import { n__, s__, __, sprintf } from '~/locale';
import CEWidgetOptions from '~/vue_merge_request_widget/mr_widget_options.vue';
import WidgetApprovals from './components/approvals/mr_widget_approvals';
import GeoSecondaryNode from './components/states/mr_widget_secondary_geo_node';
import WidgetApprovals from './components/approvals/mr_widget_approvals.vue';
import GeoSecondaryNode from './components/states/mr_widget_secondary_geo_node.vue';
import ReportSection from '../vue_shared/security_reports/components/report_section.vue';
import GroupedSecurityReportsApp from '../vue_shared/security_reports/grouped_security_reports_app.vue';
import reportsMixin from '../vue_shared/security_reports/mixins/reports_mixin';
......
......@@ -10,22 +10,19 @@ export default class MRWidgetService extends CEWidgetService {
}
fetchApprovals() {
return axios.get(this.approvalsPath)
.then(res => res.data);
return axios.get(this.approvalsPath).then(res => res.data);
}
approveMergeRequest() {
return axios.post(this.approvalsPath)
.then(res => res.data);
return axios.post(this.approvalsPath).then(res => res.data);
}
unapproveMergeRequest() {
return axios.delete(this.approvalsPath)
.then(res => res.data);
return axios.delete(this.approvalsPath).then(res => res.data);
}
fetchReport(endpoint) { // eslint-disable-line
return axios.get(endpoint)
.then(res => res.data);
// eslint-disable-next-line class-methods-use-this
fetchReport(endpoint) {
return axios.get(endpoint).then(res => res.data);
}
}
import CEGetStateKey from '~/vue_merge_request_widget/stores/get_state_key';
export default function (data) {
export default function(data) {
if (this.isGeoSecondaryNode) {
return 'geoSecondaryNode';
}
return CEGetStateKey.call(this, data);
}
<script>
// Analogue of link_to_member_avatar in app/helpers/projects_helper.rb
import pendingAvatarSvg from 'ee_icons/_icon_dotted_circle.svg';
......@@ -6,6 +7,7 @@ export default {
avatarUrl: {
type: String,
required: false,
default: '',
},
profileUrl: {
type: String,
......@@ -15,6 +17,7 @@ export default {
displayName: {
type: String,
required: false,
default: '',
},
extraAvatarClass: {
type: String,
......@@ -39,10 +42,7 @@ export default {
tooltipContainer: {
type: String,
required: false,
},
avatarHtml: {
type: String,
required: false,
default: 'body',
},
avatarSize: {
type: Number,
......@@ -75,32 +75,33 @@ export default {
linkClass() {
return `author_link ${this.tooltipClass} ${this.extraLinkClass} ${this.disabledClass}`;
},
tooltipContainerAttr() {
return this.tooltipContainer || 'body';
},
},
template: `
<div class="link-to-member-avatar">
<a
:href="profileUrl"
:class="linkClass"
:title="displayName"
:data-container="tooltipContainerAttr">
<img
v-if="avatarUrl"
:class="avatarClass"
:src="avatarUrl"
:width="avatarSize"
:height="avatarSize"
:alt="displayName"/>
<span
v-else
v-html="pendingAvatarSvg"
:class="avatarHtmlClass"
:width="avatarSize"
:height="avatarSize">
</span>
</a>
</div>
`,
};
</script>
<template>
<div class="link-to-member-avatar">
<a
:href="profileUrl"
:class="linkClass"
:title="displayName"
:data-container="tooltipContainer"
>
<img
v-if="avatarUrl"
:class="avatarClass"
:src="avatarUrl"
:width="avatarSize"
:height="avatarSize"
:alt="displayName"
/>
<span
v-else
v-html="pendingAvatarSvg"
:class="avatarHtmlClass"
:width="avatarSize"
:height="avatarSize"
>
</span>
</a>
</div>
</template>
import Vue from 'vue';
import _ from 'underscore';
import ApprovalsBody from 'ee/vue_merge_request_widget/components/approvals/approvals_body';
(() => {
gl.ApprovalsStore = {
data: {},
initStoreOnce() {
return {
then() {},
};
import ApprovalsBody from 'ee/vue_merge_request_widget/components/approvals/approvals_body.vue';
describe('Approvals Body Component', () => {
let vm;
const initialData = {
mr: {
isOpen: true,
},
service: {},
suggestedApprovers: [{ name: 'Approver 1' }],
userCanApprove: false,
userHasApproved: true,
approvedBy: [],
approvalsLeft: 1,
pendingAvatarSvg: '<svg></svg>',
checkmarkSvg: '<svg></svg>',
};
function initApprovalsBodyComponent() {
setFixtures(`
<div>
<div id="mock-container"></div>
</div>
`);
this.initialData = {
mr: {
isOpen: true,
},
service: {},
suggestedApprovers: [{ name: 'Approver 1' }],
userCanApprove: false,
userHasApproved: true,
approvedBy: [],
approvalsLeft: 1,
pendingAvatarSvg: '<svg></svg>',
checkmarkSvg: '<svg></svg>',
};
beforeEach(() => {
setFixtures('<div id="mock-container"></div>');
const ApprovalsBodyComponent = Vue.extend(ApprovalsBody);
this.approvalsBody = new ApprovalsBodyComponent({
vm = new ApprovalsBodyComponent({
el: '#mock-container',
propsData: this.initialData,
propsData: initialData,
});
}
});
describe('Approvals Body Component', function() {
beforeEach(function() {
initApprovalsBodyComponent.call(this);
});
afterEach(() => {
vm.$destroy();
});
it('should correctly set component props', function() {
const approvalsBody = this.approvalsBody;
_.each(approvalsBody, (propValue, propKey) => {
if (this.initialData[propKey]) {
expect(approvalsBody[propKey]).toBe(this.initialData[propKey]);
}
});
it('should correctly set component props', () => {
Object.keys(vm).forEach(propKey => {
if (initialData[propKey]) {
expect(vm[propKey]).toBe(initialData[propKey]);
}
});
});
describe('Computed properties', function() {
describe('approvalsRequiredStringified', function() {
it('should display the correct string for 1 possible approver', function() {
const correctText = 'Requires 1 more approval by';
expect(this.approvalsBody.approvalsRequiredStringified).toBe(correctText);
});
describe('Computed properties', () => {
describe('approvalsRequiredStringified', () => {
it('should display the correct string for 1 possible approver', () => {
const correctText = 'Requires 1 more approval by';
expect(vm.approvalsRequiredStringified).toBe(correctText);
});
it('should display the correct string for 2 possible approvers', function(done) {
this.approvalsBody.approvalsLeft = 2;
this.approvalsBody.suggestedApprovers.push({ name: 'Approver 2' });
it('should display the correct string for 2 possible approvers', done => {
vm.approvalsLeft = 2;
vm.suggestedApprovers.push({ name: 'Approver 2' });
Vue.nextTick(() => {
const correctText = 'Requires 2 more approvals by';
expect(this.approvalsBody.approvalsRequiredStringified).toBe(correctText);
done();
});
Vue.nextTick(() => {
const correctText = 'Requires 2 more approvals by';
expect(vm.approvalsRequiredStringified).toBe(correctText);
done();
});
});
it('shows the "Approved" text message when there is enough approvals in place', function(done) {
this.approvalsBody.approvalsLeft = 0;
it('shows the "Approved" text message when there is enough approvals in place', done => {
vm.approvalsLeft = 0;
Vue.nextTick(() => {
expect(this.approvalsBody.approvalsRequiredStringified).toBe('Approved');
done();
});
Vue.nextTick(() => {
expect(vm.approvalsRequiredStringified).toBe('Approved');
done();
});
});
it('shows the "Requires 1 more approval" without by when no suggested approvals are available', function(done) {
const correctText = 'Requires 1 more approval';
this.approvalsBody.suggestedApprovers = [];
it('shows the "Requires 1 more approval" without by when no suggested approvals are available', done => {
const correctText = 'Requires 1 more approval';
vm.suggestedApprovers = [];
Vue.nextTick(() => {
expect(this.approvalsBody.approvalsRequiredStringified).toBe(correctText);
done();
});
Vue.nextTick(() => {
expect(vm.approvalsRequiredStringified).toBe(correctText);
done();
});
});
});
describe('showApproveButton', function() {
it('should not be true when the user cannot approve', function(done) {
this.approvalsBody.userCanApprove = false;
this.approvalsBody.userHasApproved = true;
Vue.nextTick(() => {
expect(this.approvalsBody.showApproveButton).toBe(false);
done();
});
describe('showApproveButton', () => {
it('should not be true when the user cannot approve', done => {
vm.userCanApprove = false;
vm.userHasApproved = true;
Vue.nextTick(() => {
expect(vm.showApproveButton).toBe(false);
done();
});
});
it('should be true when the user can approve', function(done) {
this.approvalsBody.userCanApprove = true;
this.approvalsBody.userHasApproved = false;
Vue.nextTick(() => {
expect(this.approvalsBody.showApproveButton).toBe(true);
done();
});
it('should be true when the user can approve', done => {
vm.userCanApprove = true;
vm.userHasApproved = false;
Vue.nextTick(() => {
expect(vm.showApproveButton).toBe(true);
done();
});
});
});
describe('approveButtonText', function() {
it('The approve button should have the "Approve" text', function(done) {
this.approvalsBody.approvalsLeft = 1;
this.approvalsBody.userHasApproved = false;
this.approvalsBody.userCanApprove = true;
describe('approveButtonText', () => {
it('The approve button should have the "Approve" text', done => {
vm.approvalsLeft = 1;
vm.userHasApproved = false;
vm.userCanApprove = true;
Vue.nextTick(() => {
expect(this.approvalsBody.approveButtonText).toBe('Approve');
done();
});
Vue.nextTick(() => {
expect(vm.approveButtonText).toBe('Approve');
done();
});
});
it('The approve button should have the "Add approval" text', function(done) {
this.approvalsBody.approvalsLeft = 0;
this.approvalsBody.userHasApproved = false;
this.approvalsBody.userCanApprove = true;
it('The approve button should have the "Add approval" text', done => {
vm.approvalsLeft = 0;
vm.userHasApproved = false;
vm.userCanApprove = true;
Vue.nextTick(() => {
expect(this.approvalsBody.approveButtonText).toBe('Add approval');
done();
});
Vue.nextTick(() => {
expect(vm.approveButtonText).toBe('Add approval');
done();
});
});
});
});
})(window.gl || (window.gl = {}));
});
import Vue from 'vue';
import _ from 'underscore';
import pendingAvatarSvg from 'ee_icons/_icon_dotted_circle.svg';
import ApprovalsFooter from 'ee/vue_merge_request_widget/components/approvals/approvals_footer';
import ApprovalsFooter from 'ee/vue_merge_request_widget/components/approvals/approvals_footer.vue';
import { TEST_HOST } from 'spec/test_constants';
(() => {
gl.ApprovalsStore = {
data: {},
initStoreOnce() {
return {
then() {},
};
describe('Approvals Footer Component', () => {
let vm;
const initialData = {
mr: {
state: 'readyToMerge',
},
service: {},
userCanApprove: false,
userHasApproved: true,
approvedBy: [],
approvalsLeft: 1,
pendingAvatarSvg,
};
function initApprovalsFooterComponent() {
setFixtures(`
<div>
<div id="mock-container"></div>
</div>
`);
this.initialData = {
mr: {
state: 'readyToMerge',
},
service: {},
userCanApprove: false,
userHasApproved: true,
approvedBy: [],
approvalsLeft: 1,
pendingAvatarSvg,
};
beforeEach(() => {
setFixtures('<div id="mock-container"></div>');
const ApprovalsFooterComponent = Vue.extend(ApprovalsFooter);
this.approvalsFooter = new ApprovalsFooterComponent({
vm = new ApprovalsFooterComponent({
el: '#mock-container',
propsData: this.initialData,
beforeCreate() {},
propsData: initialData,
});
}
});
describe('Approvals Footer Component', function () {
beforeEach(function () {
initApprovalsFooterComponent.call(this);
});
afterEach(() => {
vm.$destroy();
});
it('should correctly set component props', function () {
const approvalsFooter = this.approvalsFooter;
_.each(approvalsFooter, (propValue, propKey) => {
if (this.initialData[propKey]) {
expect(approvalsFooter[propKey]).toBe(this.initialData[propKey]);
}
});
it('should correctly set component props', () => {
Object.keys(vm).forEach(propKey => {
if (initialData[propKey]) {
expect(vm[propKey]).toBe(initialData[propKey]);
}
});
});
describe('Computed properties', function () {
it('should correctly set showUnapproveButton when the user can unapprove', function () {
expect(this.approvalsFooter.showUnapproveButton).toBeTruthy();
this.approvalsFooter.mr.state = 'merged';
expect(this.approvalsFooter.showUnapproveButton).toBeFalsy();
});
describe('Computed properties', () => {
it('should correctly set showUnapproveButton when the user can unapprove', () => {
expect(vm.showUnapproveButton).toBeTruthy();
vm.mr.state = 'merged';
expect(vm.showUnapproveButton).toBeFalsy();
});
it('should correctly set showUnapproveButton when the user can not unapprove', function (done) {
this.approvalsFooter.userCanApprove = true;
it('should correctly set showUnapproveButton when the user can not unapprove', done => {
vm.userCanApprove = true;
Vue.nextTick(() => {
expect(this.approvalsFooter.showUnapproveButton).toBe(false);
done();
});
Vue.nextTick(() => {
expect(vm.showUnapproveButton).toBe(false);
done();
});
});
});
describe('approvers list', function () {
it('shows link to member avatar for for each approver', function (done) {
this.approvalsFooter.approvedBy.push({
user: {
avatar_url: '/dummy.jpg',
},
});
describe('approvers list', () => {
it('shows link to member avatar for for each approver', done => {
vm.approvedBy.push({
user: {
avatar_url: `${TEST_HOST}/dummy.jpg`,
},
});
Vue.nextTick(() => {
const memberImage = document.querySelector('.approvers-list img');
expect(memberImage.src).toMatch(/dummy\.jpg$/);
done();
});
Vue.nextTick(() => {
const memberImage = document.querySelector('.approvers-list img');
expect(memberImage.src).toMatch(/dummy\.jpg$/);
done();
});
});
});
})(window.gl || (window.gl = {}));
});
/* eslint-disable no-restricted-syntax */
import $ from 'jquery';
import Vue from 'vue';
import linkToMemberAvatar from 'ee/vue_shared/components/link_to_member_avatar';
import linkToMemberAvatar from 'ee/vue_shared/components/link_to_member_avatar.vue';
describe('Link To Members Components', () => {
let vm;
const propsData = {
avatarSize: 32,
avatarUrl: 'myavatarurl.com',
profileUrl: 'profileUrl.com',
displayName: 'mydisplayname',
extraAvatarClass: 'myextraavatarclass',
extraLinkClass: 'myextralinkclass',
showTooltip: true,
};
(() => {
function initComponent(propsData = {}) {
setFixtures(`
<div>
<div id="mock-container"></div>
</div>
`);
beforeEach(() => {
setFixtures('<div id="mock-container"></div>');
const LinkToMembersComponent = Vue.extend(linkToMemberAvatar);
this.component = new LinkToMembersComponent({
vm = new LinkToMembersComponent({
el: '#mock-container',
propsData,
}).$mount();
});
afterEach(() => {
vm.$destroy();
});
it('should default to the body as tooltip container', () => {
expect(vm.tooltipContainer).toBe('body');
});
it('should return a defined Vue component', () => {
expect(vm).toBeDefined();
expect(vm.$data).toBeDefined();
});
it('should have <a> children', () => {
const componentLink = vm.$el.querySelector('a');
expect(componentLink).not.toBeNull();
expect(componentLink.getAttribute('href')).toBe(propsData.profileUrl);
});
it('should show a <img> if the avatarUrl is set', () => {
const avatarImg = vm.$el.querySelector('a img');
expect(avatarImg).not.toBeNull();
expect(avatarImg.getAttribute('src')).toBe(propsData.avatarUrl);
});
it('should fallback to a <svg> if the avatarUrl is not set', done => {
vm.avatarUrl = undefined;
Vue.nextTick(() => {
const avatarImg = vm.$el.querySelector('a svg');
expect(avatarImg).not.toBeNull();
done();
});
});
it('should correctly compute computed values', () => {
const correctVals = {
disabledClass: '',
avatarSizeClass: 's32',
avatarHtmlClass: 's32 avatar avatar-inline avatar-placeholder',
avatarClass: 'avatar avatar-inline s32 myextraavatarclass',
tooltipClass: 'has-tooltip',
linkClass: 'author_link has-tooltip myextralinkclass ',
};
this.$document = $(document);
}
describe('Link To Members Components', function () {
describe('Initialization', function () {
beforeEach(function () {
const propsData = this.propsData = {
avatarSize: 32,
avatarUrl: 'myavatarurl.com',
displayName: 'mydisplayname',
extraAvatarClass: 'myextraavatarclass',
extraLinkClass: 'myextralinkclass',
showTooltip: true,
};
initComponent.call(this, {
propsData,
});
});
it('should return a defined Vue component', function () {
expect(this.component).toBeDefined();
expect(this.component.$data).toBeDefined();
});
it('should have <a> and <svg> children', function () {
const componentLink = this.component.$el.querySelector('a');
const componentPlaceholder = componentLink.querySelector('svg');
expect(componentLink).not.toBeNull();
expect(componentPlaceholder).not.toBeNull();
});
it('should correctly compute computed values', function (done) {
const correctVals = {
disabledClass: '',
avatarSizeClass: 's32',
avatarHtmlClass: 's32 avatar avatar-inline avatar-placeholder',
avatarClass: 'avatar avatar-inline s32 ',
tooltipClass: 'has-tooltip',
linkClass: 'author_link has-tooltip ',
tooltipContainerAttr: 'body',
};
Vue.nextTick(() => {
for (const computedKey in correctVals) {
if (Object.prototype.hasOwnProperty.call(correctVals, computedKey)) {
const expectedVal = correctVals[computedKey];
const actualComputed = this.component[computedKey];
expect(actualComputed).toBe(expectedVal);
}
}
done();
});
});
Object.keys(correctVals).forEach(computedKey => {
const expectedVal = correctVals[computedKey];
const actualComputed = vm[computedKey];
expect(actualComputed).toBe(expectedVal);
});
});
})();
});
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