refactor poll creation to not rely on Ember Data model (#710)

This commit is contained in:
Jeldrik Hanschke 2023-10-28 17:50:17 +02:00 committed by GitHub
parent 8a4954f4e8
commit 1072953cd4
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 366 additions and 401 deletions

View file

@ -9,17 +9,9 @@ export default class CreateOptionsDates extends Component {
this.selectedDays.length >= 1 ? this.selectedDays[0] : DateTime.local();
get selectedDays() {
// Options may contain the same date multiple times with different ime
// Must filter out those duplicates as otherwise unselect would only
// 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));
return Array.from(this.args.options).map(({ value }) =>
DateTime.fromISO(value),
);
}
get calendarCenterNext() {
@ -34,53 +26,8 @@ export default class CreateOptionsDates extends Component {
return;
}
// A date has either been added or removed. If it has been removed, we must
// remove all options for that date. It may be multiple options with
// 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.',
newDatesAsLuxonDateTime.map((datetime) => datetime.toISODate()),
);
}

View file

@ -14,47 +14,39 @@
as |form|
>
<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
}}
<div
{{!
TODO: daysValidationState is not defined!
}}
class={{if
(get this.daysValidationState option.day)
(concat "label-has-" (get this.daysValidationState option.day))
(get this.daysValidationState date)
(concat "label-has-" (get this.daysValidationState date))
"label-has-no-validation"
}}
data-test-day={{option.day}}
data-test-day={{date}}
>
<form.element
{{!
TODO: Simplify to dateStyle="full" after upgrading to Ember Intl v6
}}
@label={{format-date
option.jsDate
timeOption.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.
show label only for the first time of this date
}}
@invisibleLabel={{eq
option.day
(get
(object-at
(if index (sub index 1) this.formData.options.length)
this.formData.options
)
"day"
)
}}
@model={{option}}
@invisibleLabel={{gt indexInTimeOptions 0}}
@model={{timeOption}}
@property="time"
class="option"
as |el|
@ -65,10 +57,10 @@
@type="time"
@value={{el.value}}
{{! focus input if it's the first one }}
{{autofocus enabled=(eq index 0)}}
{{autofocus enabled=timeOption.isFirstTimeOnFirstDate}}
{{! run validation for partially filled input on focusout event }}
{{on "focusout" (fn this.validateInput option)}}
{{on "change" (fn this.validateInput option)}}
{{on "focusout" (fn this.validateInput timeOption)}}
{{on "change" (fn this.validateInput timeOption)}}
{{!
Validation for partially input field must be reset if input is cleared.
But `@onChange` is not called and `focusout` event not triggered in that
@ -83,13 +75,13 @@
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)}}
{{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 option}}
@onClick={{fn this.formData.deleteOption timeOption}}
@type="link"
class="delete"
data-test-action="delete"
@ -107,7 +99,7 @@
</div>
<BsButton
@onClick={{fn this.formData.addOption index option.day}}
@onClick={{fn this.formData.addOption date}}
@type="link"
@size="sm"
class="add cr-option-menu__button cr-option-menu__add-button float-left"
@ -125,6 +117,7 @@
</form.element>
</div>
{{/each}}
{{/each-in}}
</div>
{{#if this.formData.hasMultipleDays}}
@ -145,7 +138,7 @@
<NextButton />
</div>
<div class="col-6 col-md-4 order-1 text-right">
<BackButton @onClick={{action "previousPage"}} />
<BackButton @onClick={{this.previousPage}} />
</div>
</div>
</BsForm>

View file

@ -2,15 +2,15 @@ import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { tracked } from '@glimmer/tracking';
import { TrackedArray } from 'tracked-built-ins';
import { TrackedMap, TrackedSet } from 'tracked-built-ins';
import { DateTime } from 'luxon';
import IntlMessage from '../utils/intl-message';
class FormDataOption {
class FormDataTimeOption {
formData;
// ISO 8601 date string: YYYY-MM-DD
day;
date;
// ISO 8601 time string without seconds: HH:mm
@tracked time;
@ -31,11 +31,11 @@ class FormDataOption {
// 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
// duplicated time should show the validation error.
const { formData, day } = this;
const optionsForThisDay = formData.optionsGroupedByDay[day];
const isDuplicate = optionsForThisDay
.slice(0, optionsForThisDay.indexOf(this))
.some((option) => option.time == this.time);
const { formData, date } = this;
const timesForThisDate = Array.from(formData.datetimes.get(date));
const isDuplicate = timesForThisDate
.slice(0, timesForThisDate.indexOf(this))
.some((timeOption) => timeOption.time == this.time);
if (isDuplicate) {
return new IntlMessage('create.options-datetime.error.duplicatedDate');
}
@ -44,8 +44,8 @@ class FormDataOption {
}
get datetime() {
const { day, time } = this;
const isoString = time === null ? day : `${day}T${time}`;
const { date, time } = this;
const isoString = time === null ? date : `${date}T${time}`;
return DateTime.fromISO(isoString);
}
@ -59,66 +59,61 @@ class FormDataOption {
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.day = day;
this.date = date;
this.time = time;
}
}
class FormData {
@tracked options;
@tracked datetimes;
get optionsValidation() {
const { options } = this;
const allOptionsAreValid = options.every((option) => option.isValid);
if (!allOptionsAreValid) {
const { datetimes } = this;
const allTimeOptionsAreValid = Array.from(datetimes.values()).every(
(timeOptionsForDate) =>
Array.from(timeOptionsForDate).every(
(timeOption) => timeOption.isValid,
),
);
if (!allTimeOptionsAreValid) {
return IntlMessage('create.options-datetime.error.invalidTime');
}
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() {
return Object.keys(this.optionsGroupedByDay).length > 1;
return this.datetimes.size > 1;
}
@action
addOption(position, day) {
this.options.splice(
position + 1,
0,
new FormDataOption(this, { day, time: null }),
);
addOption(date) {
this.datetimes
.get(date)
.add(new FormDataTimeOption(this, { date, 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
*/
@action
deleteOption(option) {
const optionsForThisDay = this.optionsGroupedByDay[option.day];
const timeOptionsForDate = this.datetimes.get(option.date);
if (optionsForThisDay.length > 1) {
this.options.splice(this.options.indexOf(option), 1);
if (timeOptionsForDate.size > 1) {
timeOptionsForDate.delete(option);
} else {
option.time = null;
}
@ -126,42 +121,53 @@ class FormData {
@action
adoptTimesOfFirstDay() {
const { optionsGroupedByDay } = this;
const days = Object.keys(optionsGroupedByDay).sort();
const firstDay = days[0];
const optionsForFirstDay = optionsGroupedByDay[firstDay];
const timesForFirstDayAreValid = optionsForFirstDay.every(
(option) => option.isValid,
const timeOptionsForFirstDay = Array.from(
Array.from(this.datetimes.values())[0],
);
const timesForFirstDayAreValid = timeOptionsForFirstDay.every(
(timeOption) => timeOption.isValid,
);
if (!timesForFirstDayAreValid) {
return false;
}
const timesForFirstDay = optionsForFirstDay.map((option) => option.time);
this.options = new TrackedArray(
days
.map((day) =>
timesForFirstDay.map(
(time) => new FormDataOption(this, { day, time }),
for (const date of Array.from(this.datetimes.keys()).slice(1)) {
this.datetimes.set(
date,
new TrackedSet(
timeOptionsForFirstDay.map(
({ time }) => new FormDataTimeOption(this, { date, time }),
),
),
);
}
}
constructor({ dates, times }) {
const datetimes = new Map();
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 }),
),
),
)
.flat(),
);
}
constructor(options) {
this.options = new TrackedArray(
options.map(({ day, time }) => new FormDataOption(this, { day, time })),
);
this.datetimes = new TrackedMap(datetimes);
}
}
export default class CreateOptionsDatetime extends Component {
@service router;
formData = new FormData(this.args.dates);
formData = new FormData({ dates: this.args.dates, times: this.args.times });
@tracked errorMesage = null;
@ -208,7 +214,25 @@ export default class CreateOptionsDatetime extends Component {
@action
handleTransition(transition) {
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);
}
}

View file

@ -79,10 +79,12 @@ class FormData {
constructor({ options }, { defaultOptionCount }) {
const normalizedOptions =
options.length === 0 && defaultOptionCount > 0 ? ['', ''] : options;
options.size === 0 && defaultOptionCount > 0
? ['', '']
: Array.from(options);
this.options = new TrackedArray(
normalizedOptions.map(({ title }) => new FormDataOption(this, title)),
normalizedOptions.map((value) => new FormDataOption(this, value)),
);
}
}

View file

@ -23,12 +23,16 @@ export default class CreateController extends Controller {
get canEnterOptionsDatetimeStep() {
return (
this.visitedSteps.has('options-datetime') &&
this.model.options.length >= 1
this.model.dateOptions.size >= 1
);
}
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() {

View file

@ -1,6 +1,5 @@
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { DateTime } from 'luxon';
import { inject as service } from '@ember/service';
export default class CreateOptionsDatetimeController extends Controller {
@ -18,14 +17,7 @@ export default class CreateOptionsDatetimeController extends Controller {
}
@action
updateOptions(options) {
this.model.options = options
.map(({ day, time }) =>
time ? DateTime.fromISO(`${day}T${time}`).toISO() : day,
)
.sort()
.map((isoString) => {
return this.store.createFragment('option', { title: isoString });
});
updateOptions(datetimes) {
this.model.timesForDateOptions = new Map(datetimes.entries());
}
}

View file

@ -1,6 +1,7 @@
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { TrackedSet } from 'tracked-built-ins/.';
export default class CreateOptionsController extends Controller {
@service router;
@ -8,9 +9,9 @@ export default class CreateOptionsController extends Controller {
@action
nextPage() {
const { isFindADate } = this.model;
const { pollType } = this.model;
if (isFindADate) {
if (pollType === 'FindADate') {
this.router.transitionTo('create.options-datetime');
} else {
this.router.transitionTo('create.settings');
@ -24,8 +25,13 @@ export default class CreateOptionsController extends Controller {
@action
updateOptions(newOptions) {
this.model.options = newOptions.map(({ value }) =>
this.store.createFragment('option', { title: value }),
);
const { pollType } = this.model;
const options = newOptions.map(({ value }) => value);
if (pollType === 'FindADate') {
this.model.dateOptions = new TrackedSet(options.sort());
} else {
this.model.freetextOptions = new TrackedSet(options);
}
}
}

View file

@ -9,6 +9,7 @@ export default class CreateSettings extends Controller {
@service flashMessages;
@service intl;
@service router;
@service store;
get anonymousUser() {
return this.model.anonymousUser;
@ -77,9 +78,9 @@ export default class CreateSettings extends Controller {
@action
previousPage() {
let { isFindADate } = this.model;
let { pollType } = this.model;
if (isFindADate) {
if (pollType === 'FindADate') {
this.router.transitionTo('create.options-datetime');
} else {
this.router.transitionTo('create.options');
@ -88,19 +89,68 @@ export default class CreateSettings extends Controller {
@action
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
if (
poll.isFindADate &&
poll.options.toArray().some((option) => {
return option.hasTime;
pollType === 'FindADate' &&
Array.from(timesForDateOptions.values()).some((timesForDateOptions) => {
return timesForDateOptions.size > 0;
})
) {
this.set(
'model.timezone',
Intl.DateTimeFormat().resolvedOptions().timeZone,
);
poll.timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
}
// save poll

View file

@ -1,13 +1,20 @@
import { modifier } from 'ember-modifier';
import Modifier from 'ember-modifier';
export default class AutofocusModifier extends Modifier {
isInstalled = false;
modify(element, positional, { enabled = true }) {
// element should be only autofocused on initial render
// not when `enabled` option is invalidated
if (this.isInstalled) {
return;
}
this.isInstalled = true;
export default modifier(function autofocus(
element,
params,
{ enabled = true },
) {
if (!enabled) {
return;
}
element.focus();
});
}
}

View file

@ -1,10 +1,22 @@
import classic from 'ember-classic-decorator';
import { inject as service } from '@ember/service';
import Route from '@ember/routing/route';
import config from 'croodle/config/environment';
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 {
@service encryption;
@service router;
@ -21,17 +33,7 @@ export default class CreateRoute extends Route {
}
model() {
// create empty poll
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,
});
return new PollData();
}
activate() {

View file

@ -1,5 +1,6 @@
<CreateOptionsDatetime
@dates={{@model.options}}
@dates={{@model.dateOptions}}
@times={{@model.timesForDateOptions}}
@onNextPage={{this.nextPage}}
@onPrevPage={{this.previousPage}}
@updateOptions={{this.updateOptions}}

View file

@ -1,7 +1,11 @@
<CreateOptions
@isFindADate={{@model.isFindADate}}
@isMakeAPoll={{@model.isMakeAPoll}}
@options={{@model.options}}
@isFindADate={{eq @model.pollType "FindADate"}}
@isMakeAPoll={{eq @model.pollType "MakeAPoll"}}
@options={{if
(eq @model.pollType "FindADate")
@model.dateOptions
@model.freetextOptions
}}
@onPrevPage={{this.previousPage}}
@onNextPage={{this.nextPage}}
@updateOptions={{this.updateOptions}}

View file

@ -923,7 +923,7 @@ module('Acceptance | create a poll', function (hooks) {
await pageCreateMeta.next();
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.next();
assert.equal(currentRouteName(), 'create.settings');

View file

@ -1,9 +1,7 @@
import { run } from '@ember/runloop';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, click, find, findAll } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { DateTime } from 'luxon';
module('Integration | Component | create options datetime', function (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) {
// validation is based on validation of every option fragment
// 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
run(() => {
this.set(
'poll',
this.store.createRecord('poll', {
pollType: 'FindADate',
options: [{ title: '2015-01-01' }],
}),
this.set('dates', new Set(['2015-01-01']));
this.set('times', new Map());
await render(
hbs`<CreateOptionsDatetime @dates={{this.dates}} @times={{this.times}} />`,
);
});
await render(hbs`<CreateOptionsDatetime @dates={{this.poll.options}} />`);
assert.equal(
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) {
// validation is based on validation of every option fragment
// 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
run(() => {
this.set(
'poll',
this.store.createRecord('poll', {
pollType: 'FindADate',
options: [{ title: '2015-01-01T11:11:00.000Z' }],
}),
this.set('dates', new Set(['2015-01-01']));
this.set('times', new Map([['2015-01-01', new Set(['11:11'])]]));
await render(
hbs`<CreateOptionsDatetime @dates={{this.dates}} @times={{this.times}} />`,
);
});
await render(hbs`<CreateOptionsDatetime @dates={{this.poll.options}} />`);
assert.equal(
findAll('.days .form-group input').length,
@ -70,30 +50,17 @@ module('Integration | Component | create options datetime', function (hooks) {
);
assert.equal(
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',
);
});
test('it hides repeated labels', async function (assert) {
// validation is based on validation of every option fragment
// 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
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' },
],
}),
this.set('dates', new Set(['2015-01-01', '2015-02-02']));
this.set('times', new Map([['2015-01-01', new Set(['11:11', '22:22'])]]));
await render(
hbs`<CreateOptionsDatetime @dates={{this.dates}} @times={{this.times}} />`,
);
});
await render(hbs`<CreateOptionsDatetime @dates={{this.poll.options}} />`);
assert.equal(
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) {
// validation is based on validation of every option fragment
// 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
run(() => {
this.set(
'poll',
this.store.createRecord('poll', {
options: [{ title: '2015-01-01' }, { title: '2015-02-02' }],
}),
this.set('dates', new Set(['2015-01-01', '2015-02-02']));
this.set('times', new Map());
await render(
hbs`<CreateOptionsDatetime @dates={{this.dates}} @times={{this.times}} />`,
);
});
await render(hbs`<CreateOptionsDatetime @dates={{this.poll.options}} />`);
assert.equal(
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) {
// validation is based on validation of every option fragment
// 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
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() },
],
}),
this.set('dates', new Set(['2015-01-01']));
this.set('times', new Map([['2015-01-01', new Set(['11:11', '22:22'])]]));
await render(
hbs`<CreateOptionsDatetime @dates={{this.dates}} @times={{this.times}} />`,
);
});
await render(hbs`<CreateOptionsDatetime @dates={{this.poll.options}} />`);
assert.equal(
findAll('.days input').length,

View file

@ -1,8 +1,8 @@
import { run } from '@ember/runloop';
import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit';
import { render, findAll, blur, fillIn, focus } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
import { TrackedSet } from 'tracked-built-ins';
module('Integration | Component | create options', function (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) {
assert.expect(5);
// validation is based on validation of every option fragment
// 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'));
this.set('options', new TrackedSet());
await render(hbs`
<CreateOptions
@options={{this.options}}
@isDateTime={{false}}
@isFindADate={{this.poll.isFindADate}}
@isMakeAPoll={{this.poll.isMakeAPoll}}
@isFindADate={{false}}
@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) {
// validation is based on validation of every option fragment
// 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'));
this.set('options', new TrackedSet());
await render(hbs`
<CreateOptions
@options={{this.options}}
@isDateTime={{false}}
@isFindADate={{this.poll.isFindADate}}
@isMakeAPoll={{this.poll.isMakeAPoll}}
@isFindADate={{false}}
@isMakeAPoll={{true}}
/>
`);

View file

@ -25,4 +25,12 @@ module('Integration | Modifier | autofocus', function (hooks) {
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();
});
});