Use native browser APIs and Luxon instead of Moment (#612)

This commit is contained in:
Jeldrik Hanschke 2023-09-21 12:30:14 +02:00 committed by GitHub
parent 9f47286899
commit 2a4b1f8b73
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
48 changed files with 1514 additions and 817 deletions

View file

@ -4,7 +4,7 @@ import { inject as service } from '@ember/service';
import Component from '@ember/component';
import { isArray } from '@ember/array';
import { isPresent } from '@ember/utils';
import moment from 'moment';
import { DateTime } from "luxon";
@classic
export default class CreateOptionsDates extends Component {
@ -16,56 +16,60 @@ export default class CreateOptionsDates extends Component {
return this.options
// should be unique
.uniqBy('day')
// raw dates
.map(({ date }) => date)
// filter out invalid
.filter(moment.isMoment)
.filter(({ isDate }) => isDate)
// raw dates
.map(({ datetime }) => datetime)
.toArray();
}
@computed('calendarCenter')
get calendarCenterNext() {
return moment(this.calendarCenter).add(1, 'months');
return this.calendarCenter.plus({ months: 1 });
}
@action
daysSelected({ moment: newMoments }) {
daysSelected({ datetime: newDatesAsLuxonDateTime }) {
let { options } = this;
if (!isArray(newMoments)) {
if (!isArray(newDatesAsLuxonDateTime)) {
// 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);
const newDates = newDatesAsLuxonDateTime.map((dateAsLuxonDateTime) => {
return dateAsLuxonDateTime.toISODate();
});
// 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);
// array of options that represent days missing in updated selection
let removedOptions = options.filter((option) => {
return !newDates.find((newDate) => newDate === option.day);
});
// array of dates that aren't represented yet by an option
let addedDates = newDates.filter((newDate) => {
return !options.find((option) => newDate === option.day);
});
// remove options that represent deselected days
options.removeObjects(removedOptions);
// add options for newly selected days
let newOptions = addedMoments.map((moment) => {
let newOptions = addedDates.map((newDate) => {
return this.store.createFragment('option', {
title: moment.format('YYYY-MM-DD'),
title: newDate,
})
});
newOptions.forEach((newOption) => {
// options must be insert into options array at correct position
let insertBefore = options.find(({ date }) => {
if (!moment.isMoment(date)) {
let insertBefore = options.find((option) => {
if (!option.isDate) {
// ignore options that do not represent a valid date
return false;
}
return date.isAfter(newOption.date);
return option.title > newOption.title;
});
let position = isPresent(insertBefore) ? options.indexOf(insertBefore) : options.length;
options.insertAt(position, newOption);
@ -82,6 +86,6 @@ export default class CreateOptionsDates extends Component {
super.init(arguments);
let { selectedDays } = this;
this.set('calendarCenter', selectedDays.length >= 1 ? selectedDays[0] : moment());
this.set('calendarCenter', selectedDays.length >= 1 ? selectedDays[0] : DateTime.local());
}
}

View file

@ -88,7 +88,7 @@ export default class CreateOptionsDatetime extends Component.extend(modelValidat
items.slice(1)
);
// set title as date without time
remainingOption.set('title', remainingOption.get('date').format('YYYY-MM-DD'));
remainingOption.set('title', remainingOption.day);
} else {
// adopt times of first day
if (timesForFirstDay.get('length') < items.length) {
@ -102,9 +102,9 @@ export default class CreateOptionsDatetime extends Component.extend(modelValidat
timesForFirstDay.forEach((timeOfFirstDate, index) => {
const target = items[index];
if (target === undefined) {
const basisDate = get(items[0], 'date').clone();
let [hour, minute] = timeOfFirstDate.split(':');
let dateString = basisDate.hour(hour).minute(minute).toISOString();
const basisDate = get(items[0], 'datetime');
let [hours, minutes] = timeOfFirstDate.split(':');
let dateString = basisDate.set({ hours, minutes }).toISO();
let fragment = this.store.createFragment('option', {
title: dateString
});

View file

@ -1,8 +1,6 @@
import classic from 'ember-classic-decorator';
import { classNames, tagName } from '@ember-decorators/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import { readOnly } from '@ember/object/computed';
import Component from '@ember/component';
import localesMeta from 'croodle/locales/meta';
@ -13,33 +11,21 @@ export default class LanguageSelect extends Component {
@service
intl;
@service
moment;
@service
powerCalendar;
@readOnly('intl.primaryLocale')
current;
@computed('intl.locales')
get locales() {
let currentLocale = this.intl.primaryLocale;
return Object.keys(localesMeta).map(function(locale) {
return {
id: locale,
selected: locale === currentLocale,
text: localesMeta[locale]
};
});
get currentLocale() {
return this.intl.primaryLocale;
}
change() {
let locale = this.element.options[this.element.selectedIndex].value;
get locales() {
return localesMeta;
}
change(event) {
const locale = event.target.value;
this.intl.set('locale', locale.includes('-') ? [locale, locale.split('-')[0]] : [locale]);
this.moment.changeLocale(locale);
this.powerCalendar.set('locale', locale);
if (window.localStorage) {

View file

@ -2,7 +2,7 @@ import classic from 'ember-classic-decorator';
import { classNames } from '@ember-decorators/component';
import { computed } from '@ember/object';
import { inject as service } from '@ember/service';
import { readOnly, max, mapBy, gt } from '@ember/object/computed';
import { readOnly, gt } from '@ember/object/computed';
import Component from '@ember/component';
import { copy } from '@ember/object/internals';
import { isEmpty } from '@ember/utils';
@ -13,7 +13,7 @@ export default class PollEvaluationSummary extends Component {
@service
intl;
@computed('users.[]')
@computed('poll.{answers,isFreeText,options}', 'users.[]')
get bestOptions() {
// can not evaluate answer type free text
if (this.get('poll.isFreeText')) {
@ -82,11 +82,17 @@ export default class PollEvaluationSummary extends Component {
@gt('bestOptions.length', 1)
multipleBestOptions;
@max('participationDates')
lastParticipationAt;
get lastParticipationAt() {
let lastParticipationAt = null;
@mapBy('users', 'creationDate')
participationDates;
for (const { creationDate } of this.users.toArray()) {
if (creationDate >= lastParticipationAt) {
lastParticipationAt = creationDate;
}
}
return lastParticipationAt;
}
@readOnly('users.length')
participantsCount;

View file

@ -2,7 +2,6 @@ import classic from 'ember-classic-decorator';
import { action } from '@ember/object';
import { alias } from '@ember/object/computed';
import Controller from '@ember/controller';
import moment from 'moment';
@classic
export default class CreateOptionsDatetimeController extends Controller {
@ -24,10 +23,10 @@ export default class CreateOptionsDatetimeController extends Controller {
// remove all days from options which haven't a time but there is atleast
// one option with time for that day
const daysWithTime = options.map((option) => {
if (moment(option.get('title'), 'YYYY-MM-DD', true).isValid()) {
if (!option.hasTime) {
return null;
} else {
return moment(option.get('title')).format('YYYY-MM-DD');
return option.day;
}
}).uniq().filter((option) => option !== null);
const removeObjects = options.filter((option) => {

View file

@ -8,7 +8,7 @@ import {
validator, buildValidations
}
from 'ember-cp-validations';
import moment from 'moment';
import { DateTime, Duration } from 'luxon';
const Validations = buildValidations({
anonymousUser: validator('presence', {
@ -58,9 +58,8 @@ export default class CreateSettings extends Controller.extend(Validations) {
set expirationDuration(value) {
this.set(
'model.expirationDate',
isPresent(value) ? moment().add(moment.duration(value)).toISOString(): ''
isPresent(value) ? DateTime.local().plus(Duration.fromISO(value)).toISO() : ''
);
return value;
}
@computed
@ -100,11 +99,11 @@ export default class CreateSettings extends Controller.extend(Validations) {
// set timezone if there is atleast one option with time
if (
poll.isFindADate &&
poll.options.any(({ title }) => {
return !moment(title, 'YYYY-MM-DD', true).isValid();
poll.options.any((option) => {
return option.hasTime;
})
) {
this.set('model.timezone', moment.tz.guess());
this.set('model.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
}
// save poll

View file

@ -4,7 +4,7 @@ import Controller from '@ember/controller';
import { isPresent, isEmpty } from '@ember/utils';
import { action, computed } from '@ember/object';
import { observes } from '@ember-decorators/object';
import moment from 'moment';
import { DateTime } from 'luxon';
export default class PollController extends Controller {
@service
@ -28,16 +28,6 @@ export default class PollController extends Controller {
@readOnly('intl.primaryLocale')
currentLocale;
@computed('currentLocale')
get momentLongDayFormat() {
let currentLocale = this.currentLocale;
return moment.localeData(currentLocale)
.longDateFormat('LLLL')
.replace(
moment.localeData(currentLocale).longDateFormat('LT'), '')
.trim();
}
@readOnly('model')
poll;
@ -52,7 +42,7 @@ export default class PollController extends Controller {
if (isEmpty(expirationDate)) {
return false;
}
return moment().add(2, 'weeks').isAfter(moment(expirationDate));
return DateTime.local().plus({ weeks: 2 }) >= DateTime.fromISO(expirationDate);
}
/*
@ -61,7 +51,7 @@ export default class PollController extends Controller {
@computed('poll.timezone')
get timezoneDiffers() {
let modelTimezone = this.poll.timezone;
return isPresent(modelTimezone) && moment.tz.guess() !== modelTimezone;
return isPresent(modelTimezone) && Intl.DateTimeFormat().resolvedOptions().timeZone !== modelTimezone;
}
@computed('timezoneDiffers', 'timezoneChoosen')
@ -69,7 +59,7 @@ export default class PollController extends Controller {
return this.timezoneDiffers && !this.timezoneChoosen;
}
@computed('useLocalTimezone')
@computed('poll.timezone', 'useLocalTimezone')
get timezone() {
return this.useLocalTimezone ? undefined : this.poll.timezone;
}

View file

@ -15,9 +15,6 @@ export default class PollEvaluationController extends Controller {
@service
intl;
@readOnly('pollController.momentLongDayFormat')
momentLongDayFormat;
@readOnly('model')
poll;
@ -34,7 +31,7 @@ export default class PollEvaluationController extends Controller {
* evaluates poll data
* if free text answers are allowed evaluation is disabled
*/
@computed('users.[]')
@computed('isEvaluable', 'poll.{answers,forceAnswer,options,users}', 'users.[]')
get evaluation() {
if (!this.isEvaluable) {
return [];

View file

@ -3,13 +3,12 @@ import { inject as service } from '@ember/service';
import { not, readOnly } from '@ember/object/computed';
import Controller, { inject as controller } from '@ember/controller';
import { getOwner } from '@ember/application';
import { isPresent, isEmpty } from '@ember/utils';
import { isEmpty } from '@ember/utils';
import EmberObject, { computed } from '@ember/object';
import {
validator, buildValidations
}
from 'ember-cp-validations';
import moment from 'moment';
import config from 'croodle/config/environment';
const validCollection = function(collection) {
@ -156,8 +155,6 @@ export default Controller.extend(Validations, {
isFreeText: readOnly('poll.isFreeText'),
isFindADate: readOnly('poll.isFindADate'),
momentLongDayFormat: readOnly('pollController.momentLongDayFormat'),
name: '',
options: readOnly('poll.options'),
@ -165,7 +162,7 @@ export default Controller.extend(Validations, {
poll: readOnly('model'),
pollController: controller('poll'),
possibleAnswers: computed('poll.answers', function() {
possibleAnswers: computed('labelTranslation', 'poll.answers', function() {
return this.get('poll.answers').map((answer) => {
const owner = getOwner(this);
@ -177,7 +174,7 @@ export default Controller.extend(Validations, {
if (!isEmpty(answer.get('labelTranslation'))) {
return AnswerObject.extend({
intl: service(),
label: computed('intl.locale', function() {
label: computed('intl.locale', 'labelTranslation', function() {
return this.intl.t(this.labelTranslation);
}),
labelTranslation: answer.get('labelTranslation'),
@ -192,41 +189,28 @@ export default Controller.extend(Validations, {
savingFailed: false,
selections: computed('options', 'pollController.dates', function() {
selections: computed('forceAnswer', 'isFindADate', 'isFreeText', 'options', 'pollController.dates', 'timezone', function() {
let options = this.options;
let isFindADate = this.isFindADate;
let lastDate;
let lastOption;
return options.map((option) => {
let labelValue;
let momentFormat;
let value = option.get('title');
const labelString = option.title;
const labelValue = option.isDate ? option.jsDate : option.title;
const showDate = isFindADate && (!lastOption || option.get('day') !== lastOption.get('day'));
const showTime = isFindADate && option.get('hasTime');
// format label
if (isFindADate) {
let hasTime = value.length > 10; // 'YYYY-MM-DD'.length === 10
let timezone = this.timezone;
let date = isPresent(timezone) ? moment.tz(value, timezone) : moment(value);
if (hasTime && lastDate && date.format('YYYY-MM-DD') === lastDate.format('YYYY-MM-DD')) {
labelValue = value;
// do not repeat dates for different times
momentFormat = 'LT';
} else {
labelValue = value;
momentFormat = hasTime ? 'LLLL' : 'day';
lastDate = date;
}
} else {
labelValue = value;
}
lastOption = option;
// https://github.com/offirgolan/ember-cp-validations#basic-usage---objects
// To lookup validators, container access is required which can cause an issue with Object creation
// if the object is statically imported. The current fix for this is as follows.
let owner = getOwner(this);
return SelectionObject.create(owner.ownerInjection(), {
labelString,
labelValue,
momentFormat,
showDate,
showTime,
// forceAnswer and isFreeText must be included in model
// cause otherwise validations can't depend on it

View file

@ -0,0 +1,19 @@
import Helper from '@ember/component/helper';
import { DateTime } from 'luxon';
import { inject as service } from '@ember/service';
export default class FormatDateRelativeHelper extends Helper {
@service intl;
compute([date]) {
if (date instanceof Date) {
date = date.toISOString();
}
return DateTime.fromISO(date).toRelative({
locale: this.intl.primaryLocale,
padding: 1000,
});
}
}

View file

@ -5,14 +5,12 @@ export default {
name: 'i18n',
initialize(appInstance) {
let intl = appInstance.lookup('service:intl');
let moment = appInstance.lookup('service:moment');
let powerCalendar = appInstance.lookup('service:power-calendar');
let availableLocales = Object.keys(localesMeta);
let locale = getLocale(availableLocales);
intl.set('locale', locale.includes('-') ? [locale, locale.split('-')[0]] : [locale]);
moment.changeLocale(locale);
powerCalendar.set('local', locale);
}
};

View file

@ -5,7 +5,7 @@ import { readOnly } from '@ember/object/computed';
import { attr } from '@ember-data/model';
import { assert } from '@ember/debug';
import { isEmpty } from '@ember/utils';
import moment from 'moment';
import { DateTime } from 'luxon';
import Fragment from 'ember-data-model-fragments/fragment';
import { fragmentOwner } from 'ember-data-model-fragments/attributes';
import {
@ -20,12 +20,6 @@ const Validations = buildValidations({
title: [
validator('iso8601', {
active: readOnly('model.poll.isFindADate'),
validFormats: [
'YYYY-MM-DD',
'YYYY-MM-DDTHH:mmZ',
'YYYY-MM-DDTHH:mm:ssZ',
'YYYY-MM-DDTHH:mm:ss.SSSZ'
],
}),
validator('presence', {
presence: true,
@ -73,103 +67,62 @@ export default class Option extends Fragment.extend(Validations) {
// working based on a property of the model.
isPartiallyFilled = false;
@computed('title')
get date() {
const allowedFormats = [
'YYYY-MM-DD',
'YYYY-MM-DDTHH:mm:ss.SSSZ'
];
const value = this.title;
if (isEmpty(value)) {
get datetime() {
const { title } = this;
if (isEmpty(title)) {
return null;
}
const format = allowedFormats.find((f) => {
// if format length does not match value length
// string can't be in this format
return f.length === value.length && moment(value, f, true).isValid();
});
if (isEmpty(format)) {
return null;
}
return moment(value, format, true);
return DateTime.fromISO(title);
}
get isDate() {
const { datetime } = this;
return datetime !== null && datetime.isValid;
}
@computed('date')
get day() {
const date = this.date;
if (!moment.isMoment(date)) {
return null;
}
return date.format('YYYY-MM-DD');
}
@computed('date', 'intl.primaryLocale')
get dayFormatted() {
let date = this.date;
if (!moment.isMoment(date)) {
if (!this.isDate) {
return null;
}
const locale = this.get('intl.primaryLocale');
const format = moment.localeData(locale)
.longDateFormat('LLLL')
.replace(
moment.localeData(locale).longDateFormat('LT'), '')
.trim();
// momentjs object caches the locale on creation
if (date.locale() !== locale) {
// we clone the date to allow adjusting timezone without changing the object
date = date.clone();
date.locale(locale);
}
return date.format(format);
return this.datetime.toISODate();
}
get jsDate() {
return this.datetime.toJSDate();
}
@computed('title')
get hasTime() {
return moment.isMoment(this.date) &&
this.title.length === 'YYYY-MM-DDTHH:mm:ss.SSSZ'.length;
return this.isDate &&
this.title.length >= 'YYYY-MM-DDTHH:mm'.length;
}
@computed('date')
get time() {
const date = this.date;
if (!moment.isMoment(date)) {
return null;
}
// verify that value is an ISO 8601 date string containg time
// testing length is faster than parsing with moment
const value = this.title;
if (value.length !== 'YYYY-MM-DDTHH:mm:ss.SSSZ'.length) {
if (!this.isDate || !this.hasTime) {
return null;
}
return date.format('HH:mm');
return this.datetime.toISOTime().substring(0, 5);
}
set time(value) {
let date = this.date;
assert(
'can not set a time if current value is not a valid date',
moment.isMoment(date)
this.isDate
);
// set time to undefined if value is false
if (isEmpty(value)) {
this.set('title', date.format('YYYY-MM-DD'));
return value;
this.set('title', this.day);
return;
}
if (!moment(value, 'HH:mm', true).isValid()) {
return value;
const datetime = DateTime.fromISO(value);
if (!datetime.isValid) {
return;
}
const [ hour, minute ] = value.split(':');
this.set('title', date.hour(hour).minute(minute).toISOString());
return value;
this.set('title', this.datetime.set({ hours: datetime.hour, minutes: datetime.minute }).toISO());
}
init() {

View file

@ -72,7 +72,7 @@ export default class Poll extends Model {
/*
* computed properties
*/
@computed('options.[]')
@computed('isMakeAPoll', 'options.[]')
get hasTimes() {
if (this.isMakeAPoll) {
return false;

View file

@ -3,7 +3,7 @@ import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';
import config from 'croodle/config/environment';
import answersForAnswerType from 'croodle/utils/answers-for-answer-type';
/* global moment */
import { DateTime } from 'luxon';
@classic
export default class CreateRoute extends Route {
@ -30,7 +30,7 @@ export default class CreateRoute extends Route {
anonymousUser: false,
pollType: 'FindADate',
timezone: null,
expirationDate: moment().add(3, 'month').toISOString(),
expirationDate: DateTime.local().plus({ months: 3 }).toISO(),
version: config.APP.version,
});
}

View file

@ -6,20 +6,20 @@
as |el|
>
<div
class="
form-control
cr-h-auto
cr-pr-validation
{{if (eq el.validation "error") "is-invalid"}}
{{if (eq el.validation "success") "is-valid"}}
"
class="form-control cr-h-auto cr-pr-validation
{{if (eq el.validation 'error') 'is-invalid'}}
{{if (eq el.validation 'success') 'is-valid'}}
"
>
<div class="row">
<div class="col-12 col-md-6">
<InlineDatepicker
@center={{this.calendarCenter}}
@selectedDays={{this.selectedDays}}
@onCenterChange={{action (mut this.calendarCenter) value="moment"}}
@onCenterChange={{action
(mut this.calendarCenter)
value="datetime"
}}
@onSelect={{action "daysSelected"}}
/>
</div>
@ -27,7 +27,10 @@
<InlineDatepicker
@center={{this.calendarCenterNext}}
@selectedDays={{this.selectedDays}}
@onCenterChange={{action (mut this.calendarCenter) value="moment"}}
@onCenterChange={{action
(mut this.calendarCenter)
value="datetime"
}}
@onSelect={{action "daysSelected"}}
/>
</div>

View file

@ -27,7 +27,16 @@
data-test-day={{date.day}}
>
<form.element
@label={{date.dayFormatted}}
{{!
TODO: Simplify to dateStyle="full" after upgrading to Ember Intl v6
}}
@label={{format-date
date.jsDate
weekday="long"
day="numeric"
month="long"
year="numeric"
}}
{{!
show label only if it differ from label before
Nested-helpers are called first and object-at requires a positive integer
@ -35,7 +44,12 @@
Therefore we pass in array length if index is null. Cause index starting
by zero there can't be any element with an index === array.length.
}}
@invisibleLabel={{eq date.dayFormatted (get (object-at (if index (sub index 1) @dates.length) @dates) "dayFormatted")}}
@invisibleLabel={{eq
date.day
(get
(object-at (if index (sub index 1) @dates.length) @dates) "day"
)
}}
@model={{date}}
@property="time"
class="option"
@ -47,14 +61,11 @@
@placeholder="00:00"
@type="time"
@value={{el.value}}
{{! focus input if it's the first one }}
{{autofocus enabled=(eq index 0)}}
{{! run validation for partially filled input on focusout event }}
{{on "focusout" (fn this.validateInput date)}}
{{!--
{{!
Validation for partially input field must be reset if input is cleared.
But `@onChange` is not called and `focusout` event not triggered in that
scenario. Need to listen to additional events to ensure that partially
@ -67,10 +78,9 @@
Firefox does not consider partially time input as invalid, Edge prevents
partially filling in first place and Desktop Safari as well as IE 11
do not support `<input type="time">`.
--}}
}}
{{on "focusin" (fn this.updateInputValidation date)}}
{{on "keyup" (fn this.updateInputValidation date)}}
id={{el.id}}
/>
<div class="input-group-append">
@ -81,8 +91,14 @@
{{! disable delete button if there is only one option }}
disabled={{lte @dates.length 1}}
>
<span class="oi oi-trash" title={{t "create.options.button.delete.label"}} aria-hidden="true"></span>
<span class="sr-only">{{t "create.options.button.delete.label"}}</span>
<span
class="oi oi-trash"
title={{t "create.options.button.delete.label"}}
aria-hidden="true"
></span>
<span class="sr-only">{{t
"create.options.button.delete.label"
}}</span>
</BsButton>
</div>
</div>
@ -93,8 +109,14 @@
@size="sm"
class="add cr-option-menu__button cr-option-menu__add-button float-left"
>
<span class="oi oi-plus" title={{t "create.options.button.add.label"}} aria-hidden="true"></span>
<span class="sr-only">{{t "create.options.button.add.label"}}</span>
<span
class="oi oi-plus"
title={{t "create.options.button.add.label"}}
aria-hidden="true"
></span>
<span class="sr-only">{{t
"create.options.button.add.label"
}}</span>
</BsButton>
</form.element>
</div>

View file

@ -2,7 +2,8 @@
@center={{@center}}
@selected={{@selectedDays}}
@onCenterChange={{@onCenterChange}}
@onSelect={{@onSelect}} as |calendar|
@onSelect={{@onSelect}}
as |calendar|
>
<nav class="ember-power-calendar-nav">
<button
@ -13,7 +14,7 @@
«
</button>
<div class="ember-power-calendar-nav-title">
{{moment-format calendar.center "MMMM YYYY"}}
{{format-date calendar.center month="long" year="numeric"}}
</div>
<button
type="button"

View file

@ -1,5 +1,5 @@
{{#each this.locales as |locale|}}
<option value={{locale.id}} selected={{locale.selected}}>
{{locale.text}}
{{#each-in this.locales as |localeKey localeName|}}
<option value={{localeKey}} selected={{eq localeKey this.currentLocale}}>
{{localeName}}
</option>
{{/each}}
{{/each-in}}

View file

@ -1,17 +1,23 @@
<div class="participants-table">
<table
class="table"
data-test-table-of="participants"
>
<table class="table" data-test-table-of="participants">
<thead>
{{#if this.hasTimes}}
<tr>
<th>
{{!-- column for name --}}
{{! column for name }}
</th>
{{#each this.optionsGroupedByDays as |optionGroup|}}
<th colspan={{optionGroup.items.length}}>
{{moment-format optionGroup.value @momentLongDayFormat}}
{{!
TODO: Simplify to dateStyle="full" after upgrading to Ember Intl v6
}}
{{format-date
optionGroup.value
weekday="long"
day="numeric"
month="long"
year="numeric"
}}
</th>
{{/each}}
</tr>
@ -19,16 +25,28 @@
<tr>
<th>
{{!-- column for name --}}
{{! column for name }}
</th>
{{#each this.options as |option|}}
<th>
{{#if (and this.isFindADate this.hasTimes)}}
{{#if option.hasTime}}
{{moment-format option.date "LT"}}
{{!
TODO: Simplify to timeStyle="short" after upgrading to Ember Intl v6
}}
{{format-date option.date hour="numeric" minute="numeric"}}
{{/if}}
{{else if this.isFindADate}}
{{moment-format option.date @momentLongDayFormat}}
{{!
TODO: Simplify to dateStyle="full" after upgrading to Ember Intl v6
}}
{{format-date
option.jsDate
weekday="long"
day="numeric"
month="long"
year="numeric"
}}
{{else}}
{{option.title}}
{{/if}}
@ -40,9 +58,7 @@
<tbody>
{{#each this.usersSorted as |user|}}
<tr data-test-participant={{user.id}}>
<td
data-test-value-for="name"
>
<td data-test-value-for="name">
{{user.name}}
</td>
{{#each this.options as |option index|}}

View file

@ -1,23 +1,32 @@
{{!--
{{!
There must not be a line break between option text and "</strong>." cause otherwise
we will see a space between option string and following dot.
--}}
}}
{{#if @isFindADate}}
{{! Need to disable block indentation rule cause there shouldn't be a space between date and dot }}
{{! template-lint-disable block-indentation }}
<strong class="best-option-value">
{{moment-format
@evaluationBestOption.option.title
(if @evaluationBestOption.option.hasTime "LLLL" @momentLongDayFormat)
locale=@currentLocale
timeZone=@timezone
{{!
TODO: Simplify to dateStyle="full" and timeStyle="short" after upgrading to Ember Intl v6
}}
{{format-date
@evaluationBestOption.option.jsDate
weekday="long"
day="numeric"
month="long"
year="numeric"
hour=(if @evaluationBestOption.option.hasTime "numeric" undefined)
minute=(if @evaluationBestOption.option.hasTime "numeric" undefined)
timeZone=(if @timeZone @timeZone undefined)
}}</strong>.
{{! template-lint-enable block-indentation }}
{{else}}
<strong class="best-option-value">{{@evaluationBestOption.option.title}}</strong>.
<strong
class="best-option-value"
>{{@evaluationBestOption.option.title}}</strong>.
{{/if}}
<br>
<br />
{{#if @isFindADate}}
{{#if @evaluationBestOption.answers.yes}}

View file

@ -27,8 +27,7 @@
@currentLocale={{this.currentLocale}}
@evaluationBestOption={{evaluationBestOption}}
@isFindADate={{@poll.isFindADate}}
@momentLongDayFormat={{@momentLongDayFormat}}
@timezone={{@timezone}}
@timeZone={{@timeZone}}
/>
</li>
{{/each}}
@ -38,8 +37,7 @@
@currentLocale={{this.currentLocale}}
@evaluationBestOption={{this.bestOptions.firstObject}}
@isFindADate={{@poll.isFindADate}}
@momentLongDayFormat={{@momentLongDayFormat}}
@timezone={{@timezone}}
@timeZone={{@timeZone}}
/>
{{/if}}
</p>
@ -47,6 +45,6 @@
<p class="last-participation">
{{t
"poll.evaluation.lastParticipation"
ago=(moment-from-now this.lastParticipationAt locale=this.currentLocale timezone=@timezone)
ago=(format-date-relative this.lastParticipationAt)
}}
</p>

View file

@ -9,17 +9,39 @@
<p class="description">{{poll.description}}</p>
<p class="dates">
<span class="creationDate">
{{!
TODO: Simplify to dateStyle="full" and timeStyle="short" after upgrading to Ember Intl v6
}}
{{t
"poll.created-date"
date=(moment-format poll.creationDate "LLLL" locale=this.currentLocale)
date=(format-date
poll.creationDate
weekday="long"
day="numeric"
month="long"
year="numeric"
hour="numeric"
minute="numeric"
)
}}
</span>
{{#if poll.expirationDate}}
<br>
<br />
<span class="expirationDate">
{{!
TODO: Simplify to dateStyle="full" and timeStyle="short" after upgrading to Ember Intl v6
}}
{{t
"poll.expiration-date"
date=(moment-format poll.expirationDate "LLLL" locale=this.currentLocale)
date=(format-date
poll.expirationDate
weekday="long"
day="numeric"
month="long"
year="numeric"
hour="numeric"
minute="numeric"
)
}}
</span>
{{/if}}
@ -40,7 +62,11 @@
class="btn btn-secondary cr-poll-link__copy-btn btn-sm"
>
{{t "poll.link.copy-label"}}&nbsp;
<span class="oi oi-clipboard" title={{t "poll.link.copy-label"}} aria-hidden="true"></span>
<span
class="oi oi-clipboard"
title={{t "poll.link.copy-label"}}
aria-hidden="true"
></span>
</CopyButton>
</p>
<small class="text-muted">
@ -56,7 +82,7 @@
<BsAlert @type="warning" class="expiration-warning">
{{t
"poll.expiration-date-warning"
timeToNow=(moment-from-now poll.expirationDate locale=this.currentLocale)
timeToNow=(format-date-relative poll.expirationDate)
}}
</BsAlert>
</div>
@ -65,12 +91,24 @@
<div class="box">
<ul class="nav nav-tabs" role="tablist">
<LinkTo @route="poll.participation" @model={{poll}} @tagName="li" @activeClass="active" class="participation nav-item">
<LinkTo
@route="poll.participation"
@model={{poll}}
@tagName="li"
@activeClass="active"
class="participation nav-item"
>
<LinkTo @route="poll.participation" @model={{poll}} class="nav-link">
{{t "poll.tab-title.participation"}}
</LinkTo>
</LinkTo>
<LinkTo @route="poll.evaluation" @model={{poll}} @tagName="li" @activeClass="active" class="evaluation nav-item">
<LinkTo
@route="poll.evaluation"
@model={{poll}}
@tagName="li"
@activeClass="active"
class="evaluation nav-item"
>
<LinkTo @route="poll.evaluation" @model={{poll}} class="nav-link">
{{t "poll.tab-title.evaluation"}}
</LinkTo>

View file

@ -1,16 +1,8 @@
{{#let @model as |poll|}}
{{#if this.isEvaluable}}
<PollEvaluationSummary
@momentLongDayFormat={{this.momentLongDayFormat}}
@poll={{poll}}
@timezone={{this.timezone}}
/>
<PollEvaluationSummary @poll={{poll}} @timeZone={{this.timezone}} />
{{/if}}
<h3>{{t "poll.evaluation.participantTable"}}</h3>
<PollEvaluationParticipantsTable
@momentLongDayFormat={{this.momentLongDayFormat}}
@poll={{poll}}
@timezone={{this.timezone}}
/>
<PollEvaluationParticipantsTable @poll={{poll}} @timeZone={{this.timezone}} />
{{/let}}

View file

@ -25,12 +25,20 @@
{{#if this.isFreeText}}
<form.element
@controlType="text"
@label={{if this.isFindADate
(moment-format
{{!
TODO: Simplify date formating to dateStyle="full" and timeStyle="short" after upgrading to Ember Intl v6
}}
@label={{if
this.isFindADate
(format-date
selection.labelValue
(if (eq selection.momentFormat "day") this.momentLongDayFormat selection.momentFormat)
locale=this.currentLocale
timeZone=this.timezone
weekday=(if selection.showDate "long" undefined)
day=(if selection.showDate "numeric" undefined)
month=(if selection.showDate "long" undefined)
year=(if selection.showDate "numeric" undefined)
hour=(if selection.showTime "numeric" undefined)
minute=(if selection.showTime "numeric" undefined)
timeZone=(if this.timezone this.timezone undefined)
)
selection.labelValue
}}
@ -39,12 +47,20 @@
/>
{{else}}
<form.element
@label={{if this.isFindADate
(moment-format
{{!
TODO: Simplify date formating to dateStyle="full" and timeStyle="short" after upgrading to Ember Intl v6
}}
@label={{if
this.isFindADate
(format-date
selection.labelValue
(if (eq selection.momentFormat "day") this.momentLongDayFormat selection.momentFormat)
locale=this.currentLocale
timeZone=this.timezone
weekday=(if selection.showDate "long" undefined)
day=(if selection.showDate "numeric" undefined)
month=(if selection.showDate "long" undefined)
year=(if selection.showDate "numeric" undefined)
hour=(if selection.showTime "numeric" undefined)
minute=(if selection.showTime "numeric" undefined)
timeZone=(if this.timezone this.timezone undefined)
)
selection.labelValue
}}
@ -52,24 +68,30 @@
@property="value"
@showValidationOn="change"
@useIcons={{false}}
data-test-form-element={{concat "option-" selection.labelValue}}
data-test-form-element={{concat "option-" selection.labelString}}
as |el|
>
{{#each this.possibleAnswers as |possibleAnswer|}}
<div class="radio custom-control custom-radio custom-control-inline {{possibleAnswer.type}}">
<div
class="radio custom-control custom-radio custom-control-inline
{{possibleAnswer.type}}"
>
<input
class="custom-control-input
{{if (eq el.validation "success") "is-valid"}}
{{if (eq el.validation "error") "is-invalid"}}
"
{{if (eq el.validation 'success') 'is-valid'}}
{{if (eq el.validation 'error') 'is-invalid'}}
"
type="radio"
value={{possibleAnswer.type}}
checked={{eq possibleAnswer.type el.value}}
onchange={{action (mut el.value) possibleAnswer.type}}
id={{concat el.id "_" possibleAnswer.type}}
name={{selection.labelValue}}
/>
<label
class="custom-control-label"
for={{concat el.id "_" possibleAnswer.type}}
>
<label class="custom-control-label" for={{concat el.id "_" possibleAnswer.type}}>
{{possibleAnswer.label}}
</label>
</div>
@ -81,7 +103,10 @@
<div class="row cr-steps-bottom-nav">
<div class="col-md-8 offset-md-4">
<SaveButton @isPending={{form.isSubmitting}} data-test-button="submit" />
<SaveButton
@isPending={{form.isSubmitting}}
data-test-button="submit"
/>
</div>
</div>
</BsForm>
@ -94,17 +119,12 @@
data-test-modal="saving-failed"
as |modal|
>
<modal.header
@closeButton={{false}}
@title={{t "modal.save-retry.title"}}
/>
<modal.header @closeButton={{false}} @title={{t "modal.save-retry.title"}} />
<modal.body>
<p>{{t "modal.save-retry.text"}}</p>
</modal.body>
<modal.footer>
<BsButton
@onClick={{action modal.close}}
>
<BsButton @onClick={{action modal.close}}>
{{t "action.abort"}}
</BsButton>
<BsButton

View file

@ -1,21 +1,10 @@
import classic from 'ember-classic-decorator';
import { isArray } from '@ember/array';
import { isEmpty } from '@ember/utils';
import { assert } from '@ember/debug';
import BaseValidator from 'ember-cp-validations/validators/base';
import moment from 'moment';
import { DateTime } from 'luxon';
@classic
export default class Iso8601Validator extends BaseValidator {
validate(value, options = {}) {
assert(
'options.validFormats must not be set or an array of momentJS format strings',
isEmpty(options.validFormats) || isArray(options.validFormats)
);
let valid;
const validFormats = isEmpty(options.validFormats) ? ['YYYY-MM-DDTHH:mm:ss.SSSZ'] : options.validFormats;
if (
options.active === false ||
(typeof options.active === 'function' && options.active() === false)
@ -25,9 +14,7 @@ export default class Iso8601Validator extends BaseValidator {
options.value = value;
valid = validFormats.any((validFormat) => {
return moment(value, validFormat, true).isValid();
});
const valid = DateTime.fromISO(value).isValid;
if (valid) {
return true;

View file

@ -1,7 +1,7 @@
import classic from 'ember-classic-decorator';
import { isEmpty } from '@ember/utils';
import BaseValidator from 'ember-cp-validations/validators/base';
import moment from 'moment';
import { DateTime } from 'luxon';
@classic
export default class TimeValidator extends BaseValidator {
@ -14,15 +14,15 @@ export default class TimeValidator extends BaseValidator {
options.value = value;
if (options.allowEmpty && isEmpty(value)) {
return true;
if (isEmpty(value)) {
return options.allowEmpty === true ? true : this.createErrorMessage('time', value, options);
}
if (!isEmpty(value) && typeof value.trim === 'function') {
if (typeof value.trim === 'function') {
value = value.trim();
}
valid = moment(value, 'H:mm', true).isValid();
valid = DateTime.fromFormat(value, 'H:mm').isValid;
if (valid && value !== '24:00') {
return true;

View file

@ -34,11 +34,6 @@ module.exports = function(environment) {
'media-src': "'none'",
},
contentSecurityPolicyMeta: true,
moment: {
includeLocales: ['ca', 'de', 'en-gb', 'es', 'fr', 'it', 'nb'],
includeTimezone: 'subset'
},
};
if (environment === 'development') {

View file

@ -29,7 +29,7 @@
"babel-eslint": "^10.1.0",
"bootstrap": "^4.3.1",
"broccoli-asset-rev": "^3.0.0",
"ember-auto-import": "^1.6.0",
"ember-auto-import": "^2.0.0",
"ember-awesome-macros": "^6.0.0",
"ember-bootstrap": "^3.0.0",
"ember-bootstrap-cp-validations": "^2.0.0",
@ -39,7 +39,7 @@
"ember-cli-app-version": "^4.0.0",
"ember-cli-babel": "^7.21.0",
"ember-cli-browser-navigation-button-test-helper": "^0.3.0",
"ember-cli-browserstack": "^1.0.0",
"ember-cli-browserstack": "^2.0.0",
"ember-cli-bundlesize": "^0.3.0",
"ember-cli-clipboard": "^0.16.0",
"ember-cli-content-security-policy": "^1.0.0",
@ -49,7 +49,6 @@
"ember-cli-htmlbars": "^5.2.0",
"ember-cli-inject-live-reload": "^2.0.2",
"ember-cli-mirage": "^2.0.0",
"ember-cli-moment-shim": "jelhan/ember-cli-moment-shim#6eabccf0bee2a3840c3024c5d526bc9c349fa13f",
"ember-cli-page-object": "^1.11.0",
"ember-cli-sass": "^10.0.0",
"ember-cli-sri": "^2.1.1",
@ -66,10 +65,9 @@
"ember-math-helpers": "^2.8.1",
"ember-maybe-import-regenerator": "^0.1.6",
"ember-modifier": "^2.1.1",
"ember-moment": "^8.0.0",
"ember-page-title": "^6.0.0",
"ember-power-calendar": "^0.19.0",
"ember-power-calendar-moment": "^0.1.4",
"ember-power-calendar-luxon": "^0.4.0",
"ember-qunit": "^4.6.0",
"ember-resolver": "^8.0.0",
"ember-source": "~3.20.2",
@ -89,7 +87,8 @@
"release-it": "^16.0.0",
"release-it-lerna-changelog": "^5.0.0",
"sass": "^1.19.0",
"sjcl": "^1.0.8"
"sjcl": "^1.0.8",
"webpack": "^5.0.0"
},
"engines": {
"node": "16.* || >=18"

View file

@ -58,7 +58,7 @@ module.exports = {
'--os',
'OS X',
'--osv',
'Mojave',
'Ventura',
'--b',
'safari',
'--bv',
@ -76,7 +76,7 @@ module.exports = {
'--os',
'OS X',
'--osv',
'Catalina',
'Ventura',
'--b',
'safari',
'--bv',

View file

@ -4,7 +4,7 @@ import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { setupIntl, t } from 'ember-intl/test-support';
import { setupBrowserNavigationButtons, backButton } from 'ember-cli-browser-navigation-button-test-helper/test-support';
import moment from 'moment';
import { DateTime } from 'luxon';
import pageCreateIndex from 'croodle/tests/pages/create/index';
import pageCreateMeta from 'croodle/tests/pages/create/meta';
import pageCreateOptions from 'croodle/tests/pages/create/options';
@ -28,8 +28,8 @@ module('Acceptance | create a poll', function(hooks) {
test('create a default poll', async function(assert) {
const dates = [
moment().add(1, 'day'),
moment().add(1, 'week')
DateTime.now().plus({ days: 1 }),
DateTime.now().plus({ weeks: 1 }),
];
await pageCreateIndex.visit();
@ -174,13 +174,9 @@ module('Acceptance | create a poll', function(hooks) {
'',
'poll description is correct'
);
const dayFormat = moment.localeData().longDateFormat('LLLL')
.replace(
moment.localeData().longDateFormat('LT'), '')
.trim();
assert.deepEqual(
pagePollParticipation.options().labels,
dates.map((date) => date.format(dayFormat)),
dates.map((date) => Intl.DateTimeFormat('en-US', { dateStyle: "full" }).format(date)),
'options are correctly labeled'
);
assert.deepEqual(
@ -338,14 +334,10 @@ module('Acceptance | create a poll', function(hooks) {
});
test('create a poll with times and description', async function(assert) {
let days = [
moment().add(1, 'day'),
moment().add(1, 'week')
const days = [
DateTime.now().plus({ days: 1 }),
DateTime.now().plus({ weeks: 1 }),
];
const dayFormat = moment.localeData().longDateFormat('LLLL')
.replace(
moment.localeData().longDateFormat('LT'), '')
.trim();
await pageCreateIndex.visit();
await pageCreateIndex.next();
@ -362,7 +354,7 @@ module('Acceptance | create a poll', function(hooks) {
assert.equal(currentRouteName(), 'create.options-datetime');
assert.deepEqual(
pageCreateOptionsDatetime.days().labels,
days.map((day) => moment(day).format(dayFormat)),
days.map((day) => Intl.DateTimeFormat('en-US', { dateStyle: "full" }).format(day)),
'time inputs having days as label'
);
@ -392,20 +384,16 @@ module('Acceptance | create a poll', function(hooks) {
assert.deepEqual(
pagePollParticipation.options().labels,
[
days[0].hour(10).minute(0).format('LLLL'),
days[0].hour(18).minute(0).format('LT'),
days[1].hour(12).minute(0).format('LLLL')
Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(days[0].set({ hour: 10, minutes: 0 })),
Intl.DateTimeFormat('en-US', { timeStyle: "short" }).format(days[0].set({ hours: 18, minutes: 0 })),
Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(days[1].set({ hour: 12, minutes: 0 })),
],
'options are correctly labeled'
);
});
test('create a poll with only one day and multiple times', async function(assert) {
let day = moment().add(1, 'day');
const dayFormat = moment.localeData().longDateFormat('LLLL')
.replace(
moment.localeData().longDateFormat('LT'), '')
.trim();
const day = DateTime.now().plus({ days: 1 });
await pageCreateIndex.visit();
await pageCreateIndex.next();
@ -422,7 +410,7 @@ module('Acceptance | create a poll', function(hooks) {
assert.equal(currentRouteName(), 'create.options-datetime');
assert.deepEqual(
pageCreateOptionsDatetime.days().labels,
[ day.format(dayFormat) ],
[ Intl.DateTimeFormat('en-US', { dateStyle: "full" }).format(day) ],
'time inputs having days as label'
);
@ -451,19 +439,15 @@ module('Acceptance | create a poll', function(hooks) {
assert.deepEqual(
pagePollParticipation.options().labels,
[
day.hour(10).minute(0).format('LLLL'),
day.hour(18).minute(0).format('LT')
Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(day.set({ hours: 10, minutes: 0 })),
Intl.DateTimeFormat('en-US', { timeStyle: "short" }).format(day.set({ hours: 18, minutes: 0 })),
],
'options are correctly labeled'
);
});
test('create a poll with only one day (without time)', async function(assert) {
let day = moment().add(1, 'day');
const dayFormat = moment.localeData().longDateFormat('LLLL')
.replace(
moment.localeData().longDateFormat('LT'), '')
.trim();
const day = DateTime.now().plus({ days: 1 });
await pageCreateIndex.visit();
await pageCreateIndex.next();
@ -480,7 +464,7 @@ module('Acceptance | create a poll', function(hooks) {
assert.equal(currentRouteName(), 'create.options-datetime');
assert.deepEqual(
pageCreateOptionsDatetime.days().labels,
[ day.format(dayFormat) ],
[ Intl.DateTimeFormat('en-US', { dateStyle: "full" }).format(day) ],
'time inputs having days as label'
);
@ -506,18 +490,14 @@ module('Acceptance | create a poll', function(hooks) {
assert.deepEqual(
pagePollParticipation.options().labels,
[
day.format(dayFormat)
Intl.DateTimeFormat('en-US', { dateStyle: "full" }).format(day)
],
'options are correctly labeled'
);
});
test('create a poll with only one day (with time)', async function(assert) {
let day = moment().add(1, 'day');
const dayFormat = moment.localeData().longDateFormat('LLLL')
.replace(
moment.localeData().longDateFormat('LT'), '')
.trim();
const day = DateTime.now().plus({ days: 1 });
await pageCreateIndex.visit();
await pageCreateIndex.next();
@ -534,7 +514,7 @@ module('Acceptance | create a poll', function(hooks) {
assert.equal(currentRouteName(), 'create.options-datetime');
assert.deepEqual(
pageCreateOptionsDatetime.days().labels,
[ day.format(dayFormat) ],
[ Intl.DateTimeFormat('en-US', { dateStyle: "full" }).format(day) ],
'time inputs having days as label'
);
@ -561,7 +541,7 @@ module('Acceptance | create a poll', function(hooks) {
assert.deepEqual(
pagePollParticipation.options().labels,
[
day.hour(22).minute(30).format('LLLL')
Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(day.set({ hours: 22, minutes: 30 })),
],
'options are correctly labeled'
);
@ -621,13 +601,9 @@ module('Acceptance | create a poll', function(hooks) {
test('create a poll and using back button (find a date)', async function(assert) {
let days = [
moment('2016-01-02'),
moment('2016-01-13'),
DateTime.fromISO('2016-01-02'),
DateTime.fromISO('2016-01-13'),
];
const dayFormat = moment.localeData().longDateFormat('LLLL')
.replace(
moment.localeData().longDateFormat('LT'), '')
.trim();
setupBrowserNavigationButtons();
@ -641,14 +617,12 @@ module('Acceptance | create a poll', function(hooks) {
.next();
assert.equal(currentRouteName(), 'create.options');
await pageCreateOptions.selectDates(
days.map((_) => _.toDate())
);
await pageCreateOptions.selectDates(days);
await pageCreateOptions.next();
assert.equal(currentRouteName(), 'create.options-datetime');
assert.deepEqual(
pageCreateOptionsDatetime.days().labels,
days.map((_) => _.format(dayFormat)),
days.map((day) => Intl.DateTimeFormat('en-US', { dateStyle: "full" }).format(day)),
'time inputs having days as label'
);
@ -657,7 +631,7 @@ module('Acceptance | create a poll', function(hooks) {
assert.equal(currentRouteName(), 'create.options');
assert.deepEqual(
findAll('.ember-power-calendar-day--selected').map((el) => el.dataset.date),
days.map((_) => _.format('YYYY-MM-DD')),
days.map((day) => day.toISODate()),
'days are still present after back button is used'
);
@ -686,8 +660,8 @@ module('Acceptance | create a poll', function(hooks) {
assert.deepEqual(
pagePollParticipation.options().labels,
[
days[0].format(dayFormat),
days[1].clone().hour(10).minute(0).format('LLLL')
Intl.DateTimeFormat('en-US', { dateStyle: "full" }).format(days[0]),
Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(days[1].set({ hours: 10, minutes: 0 })),
],
'options are correctly labeled'
);

View file

@ -1,7 +1,6 @@
import { find, visit } from '@ember/test-helpers';
import { fillIn, find, visit } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import pageIndex from 'croodle/tests/pages/index';
module('Acceptance | i18n', function(hooks) {
hooks.beforeEach(function() {
@ -14,7 +13,7 @@ module('Acceptance | i18n', function(hooks) {
await visit('/');
assert.equal(find('.language-select').value, 'de', 'picks up locale in locale storage');
await pageIndex.locale('en');
await fillIn('.language-select', 'en');
assert.equal(find('.language-select').value, 'en');
assert.equal(
window.localStorage.getItem('locale'), 'en',

View file

@ -5,7 +5,6 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
import { setupIntl, t } from 'ember-intl/test-support';
import switchTab from 'croodle/tests/helpers/switch-tab';
import pollParticipate from 'croodle/tests/helpers/poll-participate';
import moment from 'moment';
import PollParticipationPage from 'croodle/tests/pages/poll/participation';
import PollEvaluationPage from 'croodle/tests/pages/poll/evaluation';
@ -57,9 +56,9 @@ module('Acceptance | legacy support', function(hooks) {
assert.deepEqual(
PollParticipationPage.options().labels,
[
moment('2015-12-24T17:00:00.000Z').format('LLLL'),
moment('2015-12-24T19:00:00.000Z').format('LT'),
moment('2015-12-31T22:59:00.000Z').format('LLLL')
Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-24T17:00:00.000Z')),
Intl.DateTimeFormat('en-US', { timeStyle: "short" }).format(new Date('2015-12-24T19:00:00.000Z')),
Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-31T22:59:00.000Z')),
]
);
assert.deepEqual(

View file

@ -3,9 +3,9 @@ import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
import { setupIntl, t } from 'ember-intl/test-support';import switchTab from 'croodle/tests/helpers/switch-tab';
import moment from 'moment';
import PollEvaluationPage from 'croodle/tests/pages/poll/evaluation';
import { assign } from '@ember/polyfills';
import { DateTime } from 'luxon';
module('Acceptance | view evaluation', function(hooks) {
hooks.beforeEach(function() {
@ -32,7 +32,7 @@ module('Acceptance | view evaluation', function(hooks) {
test('evaluation is correct for FindADate', async function(assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let user1 = this.server.create('user', {
creationDate: '2015-01-01T00:00:00.000Z',
creationDate: DateTime.local().minus({ months: 8, weeks: 3 }).toISO(),
encryptionKey,
name: 'Maximilian',
selections: [
@ -51,7 +51,7 @@ module('Acceptance | view evaluation', function(hooks) {
]
});
let user2 = this.server.create('user', {
creationDate: '2015-08-01T00:00:00.000Z',
creationDate: DateTime.local().minus({ months: 3, weeks: 2 }).toISO(),
encryptionKey,
name: 'Peter',
selections: [
@ -108,7 +108,7 @@ module('Acceptance | view evaluation', function(hooks) {
assert.equal(
find('.last-participation').textContent.trim(),
t('poll.evaluation.lastParticipation', {
ago: moment('2015-08-01T00:00:00.000Z').from()
ago: '3 months ago'
}).toString(),
'last participation is evaluated correctly'
);
@ -118,7 +118,7 @@ module('Acceptance | view evaluation', function(hooks) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let usersData = [
{
creationDate: '2015-01-01T00:00:00.000Z',
creationDate: DateTime.local().minus({ weeks: 5 }).toISO(),
encryptionKey,
name: 'Maximilian',
selections: [
@ -137,7 +137,7 @@ module('Acceptance | view evaluation', function(hooks) {
]
},
{
creationDate: '2015-08-01T00:00:00.000Z',
creationDate: DateTime.local().minus({ days: 3 }).toISO(),
encryptionKey,
name: 'Peter',
selections: [
@ -215,7 +215,7 @@ module('Acceptance | view evaluation', function(hooks) {
assert.equal(
find('.last-participation').textContent.trim(),
t('poll.evaluation.lastParticipation', {
ago: moment('2015-08-01T00:00:00.000Z').from()
ago: '3 days ago'
}).toString(),
'last participation is evaluated correctly'
);

View file

@ -5,7 +5,7 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
import switchTab from 'croodle/tests/helpers/switch-tab';
import pageParticipation from 'croodle/tests/pages/poll/participation';
import pageEvaluation from 'croodle/tests/pages/poll/evaluation';
import moment from 'moment';
import { DateTime } from 'luxon';
import { triggerCopySuccess } from 'ember-cli-clipboard/test-support';
module('Acceptance | view poll', function(hooks) {
@ -42,7 +42,7 @@ module('Acceptance | view poll', function(hooks) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
encryptionKey,
expirationDate: moment().add(1, 'week')
expirationDate: DateTime.local().plus({ weeks: 1 }).toISO(),
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
@ -73,18 +73,18 @@ module('Acceptance | view poll', function(hooks) {
test('view a poll with dates and times', async function(assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let timezone = moment.tz.guess();
let timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ;
let poll = this.server.create('poll', {
encryptionKey,
expirationDate: moment().add(1, 'year'),
expirationDate: DateTime.local().plus({ years: 1 }).toISO(),
isDateTime: true,
options: [
// need to parse the date with moment cause Safari's Date.parse()
// need to parse the date with luxon cause Safari's Date.parse()
// implementation treats a data-time string without explicit
// time zone as UTC rather than local time
{ title: moment('2015-12-12T11:11:00').toISOString() },
{ title: moment('2015-12-12T13:13:00').toISOString() },
{ title: moment('2016-01-01T11:11:00').toISOString() }
{ title: DateTime.fromISO('2015-12-12T11:11:00').toISO() },
{ title: DateTime.fromISO('2015-12-12T13:13:00').toISO() },
{ title: DateTime.fromISO('2016-01-01T11:11:00').toISO() }
],
timezone
});
@ -94,11 +94,11 @@ module('Acceptance | view poll', function(hooks) {
pageParticipation.options().labels,
[
// full date
'Saturday, December 12, 2015 11:11 AM',
'Saturday, December 12, 2015 at 11:11 AM',
// only time cause day is repeated
'1:13 PM',
// full date cause day changed
'Friday, January 1, 2016 11:11 AM',
'Friday, January 1, 2016 at 11:11 AM',
]
);
assert.notOk(
@ -109,7 +109,7 @@ module('Acceptance | view poll', function(hooks) {
test('view a poll while timezone differs from the one poll got created in and choose local timezone', async function(assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let timezoneUser = moment.tz.guess();
let timezoneUser = Intl.DateTimeFormat().resolvedOptions().timeZone ;
let timezonePoll = timezoneUser !== 'America/Caracas' ? 'America/Caracas' : 'Europe/Moscow';
let poll = this.server.create('poll', {
encryptionKey,
@ -148,8 +148,8 @@ module('Acceptance | view poll', function(hooks) {
assert.deepEqual(
pageParticipation.options().labels,
[
moment.tz('2015-12-12T11:11:00.000Z', timezoneUser).locale('en').format('LLLL'),
moment.tz('2016-01-01T11:11:00.000Z', timezoneUser).locale('en').format('LLLL')
Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-12T11:11:00.000Z')),
Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(new Date('2016-01-01T11:11:00.000Z')),
]
);
assert.dom('[data-test-modal="choose-timezone"]').doesNotExist('modal is closed');
@ -157,13 +157,13 @@ module('Acceptance | view poll', function(hooks) {
await switchTab('evaluation');
assert.deepEqual(
pageEvaluation.preferedOptions,
[moment.tz('2015-12-12T11:11:00.000Z', timezoneUser).locale('en').format('LLLL')]
[Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-12T11:11:00.000Z'))]
);
});
test('view a poll while timezone differs from the one poll got created in and choose poll timezone', async function(assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let timezoneUser = moment.tz.guess();
let timezoneUser = Intl.DateTimeFormat().resolvedOptions().timeZone ;
let timezonePoll = timezoneUser !== 'America/Caracas' ? 'America/Caracas' : 'Europe/Moscow';
let poll = this.server.create('poll', {
encryptionKey,
@ -202,8 +202,8 @@ module('Acceptance | view poll', function(hooks) {
assert.deepEqual(
pageParticipation.options().labels,
[
moment.tz('2015-12-12T11:11:00.000Z', timezonePoll).locale('en').format('LLLL'),
moment.tz('2016-01-01T11:11:00.000Z', timezonePoll).locale('en').format('LLLL')
Intl.DateTimeFormat('en-US', { timeZone: timezonePoll, dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-12T11:11:00.000Z')),
Intl.DateTimeFormat('en-US', { timeZone: timezonePoll, dateStyle: "full", timeStyle: "short" }).format(new Date('2016-01-01T11:11:00.000Z')),
]
);
assert.dom('[data-test-modal="choose-timezone"]').doesNotExist('modal is closed');
@ -211,7 +211,7 @@ module('Acceptance | view poll', function(hooks) {
await switchTab('evaluation');
assert.deepEqual(
pageEvaluation.preferedOptions,
[moment.tz('2015-12-12T11:11:00.000Z', timezonePoll).locale('en').format('LLLL')]
[Intl.DateTimeFormat('en-US', { timeZone: timezonePoll, dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-12T11:11:00.000Z')),]
);
});

View file

@ -35,7 +35,7 @@ module('Integration | Component | create options dates', function(hooks) {
await calendarSelect('[data-test-form-element-for="days"]', new Date(2015, 0, 1));
await calendarSelect('[data-test-form-element-for="days"]', new Date(2015, 0, 2));
assert.deepEqual(
this.get('options').map((option) => option.title),
this.options.map((option) => option.title),
['2015-01-01', '2015-01-02'],
'dates are correct'
);
@ -43,7 +43,7 @@ module('Integration | Component | create options dates', function(hooks) {
await calendarSelect('[data-test-form-element-for="days"]', new Date(2016, 11, 31));
await calendarSelect('[data-test-form-element-for="days"]', new Date(2016, 0, 1));
assert.deepEqual(
this.get('options').map((option) => option.title),
this.options.map((option) => option.title),
['2015-01-01', '2015-01-02', '2016-01-01', '2016-12-31'],
'dates are sorted'
);

View file

@ -11,7 +11,7 @@ import {
triggerEvent
} from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import moment from 'moment';
import { DateTime } from 'luxon';
module('Integration | Component | create options datetime', function(hooks) {
setupRenderingTest(hooks);
@ -78,7 +78,7 @@ module('Integration | Component | create options datetime', function(hooks) {
);
assert.equal(
find('.days .form-group input').value,
moment('2015-01-01T11:11:00.000Z').format('HH:mm'),
DateTime.fromISO('2015-01-01T11:11:00.000Z').toFormat('HH:mm'),
'it has time in option as value'
);
});
@ -93,8 +93,8 @@ module('Integration | Component | create options datetime', function(hooks) {
isFindADate: true,
isMakeAPoll: false,
options: [
{ title: moment('2015-01-01T10:11').toISOString() },
{ title: moment('2015-01-01T22:22').toISOString() },
{ title: DateTime.fromISO('2015-01-01T10:11').toISO() },
{ title: DateTime.fromISO('2015-01-01T22:22').toISO() },
{ title: '2015-02-02' }
]
}));
@ -173,8 +173,8 @@ module('Integration | Component | create options datetime', function(hooks) {
isFindADate: true,
isMakeAPoll: false,
options: [
{ title: moment('2015-01-01T11:11').toISOString() },
{ title: moment('2015-01-01T22:22').toISOString() }
{ title: DateTime.fromISO('2015-01-01T11:11').toISO() },
{ title: DateTime.fromISO('2015-01-01T22:22').toISO() }
]
}));
});
@ -208,7 +208,7 @@ module('Integration | Component | create options datetime', function(hooks) {
);
assert.equal(
this.get('poll.options.firstObject.title'),
moment('2015-01-01T22:22').toISOString(),
DateTime.fromISO('2015-01-01T22:22').toISO(),
'correct option is deleted'
);
});
@ -221,7 +221,7 @@ module('Integration | Component | create options datetime', function(hooks) {
run(() => {
this.set('poll', this.store.createRecord('poll', {
options: [
{ title: moment().hour(10).minute(0).toISOString() },
{ title: DateTime.local().set({ hours: 10, minutes: 0 }).toISO() },
{ title: '2015-02-02' },
{ title: '2015-03-03' }
]
@ -254,8 +254,8 @@ module('Integration | Component | create options datetime', function(hooks) {
run(() => {
this.set('poll', this.store.createRecord('poll', {
options: [
{ title: moment().hour(10).minute(0).toISOString() },
{ title: moment().hour(22).minute(0).toISOString() },
{ title: DateTime.local().set({ hours: 10, minutes: 0 }).toISO() },
{ title: DateTime.local().set({ hours: 22, minutes: 0 }).toISO() },
{ title: '2015-02-02' },
{ title: '2015-03-03' }
]
@ -280,9 +280,9 @@ module('Integration | Component | create options datetime', function(hooks) {
isFindADate: true,
isMakeAPoll: false,
options: [
{ title: moment().hour(10).minute(0).toISOString() },
{ title: moment().add(1, 'day').hour(10).minute(0).toISOString() },
{ title: moment().add(1, 'day').hour(22).minute(0).toISOString() }
{ title: DateTime.local().set({ hours: 10, minutes: 0 }).toISO() },
{ title: DateTime.local().plus({ days: 1 }).set({ hours: 10, minutes: 0 }).toISO() },
{ title: DateTime.local().plus({ days: 1 }).set({ hours: 22, minutes: 0 }).toISO() }
]
}));
});

View file

@ -54,9 +54,9 @@ module('Integration | Component | create options', function(hooks) {
let poll;
run(() => {
poll = this.store.createRecord('poll', {
isFindADate: this.get('isFindADate'),
isDateTime: this.get('isDateTime'),
isMakeAPoll: this.get('isMakeAPoll')
isFindADate: this.isFindADate,
isDateTime: this.isDateTime,
isMakeAPoll: this.isMakeAPoll
});
});
this.set('options', poll.get('options'));
@ -96,9 +96,9 @@ module('Integration | Component | create options', function(hooks) {
let poll;
run(() => {
poll = this.store.createRecord('poll', {
isFindADate: this.get('isFindADate'),
isDateTime: this.get('isDateTime'),
isMakeAPoll: this.get('isMakeAPoll')
isFindADate: this.isFindADate,
isDateTime: this.isDateTime,
isMakeAPoll: this.isMakeAPoll
});
});
this.set('options', poll.get('options'));

View file

@ -53,7 +53,7 @@ module('Integration | Component | create options text', function(hooks) {
);
run(() => {
this.get('options').pushObject(
this.options.pushObject(
EmberObject.create({ title: 'baz' })
);
});
@ -79,7 +79,7 @@ module('Integration | Component | create options text', function(hooks) {
await fillIn(findAll('input')[0], 'baz');
assert.equal(
this.get('options')[0].get('title'),
this.options[0].get('title'),
'baz',
'option was updated'
);
@ -93,9 +93,9 @@ module('Integration | Component | create options text', function(hooks) {
let poll;
run(() => {
poll = this.store.createRecord('poll', {
isFindADate: this.get('isFindADate'),
isDateTime: this.get('isDateTime'),
isMakeAPoll: this.get('isMakeAPoll'),
isFindADate: this.isFindADate,
isDateTime: this.isDateTime,
isMakeAPoll: this.isMakeAPoll,
options: [
{ title: 'foo' },
{ title: 'bar' }
@ -125,7 +125,7 @@ module('Integration | Component | create options text', function(hooks) {
await fillIn(findAll('.form-group input')[1], 'baz')
assert.equal(
this.get('options').objectAt(1).get('title'),
this.options.objectAt(1).get('title'),
'baz',
'options are observed for new input field'
);
@ -161,7 +161,7 @@ module('Integration | Component | create options text', function(hooks) {
'correct input field is deleted'
);
assert.deepEqual(
this.get('options').map((option) => option.get('title')),
this.options.map((option) => option.get('title')),
['foo', 'baz'],
'option is updated'
);

View file

@ -0,0 +1,65 @@
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { DateTime } from 'luxon';
module('Integration | Helper | format-date-relative', function(hooks) {
setupRenderingTest(hooks);
test('it formats an ISO date to relative duration from now', async function(assert) {
this.set('date', DateTime.local().plus({ hours: 6 }));
await render(hbs`{{format-date-relative this.date}}`);
assert.dom(this.element).hasText('in 6 hours');
this.set('date', DateTime.local().plus({ weeks: 1 }));
assert.dom(this.element).hasText('in 7 days');
this.set('date', DateTime.local().plus({ weeks: 2 }));
assert.dom(this.element).hasText('in 14 days');
this.set('date', DateTime.local().plus({ weeks: 3 }));
assert.dom(this.element).hasText('in 21 days');
this.set('date', DateTime.local().plus({ months: 1 }));
assert.dom(this.element).hasText('in 1 month');
this.set('date', DateTime.local().plus({ months: 3 }));
assert.dom(this.element).hasText('in 3 months');
this.set('date', DateTime.local().plus({ months: 6 }));
assert.dom(this.element).hasText('in 6 months');
this.set('date', DateTime.local().plus({ years: 1 }));
assert.dom(this.element).hasText('in 1 year');
});
test('it formats an ISO date to relative duration to now', async function(assert) {
this.set('date', DateTime.local().minus({ hours: 6 }));
await render(hbs`{{format-date-relative this.date}}`);
assert.dom(this.element).hasText('6 hours ago');
this.set('date', DateTime.local().minus({ weeks: 1 }));
assert.dom(this.element).hasText('7 days ago');
this.set('date', DateTime.local().minus({ weeks: 2 }));
assert.dom(this.element).hasText('14 days ago');
this.set('date', DateTime.local().minus({ weeks: 3 }));
assert.dom(this.element).hasText('21 days ago');
this.set('date', DateTime.local().minus({ months: 1 }));
assert.dom(this.element).hasText('1 month ago');
this.set('date', DateTime.local().minus({ months: 3 }));
assert.dom(this.element).hasText('3 months ago');
this.set('date', DateTime.local().minus({ months: 6 }));
assert.dom(this.element).hasText('6 months ago');
this.set('date', DateTime.local().minus({ years: 1 }));
assert.dom(this.element).hasText('1 year ago');
});
});

View file

@ -13,20 +13,20 @@ 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';
import { DateTime } from 'luxon';
function selectDates(selector) {
return {
isDescriptor: true,
async value(dateOrMoments) {
async value(dateOrDateTimes) {
assert(
'selectDates expects an array of date or moment objects as frist argument',
isArray(dateOrMoments) && dateOrMoments.every((dateOrMoment) => dateOrMoment instanceof Date || moment.isMoment(dateOrMoment))
'selectDates expects an array of date or DateTime (luxon) objects as frist argument',
isArray(dateOrDateTimes) && dateOrDateTimes.every((dateOrDateTime) => dateOrDateTime instanceof Date || DateTime.isDateTime(dateOrDateTime))
)
for (let i = 0; i < dateOrMoments.length; i++) {
let dateOrMoment = dateOrMoments[i];
let date = moment.isMoment(dateOrMoment) ? dateOrMoment.toDate() : dateOrMoment;
for (let i = 0; i < dateOrDateTimes.length; i++) {
let dateOrDateTime = dateOrDateTimes[i];
let date = DateTime.isDateTime(dateOrDateTime) ? dateOrDateTime.toJSDate() : dateOrDateTime;
await calendarSelect(selector, date);
}
}

View file

@ -1,7 +1,7 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { isArray } from '@ember/array';
import moment from 'moment';
import { DateTime } from 'luxon';
module('Unit | Component | create options dates', function(hooks) {
setupTest(hooks);
@ -31,13 +31,11 @@ module('Unit | Component | create options dates', function(hooks) {
'array length is correct'
);
assert.ok(
component.selectedDays.every((el) => {
return moment.isMoment(el);
}),
'array elements are moment objects'
component.selectedDays.every(DateTime.isDateTime),
'array elements are luxon DateTime objects'
);
assert.deepEqual(
component.selectedDays.map((el) => el.format('YYYY-MM-DD')),
component.selectedDays.map((day) => day.toISODate()),
values.slice(0, 2),
'values are correct'
);
@ -45,9 +43,9 @@ module('Unit | Component | create options dates', function(hooks) {
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(),
DateTime.fromISO('2014-01-01T12:00').toISO(),
DateTime.fromISO('2015-02-02T15:00').toISO(),
DateTime.fromISO('2015-02-02T15:00').toISO(),
'2016-03-03',
];
@ -65,11 +63,11 @@ module('Unit | Component | create options dates', function(hooks) {
'array length is correct'
);
assert.ok(
component.selectedDays.every(moment.isMoment),
'array elements are moment objects'
component.selectedDays.every(DateTime.isDateTime),
'array elements are Luxon DateTime objects'
);
assert.deepEqual(
component.selectedDays.map((day) => day.format('YYYY-MM-DD')),
component.selectedDays.map((day) => day.toISODate()),
['2014-01-01', '2015-02-02', '2016-03-03'],
'dates are correct'
);
@ -82,7 +80,7 @@ module('Unit | Component | create options dates', function(hooks) {
let component = this.owner.factoryFor('component:create-options-dates').create({ options });
component.actions.daysSelected.bind(component)({
moment: values.map((_) => moment(_)),
datetime: values.map((_) => DateTime.fromISO(_)),
});
assert.ok(isArray(options), 'options is still an array');
@ -91,21 +89,18 @@ module('Unit | Component | create options dates', function(hooks) {
options.every(({ title }) => typeof title === 'string'),
'title property of options are strings'
);
assert.ok(
options.every(({ title }) => moment(title, 'YYYY-MM-DD', true).isValid()),
'title property of options are ISO-8601 date string without time'
);
assert.deepEqual(
options.map(({ title }) => title), values.sort(),
options.map(({ title }) => title),
['1917-10-25', '1918-11-09'],
'options having correct value and are sorted'
);
});
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(),
DateTime.fromISO('2015-01-01T11:11').toISO(),
DateTime.fromISO('2015-01-01T22:22').toISO(),
DateTime.fromISO('2015-06-06T08:08').toISO(),
'2016-01-01'
];
let additional = '2016-06-06';
@ -118,7 +113,7 @@ module('Unit | Component | create options dates', function(hooks) {
});
component.actions.daysSelected.bind(component)({
moment: merged.map((_) => moment(_)),
datetime: merged.map((_) => DateTime.fromISO(_)),
});
assert.deepEqual(
@ -130,9 +125,9 @@ module('Unit | Component | create options dates', function(hooks) {
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(),
DateTime.fromISO('2015-01-01T11:11').toISO(),
DateTime.fromISO('2015-01-01T22:22').toISO(),
DateTime.fromISO('2015-06-06T08:08').toISO(),
'2016-01-01'
];
let reduced = existing.slice();
@ -144,7 +139,7 @@ module('Unit | Component | create options dates', function(hooks) {
});
component.actions.daysSelected.bind(component)({
moment: reduced.map((_) => moment(_)),
datetime: reduced.map((_) => DateTime.fromISO(_)),
});
assert.deepEqual(

View file

@ -1,7 +1,7 @@
import { run } from '@ember/runloop';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import moment from 'moment';
import { DateTime } from 'luxon';
import { settled } from '@ember/test-helpers';
module('Unit | Component | create options datetime', function(hooks) {
@ -15,9 +15,9 @@ module('Unit | Component | create options datetime', function(hooks) {
let component = this.owner.factoryFor('component:create-options-datetime').create();
let a, b, c, d, e;
run(() => {
a = this.store.createFragment('option', { title: moment('2015-01-01T01:01:00.000').toISOString() });
b = this.store.createFragment('option', { title: moment('2015-01-01T11:11:00.000').toISOString() });
c = this.store.createFragment('option', { title: moment('2015-02-02T11:11:00.000').toISOString() });
a = this.store.createFragment('option', { title: DateTime.fromISO('2015-01-01T01:01:00.000').toISO() });
b = this.store.createFragment('option', { title: DateTime.fromISO('2015-01-01T11:11:00.000').toISO() });
c = this.store.createFragment('option', { title: DateTime.fromISO('2015-02-02T11:11:00.000').toISO() });
d = this.store.createFragment('option', { title: '2015-02-02' });
e = this.store.createFragment('option', { title: '2015-02-03' });
component.set('dates', [a, b, c, d, e]);
@ -26,8 +26,8 @@ module('Unit | Component | create options datetime', function(hooks) {
assert.deepEqual(
component.get('dates').map((date) => date.get('title')),
[
moment('2015-01-01T01:01:00.000').toISOString(),
moment('2015-02-02T11:11:00.000').toISOString(),
DateTime.fromISO('2015-01-01T01:01:00.000').toISO(),
DateTime.fromISO('2015-02-02T11:11:00.000').toISO(),
'2015-02-02',
'2015-02-03'
],
@ -37,8 +37,8 @@ module('Unit | Component | create options datetime', function(hooks) {
assert.deepEqual(
component.get('dates').map((date) => date.get('title')),
[
moment('2015-01-01T01:01:00.000').toISOString(),
moment('2015-02-02T11:11:00.000').toISOString(),
DateTime.fromISO('2015-01-01T01:01:00.000').toISO(),
DateTime.fromISO('2015-02-02T11:11:00.000').toISO(),
'2015-02-03'
],
'date get deleted if there is another date with same day (date does not have a time)'
@ -49,7 +49,7 @@ module('Unit | Component | create options datetime', function(hooks) {
assert.deepEqual(
component.get('dates').map((date) => date.get('title')),
[
moment('2015-01-01T01:01:00.000').toISOString(),
DateTime.fromISO('2015-01-01T01:01:00.000').toISO(),
'2015-02-02',
'2015-02-03'
],
@ -59,7 +59,7 @@ module('Unit | Component | create options datetime', function(hooks) {
assert.deepEqual(
component.get('dates').map((date) => date.get('title')),
[
moment('2015-01-01T01:01:00.000').toISOString(),
DateTime.fromISO('2015-01-01T01:01:00.000').toISO(),
'2015-02-02',
'2015-02-03'
],
@ -73,9 +73,9 @@ module('Unit | Component | create options datetime', function(hooks) {
// have to set dates in local time and than convert to ISO 8601 strings
// because otherwise test could fail caused by timezone
run(() => {
a = this.store.createFragment('option', { title: moment('2015-01-01T01:01:00').toISOString() });
b = this.store.createFragment('option', { title: moment('2015-01-01T11:11:00').toISOString() });
c = this.store.createFragment('option', { title: moment('2015-02-02T01:01:00').toISOString() });
a = this.store.createFragment('option', { title: DateTime.fromISO('2015-01-01T01:01:00').toISO() });
b = this.store.createFragment('option', { title: DateTime.fromISO('2015-01-01T11:11:00').toISO() });
c = this.store.createFragment('option', { title: DateTime.fromISO('2015-02-02T01:01:00').toISO() });
component.set('dates', [a, b, c]);
});
assert.equal(
@ -85,13 +85,13 @@ module('Unit | Component | create options datetime', function(hooks) {
);
assert.deepEqual(
component.get('groupedDates.firstObject.items').map((item) => {
return item.get('date').toISOString();
return item.get('title');
}),
[a.get('title'), b.get('title')],
'first dates having same day are grouped together'
);
assert.equal(
component.get('groupedDates.lastObject.items.firstObject.date').toISOString(),
component.get('groupedDates.lastObject.items.firstObject.title'),
[c.get('title')],
'last date having another day is in a separate group'
);
@ -99,16 +99,16 @@ module('Unit | Component | create options datetime', function(hooks) {
test('bindings are working on grouped datetimes', function(assert) {
let component = this.owner.factoryFor('component:create-options-datetime').create();
let baseDate = moment('2015-01-01T11:11');
let baseDate = DateTime.fromISO('2015-01-01T11:11');
component.set('dates', [
this.store.createFragment('option', {
title: baseDate.toISOString()
title: baseDate.toISO()
})
]);
assert.equal(
component.get('groupedDates.firstObject.items.firstObject.time'),
baseDate.format('HH:mm'),
baseDate.toISOTime('HH:mm').substr(0, 5),
'time is correct before'
);
@ -118,13 +118,13 @@ module('Unit | Component | create options datetime', function(hooks) {
);
assert.equal(
component.get('dates.firstObject.title'),
moment(baseDate).hour(0).minute(0).toISOString(),
baseDate.set({ hours: 0, minutes: 0 }).toISO(),
'option is updated after time changed on grouped datetimes'
);
component.get('dates').pushObject(
this.store.createFragment('option', {
title: baseDate.add(1, 'hour').add(1, 'minute').toISOString(),
title: baseDate.plus({ hours: 1, minutes: 1 }).toISO(),
})
);
assert.equal(
@ -139,7 +139,7 @@ module('Unit | Component | create options datetime', function(hooks) {
);
component.get('dates').pushObject(
this.store.createFragment('option', { title: moment('2015-02-02T01:01').toISOString() })
this.store.createFragment('option', { title: DateTime.fromISO('2015-02-02T01:01').toISO() })
);
assert.equal(
component.get('groupedDates.length'),
@ -156,10 +156,10 @@ module('Unit | Component | create options datetime', function(hooks) {
test('adopt times of first day - simple', async function(assert) {
let poll = this.store.createRecord('poll', {
options: [
{ title: moment('2015-01-01T11:11:00.000').toISOString() },
{ title: moment('2015-01-01T22:22:00.000').toISOString() },
{ title: moment('2015-01-02').toISOString() },
{ title: moment('2015-01-03').toISOString() }
{ title: DateTime.fromISO('2015-01-01T11:11:00.000').toISO() },
{ title: DateTime.fromISO('2015-01-01T22:22:00.000').toISO() },
{ title: DateTime.fromISO('2015-01-02').toISO() },
{ title: DateTime.fromISO('2015-01-03').toISO() }
]
});
let component = this.owner.factoryFor('component:create-options-datetime').create({
@ -171,12 +171,12 @@ module('Unit | Component | create options datetime', function(hooks) {
assert.deepEqual(
component.get('dates').map((option) => option.get('title')),
[
moment('2015-01-01T11:11:00.000').toISOString(),
moment('2015-01-01T22:22:00.000').toISOString(),
moment('2015-01-02T11:11:00.000').toISOString(),
moment('2015-01-02T22:22:00.000').toISOString(),
moment('2015-01-03T11:11:00.000').toISOString(),
moment('2015-01-03T22:22:00.000').toISOString()
DateTime.fromISO('2015-01-01T11:11:00.000').toISO(),
DateTime.fromISO('2015-01-01T22:22:00.000').toISO(),
DateTime.fromISO('2015-01-02T11:11:00.000').toISO(),
DateTime.fromISO('2015-01-02T22:22:00.000').toISO(),
DateTime.fromISO('2015-01-03T11:11:00.000').toISO(),
DateTime.fromISO('2015-01-03T22:22:00.000').toISO()
],
'times adopted correctly'
);
@ -185,14 +185,14 @@ module('Unit | Component | create options datetime', function(hooks) {
test('adopt times of first day - having times on the other days', async function(assert) {
let poll = this.store.createRecord('poll', {
options: [
{ title: moment('2015-01-01T11:11:00.000').toISOString() },
{ title: moment('2015-01-01T22:22:00.000').toISOString() },
{ title: moment('2015-01-02T09:11:00.000').toISOString() },
{ title: moment('2015-01-03T01:11:00.000').toISOString() },
{ title: moment('2015-01-03T11:11:00.000').toISOString() },
{ title: moment('2015-01-04T02:11:00.000').toISOString() },
{ title: moment('2015-01-04T05:11:00.000').toISOString() },
{ title: moment('2015-01-04T12:11:00.000').toISOString() }
{ title: DateTime.fromISO('2015-01-01T11:11:00.000').toISO() },
{ title: DateTime.fromISO('2015-01-01T22:22:00.000').toISO() },
{ title: DateTime.fromISO('2015-01-02T09:11:00.000').toISO() },
{ title: DateTime.fromISO('2015-01-03T01:11:00.000').toISO() },
{ title: DateTime.fromISO('2015-01-03T11:11:00.000').toISO() },
{ title: DateTime.fromISO('2015-01-04T02:11:00.000').toISO() },
{ title: DateTime.fromISO('2015-01-04T05:11:00.000').toISO() },
{ title: DateTime.fromISO('2015-01-04T12:11:00.000').toISO() }
]
});
let component = this.owner.factoryFor('component:create-options-datetime').create({
@ -204,14 +204,14 @@ module('Unit | Component | create options datetime', function(hooks) {
assert.deepEqual(
component.get('dates').map((option) => option.get('title')),
[
moment('2015-01-01T11:11:00.000').toISOString(),
moment('2015-01-01T22:22:00.000').toISOString(),
moment('2015-01-02T11:11:00.000').toISOString(),
moment('2015-01-02T22:22:00.000').toISOString(),
moment('2015-01-03T11:11:00.000').toISOString(),
moment('2015-01-03T22:22:00.000').toISOString(),
moment('2015-01-04T11:11:00.000').toISOString(),
moment('2015-01-04T22:22:00.000').toISOString()
DateTime.fromISO('2015-01-01T11:11:00.000').toISO(),
DateTime.fromISO('2015-01-01T22:22:00.000').toISO(),
DateTime.fromISO('2015-01-02T11:11:00.000').toISO(),
DateTime.fromISO('2015-01-02T22:22:00.000').toISO(),
DateTime.fromISO('2015-01-03T11:11:00.000').toISO(),
DateTime.fromISO('2015-01-03T22:22:00.000').toISO(),
DateTime.fromISO('2015-01-04T11:11:00.000').toISO(),
DateTime.fromISO('2015-01-04T22:22:00.000').toISO()
],
'times adopted correctly'
);
@ -222,8 +222,8 @@ module('Unit | Component | create options datetime', function(hooks) {
options: [
{ title: '2015-01-01' },
{ title: '2015-01-02' },
{ title: moment('2015-01-03T11:00').toISOString() },
{ title: moment('2015-01-03T15:00').toISOString() }
{ title: DateTime.fromISO('2015-01-03T11:00').toISO() },
{ title: DateTime.fromISO('2015-01-03T15:00').toISO() }
]
});
let component = this.owner.factoryFor('component:create-options-datetime').create({

View file

@ -1,7 +1,7 @@
import { run } from '@ember/runloop';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import moment from 'moment';
import { DateTime } from 'luxon';
module('Unit | Component | create options', function(hooks) {
setupTest(hooks);
@ -157,7 +157,7 @@ module('Unit | Component | create options', function(hooks) {
);
run(() => {
let option = this.store.createFragment('option', {
title: moment().add('1', 'day').format('YYYY-MM-DD')
title: DateTime.local().plus({ days: 1 }).toISODate(),
});
poll.get('options').pushObject(option);
component.get('options').pushObject(option);
@ -166,18 +166,5 @@ module('Unit | Component | create options', function(hooks) {
component.get('validations.isValid'),
'valid if there is atleast one valid date'
);
/*
Ember.run(() => {
let option = this.store.createFragment('option', {
title: moment().add('1', 'day').hour(22).minute(30).seconds(0).milliseconds(0).toISOString()
});
poll.get('options').pushObject(option);
component.get('options').pushObject(option);
});
assert.notOk(
component.get('validations.isValid'),
'invalid if there is a option without time for a day with has another option with time specified'
);
*/
});
});

View file

@ -1,21 +1,22 @@
import { run } from '@ember/runloop';
import EmberObject from '@ember/object';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import moment from 'moment';
import { DateTime } from 'luxon';
module('Unit | Controller | create/options datetime', function(hooks) {
setupTest(hooks);
test('normalize options - remove days without time if there is another option with a time for that day', function(assert) {
const dirtyOption = EmberObject.create({ title: '2015-01-01' });
test('normalize options - remove days without time if there is another option with a time for that day', function (assert) {
const StoreService = this.owner.lookup('service:store');
const dirtyOption = StoreService.createRecord('option', { title: '2015-01-01' });
let controller = this.owner.factoryFor('controller:create/options-datetime').create({
model: {
options: [
EmberObject.create({ title: moment('2015-01-01T12:00').toISOString() }),
StoreService.createRecord('option', { title: DateTime.fromISO('2015-01-01T12:00').toISO() }),
dirtyOption,
EmberObject.create({ title: '2017-11-11' }),
EmberObject.create({ title: moment('2018-04-04T11:11').toISOString() })
StoreService.createRecord('option', { title: '2017-11-11' }),
StoreService.createRecord('option', { title: DateTime.fromISO('2018-04-04T11:11').toISO() })
]
}
});
@ -31,18 +32,20 @@ module('Unit | Controller | create/options datetime', function(hooks) {
);
});
test('normalize options - sort them', function(assert) {
const dateA = moment().hour(5).toISOString();
const dateB = moment().hour(10).toISOString();
const dateC = moment().hour(22).toISOString();
const dateD = moment().add(1, 'day').toISOString();
test('normalize options - sort them', function (assert) {
const StoreService = this.owner.lookup('service:store');
const dateA = DateTime.local().set({ hours: 5 }).toISO();
const dateB = DateTime.local().set({ hours: 10 }).toISO();
const dateC = DateTime.local().set({ hours: 22 }).toISO();
const dateD = DateTime.local().plus({ days: 1 }).toISO();
let controller = this.owner.factoryFor('controller:create/options-datetime').create({
model: {
options: [
EmberObject.create({ title: dateB }),
EmberObject.create({ title: dateA }),
EmberObject.create({ title: dateC }),
EmberObject.create({ title: dateD })
StoreService.createRecord('option', { title: dateB }),
StoreService.createRecord('option', { title: dateA }),
StoreService.createRecord('option', { title: dateC }),
StoreService.createRecord('option', { title: dateD })
]
}
});

View file

@ -1,6 +1,6 @@
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import moment from 'moment';
import { DateTime } from 'luxon';
module('Unit | Controller | poll', function(hooks) {
setupTest(hooks);
@ -16,13 +16,13 @@ module('Unit | Controller | poll', function(hooks) {
'is false if expirationDate is undefined'
);
controller.set('model.expirationDate', moment().add(1, 'week').toISOString());
controller.set('model.expirationDate', DateTime.local().plus({ weeks: 1 }).toISO());
assert.ok(
controller.get('showExpirationWarning'),
'is true if expirationDate is less than 2 weeks in future'
);
controller.set('model.expirationDate', moment().add(1, 'month').toISOString());
controller.set('model.expirationDate', DateTime.local().plus({ months: 1 }).toISO());
assert.notOk(
controller.get('showExpirationWarning'),
'is false if expirationDate is more than 2 weeks in future'

View file

@ -2,89 +2,12 @@ import { run } from '@ember/runloop';
import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import { setupIntl } from 'ember-intl/test-support';
import moment from 'moment';
import { DateTime } from 'luxon';
module('Unit | Model | option', function(hooks) {
setupTest(hooks);
setupIntl(hooks, 'en');
hooks.beforeEach(function() {
moment.locale('en');
});
test('date property (get)', function(assert) {
let option = run(() => this.owner.lookup('service:store').createRecord('option', {
title: '2015-01-01'
}));
assert.ok(
moment.isMoment(option.get('date')),
'returns a moment instance if title is an ISO 8601 day string'
);
assert.equal(
option.get('date').format('YYYY-MM-DD HH:mm:ss.SSS'),
'2015-01-01 00:00:00.000',
'string to date conversion is correct for ISO 8601 day string'
);
run(() => {
option.set('title', '2015-01-01T11:11:00.000Z');
});
assert.ok(
moment.isMoment(option.get('date')),
'returns a moment instance if title is an ISO 8601 datetime string'
);
assert.equal(
option.get('date').toISOString(),
'2015-01-01T11:11:00.000Z',
'string to date conversion is correct for ISO 8601 datetime string'
);
run(() => {
option.set('title', null);
});
assert.equal(
option.get('date'),
undefined,
'returns undefined if title is empty'
);
run(() => {
option.set('title', 'abc');
});
assert.equal(
option.get('date'),
undefined,
'returns undefined if title is not a valid ISO 8601 date string'
);
run(() => {
option.set('title', '2015');
});
assert.equal(
option.get('date'),
undefined,
'returns undefined if title ISO 8601 string only contains a year'
);
run(() => {
option.set('title', '2015-01');
});
assert.equal(
option.get('date'),
undefined,
'returns undefined if title ISO 8601 string only contains a year and a month'
);
run(() => {
option.set('title', '2013W06');
});
assert.equal(
option.get('date'),
undefined,
'returns undefined if title ISO 8601 string only contains a year and a week'
);
});
test('day property (get)', function(assert) {
let option = run(() => this.owner.lookup('service:store').createRecord('option', {
title: '2015-01-01'
@ -100,7 +23,7 @@ module('Unit | Model | option', function(hooks) {
});
assert.equal(
option.get('day'),
moment('2015-01-01T11:11:00.000Z').format('YYYY-MM-DD'),
DateTime.fromISO('2015-01-01T11:11:00.000Z').toISODate(),
'returns ISO 8601 day string if title is ISO 8601 datetime string'
);
@ -123,44 +46,6 @@ module('Unit | Model | option', function(hooks) {
);
});
test('dayFormatted property (get)', function(assert) {
let option = run(() => this.owner.lookup('service:store').createRecord('option', {
title: '2015-01-01'
}));
assert.equal(
option.get('dayFormatted'),
'Thursday, January 1, 2015',
'returns formatted date if title is ISO 8601 day string'
);
run(() => {
option.set('title', moment('2015-01-01').toISOString());
});
assert.equal(
option.get('dayFormatted'),
'Thursday, January 1, 2015',
'returns formatted date if title is ISO 8601 datetime string'
);
run(() => {
option.set('intl.locale', 'de');
});
assert.equal(
option.get('dayFormatted'),
'Donnerstag, 1. Januar 2015',
'observes locale changes'
);
run(() => {
option.set('title', 'abc');
});
assert.equal(
option.get('dayFormatted'),
undefined,
'returns undfined if title is not a valid ISO 8601 string'
);
});
test('hasTime property', function(assert) {
let option = run(() => this.owner.lookup('service:store').createRecord('option', {
title: '2015-01-01T11:11:00.000Z'
@ -182,7 +67,7 @@ module('Unit | Model | option', function(hooks) {
}));
assert.equal(
option.get('time'),
moment('2015-01-01T11:11:00.000Z').format('HH:mm'),
DateTime.fromISO('2015-01-01T11:11:00.000Z').toFormat('HH:mm'),
'returns time if title is ISO 8601 datetime string'
);
@ -215,7 +100,7 @@ module('Unit | Model | option', function(hooks) {
});
assert.equal(
option.get('title'),
moment('2015-01-01T11:00').toISOString(),
DateTime.fromISO('2015-01-01T11:00').toISO(),
'sets title according to time'
);

View file

@ -57,42 +57,22 @@ module('Unit | Validator | iso8601', function(hooks) {
);
});
test('valid iso8601 string formats could be defined by options.validFormats', function(assert) {
test('other iso8601 strings', function(assert) {
let validator = this.owner.lookup('validator:iso8601');
const buildOptions = validator.buildOptions({
validFormats: [
'YYYY-MM-DD',
'YYYY-MM-DDTHH:mmZ',
'YYYY-MM-DDTHH:mm:ssZ'
]
}, {});
assert.notOk(
validator.validate('1945-05-08') === true,
'iso 8601 date string is invalid by default'
);
assert.equal(
validator.validate('1945-05-08', buildOptions),
assert.strictEqual(
validator.validate('1945-05-08'),
true,
'iso 8601 date string is valid if it\'s in validFormats options'
'iso 8601 date string is valid'
);
assert.notOk(
validator.validate('1945-05-08T23:01Z') === true,
'iso 8601 datetime string in UTC is invalid by default'
);
assert.equal(
validator.validate('1945-05-08T23:01Z', buildOptions),
assert.strictEqual(
validator.validate('1945-05-08T23:01Z'),
true,
'iso 8601 datetime string in UTC is valid if it\'s in validFormats options'
'iso 8601 datetime string in UTC is valid'
);
assert.notOk(
validator.validate('1945-05-08T23:01:00Z') === true,
assert.strictEqual(
validator.validate('1945-05-08T23:01:00Z'),
true,
'iso 8601 datetime string with seconds in UTC is invalid by default'
);
assert.equal(
validator.validate('1945-05-08T23:01:00Z', buildOptions),
true,
'iso 8601 datetime string with seconds in UTC is valid if it\'s in validFormats options'
'iso 8601 datetime string with seconds in UTC is valid'
);
});
});

1039
yarn.lock

File diff suppressed because it is too large Load diff