tests & refactor calendar component #811

This commit is contained in:
Joan Sanchez 2019-04-03 11:34:58 +02:00
parent 836f210f6c
commit f337799e39
8 changed files with 277 additions and 106 deletions

View File

@ -75,14 +75,14 @@
</section>
</vn-horizontal>
<vn-horizontal class="days">
<section ng-repeat="day in $ctrl.days" class="day {{day.className}}"
<section ng-repeat="day in $ctrl.days" class="day {{day.event.className || day.className}}"
ng-click="$ctrl.select($index)"
ng-style="{'color': day.style.color}">
ng-style="{'color': day.event.style.color}">
<span ng-if="day.event" vn-tooltip="{{day.event.title}}"
ng-style="{'background-color': day.style.background}">
{{::day.date | date: 'd'}}
ng-style="{'background-color': day.event.style.background}">
{{::day.dated | date: 'd'}}
</span>
<span ng-if="!day.event">{{::day.date | date: 'd'}}</span>
<span ng-if="!day.event">{{::day.dated | date: 'd'}}</span>
</section>
</vn-horizontal>
</vn-vertical>

View File

@ -15,37 +15,57 @@ export default class Calendar extends Component {
this.skip = 1;
}
/**
* Returns the initial date
*
* @return {Date} - Default date
*/
get defaultDate() {
return this._defaultDate;
}
/**
* Sets a new initial date
*
* @param {Date} value - New default date
*/
set defaultDate(value) {
this._defaultDate = value;
this.repaint();
}
get currentMonth() {
return this.defaultDate;
}
get events() {
return this._events;
}
set events(value) {
/**
* Sets initial events
*
* @param {Array} value - Array of events
*/
set data(value) {
if (!value) return;
value.map(event => {
event.date = new Date(event.date);
});
this.events = [];
this._events = value;
value.forEach(event => {
this.addEvent(event);
});
if (value.length && this.defaultDate)
this.repaint();
}
/**
* Gets current month date
*/
get currentMonth() {
return this.defaultDate;
}
/**
* Gets next month date
*
* @return {Date}
*/
get nextMonth() {
const newDate = new Date(this.currentMonth);
newDate.setMonth(this.currentMonth.getMonth() + 1);
@ -53,6 +73,11 @@ export default class Calendar extends Component {
return newDate;
}
/**
* Gets previous month date
*
* @return {Date}
*/
get previousMonth() {
const newDate = new Date(this.currentMonth);
newDate.setMonth(this.currentMonth.getMonth() - 1);
@ -101,73 +126,100 @@ export default class Calendar extends Component {
this.days = [];
for (let fieldIndex = 1; fieldIndex <= maxFields; fieldIndex++) {
for (let fieldIndex = 1; fieldIndex < maxFields; fieldIndex++) {
// Insert previous month days
if (fieldIndex < weekdayOffset) {
this.addDay(this.previousMonth, dayPrevious, 'gray');
const dated = new Date(
this.previousMonth.getFullYear(),
this.previousMonth.getMonth(), dayPrevious);
this.insertDay(dated, 'gray');
dayPrevious++;
} else if (fieldIndex >= weekdayOffset && dayCurrent <= currentLastDay) {
this.addDay(this.currentMonth, dayCurrent);
}
// Insert current month days
if (fieldIndex >= weekdayOffset && dayCurrent <= currentLastDay) {
const dated = new Date(
this.currentMonth.getFullYear(),
this.currentMonth.getMonth(), dayCurrent);
this.insertDay(dated);
dayCurrent++;
} else if (fieldIndex >= weekdayOffset && dayCurrent > currentLastDay) {
this.addDay(this.nextMonth, dayNext, 'gray');
}
// Insert next month days
if (fieldIndex >= weekdayOffset && dayCurrent > currentLastDay) {
const dated = new Date(
this.nextMonth.getFullYear(),
this.nextMonth.getMonth(), dayNext);
this.insertDay(dated, 'gray');
dayNext++;
}
}
}
addDay(date, day, className = '', style) {
const newDate = new Date(
date.getFullYear(),
date.getMonth(), day);
/**
* Inserts a date on an array of month days
*
* @param {Date} dated - Date of month
* @param {String} className - Default class style
*/
insertDay(dated, className = '') {
let event = this.events.find(event => {
return event.date >= newDate && event.date <= newDate;
return event.dated >= dated && event.dated <= dated;
});
if (newDate.getMonth() === this.currentMonth.getMonth() && newDate.getDay() == 0)
// Weeekends
if (dated.getMonth() === this.currentMonth.getMonth() && dated.getDay() == 0)
className = 'red';
if (event) {
className = event.className;
style = event.style;
}
this.days.push({date: newDate, className, style, event});
this.days.push({dated, className, event});
}
/**
* Adds a new calendar event
*
* @param {Date} date - Day to add event
* @param {String} className - [green, blue, orange, red]
* @param {String} title - Tooltip description
* @param {Boolean} isRemovable - True if is removable by users
* @param {Object} options - Event params
* @param {Date} options.dated - Day to add event
* @param {String} options.title - Tooltip description
* @param {String} options.className - ClassName style
* @param {Object} options.style - Style properties
* @param {Boolean} options.isRemovable - True if is removable by users
*/
addEvent(date, className, title = '', isRemovable = true) {
addEvent(options) {
if (!Object.hasOwnProperty.call(options, 'isRemovable'))
options.isRemovable = true;
options.dated = new Date(options.dated);
options.dated.setHours(0, 0, 0, 0);
const event = this.events.findIndex(event => {
return event.date >= date && event.date <= date;
return event.dated >= options.dated && event.dated <= options.dated;
});
if (event == -1)
this.events.push({date, className, title, isRemovable});
this.repaint();
if (event < 0)
this.events.push(options);
}
removeEvent(date) {
/**
* Removes an event from an array of events
* @param {Object} dated - Dated event
*/
removeEvent(dated) {
dated = new Date(dated);
dated.setHours(0, 0, 0, 0);
const event = this.events.findIndex(event => {
return event.date >= date && event.date <= date;
return event.dated >= dated && event.dated <= dated;
});
if (event > -1)
this.events.splice(event, 1);
this.repaint();
}
/**
* Moves to next month
* Moves to next month(s)
*
* @param {Integer} skip - Months to skip at once
*/
@ -180,7 +232,7 @@ export default class Calendar extends Component {
}
/**
* Moves to previous month
* Moves to previous month(s)
*
* @param {Integer} skip - Months to skip at once
*/
@ -204,11 +256,16 @@ export default class Calendar extends Component {
this.emit('selection', {values: [day]});
}
/**
* WeekDay selection event
*
* @param {Integer} weekday - weekday index
*/
selectAll(weekday) {
let selected = [];
for (let i in this.days) {
const day = this.days[i];
const date = day.date;
const date = day.dated;
day.index = i;
if (date.getDay() === weekday && date.getMonth() == this.defaultDate.getMonth())
selected.push(day);
@ -217,14 +274,14 @@ export default class Calendar extends Component {
}
}
Calendar.$inject = ['$element', '$scope', '$attrs'];
Calendar.$inject = ['$element', '$scope'];
ngModule.component('vnCalendar', {
template: require('./index.html'),
controller: Calendar,
bindings: {
model: '<',
events: '<?',
data: '<?',
defaultDate: '<?',
onSelection: '&?',
onMoveNext: '&?',

View File

@ -0,0 +1,125 @@
fdescribe('Component vnCalendar', () => {
let controller;
let $element;
beforeEach(ngModule('vnCore'));
beforeEach(inject(($compile, $rootScope) => {
$element = $compile(`<vn-calendar></vn-calendar`)($rootScope);
controller = $element.controller('vnCalendar');
controller.defaultDate = new Date();
}));
afterEach(() => {
$element.remove();
});
describe('events() setter', () => {
it(`should set an array of events and convert string dates to string object, then call repaint() method`, () => {
spyOn(controller, 'repaint');
let currentDate = new Date().toString();
controller.events = [
{dated: currentDate, title: 'Event 1'},
{dated: currentDate, title: 'Event 2'},
];
expect(controller.events[0].dated instanceof Object).toBeTruthy();
expect(controller.repaint).toHaveBeenCalledWith();
});
});
describe('addEvent()', () => {
it(`should add an event to an array of events, then call repaint() method`, () => {
spyOn(controller, 'repaint');
controller.events = [];
controller.addEvent({
dated: new Date(),
title: 'My event',
className: 'color'
});
const firstEvent = controller.events[0];
expect(firstEvent.title).toEqual('My event');
expect(firstEvent.isRemovable).toBeDefined();
expect(firstEvent.isRemovable).toBeTruthy();
expect(controller.repaint).toHaveBeenCalledWith();
});
it(`should not repeat an event for the same date, should not call repaint() method`, () => {
spyOn(controller, 'repaint');
const curDate = new Date();
controller._events = [{
dated: curDate,
title: 'My event 1',
className: 'color'
}];
controller.addEvent({
dated: curDate,
title: 'My event 2',
className: 'color'
});
const firstEvent = controller.events[0];
expect(controller.events.length).toEqual(1);
expect(firstEvent.title).toEqual('My event 1');
expect(controller.repaint).not.toHaveBeenCalledWith();
});
});
describe('removeEvent()', () => {
it(`should remove an event from an array of events, then call repaint() method`, () => {
spyOn(controller, 'repaint');
const curDate = new Date();
controller._events = [{
dated: curDate,
title: 'My event 1',
className: 'color'
}];
controller.removeEvent(curDate);
expect(controller.events.length).toEqual(0);
expect(controller.repaint).toHaveBeenCalledWith();
});
});
describe('moveNext()', () => {
it(`should shift to the next n months, then emit a 'moveNext' event`, () => {
spyOn(controller, 'emit');
const currentMonth = controller.defaultDate.getMonth();
let nextMonth = currentMonth + 1;
controller.moveNext(1);
expect(controller.defaultDate.getMonth()).toEqual(nextMonth);
expect(controller.emit).toHaveBeenCalledWith('moveNext');
});
});
describe('movePrevious()', () => {
it(`should shift to the previous n months, then emit a 'movePrevious' event`, () => {
spyOn(controller, 'emit');
const currentMonth = controller.defaultDate.getMonth();
let previousMonth = currentMonth - 1;
controller.movePrevious(1);
expect(controller.defaultDate.getMonth()).toEqual(previousMonth);
expect(controller.emit).toHaveBeenCalledWith('movePrevious');
});
});
describe('select()', () => {
it(`should return the selected element, then emit a 'selection' event`, () => {
spyOn(controller, 'emit');
const days = [{dated: new Date()}];
controller.days = days;
controller.select(0);
expect(controller.emit).toHaveBeenCalledWith('selection', {values: days});
});
});
});

View File

@ -19,6 +19,7 @@ vn-calendar {
.weekdays {
border-bottom: 1px solid $color-hover-cd;
border-top: 1px solid $color-hover-cd;
color: $color-font-secondary;
font-weight: bold
}

View File

@ -7,20 +7,14 @@
primary-key="zoneFk"
auto-load="true">
</vn-crud-model>
<vn-calendar
pad-small
vn-id="stMonth"
events="$ctrl.events"
skip="2"
<vn-calendar pad-small vn-id="stMonth" skip="2"
data="$ctrl.events"
on-selection="$ctrl.onSelection(stMonth, values)"
on-move-next="$ctrl.onMoveNext(ndMonth)"
on-move-previous="$ctrl.onMovePrevious(ndMonth)">
</vn-calendar>
<vn-calendar
pad-small
vn-id="ndMonth"
events="$ctrl.events"
skip="2"
<vn-calendar pad-small vn-id="ndMonth" skip="2"
data="$ctrl.events"
display-controls="false"
on-selection="$ctrl.onSelection(ndMonth, values)"
default-date="$ctrl.ndMonthDate">

View File

@ -9,7 +9,6 @@ class Controller {
this.stMonthDate = new Date();
this.ndMonthDate = new Date();
this.ndMonthDate.setMonth(this.ndMonthDate.getMonth() + 1);
this.events = [];
}
$postLink() {
@ -53,28 +52,23 @@ class Controller {
this._data = value;
if (!value) return;
const events = [];
value.forEach(event => {
let date = new Date(event.delivered);
date.setHours(0, 0, 0, 0);
events.push({
date: date,
dated: event.delivered,
className: 'green-circle',
title: 'Has delivery',
isRemovable: true
title: 'Has delivery'
});
});
this.events = this.events.concat(events);
this.events = events;
}
onSelection(calendar, values) {
let totalEvents = 0;
values.forEach(day => {
const exists = this.events.findIndex(event => {
return event.date >= day.date && event.date <= day.date
const exists = calendar.events.findIndex(event => {
return event.dated >= day.dated && event.dated <= day.dated
&& event.isRemovable;
});
@ -82,54 +76,56 @@ class Controller {
});
if (totalEvents > (values.length / 2))
this.removeEvents(values);
this.removeEvents(calendar, values);
else
this.addEvents(values);
this.insertEvents(calendar, values);
}
addEvents(days) {
insertEvents(calendar, days) {
days.forEach(day => {
const event = this.events.find(event => {
return event.date >= day.date && event.date <= day.date;
const event = calendar.events.find(event => {
return event.dated >= day.dated && event.dated <= day.dated;
});
if (event)
return false;
if (event) return false;
this.$scope.model.insert({
zoneFk: this.zone.id,
delivered: day.date
delivered: day.dated
});
this.stMonth.addEvent(day.date, 'green-circle', 'Has delivery', true);
this.stMonth.repaint();
this.ndMonth.addEvent(day.date, 'green-circle', 'Has delivery', true);
this.ndMonth.repaint();
calendar.addEvent({
dated: day.dated,
className: 'green-circle',
title: 'Has delivery'
});
});
this.$scope.model.save().then(() => {
this.events = calendar.events;
});
this.$scope.model.save();
}
removeEvents(days) {
removeEvents(calendar, days) {
let dates = [];
days.forEach(day => {
const event = this.events.find(event => {
return event.date >= day.date && event.date <= day.date;
const event = calendar.events.find(event => {
return event.dated >= day.dated && event.dated <= day.dated;
});
if (event && !event.isRemovable)
return false;
dates.push(day.date);
dates.push(day.dated);
this.stMonth.removeEvent(day.date);
this.stMonth.repaint();
this.ndMonth.removeEvent(day.date);
this.ndMonth.repaint();
calendar.removeEvent(day.dated);
});
if (dates.length == 0) return;
const params = {zoneFk: this.zone.id, dates};
this.$http.post('/agency/api/zoneCalendars/removeByDate', params);
this.$http.post('/agency/api/zoneCalendars/removeByDate', params).then(() => {
this.events = calendar.events;
});
}
onMoveNext(calendar) {

View File

@ -11,9 +11,9 @@
</vn-horizontal>
<vn-horizontal class="calendar-list">
<section class="calendar" ng-repeat="month in $ctrl.months">
<vn-calendar vn-id="stMonth"
<vn-calendar
data="$ctrl.events"
default-date="month"
events="$ctrl.events"
display-controls="false">
</vn-calendar>
</section>

View File

@ -7,7 +7,6 @@ class Controller {
this.$http = $http;
this.months = this.monthsOfYear();
this.events = [];
this.eventMap = {};
}
get worker() {
@ -31,7 +30,6 @@ class Controller {
this.setHolidays(res.data);
this.setWorkerCalendar(res.data);
this.calendar = res.data.calendar;
});
}
@ -43,7 +41,7 @@ class Controller {
const events = [];
holidays.forEach(holiday => {
events.push({
date: holiday.dated,
dated: holiday.dated,
className: 'red',
title: holiday.detail.description || holiday.type.name,
isRemovable: false
@ -60,7 +58,7 @@ class Controller {
absences.forEach(absence => {
const absenceType = absence.absenceType;
events.push({
date: absence.dated,
dated: absence.dated,
title: absenceType.name,
style: {
background: absenceType.rgb