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
1
Merge Requests
1
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
nexedi
gitlab-ce
Commits
b42b0f97
Commit
b42b0f97
authored
Nov 11, 2017
by
Clement Ho
Committed by
Filipa Lacerda
Nov 11, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add epic sidebar
parent
ccb69ca0
Changes
30
Hide whitespace changes
Inline
Side-by-side
Showing
30 changed files
with
1318 additions
and
32 deletions
+1318
-32
app/assets/javascripts/lib/utils/datetime_utility.js
app/assets/javascripts/lib/utils/datetime_utility.js
+14
-1
app/assets/javascripts/lib/utils/text_utility.js
app/assets/javascripts/lib/utils/text_utility.js
+4
-0
app/assets/javascripts/vue_shared/components/pikaday.vue
app/assets/javascripts/vue_shared/components/pikaday.vue
+79
-0
app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
...vue_shared/components/sidebar/collapsed_calendar_icon.vue
+46
-0
app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
...ared/components/sidebar/collapsed_grouped_date_picker.vue
+109
-0
app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
...javascripts/vue_shared/components/sidebar/date_picker.vue
+163
-0
app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
...ascripts/vue_shared/components/sidebar/toggle_sidebar.vue
+30
-0
app/assets/stylesheets/framework/buttons.scss
app/assets/stylesheets/framework/buttons.scss
+23
-0
app/assets/stylesheets/framework/sidebar.scss
app/assets/stylesheets/framework/sidebar.scss
+23
-2
app/assets/stylesheets/pages/issuable.scss
app/assets/stylesheets/pages/issuable.scss
+27
-3
app/helpers/nav_helper.rb
app/helpers/nav_helper.rb
+2
-1
changelogs/unreleased-ee/add-epic-sidebar.yml
changelogs/unreleased-ee/add-epic-sidebar.yml
+5
-0
ee/app/assets/javascripts/epics/epic_show/components/epic_show_app.vue
.../javascripts/epics/epic_show/components/epic_show_app.vue
+32
-14
ee/app/assets/javascripts/epics/epic_show/epic_show_bundle.js
...pp/assets/javascripts/epics/epic_show/epic_show_bundle.js
+4
-0
ee/app/assets/javascripts/epics/sidebar/components/sidebar_app.vue
...sets/javascripts/epics/sidebar/components/sidebar_app.vue
+127
-0
ee/app/assets/javascripts/epics/sidebar/services/sidebar_service.js
...ets/javascripts/epics/sidebar/services/sidebar_service.js
+23
-0
ee/app/assets/javascripts/epics/sidebar/stores/sidebar_store.js
.../assets/javascripts/epics/sidebar/stores/sidebar_store.js
+16
-0
ee/app/helpers/epics_helper.rb
ee/app/helpers/epics_helper.rb
+3
-1
spec/ee/spec/helpers/epics_helper_spec.rb
spec/ee/spec/helpers/epics_helper_spec.rb
+1
-1
spec/javascripts/datetime_utility_spec.js
spec/javascripts/datetime_utility_spec.js
+17
-5
spec/javascripts/epics/epic_show/components/epic_show_app_spec.js
...ascripts/epics/epic_show/components/epic_show_app_spec.js
+16
-0
spec/javascripts/epics/epic_show/mock_data.js
spec/javascripts/epics/epic_show/mock_data.js
+2
-0
spec/javascripts/epics/sidebar/stores/sidebar_store_spec.js
spec/javascripts/epics/sidebar/stores/sidebar_store_spec.js
+57
-0
spec/javascripts/lib/utils/text_utility_spec.js
spec/javascripts/lib/utils/text_utility_spec.js
+10
-4
spec/javascripts/vue_shared/components/pikaday_spec.js
spec/javascripts/vue_shared/components/pikaday_spec.js
+29
-0
spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
...shared/components/sidebar/collapsed_calendar_icon_spec.js
+35
-0
spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
.../components/sidebar/collapsed_grouped_date_picker_spec.js
+91
-0
spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js
...scripts/vue_shared/components/sidebar/date_picker_spec.js
+117
-0
spec/javascripts/vue_shared/components/sidebar/sidebar_app_spec.js
...scripts/vue_shared/components/sidebar/sidebar_app_spec.js
+181
-0
spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js
...ipts/vue_shared/components/sidebar/toggle_sidebar_spec.js
+32
-0
No files found.
app/assets/javascripts/lib/utils/datetime_utility.js
View file @
b42b0f97
...
...
@@ -135,7 +135,6 @@ window.dateFormat = dateFormat;
* @param {Number} seconds
* @return {String}
*/
// eslint-disable-next-line import/prefer-default-export
export
function
timeIntervalInWords
(
intervalInSeconds
)
{
const
secondsInteger
=
parseInt
(
intervalInSeconds
,
10
);
const
minutes
=
Math
.
floor
(
secondsInteger
/
60
);
...
...
@@ -149,3 +148,17 @@ export function timeIntervalInWords(intervalInSeconds) {
}
return
text
;
}
export
function
dateInWords
(
date
,
abbreviated
=
false
)
{
if
(
!
date
)
return
date
;
const
month
=
date
.
getMonth
();
const
year
=
date
.
getFullYear
();
const
monthNames
=
[
s__
(
'
January
'
),
s__
(
'
February
'
),
s__
(
'
March
'
),
s__
(
'
April
'
),
s__
(
'
May
'
),
s__
(
'
June
'
),
s__
(
'
July
'
),
s__
(
'
August
'
),
s__
(
'
September
'
),
s__
(
'
October
'
),
s__
(
'
November
'
),
s__
(
'
December
'
)];
const
monthNamesAbbr
=
[
s__
(
'
Jan
'
),
s__
(
'
Feb
'
),
s__
(
'
Mar
'
),
s__
(
'
Apr
'
),
s__
(
'
May
'
),
s__
(
'
Jun
'
),
s__
(
'
Jul
'
),
s__
(
'
Aug
'
),
s__
(
'
Sep
'
),
s__
(
'
Oct
'
),
s__
(
'
Nov
'
),
s__
(
'
Dec
'
)];
const
monthName
=
abbreviated
?
monthNamesAbbr
[
month
]
:
monthNames
[
month
];
return
`
${
monthName
}
${
date
.
getDate
()}
,
${
year
}
`
;
}
app/assets/javascripts/lib/utils/text_utility.js
View file @
b42b0f97
...
...
@@ -24,6 +24,10 @@ export function highCountTrim(count) {
return
count
>
99
?
'
99+
'
:
count
;
}
export
function
capitalizeFirstCharacter
(
text
)
{
return
`
${
text
[
0
].
toUpperCase
()}${
text
.
slice
(
1
)}
`
;
}
gl
.
text
.
randomString
=
function
()
{
return
Math
.
random
().
toString
(
36
).
substring
(
7
);
};
...
...
app/assets/javascripts/vue_shared/components/pikaday.vue
0 → 100644
View file @
b42b0f97
<
script
>
import
Pikaday
from
'
pikaday
'
;
import
{
parsePikadayDate
,
pikadayToString
}
from
'
../../lib/utils/datefix
'
;
export
default
{
name
:
'
datePicker
'
,
props
:
{
label
:
{
type
:
String
,
required
:
false
,
default
:
'
Date picker
'
,
},
selectedDate
:
{
type
:
Date
,
required
:
false
,
},
minDate
:
{
type
:
Date
,
required
:
false
,
},
maxDate
:
{
type
:
Date
,
required
:
false
,
},
},
methods
:
{
selected
(
dateText
)
{
this
.
$emit
(
'
newDateSelected
'
,
this
.
calendar
.
toString
(
dateText
));
},
toggled
()
{
this
.
$emit
(
'
hidePicker
'
);
},
},
mounted
()
{
this
.
calendar
=
new
Pikaday
({
field
:
this
.
$el
.
querySelector
(
'
.dropdown-menu-toggle
'
),
theme
:
'
gitlab-theme animate-picker
'
,
format
:
'
yyyy-mm-dd
'
,
container
:
this
.
$el
,
defaultDate
:
this
.
selectedDate
,
setDefaultDate
:
!!
this
.
selectedDate
,
minDate
:
this
.
minDate
,
maxDate
:
this
.
maxDate
,
parse
:
dateString
=>
parsePikadayDate
(
dateString
),
toString
:
date
=>
pikadayToString
(
date
),
onSelect
:
this
.
selected
.
bind
(
this
),
onClose
:
this
.
toggled
.
bind
(
this
),
});
this
.
$el
.
append
(
this
.
calendar
.
el
);
this
.
calendar
.
show
();
},
beforeDestroy
()
{
this
.
calendar
.
destroy
();
},
};
</
script
>
<
template
>
<div
class=
"pikaday-container"
>
<div
class=
"dropdown open"
>
<button
type=
"button"
class=
"dropdown-menu-toggle"
data-toggle=
"dropdown"
@
click=
"toggled"
>
<span
class=
"dropdown-toggle-text"
>
{{
label
}}
</span>
<i
class=
"fa fa-chevron-down"
aria-hidden=
"true"
>
</i>
</button>
</div>
</div>
</
template
>
app/assets/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon.vue
0 → 100644
View file @
b42b0f97
<
script
>
export
default
{
name
:
'
collapsedCalendarIcon
'
,
props
:
{
containerClass
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
text
:
{
type
:
String
,
required
:
false
,
default
:
''
,
},
showIcon
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
},
methods
:
{
click
()
{
this
.
$emit
(
'
click
'
);
},
},
};
</
script
>
<
template
>
<div
:class=
"containerClass"
@
click=
"click"
>
<i
v-if=
"showIcon"
class=
"fa fa-calendar"
aria-hidden=
"true"
>
</i>
<slot>
<span>
{{
text
}}
</span>
</slot>
</div>
</
template
>
app/assets/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
0 → 100644
View file @
b42b0f97
<
script
>
import
{
dateInWords
}
from
'
../../../lib/utils/datetime_utility
'
;
import
toggleSidebar
from
'
./toggle_sidebar.vue
'
;
import
collapsedCalendarIcon
from
'
./collapsed_calendar_icon.vue
'
;
export
default
{
name
:
'
sidebarCollapsedGroupedDatePicker
'
,
props
:
{
collapsed
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
showToggleSidebar
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
minDate
:
{
type
:
Date
,
required
:
false
,
},
maxDate
:
{
type
:
Date
,
required
:
false
,
},
disableClickableIcons
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
},
components
:
{
toggleSidebar
,
collapsedCalendarIcon
,
},
computed
:
{
hasMinAndMaxDates
()
{
return
this
.
minDate
&&
this
.
maxDate
;
},
hasNoMinAndMaxDates
()
{
return
!
this
.
minDate
&&
!
this
.
maxDate
;
},
showMinDateBlock
()
{
return
this
.
minDate
||
this
.
hasNoMinAndMaxDates
;
},
showFromText
()
{
return
!
this
.
maxDate
&&
this
.
minDate
;
},
iconClass
()
{
const
disabledClass
=
this
.
disableClickableIcons
?
'
disabled
'
:
''
;
return
`block sidebar-collapsed-icon calendar-icon
${
disabledClass
}
`
;
},
},
methods
:
{
toggleSidebar
()
{
this
.
$emit
(
'
toggleCollapse
'
);
},
dateText
(
dateType
=
'
min
'
)
{
const
date
=
this
[
`
${
dateType
}
Date`
];
const
dateWords
=
dateInWords
(
date
,
true
);
const
parsedDateWords
=
dateWords
?
dateWords
.
replace
(
'
,
'
,
''
)
:
dateWords
;
return
date
?
parsedDateWords
:
'
None
'
;
},
},
};
</
script
>
<
template
>
<div
class=
"block sidebar-grouped-item"
>
<div
v-if=
"showToggleSidebar"
class=
"issuable-sidebar-header"
>
<toggle-sidebar
:collapsed=
"collapsed"
@
toggle=
"toggleSidebar"
/>
</div>
<collapsed-calendar-icon
v-if=
"showMinDateBlock"
:container-class=
"iconClass"
@
click=
"toggleSidebar"
>
<span
class=
"sidebar-collapsed-value"
>
<span
v-if=
"showFromText"
>
From
</span>
<span>
{{
dateText
(
'
min
'
)
}}
</span>
</span>
</collapsed-calendar-icon>
<div
v-if=
"hasMinAndMaxDates"
class=
"text-center sidebar-collapsed-divider"
>
-
</div>
<collapsed-calendar-icon
v-if=
"maxDate"
:container-class=
"iconClass"
:show-icon=
"!minDate"
@
click=
"toggleSidebar"
>
<span
class=
"sidebar-collapsed-value"
>
<span
v-if=
"!minDate"
>
Until
</span>
<span>
{{
dateText
(
'
max
'
)
}}
</span>
</span>
</collapsed-calendar-icon>
</div>
</
template
>
app/assets/javascripts/vue_shared/components/sidebar/date_picker.vue
0 → 100644
View file @
b42b0f97
<
script
>
import
datePicker
from
'
../pikaday.vue
'
;
import
loadingIcon
from
'
../loading_icon.vue
'
;
import
toggleSidebar
from
'
./toggle_sidebar.vue
'
;
import
collapsedCalendarIcon
from
'
./collapsed_calendar_icon.vue
'
;
import
{
dateInWords
}
from
'
../../../lib/utils/datetime_utility
'
;
export
default
{
name
:
'
sidebarDatePicker
'
,
props
:
{
collapsed
:
{
type
:
Boolean
,
required
:
false
,
default
:
true
,
},
showToggleSidebar
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
isLoading
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
editable
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
label
:
{
type
:
String
,
required
:
false
,
default
:
'
Date picker
'
,
},
selectedDate
:
{
type
:
Date
,
required
:
false
,
},
minDate
:
{
type
:
Date
,
required
:
false
,
},
maxDate
:
{
type
:
Date
,
required
:
false
,
},
},
data
()
{
return
{
editing
:
false
,
};
},
components
:
{
datePicker
,
toggleSidebar
,
loadingIcon
,
collapsedCalendarIcon
,
},
computed
:
{
selectedAndEditable
()
{
return
this
.
selectedDate
&&
this
.
editable
;
},
selectedDateWords
()
{
return
dateInWords
(
this
.
selectedDate
,
true
);
},
collapsedText
()
{
return
this
.
selectedDateWords
?
this
.
selectedDateWords
:
'
None
'
;
},
},
methods
:
{
stopEditing
()
{
this
.
editing
=
false
;
},
toggleDatePicker
()
{
this
.
editing
=
!
this
.
editing
;
},
newDateSelected
(
date
=
null
)
{
this
.
date
=
date
;
this
.
editing
=
false
;
this
.
$emit
(
'
saveDate
'
,
date
);
},
toggleSidebar
()
{
this
.
$emit
(
'
toggleCollapse
'
);
},
},
};
</
script
>
<
template
>
<div
class=
"block"
>
<div
class=
"issuable-sidebar-header"
>
<toggle-sidebar
:collapsed=
"collapsed"
@
toggle=
"toggleSidebar"
/>
</div>
<collapsed-calendar-icon
class=
"sidebar-collapsed-icon"
:text=
"collapsedText"
/>
<div
class=
"title"
>
{{
label
}}
<loading-icon
v-if=
"isLoading"
:inline=
"true"
/>
<div
class=
"pull-right"
>
<button
v-if=
"editable && !editing"
type=
"button"
class=
"btn-blank btn-link btn-primary-hover-link btn-sidebar-action"
@
click=
"toggleDatePicker"
>
Edit
</button>
<toggle-sidebar
v-if=
"showToggleSidebar"
:collapsed=
"collapsed"
@
toggle=
"toggleSidebar"
/>
</div>
</div>
<div
class=
"value"
>
<date-picker
v-if=
"editing"
:selected-date=
"selectedDate"
:min-date=
"minDate"
:max-date=
"maxDate"
:label=
"label"
@
newDateSelected=
"newDateSelected"
@
hidePicker=
"stopEditing"
/>
<span
v-else
class=
"value-content"
>
<template
v-if=
"selectedDate"
>
<strong>
{{
selectedDateWords
}}
</strong>
<span
v-if=
"selectedAndEditable"
class=
"no-value"
>
-
<button
type=
"button"
class=
"btn-blank btn-link btn-secondary-hover-link"
@
click=
"newDateSelected(null)"
>
remove
</button>
</span>
</
template
>
<span
v-else
class=
"no-value"
>
None
</span>
</span>
</div>
</div>
</template>
app/assets/javascripts/vue_shared/components/sidebar/toggle_sidebar.vue
0 → 100644
View file @
b42b0f97
<
script
>
export
default
{
name
:
'
toggleSidebar
'
,
props
:
{
collapsed
:
{
type
:
Boolean
,
required
:
true
,
},
},
methods
:
{
toggle
()
{
this
.
$emit
(
'
toggle
'
);
},
},
};
</
script
>
<
template
>
<button
type=
"button"
class=
"btn btn-blank gutter-toggle btn-sidebar-action"
@
click=
"toggle"
>
<i
aria-label=
"toggle collapse"
class=
"fa"
:class=
"
{ 'fa-angle-double-right': !collapsed, 'fa-angle-double-left': collapsed }"
>
</i>
</button>
</
template
>
app/assets/stylesheets/framework/buttons.scss
View file @
b42b0f97
...
...
@@ -412,6 +412,7 @@
padding
:
0
;
background
:
transparent
;
border
:
0
;
border-radius
:
0
;
&
:hover
,
&
:active
,
...
...
@@ -421,3 +422,25 @@
box-shadow
:
none
;
}
}
.btn-link.btn-secondary-hover-link
{
color
:
$gl-text-color-secondary
;
&
:hover
,
&
:active
,
&
:focus
{
color
:
$gl-link-color
;
text-decoration
:
none
;
}
}
.btn-link.btn-primary-hover-link
{
color
:
inherit
;
&
:hover
,
&
:active
,
&
:focus
{
color
:
$gl-link-color
;
text-decoration
:
none
;
}
}
app/assets/stylesheets/framework/sidebar.scss
View file @
b42b0f97
...
...
@@ -43,11 +43,13 @@
}
.sidebar-collapsed-icon
{
cursor
:
pointer
;
.btn
{
background-color
:
$gray-light
;
}
&
:not
(
.disabled
)
{
cursor
:
pointer
;
}
}
}
...
...
@@ -55,6 +57,10 @@
padding-right
:
0
;
z-index
:
300
;
.btn-sidebar-action
{
display
:
inline-flex
;
}
@media
(
min-width
:
$screen-sm-min
)
and
(
max-width
:
$screen-sm-max
)
{
&
:not
(
.wiki-sidebar
)
:not
(
.build-sidebar
)
:not
(
.issuable-bulk-update-sidebar
)
.content-wrapper
{
padding-right
:
$gutter_collapsed_width
;
...
...
@@ -136,3 +142,18 @@
.issuable-sidebar
{
@include
new-style-dropdown
;
}
.pikaday-container
{
.pika-single
{
margin-top
:
2px
;
width
:
250px
;
}
.dropdown-menu-toggle
{
line-height
:
20px
;
}
}
.sidebar-collapsed-icon
.sidebar-collapsed-value
{
font-size
:
12px
;
}
app/assets/stylesheets/pages/issuable.scss
View file @
b42b0f97
...
...
@@ -284,10 +284,15 @@
font-weight
:
$gl-font-weight-normal
;
}
.no-value
{
.no-value
,
.btn-secondary-hover-link
{
color
:
$gl-text-color-secondary
;
}
.btn-secondary-hover-link
:hover
{
color
:
$gl-link-color
;
}
.sidebar-collapsed-icon
{
display
:
none
;
}
...
...
@@ -353,7 +358,8 @@
.gutter-toggle
{
width
:
100%
;
margin-left
:
0
;
padding-left
:
25px
;
padding-left
:
0
;
text-align
:
center
;
}
.sidebar-collapsed-icon
{
...
...
@@ -367,7 +373,7 @@
fill
:
$issuable-sidebar-color
;
}
&
:hover
,
&
:hover
:not
(
.disabled
)
,
&
:hover
.todo-undone
{
color
:
$gl-text-color
;
...
...
@@ -953,3 +959,21 @@
.add-issuable-form-actions
{
margin-top
:
$gl-padding
;
}
.right-sidebar-collapsed
{
.sidebar-grouped-item
{
.sidebar-collapsed-icon
{
margin-bottom
:
0
;
}
.sidebar-collapsed-divider
{
line-height
:
5px
;
font-size
:
12px
;
color
:
$theme-gray-700
;
+
.sidebar-collapsed-icon
{
padding-top
:
0
;
}
}
}
}
app/helpers/nav_helper.rb
View file @
b42b0f97
...
...
@@ -11,7 +11,8 @@ module NavHelper
if
current_path?
(
'merge_requests#show'
)
||
current_path?
(
'projects/merge_requests/conflicts#show'
)
||
current_path?
(
'issues#show'
)
||
current_path?
(
'milestones#show'
)
current_path?
(
'milestones#show'
)
||
current_path?
(
'epics#show'
)
if
cookies
[
:collapsed_gutter
]
==
'true'
%w[page-gutter right-sidebar-collapsed]
else
...
...
changelogs/unreleased-ee/add-epic-sidebar.yml
0 → 100644
View file @
b42b0f97
---
title
:
Add sidebar for epic
merge_request
:
author
:
type
:
added
ee/app/assets/javascripts/epics/epic_show/components/epic_show_app.vue
View file @
b42b0f97
<
script
>
import
issuableApp
from
'
~/issue_show/components/app.vue
'
;
import
epicHeader
from
'
./epic_header.vue
'
;
import
epicSidebar
from
'
../../sidebar/components/sidebar_app.vue
'
;
export
default
{
name
:
'
epicShowApp
'
,
...
...
@@ -55,9 +56,18 @@
type
:
Object
,
required
:
true
,
},
startDate
:
{
type
:
String
,
required
:
false
,
},
endDate
:
{
type
:
String
,
required
:
false
,
},
},
components
:
{
epicHeader
,
epicSidebar
,
issuableApp
,
},
created
()
{
...
...
@@ -75,21 +85,29 @@
:author=
"author"
:created=
"created"
/>
<div
class=
"issuable-details detail-page-description content-block"
>
<issuable-app
:can-update=
"canUpdate"
:can-destroy=
"canDestroy"
<div
class=
"issuable-details content-block"
>
<div
class=
"detail-page-description"
>
<issuable-app
:can-update=
"canUpdate"
:can-destroy=
"canDestroy"
:endpoint=
"endpoint"
:issuable-ref=
"issuableRef"
:initial-title-html=
"initialTitleHtml"
:initial-title-text=
"initialTitleText"
:initial-description-html=
"initialDescriptionHtml"
:initial-description-text=
"initialDescriptionText"
:markdown-preview-path=
"markdownPreviewPath"
:markdown-docs-path=
"markdownDocsPath"
:project-path=
"projectPath"
:project-namespace=
"projectNamespace"
:show-inline-edit-button=
"true"
/>
</div>
<epic-sidebar
:endpoint=
"endpoint"
:issuable-ref=
"issuableRef"
:initial-title-html=
"initialTitleHtml"
:initial-title-text=
"initialTitleText"
:initial-description-html=
"initialDescriptionHtml"
:initial-description-text=
"initialDescriptionText"
:markdown-preview-path=
"markdownPreviewPath"
:markdown-docs-path=
"markdownDocsPath"
:project-path=
"projectPath"
:project-namespace=
"projectNamespace"
:show-inline-edit-button=
"true"
:editable=
"canUpdate"
:initialStartDate=
"startDate"
:initialEndDate=
"endDate"
/>
</div>
</div>
...
...
ee/app/assets/javascripts/epics/epic_show/epic_show_bundle.js
View file @
b42b0f97
...
...
@@ -12,6 +12,10 @@ document.addEventListener('DOMContentLoaded', () => {
canDestroy
:
false
,
});
// Convert backend casing to match frontend style guide
props
.
startDate
=
props
.
start_date
;
props
.
endDate
=
props
.
end_date
;
return
new
Vue
({
el
,
components
:
{
...
...
ee/app/assets/javascripts/epics/sidebar/components/sidebar_app.vue
0 → 100644
View file @
b42b0f97
<
script
>
import
Cookies
from
'
js-cookie
'
;
import
Flash
from
'
~/flash
'
;
import
{
capitalizeFirstCharacter
}
from
'
~/lib/utils/text_utility
'
;
import
sidebarDatePicker
from
'
~/vue_shared/components/sidebar/date_picker.vue
'
;
import
sidebarCollapsedGroupedDatePicker
from
'
~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
'
;
import
SidebarService
from
'
../services/sidebar_service
'
;
import
Store
from
'
../stores/sidebar_store
'
;
export
default
{
name
:
'
epicSidebar
'
,
props
:
{
endpoint
:
{
type
:
String
,
required
:
true
,
},
editable
:
{
type
:
Boolean
,
required
:
false
,
default
:
false
,
},
initialStartDate
:
{
type
:
String
,
required
:
false
,
},
initialEndDate
:
{
type
:
String
,
required
:
false
,
},
},
data
()
{
const
store
=
new
Store
({
startDate
:
this
.
initialStartDate
,
endDate
:
this
.
initialEndDate
,
});
return
{
store
,
// Backend will pass the appropriate css class for the contentContainer
collapsed
:
Cookies
.
get
(
'
collapsed_gutter
'
)
===
'
true
'
,
savingStartDate
:
false
,
savingEndDate
:
false
,
service
:
new
SidebarService
(
this
.
endpoint
),
};
},
components
:
{
sidebarDatePicker
,
sidebarCollapsedGroupedDatePicker
,
},
methods
:
{
toggleSidebar
()
{
this
.
collapsed
=
!
this
.
collapsed
;
const
contentContainer
=
this
.
$el
.
closest
(
'
.page-with-sidebar
'
);
contentContainer
.
classList
.
toggle
(
'
right-sidebar-expanded
'
);
contentContainer
.
classList
.
toggle
(
'
right-sidebar-collapsed
'
);
Cookies
.
set
(
'
collapsed_gutter
'
,
this
.
collapsed
);
},
saveDate
(
dateType
=
'
start
'
,
newDate
)
{
const
type
=
dateType
===
'
start
'
?
dateType
:
'
end
'
;
const
capitalizedType
=
capitalizeFirstCharacter
(
type
);
const
serviceMethod
=
`update
${
capitalizedType
}
Date`
;
const
savingBoolean
=
`saving
${
capitalizedType
}
Date`
;
this
[
savingBoolean
]
=
true
;
return
this
.
service
[
serviceMethod
](
newDate
)
.
then
(()
=>
{
this
[
savingBoolean
]
=
false
;
this
.
store
[
`
${
type
}
Date`
]
=
newDate
;
})
.
catch
(()
=>
{
this
[
savingBoolean
]
=
false
;
Flash
(
`An error occurred while saving
${
type
}
date`
);
});
},
saveStartDate
(
date
)
{
return
this
.
saveDate
(
'
start
'
,
date
);
},
saveEndDate
(
date
)
{
return
this
.
saveDate
(
'
end
'
,
date
);
},
},
};
</
script
>
<
template
>
<aside
class=
"right-sidebar"
:class=
"
{ 'right-sidebar-expanded' : !collapsed, 'right-sidebar-collapsed': collapsed }"
>
<div
class=
"issuable-sidebar"
>
<sidebar-date-picker
v-if=
"!collapsed"
:collapsed=
"collapsed"
:is-loading=
"savingStartDate"
:editable=
"editable"
label=
"Planned start date"
:selected-date=
"store.startDateTime"
:max-date=
"store.endDateTime"
:show-toggle-sidebar=
"true"
@
saveDate=
"saveStartDate"
@
toggleCollapse=
"toggleSidebar"
/>
<sidebar-date-picker
v-if=
"!collapsed"
:collapsed=
"collapsed"
:is-loading=
"savingEndDate"
:editable=
"editable"
label=
"Planned finish date"
:selected-date=
"store.endDateTime"
:min-date=
"store.startDateTime"
@
saveDate=
"saveEndDate"
@
toggleCollapse=
"toggleSidebar"
/>
<sidebar-collapsed-grouped-date-picker
v-if=
"collapsed"
:collapsed=
"collapsed"
:min-date=
"store.startDateTime"
:max-date=
"store.endDateTime"
:show-toggle-sidebar=
"true"
@
toggleCollapse=
"toggleSidebar"
/>
</div>
</aside>
</
template
>
ee/app/assets/javascripts/epics/sidebar/services/sidebar_service.js
0 → 100644
View file @
b42b0f97
import
Vue
from
'
vue
'
;
import
VueResource
from
'
vue-resource
'
;
Vue
.
use
(
VueResource
);
export
default
class
SidebarService
{
constructor
(
endpoint
)
{
this
.
endpoint
=
endpoint
;
this
.
resource
=
Vue
.
resource
(
`
${
this
.
endpoint
}
.json`
,
{});
}
updateStartDate
(
startDate
)
{
return
this
.
resource
.
update
({
start_date
:
startDate
,
});
}
updateEndDate
(
endDate
)
{
return
this
.
resource
.
update
({
end_date
:
endDate
,
});
}
}
ee/app/assets/javascripts/epics/sidebar/stores/sidebar_store.js
0 → 100644
View file @
b42b0f97
import
{
parsePikadayDate
}
from
'
~/lib/utils/datefix
'
;
export
default
class
SidebarStore
{
constructor
({
startDate
,
endDate
})
{
this
.
startDate
=
startDate
;
this
.
endDate
=
endDate
;
}
get
startDateTime
()
{
return
this
.
startDate
?
parsePikadayDate
(
this
.
startDate
)
:
null
;
}
get
endDateTime
()
{
return
this
.
endDate
?
parsePikadayDate
(
this
.
endDate
)
:
null
;
}
}
ee/app/helpers/epics_helper.rb
View file @
b42b0f97
...
...
@@ -9,7 +9,9 @@ module EpicsHelper
url:
user_path
(
author
),
username:
"@
#{
author
.
username
}
"
,
src:
avatar_icon
(
@epic
.
author
)
}
},
start_date:
@epic
.
start_date
,
end_date:
@epic
.
end_date
}
data
.
to_json
...
...
spec/ee/spec/helpers/epics_helper_spec.rb
View file @
b42b0f97
...
...
@@ -8,7 +8,7 @@ describe EpicsHelper do
user
=
create
(
:user
)
@epic
=
create
(
:epic
,
author:
user
)
expect
(
JSON
.
parse
(
epic_meta_data
).
keys
).
to
match_array
(
%w[created author]
)
expect
(
JSON
.
parse
(
epic_meta_data
).
keys
).
to
match_array
(
%w[created author
start_date end_date
]
)
expect
(
JSON
.
parse
(
epic_meta_data
)[
'author'
]).
to
eq
({
'name'
=>
user
.
name
,
'url'
=>
"/
#{
user
.
username
}
"
,
...
...
spec/javascripts/datetime_utility_spec.js
View file @
b42b0f97
import
{
timeIntervalInWords
}
from
'
~/lib/utils/datetime_utility
'
;
import
*
as
datetimeUtility
from
'
~/lib/utils/datetime_utility
'
;
(()
=>
{
describe
(
'
Date time utils
'
,
()
=>
{
...
...
@@ -89,10 +89,22 @@ import { timeIntervalInWords } from '~/lib/utils/datetime_utility';
describe
(
'
timeIntervalInWords
'
,
()
=>
{
it
(
'
should return string with number of minutes and seconds
'
,
()
=>
{
expect
(
timeIntervalInWords
(
9.54
)).
toEqual
(
'
9 seconds
'
);
expect
(
timeIntervalInWords
(
1
)).
toEqual
(
'
1 second
'
);
expect
(
timeIntervalInWords
(
200
)).
toEqual
(
'
3 minutes 20 seconds
'
);
expect
(
timeIntervalInWords
(
6008
)).
toEqual
(
'
100 minutes 8 seconds
'
);
expect
(
datetimeUtility
.
timeIntervalInWords
(
9.54
)).
toEqual
(
'
9 seconds
'
);
expect
(
datetimeUtility
.
timeIntervalInWords
(
1
)).
toEqual
(
'
1 second
'
);
expect
(
datetimeUtility
.
timeIntervalInWords
(
200
)).
toEqual
(
'
3 minutes 20 seconds
'
);
expect
(
datetimeUtility
.
timeIntervalInWords
(
6008
)).
toEqual
(
'
100 minutes 8 seconds
'
);
});
});
describe
(
'
dateInWords
'
,
()
=>
{
const
date
=
new
Date
(
'
07/01/2016
'
);
it
(
'
should return date in words
'
,
()
=>
{
expect
(
datetimeUtility
.
dateInWords
(
date
)).
toEqual
(
'
July 1, 2016
'
);
});
it
(
'
should return abbreviated month name
'
,
()
=>
{
expect
(
datetimeUtility
.
dateInWords
(
date
,
true
)).
toEqual
(
'
Jul 1, 2016
'
);
});
});
})();
spec/javascripts/epics/epic_show/components/epic_show_app_spec.js
View file @
b42b0f97
import
Vue
from
'
vue
'
;
import
epicShowApp
from
'
ee/epics/epic_show/components/epic_show_app.vue
'
;
import
epicHeader
from
'
ee/epics/epic_show/components/epic_header.vue
'
;
import
epicSidebar
from
'
ee/epics/sidebar/components/sidebar_app.vue
'
;
import
issuableApp
from
'
~/issue_show/components/app.vue
'
;
import
mountComponent
from
'
../../../helpers/vue_mount_component_helper
'
;
import
{
props
}
from
'
../mock_data
'
;
...
...
@@ -10,6 +11,7 @@ describe('EpicShowApp', () => {
let
vm
;
let
headerVm
;
let
issuableAppVm
;
let
sidebarVm
;
const
interceptor
=
(
request
,
next
)
=>
{
next
(
request
.
respondWith
(
JSON
.
stringify
(
issueShowData
.
initialRequest
),
{
...
...
@@ -26,6 +28,8 @@ describe('EpicShowApp', () => {
endpoint
,
initialTitleHtml
,
initialTitleText
,
startDate
,
endDate
,
markdownPreviewPath
,
markdownDocsPath
,
author
,
...
...
@@ -57,6 +61,14 @@ describe('EpicShowApp', () => {
projectNamespace
:
''
,
showInlineEditButton
:
true
,
});
const
EpicSidebar
=
Vue
.
extend
(
epicSidebar
);
sidebarVm
=
mountComponent
(
EpicSidebar
,
{
endpoint
,
editable
:
canUpdate
,
initialStartDate
:
startDate
,
initialEndDate
:
endDate
,
});
});
afterEach
(()
=>
{
...
...
@@ -70,4 +82,8 @@ describe('EpicShowApp', () => {
it
(
'
should render issuable-app
'
,
()
=>
{
expect
(
vm
.
$el
.
innerHTML
.
indexOf
(
issuableAppVm
.
$el
.
innerHTML
)
!==
-
1
).
toEqual
(
true
);
});
it
(
'
should render epic-sidebar
'
,
()
=>
{
expect
(
vm
.
$el
.
innerHTML
.
indexOf
(
sidebarVm
.
$el
.
innerHTML
)
!==
-
1
).
toEqual
(
true
);
});
});
spec/javascripts/epics/epic_show/mock_data.js
View file @
b42b0f97
...
...
@@ -7,6 +7,8 @@ export const contentProps = {
groupPath
:
''
,
initialTitleHtml
:
''
,
initialTitleText
:
''
,
startDate
:
'
2017-01-01
'
,
endDate
:
'
2017-10-10
'
,
};
export
const
headerProps
=
{
...
...
spec/javascripts/epics/sidebar/stores/sidebar_store_spec.js
0 → 100644
View file @
b42b0f97
import
SidebarStore
from
'
ee/epics/sidebar/stores/sidebar_store
'
;
describe
(
'
Sidebar Store
'
,
()
=>
{
const
dateString
=
'
2017-01-20
'
;
describe
(
'
constructor
'
,
()
=>
{
it
(
'
should set startDate
'
,
()
=>
{
const
store
=
new
SidebarStore
({
startDate
:
dateString
,
});
expect
(
store
.
startDate
).
toEqual
(
dateString
);
});
it
(
'
should set endDate
'
,
()
=>
{
const
store
=
new
SidebarStore
({
endDate
:
dateString
,
});
expect
(
store
.
endDate
).
toEqual
(
dateString
);
});
});
describe
(
'
startDateTime
'
,
()
=>
{
it
(
'
should return null when there is no startDate
'
,
()
=>
{
const
store
=
new
SidebarStore
({});
expect
(
store
.
startDateTime
).
toEqual
(
null
);
});
it
(
'
should return date
'
,
()
=>
{
const
store
=
new
SidebarStore
({
startDate
:
dateString
,
});
const
date
=
store
.
startDateTime
;
expect
(
date
.
getDate
()).
toEqual
(
20
);
expect
(
date
.
getMonth
()).
toEqual
(
0
);
expect
(
date
.
getFullYear
()).
toEqual
(
2017
);
});
});
describe
(
'
endDateTime
'
,
()
=>
{
it
(
'
should return null when there is no endDate
'
,
()
=>
{
const
store
=
new
SidebarStore
({});
expect
(
store
.
endDateTime
).
toEqual
(
null
);
});
it
(
'
should return date
'
,
()
=>
{
const
store
=
new
SidebarStore
({
endDate
:
dateString
,
});
const
date
=
store
.
endDateTime
;
expect
(
date
.
getDate
()).
toEqual
(
20
);
expect
(
date
.
getMonth
()).
toEqual
(
0
);
expect
(
date
.
getFullYear
()).
toEqual
(
2017
);
});
});
});
spec/javascripts/lib/utils/text_utility_spec.js
View file @
b42b0f97
import
{
highCountTrim
}
from
'
~/lib/utils/text_utility
'
;
import
*
as
textUtility
from
'
~/lib/utils/text_utility
'
;
describe
(
'
text_utility
'
,
()
=>
{
describe
(
'
gl.text.getTextWidth
'
,
()
=>
{
...
...
@@ -37,12 +37,18 @@ describe('text_utility', () => {
describe
(
'
highCountTrim
'
,
()
=>
{
it
(
'
returns 99+ for count >= 100
'
,
()
=>
{
expect
(
highCountTrim
(
105
)).
toBe
(
'
99+
'
);
expect
(
highCountTrim
(
100
)).
toBe
(
'
99+
'
);
expect
(
textUtility
.
highCountTrim
(
105
)).
toBe
(
'
99+
'
);
expect
(
textUtility
.
highCountTrim
(
100
)).
toBe
(
'
99+
'
);
});
it
(
'
returns exact number for count < 100
'
,
()
=>
{
expect
(
highCountTrim
(
45
)).
toBe
(
45
);
expect
(
textUtility
.
highCountTrim
(
45
)).
toBe
(
45
);
});
});
describe
(
'
capitalizeFirstCharacter
'
,
()
=>
{
it
(
'
returns string with first letter capitalized
'
,
()
=>
{
expect
(
textUtility
.
capitalizeFirstCharacter
(
'
gitlab
'
)).
toEqual
(
'
Gitlab
'
);
});
});
...
...
spec/javascripts/vue_shared/components/pikaday_spec.js
0 → 100644
View file @
b42b0f97
import
Vue
from
'
vue
'
;
import
datePicker
from
'
~/vue_shared/components/pikaday.vue
'
;
import
mountComponent
from
'
../../helpers/vue_mount_component_helper
'
;
describe
(
'
datePicker
'
,
()
=>
{
let
vm
;
beforeEach
(()
=>
{
const
DatePicker
=
Vue
.
extend
(
datePicker
);
vm
=
mountComponent
(
DatePicker
,
{
label
:
'
label
'
,
});
});
it
(
'
should render label text
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.dropdown-toggle-text
'
).
innerText
.
trim
()).
toEqual
(
'
label
'
);
});
it
(
'
should show calendar
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.pika-single
'
)).
toBeDefined
();
});
it
(
'
should toggle when dropdown is clicked
'
,
()
=>
{
const
hidePicker
=
jasmine
.
createSpy
();
vm
.
$on
(
'
hidePicker
'
,
hidePicker
);
vm
.
$el
.
querySelector
(
'
.dropdown-menu-toggle
'
).
click
();
expect
(
hidePicker
).
toHaveBeenCalled
();
});
});
spec/javascripts/vue_shared/components/sidebar/collapsed_calendar_icon_spec.js
0 → 100644
View file @
b42b0f97
import
Vue
from
'
vue
'
;
import
collapsedCalendarIcon
from
'
~/vue_shared/components/sidebar/collapsed_calendar_icon.vue
'
;
import
mountComponent
from
'
../../../helpers/vue_mount_component_helper
'
;
describe
(
'
collapsedCalendarIcon
'
,
()
=>
{
let
vm
;
beforeEach
(()
=>
{
const
CollapsedCalendarIcon
=
Vue
.
extend
(
collapsedCalendarIcon
);
vm
=
mountComponent
(
CollapsedCalendarIcon
,
{
containerClass
:
'
test-class
'
,
text
:
'
text
'
,
showIcon
:
false
,
});
});
it
(
'
should add class to container
'
,
()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'
test-class
'
)).
toEqual
(
true
);
});
it
(
'
should hide calendar icon if showIcon
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.fa-calendar
'
)).
toBeNull
();
});
it
(
'
should render text
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
span
'
).
innerText
.
trim
()).
toEqual
(
'
text
'
);
});
it
(
'
should emit click event when container is clicked
'
,
()
=>
{
const
click
=
jasmine
.
createSpy
();
vm
.
$on
(
'
click
'
,
click
);
vm
.
$el
.
click
();
expect
(
click
).
toHaveBeenCalled
();
});
});
spec/javascripts/vue_shared/components/sidebar/collapsed_grouped_date_picker_spec.js
0 → 100644
View file @
b42b0f97
import
Vue
from
'
vue
'
;
import
collapsedGroupedDatePicker
from
'
~/vue_shared/components/sidebar/collapsed_grouped_date_picker.vue
'
;
import
mountComponent
from
'
../../../helpers/vue_mount_component_helper
'
;
describe
(
'
collapsedGroupedDatePicker
'
,
()
=>
{
let
vm
;
beforeEach
(()
=>
{
const
CollapsedGroupedDatePicker
=
Vue
.
extend
(
collapsedGroupedDatePicker
);
vm
=
mountComponent
(
CollapsedGroupedDatePicker
,
{
showToggleSidebar
:
true
,
});
});
it
(
'
should render toggle sidebar if showToggleSidebar
'
,
(
done
)
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.issuable-sidebar-header
'
)).
toBeDefined
();
vm
.
showToggleSidebar
=
false
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.issuable-sidebar-header
'
)).
toBeNull
();
done
();
});
});
it
(
'
toggleCollapse events
'
,
()
=>
{
const
toggleCollapse
=
jasmine
.
createSpy
();
beforeEach
((
done
)
=>
{
vm
.
minDate
=
new
Date
(
'
07/17/2016
'
);
Vue
.
nextTick
(
done
);
});
it
(
'
should emit when sidebar is toggled
'
,
()
=>
{
vm
.
$el
.
querySelector
(
'
.gutter-toggle
'
).
click
();
expect
(
toggleCollapse
).
toHaveBeenCalled
();
});
it
(
'
should emit when collapsed-calendar-icon is clicked
'
,
()
=>
{
vm
.
$el
.
querySelector
(
'
.sidebar-collapsed-icon
'
).
click
();
expect
(
toggleCollapse
).
toHaveBeenCalled
();
});
});
describe
(
'
minDate and maxDate
'
,
()
=>
{
beforeEach
((
done
)
=>
{
vm
.
minDate
=
new
Date
(
'
07/17/2016
'
);
vm
.
maxDate
=
new
Date
(
'
07/17/2017
'
);
Vue
.
nextTick
(
done
);
});
it
(
'
should render both collapsed-calendar-icon
'
,
()
=>
{
const
icons
=
vm
.
$el
.
querySelectorAll
(
'
.sidebar-collapsed-icon
'
);
expect
(
icons
.
length
).
toEqual
(
2
);
expect
(
icons
[
0
].
innerText
.
trim
()).
toEqual
(
'
Jul 17 2016
'
);
expect
(
icons
[
1
].
innerText
.
trim
()).
toEqual
(
'
Jul 17 2017
'
);
});
});
describe
(
'
minDate
'
,
()
=>
{
beforeEach
((
done
)
=>
{
vm
.
minDate
=
new
Date
(
'
07/17/2016
'
);
Vue
.
nextTick
(
done
);
});
it
(
'
should render minDate in collapsed-calendar-icon
'
,
()
=>
{
const
icons
=
vm
.
$el
.
querySelectorAll
(
'
.sidebar-collapsed-icon
'
);
expect
(
icons
.
length
).
toEqual
(
1
);
expect
(
icons
[
0
].
innerText
.
trim
()).
toEqual
(
'
From Jul 17 2016
'
);
});
});
describe
(
'
maxDate
'
,
()
=>
{
beforeEach
((
done
)
=>
{
vm
.
maxDate
=
new
Date
(
'
07/17/2017
'
);
Vue
.
nextTick
(
done
);
});
it
(
'
should render maxDate in collapsed-calendar-icon
'
,
()
=>
{
const
icons
=
vm
.
$el
.
querySelectorAll
(
'
.sidebar-collapsed-icon
'
);
expect
(
icons
.
length
).
toEqual
(
1
);
expect
(
icons
[
0
].
innerText
.
trim
()).
toEqual
(
'
Until Jul 17 2017
'
);
});
});
describe
(
'
no dates
'
,
()
=>
{
it
(
'
should render None
'
,
()
=>
{
const
icons
=
vm
.
$el
.
querySelectorAll
(
'
.sidebar-collapsed-icon
'
);
expect
(
icons
.
length
).
toEqual
(
1
);
expect
(
icons
[
0
].
innerText
.
trim
()).
toEqual
(
'
None
'
);
});
});
});
spec/javascripts/vue_shared/components/sidebar/date_picker_spec.js
0 → 100644
View file @
b42b0f97
import
Vue
from
'
vue
'
;
import
sidebarDatePicker
from
'
~/vue_shared/components/sidebar/date_picker.vue
'
;
import
mountComponent
from
'
../../../helpers/vue_mount_component_helper
'
;
describe
(
'
sidebarDatePicker
'
,
()
=>
{
let
vm
;
beforeEach
(()
=>
{
const
SidebarDatePicker
=
Vue
.
extend
(
sidebarDatePicker
);
vm
=
mountComponent
(
SidebarDatePicker
,
{
label
:
'
label
'
,
isLoading
:
true
,
});
});
it
(
'
should emit toggleCollapse when collapsed toggle sidebar is clicked
'
,
()
=>
{
const
toggleCollapse
=
jasmine
.
createSpy
();
vm
.
$on
(
'
toggleCollapse
'
,
toggleCollapse
);
vm
.
$el
.
querySelector
(
'
.issuable-sidebar-header .gutter-toggle
'
).
click
();
expect
(
toggleCollapse
).
toHaveBeenCalled
();
});
it
(
'
should render collapsed-calendar-icon
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.sidebar-collapsed-icon
'
)).
toBeDefined
();
});
it
(
'
should render label
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.title
'
).
innerText
.
trim
()).
toEqual
(
'
label
'
);
});
it
(
'
should render loading-icon when isLoading
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.fa-spin
'
)).
toBeDefined
();
});
it
(
'
should render value when not editing
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.value-content
'
)).
toBeDefined
();
});
it
(
'
should render None if there is no selectedDate
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.value-content span
'
).
innerText
.
trim
()).
toEqual
(
'
None
'
);
});
it
(
'
should render date-picker when editing
'
,
(
done
)
=>
{
vm
.
editing
=
true
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.pika-label
'
)).
toBeDefined
();
done
();
});
});
describe
(
'
editable
'
,
()
=>
{
beforeEach
((
done
)
=>
{
vm
.
editable
=
true
;
Vue
.
nextTick
(
done
);
});
it
(
'
should render edit button
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.title .btn-blank
'
).
innerText
.
trim
()).
toEqual
(
'
Edit
'
);
});
it
(
'
should enable editing when edit button is clicked
'
,
(
done
)
=>
{
vm
.
isLoading
=
false
;
Vue
.
nextTick
(()
=>
{
vm
.
$el
.
querySelector
(
'
.title .btn-blank
'
).
click
();
expect
(
vm
.
editing
).
toEqual
(
true
);
done
();
});
});
});
it
(
'
should render date if selectedDate
'
,
(
done
)
=>
{
vm
.
selectedDate
=
new
Date
(
'
07/07/2017
'
);
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.value-content strong
'
).
innerText
.
trim
()).
toEqual
(
'
Jul 7, 2017
'
);
done
();
});
});
describe
(
'
selectedDate and editable
'
,
()
=>
{
beforeEach
((
done
)
=>
{
vm
.
selectedDate
=
new
Date
(
'
07/07/2017
'
);
vm
.
editable
=
true
;
Vue
.
nextTick
(
done
);
});
it
(
'
should render remove button if selectedDate and editable
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.value-content .btn-blank
'
).
innerText
.
trim
()).
toEqual
(
'
remove
'
);
});
it
(
'
should emit saveDate when remove button is clicked
'
,
()
=>
{
const
saveDate
=
jasmine
.
createSpy
();
vm
.
$on
(
'
saveDate
'
,
saveDate
);
vm
.
$el
.
querySelector
(
'
.value-content .btn-blank
'
).
click
();
expect
(
saveDate
).
toHaveBeenCalled
();
});
});
describe
(
'
showToggleSidebar
'
,
()
=>
{
beforeEach
((
done
)
=>
{
vm
.
showToggleSidebar
=
true
;
Vue
.
nextTick
(
done
);
});
it
(
'
should render toggle-sidebar when showToggleSidebar
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.title .gutter-toggle
'
)).
toBeDefined
();
});
it
(
'
should emit toggleCollapse when toggle sidebar is clicked
'
,
()
=>
{
const
toggleCollapse
=
jasmine
.
createSpy
();
vm
.
$on
(
'
toggleCollapse
'
,
toggleCollapse
);
vm
.
$el
.
querySelector
(
'
.title .gutter-toggle
'
).
click
();
expect
(
toggleCollapse
).
toHaveBeenCalled
();
});
});
});
spec/javascripts/vue_shared/components/sidebar/sidebar_app_spec.js
0 → 100644
View file @
b42b0f97
import
Vue
from
'
vue
'
;
import
Cookies
from
'
js-cookie
'
;
import
epicSidebar
from
'
ee/epics/sidebar/components/sidebar_app.vue
'
;
import
mountComponent
from
'
../../../helpers/vue_mount_component_helper
'
;
describe
(
'
epicSidebar
'
,
()
=>
{
let
vm
;
let
originalCookieState
;
let
EpicSidebar
;
beforeEach
(()
=>
{
setFixtures
(
`
<div class="page-with-sidebar right-sidebar-expanded">
<div id="epic-sidebar"></div>
</div>
`
);
originalCookieState
=
Cookies
.
get
(
'
collapsed_gutter
'
);
Cookies
.
set
(
'
collapsed_gutter
'
,
null
);
EpicSidebar
=
Vue
.
extend
(
epicSidebar
);
vm
=
mountComponent
(
EpicSidebar
,
{
endpoint
:
gl
.
TEST_HOST
,
},
'
#epic-sidebar
'
);
});
afterEach
(()
=>
{
Cookies
.
set
(
'
collapsed_gutter
'
,
originalCookieState
);
});
it
(
'
should render right-sidebar-expanded class when not collapsed
'
,
()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'
right-sidebar-expanded
'
)).
toEqual
(
true
);
});
it
(
'
should render min date sidebar-date-picker
'
,
()
=>
{
vm
=
mountComponent
(
EpicSidebar
,
{
endpoint
:
gl
.
TEST_HOST
,
initialStartDate
:
'
2017-01-01
'
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.value-content strong
'
).
innerText
.
trim
()).
toEqual
(
'
Jan 1, 2017
'
);
});
it
(
'
should render max date sidebar-date-picker
'
,
()
=>
{
vm
=
mountComponent
(
EpicSidebar
,
{
endpoint
:
gl
.
TEST_HOST
,
initialEndDate
:
'
2018-01-01
'
,
});
expect
(
vm
.
$el
.
querySelector
(
'
.value-content strong
'
).
innerText
.
trim
()).
toEqual
(
'
Jan 1, 2018
'
);
});
it
(
'
should render both sidebar-date-picker
'
,
()
=>
{
vm
=
mountComponent
(
EpicSidebar
,
{
endpoint
:
gl
.
TEST_HOST
,
initialStartDate
:
'
2017-01-01
'
,
initialEndDate
:
'
2018-01-01
'
,
});
const
datePickers
=
vm
.
$el
.
querySelectorAll
(
'
.block
'
);
expect
(
datePickers
[
0
].
querySelector
(
'
.value-content strong
'
).
innerText
.
trim
()).
toEqual
(
'
Jan 1, 2017
'
);
expect
(
datePickers
[
1
].
querySelector
(
'
.value-content strong
'
).
innerText
.
trim
()).
toEqual
(
'
Jan 1, 2018
'
);
});
describe
(
'
when collapsed
'
,
()
=>
{
beforeEach
(()
=>
{
Cookies
.
set
(
'
collapsed_gutter
'
,
'
true
'
);
vm
=
mountComponent
(
EpicSidebar
,
{
endpoint
:
gl
.
TEST_HOST
,
initialStartDate
:
'
2017-01-01
'
,
});
});
it
(
'
should render right-sidebar-collapsed class
'
,
()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'
right-sidebar-collapsed
'
)).
toEqual
(
true
);
});
it
(
'
should render collapsed grouped date picker
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.sidebar-collapsed-icon span
'
).
innerText
.
trim
()).
toEqual
(
'
From Jan 1 2017
'
);
});
});
describe
(
'
toggleSidebar
'
,
()
=>
{
it
(
'
should toggle collapsed_gutter cookie
'
,
()
=>
{
expect
(
vm
.
$el
.
classList
.
contains
(
'
right-sidebar-expanded
'
)).
toEqual
(
true
);
vm
.
$el
.
querySelector
(
'
.gutter-toggle
'
).
click
();
expect
(
Cookies
.
get
(
'
collapsed_gutter
'
)).
toEqual
(
'
true
'
);
});
it
(
'
should toggle contentContainer css class
'
,
()
=>
{
const
contentContainer
=
document
.
querySelector
(
'
.page-with-sidebar
'
);
expect
(
contentContainer
.
classList
.
contains
(
'
right-sidebar-expanded
'
)).
toEqual
(
true
);
expect
(
contentContainer
.
classList
.
contains
(
'
right-sidebar-collapsed
'
)).
toEqual
(
false
);
vm
.
$el
.
querySelector
(
'
.gutter-toggle
'
).
click
();
expect
(
contentContainer
.
classList
.
contains
(
'
right-sidebar-expanded
'
)).
toEqual
(
false
);
expect
(
contentContainer
.
classList
.
contains
(
'
right-sidebar-collapsed
'
)).
toEqual
(
true
);
});
});
describe
(
'
saveDate
'
,
()
=>
{
let
interceptor
;
let
component
;
beforeEach
(()
=>
{
interceptor
=
(
request
,
next
)
=>
{
next
(
request
.
respondWith
(
JSON
.
stringify
({}),
{
status
:
200
,
}));
};
Vue
.
http
.
interceptors
.
push
(
interceptor
);
component
=
new
EpicSidebar
({
propsData
:
{
endpoint
:
gl
.
TEST_HOST
,
},
});
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
interceptor
);
});
it
(
'
should save startDate
'
,
(
done
)
=>
{
const
date
=
'
2017-01-01
'
;
expect
(
component
.
store
.
startDate
).
toBeUndefined
();
component
.
saveStartDate
(
date
)
.
then
(()
=>
{
expect
(
component
.
store
.
startDate
).
toEqual
(
date
);
done
();
})
.
catch
(
done
.
fail
);
});
it
(
'
should save endDate
'
,
(
done
)
=>
{
const
date
=
'
2017-01-01
'
;
expect
(
component
.
store
.
endDate
).
toBeUndefined
();
component
.
saveEndDate
(
date
)
.
then
(()
=>
{
expect
(
component
.
store
.
endDate
).
toEqual
(
date
);
done
();
})
.
catch
(
done
.
fail
);
});
it
(
'
should handle errors gracefully
'
,
()
=>
{});
});
describe
(
'
saveDate error
'
,
()
=>
{
let
interceptor
;
let
component
;
beforeEach
(()
=>
{
interceptor
=
(
request
,
next
)
=>
{
next
(
request
.
respondWith
(
JSON
.
stringify
({}),
{
status
:
500
,
}));
};
Vue
.
http
.
interceptors
.
push
(
interceptor
);
component
=
new
EpicSidebar
({
propsData
:
{
endpoint
:
gl
.
TEST_HOST
,
},
});
});
afterEach
(()
=>
{
Vue
.
http
.
interceptors
=
_
.
without
(
Vue
.
http
.
interceptors
,
interceptor
);
});
it
(
'
should handle errors gracefully
'
,
(
done
)
=>
{
const
date
=
'
2017-01-01
'
;
expect
(
component
.
store
.
startDate
).
toBeUndefined
();
component
.
saveDate
(
'
start
'
,
date
)
.
then
(()
=>
{
expect
(
component
.
store
.
startDate
).
toBeUndefined
();
done
();
})
.
catch
(
done
.
fail
);
});
});
});
spec/javascripts/vue_shared/components/sidebar/toggle_sidebar_spec.js
0 → 100644
View file @
b42b0f97
import
Vue
from
'
vue
'
;
import
toggleSidebar
from
'
~/vue_shared/components/sidebar/toggle_sidebar.vue
'
;
import
mountComponent
from
'
../../../helpers/vue_mount_component_helper
'
;
describe
(
'
toggleSidebar
'
,
()
=>
{
let
vm
;
beforeEach
(()
=>
{
const
ToggleSidebar
=
Vue
.
extend
(
toggleSidebar
);
vm
=
mountComponent
(
ToggleSidebar
,
{
collapsed
:
true
,
});
});
it
(
'
should render << when collapsed
'
,
()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.fa
'
).
classList
.
contains
(
'
fa-angle-double-left
'
)).
toEqual
(
true
);
});
it
(
'
should render >> when collapsed
'
,
()
=>
{
vm
.
collapsed
=
false
;
Vue
.
nextTick
(()
=>
{
expect
(
vm
.
$el
.
querySelector
(
'
.fa
'
).
classList
.
contains
(
'
fa-angle-double-right
'
)).
toEqual
(
true
);
});
});
it
(
'
should emit toggle event when button clicked
'
,
()
=>
{
const
toggle
=
jasmine
.
createSpy
();
vm
.
$on
(
'
toggle
'
,
toggle
);
vm
.
$el
.
click
();
expect
(
toggle
).
toHaveBeenCalled
();
});
});
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