Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
G
gitlab-ce
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Léo-Paul Géneau
gitlab-ce
Commits
295e8b9b
Commit
295e8b9b
authored
Dec 08, 2018
by
Kushal Pandya
Committed by
Phil Hughes
Dec 08, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
CE Backport: Epic issue list and related issue list re-design
parent
6018259f
Changes
7
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
627 additions
and
6 deletions
+627
-6
app/assets/javascripts/boards/components/issue_due_date.vue
app/assets/javascripts/boards/components/issue_due_date.vue
+14
-6
app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
...vascripts/vue_shared/components/issue/issue_assignees.vue
+94
-0
app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
...vascripts/vue_shared/components/issue/issue_milestone.vue
+90
-0
locale/gitlab.pot
locale/gitlab.pot
+12
-0
spec/javascripts/boards/mock_data.js
spec/javascripts/boards/mock_data.js
+69
-0
spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js
...ripts/vue_shared/components/issue/issue_assignees_spec.js
+114
-0
spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js
...ripts/vue_shared/components/issue/issue_milestone_spec.js
+234
-0
No files found.
app/assets/javascripts/boards/components/issue_due_date.vue
View file @
295e8b9b
...
@@ -15,6 +15,16 @@ export default {
...
@@ -15,6 +15,16 @@ export default {
type
:
String
,
type
:
String
,
required
:
true
,
required
:
true
,
},
},
cssClass
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
tooltipPlacement
:
{
type
:
String
,
required
:
false
,
default
:
'
bottom
'
,
},
},
},
computed
:
{
computed
:
{
title
()
{
title
()
{
...
@@ -66,15 +76,13 @@ export default {
...
@@ -66,15 +76,13 @@ export default {
<
template
>
<
template
>
<span>
<span>
<span
ref=
"issueDueDate"
class=
"board-card-info card-number"
>
<span
ref=
"issueDueDate"
:class=
"cssClass"
class=
"board-card-info card-number"
>
<icon
<icon
:class=
"
{ 'text-danger': isPastDue, 'board-card-info-icon': true }" name="calendar" />
:class=
"
{ 'text-danger': isPastDue, 'board-card-info-icon': true }"
<time
:class=
"
{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">
{{
name="calendar"
/>
<time
:class=
"
{ 'text-danger': isPastDue }" datetime="date" class="board-card-info-text">
{{
body
body
}}
</time>
}}
</time>
</span>
</span>
<gl-tooltip
:target=
"() => $refs.issueDueDate"
placement=
"bottom
"
>
<gl-tooltip
:target=
"() => $refs.issueDueDate"
:placement=
"tooltipPlacement
"
>
<span
class=
"bold"
>
{{
__
(
'
Due date
'
)
}}
</span>
<br
/>
<span
class=
"bold"
>
{{
__
(
'
Due date
'
)
}}
</span>
<br
/>
<span
:class=
"
{ 'text-danger-muted': isPastDue }">
{{
title
}}
</span>
<span
:class=
"
{ 'text-danger-muted': isPastDue }">
{{
title
}}
</span>
</gl-tooltip>
</gl-tooltip>
...
...
app/assets/javascripts/vue_shared/components/issue/issue_assignees.vue
0 → 100644
View file @
295e8b9b
<
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
>
app/assets/javascripts/vue_shared/components/issue/issue_milestone.vue
0 → 100644
View file @
295e8b9b
<
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
>
locale/gitlab.pot
View file @
295e8b9b
...
@@ -849,6 +849,9 @@ msgstr ""
...
@@ -849,6 +849,9 @@ msgstr ""
msgid "Available specific runners"
msgid "Available specific runners"
msgstr ""
msgstr ""
msgid "Avatar for %{assigneeName}"
msgstr ""
msgid "Avatar will be removed. Are you sure?"
msgid "Avatar will be removed. Are you sure?"
msgstr ""
msgstr ""
...
@@ -2920,6 +2923,9 @@ msgstr ""
...
@@ -2920,6 +2923,9 @@ msgstr ""
msgid "Expiration date"
msgid "Expiration date"
msgstr ""
msgstr ""
msgid "Expired %{expiredOn}"
msgstr ""
msgid "Expires in %{expires_at}"
msgid "Expires in %{expires_at}"
msgstr ""
msgstr ""
...
@@ -6275,6 +6281,12 @@ msgstr ""
...
@@ -6275,6 +6281,12 @@ msgstr ""
msgid "Started"
msgid "Started"
msgstr ""
msgstr ""
msgid "Started %{startsIn}"
msgstr ""
msgid "Starts %{startsIn}"
msgstr ""
msgid "Starts at (UTC)"
msgid "Starts at (UTC)"
msgstr ""
msgstr ""
...
...
spec/javascripts/boards/mock_data.js
View file @
295e8b9b
import
BoardService
from
'
~/boards/services/board_service
'
;
import
BoardService
from
'
~/boards/services/board_service
'
;
export
const
boardObj
=
{
id
:
1
,
name
:
'
test
'
,
milestone_id
:
null
,
};
export
const
listObj
=
{
export
const
listObj
=
{
id
:
300
,
id
:
300
,
position
:
0
,
position
:
0
,
...
@@ -40,6 +46,12 @@ export const BoardsMockData = {
...
@@ -40,6 +46,12 @@ export const BoardsMockData = {
},
},
],
],
},
},
'
/test/issue-boards/milestones.json
'
:
[
{
id
:
1
,
title
:
'
test
'
,
},
],
},
},
POST
:
{
POST
:
{
'
/test/-/boards/1/lists
'
:
listObj
,
'
/test/-/boards/1/lists
'
:
listObj
,
...
@@ -70,3 +82,60 @@ export const mockBoardService = (opts = {}) => {
...
@@ -70,3 +82,60 @@ export const mockBoardService = (opts = {}) => {
boardId
,
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
'
,
};
spec/javascripts/vue_shared/components/issue/issue_assignees_spec.js
0 → 100644
View file @
295e8b9b
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
'
);
});
});
});
spec/javascripts/vue_shared/components/issue/issue_milestone_spec.js
0 → 100644
View file @
295e8b9b
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
,
);
});
});
});
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment