Merge branch 'kp-issue_6086' into 'master'
CE Backport: Epic issue list and related issue list re-design See merge request gitlab-org/gitlab-ce!23658
This commit is contained in:
commit
7cb0dd9859
7 changed files with 627 additions and 6 deletions
|
@ -15,6 +15,16 @@ export default {
|
|||
type: String,
|
||||
required: true,
|
||||
},
|
||||
cssClass: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '',
|
||||
},
|
||||
tooltipPlacement: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: 'bottom',
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
title() {
|
||||
|
@ -66,15 +76,13 @@ export default {
|
|||
|
||||
<template>
|
||||
<span>
|
||||
<span ref="issueDueDate" class="board-card-info card-number">
|
||||
<icon
|
||||
:class="{ 'text-danger': isPastDue, 'board-card-info-icon': true }"
|
||||
name="calendar"
|
||||
/><time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
|
||||
<span ref="issueDueDate" :class="cssClass" class="board-card-info card-number">
|
||||
<icon :class="{ 'text-danger': isPastDue, 'board-card-info-icon': true }" name="calendar" />
|
||||
<time :class="{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">{{
|
||||
body
|
||||
}}</time>
|
||||
</span>
|
||||
<gl-tooltip :target="() => $refs.issueDueDate" placement="bottom">
|
||||
<gl-tooltip :target="() => $refs.issueDueDate" :placement="tooltipPlacement">
|
||||
<span class="bold">{{ __('Due date') }}</span> <br />
|
||||
<span :class="{ 'text-danger-muted': isPastDue }">{{ title }}</span>
|
||||
</gl-tooltip>
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
<script>
|
||||
import { GlTooltipDirective } from '@gitlab/ui';
|
||||
import { __, sprintf } from '~/locale';
|
||||
|
||||
import UserAvatarLink from '~/vue_shared/components/user_avatar/user_avatar_link.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
UserAvatarLink,
|
||||
},
|
||||
directives: {
|
||||
GlTooltip: GlTooltipDirective,
|
||||
},
|
||||
props: {
|
||||
assignees: {
|
||||
type: Array,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
maxVisibleAssignees: 2,
|
||||
maxAssigneeAvatars: 3,
|
||||
maxAssignees: 99,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
countOverLimit() {
|
||||
return this.assignees.length - this.maxVisibleAssignees;
|
||||
},
|
||||
assigneesToShow() {
|
||||
if (this.assignees.length > this.maxAssigneeAvatars) {
|
||||
return this.assignees.slice(0, this.maxVisibleAssignees);
|
||||
}
|
||||
return this.assignees;
|
||||
},
|
||||
assigneesCounterTooltip() {
|
||||
const { countOverLimit, maxAssignees } = this;
|
||||
const count = countOverLimit > maxAssignees ? maxAssignees : countOverLimit;
|
||||
|
||||
return sprintf(__('%{count} more assignees'), { count });
|
||||
},
|
||||
shouldRenderAssigneesCounter() {
|
||||
const assigneesCount = this.assignees.length;
|
||||
if (assigneesCount <= this.maxAssigneeAvatars) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return assigneesCount > this.countOverLimit;
|
||||
},
|
||||
assigneeCounterLabel() {
|
||||
if (this.countOverLimit > this.maxAssignees) {
|
||||
return `${this.maxAssignees}+`;
|
||||
}
|
||||
|
||||
return `+${this.countOverLimit}`;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
avatarUrlTitle(assignee) {
|
||||
return sprintf(__('Avatar for %{assigneeName}'), {
|
||||
assigneeName: assignee.name,
|
||||
});
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div class="issue-assignees">
|
||||
<user-avatar-link
|
||||
v-for="assignee in assigneesToShow"
|
||||
:key="assignee.id"
|
||||
:link-href="assignee.web_url"
|
||||
:img-alt="avatarUrlTitle(assignee)"
|
||||
:img-src="assignee.avatar_url"
|
||||
:img-size="24"
|
||||
class="js-no-trigger"
|
||||
tooltip-placement="bottom"
|
||||
>
|
||||
<span class="js-assignee-tooltip">
|
||||
<span class="bold d-block">{{ __('Assignee') }}</span> {{ assignee.name }}
|
||||
<span class="text-white-50">@{{ assignee.username }}</span>
|
||||
</span>
|
||||
</user-avatar-link>
|
||||
<span
|
||||
v-if="shouldRenderAssigneesCounter"
|
||||
v-gl-tooltip
|
||||
:title="assigneesCounterTooltip"
|
||||
class="avatar-counter"
|
||||
data-placement="bottom"
|
||||
>{{ assigneeCounterLabel }}</span
|
||||
>
|
||||
</div>
|
||||
</template>
|
|
@ -0,0 +1,90 @@
|
|||
<script>
|
||||
import { GlTooltip } from '@gitlab/ui';
|
||||
import { __, sprintf } from '~/locale';
|
||||
import timeagoMixin from '~/vue_shared/mixins/timeago';
|
||||
import { timeFor, parsePikadayDate, dateInWords } from '~/lib/utils/datetime_utility';
|
||||
import Icon from '~/vue_shared/components/icon.vue';
|
||||
|
||||
export default {
|
||||
components: {
|
||||
Icon,
|
||||
GlTooltip,
|
||||
},
|
||||
mixins: [timeagoMixin],
|
||||
props: {
|
||||
milestone: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
milestoneDue: this.milestone.due_date ? parsePikadayDate(this.milestone.due_date) : null,
|
||||
milestoneStart: this.milestone.start_date
|
||||
? parsePikadayDate(this.milestone.start_date)
|
||||
: null,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
isMilestoneStarted() {
|
||||
if (!this.milestoneStart) {
|
||||
return false;
|
||||
}
|
||||
return Date.now() > this.milestoneStart;
|
||||
},
|
||||
isMilestonePastDue() {
|
||||
if (!this.milestoneDue) {
|
||||
return false;
|
||||
}
|
||||
return Date.now() > this.milestoneDue;
|
||||
},
|
||||
milestoneDatesAbsolute() {
|
||||
if (this.milestoneDue) {
|
||||
return `(${dateInWords(this.milestoneDue)})`;
|
||||
} else if (this.milestoneStart) {
|
||||
return `(${dateInWords(this.milestoneStart)})`;
|
||||
}
|
||||
return '';
|
||||
},
|
||||
milestoneDatesHuman() {
|
||||
if (this.milestoneStart || this.milestoneDue) {
|
||||
if (this.milestoneDue) {
|
||||
return timeFor(
|
||||
this.milestoneDue,
|
||||
sprintf(__('Expired %{expiredOn}'), {
|
||||
expiredOn: this.timeFormated(this.milestoneDue),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
return sprintf(
|
||||
this.isMilestoneStarted ? __('Started %{startsIn}') : __('Starts %{startsIn}'),
|
||||
{
|
||||
startsIn: this.timeFormated(this.milestoneStart),
|
||||
},
|
||||
);
|
||||
}
|
||||
return '';
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
<template>
|
||||
<div ref="milestoneDetails" class="issue-milestone-details">
|
||||
<icon :size="16" class="inline icon" name="clock" />
|
||||
<span class="milestone-title">{{ milestone.title }}</span>
|
||||
<gl-tooltip :target="() => $refs.milestoneDetails" placement="bottom" class="js-item-milestone">
|
||||
<span class="bold">{{ __('Milestone') }}</span> <br />
|
||||
<span>{{ milestone.title }}</span> <br />
|
||||
<span
|
||||
v-if="milestoneStart || milestoneDue"
|
||||
:class="{
|
||||
'text-danger-muted': isMilestonePastDue,
|
||||
'text-tertiary': !isMilestonePastDue,
|
||||
}"
|
||||
><span>{{ milestoneDatesHuman }}</span
|
||||
><br /><span>{{ milestoneDatesAbsolute }}</span>
|
||||
</span>
|
||||
</gl-tooltip>
|
||||
</div>
|
||||
</template>
|
|
@ -849,6 +849,9 @@ msgstr ""
|
|||
msgid "Available specific runners"
|
||||
msgstr ""
|
||||
|
||||
msgid "Avatar for %{assigneeName}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Avatar will be removed. Are you sure?"
|
||||
msgstr ""
|
||||
|
||||
|
@ -2920,6 +2923,9 @@ msgstr ""
|
|||
msgid "Expiration date"
|
||||
msgstr ""
|
||||
|
||||
msgid "Expired %{expiredOn}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Expires in %{expires_at}"
|
||||
msgstr ""
|
||||
|
||||
|
@ -6275,6 +6281,12 @@ msgstr ""
|
|||
msgid "Started"
|
||||
msgstr ""
|
||||
|
||||
msgid "Started %{startsIn}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Starts %{startsIn}"
|
||||
msgstr ""
|
||||
|
||||
msgid "Starts at (UTC)"
|
||||
msgstr ""
|
||||
|
||||
|
|
|
@ -1,5 +1,11 @@
|
|||
import BoardService from '~/boards/services/board_service';
|
||||
|
||||
export const boardObj = {
|
||||
id: 1,
|
||||
name: 'test',
|
||||
milestone_id: null,
|
||||
};
|
||||
|
||||
export const listObj = {
|
||||
id: 300,
|
||||
position: 0,
|
||||
|
@ -40,6 +46,12 @@ export const BoardsMockData = {
|
|||
},
|
||||
],
|
||||
},
|
||||
'/test/issue-boards/milestones.json': [
|
||||
{
|
||||
id: 1,
|
||||
title: 'test',
|
||||
},
|
||||
],
|
||||
},
|
||||
POST: {
|
||||
'/test/-/boards/1/lists': listObj,
|
||||
|
@ -70,3 +82,60 @@ export const mockBoardService = (opts = {}) => {
|
|||
boardId,
|
||||
});
|
||||
};
|
||||
|
||||
export const mockAssigneesList = [
|
||||
{
|
||||
id: 2,
|
||||
name: 'Terrell Graham',
|
||||
username: 'monserrate.gleichner',
|
||||
state: 'active',
|
||||
avatar_url: 'https://www.gravatar.com/avatar/598fd02741ac58b88854a99d16704309?s=80&d=identicon',
|
||||
web_url: 'http://127.0.0.1:3001/monserrate.gleichner',
|
||||
path: '/monserrate.gleichner',
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
name: 'Susy Johnson',
|
||||
username: 'tana_harvey',
|
||||
state: 'active',
|
||||
avatar_url: 'https://www.gravatar.com/avatar/e021a7b0f3e4ae53b5068d487e68c031?s=80&d=identicon',
|
||||
web_url: 'http://127.0.0.1:3001/tana_harvey',
|
||||
path: '/tana_harvey',
|
||||
},
|
||||
{
|
||||
id: 20,
|
||||
name: 'Conchita Eichmann',
|
||||
username: 'juliana_gulgowski',
|
||||
state: 'active',
|
||||
avatar_url: 'https://www.gravatar.com/avatar/c43c506cb6fd7b37017d3b54b94aa937?s=80&d=identicon',
|
||||
web_url: 'http://127.0.0.1:3001/juliana_gulgowski',
|
||||
path: '/juliana_gulgowski',
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
name: 'Bryce Turcotte',
|
||||
username: 'melynda',
|
||||
state: 'active',
|
||||
avatar_url: 'https://www.gravatar.com/avatar/cc2518f2c6f19f8fac49e1a5ee092a9b?s=80&d=identicon',
|
||||
web_url: 'http://127.0.0.1:3001/melynda',
|
||||
path: '/melynda',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
name: 'Administrator',
|
||||
username: 'root',
|
||||
state: 'active',
|
||||
avatar_url: 'https://www.gravatar.com/avatar/e64c7d89f26bd1972efa854d13d7dd61?s=80&d=identicon',
|
||||
web_url: 'http://127.0.0.1:3001/root',
|
||||
path: '/root',
|
||||
},
|
||||
];
|
||||
|
||||
export const mockMilestone = {
|
||||
id: 1,
|
||||
state: 'active',
|
||||
title: 'Milestone title',
|
||||
description: 'Harum corporis aut consequatur quae dolorem error sequi quia.',
|
||||
start_date: '2018-01-01',
|
||||
due_date: '2019-12-31',
|
||||
};
|
||||
|
|
|
@ -0,0 +1,114 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
import IssueAssignees from '~/vue_shared/components/issue/issue_assignees.vue';
|
||||
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { mockAssigneesList } from 'spec/boards/mock_data';
|
||||
|
||||
const createComponent = (assignees = mockAssigneesList, cssClass = '') => {
|
||||
const Component = Vue.extend(IssueAssignees);
|
||||
|
||||
return mountComponent(Component, {
|
||||
assignees,
|
||||
cssClass,
|
||||
});
|
||||
};
|
||||
|
||||
describe('IssueAssigneesComponent', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
vm = createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('data', () => {
|
||||
it('returns default data props', () => {
|
||||
expect(vm.maxVisibleAssignees).toBe(2);
|
||||
expect(vm.maxAssigneeAvatars).toBe(3);
|
||||
expect(vm.maxAssignees).toBe(99);
|
||||
});
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('countOverLimit', () => {
|
||||
it('should return difference between assignees count and maxVisibleAssignees', () => {
|
||||
expect(vm.countOverLimit).toBe(mockAssigneesList.length - vm.maxVisibleAssignees);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assigneesToShow', () => {
|
||||
it('should return assignees containing only 2 items when count more than maxAssigneeAvatars', () => {
|
||||
expect(vm.assigneesToShow.length).toBe(2);
|
||||
});
|
||||
|
||||
it('should return all assignees as it is when count less than maxAssigneeAvatars', () => {
|
||||
vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees
|
||||
|
||||
expect(vm.assigneesToShow.length).toBe(3);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assigneesCounterTooltip', () => {
|
||||
it('should return string containing count of remaining assignees when count more than maxAssigneeAvatars', () => {
|
||||
expect(vm.assigneesCounterTooltip).toBe('3 more assignees');
|
||||
});
|
||||
});
|
||||
|
||||
describe('shouldRenderAssigneesCounter', () => {
|
||||
it('should return `false` when assignees count less than maxAssigneeAvatars', () => {
|
||||
vm.assignees = mockAssigneesList.slice(0, 3); // Set 3 Assignees
|
||||
|
||||
expect(vm.shouldRenderAssigneesCounter).toBe(false);
|
||||
});
|
||||
|
||||
it('should return `true` when assignees count more than maxAssigneeAvatars', () => {
|
||||
expect(vm.shouldRenderAssigneesCounter).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('assigneeCounterLabel', () => {
|
||||
it('should return count of additional assignees total assignees count more than maxAssigneeAvatars', () => {
|
||||
expect(vm.assigneeCounterLabel).toBe('+3');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('methods', () => {
|
||||
describe('avatarUrlTitle', () => {
|
||||
it('returns string containing alt text for assignee avatar', () => {
|
||||
expect(vm.avatarUrlTitle(mockAssigneesList[0])).toBe('Avatar for Terrell Graham');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders component root element with class `issue-assignees`', () => {
|
||||
expect(vm.$el.classList.contains('issue-assignees')).toBe(true);
|
||||
});
|
||||
|
||||
it('renders assignee avatars', () => {
|
||||
expect(vm.$el.querySelectorAll('.user-avatar-link').length).toBe(2);
|
||||
});
|
||||
|
||||
it('renders assignee tooltips', () => {
|
||||
const tooltipText = vm.$el
|
||||
.querySelectorAll('.user-avatar-link')[0]
|
||||
.querySelector('.js-assignee-tooltip').innerText;
|
||||
|
||||
expect(tooltipText).toContain('Assignee');
|
||||
expect(tooltipText).toContain('Terrell Graham');
|
||||
expect(tooltipText).toContain('@monserrate.gleichner');
|
||||
});
|
||||
|
||||
it('renders additional assignees count', () => {
|
||||
const avatarCounterEl = vm.$el.querySelector('.avatar-counter');
|
||||
|
||||
expect(avatarCounterEl.innerText.trim()).toBe('+3');
|
||||
expect(avatarCounterEl.getAttribute('data-original-title')).toBe('3 more assignees');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,234 @@
|
|||
import Vue from 'vue';
|
||||
|
||||
import IssueMilestone from '~/vue_shared/components/issue/issue_milestone.vue';
|
||||
|
||||
import mountComponent from 'spec/helpers/vue_mount_component_helper';
|
||||
import { mockMilestone } from 'spec/boards/mock_data';
|
||||
|
||||
const createComponent = (milestone = mockMilestone) => {
|
||||
const Component = Vue.extend(IssueMilestone);
|
||||
|
||||
return mountComponent(Component, {
|
||||
milestone,
|
||||
});
|
||||
};
|
||||
|
||||
describe('IssueMilestoneComponent', () => {
|
||||
let vm;
|
||||
|
||||
beforeEach(() => {
|
||||
vm = createComponent();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
vm.$destroy();
|
||||
});
|
||||
|
||||
describe('computed', () => {
|
||||
describe('isMilestoneStarted', () => {
|
||||
it('should return `false` when milestoneStart prop is not defined', done => {
|
||||
const vmStartUndefined = createComponent(
|
||||
Object.assign({}, mockMilestone, {
|
||||
start_date: '',
|
||||
}),
|
||||
);
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vmStartUndefined.isMilestoneStarted).toBe(false);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
||||
vmStartUndefined.$destroy();
|
||||
});
|
||||
|
||||
it('should return `true` when milestone start date is past current date', done => {
|
||||
const vmStarted = createComponent(
|
||||
Object.assign({}, mockMilestone, {
|
||||
start_date: '1990-07-22',
|
||||
}),
|
||||
);
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vmStarted.isMilestoneStarted).toBe(true);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
||||
vmStarted.$destroy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('isMilestonePastDue', () => {
|
||||
it('should return `false` when milestoneDue prop is not defined', done => {
|
||||
const vmDueUndefined = createComponent(
|
||||
Object.assign({}, mockMilestone, {
|
||||
due_date: '',
|
||||
}),
|
||||
);
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vmDueUndefined.isMilestonePastDue).toBe(false);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
||||
vmDueUndefined.$destroy();
|
||||
});
|
||||
|
||||
it('should return `true` when milestone due is past current date', done => {
|
||||
const vmPastDue = createComponent(
|
||||
Object.assign({}, mockMilestone, {
|
||||
due_date: '1990-07-22',
|
||||
}),
|
||||
);
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vmPastDue.isMilestonePastDue).toBe(true);
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
||||
vmPastDue.$destroy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('milestoneDatesAbsolute', () => {
|
||||
it('returns string containing absolute milestone due date', () => {
|
||||
expect(vm.milestoneDatesAbsolute).toBe('(December 31, 2019)');
|
||||
});
|
||||
|
||||
it('returns string containing absolute milestone start date when due date is not present', done => {
|
||||
const vmDueUndefined = createComponent(
|
||||
Object.assign({}, mockMilestone, {
|
||||
due_date: '',
|
||||
}),
|
||||
);
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vmDueUndefined.milestoneDatesAbsolute).toBe('(January 1, 2018)');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
||||
vmDueUndefined.$destroy();
|
||||
});
|
||||
|
||||
it('returns empty string when both milestone start and due dates are not present', done => {
|
||||
const vmDatesUndefined = createComponent(
|
||||
Object.assign({}, mockMilestone, {
|
||||
start_date: '',
|
||||
due_date: '',
|
||||
}),
|
||||
);
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vmDatesUndefined.milestoneDatesAbsolute).toBe('');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
||||
vmDatesUndefined.$destroy();
|
||||
});
|
||||
});
|
||||
|
||||
describe('milestoneDatesHuman', () => {
|
||||
it('returns string containing milestone due date when date is yet to be due', done => {
|
||||
const vmFuture = createComponent(
|
||||
Object.assign({}, mockMilestone, {
|
||||
due_date: `${new Date().getFullYear() + 10}-01-01`,
|
||||
}),
|
||||
);
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vmFuture.milestoneDatesHuman).toContain('years remaining');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
||||
vmFuture.$destroy();
|
||||
});
|
||||
|
||||
it('returns string containing milestone start date when date has already started and due date is not present', done => {
|
||||
const vmStarted = createComponent(
|
||||
Object.assign({}, mockMilestone, {
|
||||
start_date: '1990-07-22',
|
||||
due_date: '',
|
||||
}),
|
||||
);
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vmStarted.milestoneDatesHuman).toContain('Started');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
||||
vmStarted.$destroy();
|
||||
});
|
||||
|
||||
it('returns string containing milestone start date when date is yet to start and due date is not present', done => {
|
||||
const vmStarts = createComponent(
|
||||
Object.assign({}, mockMilestone, {
|
||||
start_date: `${new Date().getFullYear() + 10}-01-01`,
|
||||
due_date: '',
|
||||
}),
|
||||
);
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vmStarts.milestoneDatesHuman).toContain('Starts');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
||||
vmStarts.$destroy();
|
||||
});
|
||||
|
||||
it('returns empty string when milestone start and due dates are not present', done => {
|
||||
const vmDatesUndefined = createComponent(
|
||||
Object.assign({}, mockMilestone, {
|
||||
start_date: '',
|
||||
due_date: '',
|
||||
}),
|
||||
);
|
||||
|
||||
Vue.nextTick()
|
||||
.then(() => {
|
||||
expect(vmDatesUndefined.milestoneDatesHuman).toBe('');
|
||||
})
|
||||
.then(done)
|
||||
.catch(done.fail);
|
||||
|
||||
vmDatesUndefined.$destroy();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('template', () => {
|
||||
it('renders component root element with class `issue-milestone-details`', () => {
|
||||
expect(vm.$el.classList.contains('issue-milestone-details')).toBe(true);
|
||||
});
|
||||
|
||||
it('renders milestone icon', () => {
|
||||
expect(vm.$el.querySelector('svg use').getAttribute('xlink:href')).toContain('clock');
|
||||
});
|
||||
|
||||
it('renders milestone title', () => {
|
||||
expect(vm.$el.querySelector('.milestone-title').innerText.trim()).toBe(mockMilestone.title);
|
||||
});
|
||||
|
||||
it('renders milestone tooltip', () => {
|
||||
expect(vm.$el.querySelector('.js-item-milestone').innerText.trim()).toContain(
|
||||
mockMilestone.title,
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue