Commit 847e6a65 authored by Natalia Tepluhina's avatar Natalia Tepluhina

Merge branch '262860-add-rotation-to-schedule' into 'master'

Add grid for rotations display + current day indicator

See merge request gitlab-org/gitlab!49248
parents 3379e08a f908ee29
...@@ -30,8 +30,9 @@ ...@@ -30,8 +30,9 @@
} }
//// Copied from roadmaps.scss - adapted for on-call schedules //// Copied from roadmaps.scss - adapted for on-call schedules
$header-item-height: 60px; $header-item-height: 72px;
$details-cell-width: px-to-rem(150px); $item-height: 40px;
$details-cell-width: 150px;
$timeline-cell-height: 32px; $timeline-cell-height: 32px;
$timeline-cell-width: 180px; $timeline-cell-width: 180px;
$border-style: 1px solid var(--gray-100, $gray-100); $border-style: 1px solid var(--gray-100, $gray-100);
...@@ -40,7 +41,6 @@ $gradient-gray: rgba(255, 255, 255, 0.001); ...@@ -40,7 +41,6 @@ $gradient-gray: rgba(255, 255, 255, 0.001);
$scroll-top-gradient: linear-gradient(to bottom, $gradient-dark-gray 0%, $gradient-gray 100%); $scroll-top-gradient: linear-gradient(to bottom, $gradient-dark-gray 0%, $gradient-gray 100%);
$scroll-bottom-gradient: linear-gradient(to bottom, $gradient-gray 0%, $gradient-dark-gray 100%); $scroll-bottom-gradient: linear-gradient(to bottom, $gradient-gray 0%, $gradient-dark-gray 100%);
$column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradient-gray 100%); $column-right-gradient: linear-gradient(to right, $gradient-dark-gray 0%, $gradient-gray 100%);
$epic-details-cell-width: 150px;
.schedule-shell { .schedule-shell {
@include gl-relative; @include gl-relative;
...@@ -55,7 +55,6 @@ $epic-details-cell-width: 150px; ...@@ -55,7 +55,6 @@ $epic-details-cell-width: 150px;
.timeline-section { .timeline-section {
@include gl-sticky; @include gl-sticky;
position: -webkit-sticky;
@include gl-top-0; @include gl-top-0;
z-index: 20; z-index: 20;
...@@ -69,27 +68,15 @@ $epic-details-cell-width: 150px; ...@@ -69,27 +68,15 @@ $epic-details-cell-width: 150px;
.timeline-header-blank { .timeline-header-blank {
@include gl-sticky; @include gl-sticky;
position: -webkit-sticky;
@include gl-top-0; @include gl-top-0;
@include gl-left-0; @include gl-left-0;
width: $details-cell-width; width: $details-cell-width;
z-index: 2; z-index: 2;
&::after {
height: $header-item-height;
@include gl-content-empty;
@include gl-absolute;
@include gl-top-0;
right: -$grid-size;
width: $grid-size;
@include gl-pointer-events-none;
background: $column-right-gradient;
}
} }
.timeline-header-item { .timeline-header-item {
// container size minus left panel width divided by 2 week timeframes // container size minus left panel width divided by 2 week timeframes
width: calc((100% - #{$epic-details-cell-width}) / 2); width: calc((100% - #{$details-cell-width}) / 2);
&:last-of-type .item-label { &:last-of-type .item-label {
@include gl-border-r-0; @include gl-border-r-0;
...@@ -110,7 +97,8 @@ $epic-details-cell-width: 150px; ...@@ -110,7 +97,8 @@ $epic-details-cell-width: 150px;
} }
.item-label { .item-label {
padding: $gl-padding-8 $gl-padding; @include gl-py-4;
@include gl-pl-7;
border-right: $border-style; border-right: $border-style;
border-bottom: $border-style; border-bottom: $border-style;
} }
...@@ -124,19 +112,72 @@ $epic-details-cell-width: 150px; ...@@ -124,19 +112,72 @@ $epic-details-cell-width: 150px;
@include gl-flex-basis-0; @include gl-flex-basis-0;
text-align: center; text-align: center;
font-size: $code-font-size; @include gl-font-base;
line-height: 1.5;
padding: 2px 0; padding: 2px 0;
} }
} }
.current-day-indicator-header { .current-day-indicator-header {
@include gl-absolute;
@include gl-bottom-0; @include gl-bottom-0;
height: $gl-vert-padding; height: $grid-size;
width: $gl-vert-padding; width: $grid-size;
background-color: var(--red-500, $red-500); background-color: var(--red-500, $red-500);
border-radius: 50%; @include gl-rounded-full;
transform: translateX(-3px); transform: translate(-50%, 50%);
} }
} }
} }
.timeline-section .timeline-header-blank,
.list-section .details-cell {
&::after {
@include gl-h-full;
@include gl-content-empty;
@include gl-absolute;
@include gl-top-0;
right: -$grid-size;
width: $grid-size;
@include gl-pointer-events-none;
background: $column-right-gradient;
}
}
.details-cell,
.timeline-cell {
@include float-left;
height: $item-height;
border-bottom: $border-style;
}
.details-cell {
@include gl-sticky;
@include gl-left-0;
width: $details-cell-width;
@include gl-font-base;
background-color: var(--white, $white);
z-index: 10;
}
.timeline-cell {
@include gl-relative;
// width: $timeline-cell-width;
// container size minus left panel width divided by 2 week timeframes
width: calc((100% - #{$details-cell-width}) / 2);
@include gl-bg-transparent;
border-right: $border-style;
&:last-child {
@include gl-border-r-0;
}
.current-day-indicator {
@include gl-absolute;
top: -1px;
width: $gl-spacing-scale-1;
height: calc(100% + 1px);
background-color: var(--red-500, $red-500);
@include gl-pointer-events-none;
transform: translateX(-50%);
}
}
...@@ -25,6 +25,10 @@ export default { ...@@ -25,6 +25,10 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
modalId: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -88,7 +92,7 @@ export default { ...@@ -88,7 +92,7 @@ export default {
<template> <template>
<gl-modal <gl-modal
ref="deleteScheduleModal" ref="deleteScheduleModal"
modal-id="deleteScheduleModal" :modal-id="modalId"
size="sm" size="sm"
:data-testid="`delete-schedule-modal-${schedule.iid}`" :data-testid="`delete-schedule-modal-${schedule.iid}`"
:title="$options.i18n.deleteSchedule" :title="$options.i18n.deleteSchedule"
......
...@@ -26,6 +26,10 @@ export default { ...@@ -26,6 +26,10 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
modalId: {
type: String,
required: true,
},
}, },
data() { data() {
return { return {
...@@ -112,7 +116,7 @@ export default { ...@@ -112,7 +116,7 @@ export default {
<template> <template>
<gl-modal <gl-modal
ref="updateScheduleModal" ref="updateScheduleModal"
modal-id="updateScheduleModal" :modal-id="modalId"
size="sm" size="sm"
:data-testid="`update-schedule-modal-${schedule.iid}`" :data-testid="`update-schedule-modal-${schedule.iid}`"
:title="$options.i18n.editSchedule" :title="$options.i18n.editSchedule"
......
...@@ -11,18 +11,30 @@ import { s__ } from '~/locale'; ...@@ -11,18 +11,30 @@ import { s__ } from '~/locale';
import ScheduleTimelineSection from './schedule/components/schedule_timeline_section.vue'; import ScheduleTimelineSection from './schedule/components/schedule_timeline_section.vue';
import DeleteScheduleModal from './delete_schedule_modal.vue'; import DeleteScheduleModal from './delete_schedule_modal.vue';
import EditScheduleModal from './edit_schedule_modal.vue'; import EditScheduleModal from './edit_schedule_modal.vue';
import AddRotationModal from './rotations/add_rotation_modal.vue';
import { getTimeframeForWeeksView } from './schedule/utils'; import { getTimeframeForWeeksView } from './schedule/utils';
import { PRESET_TYPES } from './schedule/constants'; import { PRESET_TYPES } from './schedule/constants';
import { getFormattedTimezone } from '../utils/common_utils'; import { getFormattedTimezone } from '../utils/common_utils';
import RotationsListSection from './schedule/components/rotations_list_section.vue';
export const i18n = { export const i18n = {
scheduleForTz: s__('OnCallSchedules|On-call schedule for the %{tzShort}'), scheduleForTz: s__('OnCallSchedules|On-call schedule for the %{tzShort}'),
editScheduleLabel: s__('OnCallSchedules|Edit schedule'), editScheduleLabel: s__('OnCallSchedules|Edit schedule'),
deleteScheduleLabel: s__('OnCallSchedules|Delete schedule'), deleteScheduleLabel: s__('OnCallSchedules|Delete schedule'),
rotationTitle: s__('OnCallSchedules|Rotations'),
addARotation: s__('OnCallSchedules|Add a rotation'),
}; };
export const addRotationModalId = 'addRotationModal';
export const editScheduleModalId = 'editScheduleModal';
export const deleteScheduleModalId = 'deleteScheduleModal';
export default { export default {
i18n, i18n,
addRotationModalId,
editScheduleModalId,
deleteScheduleModalId,
presetType: PRESET_TYPES.WEEKS, presetType: PRESET_TYPES.WEEKS,
inject: ['timezones'], inject: ['timezones'],
components: { components: {
...@@ -33,6 +45,8 @@ export default { ...@@ -33,6 +45,8 @@ export default {
GlButton, GlButton,
DeleteScheduleModal, DeleteScheduleModal,
EditScheduleModal, EditScheduleModal,
AddRotationModal,
RotationsListSection,
}, },
directives: { directives: {
GlModal: GlModalDirective, GlModal: GlModalDirective,
...@@ -43,6 +57,11 @@ export default { ...@@ -43,6 +57,11 @@ export default {
type: Object, type: Object,
required: true, required: true,
}, },
rotations: {
type: Array,
required: false,
default: () => [],
},
}, },
computed: { computed: {
tzLong() { tzLong() {
...@@ -60,18 +79,21 @@ export default { ...@@ -60,18 +79,21 @@ export default {
<div> <div>
<gl-card> <gl-card>
<template #header> <template #header>
<div class="gl-display-flex gl-justify-content-space-between gl-m-0"> <div
class="gl-display-flex gl-justify-content-space-between gl-m-0"
data-testid="scheduleHeader"
>
<span class="gl-font-weight-bold gl-font-lg">{{ schedule.name }}</span> <span class="gl-font-weight-bold gl-font-lg">{{ schedule.name }}</span>
<gl-button-group> <gl-button-group>
<gl-button <gl-button
v-gl-modal.updateScheduleModal v-gl-modal="$options.editScheduleModalId"
v-gl-tooltip v-gl-tooltip
:title="$options.i18n.editScheduleLabel" :title="$options.i18n.editScheduleLabel"
icon="pencil" icon="pencil"
:aria-label="$options.i18n.editScheduleLabel" :aria-label="$options.i18n.editScheduleLabel"
/> />
<gl-button <gl-button
v-gl-modal.deleteScheduleModal v-gl-modal="$options.deleteScheduleModalId"
v-gl-tooltip v-gl-tooltip
:title="$options.i18n.deleteScheduleLabel" :title="$options.i18n.deleteScheduleLabel"
icon="remove" icon="remove"
...@@ -80,19 +102,38 @@ export default { ...@@ -80,19 +102,38 @@ export default {
</gl-button-group> </gl-button-group>
</div> </div>
</template> </template>
<p class="gl-text-gray-500 gl-mb-5" data-testid="scheduleBody">
<p class="gl-text-gray-500 gl-mb-5">
<gl-sprintf :message="$options.i18n.scheduleForTz"> <gl-sprintf :message="$options.i18n.scheduleForTz">
<template #tzShort>{{ schedule.timezone }}</template> <template #tzShort>{{ schedule.timezone }}</template>
</gl-sprintf> </gl-sprintf>
| {{ tzLong }} | {{ tzLong }}
</p> </p>
<div class="schedule-shell"> <gl-card header-class="gl-bg-transparent">
<schedule-timeline-section :preset-type="$options.presetType" :timeframe="timeframe" /> <template #header>
</div> <div
class="gl-display-flex gl-justify-content-space-between"
data-testid="rotationsHeader"
>
<h6 class="gl-m-0">{{ $options.i18n.rotationTitle }}</h6>
<gl-button v-gl-modal="$options.addRotationModalId" variant="link"
>{{ $options.i18n.addARotation }}
</gl-button>
</div>
</template>
<div class="schedule-shell" data-testid="rotationsBody">
<schedule-timeline-section :preset-type="$options.presetType" :timeframe="timeframe" />
<rotations-list-section
:preset-type="$options.presetType"
:rotations="rotations"
:timeframe="timeframe"
/>
</div>
</gl-card>
</gl-card> </gl-card>
<delete-schedule-modal :schedule="schedule" /> <delete-schedule-modal :schedule="schedule" :modal-id="$options.deleteScheduleModalId" />
<edit-schedule-modal :schedule="schedule" /> <edit-schedule-modal :schedule="schedule" :modal-id="$options.editScheduleModalId" />
<add-rotation-modal :modal-id="$options.addRotationModalId" />
</div> </div>
</template> </template>
...@@ -7,7 +7,7 @@ import { s__ } from '~/locale'; ...@@ -7,7 +7,7 @@ import { s__ } from '~/locale';
import getOncallSchedulesQuery from '../graphql/queries/get_oncall_schedules.query.graphql'; import getOncallSchedulesQuery from '../graphql/queries/get_oncall_schedules.query.graphql';
import { fetchPolicies } from '~/lib/graphql'; import { fetchPolicies } from '~/lib/graphql';
const addScheduleModalId = 'addScheduleModal'; export const addScheduleModalId = 'addScheduleModal';
export const i18n = { export const i18n = {
title: s__('OnCallSchedules|On-call schedule'), title: s__('OnCallSchedules|On-call schedule'),
......
...@@ -60,6 +60,12 @@ export default { ...@@ -60,6 +60,12 @@ export default {
GlAvatarLabeled, GlAvatarLabeled,
GlAlert, GlAlert,
}, },
props: {
modalId: {
type: String,
required: true,
},
},
apollo: { apollo: {
participants: { participants: {
query: usersSearchQuery, query: usersSearchQuery,
...@@ -169,7 +175,7 @@ export default { ...@@ -169,7 +175,7 @@ export default {
<template> <template>
<gl-modal <gl-modal
ref="createScheduleRotationModal" ref="createScheduleRotationModal"
modal-id="create-schedule-rotation-modal" :modal-id="modalId"
size="sm" size="sm"
:title="$options.i18n.addRotation" :title="$options.i18n.addRotation"
:action-primary="actionsProps.primary" :action-primary="actionsProps.primary"
......
<script>
import CommonMixin from '../mixins/common_mixin';
export default {
mixins: [CommonMixin],
props: {
presetType: {
type: String,
required: true,
},
timeframeItem: {
type: [Date, Object],
required: true,
},
},
};
</script>
<template>
<span v-if="hasToday" :style="getIndicatorStyles()" class="current-day-indicator"></span>
</template>
<script> <script>
import { monthInWords } from '~/lib/utils/datetime_utility'; import { monthInWords } from '~/lib/utils/datetime_utility';
import WeeksHeaderSubItem from './weeks_header_sub_item.vue'; import WeeksHeaderSubItem from './weeks_header_sub_item.vue';
import CommonMixin from '../../mixins/common_mixin';
export default { export default {
components: { components: {
WeeksHeaderSubItem, WeeksHeaderSubItem,
}, },
mixins: [CommonMixin],
props: { props: {
timeframeIndex: { timeframeIndex: {
type: Number, type: Number,
...@@ -21,14 +22,6 @@ export default { ...@@ -21,14 +22,6 @@ export default {
required: true, required: true,
}, },
}, },
data() {
const currentDate = new Date();
currentDate.setHours(0, 0, 0, 0);
return {
currentDate,
};
},
computed: { computed: {
lastDayOfCurrentWeek() { lastDayOfCurrentWeek() {
const lastDayOfCurrentWeek = new Date(this.timeframeItem.getTime()); const lastDayOfCurrentWeek = new Date(this.timeframeItem.getTime());
...@@ -50,7 +43,7 @@ export default { ...@@ -50,7 +43,7 @@ export default {
return `${monthInWords(this.timeframeItem, true)} ${timeframeItemDate}`; return `${monthInWords(this.timeframeItem, true)} ${timeframeItemDate}`;
}, },
timelineHeaderClass() { timelineHeaderClass() {
const currentDateTime = this.currentDate.getTime(); const currentDateTime = this.$options.currentDate.getTime();
const lastDayOfCurrentWeekTime = this.lastDayOfCurrentWeek.getTime(); const lastDayOfCurrentWeekTime = this.lastDayOfCurrentWeek.getTime();
if ( if (
...@@ -68,7 +61,9 @@ export default { ...@@ -68,7 +61,9 @@ export default {
<template> <template>
<span class="timeline-header-item"> <span class="timeline-header-item">
<div :class="timelineHeaderClass" class="item-label">{{ timelineHeaderLabel }}</div> <div :class="timelineHeaderClass" class="item-label" data-testid="timeline-header-label">
<weeks-header-sub-item :timeframe-item="timeframeItem" :current-date="currentDate" /> {{ timelineHeaderLabel }}
</div>
<weeks-header-sub-item :timeframe-item="timeframeItem" />
</span> </span>
</template> </template>
<script> <script>
import { PRESET_TYPES } from '../../constants';
import CommonMixin from '../../mixins/common_mixin'; import CommonMixin from '../../mixins/common_mixin';
export default { export default {
mixins: [CommonMixin], mixins: [CommonMixin],
props: { props: {
currentDate: {
type: Date,
required: true,
},
timeframeItem: { timeframeItem: {
type: Date, type: Date,
required: true, required: true,
}, },
}, },
data() {
return {
presetType: PRESET_TYPES.WEEKS,
indicatorStyle: {},
};
},
computed: { computed: {
headerSubItems() { headerSubItems() {
const timeframeItem = new Date(this.timeframeItem.getTime()); const timeframeItem = new Date(this.timeframeItem.getTime());
...@@ -37,17 +26,12 @@ export default { ...@@ -37,17 +26,12 @@ export default {
return headerSubItems; return headerSubItems;
}, },
}, },
mounted() {
this.$nextTick(() => {
this.indicatorStyle = this.getIndicatorStyles();
});
},
methods: { methods: {
getSubItemValueClass(subItem) { getSubItemValueClass(subItem) {
// Show dark color text only for current & upcoming dates // Show dark color text only for current & upcoming dates
if (subItem.getTime() === this.currentDate.getTime()) { if (subItem.getTime() === this.$options.currentDate.getTime()) {
return 'label-dark label-bold'; return 'label-dark label-bold';
} else if (subItem > this.currentDate) { } else if (subItem > this.$options.currentDate) {
return 'label-dark'; return 'label-dark';
} }
return ''; return '';
...@@ -63,12 +47,13 @@ export default { ...@@ -63,12 +47,13 @@ export default {
:key="index" :key="index"
:class="getSubItemValueClass(subItem)" :class="getSubItemValueClass(subItem)"
class="sublabel-value" class="sublabel-value"
data-testid="sublabel-value"
>{{ subItem.getDate() }}</span >{{ subItem.getDate() }}</span
> >
<span <span
v-if="hasToday" v-if="hasToday"
:style="indicatorStyle" :style="getIndicatorStyles()"
class="current-day-indicator-header preset-weeks gl-absolute" class="current-day-indicator-header preset-weeks"
></span> ></span>
</div> </div>
</template> </template>
<script>
import CurrentDayIndicator from './current_day_indicator.vue';
export default {
components: {
CurrentDayIndicator,
},
props: {
presetType: {
type: String,
required: true,
},
rotations: {
type: Array,
required: true,
},
timeframe: {
type: Array,
required: true,
},
},
};
</script>
<template>
<div class="list-section">
<div class="list-item list-item-empty clearfix">
<span class="details-cell"></span>
<span
v-for="(timeframeItem, index) in timeframe"
:key="index"
class="timeline-cell"
data-testid="timelineCell"
>
<current-day-indicator :preset-type="presetType" :timeframe-item="timeframeItem" />
</span>
</div>
</div>
</template>
<script> <script>
import { GlCard, GlButton, GlModalDirective } from '@gitlab/ui';
import { s__ } from '~/locale';
import WeeksHeaderItem from './preset_weeks/weeks_header_item.vue'; import WeeksHeaderItem from './preset_weeks/weeks_header_item.vue';
import AddRotationModal from '../../rotations/add_rotation_modal.vue';
export const i18n = {
rotationTitle: s__('OnCallSchedules|Rotations'),
addARotation: s__('OnCallSchedules|Add a rotation'),
};
export default { export default {
i18n,
components: { components: {
GlButton,
GlCard,
WeeksHeaderItem, WeeksHeaderItem,
AddRotationModal,
},
directives: {
GlModal: GlModalDirective,
}, },
props: { props: {
presetType: { presetType: {
...@@ -34,28 +19,14 @@ export default { ...@@ -34,28 +19,14 @@ export default {
</script> </script>
<template> <template>
<div> <div class="timeline-section clearfix">
<gl-card header-class="gl-bg-transparent"> <span class="timeline-header-blank"></span>
<template #header> <weeks-header-item
<div class="gl-display-flex gl-justify-content-space-between"> v-for="(timeframeItem, index) in timeframe"
<h6 class="gl-m-0">{{ $options.i18n.rotationTitle }}</h6> :key="index"
<gl-button v-gl-modal="'create-schedule-rotation-modal'" variant="link">{{ :timeframe-index="index"
$options.i18n.addARotation :timeframe-item="timeframeItem"
}}</gl-button> :timeframe="timeframe"
</div> />
</template>
<div class="timeline-section clearfix">
<span class="timeline-header-blank"></span>
<weeks-header-item
v-for="(timeframeItem, index) in timeframe"
:key="index"
:timeframe-index="index"
:timeframe-item="timeframeItem"
:timeframe="timeframe"
/>
</div>
</gl-card>
<add-rotation-modal />
</div> </div>
</template> </template>
...@@ -9,7 +9,3 @@ export const PRESET_DEFAULTS = { ...@@ -9,7 +9,3 @@ export const PRESET_DEFAULTS = {
TIMEFRAME_LENGTH: 2, TIMEFRAME_LENGTH: 2,
}, },
}; };
export const PAST_DATE = new Date(new Date().getFullYear() - 100, 0, 1);
export const FUTURE_DATE = new Date(new Date().getFullYear() + 100, 0, 1);
import { DAYS_IN_WEEK } from '../constants'; import { DAYS_IN_WEEK } from '../constants';
export default { export default {
currentDate: null,
computed: { computed: {
hasToday() { hasToday() {
const timeframeItem = new Date(this.timeframeItem.getTime()); const timeframeItem = new Date(this.timeframeItem.getTime());
...@@ -16,11 +17,17 @@ export default { ...@@ -16,11 +17,17 @@ export default {
); );
return ( return (
this.currentDate.getTime() >= headerSubItems[0].getTime() && this.$options.currentDate.getTime() >= headerSubItems[0].getTime() &&
this.currentDate.getTime() <= headerSubItems[headerSubItems.length - 1].getTime() this.$options.currentDate.getTime() <= headerSubItems[headerSubItems.length - 1].getTime()
); );
}, },
}, },
beforeCreate() {
const currentDate = new Date();
currentDate.setHours(0, 0, 0, 0);
this.$options.currentDate = currentDate;
},
methods: { methods: {
getIndicatorStyles() { getIndicatorStyles() {
// as we start schedule scale from the current date the indicator will always be on the first date. So we find // as we start schedule scale from the current date the indicator will always be on the first date. So we find
......
...@@ -5,7 +5,7 @@ exports[`AddScheduleModal renders modal layout 1`] = ` ...@@ -5,7 +5,7 @@ exports[`AddScheduleModal renders modal layout 1`] = `
actioncancel="[object Object]" actioncancel="[object Object]"
actionprimary="[object Object]" actionprimary="[object Object]"
modalclass="" modalclass=""
modalid="modalId" modalid="addScheduleModal"
size="sm" size="sm"
title="Add schedule" title="Add schedule"
titletag="h4" titletag="h4"
......
...@@ -6,7 +6,7 @@ exports[`UpdateScheduleModal renders update schedule modal layout 1`] = ` ...@@ -6,7 +6,7 @@ exports[`UpdateScheduleModal renders update schedule modal layout 1`] = `
actionprimary="[object Object]" actionprimary="[object Object]"
data-testid="update-schedule-modal-37" data-testid="update-schedule-modal-37"
modalclass="" modalclass=""
modalid="updateScheduleModal" modalid="editScheduleModal"
size="sm" size="sm"
title="Edit schedule" title="Edit schedule"
titletag="h4" titletag="h4"
......
...@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils'; ...@@ -2,6 +2,7 @@ import { shallowMount } from '@vue/test-utils';
import { GlModal, GlAlert } from '@gitlab/ui'; import { GlModal, GlAlert } from '@gitlab/ui';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import AddScheduleModal from 'ee/oncall_schedules/components/add_schedule_modal.vue'; import AddScheduleModal from 'ee/oncall_schedules/components/add_schedule_modal.vue';
import { addScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedules_wrapper';
import { getOncallSchedulesQueryResponse } from './mocks/apollo_mock'; import { getOncallSchedulesQueryResponse } from './mocks/apollo_mock';
import mockTimezones from './mocks/mockTimezones.json'; import mockTimezones from './mocks/mockTimezones.json';
...@@ -22,7 +23,7 @@ describe('AddScheduleModal', () => { ...@@ -22,7 +23,7 @@ describe('AddScheduleModal', () => {
}; };
}, },
propsData: { propsData: {
modalId: 'modalId', modalId: addScheduleModalId,
...props, ...props,
}, },
provide: { provide: {
......
...@@ -8,6 +8,7 @@ import destroyOncallScheduleMutation from 'ee/oncall_schedules/graphql/mutations ...@@ -8,6 +8,7 @@ import destroyOncallScheduleMutation from 'ee/oncall_schedules/graphql/mutations
import DeleteScheduleModal, { import DeleteScheduleModal, {
i18n, i18n,
} from 'ee/oncall_schedules/components/delete_schedule_modal.vue'; } from 'ee/oncall_schedules/components/delete_schedule_modal.vue';
import { deleteScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedule';
import { import {
getOncallSchedulesQueryResponse, getOncallSchedulesQueryResponse,
destroyScheduleResponse, destroyScheduleResponse,
...@@ -53,6 +54,7 @@ describe('DeleteScheduleModal', () => { ...@@ -53,6 +54,7 @@ describe('DeleteScheduleModal', () => {
}; };
}, },
propsData: { propsData: {
modalId: deleteScheduleModalId,
schedule, schedule,
...props, ...props,
}, },
...@@ -95,6 +97,7 @@ describe('DeleteScheduleModal', () => { ...@@ -95,6 +97,7 @@ describe('DeleteScheduleModal', () => {
apolloProvider: fakeApollo, apolloProvider: fakeApollo,
propsData: { propsData: {
schedule, schedule,
modalId: deleteScheduleModalId,
}, },
provide: { provide: {
projectPath, projectPath,
......
...@@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises'; ...@@ -6,6 +6,7 @@ import waitForPromises from 'helpers/wait_for_promises';
import getOncallSchedulesQuery from 'ee/oncall_schedules/graphql/queries/get_oncall_schedules.query.graphql'; import getOncallSchedulesQuery from 'ee/oncall_schedules/graphql/queries/get_oncall_schedules.query.graphql';
import updateOncallScheduleMutation from 'ee/oncall_schedules/graphql/mutations/update_oncall_schedule.mutation.graphql'; import updateOncallScheduleMutation from 'ee/oncall_schedules/graphql/mutations/update_oncall_schedule.mutation.graphql';
import UpdateScheduleModal, { i18n } from 'ee/oncall_schedules/components/edit_schedule_modal.vue'; import UpdateScheduleModal, { i18n } from 'ee/oncall_schedules/components/edit_schedule_modal.vue';
import { editScheduleModalId } from 'ee/oncall_schedules/components/oncall_schedule';
import { import {
getOncallSchedulesQueryResponse, getOncallSchedulesQueryResponse,
updateScheduleResponse, updateScheduleResponse,
...@@ -52,6 +53,7 @@ describe('UpdateScheduleModal', () => { ...@@ -52,6 +53,7 @@ describe('UpdateScheduleModal', () => {
}; };
}, },
propsData: { propsData: {
modalId: editScheduleModalId,
schedule, schedule,
...props, ...props,
}, },
...@@ -98,6 +100,7 @@ describe('UpdateScheduleModal', () => { ...@@ -98,6 +100,7 @@ describe('UpdateScheduleModal', () => {
}; };
}, },
propsData: { propsData: {
modalId: editScheduleModalId,
schedule, schedule,
}, },
provide: { provide: {
...@@ -122,7 +125,7 @@ describe('UpdateScheduleModal', () => { ...@@ -122,7 +125,7 @@ describe('UpdateScheduleModal', () => {
describe('renders update modal with the correct schedule information', () => { describe('renders update modal with the correct schedule information', () => {
it('renders name of correct modal id', () => { it('renders name of correct modal id', () => {
expect(findModal().attributes('modalid')).toBe('updateScheduleModal'); expect(findModal().attributes('modalid')).toBe(editScheduleModalId);
}); });
it('renders name of schedule to update', () => { it('renders name of schedule to update', () => {
......
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlCard, GlSprintf } from '@gitlab/ui'; import { GlCard, GlSprintf, GlButton } from '@gitlab/ui';
import OnCallSchedule, { i18n } from 'ee/oncall_schedules/components/oncall_schedule.vue'; import OnCallSchedule, { i18n } from 'ee/oncall_schedules/components/oncall_schedule.vue';
import ScheduleTimelineSection from 'ee/oncall_schedules/components/schedule/components/schedule_timeline_section.vue'; import ScheduleTimelineSection from 'ee/oncall_schedules/components/schedule/components/schedule_timeline_section.vue';
import RotationsListSection from 'ee/oncall_schedules/components/schedule/components/rotations_list_section.vue';
import * as utils from 'ee/oncall_schedules/components/schedule/utils'; import * as utils from 'ee/oncall_schedules/components/schedule/utils';
import * as commonUtils from 'ee/oncall_schedules/utils/common_utils'; import * as commonUtils from 'ee/oncall_schedules/utils/common_utils';
import { PRESET_TYPES } from 'ee/oncall_schedules/components/schedule/constants'; import { PRESET_TYPES } from 'ee/oncall_schedules/components/schedule/constants';
import { extendedWrapper } from 'helpers/vue_test_utils_helper';
import mockTimezones from './mocks/mockTimezones.json'; import mockTimezones from './mocks/mockTimezones.json';
describe('On-call schedule', () => { describe('On-call schedule', () => {
...@@ -21,18 +23,20 @@ describe('On-call schedule', () => { ...@@ -21,18 +23,20 @@ describe('On-call schedule', () => {
const formattedTimezone = '(UTC-09:00) AKST Alaska'; const formattedTimezone = '(UTC-09:00) AKST Alaska';
function mountComponent({ schedule } = {}) { function mountComponent({ schedule } = {}) {
wrapper = shallowMount(OnCallSchedule, { wrapper = extendedWrapper(
propsData: { shallowMount(OnCallSchedule, {
schedule, propsData: {
}, schedule,
provide: { },
timezones: mockTimezones, provide: {
}, timezones: mockTimezones,
stubs: { },
GlCard, stubs: {
GlSprintf, GlCard,
}, GlSprintf,
}); },
}),
);
} }
beforeEach(() => { beforeEach(() => {
...@@ -46,23 +50,32 @@ describe('On-call schedule', () => { ...@@ -46,23 +50,32 @@ describe('On-call schedule', () => {
wrapper = null; wrapper = null;
}); });
const findCardHeader = () => wrapper.find('.gl-card-header'); const findScheduleHeader = () => wrapper.findByTestId('scheduleHeader');
const findCardDescription = () => wrapper.find('.gl-card-body'); const findRotationsHeader = () => wrapper.findByTestId('rotationsHeader');
const findScheduleTimeline = () => findCardDescription().find(ScheduleTimelineSection); const findSchedule = () => wrapper.findByTestId('scheduleBody');
const findRotations = () => wrapper.findByTestId('rotationsBody');
const findAddRotationsBtn = () => findRotationsHeader().find(GlButton);
const findScheduleTimeline = () => findRotations().find(ScheduleTimelineSection);
const findRotationsList = () => findRotations().find(RotationsListSection);
it('shows schedule title', () => { it('shows schedule title', () => {
expect(findCardHeader().text()).toBe(mockSchedule.name); expect(findScheduleHeader().text()).toBe(mockSchedule.name);
}); });
it('shows timezone info', () => { it('shows timezone info', () => {
const shortTz = i18n.scheduleForTz.replace('%{tzShort}', lastTz.identifier); const shortTz = i18n.scheduleForTz.replace('%{tzShort}', lastTz.identifier);
const longTz = formattedTimezone; const longTz = formattedTimezone;
const description = findCardDescription().text(); const description = findSchedule().text();
expect(description).toContain(shortTz); expect(description).toContain(shortTz);
expect(description).toContain(longTz); expect(description).toContain(longTz);
}); });
it('renders ScheduleShell', () => { it('renders rotations header', () => {
expect(findRotationsHeader().text()).toContain(i18n.rotationTitle);
expect(findAddRotationsBtn().text()).toContain(i18n.addARotation);
});
it('renders schedule timeline', () => {
const timeline = findScheduleTimeline(); const timeline = findScheduleTimeline();
expect(timeline.exists()).toBe(true); expect(timeline.exists()).toBe(true);
expect(timeline.props()).toEqual({ expect(timeline.props()).toEqual({
...@@ -70,4 +83,14 @@ describe('On-call schedule', () => { ...@@ -70,4 +83,14 @@ describe('On-call schedule', () => {
timeframe: mockWeeksTimeFrame, timeframe: mockWeeksTimeFrame,
}); });
}); });
it('renders rotations list', () => {
const rotationsList = findRotationsList();
expect(rotationsList.exists()).toBe(true);
expect(rotationsList.props()).toEqual({
presetType: PRESET_TYPES.WEEKS,
timeframe: mockWeeksTimeFrame,
rotations: expect.any(Array),
});
});
}); });
...@@ -5,7 +5,7 @@ exports[`AddRotationModal renders rotation modal layout 1`] = ` ...@@ -5,7 +5,7 @@ exports[`AddRotationModal renders rotation modal layout 1`] = `
actioncancel="[object Object]" actioncancel="[object Object]"
actionprimary="[object Object]" actionprimary="[object Object]"
modalclass="" modalclass=""
modalid="create-schedule-rotation-modal" modalid="addRotationModal"
size="sm" size="sm"
title="Add rotation" title="Add rotation"
titletag="h4" titletag="h4"
......
...@@ -3,6 +3,7 @@ import createMockApollo from 'jest/helpers/mock_apollo_helper'; ...@@ -3,6 +3,7 @@ import createMockApollo from 'jest/helpers/mock_apollo_helper';
import VueApollo from 'vue-apollo'; import VueApollo from 'vue-apollo';
import waitForPromises from 'helpers/wait_for_promises'; import waitForPromises from 'helpers/wait_for_promises';
import { GlDropdownItem, GlModal, GlAlert, GlTokenSelector } from '@gitlab/ui'; import { GlDropdownItem, GlModal, GlAlert, GlTokenSelector } from '@gitlab/ui';
import { addRotationModalId } from 'ee/oncall_schedules/components/oncall_schedule';
import AddRotationModal from 'ee/oncall_schedules/components/rotations/add_rotation_modal.vue'; import AddRotationModal from 'ee/oncall_schedules/components/rotations/add_rotation_modal.vue';
// import createOncallScheduleRotationMutation from 'ee/oncall_schedules/graphql/create_oncall_schedule_rotation.mutation.graphql'; // import createOncallScheduleRotationMutation from 'ee/oncall_schedules/graphql/create_oncall_schedule_rotation.mutation.graphql';
import usersSearchQuery from '~/graphql_shared/queries/users_search.query.graphql'; import usersSearchQuery from '~/graphql_shared/queries/users_search.query.graphql';
...@@ -34,6 +35,7 @@ describe('AddRotationModal', () => { ...@@ -34,6 +35,7 @@ describe('AddRotationModal', () => {
}; };
}, },
propsData: { propsData: {
modalId: addRotationModalId,
...props, ...props,
}, },
provide: { provide: {
...@@ -58,6 +60,9 @@ describe('AddRotationModal', () => { ...@@ -58,6 +60,9 @@ describe('AddRotationModal', () => {
wrapper = shallowMount(AddRotationModal, { wrapper = shallowMount(AddRotationModal, {
localVue, localVue,
propsData: {
modalId: addRotationModalId,
},
apolloProvider: fakeApollo, apolloProvider: fakeApollo,
data() { data() {
return { return {
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RotationsListSectionComponent renders component layout 1`] = `
<div
class="list-section"
>
<div
class="list-item list-item-empty clearfix"
>
<span
class="details-cell"
/>
<span
class="timeline-cell"
data-testid="timelineCell"
>
<current-day-indicator-stub
presettype="WEEKS"
timeframeitem="Mon Jan 01 2018 00:00:00 GMT+0000 (Greenwich Mean Time)"
/>
</span>
<span
class="timeline-cell"
data-testid="timelineCell"
>
<current-day-indicator-stub
presettype="WEEKS"
timeframeitem="Mon Jan 08 2018 00:00:00 GMT+0000 (Greenwich Mean Time)"
/>
</span>
</div>
</div>
`;
import { shallowMount } from '@vue/test-utils';
import CurrentDayIndicator from 'ee/oncall_schedules/components/schedule/components/current_day_indicator.vue';
import { PRESET_TYPES, DAYS_IN_WEEK } from 'ee/oncall_schedules/components/schedule/constants';
import { useFakeDate } from 'helpers/fake_date';
describe('CurrentDayIndicator', () => {
let wrapper;
// January 3rd, 2018 - current date (faked)
useFakeDate(2018, 0, 3);
// January 1st, 2018 is the first day of the week-long timeframe
// so as long as current date (faked January 3rd, 2018) is within week timeframe
// current indicator will be rendered
const mockTimeframeInitialDate = new Date(2018, 0, 1);
function mountComponent() {
wrapper = shallowMount(CurrentDayIndicator, {
propsData: {
presetType: PRESET_TYPES.WEEKS,
timeframeItem: mockTimeframeInitialDate,
},
});
}
beforeEach(() => {
mountComponent();
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
it('renders span element containing class `current-day-indicator`', () => {
expect(wrapper.classes('current-day-indicator')).toBe(true);
});
it('sets correct styles', async () => {
const left = 100 / DAYS_IN_WEEK / 2;
expect(wrapper.attributes('style')).toBe(`left: ${left}%;`);
});
});
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import WeeksHeaderItemComponent from 'ee/oncall_schedules/components/schedule/components/preset_weeks/weeks_header_item.vue'; import WeeksHeaderItemComponent from 'ee/oncall_schedules/components/schedule/components/preset_weeks/weeks_header_item.vue';
import { getTimeframeForWeeksView } from 'ee/oncall_schedules/components/schedule/utils'; import { getTimeframeForWeeksView } from 'ee/oncall_schedules/components/schedule/utils';
import { useFakeDate } from 'helpers/fake_date';
describe('WeeksHeaderItemComponent', () => { describe('WeeksHeaderItemComponent', () => {
let wrapper; let wrapper;
// January 3rd, 2018 - current date (faked)
useFakeDate(2018, 0, 3);
const mockTimeframeIndex = 0; const mockTimeframeIndex = 0;
const mockTimeframeInitialDate = new Date(2018, 0, 1); const mockTimeframeInitialDate = new Date(2018, 0, 1);
const mockTimeframeWeeks = getTimeframeForWeeksView(mockTimeframeInitialDate); const mockTimeframeWeeks = getTimeframeForWeeksView(mockTimeframeInitialDate);
...@@ -12,7 +15,7 @@ describe('WeeksHeaderItemComponent', () => { ...@@ -12,7 +15,7 @@ describe('WeeksHeaderItemComponent', () => {
timeframeIndex = mockTimeframeIndex, timeframeIndex = mockTimeframeIndex,
timeframeItem = mockTimeframeWeeks[mockTimeframeIndex], timeframeItem = mockTimeframeWeeks[mockTimeframeIndex],
timeframe = mockTimeframeWeeks, timeframe = mockTimeframeWeeks,
}) { } = {}) {
wrapper = shallowMount(WeeksHeaderItemComponent, { wrapper = shallowMount(WeeksHeaderItemComponent, {
propsData: { propsData: {
timeframeIndex, timeframeIndex,
...@@ -23,7 +26,7 @@ describe('WeeksHeaderItemComponent', () => { ...@@ -23,7 +26,7 @@ describe('WeeksHeaderItemComponent', () => {
} }
beforeEach(() => { beforeEach(() => {
mountComponent({}); mountComponent();
}); });
afterEach(() => { afterEach(() => {
...@@ -33,66 +36,55 @@ describe('WeeksHeaderItemComponent', () => { ...@@ -33,66 +36,55 @@ describe('WeeksHeaderItemComponent', () => {
} }
}); });
describe('data', () => { const findHeaderLabel = () => wrapper.find('[data-testid="timeline-header-label"]');
it('returns default data props', () => {
const currentDate = new Date(); describe('lastDayOfCurrentWeek', () => {
expect(wrapper.vm.currentDate.getDate()).toBe(currentDate.getDate()); it('returns date object representing last day of the week as set in `timeframeItem`', () => {
expect(wrapper.vm.lastDayOfCurrentWeek.getDate()).toBe(
mockTimeframeWeeks[mockTimeframeIndex].getDate() + 7,
);
}); });
}); });
describe('computed', () => { describe('timelineHeaderLabel', () => {
describe('lastDayOfCurrentWeek', () => { it('returns string containing Year, Month and Date for the first timeframe item in the entire timeframe', () => {
it('returns date object representing last day of the week as set in `timeframeItem`', () => { expect(findHeaderLabel().text()).toBe('2018 Jan 1');
expect(wrapper.vm.lastDayOfCurrentWeek.getDate()).toBe(
mockTimeframeWeeks[mockTimeframeIndex].getDate() + 7,
);
});
}); });
describe('timelineHeaderLabel', () => { it('returns string containing Year, Month and Date for timeframe item that is the first week of the year', () => {
it('returns string containing Year, Month and Date for the first timeframe item in the entire timeframe', () => { mountComponent({
expect(wrapper.vm.timelineHeaderLabel).toBe('2018 Jan 1'); timeframeIndex: 3,
}); timeframeItem: new Date(2019, 0, 6),
it('returns string containing Year, Month and Date for timeframe item when it is first week of the year', () => {
mountComponent({
timeframeIndex: 3,
timeframeItem: new Date(2019, 0, 6),
});
expect(wrapper.vm.timelineHeaderLabel).toBe('2019 Jan 6');
}); });
it('returns string containing only Month and Date timeframe item when it is somewhere in the middle of timeframe', () => { expect(findHeaderLabel().text()).toBe('2019 Jan 6');
mountComponent({
timeframeIndex: mockTimeframeIndex + 1,
timeframeItem: mockTimeframeWeeks[mockTimeframeIndex + 1],
});
expect(wrapper.vm.timelineHeaderLabel).toBe('Jan 8');
});
}); });
describe('timelineHeaderClass', () => { it('returns string containing only Month and Date when timeframe item is somewhere in the middle of the timeframe', () => {
it('returns empty string when timeframeItem week is less than current week', () => { mountComponent({
expect(wrapper.vm.timelineHeaderClass).toBe(''); timeframeIndex: mockTimeframeIndex + 1,
timeframeItem: mockTimeframeWeeks[mockTimeframeIndex + 1],
}); });
it('returns string containing `label-dark label-bold` when current week is same as timeframeItem week', () => { expect(findHeaderLabel().text()).toBe('Jan 8');
wrapper.setData({ currentDate: mockTimeframeWeeks[mockTimeframeIndex] });
expect(wrapper.vm.timelineHeaderClass).toBe('label-dark label-bold');
});
}); });
}); });
describe('template', () => { describe('timelineHeaderClass', () => {
it('renders component container element with class `timeline-header-item`', () => { it('returns empty string when timeframeItem week is outside of current week', () => {
expect(wrapper.classes()).toContain('timeline-header-item'); mountComponent({
timeframeIndex: 3,
timeframeItem: new Date(2017, 0, 6),
});
expect(findHeaderLabel().classes()).not.toEqual(
expect.arrayContaining(['label-dark', 'label-bold']),
);
}); });
it('renders item label element class `item-label` and value as `timelineHeaderLabel`', () => { it('returns string containing `label-dark label-bold` when current week is same as timeframeItem week', () => {
expect(wrapper.find('.item-label').text()).toBe('2018 Jan 1'); expect(findHeaderLabel().classes()).toEqual(
expect.arrayContaining(['label-dark', 'label-bold']),
);
}); });
}); });
}); });
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import WeeksHeaderSubItemComponent from 'ee/oncall_schedules/components/schedule/components/preset_weeks/weeks_header_sub_item.vue'; import WeeksHeaderSubItemComponent from 'ee/oncall_schedules/components/schedule/components/preset_weeks/weeks_header_sub_item.vue';
import { getTimeframeForWeeksView } from 'ee/oncall_schedules/components/schedule/utils'; import { getTimeframeForWeeksView } from 'ee/oncall_schedules/components/schedule/utils';
import { PRESET_TYPES } from 'ee/oncall_schedules/components/schedule/constants'; import { useFakeDate } from 'helpers/fake_date';
describe('MonthsHeaderSubItemComponent', () => { describe('WeeksHeaderSubItemComponent', () => {
let wrapper; let wrapper;
// January 3rd, 2018 - current date (faked)
useFakeDate(2018, 0, 3);
const mockTimeframeInitialDate = new Date(2018, 0, 1); const mockTimeframeInitialDate = new Date(2018, 0, 1);
const mockTimeframeWeeks = getTimeframeForWeeksView(mockTimeframeInitialDate); const mockTimeframeWeeks = getTimeframeForWeeksView(mockTimeframeInitialDate);
function mountComponent({ function mountComponent({ timeframeItem = mockTimeframeWeeks[0] }) {
currentDate = mockTimeframeWeeks[0],
timeframeItem = mockTimeframeWeeks[0],
}) {
wrapper = shallowMount(WeeksHeaderSubItemComponent, { wrapper = shallowMount(WeeksHeaderSubItemComponent, {
propsData: { propsData: {
currentDate,
timeframeItem, timeframeItem,
}, },
}); });
...@@ -31,12 +29,7 @@ describe('MonthsHeaderSubItemComponent', () => { ...@@ -31,12 +29,7 @@ describe('MonthsHeaderSubItemComponent', () => {
} }
}); });
describe('data', () => { const findSublabelValues = () => wrapper.findAll('[data-testid="sublabel-value"]');
it('initializes `presetType` and `indicatorStyles` data props', () => {
expect(wrapper.vm.presetType).toBe(PRESET_TYPES.WEEKS);
expect(wrapper.vm.indicatorStyle).toBeDefined();
});
});
describe('computed', () => { describe('computed', () => {
describe('headerSubItems', () => { describe('headerSubItems', () => {
...@@ -50,29 +43,6 @@ describe('MonthsHeaderSubItemComponent', () => { ...@@ -50,29 +43,6 @@ describe('MonthsHeaderSubItemComponent', () => {
}); });
}); });
describe('methods', () => {
describe('getSubItemValueClass', () => {
it('returns string containing `label-dark` when provided subItem is greater than current week day', () => {
mountComponent({
currentDate: new Date(2018, 0, 1), // Jan 1, 2018
});
const subItem = new Date(2018, 0, 25); // Jan 25, 2018
expect(wrapper.vm.getSubItemValueClass(subItem)).toBe('label-dark');
});
it('returns string containing `label-dark label-bold` when provided subItem is same as current week day', () => {
const currentDate = new Date(2018, 0, 25);
mountComponent({
currentDate,
});
const subItem = currentDate;
expect(wrapper.vm.getSubItemValueClass(subItem)).toBe('label-dark label-bold');
});
});
});
describe('template', () => { describe('template', () => {
it('renders component container element with class `item-sublabel`', () => { it('renders component container element with class `item-sublabel`', () => {
expect(wrapper.classes()).toContain('item-sublabel'); expect(wrapper.classes()).toContain('item-sublabel');
...@@ -83,7 +53,29 @@ describe('MonthsHeaderSubItemComponent', () => { ...@@ -83,7 +53,29 @@ describe('MonthsHeaderSubItemComponent', () => {
}); });
it('renders element with class `current-day-indicator-header` when hasToday is true', () => { it('renders element with class `current-day-indicator-header` when hasToday is true', () => {
// January 1st, 2018 is the first day of the week-long timeframe
// so as long as current date (faked January 3rd, 2018) is within week timeframe
// current indicator will be rendered
expect(wrapper.find('.current-day-indicator-header.preset-weeks').exists()).toBe(true); expect(wrapper.find('.current-day-indicator-header.preset-weeks').exists()).toBe(true);
}); });
it('sublabel has `label-dark` class when it is for the day greater than current week day', () => {
// Timeframe starts at Jan 1, 2018, faked today is Jan 3, 2018 (3rd item in a week timeframe)
// labels for dates after current have 'label-dark' class
expect(
findSublabelValues()
.at(3)
.classes(),
).toContain('label-dark');
});
it("sublabel has `label-dark label-bold` classes when it is for today's date", () => {
// Timeframe starts at Jan 1, 2018, faked today is Jan 3, 2018 (3rd item in a week timeframe)
expect(
findSublabelValues()
.at(2)
.classes(),
).toEqual(expect.arrayContaining(['label-dark', 'label-bold']));
});
}); });
}); });
import { shallowMount } from '@vue/test-utils';
import { GlCard } from '@gitlab/ui';
import RotationsListSection from 'ee/oncall_schedules/components/schedule/components/rotations_list_section.vue';
import CurrentDayIndicator from 'ee/oncall_schedules/components/schedule/components/current_day_indicator.vue';
import { getTimeframeForWeeksView } from 'ee/oncall_schedules/components/schedule/utils';
import { PRESET_TYPES } from 'ee/oncall_schedules/components/schedule/constants';
describe('RotationsListSectionComponent', () => {
let wrapper;
const mockTimeframeInitialDate = new Date(2018, 0, 1);
const mockTimeframeWeeks = getTimeframeForWeeksView(mockTimeframeInitialDate);
function mountComponent({
presetType = PRESET_TYPES.WEEKS,
timeframe = mockTimeframeWeeks,
} = {}) {
wrapper = shallowMount(RotationsListSection, {
propsData: {
presetType,
timeframe,
rotations: [],
},
stubs: {
GlCard,
},
});
}
beforeEach(() => {
mountComponent();
});
afterEach(() => {
if (wrapper) {
wrapper.destroy();
wrapper = null;
}
});
const findTimelineCells = () => wrapper.findAll('[data-testid="timelineCell"]');
it('renders component layout', () => {
expect(wrapper.element).toMatchSnapshot();
});
it('renders timeline cell items based on timeframe data', () => {
expect(findTimelineCells().length).toBe(mockTimeframeWeeks.length);
});
it('renders current day indicator in the first timeline cell', () => {
expect(
findTimelineCells()
.at(0)
.find(CurrentDayIndicator)
.exists(),
).toBe(true);
});
});
import { shallowMount } from '@vue/test-utils'; import { shallowMount } from '@vue/test-utils';
import { GlCard, GlButton } from '@gitlab/ui'; import { GlCard } from '@gitlab/ui';
import ScheduleTimelineSection, { import ScheduleTimelineSection from 'ee/oncall_schedules/components/schedule/components/schedule_timeline_section.vue';
i18n,
} from 'ee/oncall_schedules/components/schedule/components/schedule_timeline_section.vue';
import WeeksHeaderItem from 'ee/oncall_schedules/components/schedule/components/preset_weeks/weeks_header_item.vue'; import WeeksHeaderItem from 'ee/oncall_schedules/components/schedule/components/preset_weeks/weeks_header_item.vue';
import { getTimeframeForWeeksView } from 'ee/oncall_schedules/components/schedule/utils'; import { getTimeframeForWeeksView } from 'ee/oncall_schedules/components/schedule/utils';
import { PRESET_TYPES } from 'ee/oncall_schedules/components/schedule/constants'; import { PRESET_TYPES } from 'ee/oncall_schedules/components/schedule/constants';
describe('RoadmapTimelineSectionComponent', () => { describe('TimelineSectionComponent', () => {
let wrapper; let wrapper;
const mockTimeframeInitialDate = new Date(2018, 0, 1); const mockTimeframeInitialDate = new Date(2018, 0, 1);
const mockTimeframeWeeks = getTimeframeForWeeksView(mockTimeframeInitialDate); const mockTimeframeWeeks = getTimeframeForWeeksView(mockTimeframeInitialDate);
const findRotations = () => wrapper.find(GlCard);
const findAddRotation = () => wrapper.find(GlButton);
function mountComponent({ function mountComponent({
presetType = PRESET_TYPES.WEEKS, presetType = PRESET_TYPES.WEEKS,
timeframe = mockTimeframeWeeks, timeframe = mockTimeframeWeeks,
...@@ -52,13 +47,4 @@ describe('RoadmapTimelineSectionComponent', () => { ...@@ -52,13 +47,4 @@ describe('RoadmapTimelineSectionComponent', () => {
it('renders weeks header items based on timeframe data', () => { it('renders weeks header items based on timeframe data', () => {
expect(wrapper.findAll(WeeksHeaderItem).length).toBe(mockTimeframeWeeks.length); expect(wrapper.findAll(WeeksHeaderItem).length).toBe(mockTimeframeWeeks.length);
}); });
it('renders the rotation card wrapper', () => {
expect(findRotations().exists()).toBe(true);
});
it('renders the add rotation button in the rotation card wrapper', () => {
expect(findAddRotation().exists()).toBe(true);
expect(findAddRotation().text()).toBe(i18n.addARotation);
});
}); });
import { shallowMount } from '@vue/test-utils';
import CommonMixin from 'ee/oncall_schedules/components/schedule/mixins/common_mixin';
import { DAYS_IN_WEEK } from 'ee/oncall_schedules/components/schedule/constants';
import { useFakeDate } from 'helpers/fake_date';
describe('Schedule Common Mixins', () => {
// January 3rd, 2018
useFakeDate(2018, 0, 3);
const today = new Date();
let wrapper;
const component = {
template: `<span></span>`,
props: {
timeframeItem: {
type: [Date, Object],
required: true,
},
},
mixins: [CommonMixin],
};
const mountComponent = (props = {}) => {
wrapper = shallowMount(component, {
propsData: {
timeframeItem: today,
...props,
},
});
};
describe('data', () => {
it('initializes currentDate default value', () => {
mountComponent();
expect(wrapper.vm.$options.currentDate).toEqual(today);
});
});
describe('hasToday', () => {
it('returns true when today (January 3rd, 2018) is within the set week (January 1st, 2018)', () => {
// January 1st, 2018
mountComponent({
timeframeItem: new Date(2018, 0, 1),
});
expect(wrapper.vm.hasToday).toBe(true);
});
it('returns false when today (January 3rd, 2018) is NOT within the set week (January 8th, 2018)', () => {
// February 1st, 2018
mountComponent({
timeframeItem: new Date(2018, 0, 8),
});
expect(wrapper.vm.hasToday).toBe(false);
});
});
describe('getIndicatorStyles', () => {
it('returns object containing `left` offset', () => {
const left = 100 / DAYS_IN_WEEK / 2;
expect(wrapper.vm.getIndicatorStyles()).toEqual(
expect.objectContaining({
left: `${left}%`,
}),
);
});
});
});
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