Commit edaa8a5f authored by Filipa Lacerda's avatar Filipa Lacerda

Merge branch '9831-roadmap-vuex-epics-fetch' into 'master'

[Part 2] Fetch epics for Roadmap via Vuex

Closes #9831

See merge request gitlab-org/gitlab-ee!10419
parents 4229e13f 7a42f45d
<script>
import { mapState, mapActions } from 'vuex';
import _ from 'underscore';
import Flash from '~/flash';
import { s__ } from '~/locale';
import epicsListEmpty from './epics_list_empty.vue';
import roadmapShell from './roadmap_shell.vue';
......@@ -15,14 +14,6 @@ export default {
roadmapShell,
},
props: {
store: {
type: Object,
required: true,
},
service: {
type: Object,
required: true,
},
presetType: {
type: String,
required: true,
......@@ -42,19 +33,20 @@ export default {
},
data() {
return {
isLoading: false,
isEpicsListEmpty: false,
hasError: false,
handleResizeThrottled: {},
};
},
computed: {
epics() {
return this.store.getEpics();
},
timeframe() {
return this.store.getTimeframe();
},
...mapState([
'currentGroupId',
'epics',
'timeframe',
'extendedTimeframe',
'epicsFetchInProgress',
'epicsFetchForTimeframeInProgress',
'epicsFetchResultEmpty',
'epicsFetchFailure',
]),
timeframeStart() {
return this.timeframe[0];
},
......@@ -62,71 +54,32 @@ export default {
const last = this.timeframe.length - 1;
return this.timeframe[last];
},
currentGroupId() {
return this.store.getCurrentGroupId();
},
showRoadmap() {
return !this.hasError && !this.isLoading && !this.isEpicsListEmpty;
},
return !this.epicsFetchFailure && !this.epicsFetchInProgress && !this.epicsFetchResultEmpty;
},
mounted() {
this.fetchEpics();
this.handleResizeThrottled = _.throttle(this.handleResize, 600);
window.addEventListener('resize', this.handleResizeThrottled, false);
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResizeThrottled, false);
},
methods: {
fetchEpics() {
this.hasError = false;
this.service
.getEpics()
.then(res => res.data)
.then(epics => {
if (epics.length) {
this.store.setEpics(epics);
watch: {
epicsFetchInProgress(value) {
if (!value && this.epics.length) {
this.$nextTick(() => {
// Render timeline bars as we're already having timeline
// rendered before fetch
eventHub.$emit('refreshTimeline', {
todayBarReady: true,
initialRender: true,
});
});
} else {
this.isEpicsListEmpty = true;
}
})
.catch(() => {
this.isLoading = false;
this.hasError = true;
Flash(s__('GroupRoadmap|Something went wrong while fetching epics'));
});
},
fetchEpicsForTimeframe({ timeframe, roadmapTimelineEl, extendType }) {
this.hasError = false;
this.service
.getEpicsForTimeframe(this.presetType, timeframe)
.then(res => res.data)
.then(epics => {
if (epics.length) {
this.store.addEpics(epics);
}
this.$nextTick(() => {
// Re-render timeline bars with updated timeline
this.processExtendedTimeline({
itemsCount: timeframe ? timeframe.length : 0,
extendType,
roadmapTimelineEl,
});
});
})
.catch(() => {
this.hasError = true;
Flash(s__('GroupRoadmap|Something went wrong while fetching epics'));
});
},
mounted() {
this.fetchEpics();
this.handleResizeThrottled = _.throttle(this.handleResize, 600);
window.addEventListener('resize', this.handleResizeThrottled, false);
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResizeThrottled, false);
},
methods: {
...mapActions(['fetchEpics', 'fetchEpicsForTimeframe', 'extendTimeframe', 'refreshEpicDates']),
/**
* Roadmap view works with absolute sizing and positioning
* of following child components of RoadmapShell;
......@@ -175,21 +128,32 @@ export default {
}
},
handleScrollToExtend(roadmapTimelineEl, extendType = EXTEND_AS.PREPEND) {
const timeframe = this.store.extendTimeframe(extendType);
this.extendTimeframe({ extendAs: extendType });
this.refreshEpicDates();
this.$nextTick(() => {
this.fetchEpicsForTimeframe({
timeframe,
timeframe: this.extendedTimeframe,
})
.then(() => {
this.$nextTick(() => {
// Re-render timeline bars with updated timeline
this.processExtendedTimeline({
itemsCount: this.extendedTimeframe ? this.extendedTimeframe.length : 0,
extendType,
roadmapTimelineEl,
});
});
})
.catch(() => {});
});
},
},
};
</script>
<template>
<div :class="{ 'overflow-reset': isEpicsListEmpty }" class="roadmap-container">
<div :class="{ 'overflow-reset': epicsFetchResultEmpty }" class="roadmap-container">
<roadmap-shell
v-if="showRoadmap"
:preset-type="presetType"
......@@ -200,7 +164,7 @@ export default {
@onScrollToEnd="handleScrollToExtend"
/>
<epics-list-empty
v-if="isEpicsListEmpty"
v-if="epicsFetchResultEmpty"
:preset-type="presetType"
:timeframe-start="timeframeStart"
:timeframe-end="timeframeEnd"
......
......@@ -11,8 +11,6 @@ import { PRESET_TYPES, EPIC_DETAILS_CELL_WIDTH } from './constants';
import { getTimeframeForPreset, getEpicsPathForPreset } from './utils/roadmap_utils';
import createStore from './store';
import RoadmapStore from './store/roadmap_store';
import RoadmapService from './service/roadmap_service';
import roadmapApp from './components/app.vue';
......@@ -45,7 +43,6 @@ export default () => {
data() {
const supportedPresetTypes = Object.keys(PRESET_TYPES);
const { dataset } = this.$options.el;
const hasFiltersApplied = parseBoolean(dataset.hasFiltersApplied);
const presetType =
supportedPresetTypes.indexOf(dataset.presetType) > -1
? dataset.presetType
......@@ -63,32 +60,17 @@ export default () => {
timeframe,
});
const store = new RoadmapStore({
groupId: parseInt(dataset.groupId, 0),
sortedBy: dataset.sortedBy,
timeframe,
presetType,
});
const service = new RoadmapService({
initialEpicsPath,
filterQueryString,
basePath: dataset.epicsPath,
epicsState: dataset.epicsState,
});
return {
store,
service,
presetType,
hasFiltersApplied,
epicsState: dataset.epicsState,
newEpicEndpoint: dataset.newEpicEndpoint,
emptyStateIllustrationPath: dataset.emptyStateIllustration,
// Part of Vuex Store
hasFiltersApplied: parseBoolean(dataset.hasFiltersApplied),
currentGroupId: parseInt(dataset.groupId, 0),
newEpicEndpoint: dataset.newEpicEndpoint,
epicsState: dataset.epicsState,
basePath: dataset.epicsPath,
sortedBy: dataset.sortedBy,
filterQueryString,
initialEpicsPath,
presetType,
timeframe,
};
},
......@@ -98,6 +80,9 @@ export default () => {
sortedBy: this.sortedBy,
presetType: this.presetType,
timeframe: this.timeframe,
basePath: this.basePath,
filterQueryString: this.filterQueryString,
initialEpicsPath: this.initialEpicsPath,
});
},
methods: {
......@@ -107,7 +92,6 @@ export default () => {
return createElement('roadmap-app', {
props: {
store: this.store,
service: this.service,
presetType: this.presetType,
hasFiltersApplied: this.hasFiltersApplied,
epicsState: this.epicsState,
......
import axios from '~/lib/utils/axios_utils';
import { getEpicsPathForPreset } from '../utils/roadmap_utils';
export default class RoadmapService {
constructor({ basePath, epicsState, filterQueryString, initialEpicsPath }) {
this.basePath = basePath;
this.epicsState = epicsState;
this.filterQueryString = filterQueryString;
this.initialEpicsPath = initialEpicsPath;
}
getEpics() {
return axios.get(this.initialEpicsPath);
}
getEpicsForTimeframe(presetType, timeframe) {
const epicsPath = getEpicsPathForPreset({
basePath: this.basePath,
epicsState: this.epicsState,
filterQueryString: this.filterQueryString,
presetType,
timeframe,
});
return axios.get(epicsPath);
}
}
import flash from '~/flash';
import { s__ } from '~/locale';
import axios from '~/lib/utils/axios_utils';
import * as epicUtils from '../utils/epic_utils';
import { getEpicsPathForPreset, sortEpics, extendTimeframeForPreset } from '../utils/roadmap_utils';
import { EXTEND_AS } from '../constants';
import * as types from './mutation_types';
export const setInitialData = ({ commit }, data) => commit(types.SET_INITIAL_DATA, data);
export const requestEpics = ({ commit }) => commit(types.REQUEST_EPICS);
export const requestEpicsForTimeframe = ({ commit }) => commit(types.REQUEST_EPICS_FOR_TIMEFRAME);
export const receiveEpicsSuccess = (
{ commit, state, getters },
{ rawEpics, newEpic, timeframeExtended },
) => {
const epics = rawEpics.reduce((filteredEpics, epic) => {
const formattedEpic = epicUtils.formatEpicDetails(
epic,
getters.timeframeStartDate,
getters.timeframeEndDate,
);
// Exclude any Epic that has invalid dates
// or is already present in Roadmap timeline
if (
formattedEpic.startDate <= formattedEpic.endDate &&
state.epicIds.indexOf(formattedEpic.id) < 0
) {
Object.assign(formattedEpic, {
newEpic,
});
filteredEpics.push(formattedEpic);
commit(types.UPDATE_EPIC_IDS, formattedEpic.id);
}
return filteredEpics;
}, []);
if (timeframeExtended) {
const updatedEpics = state.epics.concat(epics);
sortEpics(updatedEpics, state.sortedBy);
commit(types.RECEIVE_EPICS_FOR_TIMEFRAME_SUCCESS, updatedEpics);
} else {
commit(types.RECEIVE_EPICS_SUCCESS, epics);
}
};
export const receiveEpicsFailure = ({ commit }) => {
commit(types.RECEIVE_EPICS_FAILURE);
flash(s__('GroupRoadmap|Something went wrong while fetching epics'));
};
export const fetchEpics = ({ state, dispatch }) => {
dispatch('requestEpics');
return axios
.get(state.initialEpicsPath)
.then(({ data }) => {
dispatch('receiveEpicsSuccess', { rawEpics: data });
})
.catch(() => {
dispatch('receiveEpicsFailure');
});
};
export const fetchEpicsForTimeframe = ({ state, dispatch }, { timeframe }) => {
dispatch('requestEpicsForTimeframe');
const epicsPath = getEpicsPathForPreset({
basePath: state.basePath,
epicsState: state.epicsState,
filterQueryString: state.filterQueryString,
presetType: state.presetType,
timeframe,
});
return axios
.get(epicsPath)
.then(({ data }) => {
dispatch('receiveEpicsSuccess', {
rawEpics: data,
newEpic: true,
timeframeExtended: true,
});
})
.catch(() => {
dispatch('receiveEpicsFailure');
});
};
export const extendTimeframe = ({ commit, state, getters }, { extendAs }) => {
const isExtendTypePrepend = extendAs === EXTEND_AS.PREPEND;
const timeframeToExtend = extendTimeframeForPreset({
extendAs,
presetType: state.presetType,
initialDate: isExtendTypePrepend ? getters.timeframeStartDate : getters.timeframeEndDate,
});
if (isExtendTypePrepend) {
commit(types.PREPEND_TIMEFRAME, timeframeToExtend);
} else {
commit(types.APPEND_TIMEFRAME, timeframeToExtend);
}
};
export const refreshEpicDates = ({ commit, state, getters }) => {
const epics = state.epics.map(epic =>
epicUtils.processEpicDates(epic, getters.timeframeStartDate, getters.timeframeEndDate),
);
commit(types.SET_EPICS, epics);
};
// prevent babel-plugin-rewire from generating an invalid default during karma tests
export default () => {};
export const SET_INITIAL_DATA = 'SET_INITIAL_DATA';
export const SET_EPICS = 'SET_EPICS';
export const UPDATE_EPIC_IDS = 'UPDATE_EPIC_IDS';
export const REQUEST_EPICS = 'REQUEST_EPICS';
export const REQUEST_EPICS_SUCCESS = 'REQUEST_EPICS_SUCCESS';
export const REQUEST_EPICS_FAILURE = 'REQUEST_EPICS_FAILURE';
export const REQUEST_EPICS_FOR_TIMEFRAME = 'REQUEST_EPICS_FOR_TIMEFRAME';
export const RECEIVE_EPICS_SUCCESS = 'RECEIVE_EPICS_SUCCESS';
export const RECEIVE_EPICS_FOR_TIMEFRAME_SUCCESS = 'RECEIVE_EPICS_FOR_TIMEFRAME_SUCCESS';
export const RECEIVE_EPICS_FAILURE = 'RECEIVE_EPICS_FAILURE';
export const PREPEND_TIMEFRAME = 'PREPEND_TIMEFRAME';
export const APPEND_TIMEFRAME = 'APPEND_TIMEFRAME';
......@@ -4,4 +4,46 @@ export default {
[types.SET_INITIAL_DATA](state, data) {
Object.assign(state, { ...data });
},
[types.SET_EPICS](state, epics) {
state.epics = epics;
},
[types.UPDATE_EPIC_IDS](state, epicId) {
state.epicIds.push(epicId);
},
[types.REQUEST_EPICS](state) {
state.epicsFetchInProgress = true;
},
[types.REQUEST_EPICS_FOR_TIMEFRAME](state) {
state.epicsFetchForTimeframeInProgress = true;
},
[types.RECEIVE_EPICS_SUCCESS](state, epics) {
state.epicsFetchResultEmpty = epics.length === 0;
if (!state.epicsFetchResultEmpty) {
state.epics = epics;
}
state.epicsFetchInProgress = false;
},
[types.RECEIVE_EPICS_FOR_TIMEFRAME_SUCCESS](state, epics) {
state.epics = epics;
state.epicsFetchForTimeframeInProgress = false;
},
[types.RECEIVE_EPICS_FAILURE](state) {
state.epicsFetchInProgress = false;
state.epicsFetchForTimeframeInProgress = false;
state.epicsFetchFailure = true;
},
[types.PREPEND_TIMEFRAME](state, extendedTimeframe) {
state.extendedTimeframe = extendedTimeframe;
state.timeframe.unshift(...extendedTimeframe);
},
[types.APPEND_TIMEFRAME](state, extendedTimeframe) {
state.extendedTimeframe = extendedTimeframe;
state.timeframe.push(...extendedTimeframe);
},
};
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { newDate, parsePikadayDate } from '~/lib/utils/datetime_utility';
import { extendTimeframeForPreset, sortEpics } from '../utils/roadmap_utils';
import { PRESET_TYPES, EXTEND_AS } from '../constants';
export default class RoadmapStore {
constructor({ groupId, timeframe, presetType, sortedBy }) {
this.state = {};
this.state.epics = [];
this.state.epicIds = [];
this.state.currentGroupId = groupId;
this.state.timeframe = timeframe;
this.presetType = presetType;
this.sortedBy = sortedBy;
this.initTimeframeThreshold();
}
initTimeframeThreshold() {
const [startFrame] = this.state.timeframe;
const lastTimeframeIndex = this.state.timeframe.length - 1;
if (this.presetType === PRESET_TYPES.QUARTERS) {
[this.timeframeStartDate] = startFrame.range;
// eslint-disable-next-line prefer-destructuring
this.timeframeEndDate = this.state.timeframe[lastTimeframeIndex].range[2];
} else if (this.presetType === PRESET_TYPES.MONTHS) {
this.timeframeStartDate = startFrame;
this.timeframeEndDate = this.state.timeframe[lastTimeframeIndex];
} else if (this.presetType === PRESET_TYPES.WEEKS) {
this.timeframeStartDate = startFrame;
this.timeframeEndDate = newDate(this.state.timeframe[lastTimeframeIndex]);
this.timeframeEndDate.setDate(this.timeframeEndDate.getDate() + 7);
}
}
setEpics(epics) {
this.state.epicIds = [];
this.state.epics = RoadmapStore.filterInvalidEpics({
timeframeStartDate: this.timeframeStartDate,
timeframeEndDate: this.timeframeEndDate,
state: this.state,
epics,
});
}
addEpics(epics) {
this.state.epics = this.state.epics.concat(
RoadmapStore.filterInvalidEpics({
timeframeStartDate: this.timeframeStartDate,
timeframeEndDate: this.timeframeEndDate,
state: this.state,
newEpic: true,
epics,
}),
);
sortEpics(this.state.epics, this.sortedBy);
}
getEpics() {
return this.state.epics;
}
getCurrentGroupId() {
return this.state.currentGroupId;
}
getTimeframe() {
return this.state.timeframe;
}
extendTimeframe(extendAs = EXTEND_AS.PREPEND) {
const timeframeToExtend = extendTimeframeForPreset({
presetType: this.presetType,
extendAs,
initialDate: extendAs === EXTEND_AS.PREPEND ? this.timeframeStartDate : this.timeframeEndDate,
});
if (extendAs === EXTEND_AS.PREPEND) {
this.state.timeframe.unshift(...timeframeToExtend);
} else {
this.state.timeframe.push(...timeframeToExtend);
}
this.initTimeframeThreshold();
this.state.epics.forEach(epic =>
RoadmapStore.processEpicDates(epic, this.timeframeStartDate, this.timeframeEndDate),
);
return timeframeToExtend;
}
static filterInvalidEpics({
epics,
timeframeStartDate,
timeframeEndDate,
state,
newEpic = false,
}) {
return epics.reduce((filteredEpics, epic) => {
const formattedEpic = RoadmapStore.formatEpicDetails(
epic,
timeframeStartDate,
timeframeEndDate,
);
// Exclude any Epic that has invalid dates
// or is already present in Roadmap timeline
if (
formattedEpic.startDate <= formattedEpic.endDate &&
state.epicIds.indexOf(formattedEpic.id) < 0
) {
Object.assign(formattedEpic, {
newEpic,
});
filteredEpics.push(formattedEpic);
state.epicIds.push(formattedEpic.id);
}
return filteredEpics;
}, []);
}
/**
* This method constructs Epic object and assigns proxy dates
* in case start or end dates are unavailable.
*
* @param {Object} rawEpic
* @param {Date} timeframeStartDate
* @param {Date} timeframeEndDate
*/
static formatEpicDetails(rawEpic, timeframeStartDate, timeframeEndDate) {
const epicItem = convertObjectPropsToCamelCase(rawEpic);
if (rawEpic.start_date) {
// If startDate is present
const startDate = parsePikadayDate(rawEpic.start_date);
epicItem.startDate = startDate;
epicItem.originalStartDate = startDate;
} else {
// startDate is not available
epicItem.startDateUndefined = true;
}
if (rawEpic.end_date) {
// If endDate is present
const endDate = parsePikadayDate(rawEpic.end_date);
epicItem.endDate = endDate;
epicItem.originalEndDate = endDate;
} else {
// endDate is not available
epicItem.endDateUndefined = true;
}
RoadmapStore.processEpicDates(epicItem, timeframeStartDate, timeframeEndDate);
return epicItem;
}
static processEpicDates(epic, timeframeStartDate, timeframeEndDate) {
if (!epic.startDateUndefined) {
// If startDate is less than first timeframe item
if (epic.originalStartDate.getTime() < timeframeStartDate.getTime()) {
Object.assign(epic, {
// startDate is out of range
startDateOutOfRange: true,
// Use startDate object to set a proxy date so
// that timeline bar can render it.
startDate: newDate(timeframeStartDate),
});
} else {
Object.assign(epic, {
// startDate is within range
startDateOutOfRange: false,
// Set startDate to original startDate
startDate: newDate(epic.originalStartDate),
});
}
} else {
Object.assign(epic, {
startDate: newDate(timeframeStartDate),
});
}
if (!epic.endDateUndefined) {
// If endDate is greater than last timeframe item
if (epic.originalEndDate.getTime() > timeframeEndDate.getTime()) {
Object.assign(epic, {
// endDate is out of range
endDateOutOfRange: true,
// Use endDate object to set a proxy date so
// that timeline bar can render it.
endDate: newDate(timeframeEndDate),
});
} else {
Object.assign(epic, {
// startDate is within range
endDateOutOfRange: false,
// Set startDate to original startDate
endDate: newDate(epic.originalEndDate),
});
}
} else {
Object.assign(epic, {
endDate: newDate(timeframeEndDate),
});
}
}
}
export default () => ({
// API Calls
basePath: '',
epicsState: '',
filterQueryString: '',
initialEpicsPath: '',
// Data
epics: [],
epicIds: [],
currentGroupId: -1,
timeframe: [],
extendedTimeframe: [],
presetType: '',
sortedBy: '',
// UI Flags
epicsFetchInProgress: false,
epicsFetchForTimeframeInProgress: false,
epicsFetchFailure: false,
epicsFetchResultEmpty: false,
});
import { convertObjectPropsToCamelCase } from '~/lib/utils/common_utils';
import { newDate, parsePikadayDate } from '~/lib/utils/datetime_utility';
/**
* Updates provided `epic` object with necessary props
* representing underlying dates.
*
* @param {Object} epic
* @param {Date} timeframeStartDate
* @param {Date} timeframeEndDate
*/
export const processEpicDates = (epic, timeframeStartDate, timeframeEndDate) => {
if (!epic.startDateUndefined) {
// If startDate is less than first timeframe item
if (epic.originalStartDate.getTime() < timeframeStartDate.getTime()) {
Object.assign(epic, {
// startDate is out of range
startDateOutOfRange: true,
// Use startDate object to set a proxy date so
// that timeline bar can render it.
startDate: newDate(timeframeStartDate),
});
} else {
Object.assign(epic, {
// startDate is within range
startDateOutOfRange: false,
// Set startDate to original startDate
startDate: newDate(epic.originalStartDate),
});
}
} else {
Object.assign(epic, {
startDate: newDate(timeframeStartDate),
});
}
if (!epic.endDateUndefined) {
// If endDate is greater than last timeframe item
if (epic.originalEndDate.getTime() > timeframeEndDate.getTime()) {
Object.assign(epic, {
// endDate is out of range
endDateOutOfRange: true,
// Use endDate object to set a proxy date so
// that timeline bar can render it.
endDate: newDate(timeframeEndDate),
});
} else {
Object.assign(epic, {
// startDate is within range
endDateOutOfRange: false,
// Set startDate to original startDate
endDate: newDate(epic.originalEndDate),
});
}
} else {
Object.assign(epic, {
endDate: newDate(timeframeEndDate),
});
}
return epic;
};
/**
* Constructs Epic object with camelCase props and assigns proxy dates in case
* start or end dates are unavailable.
*
* @param {Object} rawEpic
* @param {Date} timeframeStartDate
* @param {Date} timeframeEndDate
*/
export const formatEpicDetails = (rawEpic, timeframeStartDate, timeframeEndDate) => {
const epicItem = convertObjectPropsToCamelCase(rawEpic);
if (rawEpic.start_date) {
// If startDate is present
const startDate = parsePikadayDate(rawEpic.start_date);
epicItem.startDate = startDate;
epicItem.originalStartDate = startDate;
} else {
// startDate is not available
epicItem.startDateUndefined = true;
}
if (rawEpic.end_date) {
// If endDate is present
const endDate = parsePikadayDate(rawEpic.end_date);
epicItem.endDate = endDate;
epicItem.originalEndDate = endDate;
} else {
// endDate is not available
epicItem.endDateUndefined = true;
}
processEpicDates(epicItem, timeframeStartDate, timeframeEndDate);
return epicItem;
};
import Vue from 'vue';
import epicsListSectionComponent from 'ee/roadmap/components/epics_list_section.vue';
import RoadmapStore from 'ee/roadmap/store/roadmap_store';
import createStore from 'ee/roadmap/store';
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 {
rawEpics,
mockShellWidth,
mockTimeframeInitialDate,
mockGroupId,
mockShellWidth,
rawEpics,
mockSortedBy,
basePath,
epicsPath,
} from '../mock_data';
const mockTimeframeMonths = getTimeframeForMonthsView(mockTimeframeInitialDate);
const store = new RoadmapStore({
groupId: mockGroupId,
presetType: PRESET_TYPES.MONTHS,
const store = createStore();
store.dispatch('setInitialData', {
currentGroupId: mockGroupId,
sortedBy: mockSortedBy,
presetType: PRESET_TYPES.MONTHS,
timeframe: mockTimeframeMonths,
filterQueryString: '',
initialEpicsPath: epicsPath,
basePath,
});
store.setEpics(rawEpics);
const mockEpics = store.getEpics();
store.dispatch('receiveEpicsSuccess', { rawEpics });
const mockEpics = store.state.epics;
const createComponent = ({
epics = mockEpics,
......
......@@ -54,6 +54,8 @@ export const mockTimeframeQuartersAppend = [
];
export const mockTimeframeMonthsPrepend = [
new Date(2017, 2, 1),
new Date(2017, 3, 1),
new Date(2017, 4, 1),
new Date(2017, 5, 1),
new Date(2017, 6, 1),
......@@ -66,7 +68,8 @@ export const mockTimeframeMonthsAppend = [
new Date(2018, 7, 1),
new Date(2018, 8, 1),
new Date(2018, 9, 1),
new Date(2018, 10, 30),
new Date(2018, 10, 1),
new Date(2018, 11, 31),
];
export const mockTimeframeWeeksPrepend = [
......@@ -101,6 +104,37 @@ export const mockEpic = {
webUrl: '/groups/gitlab-org/-/epics/1',
};
export const mockRawEpic = {
id: 41,
iid: 2,
description: null,
title: 'Another marketing',
group_id: 56,
group_name: 'Marketing',
group_full_name: 'Gitlab Org / Marketing',
start_date: '2017-6-26',
end_date: '2018-03-10',
web_url: '/groups/gitlab-org/marketing/-/epics/2',
};
export const mockFormattedEpic = {
id: 41,
iid: 2,
description: null,
title: 'Another marketing',
groupId: 56,
groupName: 'Marketing',
groupFullName: 'Gitlab Org / Marketing',
startDate: new Date(2017, 10, 1),
originalStartDate: new Date(2017, 5, 26),
endDate: new Date(2018, 2, 10),
originalEndDate: new Date(2018, 2, 10),
startDateOutOfRange: true,
endDateOutOfRange: false,
webUrl: '/groups/gitlab-org/marketing/-/epics/2',
newEpic: undefined,
};
export const rawEpics = [
{
id: 41,
......
import axios from '~/lib/utils/axios_utils';
import RoadmapService from 'ee/roadmap/service/roadmap_service';
import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils';
import { PRESET_TYPES } from 'ee/roadmap/constants';
import { basePath, epicsPath, mockTimeframeInitialDate } from '../mock_data';
const mockTimeframeMonths = getTimeframeForMonthsView(mockTimeframeInitialDate);
describe('RoadmapService', () => {
let service;
beforeEach(() => {
service = new RoadmapService({
initialEpicsPath: epicsPath,
epicsState: 'all',
filterQueryString: '',
basePath,
});
});
describe('getEpics', () => {
it('returns axios instance for Epics path', () => {
spyOn(axios, 'get').and.stub();
service.getEpics();
expect(axios.get).toHaveBeenCalledWith(
'/groups/gitlab-org/-/epics.json?start_date=2017-11-1&end_date=2018-4-30',
);
});
});
describe('getEpicsForTimeframe', () => {
it('calls `getEpicsPathForPreset` to construct epics path', () => {
const getEpicsPathSpy = spyOnDependency(RoadmapService, 'getEpicsPathForPreset');
spyOn(axios, 'get').and.stub();
const presetType = PRESET_TYPES.MONTHS;
service.getEpicsForTimeframe(presetType, mockTimeframeMonths);
expect(getEpicsPathSpy).toHaveBeenCalledWith(
jasmine.objectContaining({
timeframe: mockTimeframeMonths,
epicsState: 'all',
filterQueryString: '',
basePath,
presetType,
}),
);
});
});
});
import MockAdapter from 'axios-mock-adapter';
import * as actions from 'ee/roadmap/store/actions';
import * as types from 'ee/roadmap/store/mutation_types';
import defaultState from 'ee/roadmap/store/state';
import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils';
import { formatEpicDetails } from 'ee/roadmap/utils/epic_utils';
import { PRESET_TYPES, EXTEND_AS } from 'ee/roadmap/constants';
import axios from '~/lib/utils/axios_utils';
import testAction from 'spec/helpers/vuex_action_helper';
import {
mockGroupId,
basePath,
epicsPath,
mockTimeframeInitialDate,
mockTimeframeMonthsPrepend,
mockTimeframeMonthsAppend,
rawEpics,
mockRawEpic,
mockFormattedEpic,
mockSortedBy,
} from '../mock_data';
const mockTimeframeMonths = getTimeframeForMonthsView(mockTimeframeInitialDate);
describe('Roadmap Vuex Actions', () => {
const timeframeStartDate = mockTimeframeMonths[0];
const timeframeEndDate = mockTimeframeMonths[mockTimeframeMonths.length - 1];
let state;
beforeEach(() => {
state = Object.assign({}, defaultState(), {
groupId: mockGroupId,
timeframe: mockTimeframeMonths,
presetType: PRESET_TYPES.MONTHS,
sortedBy: mockSortedBy,
initialEpicsPath: epicsPath,
filterQueryString: '',
basePath,
timeframeStartDate,
timeframeEndDate,
});
});
describe('setInitialData', () => {
it('Should set initial roadmap props', done => {
const mockRoadmap = {
......@@ -14,7 +56,260 @@ describe('Roadmap Vuex Actions', () => {
actions.setInitialData,
mockRoadmap,
{},
[{ type: 'SET_INITIAL_DATA', payload: mockRoadmap }],
[{ type: types.SET_INITIAL_DATA, payload: mockRoadmap }],
[],
done,
);
});
});
describe('requestEpics', () => {
it('Should set `epicsFetchInProgress` to true', done => {
testAction(actions.requestEpics, {}, state, [{ type: 'REQUEST_EPICS' }], [], done);
});
});
describe('requestEpicsForTimeframe', () => {
it('Should set `epicsFetchForTimeframeInProgress` to true', done => {
testAction(
actions.requestEpicsForTimeframe,
{},
state,
[{ type: types.REQUEST_EPICS_FOR_TIMEFRAME }],
[],
done,
);
});
});
describe('receiveEpicsSuccess', () => {
it('Should set formatted epics array and epicId to IDs array in state based on provided epics list', done => {
testAction(
actions.receiveEpicsSuccess,
{
rawEpics: [
Object.assign({}, mockRawEpic, {
start_date: '2017-12-31',
end_date: '2018-2-15',
}),
],
},
state,
[
{ type: types.UPDATE_EPIC_IDS, payload: mockRawEpic.id },
{
type: types.RECEIVE_EPICS_SUCCESS,
payload: [
Object.assign({}, mockFormattedEpic, {
startDateOutOfRange: false,
endDateOutOfRange: false,
startDate: new Date(2017, 11, 31),
originalStartDate: new Date(2017, 11, 31),
endDate: new Date(2018, 1, 15),
originalEndDate: new Date(2018, 1, 15),
}),
],
},
],
[],
done,
);
});
it('Should set formatted epics array and epicId to IDs array in state based on provided epics list when timeframe was extended', done => {
testAction(
actions.receiveEpicsSuccess,
{ rawEpics: [mockRawEpic], newEpic: true, timeframeExtended: true },
state,
[
{ type: types.UPDATE_EPIC_IDS, payload: mockRawEpic.id },
{
type: types.RECEIVE_EPICS_FOR_TIMEFRAME_SUCCESS,
payload: [Object.assign({}, mockFormattedEpic, { newEpic: true })],
},
],
[],
done,
);
});
});
describe('receiveEpicsFailure', () => {
beforeEach(() => {
setFixtures('<div class="flash-container"></div>');
});
it('Should set epicsFetchInProgress, epicsFetchForTimeframeInProgress to false and epicsFetchFailure to true', done => {
testAction(
actions.receiveEpicsFailure,
{},
state,
[{ type: types.RECEIVE_EPICS_FAILURE }],
[],
done,
);
});
it('Should show flash error', () => {
actions.receiveEpicsFailure({ commit: () => {} });
expect(document.querySelector('.flash-container .flash-text').innerText.trim()).toBe(
'Something went wrong while fetching epics',
);
});
});
describe('fetchEpics', () => {
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('success', () => {
it('Should dispatch requestEpics and receiveEpicsSuccess when request is successful', done => {
mock.onGet(epicsPath).replyOnce(200, rawEpics);
testAction(
actions.fetchEpics,
null,
state,
[],
[
{
type: 'requestEpics',
},
{
type: 'receiveEpicsSuccess',
payload: { rawEpics },
},
],
done,
);
});
});
describe('failure', () => {
it('Should dispatch requestEpics and receiveEpicsFailure when request fails', done => {
mock.onGet(epicsPath).replyOnce(500, {});
testAction(
actions.fetchEpics,
null,
state,
[],
[
{
type: 'requestEpics',
},
{
type: 'receiveEpicsFailure',
},
],
done,
);
});
});
});
describe('fetchEpicsForTimeframe', () => {
const mockEpicsPath =
'/groups/gitlab-org/-/epics.json?state=&start_date=2017-11-1&end_date=2018-6-30';
let mock;
beforeEach(() => {
mock = new MockAdapter(axios);
});
afterEach(() => {
mock.restore();
});
describe('success', () => {
it('Should dispatch requestEpicsForTimeframe and receiveEpicsSuccess when request is successful', done => {
mock.onGet(mockEpicsPath).replyOnce(200, rawEpics);
testAction(
actions.fetchEpicsForTimeframe,
{ timeframe: mockTimeframeMonths },
state,
[],
[
{
type: 'requestEpicsForTimeframe',
},
{
type: 'receiveEpicsSuccess',
payload: { rawEpics, newEpic: true, timeframeExtended: true },
},
],
done,
);
});
});
describe('failure', () => {
it('Should dispatch requestEpicsForTimeframe and requestEpicsFailure when request fails', done => {
mock.onGet(mockEpicsPath).replyOnce(500, {});
testAction(
actions.fetchEpicsForTimeframe,
{ timeframe: mockTimeframeMonths },
state,
[],
[
{
type: 'requestEpicsForTimeframe',
},
{
type: 'receiveEpicsFailure',
},
],
done,
);
});
});
});
describe('extendTimeframe', () => {
it('Should prepend to timeframe when called with extend type prepend', done => {
testAction(
actions.extendTimeframe,
{ extendAs: EXTEND_AS.PREPEND },
state,
[{ type: types.PREPEND_TIMEFRAME, payload: mockTimeframeMonthsPrepend }],
[],
done,
);
});
it('Should append to timeframe when called with extend type append', done => {
testAction(
actions.extendTimeframe,
{ extendAs: EXTEND_AS.APPEND },
state,
[{ type: types.APPEND_TIMEFRAME, payload: mockTimeframeMonthsAppend }],
[],
done,
);
});
});
describe('refreshEpicDates', () => {
it('Should update epics after refreshing epic dates to match with updated timeframe', done => {
const epics = rawEpics.map(epic =>
formatEpicDetails(epic, state.timeframeStartDate, state.timeframeEndDate),
);
testAction(
actions.refreshEpicDates,
{},
{ ...state, timeframe: mockTimeframeMonths.concat(mockTimeframeMonthsAppend), epics },
[{ type: types.SET_EPICS, payload: epics }],
[],
done,
);
......
import mutations from 'ee/roadmap/store/mutations';
import * as types from 'ee/roadmap/store/mutation_types';
import defaultState from 'ee/roadmap/store/state';
import { mockGroupId, basePath, epicsPath, mockSortedBy } from '../mock_data';
describe('Roadmap Store Mutations', () => {
let state;
beforeEach(() => {
state = defaultState();
});
describe('SET_INITIAL_DATA', () => {
it('Should set initial Roadmap data to state', () => {
const state = {};
const mockData = {
foo: 'bar',
bar: 'baz',
const initialData = {
epicsFetchInProgress: false,
epicsFetchForTimeframeInProgress: false,
epicsFetchFailure: false,
epicsFetchResultEmpty: false,
currentGroupId: mockGroupId,
sortedBy: mockSortedBy,
initialEpicsPath: epicsPath,
extendedTimeframe: [],
filterQueryString: '',
epicsState: 'all',
epicIds: [],
epics: [],
basePath,
};
mutations[types.SET_INITIAL_DATA](state, mockData);
mutations[types.SET_INITIAL_DATA](state, initialData);
expect(state).toEqual(jasmine.objectContaining(initialData));
});
});
describe('SET_EPICS', () => {
it('Should provided epics array in state', () => {
const epics = [{ id: 1 }, { id: 2 }];
mutations[types.SET_EPICS](state, epics);
expect(state.epics).toEqual(epics);
});
});
describe('UPDATE_EPIC_IDS', () => {
it('Should insert provided epicId to epicIds array in state', () => {
mutations[types.UPDATE_EPIC_IDS](state, 22);
expect(state.epicIds.length).toBe(1);
expect(state.epicIds[0]).toBe(22);
});
});
describe('REQUEST_EPICS', () => {
it('Should set state.epicsFetchInProgress to `true`', () => {
mutations[types.REQUEST_EPICS](state);
expect(state.epicsFetchInProgress).toBe(true);
});
});
describe('REQUEST_EPICS_FOR_TIMEFRAME', () => {
it('Should set state.epicsFetchForTimeframeInProgress to `true`', () => {
mutations[types.REQUEST_EPICS_FOR_TIMEFRAME](state);
expect(state.epicsFetchForTimeframeInProgress).toBe(true);
});
});
describe('RECEIVE_EPICS_SUCCESS', () => {
it('Should set epicsFetchResultEmpty, epics in state based on provided epics array and set epicsFetchInProgress to `false`', () => {
const epics = [{ id: 1 }, { id: 2 }];
mutations[types.RECEIVE_EPICS_SUCCESS](state, epics);
expect(state.epicsFetchResultEmpty).toBe(false);
expect(state.epics).toEqual(epics);
expect(state.epicsFetchInProgress).toBe(false);
});
});
describe('RECEIVE_EPICS_FOR_TIMEFRAME_SUCCESS', () => {
it('Should set epics in state based on provided epics array and set epicsFetchForTimeframeInProgress to `false`', () => {
const epics = [{ id: 1 }, { id: 2 }];
mutations[types.RECEIVE_EPICS_FOR_TIMEFRAME_SUCCESS](state, epics);
expect(state.epics).toEqual(epics);
expect(state.epicsFetchForTimeframeInProgress).toBe(false);
});
});
describe('RECEIVE_EPICS_FAILURE', () => {
it('Should set epicsFetchInProgress & epicsFetchForTimeframeInProgress to false and epicsFetchFailure to true', () => {
mutations[types.RECEIVE_EPICS_FAILURE](state);
expect(state.epicsFetchInProgress).toBe(false);
expect(state.epicsFetchForTimeframeInProgress).toBe(false);
expect(state.epicsFetchFailure).toBe(true);
});
});
describe('PREPEND_TIMEFRAME', () => {
it('Should set extendedTimeframe to provided extendedTimeframe param and prepend it to timeframe array in state', () => {
state.timeframe.push('foo');
const extendedTimeframe = ['bar'];
mutations[types.PREPEND_TIMEFRAME](state, extendedTimeframe);
expect(state.extendedTimeframe).toBe(extendedTimeframe);
expect(state.timeframe[0]).toBe(extendedTimeframe[0]);
});
});
describe('APPEND_TIMEFRAME', () => {
it('Should set extendedTimeframe to provided extendedTimeframe param and append it to timeframe array in state', () => {
state.timeframe.push('foo');
const extendedTimeframe = ['bar'];
mutations[types.APPEND_TIMEFRAME](state, extendedTimeframe);
expect(state).toEqual(mockData);
expect(state.extendedTimeframe).toBe(extendedTimeframe);
expect(state.timeframe[1]).toBe(extendedTimeframe[0]);
});
});
});
import RoadmapStore from 'ee/roadmap/store/roadmap_store';
import { getTimeframeForMonthsView } from 'ee/roadmap/utils/roadmap_utils';
import { PRESET_TYPES, EXTEND_AS } from 'ee/roadmap/constants';
import { mockGroupId, mockTimeframeInitialDate, rawEpics, mockSortedBy } from '../mock_data';
const mockTimeframeMonths = getTimeframeForMonthsView(mockTimeframeInitialDate);
describe('RoadmapStore', () => {
let store;
beforeEach(() => {
store = new RoadmapStore({
groupId: mockGroupId,
timeframe: mockTimeframeMonths,
presetType: PRESET_TYPES.MONTHS,
sortedBy: mockSortedBy,
});
});
describe('constructor', () => {
it('initializes default state', () => {
expect(store.state).toBeDefined();
expect(Array.isArray(store.state.epics)).toBe(true);
expect(Array.isArray(store.state.epicIds)).toBe(true);
expect(store.state.currentGroupId).toBe(mockGroupId);
expect(store.state.timeframe).toBe(mockTimeframeMonths);
expect(store.presetType).toBe(PRESET_TYPES.MONTHS);
expect(store.sortedBy).toBe(mockSortedBy);
expect(store.timeframeStartDate).toBeDefined();
expect(store.timeframeEndDate).toBeDefined();
});
});
describe('setEpics', () => {
it('sets Epics list to state while filtering out Epics with invalid dates', () => {
spyOn(RoadmapStore, 'filterInvalidEpics').and.callThrough();
store.setEpics(rawEpics);
expect(RoadmapStore.filterInvalidEpics).toHaveBeenCalledWith(
jasmine.objectContaining({
timeframeStartDate: store.timeframeStartDate,
timeframeEndDate: store.timeframeEndDate,
state: store.state,
epics: rawEpics,
}),
);
expect(store.getEpics().length).toBe(rawEpics.length - 1); // There is only 1 invalid epic
});
});
describe('addEpics', () => {
beforeEach(() => {
store.setEpics(rawEpics);
});
it('adds new Epics to the epics list within state while filtering out existing epics or epics with invalid dates and sorts the list based on `sortedBy` value', () => {
spyOn(RoadmapStore, 'filterInvalidEpics');
const sortEpicsSpy = spyOnDependency(RoadmapStore, 'sortEpics').and.stub();
const newEpic = Object.assign({}, rawEpics[0], {
id: 999,
});
store.addEpics([newEpic]);
expect(RoadmapStore.filterInvalidEpics).toHaveBeenCalledWith(
jasmine.objectContaining({
timeframeStartDate: store.timeframeStartDate,
timeframeEndDate: store.timeframeEndDate,
state: store.state,
epics: [newEpic],
newEpic: true,
}),
);
expect(sortEpicsSpy).toHaveBeenCalledWith(jasmine.any(Object), mockSortedBy);
// rawEpics contain 2 invalid epics but now that we added 1
// new epic, length will be `rawEpics.length - 1 (invalid) + 1 (new epic)`
expect(store.getEpics().length).toBe(rawEpics.length);
});
});
describe('getCurrentGroupId', () => {
it('gets currentGroupId from store state', () => {
expect(store.getCurrentGroupId()).toBe(mockGroupId);
});
});
describe('getTimeframe', () => {
it('gets timeframe from store state', () => {
expect(store.getTimeframe()).toBe(mockTimeframeMonths);
});
});
describe('extendTimeframe', () => {
beforeEach(() => {
store.setEpics(rawEpics);
store.state.timeframe = [];
});
it('calls `extendTimeframeForPreset` and prepends items to the timeframe when called with `extendAs` param as `prepend`', () => {
const extendTimeframeSpy = spyOnDependency(
RoadmapStore,
'extendTimeframeForPreset',
).and.returnValue([]);
spyOn(store.state.timeframe, 'unshift');
spyOn(store, 'initTimeframeThreshold');
spyOn(RoadmapStore, 'processEpicDates');
const itemCount = store.extendTimeframe(EXTEND_AS.PREPEND).length;
expect(extendTimeframeSpy).toHaveBeenCalledWith(jasmine.any(Object));
expect(store.state.timeframe.unshift).toHaveBeenCalled();
expect(store.initTimeframeThreshold).toHaveBeenCalled();
expect(RoadmapStore.processEpicDates).toHaveBeenCalled();
expect(itemCount).toBe(0);
});
it('calls `extendTimeframeForPreset` and appends items to the timeframe when called with `extendAs` param as `append`', () => {
const extendTimeframeSpy = spyOnDependency(
RoadmapStore,
'extendTimeframeForPreset',
).and.returnValue([]);
spyOn(store.state.timeframe, 'push');
spyOn(store, 'initTimeframeThreshold');
spyOn(RoadmapStore, 'processEpicDates');
const itemCount = store.extendTimeframe(EXTEND_AS.APPEND).length;
expect(extendTimeframeSpy).toHaveBeenCalledWith(jasmine.any(Object));
expect(store.state.timeframe.push).toHaveBeenCalled();
expect(store.initTimeframeThreshold).toHaveBeenCalled();
expect(RoadmapStore.processEpicDates).toHaveBeenCalled();
expect(itemCount).toBe(0);
});
});
describe('filterInvalidEpics', () => {
it('returns formatted epics list by filtering out epics with invalid dates', () => {
spyOn(RoadmapStore, 'formatEpicDetails').and.callThrough();
const epicsList = RoadmapStore.filterInvalidEpics({
epics: rawEpics,
timeframeStartDate: store.timeframeStartDate,
timeframeEndDate: store.timeframeEndDate,
state: store.state,
});
expect(RoadmapStore.formatEpicDetails).toHaveBeenCalled();
expect(epicsList.length).toBe(rawEpics.length - 1); // There are is only 1 invalid epic
});
it('returns formatted epics list by filtering out existing epics', () => {
store.setEpics(rawEpics);
spyOn(RoadmapStore, 'formatEpicDetails').and.callThrough();
const newEpic = Object.assign({}, rawEpics[0]);
const epicsList = RoadmapStore.filterInvalidEpics({
epics: [newEpic],
timeframeStartDate: store.timeframeStartDate,
timeframeEndDate: store.timeframeEndDate,
state: store.state,
});
expect(RoadmapStore.formatEpicDetails).toHaveBeenCalled();
expect(epicsList.length).toBe(0); // No epics are eligible to be added
});
});
describe('formatEpicDetails', () => {
const rawEpic = rawEpics[0];
it('returns formatted Epic object from raw Epic object', () => {
spyOn(RoadmapStore, 'processEpicDates');
const epic = RoadmapStore.formatEpicDetails(
rawEpic,
store.timeframeStartDate,
store.timeframeEndDate,
);
expect(RoadmapStore.processEpicDates).toHaveBeenCalled();
expect(epic.id).toBe(rawEpic.id);
expect(epic.name).toBe(rawEpic.name);
expect(epic.groupId).toBe(rawEpic.group_id);
expect(epic.groupName).toBe(rawEpic.group_name);
});
it('returns formatted Epic object with startDateUndefined and proxy date set when start date is not available', () => {
const rawEpicWithoutSD = Object.assign({}, rawEpic, {
start_date: null,
});
const epic = RoadmapStore.formatEpicDetails(
rawEpicWithoutSD,
store.timeframeStartDate,
store.timeframeEndDate,
);
expect(epic.id).toBe(rawEpic.id);
expect(epic.startDateUndefined).toBe(true);
expect(epic.startDate.getTime()).toBe(store.timeframeStartDate.getTime());
});
it('returns formatted Epic object with endDateUndefined and proxy date set when end date is not available', () => {
const rawEpicWithoutED = Object.assign({}, rawEpic, {
end_date: null,
});
const epic = RoadmapStore.formatEpicDetails(
rawEpicWithoutED,
store.timeframeStartDate,
store.timeframeEndDate,
);
expect(epic.id).toBe(rawEpic.id);
expect(epic.endDateUndefined).toBe(true);
expect(epic.endDate.getTime()).toBe(store.timeframeEndDate.getTime());
});
it('returns formatted Epic object with startDateOutOfRange, proxy date and cached original start date set when start date is out of timeframe range', () => {
const rawStartDate = '2017-1-1';
const rawEpicSDOut = Object.assign({}, rawEpic, {
start_date: rawStartDate,
});
const epic = RoadmapStore.formatEpicDetails(
rawEpicSDOut,
store.timeframeStartDate,
store.timeframeEndDate,
);
expect(epic.id).toBe(rawEpic.id);
expect(epic.startDateOutOfRange).toBe(true);
expect(epic.startDate.getTime()).toBe(store.timeframeStartDate.getTime());
expect(epic.originalStartDate.getTime()).toBe(new Date(rawStartDate).getTime());
});
it('returns formatted Epic object with endDateOutOfRange, proxy date and cached original end date set when end date is out of timeframe range', () => {
const rawEndDate = '2019-1-1';
const rawEpicEDOut = Object.assign({}, rawEpic, {
end_date: rawEndDate,
});
const epic = RoadmapStore.formatEpicDetails(
rawEpicEDOut,
store.timeframeStartDate,
store.timeframeEndDate,
);
expect(epic.id).toBe(rawEpic.id);
expect(epic.endDateOutOfRange).toBe(true);
expect(epic.endDate.getTime()).toBe(store.timeframeEndDate.getTime());
expect(epic.originalEndDate.getTime()).toBe(new Date(rawEndDate).getTime());
});
});
});
import * as epicUtils from 'ee/roadmap/utils/epic_utils';
import { parsePikadayDate } from '~/lib/utils/datetime_utility';
import { rawEpics } from '../mock_data';
describe('processEpicDates', () => {
const timeframeStartDate = new Date(2017, 0, 1);
const timeframeEndDate = new Date(2017, 11, 31);
it('Should set `startDateOutOfRange`/`endDateOutOfRange` as true and `startDate` and `endDate` to dates of timeframe range when epic dates are outside timeframe range', () => {
const mockEpic = {
originalStartDate: new Date(2016, 11, 15),
originalEndDate: new Date(2018, 0, 1),
};
const updatedEpic = epicUtils.processEpicDates(mockEpic, timeframeStartDate, timeframeEndDate);
expect(updatedEpic.startDateOutOfRange).toBe(true);
expect(updatedEpic.endDateOutOfRange).toBe(true);
expect(updatedEpic.startDate.getTime()).toBe(timeframeStartDate.getTime());
expect(updatedEpic.endDate.getTime()).toBe(timeframeEndDate.getTime());
});
it('Should set `startDateOutOfRange`/`endDateOutOfRange` as false and `startDate` and `endDate` to actual epic dates when they are within timeframe range', () => {
const mockEpic = {
originalStartDate: new Date(2017, 2, 10),
originalEndDate: new Date(2017, 6, 22),
};
const updatedEpic = epicUtils.processEpicDates(mockEpic, timeframeStartDate, timeframeEndDate);
expect(updatedEpic.startDateOutOfRange).toBe(false);
expect(updatedEpic.endDateOutOfRange).toBe(false);
expect(updatedEpic.startDate.getTime()).toBe(mockEpic.originalStartDate.getTime());
expect(updatedEpic.endDate.getTime()).toBe(mockEpic.originalEndDate.getTime());
});
it('Should set `startDate` and `endDate` to timeframe start and end dates when epic dates are undefined', () => {
const mockEpic = {
startDateUndefined: true,
endDateUndefined: true,
};
const updatedEpic = epicUtils.processEpicDates(mockEpic, timeframeStartDate, timeframeEndDate);
expect(updatedEpic.startDate.getTime()).toBe(timeframeStartDate.getTime());
expect(updatedEpic.endDate.getTime()).toBe(timeframeEndDate.getTime());
});
});
describe('formatEpicDetails', () => {
const timeframeStartDate = new Date(2017, 0, 1);
const timeframeEndDate = new Date(2017, 11, 31);
const rawEpic = rawEpics[0];
it('Should return formatted Epic object from raw Epic object', () => {
const epic = epicUtils.formatEpicDetails(rawEpic, timeframeStartDate, timeframeEndDate);
expect(epic.id).toBe(rawEpic.id);
expect(epic.name).toBe(rawEpic.name);
expect(epic.groupId).toBe(rawEpic.group_id);
expect(epic.groupName).toBe(rawEpic.group_name);
});
it('Should return formatted Epic object with `startDate`/`endDate` and `originalStartDate`/originalEndDate` initialized when dates are present', () => {
const mockRawEpic = {
start_date: '2017-2-15',
end_date: '2017-7-22',
};
const epic = epicUtils.formatEpicDetails(mockRawEpic, timeframeStartDate, timeframeEndDate);
const startDate = parsePikadayDate(mockRawEpic.start_date);
const endDate = parsePikadayDate(mockRawEpic.end_date);
expect(epic.startDate.getTime()).toBe(startDate.getTime());
expect(epic.originalStartDate.getTime()).toBe(startDate.getTime());
expect(epic.endDate.getTime()).toBe(endDate.getTime());
expect(epic.originalEndDate.getTime()).toBe(endDate.getTime());
});
it('Should return formatted Epic object with `startDateUndefined`/startDateUndefined` set to true when dates are null/undefined', () => {
const epic = epicUtils.formatEpicDetails({}, timeframeStartDate, timeframeEndDate);
expect(epic.originalStartDate).toBeUndefined();
expect(epic.originalEndDate).toBeUndefined();
expect(epic.startDateUndefined).toBe(true);
expect(epic.endDateUndefined).toBe(true);
});
});
......@@ -161,7 +161,7 @@ describe('getTimeframeForMonthsView', () => {
describe('extendTimeframeForMonthsView', () => {
it('returns extended timeframe into the past from current timeframe startDate', () => {
const initialDate = mockTimeframeMonths[0];
const extendedTimeframe = extendTimeframeForMonthsView(initialDate, -6);
const extendedTimeframe = extendTimeframeForMonthsView(initialDate, -8);
expect(extendedTimeframe.length).toBe(mockTimeframeMonthsPrepend.length);
extendedTimeframe.forEach((timeframeItem, index) => {
......@@ -171,7 +171,7 @@ describe('extendTimeframeForMonthsView', () => {
it('returns extended timeframe into the future from current timeframe endDate', () => {
const initialDate = mockTimeframeMonths[mockTimeframeMonths.length - 1];
const extendedTimeframe = extendTimeframeForMonthsView(initialDate, 7);
const extendedTimeframe = extendTimeframeForMonthsView(initialDate, 8);
expect(extendedTimeframe.length).toBe(mockTimeframeMonthsAppend.length);
extendedTimeframe.forEach((timeframeItem, index) => {
......
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