refactor: replace bootstrap-datepicker by ember-power-calendar

Also renders two linked calendars if there is enough space.

Closes #143
This commit is contained in:
Jeldrik Hanschke 2019-01-20 15:20:54 +01:00 committed by jelhan
parent 5b34199f41
commit 06a92b947f
19 changed files with 382 additions and 372 deletions

View file

@ -1,95 +1,81 @@
import Component from '@ember/component';
import { inject as service } from '@ember/service';
import { computed } from '@ember/object';
import Component from '@ember/component';
import { isArray } from '@ember/array';
import { isEmpty } from '@ember/utils';
import { isPresent } from '@ember/utils';
import moment from 'moment';
export default Component.extend({
i18n: service(),
store: service('store'),
/*
* maps optionsDates for bootstrap datepicker as a simple array of date objects
*/
optionsBootstrapDatepicker: computed('options', {
get() {
const options = this.options;
const validDates = options.filter(function(option) {
return moment(option.get('title')).isValid();
});
const normalizedDates = validDates.map(function(option) {
return moment(option.get('title'))
.hour(0)
.minute(0)
.millisecond(0);
});
// convert to primitive to allow support Ember.Array.uniq()
const uniqueDateStrings = normalizedDates
.map((moment) => {
return moment.toISOString();
})
.uniq();
const dateObjects = uniqueDateStrings.map(function(dateString) {
return moment(dateString).toDate();
});
return dateObjects;
},
/*
* value is an of Date objects set by ember-cli-bootstrap-datepicker
*/
set(key, days) {
const options = this.options;
// remove all days if value isn't an array of if it's empty
if (!isArray(days) || isEmpty(days)) {
options.clear();
return [];
}
// get days in correct order
days.sort(function(a, b) {
return a.getTime() - b.getTime();
});
// array of date objects
const newDays = days.filter((day) => {
return options.every((option) => {
return moment(day).format('YYYY-MM-DD') !== option.get('day');
});
});
// array of options fragments
const optionsForRemovedDays = options.filter((option) => {
return days.every((day) => {
return moment(day).format('YYYY-MM-DD') !== option.get('day');
});
});
options.removeObjects(optionsForRemovedDays);
newDays.forEach((newDay) => {
// new days must be entered at correct position
const insertBefore = options.find((option) => {
// options are sorted
// so we search for first option which value is greater than newDay
return option.get('date').valueOf() > newDay.valueOf();
});
let position;
if (isEmpty(insertBefore)) {
// newDay is after all existing days
position = options.get('length');
} else {
position = options.indexOf(insertBefore);
}
options.insertAt(
position,
this.store.createFragment('option', {
title: moment(newDay).format('YYYY-MM-DD')
})
);
});
return days;
}
selectedDays: computed('options.[]', function() {
return this.options
// should be unique
.uniqBy('day')
// raw dates
.map(({ date }) => date)
// filter out invalid
.filter(moment.isMoment)
.toArray();
}),
calendarCenterNext: computed('calendarCenter', function() {
return moment(this.calendarCenter).add(1, 'months');
}),
store: service('store')
actions: {
daysSelected({ moment: newMoments }) {
let { options } = this;
if (!isArray(newMoments)) {
// special case: all options are unselected
options.clear();
return;
}
// array of options that represent days missing in updated selection
let removedOptions = options.filter((option) => {
return !newMoments.find((newMoment) => newMoment.format('YYYY-MM-DD') === option.day);
});
// array of moments that aren't represented yet by an option
let addedMoments = newMoments.filter((moment) => {
return !options.find((option) => moment.format('YYYY-MM-DD') === option.day);
});
// remove options that represent deselected days
options.removeObjects(removedOptions);
// add options for newly selected days
let newOptions = addedMoments.map((moment) => {
return this.store.createFragment('option', {
title: moment.format('YYYY-MM-DD'),
})
});
newOptions.forEach((newOption) => {
// options must be insert into options array at correct position
let insertBefore = options.find(({ date }) => {
if (!moment.isMoment(date)) {
// ignore options that do not represent a valid date
return false;
}
return date.isAfter(newOption.date);
});
let position = isPresent(insertBefore) ? options.indexOf(insertBefore) : options.length;
options.insertAt(position, newOption);
});
},
updateCalenderCenter(diff) {
this.calendarCenter.add(diff, 'months');
this.notifyPropertyChange('calenderCenter');
},
},
init() {
this._super(arguments);
let { selectedDays } = this;
this.set('calendarCenter', selectedDays.length >= 1 ? selectedDays[0] : moment());
},
});

View file

@ -0,0 +1,5 @@
import Component from '@ember/component';
export default Component.extend({
tagName: '',
});

View file

@ -7,8 +7,11 @@ import localesMeta from 'croodle/locales/meta';
export default Component.extend({
tagName: 'select',
classNames: [ 'language-select' ],
i18n: service(),
moment: service(),
powerCalendar: service(),
current: readOnly('i18n.locale'),
locales: computed('i18n.locales', function() {
@ -28,6 +31,7 @@ export default Component.extend({
this.i18n.set('locale', locale);
this.moment.changeLocale(locale);
this.powerCalendar.set('locale', locale);
if (window.localStorage) {
window.localStorage.setItem('locale', locale);

View file

@ -5,12 +5,14 @@ export default {
initialize(appInstance) {
let i18n = appInstance.lookup('service:i18n');
let moment = appInstance.lookup('service:moment');
let powerCalendar = appInstance.lookup('service:power-calendar');
let availableLocales = i18n.get('locales');
let locale = getLocale(availableLocales);
i18n.set('locale', locale);
moment.changeLocale(locale);
powerCalendar.set('local', locale);
}
};

View file

@ -2,7 +2,7 @@ export default {
'ca': 'catalan',
'de': 'deutsch',
'en': 'english',
'en-GB': 'english (GB)',
'en-gb': 'english (GB)',
'es': 'español',
'it': 'italiano'
};

35
app/styles/_calendar.scss Normal file
View file

@ -0,0 +1,35 @@
@import "ember-power-calendar";
@media only screen and (max-width: $screen-sm-min) {
.ember-power-calendar {
@include ember-power-calendar($cell-size: 63px);
}
}
@media only screen and (min-width: $screen-sm-min) {
.ember-power-calendar {
@include ember-power-calendar($cell-size: 47px);
float:left;
&:first-child {
margin-right: 10px;
}
}
.ember-power-calendar ~ .help-block {
clear: both;
}
}
@media only screen and (min-width: $screen-md-min) {
.ember-power-calendar {
@include ember-power-calendar($cell-size: 40px);
}
}
@media only screen and (min-width: $screen-lg-min) {
.ember-power-calendar {
@include ember-power-calendar($cell-size: 50px);
}
}

View file

@ -1,4 +1,5 @@
@import "ember-bootstrap/bootstrap";
@import "calendar";
table tr td .form-group {
margin-bottom:0;

View file

@ -1,15 +1,18 @@
{{#form.element
classNames="days"
label=(t "create.options.dates.label")
property="options"
as |el|
data-test-form-element-for="days"
}}
{{bootstrap-datepicker-inline
id=el.id
value=optionsBootstrapDatepicker
multidate=true
calendarWeeks=true
todayHighlight=true
language=i18n.locale
}}
<InlineDatepicker
@center={{calendarCenter}}
@selectedDays={{selectedDays}}
@onCenterChange={{action (mut calendarCenter) value="moment"}}
@onSelect={{action "daysSelected"}}
/>
<InlineDatepicker
@center={{calendarCenterNext}}
@selectedDays={{selectedDays}}
@onCenterChange={{action (mut calendarCenter) value="moment"}}
@onSelect={{action "daysSelected"}}
/>
{{/form.element}}

View file

@ -0,0 +1,28 @@
<PowerCalendarMultiple
@center={{@center}}
@selected={{@selectedDays}}
@onCenterChange={{@onCenterChange}}
@onSelect={{@onSelect}} as |calendar|
>
<nav class="ember-power-calendar-nav">
<button
type="button"
class="ember-power-calendar-nav-control"
onclick={{action calendar.actions.moveCenter -1 "month"}}
>
«
</button>
<div class="ember-power-calendar-nav-title">
{{moment-format calendar.center "MMMM YYYY"}}
</div>
<button
type="button"
class="ember-power-calendar-nav-control"
onclick={{action calendar.actions.moveCenter 1 "month"}}
>
»
</button>
</nav>
<calendar.days />
</PowerCalendarMultiple>

View file

@ -36,7 +36,7 @@ module.exports = function(environment) {
},
moment: {
includeLocales: ['ca', 'de', 'es', 'it'],
includeLocales: ['ca', 'de', 'en-gb', 'es', 'it'],
includeTimezone: 'subset'
},

View file

@ -37,12 +37,6 @@ module.exports = function(defaults) {
// please specify an object with the list of modules as keys
// along with the exports of each module as its value.
app.import('vendor/bootstrap-datepicker-locales/bootstrap-datepicker.ca.min.js');
app.import('vendor/bootstrap-datepicker-locales/bootstrap-datepicker.de.min.js');
app.import('vendor/bootstrap-datepicker-locales/bootstrap-datepicker.en-GB.min.js');
app.import('vendor/bootstrap-datepicker-locales/bootstrap-datepicker.es.min.js');
app.import('vendor/bootstrap-datepicker-locales/bootstrap-datepicker.it.min.js');
app.import({
development: 'node_modules/floatthead/dist/jquery.floatThead.js',
production: 'node_modules/floatthead/dist/jquery.floatThead.min.js'

View file

@ -29,7 +29,6 @@
"ember-cli-acceptance-test-helpers": "^1.0.0",
"ember-cli-app-version": "^3.2.0",
"ember-cli-babel": "^6.16.0",
"ember-cli-bootstrap-datepicker": "^0.6.1",
"ember-cli-browser-navigation-button-test-helper": "^0.1.1",
"ember-cli-chart": "^3.3.1",
"ember-cli-clipboard": "^0.8.0",
@ -63,6 +62,8 @@
"ember-maybe-import-regenerator": "^0.1.6",
"ember-moment": "^7.8.0",
"ember-page-title": "^5.0.0",
"ember-power-calendar": "^0.10.0",
"ember-power-calendar-moment": "^0.1.4",
"ember-radio-buttons": "^4.0.1",
"ember-resolver": "^5.0.1",
"ember-route-action-helper": "^2.0.6",

View file

@ -1,4 +1,4 @@
import { currentURL, currentRouteName } from '@ember/test-helpers';
import { currentURL, currentRouteName, findAll } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
@ -86,7 +86,7 @@ module('Acceptance | create a poll', function(hooks) {
'status bar has correct items disabled (options)'
);
await pageCreateOptions.dateOptions(dates);
await pageCreateOptions.selectDates(dates);
await pageCreateOptions.next();
assert.equal(currentRouteName(), 'create.options-datetime');
assert.equal(
@ -329,12 +329,12 @@ module('Acceptance | create a poll', function(hooks) {
.next();
assert.equal(currentRouteName(), 'create.options');
await pageCreateOptions.dateOptions(days);
await pageCreateOptions.selectDates(days);
await pageCreateOptions.next();
assert.equal(currentRouteName(), 'create.options-datetime');
assert.deepEqual(
pageCreateOptionsDatetime.days().labels,
days.map((day) => day.format(dayFormat)),
days.map((day) => moment(day).format(dayFormat)),
'time inputs having days as label'
);
@ -389,7 +389,7 @@ module('Acceptance | create a poll', function(hooks) {
.next();
assert.equal(currentRouteName(), 'create.options');
await pageCreateOptions.dateOptions([ day ]);
await pageCreateOptions.selectDates([ day ]);
await pageCreateOptions.next();
assert.equal(currentRouteName(), 'create.options-datetime');
assert.deepEqual(
@ -447,7 +447,7 @@ module('Acceptance | create a poll', function(hooks) {
.next();
assert.equal(currentRouteName(), 'create.options');
await pageCreateOptions.dateOptions([ day ]);
await pageCreateOptions.selectDates([ day ]);
await pageCreateOptions.next();
assert.equal(currentRouteName(), 'create.options-datetime');
assert.deepEqual(
@ -501,7 +501,7 @@ module('Acceptance | create a poll', function(hooks) {
.next();
assert.equal(currentRouteName(), 'create.options');
await pageCreateOptions.dateOptions([ day ]);
await pageCreateOptions.selectDates([ day ]);
await pageCreateOptions.next();
assert.equal(currentRouteName(), 'create.options-datetime');
assert.deepEqual(
@ -593,8 +593,8 @@ module('Acceptance | create a poll', function(hooks) {
test('create a poll and using back button (find a date)', async function(assert) {
let days = [
moment().add(1, 'day').hours(0).minutes(0).seconds(0).milliseconds(0),
moment().add(1, 'week').hours(0).minutes(0).seconds(0).milliseconds(0)
'2016-01-02',
'2016-01-13',
];
const dayFormat = moment.localeData().longDateFormat('LLLL')
.replace(
@ -613,12 +613,14 @@ module('Acceptance | create a poll', function(hooks) {
.next();
assert.equal(currentRouteName(), 'create.options');
await pageCreateOptions.dateOptions(days);
await pageCreateOptions.selectDates(
days.map((day) => new Date(day))
);
await pageCreateOptions.next();
assert.equal(currentRouteName(), 'create.options-datetime');
assert.deepEqual(
pageCreateOptionsDatetime.days().labels,
days.map((day) => day.format(dayFormat)),
days.map((day) => moment(day).format(dayFormat)),
'time inputs having days as label'
);
@ -626,8 +628,8 @@ module('Acceptance | create a poll', function(hooks) {
await backButton();
assert.equal(currentRouteName(), 'create.options');
assert.deepEqual(
pageCreateOptions.dateOptions().map((date) => date.toISOString()),
days.map((day) => day.toISOString()),
findAll('.ember-power-calendar-day--selected').map((el) => el.dataset.date),
days,
'days are still present after back button is used'
);
@ -656,8 +658,8 @@ module('Acceptance | create a poll', function(hooks) {
assert.deepEqual(
pagePollParticipation.options().labels,
[
days[0].format(dayFormat),
days[1].hour(10).minute(0).format('LLLL')
moment(days[0]).format(dayFormat),
moment(days[1]).hour(10).minute(0).format('LLLL')
],
'options are correctly labeled'
);
@ -680,7 +682,7 @@ module('Acceptance | create a poll', function(hooks) {
.next();
assert.equal(currentRouteName(), 'create.options');
await pageCreateOptions.dateOptions([new Date()]);
await pageCreateOptions.selectDates([new Date()]);
await pageCreateOptions.next();
assert.equal(currentRouteName(), 'create.options-datetime');

View file

@ -1,66 +1,50 @@
import EmberObject from '@ember/object';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, find } from '@ember/test-helpers';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import moment from 'moment';
import jQuery from 'jquery';
import { calendarSelect } from 'ember-power-calendar/test-support';
module('Integration | Component | create options dates', function(hooks) {
setupRenderingTest(hooks);
test('it renders a ember-cli-bootstrap-datepicker component', async function(assert) {
test('it renders', async function(assert) {
this.set('options', []);
await render(hbs`{{#bs-form as |form|}}{{create-options-dates options=options form=form}}{{/bs-form}}`);
assert.dom('.days .datepicker').exists();
assert.dom('[data-test-form-element-for="days"]').exists();
});
test('bootstrap-datepicker shows dates in options', async function(assert) {
test('calendar shows existing options as selected days', async function(assert) {
let store = this.owner.lookup('service:store');
this.set('options', [
EmberObject.create({ title: '2015-01-01' }),
EmberObject.create({ title: '2015-01-02' })
store.createFragment('option', { title: '2015-01-01' }),
store.createFragment('option', { title: '2015-01-02' }),
]);
await render(hbs`{{#bs-form as |form|}}{{create-options-dates options=options form=form}}{{/bs-form}}`);
assert.equal(
jQuery(find('.days .datepicker').parentElement).datepicker('getDates')[0].toISOString(),
moment('2015-01-01').toISOString(),
'date is correct (a)'
);
assert.equal(
jQuery(find('.days .datepicker').parentElement).datepicker('getDates')[1].toISOString(),
moment('2015-01-02').toISOString(),
'date is correct (b)'
);
assert.dom('[data-test-form-element-for="days"] [data-date="2015-01-01"]')
.hasClass('ember-power-calendar-day--selected');
assert.dom('[data-test-form-element-for="days"] [data-date="2015-01-02"]')
.hasClass('ember-power-calendar-day--selected');
});
test('dates set in bootstrap-datepicker are set to options', async function(assert) {
test('options are updated with dates selected in calendar', async function(assert) {
this.set('options', []);
await render(hbs`{{#bs-form as |form|}}{{create-options-dates options=options form=form}}{{/bs-form}}`);
jQuery(find('.days .datepicker').parentElement).datepicker('setDates', [
moment('2015-01-01').toDate(),
moment('2015-01-02').toDate()
]);
assert.equal(
this.get('options.0.title'),
'2015-01-01',
'dates are correct (a)'
);
assert.equal(
this.get('options.1.title'),
'2015-01-02',
'dates are correct (b)'
await calendarSelect('[data-test-form-element-for="days"]', new Date('2015-01-01'));
await calendarSelect('[data-test-form-element-for="days"]', new Date('2015-01-02'));
assert.deepEqual(
this.get('options').map((option) => option.title),
['2015-01-01', '2015-01-02'],
'dates are correct'
);
jQuery(find('.days .datepicker').parentElement).datepicker('setDates', [
moment('2016-12-31').toDate(),
moment('2016-01-01').toDate()
]);
assert.equal(
this.get('options.firstObject.title'),
'2016-01-01',
await calendarSelect('[data-test-form-element-for="days"]', new Date('2016-12-31'));
await calendarSelect('[data-test-form-element-for="days"]', new Date('2016-01-01'));
assert.deepEqual(
this.get('options').map((option) => option.title),
['2015-01-01', '2015-01-02', '2016-01-01', '2016-12-31'],
'dates are sorted'
);
});

View file

@ -0,0 +1,15 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | inline-datepicker', function(hooks) {
setupRenderingTest(hooks);
test('it renders an ember-power-calendar', async function(assert) {
this.set('noop', () => {});
await render(hbs`{{inline-datepicker onCenterChange=noop onSelect=noop}}`);
assert.dom('.ember-power-calendar').exists();
});
});

View file

@ -1,49 +1,43 @@
import { isPresent } from '@ember/utils';
import PageObject from 'ember-cli-page-object';
import { findElementWithAssert } from 'ember-cli-page-object';
import { defaultsForCreate } from 'croodle/tests/pages/defaults';
import { hasFocus } from 'croodle/tests/pages/helpers';
const {
assign
} = Object;
let {
import {
clickable,
collection,
create,
fillable,
hasClass,
isVisible,
text
} = PageObject;
} from 'ember-cli-page-object';
import { defaultsForCreate } from 'croodle/tests/pages/defaults';
import { hasFocus } from 'croodle/tests/pages/helpers';
import { calendarSelect } from 'ember-power-calendar/test-support';
import { assign } from '@ember/polyfills';
import { isArray } from '@ember/array';
import { assert } from '@ember/debug';
import moment from 'moment';
const setBootstrapDatepicker = function(selector, options = {}) {
function selectDates(selector) {
return {
isDescriptor: true,
value(dates) {
const el = findElementWithAssert(this, selector, options).parent();
if (isPresent(dates)) {
const normalizedDates = dates.map((date) => {
if (typeof date.toDate === 'function') {
date = date.toDate();
}
date.setHours(0);
date.setMinutes(0);
date.setSeconds(0);
date.setMilliseconds(0);
return date;
});
el.datepicker('setDates', normalizedDates);
async value(dateOrMoments) {
assert(
'selectDates expects an array of date or moment objects as frist argument',
isArray(dateOrMoments) && dateOrMoments.every((dateOrMoment) => dateOrMoment instanceof Date || moment.isMoment(dateOrMoment))
)
for (let i = 0; i < dateOrMoments.length; i++) {
let dateOrMoment = dateOrMoments[i];
let date = moment.isMoment(dateOrMoment) ? dateOrMoment.toDate() : dateOrMoment;
await calendarSelect(selector, date);
}
return el.datepicker('getDates');
}
};
};
}
export default PageObject.create(assign({}, defaultsForCreate, {
dateOptions: setBootstrapDatepicker('.days .datepicker'),
export default create(assign({}, defaultsForCreate, {
selectDates: selectDates('[data-test-form-element-for="days"]'),
dateHasError: isVisible('.days.has-error'),
dateError: text('.days .help-block'),
textOptions: collection({
itemScope: '.form-group.option',
item: {

View file

@ -1,8 +1,6 @@
import { isArray } from '@ember/array';
import EmberObject from '@ember/object';
import { run } from '@ember/runloop';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { isArray } from '@ember/array';
import moment from 'moment';
module('Unit | Component | create options dates', function(hooks) {
@ -12,198 +10,147 @@ module('Unit | Component | create options dates', function(hooks) {
this.store = this.owner.lookup('service:store');
});
test('options get mapped to dates as optionsBootstrapDatepicker (used by ember-cli-bootstrap-datepicker)', function(assert) {
let controller = this.owner.factoryFor('component:create-options-dates').create();
controller.set('options', [
EmberObject.create({ title: '1945-05-09' }),
EmberObject.create({ title: '1987-05-01' }),
EmberObject.create({ title: 'non valid date string' })
]);
test('#selectedDays: options representing days are mapped correctly', function(assert) {
let values = [
'1945-05-09',
'1987-05-01',
'non valid date string',
];
let store = this.owner.lookup('service:store');
let component = this.owner.factoryFor('component:create-options-dates').create({
options: values.map((value) => store.createFragment('option', { title: value })),
});
assert.ok(
isArray(
controller.get('optionsBootstrapDatepicker')
),
"it's an array"
isArray(component.selectedDays),
'it\'s an array'
);
assert.equal(
controller.get('optionsBootstrapDatepicker.length'),
2,
component.selectedDays.length, 2,
'array length is correct'
);
assert.ok(
controller.get('optionsBootstrapDatepicker').every((el) => {
return moment.isDate(el);
component.selectedDays.every((el) => {
return moment.isMoment(el);
}),
'array elements are date objects'
'array elements are moment objects'
);
assert.equal(
controller.get('optionsBootstrapDatepicker.firstObject').toISOString(),
moment('1945-05-09').toISOString(),
'date is correct'
assert.deepEqual(
component.selectedDays.map((el) => el.format('YYYY-MM-DD')),
values.slice(0, 2),
'values are correct'
);
});
test('options having times get mapped to dates as optionsBootstrapDatepicker (used by ember-cli-bootstrap-datepicker)', function(assert) {
let controller = this.owner.factoryFor('component:create-options-dates').create();
controller.set('options', [
EmberObject.create({ title: '2014-01-01T12:00:00.00Z' }),
EmberObject.create({ title: '2015-02-02T15:00:00.00Z' }),
EmberObject.create({ title: '2015-02-02T15:00:00.00Z' }),
EmberObject.create({ title: '2016-03-03' })
]);
test('#selectedDays: options representing days with times are mapped correctly', function(assert) {
let values = [
moment('2014-01-01T12:00').toISOString(),
moment('2015-02-02T15:00').toISOString(),
moment('2015-02-02T15:00').toISOString(),
'2016-03-03',
];
let store = this.owner.lookup('service:store');
let component = this.owner.factoryFor('component:create-options-dates').create({
options: values.map((value) => store.createFragment('option', { title: value })),
});
assert.ok(
isArray(
controller.get('optionsBootstrapDatepicker')
),
"it's an array"
isArray(component.selectedDays),
'it\'s an array'
);
assert.equal(
controller.get('optionsBootstrapDatepicker.length'),
3,
component.selectedDays.length, 3,
'array length is correct'
);
assert.ok(
controller.get('optionsBootstrapDatepicker').every((el) => {
return moment.isDate(el);
}),
'array elements are date objects'
component.selectedDays.every(moment.isMoment),
'array elements are moment objects'
);
assert.deepEqual(
controller.get('optionsBootstrapDatepicker').map((option) => {
return option.toISOString();
}),
[
moment('2014-01-01').toISOString(),
moment('2015-02-02').toISOString(),
moment('2016-03-03').toISOString()
],
'date is correct'
component.selectedDays.map((day) => day.format('YYYY-MM-DD')),
['2014-01-01', '2015-02-02', '2016-03-03'],
'dates are correct'
);
});
test('options get set correctly by optionsBootstrapDatepicker (used by ember-cli-bootstrap-datepicker)', function(assert) {
let controller = this.owner.factoryFor('component:create-options-dates').create();
run(() => {
controller.set('options', []);
// dates must be in wrong order to test sorting
controller.set('optionsBootstrapDatepicker', [
moment('1918-11-09').toDate(),
moment('1917-10-25').toDate()
]);
test('action #daysSelected: new days are added in correct order', function(assert) {
// dates must be in wrong order to test sorting
let values = ['1918-11-09', '1917-10-25'];
let options = [];
let component = this.owner.factoryFor('component:create-options-dates').create({ options });
component.actions.daysSelected.bind(component)({
moment: values.map((_) => moment(_)),
});
assert.ok(isArray(options), 'options is still an array');
assert.equal(options.length, 2, 'two entries have been added');
assert.ok(
isArray(
controller.get('options')
),
'options is still an array'
);
assert.equal(
controller.get('options.length'),
2,
'array has correct length'
options.every(({ title }) => typeof title === 'string'),
'title property of options are strings'
);
assert.ok(
controller.get('options').every((option) => {
return typeof option.get('title') === 'string';
}),
'option.title is a string'
options.every(({ title }) => moment(title, 'YYYY-MM-DD', true).isValid()),
'title property of options are ISO-8601 date string without time'
);
assert.ok(
controller.get('options').every((option) => {
return moment(option.get('title'), 'YYYY-MM-DD', true).isValid();
}),
'option.title is an ISO-8601 date string without time'
);
assert.ok(
controller.get('options').findBy('title', '1918-11-09'),
'date is correct'
);
assert.equal(
controller.get('options.firstObject.title'),
'1917-10-25',
'dates are in correct order'
assert.deepEqual(
options.map(({ title }) => title), values.sort(),
'options having correct value and are sorted'
);
});
test('existing times are preserved if new days get selected', function(assert) {
let component;
run(() => {
component = this.owner.factoryFor('component:create-options-dates').create({
options: [
this.store.createFragment('option', {
title: moment('2015-01-01T11:11').toISOString()
}),
this.store.createFragment('option', {
title: moment('2015-01-01T22:22').toISOString()
}),
this.store.createFragment('option', {
title: moment('2015-06-06T08:08').toISOString()
}),
this.store.createFragment('option', {
title: '2016-01-01'
})
]
});
test('action #daysSelected: existing times are preserved if new day is selected', function(assert) {
let existing = [
moment('2015-01-01T11:11').toISOString(),
moment('2015-01-01T22:22').toISOString(),
moment('2015-06-06T08:08').toISOString(),
'2016-01-01'
];
let additional = '2016-06-06';
let merged = existing.slice();
merged.push(additional);
let store = this.owner.lookup('service:store');
let component = this.owner.factoryFor('component:create-options-dates').create({
options: existing.map((value) => store.createFragment('option', { title: value })),
});
// add another day
run(() => {
component.set('optionsBootstrapDatepicker', [
moment('2015-01-01').toDate(),
moment('2015-06-06').toDate(),
moment('2016-01-01').toDate(),
moment('2016-06-06').toDate() // new day
]);
component.actions.daysSelected.bind(component)({
moment: merged.map((_) => moment(_)),
});
assert.deepEqual(
component.get('options').map((option) => option.get('title')),
[
moment('2015-01-01T11:11').toISOString(),
moment('2015-01-01T22:22').toISOString(),
moment('2015-06-06T08:08').toISOString(),
'2016-01-01',
'2016-06-06'
],
component.options.map(({ title }) => title),
merged,
'preseve existing times if another day is added'
);
// delete a day
run(() => {
component.set('optionsBootstrapDatepicker', [
moment('2015-06-06').toDate(),
moment('2016-01-01').toDate(),
moment('2016-06-06').toDate()
]);
});
test('action #daysSelected: existing times are preserved if day gets unselected', function(assert) {
let existing = [
moment('2015-01-01T11:11').toISOString(),
moment('2015-01-01T22:22').toISOString(),
moment('2015-06-06T08:08').toISOString(),
'2016-01-01'
];
let reduced = existing.slice();
reduced.splice(2, 1);
let store = this.owner.lookup('service:store');
let component = this.owner.factoryFor('component:create-options-dates').create({
options: existing.map((value) => store.createFragment('option', { title: value })),
});
component.actions.daysSelected.bind(component)({
moment: reduced.map((_) => moment(_)),
});
assert.deepEqual(
component.get('options').map((option) => option.get('title')),
[
moment('2015-06-06T08:08').toISOString(),
'2016-01-01',
'2016-06-06'
],
component.options.map(({ title }) => title),
reduced,
'preseve existing times if a day is deleted'
);
// order if multiple days are added
run(() => {
component.set('optionsBootstrapDatepicker', [
moment('2015-06-06').toDate(),
moment('2016-01-01').toDate(),
moment('2016-06-06').toDate(),
moment('2016-12-12').toDate(),
moment('2015-01-01').toDate(),
moment('2016-03-03').toDate()
]);
});
assert.deepEqual(
component.get('options').map((option) => option.get('title')),
[
'2015-01-01',
moment('2015-06-06T08:08').toISOString(),
'2016-01-01',
'2016-03-03',
'2016-06-06',
'2016-12-12'
],
'options are in correct order after multiple days are added'
);
});
});

View file

@ -1899,13 +1899,6 @@ boom@2.x.x:
dependencies:
hoek "2.x.x"
bootstrap-datepicker@^1.7.1:
version "1.8.0"
resolved "https://registry.yarnpkg.com/bootstrap-datepicker/-/bootstrap-datepicker-1.8.0.tgz#c63513931e6f09f16ae9f11b62f32d950df3958e"
integrity sha512-213St/G8KT3mjs4qu4qwww74KWysMaIeqgq5OhrboZjIjemIpyuxlSo9FNNI5+KzpkkxkRRba+oewiRGV42B1A==
dependencies:
jquery ">=1.7.1 <4.0.0"
bootstrap-sass@^3.3.7:
version "3.4.0"
resolved "https://registry.yarnpkg.com/bootstrap-sass/-/bootstrap-sass-3.4.0.tgz#b1c330a56782347f626d31d497fa4aea16b3f99b"
@ -3452,6 +3445,13 @@ ember-array-helper@^5.0.0:
dependencies:
ember-cli-babel "^7.1.2"
ember-assign-helper@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/ember-assign-helper/-/ember-assign-helper-0.2.0.tgz#02d1b5ee6b4f9cb68036b7d19db47f2a9d74f148"
integrity sha512-WO6K24m6Wk+G1PBOMaXUtOMkzCTtt9z67SPIcAFxOVqc95hUU62wDRUdDiPa/854T5rroq5CJ7Ey4LgxorCsgg==
dependencies:
ember-cli-babel "^6.6.0"
ember-assign-polyfill@^2.0.1:
version "2.6.0"
resolved "https://registry.yarnpkg.com/ember-assign-polyfill/-/ember-assign-polyfill-2.6.0.tgz#07847e3357ee35b33f886a0b5fbec6873f6860eb"
@ -3565,7 +3565,7 @@ ember-cli-babel@^6.8.1:
ember-cli-version-checker "^2.1.2"
semver "^5.5.0"
ember-cli-babel@^7.0.0, ember-cli-babel@^7.1.2:
ember-cli-babel@^7.0.0, ember-cli-babel@^7.1.2, ember-cli-babel@^7.2.0:
version "7.2.0"
resolved "https://registry.yarnpkg.com/ember-cli-babel/-/ember-cli-babel-7.2.0.tgz#5c5bd877fb73f6fb198c878d3127ba9e18e9b8a0"
integrity sha512-vwx/AgPD7P4ebgTFJMqFovbrSNCA02UMuItlR/Il16njYjgN9ZzjUqgYxaylN7k8RF88wdJq3jrtqyMS/oOq8A==
@ -3587,16 +3587,6 @@ ember-cli-babel@^7.0.0, ember-cli-babel@^7.1.2:
ensure-posix-path "^1.0.2"
semver "^5.5.0"
ember-cli-bootstrap-datepicker@^0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/ember-cli-bootstrap-datepicker/-/ember-cli-bootstrap-datepicker-0.6.1.tgz#73ee152e9b3e4e5ff872d8c8a2ca87c544c9e8b5"
integrity sha512-imo+NTPhpDuO4WBr02iU6bAXniNqO6zilvXbp2/4lz6K3/+ilg2MRorOmEgfTIt5wLiQJk+Fvky0s2p1GmiRvA==
dependencies:
bootstrap-datepicker "^1.7.1"
broccoli-funnel "^2.0.1"
broccoli-merge-trees "^2.0.0"
ember-cli-babel "^6.6.0"
ember-cli-broccoli-sane-watcher@^2.1.1:
version "2.2.2"
resolved "https://registry.yarnpkg.com/ember-cli-broccoli-sane-watcher/-/ember-cli-broccoli-sane-watcher-2.2.2.tgz#9bb1b04ddeb2c086aecd8693cbaeca1d88dc160c"
@ -3728,7 +3718,7 @@ ember-cli-htmlbars@^2.0.1, ember-cli-htmlbars@^2.0.2, ember-cli-htmlbars@^2.0.3:
json-stable-stringify "^1.0.0"
strip-bom "^3.0.0"
ember-cli-htmlbars@^3.0.0:
ember-cli-htmlbars@^3.0.0, ember-cli-htmlbars@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/ember-cli-htmlbars/-/ember-cli-htmlbars-3.0.1.tgz#01e21f0fd05e0a6489154f26614b1041769e3e58"
integrity sha512-pyyB2s52vKTXDC5svU3IjU7GRLg2+5O81o9Ui0ZSiBS14US/bZl46H2dwcdSJAK+T+Za36ZkQM9eh1rNwOxfoA==
@ -4073,7 +4063,7 @@ ember-composable-helpers@^2.1.0:
broccoli-funnel "^1.0.1"
ember-cli-babel "^6.6.0"
ember-concurrency@^0.8.7:
ember-concurrency@^0.8.26, ember-concurrency@^0.8.7:
version "0.8.26"
resolved "https://registry.yarnpkg.com/ember-concurrency/-/ember-concurrency-0.8.26.tgz#7aeaa5c00e87903a57726823efe68787a83154b0"
integrity sha512-4GrtZdNKUMTsRbWzYwmUFXW+9pqab+78smw9Nn4ECUl1yuzIyHVcV7zKsgxgPISIGwrY6HwhgmFBCxipyyYP5w==
@ -4324,6 +4314,25 @@ ember-popper@^0.9.0:
fastboot-transform "^0.1.0"
popper.js "^1.14.1"
ember-power-calendar-moment@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/ember-power-calendar-moment/-/ember-power-calendar-moment-0.1.4.tgz#9233c11266eceba160132eff4a92772c11e13abb"
integrity sha512-TsFtxOMJi22hbIF24yemFZvdQ5PFi6TVY4mIAFrt/DROhJfn/On7+jvz9Q9gyjo1GWxOTbc722md7czstHTTuQ==
dependencies:
broccoli-funnel "^2.0.1"
ember-cli-babel "^6.6.0"
ember-cli-moment-shim "^3.7.1"
ember-power-calendar@^0.10.0:
version "0.10.0"
resolved "https://registry.yarnpkg.com/ember-power-calendar/-/ember-power-calendar-0.10.0.tgz#ac1418599f8cc11b289beacbd3a2bd986f656367"
integrity sha512-WhmtFkhAwnowV12cIartPrcAQabNZqI3Tq3H5LM2Rq2y6QgGGkEsO+PRrRPxTY/DESSUEcrL12MtkqVajtBSCg==
dependencies:
ember-assign-helper "^0.2.0"
ember-cli-babel "^7.2.0"
ember-cli-htmlbars "^3.0.1"
ember-concurrency "^0.8.26"
ember-qunit@^3.5.0:
version "3.5.3"
resolved "https://registry.yarnpkg.com/ember-qunit/-/ember-qunit-3.5.3.tgz#bfd0bff8298c78c77e870cca43fe0826e78a0d09"
@ -6399,16 +6408,16 @@ jquery-deferred@^0.3.0:
resolved "https://registry.yarnpkg.com/jquery-deferred/-/jquery-deferred-0.3.1.tgz#596eca1caaff54f61b110962b23cafea74c35355"
integrity sha1-WW7KHKr/VPYbEQlisjyv6nTDU1U=
"jquery@>=1.7.1 <4.0.0", jquery@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==
jquery@^3.2.1:
version "3.2.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.2.1.tgz#5c4d9de652af6cd0a770154a631bba12b015c787"
integrity sha1-XE2d5lKvbNCncBVKYxu6ErAVx4c=
jquery@^3.3.1:
version "3.3.1"
resolved "https://registry.yarnpkg.com/jquery/-/jquery-3.3.1.tgz#958ce29e81c9790f31be7792df5d4d95fc57fbca"
integrity sha512-Ubldcmxp5np52/ENotGxlLe6aGMvmF4R8S6tZjsP6Knsaxd/xp3Zrh50cG93lR6nPXyUFwzN3ZSOQI0wRJNdGg==
js-levenshtein@^1.1.3:
version "1.1.4"
resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.4.tgz#3a56e3cbf589ca0081eb22cd9ba0b1290a16d26e"