refactor poll creation to not rely on Ember Data model (#710)
This commit is contained in:
parent
8a4954f4e8
commit
1072953cd4
16 changed files with 366 additions and 401 deletions
|
@ -9,17 +9,9 @@ export default class CreateOptionsDates extends Component {
|
||||||
this.selectedDays.length >= 1 ? this.selectedDays[0] : DateTime.local();
|
this.selectedDays.length >= 1 ? this.selectedDays[0] : DateTime.local();
|
||||||
|
|
||||||
get selectedDays() {
|
get selectedDays() {
|
||||||
// Options may contain the same date multiple times with different ime
|
return Array.from(this.args.options).map(({ value }) =>
|
||||||
// Must filter out those duplicates as otherwise unselect would only
|
DateTime.fromISO(value),
|
||||||
// remove one entry but not all duplicates.
|
);
|
||||||
return Array.from(
|
|
||||||
// using Set to remove duplicate values
|
|
||||||
new Set(
|
|
||||||
this.args.options.map(({ value }) =>
|
|
||||||
DateTime.fromISO(value).toISODate(),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
).map((isoDate) => DateTime.fromISO(isoDate));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
get calendarCenterNext() {
|
get calendarCenterNext() {
|
||||||
|
@ -34,53 +26,8 @@ export default class CreateOptionsDates extends Component {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A date has either been added or removed. If it has been removed, we must
|
this.args.updateOptions(
|
||||||
// remove all options for that date. It may be multiple options with
|
newDatesAsLuxonDateTime.map((datetime) => datetime.toISODate()),
|
||||||
// different times at that date.
|
|
||||||
|
|
||||||
// If any date received as an input argument is _not_ yet in the list of
|
|
||||||
// dates, it has been added.
|
|
||||||
const dateAdded = newDatesAsLuxonDateTime.find((newDateAsLuxonDateTime) => {
|
|
||||||
return !this.selectedDays.some(
|
|
||||||
(selectedDay) =>
|
|
||||||
selectedDay.toISODate() === newDateAsLuxonDateTime.toISODate(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (dateAdded) {
|
|
||||||
this.args.updateOptions(
|
|
||||||
[
|
|
||||||
...this.args.options.map(({ value }) => value),
|
|
||||||
dateAdded.toISODate(),
|
|
||||||
].sort(),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no date has been added, one date must have been removed. It has been
|
|
||||||
// removed if there is one date in current selectedDays but not in the new
|
|
||||||
// dates received as input argument to the function.
|
|
||||||
const dateRemoved = this.selectedDays.find((selectedDay) => {
|
|
||||||
return !newDatesAsLuxonDateTime.some(
|
|
||||||
(newDateAsLuxonDateTime) =>
|
|
||||||
newDateAsLuxonDateTime.toISODate() === selectedDay.toISODate(),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (dateRemoved) {
|
|
||||||
this.args.updateOptions(
|
|
||||||
this.args.options
|
|
||||||
.filter(
|
|
||||||
({ value }) =>
|
|
||||||
DateTime.fromISO(value).toISODate() !== dateRemoved.toISODate(),
|
|
||||||
)
|
|
||||||
.map(({ value }) => value),
|
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
throw new Error(
|
|
||||||
'No date has been added or removed. This cannot be the case. Something spooky is going on.',
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,117 +14,110 @@
|
||||||
as |form|
|
as |form|
|
||||||
>
|
>
|
||||||
<div class="days">
|
<div class="days">
|
||||||
{{#each this.formData.options as |option index|}}
|
{{#each-in this.formData.datetimes as |date timeOptions|}}
|
||||||
{{!
|
{{#each timeOptions as |timeOption indexInTimeOptions|}}
|
||||||
show summarized validation state for all times in a day
|
{{!
|
||||||
}}
|
show summarized validation state for all times in a day
|
||||||
<div
|
|
||||||
class={{if
|
|
||||||
(get this.daysValidationState option.day)
|
|
||||||
(concat "label-has-" (get this.daysValidationState option.day))
|
|
||||||
"label-has-no-validation"
|
|
||||||
}}
|
}}
|
||||||
data-test-day={{option.day}}
|
|
||||||
>
|
|
||||||
<form.element
|
|
||||||
{{!
|
|
||||||
TODO: Simplify to dateStyle="full" after upgrading to Ember Intl v6
|
|
||||||
}}
|
|
||||||
@label={{format-date
|
|
||||||
option.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
|
|
||||||
but returns undefined if an element with the passed in index does not exist.
|
|
||||||
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
|
|
||||||
option.day
|
|
||||||
(get
|
|
||||||
(object-at
|
|
||||||
(if index (sub index 1) this.formData.options.length)
|
|
||||||
this.formData.options
|
|
||||||
)
|
|
||||||
"day"
|
|
||||||
)
|
|
||||||
}}
|
|
||||||
@model={{option}}
|
|
||||||
@property="time"
|
|
||||||
class="option"
|
|
||||||
as |el|
|
|
||||||
>
|
|
||||||
<div class="input-group">
|
|
||||||
<el.control
|
|
||||||
@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 option)}}
|
|
||||||
{{on "change" (fn this.validateInput option)}}
|
|
||||||
{{!
|
|
||||||
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
|
|
||||||
input validation is updated as soon as user fixed a partially input.
|
|
||||||
The `keyup` events captures all scenarios in which the input is cleared
|
|
||||||
using keyboard. `focusin` event is triggered if user clicks the clears
|
|
||||||
button provided by native input. As a fallback validation is rerun on
|
|
||||||
`focusout`.
|
|
||||||
As the time of implementation this was only affecting Chrome cause
|
|
||||||
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 option)}}
|
|
||||||
{{on "keyup" (fn this.updateInputValidation option)}}
|
|
||||||
id={{el.id}}
|
|
||||||
/>
|
|
||||||
<div class="input-group-append">
|
|
||||||
<BsButton
|
|
||||||
@onClick={{fn this.formData.deleteOption option}}
|
|
||||||
@type="link"
|
|
||||||
class="delete"
|
|
||||||
data-test-action="delete"
|
|
||||||
>
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<BsButton
|
<div
|
||||||
@onClick={{fn this.formData.addOption index option.day}}
|
{{!
|
||||||
@type="link"
|
TODO: daysValidationState is not defined!
|
||||||
@size="sm"
|
}}
|
||||||
class="add cr-option-menu__button cr-option-menu__add-button float-left"
|
class={{if
|
||||||
data-test-action="add"
|
(get this.daysValidationState date)
|
||||||
|
(concat "label-has-" (get this.daysValidationState date))
|
||||||
|
"label-has-no-validation"
|
||||||
|
}}
|
||||||
|
data-test-day={{date}}
|
||||||
|
>
|
||||||
|
<form.element
|
||||||
|
{{!
|
||||||
|
TODO: Simplify to dateStyle="full" after upgrading to Ember Intl v6
|
||||||
|
}}
|
||||||
|
@label={{format-date
|
||||||
|
timeOption.jsDate
|
||||||
|
weekday="long"
|
||||||
|
day="numeric"
|
||||||
|
month="long"
|
||||||
|
year="numeric"
|
||||||
|
}}
|
||||||
|
{{!
|
||||||
|
show label only for the first time of this date
|
||||||
|
}}
|
||||||
|
@invisibleLabel={{gt indexInTimeOptions 0}}
|
||||||
|
@model={{timeOption}}
|
||||||
|
@property="time"
|
||||||
|
class="option"
|
||||||
|
as |el|
|
||||||
>
|
>
|
||||||
<span
|
<div class="input-group">
|
||||||
class="oi oi-plus"
|
<el.control
|
||||||
title={{t "create.options.button.add.label"}}
|
@placeholder="00:00"
|
||||||
aria-hidden="true"
|
@type="time"
|
||||||
></span>
|
@value={{el.value}}
|
||||||
<span class="sr-only">{{t
|
{{! focus input if it's the first one }}
|
||||||
"create.options.button.add.label"
|
{{autofocus enabled=timeOption.isFirstTimeOnFirstDate}}
|
||||||
}}</span>
|
{{! run validation for partially filled input on focusout event }}
|
||||||
</BsButton>
|
{{on "focusout" (fn this.validateInput timeOption)}}
|
||||||
</form.element>
|
{{on "change" (fn this.validateInput timeOption)}}
|
||||||
</div>
|
{{!
|
||||||
{{/each}}
|
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
|
||||||
|
input validation is updated as soon as user fixed a partially input.
|
||||||
|
The `keyup` events captures all scenarios in which the input is cleared
|
||||||
|
using keyboard. `focusin` event is triggered if user clicks the clears
|
||||||
|
button provided by native input. As a fallback validation is rerun on
|
||||||
|
`focusout`.
|
||||||
|
As the time of implementation this was only affecting Chrome cause
|
||||||
|
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 timeOption)}}
|
||||||
|
{{on "keyup" (fn this.updateInputValidation timeOption)}}
|
||||||
|
id={{el.id}}
|
||||||
|
/>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<BsButton
|
||||||
|
@onClick={{fn this.formData.deleteOption timeOption}}
|
||||||
|
@type="link"
|
||||||
|
class="delete"
|
||||||
|
data-test-action="delete"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<BsButton
|
||||||
|
@onClick={{fn this.formData.addOption date}}
|
||||||
|
@type="link"
|
||||||
|
@size="sm"
|
||||||
|
class="add cr-option-menu__button cr-option-menu__add-button float-left"
|
||||||
|
data-test-action="add"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
{{/each}}
|
||||||
|
{{/each-in}}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{{#if this.formData.hasMultipleDays}}
|
{{#if this.formData.hasMultipleDays}}
|
||||||
|
@ -145,7 +138,7 @@
|
||||||
<NextButton />
|
<NextButton />
|
||||||
</div>
|
</div>
|
||||||
<div class="col-6 col-md-4 order-1 text-right">
|
<div class="col-6 col-md-4 order-1 text-right">
|
||||||
<BackButton @onClick={{action "previousPage"}} />
|
<BackButton @onClick={{this.previousPage}} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BsForm>
|
</BsForm>
|
||||||
|
|
|
@ -2,15 +2,15 @@ import Component from '@glimmer/component';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { tracked } from '@glimmer/tracking';
|
import { tracked } from '@glimmer/tracking';
|
||||||
import { TrackedArray } from 'tracked-built-ins';
|
import { TrackedMap, TrackedSet } from 'tracked-built-ins';
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import IntlMessage from '../utils/intl-message';
|
import IntlMessage from '../utils/intl-message';
|
||||||
|
|
||||||
class FormDataOption {
|
class FormDataTimeOption {
|
||||||
formData;
|
formData;
|
||||||
|
|
||||||
// ISO 8601 date string: YYYY-MM-DD
|
// ISO 8601 date string: YYYY-MM-DD
|
||||||
day;
|
date;
|
||||||
|
|
||||||
// ISO 8601 time string without seconds: HH:mm
|
// ISO 8601 time string without seconds: HH:mm
|
||||||
@tracked time;
|
@tracked time;
|
||||||
|
@ -31,11 +31,11 @@ class FormDataOption {
|
||||||
// It should show a validation error if the same time has been entered for
|
// It should show a validation error if the same time has been entered for
|
||||||
// the same day already before. Only the second input field containing the
|
// the same day already before. Only the second input field containing the
|
||||||
// duplicated time should show the validation error.
|
// duplicated time should show the validation error.
|
||||||
const { formData, day } = this;
|
const { formData, date } = this;
|
||||||
const optionsForThisDay = formData.optionsGroupedByDay[day];
|
const timesForThisDate = Array.from(formData.datetimes.get(date));
|
||||||
const isDuplicate = optionsForThisDay
|
const isDuplicate = timesForThisDate
|
||||||
.slice(0, optionsForThisDay.indexOf(this))
|
.slice(0, timesForThisDate.indexOf(this))
|
||||||
.some((option) => option.time == this.time);
|
.some((timeOption) => timeOption.time == this.time);
|
||||||
if (isDuplicate) {
|
if (isDuplicate) {
|
||||||
return new IntlMessage('create.options-datetime.error.duplicatedDate');
|
return new IntlMessage('create.options-datetime.error.duplicatedDate');
|
||||||
}
|
}
|
||||||
|
@ -44,8 +44,8 @@ class FormDataOption {
|
||||||
}
|
}
|
||||||
|
|
||||||
get datetime() {
|
get datetime() {
|
||||||
const { day, time } = this;
|
const { date, time } = this;
|
||||||
const isoString = time === null ? day : `${day}T${time}`;
|
const isoString = time === null ? date : `${date}T${time}`;
|
||||||
return DateTime.fromISO(isoString);
|
return DateTime.fromISO(isoString);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,66 +59,61 @@ class FormDataOption {
|
||||||
return timeValidation === null;
|
return timeValidation === null;
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(formData, { day, time }) {
|
get isFirstTimeOnFirstDate() {
|
||||||
|
const { formData, date } = this;
|
||||||
|
const { datetimes } = formData;
|
||||||
|
return (
|
||||||
|
Array.from(datetimes.keys())[0] === date &&
|
||||||
|
Array.from(datetimes.get(date))[0] === this
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(formData, { date, time }) {
|
||||||
this.formData = formData;
|
this.formData = formData;
|
||||||
this.day = day;
|
this.date = date;
|
||||||
this.time = time;
|
this.time = time;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class FormData {
|
class FormData {
|
||||||
@tracked options;
|
@tracked datetimes;
|
||||||
|
|
||||||
get optionsValidation() {
|
get optionsValidation() {
|
||||||
const { options } = this;
|
const { datetimes } = this;
|
||||||
const allOptionsAreValid = options.every((option) => option.isValid);
|
const allTimeOptionsAreValid = Array.from(datetimes.values()).every(
|
||||||
if (!allOptionsAreValid) {
|
(timeOptionsForDate) =>
|
||||||
|
Array.from(timeOptionsForDate).every(
|
||||||
|
(timeOption) => timeOption.isValid,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
if (!allTimeOptionsAreValid) {
|
||||||
return IntlMessage('create.options-datetime.error.invalidTime');
|
return IntlMessage('create.options-datetime.error.invalidTime');
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
get optionsGroupedByDay() {
|
|
||||||
const { options } = this;
|
|
||||||
const groupedOptions = {};
|
|
||||||
|
|
||||||
for (const option of options) {
|
|
||||||
const { day } = option;
|
|
||||||
|
|
||||||
if (!groupedOptions[day]) {
|
|
||||||
groupedOptions[day] = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
groupedOptions[day].push(option);
|
|
||||||
}
|
|
||||||
|
|
||||||
return groupedOptions;
|
|
||||||
}
|
|
||||||
|
|
||||||
get hasMultipleDays() {
|
get hasMultipleDays() {
|
||||||
return Object.keys(this.optionsGroupedByDay).length > 1;
|
return this.datetimes.size > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
addOption(position, day) {
|
addOption(date) {
|
||||||
this.options.splice(
|
this.datetimes
|
||||||
position + 1,
|
.get(date)
|
||||||
0,
|
.add(new FormDataTimeOption(this, { date, time: null }));
|
||||||
new FormDataOption(this, { day, time: null }),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* removes target option if it's not the only date for this day
|
* removes target option if it's not the only time for this date
|
||||||
* otherwise it deletes time for this date
|
* otherwise it deletes time for this date
|
||||||
*/
|
*/
|
||||||
@action
|
@action
|
||||||
deleteOption(option) {
|
deleteOption(option) {
|
||||||
const optionsForThisDay = this.optionsGroupedByDay[option.day];
|
const timeOptionsForDate = this.datetimes.get(option.date);
|
||||||
|
|
||||||
if (optionsForThisDay.length > 1) {
|
if (timeOptionsForDate.size > 1) {
|
||||||
this.options.splice(this.options.indexOf(option), 1);
|
timeOptionsForDate.delete(option);
|
||||||
} else {
|
} else {
|
||||||
option.time = null;
|
option.time = null;
|
||||||
}
|
}
|
||||||
|
@ -126,42 +121,53 @@ class FormData {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
adoptTimesOfFirstDay() {
|
adoptTimesOfFirstDay() {
|
||||||
const { optionsGroupedByDay } = this;
|
const timeOptionsForFirstDay = Array.from(
|
||||||
const days = Object.keys(optionsGroupedByDay).sort();
|
Array.from(this.datetimes.values())[0],
|
||||||
const firstDay = days[0];
|
);
|
||||||
const optionsForFirstDay = optionsGroupedByDay[firstDay];
|
const timesForFirstDayAreValid = timeOptionsForFirstDay.every(
|
||||||
|
(timeOption) => timeOption.isValid,
|
||||||
const timesForFirstDayAreValid = optionsForFirstDay.every(
|
|
||||||
(option) => option.isValid,
|
|
||||||
);
|
);
|
||||||
if (!timesForFirstDayAreValid) {
|
if (!timesForFirstDayAreValid) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const timesForFirstDay = optionsForFirstDay.map((option) => option.time);
|
for (const date of Array.from(this.datetimes.keys()).slice(1)) {
|
||||||
|
this.datetimes.set(
|
||||||
this.options = new TrackedArray(
|
date,
|
||||||
days
|
new TrackedSet(
|
||||||
.map((day) =>
|
timeOptionsForFirstDay.map(
|
||||||
timesForFirstDay.map(
|
({ time }) => new FormDataTimeOption(this, { date, time }),
|
||||||
(time) => new FormDataOption(this, { day, time }),
|
|
||||||
),
|
),
|
||||||
)
|
),
|
||||||
.flat(),
|
);
|
||||||
);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(options) {
|
constructor({ dates, times }) {
|
||||||
this.options = new TrackedArray(
|
const datetimes = new Map();
|
||||||
options.map(({ day, time }) => new FormDataOption(this, { day, time })),
|
|
||||||
);
|
for (const date of dates) {
|
||||||
|
const timesForDate = times.has(date)
|
||||||
|
? Array.from(times.get(date))
|
||||||
|
: [null];
|
||||||
|
datetimes.set(
|
||||||
|
date,
|
||||||
|
new TrackedSet(
|
||||||
|
timesForDate.map(
|
||||||
|
(time) => new FormDataTimeOption(this, { date, time }),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.datetimes = new TrackedMap(datetimes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default class CreateOptionsDatetime extends Component {
|
export default class CreateOptionsDatetime extends Component {
|
||||||
@service router;
|
@service router;
|
||||||
|
|
||||||
formData = new FormData(this.args.dates);
|
formData = new FormData({ dates: this.args.dates, times: this.args.times });
|
||||||
|
|
||||||
@tracked errorMesage = null;
|
@tracked errorMesage = null;
|
||||||
|
|
||||||
|
@ -208,7 +214,25 @@ export default class CreateOptionsDatetime extends Component {
|
||||||
@action
|
@action
|
||||||
handleTransition(transition) {
|
handleTransition(transition) {
|
||||||
if (transition.from?.name === 'create.options-datetime') {
|
if (transition.from?.name === 'create.options-datetime') {
|
||||||
this.args.updateOptions(this.formData.options);
|
this.args.updateOptions(
|
||||||
|
// FormData.datetimes Map has a Set of FormDataTime object as values
|
||||||
|
// We need to transform it to a Set of plain time strings
|
||||||
|
new Map(
|
||||||
|
Array.from(this.formData.datetimes.entries())
|
||||||
|
.map(([key, timeOptions]) => [
|
||||||
|
key,
|
||||||
|
new Set(
|
||||||
|
Array.from(timeOptions)
|
||||||
|
.map(({ time }) => time)
|
||||||
|
// There might be FormDataTime objects without a time, which
|
||||||
|
// we need to filter out
|
||||||
|
.filter((time) => time !== null),
|
||||||
|
),
|
||||||
|
])
|
||||||
|
// There might be dates without any time, which we need to filter out
|
||||||
|
.filter(([, times]) => times.size > 0),
|
||||||
|
),
|
||||||
|
);
|
||||||
this.router.off('routeWillChange', this.handleTransition);
|
this.router.off('routeWillChange', this.handleTransition);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -79,10 +79,12 @@ class FormData {
|
||||||
|
|
||||||
constructor({ options }, { defaultOptionCount }) {
|
constructor({ options }, { defaultOptionCount }) {
|
||||||
const normalizedOptions =
|
const normalizedOptions =
|
||||||
options.length === 0 && defaultOptionCount > 0 ? ['', ''] : options;
|
options.size === 0 && defaultOptionCount > 0
|
||||||
|
? ['', '']
|
||||||
|
: Array.from(options);
|
||||||
|
|
||||||
this.options = new TrackedArray(
|
this.options = new TrackedArray(
|
||||||
normalizedOptions.map(({ title }) => new FormDataOption(this, title)),
|
normalizedOptions.map((value) => new FormDataOption(this, value)),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,12 +23,16 @@ export default class CreateController extends Controller {
|
||||||
get canEnterOptionsDatetimeStep() {
|
get canEnterOptionsDatetimeStep() {
|
||||||
return (
|
return (
|
||||||
this.visitedSteps.has('options-datetime') &&
|
this.visitedSteps.has('options-datetime') &&
|
||||||
this.model.options.length >= 1
|
this.model.dateOptions.size >= 1
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
get canEnterSettingsStep() {
|
get canEnterSettingsStep() {
|
||||||
return this.visitedSteps.has('settings') && this.model.options.length >= 1;
|
const { model, visitedSteps } = this;
|
||||||
|
const { dateOptions, freetextOptions, pollType } = model;
|
||||||
|
const options = pollType === 'FindADate' ? dateOptions : freetextOptions;
|
||||||
|
|
||||||
|
return visitedSteps.has('settings') && options.size >= 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
get isFindADate() {
|
get isFindADate() {
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
|
|
||||||
export default class CreateOptionsDatetimeController extends Controller {
|
export default class CreateOptionsDatetimeController extends Controller {
|
||||||
|
@ -18,14 +17,7 @@ export default class CreateOptionsDatetimeController extends Controller {
|
||||||
}
|
}
|
||||||
|
|
||||||
@action
|
@action
|
||||||
updateOptions(options) {
|
updateOptions(datetimes) {
|
||||||
this.model.options = options
|
this.model.timesForDateOptions = new Map(datetimes.entries());
|
||||||
.map(({ day, time }) =>
|
|
||||||
time ? DateTime.fromISO(`${day}T${time}`).toISO() : day,
|
|
||||||
)
|
|
||||||
.sort()
|
|
||||||
.map((isoString) => {
|
|
||||||
return this.store.createFragment('option', { title: isoString });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import Controller from '@ember/controller';
|
import Controller from '@ember/controller';
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import { action } from '@ember/object';
|
import { action } from '@ember/object';
|
||||||
|
import { TrackedSet } from 'tracked-built-ins/.';
|
||||||
|
|
||||||
export default class CreateOptionsController extends Controller {
|
export default class CreateOptionsController extends Controller {
|
||||||
@service router;
|
@service router;
|
||||||
|
@ -8,9 +9,9 @@ export default class CreateOptionsController extends Controller {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
nextPage() {
|
nextPage() {
|
||||||
const { isFindADate } = this.model;
|
const { pollType } = this.model;
|
||||||
|
|
||||||
if (isFindADate) {
|
if (pollType === 'FindADate') {
|
||||||
this.router.transitionTo('create.options-datetime');
|
this.router.transitionTo('create.options-datetime');
|
||||||
} else {
|
} else {
|
||||||
this.router.transitionTo('create.settings');
|
this.router.transitionTo('create.settings');
|
||||||
|
@ -24,8 +25,13 @@ export default class CreateOptionsController extends Controller {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
updateOptions(newOptions) {
|
updateOptions(newOptions) {
|
||||||
this.model.options = newOptions.map(({ value }) =>
|
const { pollType } = this.model;
|
||||||
this.store.createFragment('option', { title: value }),
|
const options = newOptions.map(({ value }) => value);
|
||||||
);
|
|
||||||
|
if (pollType === 'FindADate') {
|
||||||
|
this.model.dateOptions = new TrackedSet(options.sort());
|
||||||
|
} else {
|
||||||
|
this.model.freetextOptions = new TrackedSet(options);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ export default class CreateSettings extends Controller {
|
||||||
@service flashMessages;
|
@service flashMessages;
|
||||||
@service intl;
|
@service intl;
|
||||||
@service router;
|
@service router;
|
||||||
|
@service store;
|
||||||
|
|
||||||
get anonymousUser() {
|
get anonymousUser() {
|
||||||
return this.model.anonymousUser;
|
return this.model.anonymousUser;
|
||||||
|
@ -77,9 +78,9 @@ export default class CreateSettings extends Controller {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
previousPage() {
|
previousPage() {
|
||||||
let { isFindADate } = this.model;
|
let { pollType } = this.model;
|
||||||
|
|
||||||
if (isFindADate) {
|
if (pollType === 'FindADate') {
|
||||||
this.router.transitionTo('create.options-datetime');
|
this.router.transitionTo('create.options-datetime');
|
||||||
} else {
|
} else {
|
||||||
this.router.transitionTo('create.options');
|
this.router.transitionTo('create.options');
|
||||||
|
@ -88,19 +89,68 @@ export default class CreateSettings extends Controller {
|
||||||
|
|
||||||
@action
|
@action
|
||||||
async submit() {
|
async submit() {
|
||||||
const { model: poll } = this;
|
const { model } = this;
|
||||||
|
const {
|
||||||
|
anonymousUser,
|
||||||
|
answerType,
|
||||||
|
description,
|
||||||
|
expirationDate,
|
||||||
|
forceAnswer,
|
||||||
|
freetextOptions,
|
||||||
|
dateOptions,
|
||||||
|
timesForDateOptions,
|
||||||
|
pollType,
|
||||||
|
title,
|
||||||
|
} = model;
|
||||||
|
let options = [];
|
||||||
|
|
||||||
|
if (pollType === 'FindADate') {
|
||||||
|
// merge date with times
|
||||||
|
for (const date of dateOptions) {
|
||||||
|
if (timesForDateOptions.has(date)) {
|
||||||
|
for (const time of timesForDateOptions.get(date)) {
|
||||||
|
const [hour, minute] = time.split(':');
|
||||||
|
options.push(
|
||||||
|
DateTime.fromISO(date)
|
||||||
|
.set({
|
||||||
|
hour,
|
||||||
|
minute,
|
||||||
|
second: 0,
|
||||||
|
millisecond: 0,
|
||||||
|
})
|
||||||
|
.toISO(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
options.push(date);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
options.push(...freetextOptions);
|
||||||
|
}
|
||||||
|
|
||||||
|
const poll = this.store.createRecord('poll', {
|
||||||
|
anonymousUser,
|
||||||
|
answerType,
|
||||||
|
creationDate: new Date().toISOString(),
|
||||||
|
description,
|
||||||
|
expirationDate,
|
||||||
|
forceAnswer,
|
||||||
|
options: options.map((option) =>
|
||||||
|
this.store.createFragment('option', { title: option }),
|
||||||
|
),
|
||||||
|
pollType,
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
|
||||||
// set timezone if there is atleast one option with time
|
// set timezone if there is atleast one option with time
|
||||||
if (
|
if (
|
||||||
poll.isFindADate &&
|
pollType === 'FindADate' &&
|
||||||
poll.options.toArray().some((option) => {
|
Array.from(timesForDateOptions.values()).some((timesForDateOptions) => {
|
||||||
return option.hasTime;
|
return timesForDateOptions.size > 0;
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
this.set(
|
poll.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
||||||
'model.timezone',
|
|
||||||
Intl.DateTimeFormat().resolvedOptions().timeZone,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// save poll
|
// save poll
|
||||||
|
|
|
@ -1,13 +1,20 @@
|
||||||
import { modifier } from 'ember-modifier';
|
import Modifier from 'ember-modifier';
|
||||||
|
|
||||||
export default modifier(function autofocus(
|
export default class AutofocusModifier extends Modifier {
|
||||||
element,
|
isInstalled = false;
|
||||||
params,
|
|
||||||
{ enabled = true },
|
modify(element, positional, { enabled = true }) {
|
||||||
) {
|
// element should be only autofocused on initial render
|
||||||
if (!enabled) {
|
// not when `enabled` option is invalidated
|
||||||
return;
|
if (this.isInstalled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.isInstalled = true;
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
element.focus();
|
||||||
}
|
}
|
||||||
|
}
|
||||||
element.focus();
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,10 +1,22 @@
|
||||||
import classic from 'ember-classic-decorator';
|
|
||||||
import { inject as service } from '@ember/service';
|
import { inject as service } from '@ember/service';
|
||||||
import Route from '@ember/routing/route';
|
import Route from '@ember/routing/route';
|
||||||
import config from 'croodle/config/environment';
|
|
||||||
import { DateTime } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
|
import { tracked } from '@glimmer/tracking';
|
||||||
|
import { TrackedSet } from 'tracked-built-ins';
|
||||||
|
|
||||||
|
class PollData {
|
||||||
|
@tracked anonymousUser = false;
|
||||||
|
@tracked answerType = 'YesNo';
|
||||||
|
@tracked description = '';
|
||||||
|
@tracked expirationDate = DateTime.local().plus({ months: 3 }).toISO();
|
||||||
|
@tracked forceAnswer = true;
|
||||||
|
@tracked freetextOptions = new TrackedSet();
|
||||||
|
@tracked dateOptions = new TrackedSet();
|
||||||
|
@tracked timesForDateOptions = new Map();
|
||||||
|
@tracked pollType = 'FindADate';
|
||||||
|
@tracked title = '';
|
||||||
|
}
|
||||||
|
|
||||||
@classic
|
|
||||||
export default class CreateRoute extends Route {
|
export default class CreateRoute extends Route {
|
||||||
@service encryption;
|
@service encryption;
|
||||||
@service router;
|
@service router;
|
||||||
|
@ -21,17 +33,7 @@ export default class CreateRoute extends Route {
|
||||||
}
|
}
|
||||||
|
|
||||||
model() {
|
model() {
|
||||||
// create empty poll
|
return new PollData();
|
||||||
return this.store.createRecord('poll', {
|
|
||||||
answerType: 'YesNo',
|
|
||||||
creationDate: new Date(),
|
|
||||||
forceAnswer: true,
|
|
||||||
anonymousUser: false,
|
|
||||||
pollType: 'FindADate',
|
|
||||||
timezone: null,
|
|
||||||
expirationDate: DateTime.local().plus({ months: 3 }).toISO(),
|
|
||||||
version: config.APP.version,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
activate() {
|
activate() {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
<CreateOptionsDatetime
|
<CreateOptionsDatetime
|
||||||
@dates={{@model.options}}
|
@dates={{@model.dateOptions}}
|
||||||
|
@times={{@model.timesForDateOptions}}
|
||||||
@onNextPage={{this.nextPage}}
|
@onNextPage={{this.nextPage}}
|
||||||
@onPrevPage={{this.previousPage}}
|
@onPrevPage={{this.previousPage}}
|
||||||
@updateOptions={{this.updateOptions}}
|
@updateOptions={{this.updateOptions}}
|
||||||
|
|
|
@ -1,7 +1,11 @@
|
||||||
<CreateOptions
|
<CreateOptions
|
||||||
@isFindADate={{@model.isFindADate}}
|
@isFindADate={{eq @model.pollType "FindADate"}}
|
||||||
@isMakeAPoll={{@model.isMakeAPoll}}
|
@isMakeAPoll={{eq @model.pollType "MakeAPoll"}}
|
||||||
@options={{@model.options}}
|
@options={{if
|
||||||
|
(eq @model.pollType "FindADate")
|
||||||
|
@model.dateOptions
|
||||||
|
@model.freetextOptions
|
||||||
|
}}
|
||||||
@onPrevPage={{this.previousPage}}
|
@onPrevPage={{this.previousPage}}
|
||||||
@onNextPage={{this.nextPage}}
|
@onNextPage={{this.nextPage}}
|
||||||
@updateOptions={{this.updateOptions}}
|
@updateOptions={{this.updateOptions}}
|
||||||
|
|
|
@ -923,7 +923,7 @@ module('Acceptance | create a poll', function (hooks) {
|
||||||
await pageCreateMeta.next();
|
await pageCreateMeta.next();
|
||||||
assert.equal(currentRouteName(), 'create.options');
|
assert.equal(currentRouteName(), 'create.options');
|
||||||
|
|
||||||
await pageCreateOptions.textOptions.objectAt(0).add();
|
await pageCreateOptions.textOptions.objectAt(0).title('foo');
|
||||||
await pageCreateOptions.textOptions.objectAt(1).title('bar');
|
await pageCreateOptions.textOptions.objectAt(1).title('bar');
|
||||||
await pageCreateOptions.next();
|
await pageCreateOptions.next();
|
||||||
assert.equal(currentRouteName(), 'create.settings');
|
assert.equal(currentRouteName(), 'create.settings');
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import { run } from '@ember/runloop';
|
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupRenderingTest } from 'ember-qunit';
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
import { render, click, find, findAll } from '@ember/test-helpers';
|
import { render, click, find, findAll } from '@ember/test-helpers';
|
||||||
import hbs from 'htmlbars-inline-precompile';
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
import { DateTime } from 'luxon';
|
|
||||||
|
|
||||||
module('Integration | Component | create options datetime', function (hooks) {
|
module('Integration | Component | create options datetime', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
@ -20,20 +18,11 @@ module('Integration | Component | create options datetime', function (hooks) {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
test('it generates input field for options iso 8601 date string (without time)', async function (assert) {
|
test('it generates input field for options iso 8601 date string (without time)', async function (assert) {
|
||||||
// validation is based on validation of every option fragment
|
this.set('dates', new Set(['2015-01-01']));
|
||||||
// which validates according to poll model it belongs to
|
this.set('times', new Map());
|
||||||
// therefore each option needs to be pushed to poll model to have it as
|
await render(
|
||||||
// it's owner
|
hbs`<CreateOptionsDatetime @dates={{this.dates}} @times={{this.times}} />`,
|
||||||
run(() => {
|
);
|
||||||
this.set(
|
|
||||||
'poll',
|
|
||||||
this.store.createRecord('poll', {
|
|
||||||
pollType: 'FindADate',
|
|
||||||
options: [{ title: '2015-01-01' }],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await render(hbs`<CreateOptionsDatetime @dates={{this.poll.options}} />`);
|
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
findAll('.days .form-group input').length,
|
findAll('.days .form-group input').length,
|
||||||
|
@ -48,20 +37,11 @@ module('Integration | Component | create options datetime', function (hooks) {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it generates input field for options iso 8601 datetime string (with time)', async function (assert) {
|
test('it generates input field for options iso 8601 datetime string (with time)', async function (assert) {
|
||||||
// validation is based on validation of every option fragment
|
this.set('dates', new Set(['2015-01-01']));
|
||||||
// which validates according to poll model it belongs to
|
this.set('times', new Map([['2015-01-01', new Set(['11:11'])]]));
|
||||||
// therefore each option needs to be pushed to poll model to have it as
|
await render(
|
||||||
// it's owner
|
hbs`<CreateOptionsDatetime @dates={{this.dates}} @times={{this.times}} />`,
|
||||||
run(() => {
|
);
|
||||||
this.set(
|
|
||||||
'poll',
|
|
||||||
this.store.createRecord('poll', {
|
|
||||||
pollType: 'FindADate',
|
|
||||||
options: [{ title: '2015-01-01T11:11:00.000Z' }],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await render(hbs`<CreateOptionsDatetime @dates={{this.poll.options}} />`);
|
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
findAll('.days .form-group input').length,
|
findAll('.days .form-group input').length,
|
||||||
|
@ -70,30 +50,17 @@ module('Integration | Component | create options datetime', function (hooks) {
|
||||||
);
|
);
|
||||||
assert.equal(
|
assert.equal(
|
||||||
find('.days .form-group input').value,
|
find('.days .form-group input').value,
|
||||||
DateTime.fromISO('2015-01-01T11:11:00.000Z').toFormat('HH:mm'),
|
'11:11',
|
||||||
'it has time in option as value',
|
'it has time in option as value',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('it hides repeated labels', async function (assert) {
|
test('it hides repeated labels', async function (assert) {
|
||||||
// validation is based on validation of every option fragment
|
this.set('dates', new Set(['2015-01-01', '2015-02-02']));
|
||||||
// which validates according to poll model it belongs to
|
this.set('times', new Map([['2015-01-01', new Set(['11:11', '22:22'])]]));
|
||||||
// therefore each option needs to be pushed to poll model to have it as
|
await render(
|
||||||
// it's owner
|
hbs`<CreateOptionsDatetime @dates={{this.dates}} @times={{this.times}} />`,
|
||||||
run(() => {
|
);
|
||||||
this.set(
|
|
||||||
'poll',
|
|
||||||
this.store.createRecord('poll', {
|
|
||||||
pollType: 'FindADate',
|
|
||||||
options: [
|
|
||||||
{ title: DateTime.fromISO('2015-01-01T10:11').toISO() },
|
|
||||||
{ title: DateTime.fromISO('2015-01-01T22:22').toISO() },
|
|
||||||
{ title: '2015-02-02' },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await render(hbs`<CreateOptionsDatetime @dates={{this.poll.options}} />`);
|
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
findAll('.days label').length,
|
findAll('.days label').length,
|
||||||
|
@ -126,19 +93,11 @@ module('Integration | Component | create options datetime', function (hooks) {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('allows to add another option', async function (assert) {
|
test('allows to add another option', async function (assert) {
|
||||||
// validation is based on validation of every option fragment
|
this.set('dates', new Set(['2015-01-01', '2015-02-02']));
|
||||||
// which validates according to poll model it belongs to
|
this.set('times', new Map());
|
||||||
// therefore each option needs to be pushed to poll model to have it as
|
await render(
|
||||||
// it's owner
|
hbs`<CreateOptionsDatetime @dates={{this.dates}} @times={{this.times}} />`,
|
||||||
run(() => {
|
);
|
||||||
this.set(
|
|
||||||
'poll',
|
|
||||||
this.store.createRecord('poll', {
|
|
||||||
options: [{ title: '2015-01-01' }, { title: '2015-02-02' }],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await render(hbs`<CreateOptionsDatetime @dates={{this.poll.options}} />`);
|
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
findAll('.days .form-group input').length,
|
findAll('.days .form-group input').length,
|
||||||
|
@ -166,23 +125,11 @@ module('Integration | Component | create options datetime', function (hooks) {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('allows to delete an option', async function (assert) {
|
test('allows to delete an option', async function (assert) {
|
||||||
// validation is based on validation of every option fragment
|
this.set('dates', new Set(['2015-01-01']));
|
||||||
// which validates according to poll model it belongs to
|
this.set('times', new Map([['2015-01-01', new Set(['11:11', '22:22'])]]));
|
||||||
// therefore each option needs to be pushed to poll model to have it as
|
await render(
|
||||||
// it's owner
|
hbs`<CreateOptionsDatetime @dates={{this.dates}} @times={{this.times}} />`,
|
||||||
run(() => {
|
);
|
||||||
this.set(
|
|
||||||
'poll',
|
|
||||||
this.store.createRecord('poll', {
|
|
||||||
pollType: 'FindADate',
|
|
||||||
options: [
|
|
||||||
{ title: DateTime.fromISO('2015-01-01T11:11').toISO() },
|
|
||||||
{ title: DateTime.fromISO('2015-01-01T22:22').toISO() },
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
await render(hbs`<CreateOptionsDatetime @dates={{this.poll.options}} />`);
|
|
||||||
|
|
||||||
assert.equal(
|
assert.equal(
|
||||||
findAll('.days input').length,
|
findAll('.days input').length,
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import { run } from '@ember/runloop';
|
|
||||||
import { module, test } from 'qunit';
|
import { module, test } from 'qunit';
|
||||||
import { setupRenderingTest } from 'ember-qunit';
|
import { setupRenderingTest } from 'ember-qunit';
|
||||||
import { render, findAll, blur, fillIn, focus } from '@ember/test-helpers';
|
import { render, findAll, blur, fillIn, focus } from '@ember/test-helpers';
|
||||||
import hbs from 'htmlbars-inline-precompile';
|
import hbs from 'htmlbars-inline-precompile';
|
||||||
|
import { TrackedSet } from 'tracked-built-ins';
|
||||||
|
|
||||||
module('Integration | Component | create options', function (hooks) {
|
module('Integration | Component | create options', function (hooks) {
|
||||||
setupRenderingTest(hooks);
|
setupRenderingTest(hooks);
|
||||||
|
@ -14,25 +14,14 @@ module('Integration | Component | create options', function (hooks) {
|
||||||
test('shows validation errors if options are not unique (makeAPoll)', async function (assert) {
|
test('shows validation errors if options are not unique (makeAPoll)', async function (assert) {
|
||||||
assert.expect(5);
|
assert.expect(5);
|
||||||
|
|
||||||
// validation is based on validation of every option fragment
|
this.set('options', new TrackedSet());
|
||||||
// which validates according to poll model it belongs to
|
|
||||||
// therefore each option needs to be pushed to poll model to have it as
|
|
||||||
// it's owner
|
|
||||||
let poll;
|
|
||||||
run(() => {
|
|
||||||
poll = this.store.createRecord('poll', {
|
|
||||||
pollType: 'MakeAPoll',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.set('poll', poll);
|
|
||||||
this.set('options', poll.get('options'));
|
|
||||||
|
|
||||||
await render(hbs`
|
await render(hbs`
|
||||||
<CreateOptions
|
<CreateOptions
|
||||||
@options={{this.options}}
|
@options={{this.options}}
|
||||||
@isDateTime={{false}}
|
@isDateTime={{false}}
|
||||||
@isFindADate={{this.poll.isFindADate}}
|
@isFindADate={{false}}
|
||||||
@isMakeAPoll={{this.poll.isMakeAPoll}}
|
@isMakeAPoll={{true}}
|
||||||
/>
|
/>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
@ -64,25 +53,14 @@ module('Integration | Component | create options', function (hooks) {
|
||||||
});
|
});
|
||||||
|
|
||||||
test('shows validation errors if option is empty (makeAPoll)', async function (assert) {
|
test('shows validation errors if option is empty (makeAPoll)', async function (assert) {
|
||||||
// validation is based on validation of every option fragment
|
this.set('options', new TrackedSet());
|
||||||
// which validates according to poll model it belongs to
|
|
||||||
// therefore each option needs to be pushed to poll model to have it as
|
|
||||||
// it's owner
|
|
||||||
let poll;
|
|
||||||
run(() => {
|
|
||||||
poll = this.store.createRecord('poll', {
|
|
||||||
pollType: 'MakeAPoll',
|
|
||||||
});
|
|
||||||
});
|
|
||||||
this.set('poll', poll);
|
|
||||||
this.set('options', poll.get('options'));
|
|
||||||
|
|
||||||
await render(hbs`
|
await render(hbs`
|
||||||
<CreateOptions
|
<CreateOptions
|
||||||
@options={{this.options}}
|
@options={{this.options}}
|
||||||
@isDateTime={{false}}
|
@isDateTime={{false}}
|
||||||
@isFindADate={{this.poll.isFindADate}}
|
@isFindADate={{false}}
|
||||||
@isMakeAPoll={{this.poll.isMakeAPoll}}
|
@isMakeAPoll={{true}}
|
||||||
/>
|
/>
|
||||||
`);
|
`);
|
||||||
|
|
||||||
|
|
|
@ -25,4 +25,12 @@ module('Integration | Modifier | autofocus', function (hooks) {
|
||||||
|
|
||||||
assert.dom('input').isNotFocused();
|
assert.dom('input').isNotFocused();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('it does not focus the element if `enabled` changes', async function (assert) {
|
||||||
|
this.set('enabled', false);
|
||||||
|
|
||||||
|
await render(hbs`<input {{autofocus enabled=this.enabled}} />`);
|
||||||
|
this.set('enabled', true);
|
||||||
|
assert.dom('input').isNotFocused();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in a new issue