Commit ef59084d authored by Kushal Pandya's avatar Kushal Pandya

Render today indicator in timeline cell

Renders today indicator in timeline cell instead of header cell
for better stack order control and avoid using scroll event
listener for toggling its visibility.
parent ea9ee18c
<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