Merge branch 'backport-add-epic-sidebar' into 'master'
Backport of add-epic-sidebar See merge request gitlab-org/gitlab-ce!15335
This commit is contained in:
commit
f8de23e626
17 changed files with 852 additions and 9 deletions
|
@ -150,3 +150,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}`;
|
||||
}
|
||||
|
|
|
@ -55,3 +55,12 @@ export const slugify = str => str.trim().toLowerCase();
|
|||
*/
|
||||
export const truncate = (string, maxLength) => `${string.substr(0, (maxLength - 3))}...`;
|
||||
|
||||
/**
|
||||
* Capitalizes first character
|
||||
*
|
||||
* @param {String} text
|
||||
* @return {String}
|
||||
*/
|
||||
export function capitalizeFirstCharacter(text) {
|
||||
return `${text[0].toUpperCase()}${text.slice(1)}`;
|
||||
}
|
||||
|
|
79
app/assets/javascripts/vue_shared/components/pikaday.vue
Normal file
79
app/assets/javascripts/vue_shared/components/pikaday.vue
Normal file
|
@ -0,0 +1,79 @@
|
|||
<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>
|
|
@ -0,0 +1,46 @@
|
|||
<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>
|
|
@ -0,0 +1,109 @@
|
|||
<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>
|
|
@ -0,0 +1,163 @@
|
|||
<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>
|
|
@ -0,0 +1,30 @@
|
|||
<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>
|
|
@ -408,6 +408,7 @@
|
|||
padding: 0;
|
||||
background: transparent;
|
||||
border: 0;
|
||||
border-radius: 0;
|
||||
|
||||
&:hover,
|
||||
&:active,
|
||||
|
@ -417,3 +418,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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -276,10 +276,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;
|
||||
}
|
||||
|
@ -287,6 +292,8 @@
|
|||
.gutter-toggle {
|
||||
margin-top: 7px;
|
||||
border-left: 1px solid $border-gray-normal;
|
||||
padding-left: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.title .gutter-toggle {
|
||||
|
@ -359,7 +366,7 @@
|
|||
fill: $issuable-sidebar-color;
|
||||
}
|
||||
|
||||
&:hover,
|
||||
&:hover:not(.disabled),
|
||||
&:hover .todo-undone {
|
||||
color: $gl-text-color;
|
||||
|
||||
|
@ -900,3 +907,21 @@
|
|||
margin: 0 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
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');
|
||||
});
|
||||
});
|
||||
})();
|
||||
|
|
|
@ -23,6 +23,14 @@ describe('text_utility', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('capitalizeFirstCharacter', () => {
|
||||
it('returns string with first letter capitalized', () => {
|
||||
expect(textUtils.capitalizeFirstCharacter('gitlab')).toEqual('Gitlab');
|
||||
expect(textUtils.highCountTrim(105)).toBe('99+');
|
||||
expect(textUtils.highCountTrim(100)).toBe('99+');
|
||||
});
|
||||
});
|
||||
|
||||
describe('humanize', () => {
|
||||
it('should remove underscores and uppercase the first letter', () => {
|
||||
expect(textUtils.humanize('foo_bar')).toEqual('Foo bar');
|
||||
|
|
29
spec/javascripts/vue_shared/components/pikaday_spec.js
Normal file
29
spec/javascripts/vue_shared/components/pikaday_spec.js
Normal file
|
@ -0,0 +1,29 @@
|
|||
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();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,35 @@
|
|||
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();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
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');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,117 @@
|
|||
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();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,32 @@
|
|||
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();
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue