Commit 4bc1239b authored by Phil Hughes's avatar Phil Hughes

Merge branch 'kp-improve-roadmap-today-indicator-render-logic' into 'master'

Improve Roadmap today indicator rendering logic

See merge request gitlab-org/gitlab!24669
parents c5a8c8a9 ef59084d
<script>
import CommonMixin from '../mixins/common_mixin';
export default {
mixins: [CommonMixin],
props: {
presetType: {
type: String,
required: true,
},
timeframeItem: {
type: [Date, Object],
required: true,
},
},
data() {
const currentDate = new Date();
currentDate.setHours(0, 0, 0, 0);
return {
currentDate,
indicatorStyles: {},
};
},
mounted() {
this.$nextTick(() => {
this.indicatorStyles = this.getIndicatorStyles();
});
},
};
</script>
<template>
<span
v-if="hasToday"
:style="indicatorStyles"
class="current-day-indicator position-absolute"
></span>
</template>
<script>
import tooltip from '~/vue_shared/directives/tooltip';
import CommonMixin from '../mixins/common_mixin';
import QuartersPresetMixin from '../mixins/quarters_preset_mixin';
import MonthsPresetMixin from '../mixins/months_preset_mixin';
import WeeksPresetMixin from '../mixins/weeks_preset_mixin';
import { TIMELINE_CELL_MIN_WIDTH, PRESET_TYPES } from '../constants';
import CurrentDayIndicator from './current_day_indicator.vue';
import { TIMELINE_CELL_MIN_WIDTH } from '../constants';
export default {
cellWidth: TIMELINE_CELL_MIN_WIDTH,
directives: {
tooltip,
},
mixins: [QuartersPresetMixin, MonthsPresetMixin, WeeksPresetMixin],
components: {
CurrentDayIndicator,
},
mixins: [CommonMixin, QuartersPresetMixin, MonthsPresetMixin, WeeksPresetMixin],
props: {
presetType: {
type: String,
......@@ -55,11 +61,11 @@ export default {
};
},
hasStartDate() {
if (this.presetType === PRESET_TYPES.QUARTERS) {
if (this.presetTypeQuarters) {
return this.hasStartDateForQuarter();
} else if (this.presetType === PRESET_TYPES.MONTHS) {
} else if (this.presetTypeMonths) {
return this.hasStartDateForMonth();
} else if (this.presetType === PRESET_TYPES.WEEKS) {
} else if (this.presetTypeWeeks) {
return this.hasStartDateForWeek();
}
return false;
......@@ -68,14 +74,14 @@ export default {
let barStyles = {};
if (this.hasStartDate) {
if (this.presetType === PRESET_TYPES.QUARTERS) {
if (this.presetTypeQuarters) {
// CSS properties are a false positive: https://gitlab.com/gitlab-org/frontend/eslint-plugin-i18n/issues/24
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
barStyles = `width: ${this.getTimelineBarWidthForQuarters()}px; ${this.getTimelineBarStartOffsetForQuarters()}`;
} else if (this.presetType === PRESET_TYPES.MONTHS) {
} else if (this.presetTypeMonths) {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
barStyles = `width: ${this.getTimelineBarWidthForMonths()}px; ${this.getTimelineBarStartOffsetForMonths()}`;
} else if (this.presetType === PRESET_TYPES.WEEKS) {
} else if (this.presetTypeWeeks) {
// eslint-disable-next-line @gitlab/i18n/no-non-i18n-strings
barStyles = `width: ${this.getTimelineBarWidthForWeeks()}px; ${this.getTimelineBarStartOffsetForWeeks()}`;
}
......@@ -88,6 +94,7 @@ export default {
<template>
<span class="epic-timeline-cell" data-qa-selector="epic_timeline_cell">
<current-day-indicator :preset-type="presetType" :timeframe-item="timeframeItem" />
<div class="timeline-bar-wrapper">
<a
v-if="hasStartDate"
......
......@@ -2,11 +2,13 @@
import { s__, sprintf } from '~/locale';
import { dateInWords } from '~/lib/utils/datetime_utility';
import { PRESET_TYPES, emptyStateDefault, emptyStateWithFilters } from '../constants';
import CommonMixin from '../mixins/common_mixin';
import { emptyStateDefault, emptyStateWithFilters } from '../constants';
import initEpicCreate from '../../epic/epic_bundle';
export default {
mixins: [CommonMixin],
props: {
presetType: {
type: String,
......@@ -43,7 +45,7 @@ export default {
let startDate;
let endDate;
if (this.presetType === PRESET_TYPES.QUARTERS) {
if (this.presetTypeQuarters) {
const quarterStart = this.timeframeStart.range[0];
const quarterEnd = this.timeframeEnd.range[2];
startDate = dateInWords(
......@@ -52,14 +54,14 @@ export default {
quarterStart.getFullYear() === quarterEnd.getFullYear(),
);
endDate = dateInWords(quarterEnd, true);
} else if (this.presetType === PRESET_TYPES.MONTHS) {
} else if (this.presetTypeMonths) {
startDate = dateInWords(
this.timeframeStart,
true,
this.timeframeStart.getFullYear() === this.timeframeEnd.getFullYear(),
);
endDate = dateInWords(this.timeframeEnd, true);
} else if (this.presetType === PRESET_TYPES.WEEKS) {
} else if (this.presetTypeWeeks) {
const end = new Date(this.timeframeEnd.getTime());
end.setDate(end.getDate() + 6);
......
......@@ -9,6 +9,7 @@ import eventHub from '../event_hub';
import { EPIC_DETAILS_CELL_WIDTH, TIMELINE_CELL_MIN_WIDTH, EPIC_ITEM_HEIGHT } from '../constants';
import EpicItem from './epic_item.vue';
import CurrentDayIndicator from './current_day_indicator.vue';
export default {
EpicItem,
......@@ -16,6 +17,7 @@ export default {
components: {
VirtualList,
EpicItem,
CurrentDayIndicator,
},
mixins: [glFeatureFlagsMixin()],
props: {
......@@ -143,6 +145,7 @@ export default {
v-for="(epic, index) in epics"
ref="epicItems"
:key="index"
:first-epic="index === 0"
:preset-type="presetType"
:epic="epic"
:timeframe="timeframe"
......@@ -155,11 +158,9 @@ export default {
class="epics-list-item epics-list-item-empty clearfix"
>
<span class="epic-details-cell"></span>
<span
v-for="(timeframeItem, index) in timeframe"
:key="index"
class="epic-timeline-cell"
></span>
<span v-for="(timeframeItem, index) in timeframe" :key="index" class="epic-timeline-cell">
<current-day-indicator :preset-type="presetType" :timeframe-item="timeframeItem" />
</span>
</div>
<div v-show="showBottomShadow" :style="shadowCellStyles" class="scroll-bottom-shadow"></div>
</div>
......
<script>
import { getSundays } from '~/lib/utils/datetime_utility';
import { PRESET_TYPES } from '../../constants';
import CommonMixin from '../../mixins/common_mixin';
import timelineTodayIndicator from '../timeline_today_indicator.vue';
import { PRESET_TYPES } from '../../constants';
export default {
presetType: PRESET_TYPES.MONTHS,
components: {
timelineTodayIndicator,
},
mixins: [CommonMixin],
props: {
currentDate: {
type: Date,
......@@ -20,6 +17,12 @@ export default {
required: true,
},
},
data() {
return {
presetType: PRESET_TYPES.MONTHS,
indicatorStyle: {},
};
},
computed: {
headerSubItems() {
return getSundays(this.timeframeItem);
......@@ -33,15 +36,11 @@ export default {
// Show dark color text only for dates from current month and future months.
return timeframeYear >= currentYear && timeframeMonth >= currentMonth ? 'label-dark' : '';
},
hasToday() {
const timeframeYear = this.timeframeItem.getFullYear();
const timeframeMonth = this.timeframeItem.getMonth();
return (
this.currentDate.getMonth() === timeframeMonth &&
this.currentDate.getFullYear() === timeframeYear
);
},
},
mounted() {
this.$nextTick(() => {
this.indicatorStyle = this.getIndicatorStyles();
});
},
methods: {
getSubItemValueClass(subItem) {
......@@ -71,14 +70,12 @@ export default {
:key="index"
:class="getSubItemValueClass(subItem)"
class="sublabel-value"
>{{ subItem.getDate() }}</span
>
{{ subItem.getDate() }}
</span>
<timeline-today-indicator
<span
v-if="hasToday"
:preset-type="$options.presetType"
:current-date="currentDate"
:timeframe-item="timeframeItem"
/>
:style="indicatorStyle"
class="current-day-indicator-header preset-months position-absolute"
></span>
</div>
</template>
<script>
import { monthInWords } from '~/lib/utils/datetime_utility';
import { PRESET_TYPES } from '../../constants';
import CommonMixin from '../../mixins/common_mixin';
import timelineTodayIndicator from '../timeline_today_indicator.vue';
import { PRESET_TYPES } from '../../constants';
export default {
presetType: PRESET_TYPES.QUARTERS,
components: {
timelineTodayIndicator,
},
mixins: [CommonMixin],
props: {
currentDate: {
type: Date,
......@@ -20,6 +17,12 @@ export default {
required: true,
},
},
data() {
return {
presetType: PRESET_TYPES.QUARTERS,
indicatorStyle: {},
};
},
computed: {
quarterBeginDate() {
return this.timeframeItem.range[0];
......@@ -30,9 +33,11 @@ export default {
headerSubItems() {
return this.timeframeItem.range;
},
hasToday() {
return this.currentDate >= this.quarterBeginDate && this.currentDate <= this.quarterEndDate;
},
},
mounted() {
this.$nextTick(() => {
this.indicatorStyle = this.getIndicatorStyles();
});
},
methods: {
getSubItemValueClass(subItem) {
......@@ -62,14 +67,12 @@ export default {
:key="index"
:class="getSubItemValueClass(subItem)"
class="sublabel-value"
>{{ getSubItemValue(subItem) }}</span
>
{{ getSubItemValue(subItem) }}
</span>
<timeline-today-indicator
<span
v-if="hasToday"
:preset-type="$options.presetType"
:current-date="currentDate"
:timeframe-item="timeframeItem"
/>
:style="indicatorStyle"
class="current-day-indicator-header preset-quarters position-absolute"
></span>
</div>
</template>
<script>
import { PRESET_TYPES } from '../../constants';
import CommonMixin from '../../mixins/common_mixin';
import timelineTodayIndicator from '../timeline_today_indicator.vue';
import { PRESET_TYPES } from '../../constants';
export default {
presetType: PRESET_TYPES.WEEKS,
components: {
timelineTodayIndicator,
},
mixins: [CommonMixin],
props: {
currentDate: {
type: Date,
......@@ -18,6 +15,12 @@ export default {
required: true,
},
},
data() {
return {
presetType: PRESET_TYPES.WEEKS,
indicatorStyle: {},
};
},
computed: {
headerSubItems() {
const timeframeItem = new Date(this.timeframeItem.getTime());
......@@ -34,12 +37,11 @@ export default {
return headerSubItems;
},
hasToday() {
return (
this.currentDate.getTime() >= this.headerSubItems[0].getTime() &&
this.currentDate.getTime() <= this.headerSubItems[this.headerSubItems.length - 1].getTime()
);
},
},
mounted() {
this.$nextTick(() => {
this.indicatorStyle = this.getIndicatorStyles();
});
},
methods: {
getSubItemValueClass(subItem) {
......@@ -62,14 +64,12 @@ export default {
:key="index"
:class="getSubItemValueClass(subItem)"
class="sublabel-value"
>{{ subItem.getDate() }}</span
>
{{ subItem.getDate() }}
</span>
<timeline-today-indicator
<span
v-if="hasToday"
:preset-type="$options.presetType"
:current-date="currentDate"
:timeframe-item="timeframeItem"
/>
:style="indicatorStyle"
class="current-day-indicator-header preset-weeks position-absolute"
></span>
</div>
</template>
<script>
import eventHub from '../event_hub';
import { EPIC_DETAILS_CELL_WIDTH, TIMELINE_CELL_MIN_WIDTH, PRESET_TYPES } from '../constants';
import CommonMixin from '../mixins/common_mixin';
import { EPIC_DETAILS_CELL_WIDTH, TIMELINE_CELL_MIN_WIDTH } from '../constants';
import QuartersHeaderItem from './preset_quarters/quarters_header_item.vue';
import MonthsHeaderItem from './preset_months/months_header_item.vue';
......@@ -13,6 +14,7 @@ export default {
MonthsHeaderItem,
WeeksHeaderItem,
},
mixins: [CommonMixin],
props: {
presetType: {
type: String,
......@@ -34,11 +36,11 @@ export default {
},
computed: {
headerItemComponentForPreset() {
if (this.presetType === PRESET_TYPES.QUARTERS) {
if (this.presetTypeQuarters) {
return 'quarters-header-item';
} else if (this.presetType === PRESET_TYPES.MONTHS) {
} else if (this.presetTypeMonths) {
return 'months-header-item';
} else if (this.presetType === PRESET_TYPES.WEEKS) {
} else if (this.presetTypeWeeks) {
return 'weeks-header-item';
}
return '';
......
<script>
import { totalDaysInMonth, dayInQuarter, totalDaysInQuarter } from '~/lib/utils/datetime_utility';
import { EPIC_DETAILS_CELL_WIDTH, PRESET_TYPES, DAYS_IN_WEEK, SCROLL_BAR_SIZE } from '../constants';
import eventHub from '../event_hub';
import { PRESET_TYPES, DAYS_IN_WEEK } from '../constants';
export default {
props: {
presetType: {
type: String,
required: true,
computed: {
presetTypeQuarters() {
return this.presetType === PRESET_TYPES.QUARTERS;
},
currentDate: {
type: Date,
required: true,
presetTypeMonths() {
return this.presetType === PRESET_TYPES.MONTHS;
},
timeframeItem: {
type: [Date, Object],
required: true,
presetTypeWeeks() {
return this.presetType === PRESET_TYPES.WEEKS;
},
hasToday() {
if (this.presetTypeQuarters) {
return (
this.currentDate >= this.timeframeItem.range[0] &&
this.currentDate <= this.timeframeItem.range[2]
);
} else if (this.presetTypeMonths) {
return (
this.currentDate.getMonth() === this.timeframeItem.getMonth() &&
this.currentDate.getFullYear() === this.timeframeItem.getFullYear()
);
}
const timeframeItem = new Date(this.timeframeItem.getTime());
const headerSubItems = new Array(7)
.fill()
.map(
(val, i) =>
new Date(
timeframeItem.getFullYear(),
timeframeItem.getMonth(),
timeframeItem.getDate() + i,
),
);
return (
this.currentDate.getTime() >= headerSubItems[0].getTime() &&
this.currentDate.getTime() <= headerSubItems[headerSubItems.length - 1].getTime()
);
},
},
data() {
return {
todayBarStyles: {},
todayBarReady: true,
};
},
mounted() {
eventHub.$on('epicsListScrolled', this.handleEpicsListScroll);
this.$nextTick(() => {
this.todayBarStyles = this.getTodayBarStyles();
});
},
beforeDestroy() {
eventHub.$off('epicsListScrolled', this.handleEpicsListScroll);
},
methods: {
getTodayBarStyles() {
getIndicatorStyles() {
let left;
// Get total days of current timeframe Item and then
// get size in % from current date and days in range
// based on the current presetType
if (this.presetType === PRESET_TYPES.QUARTERS) {
if (this.presetTypeQuarters) {
left = Math.floor(
(dayInQuarter(this.currentDate, this.timeframeItem.range) /
totalDaysInQuarter(this.timeframeItem.range)) *
100,
);
} else if (this.presetType === PRESET_TYPES.MONTHS) {
} else if (this.presetTypeMonths) {
left = Math.floor(
(this.currentDate.getDate() / totalDaysInMonth(this.timeframeItem)) * 100,
);
} else if (this.presetType === PRESET_TYPES.WEEKS) {
} else if (this.presetTypeWeeks) {
left = Math.floor(((this.currentDate.getDay() + 1) / DAYS_IN_WEEK) * 100 - DAYS_IN_WEEK);
}
return {
left: `${left}%`,
height: `calc(100vh - ${this.$el.getBoundingClientRect().y + SCROLL_BAR_SIZE}px)`,
};
},
handleEpicsListScroll() {
const indicatorX = this.$el.getBoundingClientRect().x;
const rootOffsetLeft = this.$root.$el.parentElement.offsetLeft;
// 3px to compensate size of bubble on top of Indicator
this.todayBarReady = indicatorX - rootOffsetLeft >= EPIC_DETAILS_CELL_WIDTH + 3;
},
},
};
</script>
<template>
<span :class="{ invisible: !todayBarReady }" :style="todayBarStyles" class="today-bar"></span>
</template>
......@@ -161,7 +161,7 @@ html.group-epics-roadmap-html {
position: sticky;
position: -webkit-sticky;
top: 0;
z-index: 3;
z-index: 20;
.timeline-header-blank,
.timeline-header-item {
......@@ -224,25 +224,15 @@ html.group-epics-roadmap-html {
line-height: 1.5;
padding: 2px 0;
}
}
.today-bar {
position: absolute;
top: 20px;
width: 2px;
background-color: $red-500;
pointer-events: none;
}
.today-bar::before {
content: '';
position: absolute;
top: -2px;
left: -3px;
height: $grid-size;
width: $grid-size;
background-color: inherit;
border-radius: 50%;
}
.current-day-indicator-header {
bottom: 0;
height: $gl-vert-padding;
width: $gl-vert-padding;
background-color: $red-500;
border-radius: 50%;
transform: translateX(-2px);
}
}
......@@ -275,7 +265,6 @@ html.group-epics-roadmap-html {
.epic-details-cell,
.epic-timeline-cell {
background-color: $white-light;
border-bottom: 0;
}
}
......@@ -308,7 +297,7 @@ html.group-epics-roadmap-html {
padding: $gl-padding-8 $gl-padding;
font-size: $code-font-size;
background-color: $white-light;
z-index: 2;
z-index: 10;
&::after {
height: $item-height;
......@@ -348,10 +337,19 @@ html.group-epics-roadmap-html {
}
.epic-timeline-cell {
position: relative;
width: $timeline-cell-width;
background-color: transparent;
border-right: $border-style;
.current-day-indicator {
top: -1px;
width: 2px;
height: calc(100% + 1px);
background-color: $red-500;
pointer-events: none;
}
.timeline-bar-wrapper {
position: relative;
}
......@@ -364,6 +362,7 @@ html.group-epics-roadmap-html {
border-radius: $border-radius-default;
opacity: 0.75;
will-change: width, left;
z-index: 5;
&:hover {
opacity: 1;
......
import { shallowMount } from '@vue/test-utils';
import CurrentDayIndicator from 'ee/roadmap/components/current_day_indicator.vue';
import {
getTimeframeForQuartersView,
getTimeframeForMonthsView,
getTimeframeForWeeksView,
} from 'ee/roadmap/utils/roadmap_utils';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import { mockTimeframeInitialDate } from '../mock_data';
const mockTimeframeQuarters = getTimeframeForQuartersView(mockTimeframeInitialDate);
const mockTimeframeMonths = getTimeframeForMonthsView(mockTimeframeInitialDate);
const mockTimeframeWeeks = getTimeframeForWeeksView(mockTimeframeInitialDate);
const createComponent = () =>
shallowMount(CurrentDayIndicator, {
propsData: {
presetType: PRESET_TYPES.MONTHS,
timeframeItem: mockTimeframeMonths[0],
},
});
describe('CurrentDayIndicator', () => {
let wrapper;
beforeEach(() => {
wrapper = createComponent();
});
afterEach(() => {
wrapper.destroy();
});
describe('data', () => {
it('initializes currentDate and indicatorStyles props with default values', () => {
const currentDate = new Date();
expect(wrapper.vm.currentDate.getDate()).toBe(currentDate.getDate());
expect(wrapper.vm.currentDate.getMonth()).toBe(currentDate.getMonth());
expect(wrapper.vm.currentDate.getFullYear()).toBe(currentDate.getFullYear());
expect(wrapper.vm.indicatorStyles).toBeDefined();
});
});
describe('computed', () => {
describe('hasToday', () => {
it('returns true when presetType is QUARTERS and currentDate is within current quarter', done => {
wrapper.setData({
currentDate: mockTimeframeQuarters[0].range[1],
});
wrapper.setProps({
presetType: PRESET_TYPES.QUARTERS,
timeframeItem: mockTimeframeQuarters[0],
});
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.hasToday).toBe(true);
done();
});
});
it('returns true when presetType is MONTHS and currentDate is within current month', done => {
wrapper.setData({
currentDate: new Date(2020, 0, 15),
});
wrapper.setProps({
presetType: PRESET_TYPES.MONTHS,
timeframeItem: new Date(2020, 0, 1),
});
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.hasToday).toBe(true);
done();
});
});
it('returns true when presetType is WEEKS and currentDate is within current week', done => {
wrapper.setData({
currentDate: mockTimeframeWeeks[0],
});
wrapper.setProps({
presetType: PRESET_TYPES.WEEKS,
timeframeItem: mockTimeframeWeeks[0],
});
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.hasToday).toBe(true);
done();
});
});
});
});
describe('methods', () => {
describe('getIndicatorStyles', () => {
it('returns object containing `left` with value `34%` when presetType is QUARTERS', done => {
wrapper.setData({
currentDate: mockTimeframeQuarters[0].range[1],
});
wrapper.setProps({
presetType: PRESET_TYPES.QUARTERS,
timeframeItem: mockTimeframeQuarters[0],
});
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.getIndicatorStyles()).toEqual(
jasmine.objectContaining({
left: '34%',
}),
);
done();
});
});
it('returns object containing `left` with value `48%` when presetType is MONTHS', done => {
wrapper.setData({
currentDate: new Date(2020, 0, 15),
});
wrapper.setProps({
presetType: PRESET_TYPES.MONTHS,
timeframeItem: new Date(2020, 0, 1),
});
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.getIndicatorStyles()).toEqual(
jasmine.objectContaining({
left: '48%',
}),
);
done();
});
});
it('returns object containing `left` with value `7%` when presetType is WEEKS', done => {
wrapper.setData({
currentDate: mockTimeframeWeeks[0],
});
wrapper.setProps({
presetType: PRESET_TYPES.WEEKS,
timeframeItem: mockTimeframeWeeks[0],
});
wrapper.vm.$nextTick(() => {
expect(wrapper.vm.getIndicatorStyles()).toEqual(
jasmine.objectContaining({
left: '7%',
}),
);
done();
});
});
});
});
describe('template', () => {
beforeEach(done => {
wrapper.setData({
currentDate: mockTimeframeMonths[0],
});
wrapper.vm.$nextTick(() => {
done();
});
});
it('renders span element containing class `current-day-indicator`', () => {
expect(wrapper.element.classList.contains('current-day-indicator')).toBe(true);
});
it('renders span element with style attribute containing `left: 3%;`', () => {
expect(wrapper.element.getAttribute('style')).toBe('left: 3%;');
});
});
});
......@@ -74,6 +74,15 @@ describe('EpicItemTimelineComponent', () => {
expect(vm.$el.classList.contains('epic-timeline-cell')).toBe(true);
});
it('renders current day indicator element', () => {
const currentDate = new Date();
vm = createComponent({
timeframeItem: new Date(currentDate.getFullYear(), currentDate.getMonth(), 1),
});
expect(vm.$el.querySelector('span.current-day-indicator')).not.toBeNull();
});
it('renders timeline bar element with class `timeline-bar` and class `timeline-bar-wrapper` as container element', () => {
vm = createComponent({
epic: Object.assign({}, mockEpic, { startDate: mockTimeframeMonths[1] }),
......
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import MonthsHeaderSubItemComponent from 'ee/roadmap/components/preset_months/months_header_sub_item.vue';
import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframeInitialDate } from 'ee_spec/roadmap/mock_data';
......@@ -27,6 +28,15 @@ describe('MonthsHeaderSubItemComponent', () => {
vm.$destroy();
});
describe('data', () => {
it('initializes `presetType` and `indicatorStyles` data props', () => {
vm = createComponent({});
expect(vm.presetType).toBe(PRESET_TYPES.MONTHS);
expect(vm.indicatorStyle).toBeDefined();
});
});
describe('computed', () => {
describe('headerSubItems', () => {
it('returns array of dates containing Sundays from timeframeItem', () => {
......@@ -55,23 +65,6 @@ describe('MonthsHeaderSubItemComponent', () => {
expect(vm.headerSubItemClass).toBe('');
});
});
describe('hasToday', () => {
it('returns true when current month and year is same as timeframe month and year', () => {
vm = createComponent({});
expect(vm.hasToday).toBe(true);
});
it('returns false when current month and year is different from timeframe month and year', () => {
vm = createComponent({
currentDate: new Date(2017, 10, 1), // Nov 1, 2017
timeframeItem: new Date(2018, 0, 1), // Jan 1, 2018
});
expect(vm.hasToday).toBe(false);
});
});
});
describe('methods', () => {
......@@ -99,5 +92,9 @@ describe('MonthsHeaderSubItemComponent', () => {
it('renders sub item element with class `sublabel-value`', () => {
expect(vm.$el.querySelector('.sublabel-value')).not.toBeNull();
});
it('renders element with class `current-day-indicator-header` when hasToday is true', () => {
expect(vm.$el.querySelector('.current-day-indicator-header.preset-months')).not.toBeNull();
});
});
});
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import QuartersHeaderSubItemComponent from 'ee/roadmap/components/preset_quarters/quarters_header_sub_item.vue';
import { getTimeframeForQuartersView } from 'ee/roadmap/utils/roadmap_utils';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframeInitialDate } from 'ee_spec/roadmap/mock_data';
......@@ -27,6 +28,15 @@ describe('QuartersHeaderSubItemComponent', () => {
vm.$destroy();
});
describe('data', () => {
it('initializes `presetType` and `indicatorStyles` data props', () => {
vm = createComponent({});
expect(vm.presetType).toBe(PRESET_TYPES.QUARTERS);
expect(vm.indicatorStyle).toBeDefined();
});
});
describe('computed', () => {
describe('quarterBeginDate', () => {
it('returns first month from the `timeframeItem.range`', () => {
......@@ -54,23 +64,6 @@ describe('QuartersHeaderSubItemComponent', () => {
});
});
});
describe('hasToday', () => {
it('returns true when current quarter is same as timeframe quarter', () => {
vm = createComponent({});
expect(vm.hasToday).toBe(true);
});
it('returns false when current quarter month is different from timeframe quarter', () => {
vm = createComponent({
currentDate: new Date(2017, 10, 1), // Nov 1, 2017
timeframeItem: mockTimeframeQuarters[0], // 2018 Apr May Jun
});
expect(vm.hasToday).toBe(false);
});
});
});
describe('methods', () => {
......@@ -98,5 +91,9 @@ describe('QuartersHeaderSubItemComponent', () => {
it('renders sub item element with class `sublabel-value`', () => {
expect(vm.$el.querySelector('.sublabel-value')).not.toBeNull();
});
it('renders element with class `current-day-indicator-header` when hasToday is true', () => {
expect(vm.$el.querySelector('.current-day-indicator-header.preset-quarters')).not.toBeNull();
});
});
});
......@@ -2,6 +2,7 @@ import Vue from 'vue';
import WeeksHeaderSubItemComponent from 'ee/roadmap/components/preset_weeks/weeks_header_sub_item.vue';
import { getTimeframeForWeeksView } from 'ee/roadmap/utils/roadmap_utils';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframeInitialDate } from 'ee_spec/roadmap/mock_data';
......@@ -27,6 +28,15 @@ describe('MonthsHeaderSubItemComponent', () => {
vm.$destroy();
});
describe('data', () => {
it('initializes `presetType` and `indicatorStyles` data props', () => {
vm = createComponent({});
expect(vm.presetType).toBe(PRESET_TYPES.WEEKS);
expect(vm.indicatorStyle).toBeDefined();
});
});
describe('computed', () => {
describe('headerSubItems', () => {
it('returns `headerSubItems` array of dates containing days of week from timeframeItem', () => {
......@@ -39,23 +49,6 @@ describe('MonthsHeaderSubItemComponent', () => {
});
});
});
describe('hasToday', () => {
it('returns true when current week is same as timeframe week', () => {
vm = createComponent({});
expect(vm.hasToday).toBe(true);
});
it('returns false when current week is different from timeframe week', () => {
vm = createComponent({
currentDate: new Date(2017, 10, 1), // Nov 1, 2017
timeframeItem: new Date(2018, 0, 1), // Jan 1, 2018
});
expect(vm.hasToday).toBe(false);
});
});
});
describe('methods', () => {
......@@ -93,5 +86,9 @@ describe('MonthsHeaderSubItemComponent', () => {
it('renders sub item element with class `sublabel-value`', () => {
expect(vm.$el.querySelector('.sublabel-value')).not.toBeNull();
});
it('renders element with class `current-day-indicator-header` when hasToday is true', () => {
expect(vm.$el.querySelector('.current-day-indicator-header.preset-weeks')).not.toBeNull();
});
});
});
import Vue from 'vue';
import timelineTodayIndicatorComponent from 'ee/roadmap/components/timeline_today_indicator.vue';
import eventHub from 'ee/roadmap/event_hub';
import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import mountComponent from 'spec/helpers/vue_mount_component_helper';
import { mockTimeframeInitialDate } from '../mock_data';
const mockTimeframeMonths = getTimeframeForMonthsView(mockTimeframeInitialDate);
const mockCurrentDate = new Date(
mockTimeframeMonths[0].getFullYear(),
mockTimeframeMonths[0].getMonth(),
15,
);
const createComponent = ({
presetType = PRESET_TYPES.MONTHS,
currentDate = mockCurrentDate,
timeframeItem = mockTimeframeMonths[0],
}) => {
const Component = Vue.extend(timelineTodayIndicatorComponent);
return mountComponent(Component, {
presetType,
currentDate,
timeframeItem,
});
};
describe('TimelineTodayIndicatorComponent', () => {
let vm;
afterEach(() => {
vm.$destroy();
});
describe('data', () => {
it('returns default data props', () => {
vm = createComponent({});
expect(vm.todayBarStyles).toEqual({});
expect(vm.todayBarReady).toBe(true);
});
});
describe('methods', () => {
describe('getTodayBarStyles', () => {
it('sets `todayBarStyles` and `todayBarReady` props', () => {
vm = createComponent({});
const stylesObj = vm.getTodayBarStyles();
expect(stylesObj.height).toBe('calc(100vh - 16px)');
expect(stylesObj.left).toBe('50%');
});
});
});
describe('mounted', () => {
it('binds `epicsListScrolled` event listener via eventHub', () => {
spyOn(eventHub, '$on');
const vmX = createComponent({});
expect(eventHub.$on).toHaveBeenCalledWith('epicsListScrolled', jasmine.any(Function));
vmX.$destroy();
});
});
describe('beforeDestroy', () => {
it('unbinds `epicsListScrolled` event listener via eventHub', () => {
spyOn(eventHub, '$off');
const vmX = createComponent({});
vmX.$destroy();
expect(eventHub.$off).toHaveBeenCalledWith('epicsListScrolled', jasmine.any(Function));
});
});
describe('template', () => {
it('renders component container element with class `today-bar`', done => {
vm = createComponent({});
vm.$nextTick(() => {
expect(vm.$el.classList.contains('today-bar')).toBe(true);
done();
});
});
});
});
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment