upgrade to Ember 3.28 blueprints (#647)

This commit is contained in:
Jeldrik Hanschke 2023-10-15 20:37:03 +02:00 committed by GitHub
parent c48d7d1441
commit 02058ab756
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
116 changed files with 2128 additions and 1609 deletions

View file

@ -14,6 +14,8 @@
/api
/coverage/
!.*
.*/
.eslintcache
# ember-try
/.node_modules.ember-try/

View file

@ -7,15 +7,14 @@ module.exports = {
ecmaVersion: 2018,
sourceType: 'module',
ecmaFeatures: {
legacyDecorators: true
}
legacyDecorators: true,
},
},
plugins: [
'ember'
],
plugins: ['ember'],
extends: [
'eslint:recommended',
'plugin:ember/recommended'
'plugin:ember/recommended',
'plugin:prettier/recommended',
],
env: {
browser: true,
@ -24,6 +23,8 @@ module.exports = {
// Croodle is not compliant with some of the recommended rules yet.
// We should refactor the code step by step and enable them as soon
// as the code is compliant.
'ember/classic-decorator-no-classic-methods': 'warn',
'ember/no-controller-access-in-routes': 'warn',
'ember/no-observers': 'warn',
'no-prototype-builtins': 'warn',
},
@ -31,29 +32,40 @@ module.exports = {
// node files
{
files: [
'.eslintrc.js',
'.template-lintrc.js',
'ember-cli-build.js',
'testem.js',
'blueprints/*/index.js',
'config/**/*.js',
'lib/*/index.js',
'server/**/*.js'
'./.eslintrc.js',
'./.prettierrc.js',
'./.template-lintrc.js',
'./ember-cli-build.js',
'./testem.js',
'./blueprints/*/index.js',
'./config/**/*.js',
'./lib/*/index.js',
'./server/**/*.js',
],
parserOptions: {
sourceType: 'script'
sourceType: 'script',
},
env: {
browser: false,
node: true
node: true,
},
plugins: ['node'],
extends: ['plugin:node/recommended'],
rules: {
// this can be removed once the following is fixed
// https://github.com/mysticatea/eslint-plugin-node/issues/77
'node/no-unpublished-require': 'off'
}
}
]
'node/no-unpublished-require': 'off',
},
},
{
// Test files:
files: ['tests/**/*-test.{js,ts}'],
extends: ['plugin:qunit/recommended'],
rules: {
'qunit/no-assert-logical-expression': 'warn',
'qunit/no-async-module-callbacks': 'warn',
'qunit/require-expect': 'warn',
},
},
],
};

1
.gitignore vendored
View file

@ -16,6 +16,7 @@
/.env*
/.pnp*
/.sass-cache
/.eslintcache
/connect.lock
/coverage/
/libpeerconnection.log

21
.prettierignore Normal file
View file

@ -0,0 +1,21 @@
# unconventional js
/blueprints/*/files/
/vendor/
# compiled output
/dist/
/tmp/
# dependencies
/bower_components/
/node_modules/
# misc
/coverage/
!.*
.eslintcache
# ember-try
/.node_modules.ember-try/
/bower.json.ember-try
/package.json.ember-try

5
.prettierrc.js Normal file
View file

@ -0,0 +1,5 @@
'use strict';
module.exports = {
singleQuote: true,
};

View file

@ -3,6 +3,7 @@
module.exports = {
extends: 'recommended',
rules: {
'no-action': false,
'no-implicit-this': {
allow: ['scroll-first-invalid-element-into-view-port'],
},

View file

@ -6,8 +6,7 @@ export default class ApplicationAdapter extends RESTAdapter {
encryption;
// set namespace to api.php in same subdirectory
namespace =
window.location.pathname
namespace = window.location.pathname
// remove index.html if it's there
.replace(/index.html$/, '')
// remove tests prefix which is added by testem (starting with a number)
@ -19,5 +18,5 @@ export default class ApplicationAdapter extends RESTAdapter {
// add api.php
.concat('/api/index.php')
// remove leading slash
.replace(/^\//g, '')
.replace(/^\//g, '');
}

View file

@ -1 +0,0 @@
{{yield}}

View file

@ -1,8 +1,8 @@
import BaseBsForm from "ember-bootstrap/components/bs-form";
import IntlMessage from "../utils/intl-message";
import BaseBsForm from 'ember-bootstrap/components/bs-form';
import IntlMessage from '../utils/intl-message';
export default class BsForm extends BaseBsForm {
"__ember-bootstrap_subclass" = true;
'__ember-bootstrap_subclass' = true;
get hasValidator() {
return true;

View file

@ -1,8 +1,8 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { isArray } from "@ember/array";
import { DateTime } from "luxon";
import { tracked } from "@glimmer/tracking";
import Component from '@glimmer/component';
import { action } from '@ember/object';
import { isArray } from '@ember/array';
import { DateTime } from 'luxon';
import { tracked } from '@glimmer/tracking';
export default class CreateOptionsDates extends Component {
@tracked calendarCenter =
@ -64,21 +64,24 @@ export default class CreateOptionsDates extends Component {
});
if (dateRemoved) {
this.args.updateOptions(this.args.options.filter(
({ value }) =>
DateTime.fromISO(value).toISODate() !== dateRemoved.toISODate()
).map(({ value }) => value),
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."
'No date has been added or removed. This cannot be the case. Something spooky is going on.'
);
}
@action
updateCalenderCenter(diff) {
this.calendarCenter = this.calendarCenter.add(diff, "months");
this.calendarCenter = this.calendarCenter.add(diff, 'months');
}
}

View file

@ -1,10 +1,10 @@
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 { DateTime } from "luxon";
import IntlMessage from "../utils/intl-message";
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 { DateTime } from 'luxon';
import IntlMessage from '../utils/intl-message';
class FormDataOption {
formData;
@ -23,7 +23,7 @@ class FormDataOption {
const { isPartiallyFilled } = this;
if (isPartiallyFilled) {
return new IntlMessage(
"create.options-datetime.error.partiallyFilledTime"
'create.options-datetime.error.partiallyFilledTime'
);
}
@ -37,7 +37,7 @@ class FormDataOption {
.slice(0, optionsForThisDay.indexOf(this))
.any((option) => option.time == this.time);
if (isDuplicate) {
return new IntlMessage("create.options-datetime.error.duplicatedDate");
return new IntlMessage('create.options-datetime.error.duplicatedDate');
}
return null;
@ -73,7 +73,7 @@ class FormData {
const { options } = this;
const allOptionsAreValid = options.every((option) => option.isValid);
if (!allOptionsAreValid) {
return IntlMessage("create.options-datetime.error.invalidTime");
return IntlMessage('create.options-datetime.error.invalidTime');
}
return null;
@ -172,7 +172,7 @@ export default class CreateOptionsDatetime extends Component {
if (!successful) {
this.errorMesage =
"create.options-datetime.fix-validation-errors-first-day";
'create.options-datetime.fix-validation-errors-first-day';
}
}
@ -207,7 +207,7 @@ export default class CreateOptionsDatetime extends Component {
@action
handleTransition(transition) {
if (transition.from?.name === "create.options-datetime") {
if (transition.from?.name === 'create.options-datetime') {
this.args.updateOptions(this.formData.options);
this.router.off('routeWillChange', this.handleTransition);
}
@ -216,6 +216,6 @@ export default class CreateOptionsDatetime extends Component {
constructor() {
super(...arguments);
this.router.on("routeWillChange", this.handleTransition);
this.router.on('routeWillChange', this.handleTransition);
}
}

View file

@ -1,9 +1,9 @@
import Component from "@glimmer/component";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import { TrackedArray } from "tracked-built-ins";
import IntlMessage from "../utils/intl-message";
import { tracked } from "@glimmer/tracking";
import Component from '@glimmer/component';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import { TrackedArray } from 'tracked-built-ins';
import IntlMessage from '../utils/intl-message';
import { tracked } from '@glimmer/tracking';
class FormDataOption {
@tracked value;
@ -14,7 +14,7 @@ class FormDataOption {
// Every option must have a label
if (!value) {
return new IntlMessage("create.options.error.valueMissing");
return new IntlMessage('create.options.error.valueMissing');
}
// Options must be unique. There must not be another option having the
@ -23,7 +23,7 @@ class FormDataOption {
.slice(0, this.formData.options.indexOf(this))
.some((option) => option.value === this.value);
if (!isUnique) {
return new IntlMessage("create.options.error.duplicatedOption");
return new IntlMessage('create.options.error.duplicatedOption');
}
return null;
@ -48,11 +48,11 @@ class FormData {
if (options.length < 1) {
// UI enforces that there is at least one option if poll type is `MakeAPoll`.
// This validation error can only happen if poll type is `FindADate`.
return new IntlMessage("create.options.error.notEnoughDates");
return new IntlMessage('create.options.error.notEnoughDates');
}
if (options.any((option) => !option.isValid)) {
return new IntlMessage("create.options.error.invalidOption");
return new IntlMessage('create.options.error.invalidOption');
}
return null;
@ -84,7 +84,7 @@ class FormData {
// enforce minimal options amount for poll of type MakeAPoll
if (this.options.length === 0 && defaultOptionCount > 0) {
this.updateOptions(["", ""]);
this.updateOptions(['', '']);
}
}
}
@ -109,15 +109,15 @@ export default class CreateOptionsComponent extends Component {
@action
handleTransition(transition) {
if (transition.from?.name === "create.options") {
if (transition.from?.name === 'create.options') {
this.args.updateOptions(this.formData.options);
this.router.off("routeWillChange", this.handleTransition);
this.router.off('routeWillChange', this.handleTransition);
}
}
constructor() {
super(...arguments);
this.router.on("routeWillChange", this.handleTransition);
this.router.on('routeWillChange', this.handleTransition);
}
}

View file

@ -1,4 +1,5 @@
<select class="language-select" {{on "change" this.handleChange}}>
{{! template-lint-disable require-input-label }}
<select class='language-select' {{on 'change' this.handleChange}}>
{{#each-in this.locales as |localeKey localeName|}}
<option value={{localeKey}} selected={{eq localeKey this.currentLocale}}>
{{localeName}}

View file

@ -19,7 +19,9 @@ export default class LanguageSelect extends Component {
handleChange(event) {
const locale = event.target.value;
this.intl.locale = locale.includes('-') ? [locale, locale.split('-')[0]] : [locale];
this.intl.locale = locale.includes('-')
? [locale, locale.split('-')[0]]
: [locale];
this.powerCalendar.locale = locale;
if (window.localStorage) {

View file

@ -1,5 +1,5 @@
import Component from "@glimmer/component";
import { DateTime } from "luxon";
import Component from '@glimmer/component';
import { DateTime } from 'luxon';
export default class PollEvaluationParticipantsTable extends Component {
get optionsPerDay() {

View file

@ -27,7 +27,7 @@ export default class PollEvaluationSummary extends Component {
return {
answers: copy(answers),
option,
score: 0
score: 0,
};
});
let bestOptions = [];
@ -56,12 +56,8 @@ export default class PollEvaluationSummary extends Component {
let bestScore = evaluation[0].score;
for (let i = 0; i < evaluation.length; i++) {
if (
bestScore === evaluation[i].score
) {
bestOptions.push(
evaluation[i]
);
if (bestScore === evaluation[i].score) {
bestOptions.push(evaluation[i]);
} else {
break;
}

View file

@ -13,12 +13,18 @@ export default class CreateController extends Controller {
get canEnterOptionsStep() {
let { title } = this.model;
return this.visitedSteps.has('options') &&
typeof title === 'string' && title.length >= 2;
return (
this.visitedSteps.has('options') &&
typeof title === 'string' &&
title.length >= 2
);
}
get canEnterOptionsDatetimeStep() {
return this.visitedSteps.has('options-datetime') && this.model.options.length >= 1;
return (
this.visitedSteps.has('options-datetime') &&
this.model.options.length >= 1
);
}
get canEnterSettingsStep() {

View file

@ -1,18 +1,18 @@
import Controller from "@ember/controller";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
export default class CreateIndex extends Controller {
@service router;
@action
submit() {
this.router.transitionTo("create.meta");
this.router.transitionTo('create.meta');
}
@action
handleTransition(transition) {
if (transition.from?.name === "create.index") {
if (transition.from?.name === 'create.index') {
const { poll, formData } = this.model;
poll.pollType = formData.pollType;
@ -22,6 +22,6 @@ export default class CreateIndex extends Controller {
constructor() {
super(...arguments);
this.router.on("routeWillChange", this.handleTransition);
this.router.on('routeWillChange', this.handleTransition);
}
}

View file

@ -1,23 +1,23 @@
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import Controller from "@ember/controller";
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import Controller from '@ember/controller';
export default class CreateMetaController extends Controller {
@service router;
@action
previousPage() {
this.router.transitionTo("create.index");
this.router.transitionTo('create.index');
}
@action
submit() {
this.router.transitionTo("create.options");
this.router.transitionTo('create.options');
}
@action
handleTransition(transition) {
if (transition.from?.name === "create.meta") {
if (transition.from?.name === 'create.meta') {
const { poll, formData } = this.model;
poll.title = formData.title;
@ -28,6 +28,6 @@ export default class CreateMetaController extends Controller {
constructor() {
super(...arguments);
this.router.on("routeWillChange", this.handleTransition);
this.router.on('routeWillChange', this.handleTransition);
}
}

View file

@ -1,5 +1,5 @@
import Controller from "@ember/controller";
import { action } from "@ember/object";
import Controller from '@ember/controller';
import { action } from '@ember/object';
import { DateTime } from 'luxon';
import { inject as service } from '@ember/service';
@ -8,21 +8,23 @@ export default class CreateOptionsDatetimeController extends Controller {
@action
nextPage() {
this.router.transitionTo("create.settings");
this.router.transitionTo('create.settings');
}
@action
previousPage() {
this.router.transitionTo("create.options");
this.router.transitionTo('create.options');
}
@action
updateOptions(options) {
this.model.options = options
.map(({ day, time }) => time ? DateTime.fromISO(`${day}T${time}`).toISO() : day)
.map(({ day, time }) =>
time ? DateTime.fromISO(`${day}T${time}`).toISO() : day
)
.sort()
.map((isoString) => {
return this.store.createFragment("option", { title: isoString });
return this.store.createFragment('option', { title: isoString });
});
}
}

View file

@ -1,6 +1,6 @@
import Controller from "@ember/controller";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import Controller from '@ember/controller';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
export default class CreateOptionsController extends Controller {
@service router;
@ -10,21 +10,21 @@ export default class CreateOptionsController extends Controller {
const { isFindADate } = this.model;
if (isFindADate) {
this.router.transitionTo("create.options-datetime");
this.router.transitionTo('create.options-datetime');
} else {
this.router.transitionTo("create.settings");
this.router.transitionTo('create.settings');
}
}
@action
previousPage() {
this.router.transitionTo("create.meta");
this.router.transitionTo('create.meta');
}
@action
updateOptions(newOptions) {
this.model.options = newOptions.map(({ value }) =>
this.store.createFragment("option", { title: value })
this.store.createFragment('option', { title: value })
);
}
}

View file

@ -39,16 +39,31 @@ export default class CreateSettings extends Controller {
set expirationDuration(value) {
this.model.expirationDate = isPresent(value)
? DateTime.local().plus(Duration.fromISO(value)).toISO()
: "";
: '';
}
get expirationDurations() {
return [
{ id: 'P7D', labelTranslation: 'create.settings.expirationDurations.P7D' },
{ id: 'P1M', labelTranslation: 'create.settings.expirationDurations.P1M' },
{ id: 'P3M', labelTranslation: 'create.settings.expirationDurations.P3M' },
{ id: 'P6M', labelTranslation: 'create.settings.expirationDurations.P6M' },
{ id: 'P1Y', labelTranslation: 'create.settings.expirationDurations.P1Y' },
{
id: 'P7D',
labelTranslation: 'create.settings.expirationDurations.P7D',
},
{
id: 'P1M',
labelTranslation: 'create.settings.expirationDurations.P1M',
},
{
id: 'P3M',
labelTranslation: 'create.settings.expirationDurations.P3M',
},
{
id: 'P6M',
labelTranslation: 'create.settings.expirationDurations.P6M',
},
{
id: 'P1Y',
labelTranslation: 'create.settings.expirationDurations.P1Y',
},
{ id: '', labelTranslation: 'create.settings.expirationDurations.never' },
];
}
@ -82,13 +97,16 @@ export default class CreateSettings extends Controller {
return option.hasTime;
})
) {
this.set('model.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone);
this.set(
'model.timezone',
Intl.DateTimeFormat().resolvedOptions().timeZone
);
}
// save poll
try {
await poll.save();
} catch(err) {
} catch (err) {
this.flashMessages.danger('error.poll.savingFailed');
throw err;

View file

@ -34,7 +34,9 @@ export default class PollController extends Controller {
if (isEmpty(expirationDate)) {
return false;
}
return DateTime.local().plus({ weeks: 2 }) >= DateTime.fromISO(expirationDate);
return (
DateTime.local().plus({ weeks: 2 }) >= DateTime.fromISO(expirationDate)
);
}
/*
@ -44,7 +46,10 @@ export default class PollController extends Controller {
const { model: poll } = this;
const { timezone: pollTimezone } = poll;
return isPresent(pollTimezone) && Intl.DateTimeFormat().resolvedOptions().timeZone !== pollTimezone;
return (
isPresent(pollTimezone) &&
Intl.DateTimeFormat().resolvedOptions().timeZone !== pollTimezone
);
}
get mustChooseTimezone() {
@ -85,7 +90,10 @@ export default class PollController extends Controller {
this.encryptionKey !== this.encryption.key
) {
// work-a-round for url not being updated
window.location.hash = window.location.hash.replace(this.encryptionKey, this.encryption.key);
window.location.hash = window.location.hash.replace(
this.encryptionKey,
this.encryption.key
);
this.set('encryptionKey', this.encryption.key);
}

View file

@ -53,14 +53,14 @@ export default class PollEvaluationController extends Controller {
}
// create lookup array
evaluation.forEach(function(value, index) {
evaluation.forEach(function (value, index) {
lookup[value.id] = index;
});
// loop over all users
poll.users.forEach((user) => {
// loop over all selections of the user
user.selections.forEach(function(selection, optionIndex) {
user.selections.forEach(function (selection, optionIndex) {
let answerIndex;
// get answer index by lookup array

View file

@ -1,17 +1,17 @@
import Controller, { inject as controller } from "@ember/controller";
import { inject as service } from "@ember/service";
import { action } from "@ember/object";
import config from "croodle/config/environment";
import { tracked } from "@glimmer/tracking";
import Controller, { inject as controller } from '@ember/controller';
import { inject as service } from '@ember/service';
import { action } from '@ember/object';
import config from 'croodle/config/environment';
import { tracked } from '@glimmer/tracking';
export default class PollParticipationController extends Controller {
@service encryption;
@service router;
@controller("poll")
@controller('poll')
pollController;
@tracked name = "";
@tracked name = '';
@tracked savingFailed = false;
@action
@ -31,7 +31,7 @@ export default class PollParticipationController extends Controller {
}
// map selection to answer if it's not freetext
let answer = answers.findBy("type", value);
let answer = answers.findBy('type', value);
let { icon, label, labelTranslation, type } = answer;
return {
@ -42,7 +42,7 @@ export default class PollParticipationController extends Controller {
};
});
let user = this.store.createRecord("user", {
let user = this.store.createRecord('user', {
creationDate: new Date(),
name,
poll,
@ -70,7 +70,7 @@ export default class PollParticipationController extends Controller {
return;
}
this.router.transitionTo("poll.evaluation", poll.id, {
this.router.transitionTo('poll.evaluation', poll.id, {
queryParams: { encryptionKey: this.encryption.key },
});
}

View file

@ -1,2 +1 @@
export default {
};
export default {};

View file

@ -2,7 +2,6 @@ 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;

View file

@ -7,24 +7,29 @@ function elementIsNotVisible(element) {
let windowHeight = window.innerHeight;
// an element is not visible if
return false ||
// it's above the current view port
elementPosition.top <= 0 ||
// it's below the current view port
elementPosition.bottom >= windowHeight ||
// it's in current view port but hidden by fixed navigation
(
getComputedStyle(document.querySelector('.cr-steps-bottom-nav')).position === 'fixed' &&
elementPosition.bottom >= windowHeight - document.querySelector('.cr-steps-bottom-nav').offsetHeight
);
return (
false ||
// it's above the current view port
elementPosition.top <= 0 ||
// it's below the current view port
elementPosition.bottom >= windowHeight ||
// it's in current view port but hidden by fixed navigation
(getComputedStyle(document.querySelector('.cr-steps-bottom-nav'))
.position === 'fixed' &&
elementPosition.bottom >=
windowHeight -
document.querySelector('.cr-steps-bottom-nav').offsetHeight)
);
}
export function scrollFirstInvalidElementIntoViewPort() {
// `schedule('afterRender', function() {})` would be more approperiate but there seems to be a
// timing issue in Firefox causing the Browser not scrolling up far enough if doing so
// delaying to next runloop therefore
next(function() {
let invalidInput = document.querySelector('.form-control.is-invalid, .custom-control-input.is-invalid');
next(function () {
let invalidInput = document.querySelector(
'.form-control.is-invalid, .custom-control-input.is-invalid'
);
assert(
'Atleast one form control must be marked as invalid if form submission was rejected as invalid',
invalidInput
@ -42,7 +47,12 @@ export function scrollFirstInvalidElementIntoViewPort() {
// As a work-a-round we look the correct label up by a custom convention for the `id` of the
// inputs and the `for` of the input group `<label>` (which should be a `<legend>`).
let scrollTarget =
document.querySelector(`label[for="${invalidInput.id.substr(0, invalidInput.id.indexOf('_'))}"`) ||
document.querySelector(
`label[for="${invalidInput.id.substr(
0,
invalidInput.id.indexOf('_')
)}"`
) ||
document.querySelector(`label[for="${invalidInput.id}"]`) ||
// For polls with type `MakeAPoll` the option inputs do not have a label at all. In that case
// we scroll to the input element itself
@ -53,6 +63,6 @@ export function scrollFirstInvalidElementIntoViewPort() {
});
}
export default helper(function() {
export default helper(function () {
return scrollFirstInvalidElementIntoViewPort;
});

View file

@ -10,16 +10,16 @@ export default {
let availableLocales = Object.keys(localesMeta);
let locale = getLocale(availableLocales);
intl.set('locale', locale.includes('-') ? [locale, locale.split('-')[0]] : [locale]);
intl.set(
'locale',
locale.includes('-') ? [locale, locale.split('-')[0]] : [locale]
);
powerCalendar.set('local', locale);
}
},
};
function getLocale(availableLocales) {
let methods = [
getSavedLocale,
getLocaleByBrowser
];
let methods = [getSavedLocale, getLocaleByBrowser];
let locale;
methods.any((method) => {

View file

@ -1,10 +1,10 @@
export default {
'ca': 'catalan',
'de': 'deutsch',
'en': 'english',
ca: 'catalan',
de: 'deutsch',
en: 'english',
'en-gb': 'english (GB)',
'es': 'español',
'fr': 'français',
'it': 'italiano',
'nb': 'norsk',
es: 'español',
fr: 'français',
it: 'italiano',
nb: 'norsk',
};

View file

@ -40,8 +40,7 @@ export default class Option extends Fragment {
}
get hasTime() {
return this.isDate &&
this.title.length >= 'YYYY-MM-DDTHH:mm'.length;
return this.isDate && this.title.length >= 'YYYY-MM-DDTHH:mm'.length;
}
get time() {
@ -67,6 +66,11 @@ export default class Option extends Fragment {
if (!datetime.isValid) {
return;
}
this.set('title', this.datetime.set({ hours: datetime.hour, minutes: datetime.minute }).toISO());
this.set(
'title',
this.datetime
.set({ hours: datetime.hour, minutes: datetime.minute })
.toISO()
);
}
}

View file

@ -29,13 +29,13 @@ export default class Poll extends Model {
// polls description
@attr('string', {
defaultValue: ''
defaultValue: '',
})
description;
// ISO 8601 date + time string in UTC
@attr('string', {
includePlainOnCreate: 'serverExpirationDate'
includePlainOnCreate: 'serverExpirationDate',
})
expirationDate;
@ -61,7 +61,7 @@ export default class Poll extends Model {
// Croodle version poll got created with
@attr('string', {
encrypted: false
encrypted: false,
})
version;

View file

@ -1,8 +1,6 @@
import classic from 'ember-classic-decorator';
import Model, { belongsTo, attr } from '@ember-data/model';
import {
fragmentArray
} from 'ember-data-model-fragments/attributes';
import { fragmentArray } from 'ember-data-model-fragments/attributes';
@classic
export default class User extends Model {
@ -30,7 +28,7 @@ export default class User extends Model {
// Croodle version user got created with
@attr('string', {
encrypted: false
encrypted: false,
})
version;
}

View file

@ -1,6 +1,10 @@
import { modifier } from 'ember-modifier';
export default modifier(function autofocus(element, params, { enabled = true }) {
export default modifier(function autofocus(
element,
params,
{ enabled = true }
) {
if (!enabled) {
return;
}

View file

@ -6,12 +6,12 @@ export default class Router extends EmberRouter {
rootURL = config.rootURL;
}
Router.map(function() {
this.route('poll', { path: '/poll/:poll_id' }, function() {
Router.map(function () {
this.route('poll', { path: '/poll/:poll_id' }, function () {
this.route('participation');
this.route('evaluation');
});
this.route('create', function() {
this.route('create', function () {
this.route('meta');
this.route('options');
this.route('options-datetime');

View file

@ -1,5 +1,5 @@
import Route from '@ember/routing/route';
import { tracked } from "@glimmer/tracking";
import { tracked } from '@glimmer/tracking';
class FormData {
@tracked pollType;

View file

@ -10,7 +10,9 @@ class FormData {
const { title } = this;
if (!title) {
return new IntlMessage('create.meta.input.title.validations.valueMissing');
return new IntlMessage(
'create.meta.input.title.validations.valueMissing'
);
}
if (title.length < 2) {

View file

@ -27,8 +27,8 @@ export default class PollRoute extends Route {
if (transition.targetName === 'poll.index') {
this.transitionTo('poll.participation', poll, {
queryParams: {
encryptionKey: this.encryption.key
}
encryptionKey: this.encryption.key,
},
});
}
}

View file

@ -1,7 +1,7 @@
import Route from "@ember/routing/route";
import { tracked } from "@glimmer/tracking";
import { TrackedArray } from "tracked-built-ins";
import IntlMessage from "../../utils/intl-message";
import Route from '@ember/routing/route';
import { tracked } from '@glimmer/tracking';
import { TrackedArray } from 'tracked-built-ins';
import IntlMessage from '../../utils/intl-message';
class FormDataSelections {
@tracked value = null;
@ -11,7 +11,7 @@ class FormDataSelections {
const { value, valueIsRequired } = this;
if (!value && valueIsRequired) {
return new IntlMessage("poll.error.selection.valueMissing");
return new IntlMessage('poll.error.selection.valueMissing');
}
return null;
@ -36,12 +36,12 @@ class FormData {
const { name, nameIsRequired, namesTaken } = this;
if (!name && nameIsRequired) {
return new IntlMessage("poll.error.name.valueMissing");
return new IntlMessage('poll.error.name.valueMissing');
}
// TODO: Validate that name is unique for this poll
if (namesTaken.includes(name)) {
return new IntlMessage("poll.error.name.duplicate");
return new IntlMessage('poll.error.name.duplicate');
}
return null;
@ -68,7 +68,7 @@ class FormData {
export default class ParticipationRoute extends Route {
model() {
const poll = this.modelFor("poll");
const poll = this.modelFor('poll');
const { anonymousUser, forceAnswer, options, users } = poll;
const formData = new FormData(options, {
nameIsRequired: !anonymousUser,

View file

@ -27,13 +27,13 @@ export default class ApplicationSerializer extends RESTSerializer {
* implement decryption
*/
normalize(modelClass, resourceHash, prop) {
// run before serialization of attribute hash
modelClass.eachAttribute(function(key, attributes) {
if (
attributes.options.encrypted !== false
) {
if (typeof resourceHash[key] !== 'undefined' && resourceHash[key] !== null) {
modelClass.eachAttribute(function (key, attributes) {
if (attributes.options.encrypted !== false) {
if (
typeof resourceHash[key] !== 'undefined' &&
resourceHash[key] !== null
) {
resourceHash[key] = this.encryption.decrypt(resourceHash[key]);
}
}
@ -63,9 +63,7 @@ export default class ApplicationSerializer extends RESTSerializer {
}
// encrypt after serialization of attribute hash
if (
attribute.options.encrypted !== false
) {
if (attribute.options.encrypted !== false) {
json[key] = this.encryption.encrypt(json[key]);
}
}

View file

@ -2,11 +2,13 @@ import { EmbeddedRecordsMixin } from '@ember-data/serializer/rest';
import { isEmpty } from '@ember/utils';
import ApplicationSerializer from './application';
export default class PollSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) {
export default class PollSerializer extends ApplicationSerializer.extend(
EmbeddedRecordsMixin
) {
attrs = {
users: {
deserialize: 'records'
}
deserialize: 'records',
},
};
legacySupport(resourceHash) {

View file

@ -14,17 +14,17 @@ export default class UserSerializer extends ApplicationSerializer {
* and selection property "type" where named "id"
*/
if (!isEmpty(resourceHash.selections[0].value)) {
resourceHash.selections.forEach(function(selection, index) {
resourceHash.selections.forEach(function (selection, index) {
if (typeof selection.value === 'string') {
resourceHash.selections[index] = {
label: selection.value
label: selection.value,
};
} else {
resourceHash.selections[index] = {
icon: selection.value.icon,
label: selection.value.label,
labelTranslation: selection.value.labelTranslation,
type: selection.value.id
type: selection.value.id,
};
}
});

View file

@ -8,19 +8,11 @@ export default class EncryptionService extends Service {
key = null;
decrypt(value) {
return JSON.parse(
sjcl.decrypt(
this.key,
value
)
);
return JSON.parse(sjcl.decrypt(this.key, value));
}
encrypt(value) {
return sjcl.encrypt(
this.key,
JSON.stringify(value)
);
return sjcl.encrypt(this.key, JSON.stringify(value));
}
generateKey() {

View file

@ -13,7 +13,7 @@
</div>
</nav>
<main role="main" class="container cr-main">
<main class="container cr-main">
<div id="messages">
{{#each this.flashMessages.queue as |flash|}}
<FlashMessage @flash={{flash}}>

View file

@ -1,46 +1,46 @@
{{#let @model as |poll|}}
{{page-title poll.title}}
<div id="poll">
<div class="row">
<div class="col-sm-6 col-lg-5">
<div class="box meta-data">
<h2 class="title">{{poll.title}}</h2>
<p class="description">{{poll.description}}</p>
<p class="dates">
<span class="creationDate">
<div id='poll'>
<div class='row'>
<div class='col-sm-6 col-lg-5'>
<div class='box meta-data'>
<h2 class='title'>{{poll.title}}</h2>
<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"
'poll.created-date'
date=(format-date
poll.creationDate
weekday="long"
day="numeric"
month="long"
year="numeric"
hour="numeric"
minute="numeric"
weekday='long'
day='numeric'
month='long'
year='numeric'
hour='numeric'
minute='numeric'
)
}}
</span>
{{#if poll.expirationDate}}
<br />
<span class="expirationDate">
<span class='expirationDate'>
{{!
TODO: Simplify to dateStyle="full" and timeStyle="short" after upgrading to Ember Intl v6
}}
{{t
"poll.expiration-date"
'poll.expiration-date'
date=(format-date
poll.expirationDate
weekday="long"
day="numeric"
month="long"
year="numeric"
hour="numeric"
minute="numeric"
weekday='long'
day='numeric'
month='long'
year='numeric'
hour='numeric'
minute='numeric'
)
}}
</span>
@ -48,40 +48,40 @@
</p>
</div>
</div>
<div class="col-sm-6 col-lg-6 offset-lg-1">
<div class="box poll-link cr-poll-link">
<p>{{t "poll.share.title"}}</p>
<p class="link cr-poll-link__link">
<div class='col-sm-6 col-lg-6 offset-lg-1'>
<div class='box poll-link cr-poll-link'>
<p>{{t 'poll.share.title'}}</p>
<p class='link cr-poll-link__link'>
<small>
<code class="cr-poll-link__url">{{this.pollUrl}}</code>
<code class='cr-poll-link__url'>{{this.pollUrl}}</code>
</small>
<CopyButton
@clipboardText={{this.pollUrl}}
@error={{action "linkAction" "selected"}}
@success={{action "linkAction" "copied"}}
class="btn btn-secondary cr-poll-link__copy-btn btn-sm"
@error={{action 'linkAction' 'selected'}}
@success={{action 'linkAction' 'copied'}}
class='btn btn-secondary cr-poll-link__copy-btn btn-sm'
>
{{t "poll.link.copy-label"}}&nbsp;
{{t 'poll.link.copy-label'}}&nbsp;
<span
class="oi oi-clipboard"
title={{t "poll.link.copy-label"}}
aria-hidden="true"
class='oi oi-clipboard'
title={{t 'poll.link.copy-label'}}
aria-hidden='true'
></span>
</CopyButton>
</p>
<small class="text-muted">
{{t "poll.share.notice"}}
<small class='text-muted'>
{{t 'poll.share.notice'}}
</small>
</div>
</div>
</div>
{{#if this.showExpirationWarning}}
<div class="row">
<div class="col-xs-12">
<BsAlert @type="warning" class="expiration-warning">
<div class='row'>
<div class='col-xs-12'>
<BsAlert @type='warning' class='expiration-warning'>
{{t
"poll.expiration-date-warning"
'poll.expiration-date-warning'
timeToNow=(format-date-relative poll.expirationDate)
}}
</BsAlert>
@ -89,34 +89,39 @@
</div>
{{/if}}
<div class="box">
<ul class="nav nav-tabs" role="tablist">
<div class='box'>
<ul class='nav nav-tabs' role='tablist'>
{{! template-lint-disable no-unknown-arguments-for-builtin-components }}
{{!
TODO: Refactor to current Bootstrap markup, which uses a regular
`<a></a>` element within a `<li class="nav-item"></li>`.
}}
<LinkTo
@route="poll.participation"
@route='poll.participation'
@model={{poll}}
@tagName="li"
@activeClass="active"
class="participation nav-item"
@tagName='li'
@activeClass='active'
class='participation nav-item'
>
<LinkTo @route="poll.participation" @model={{poll}} class="nav-link">
{{t "poll.tab-title.participation"}}
<LinkTo @route='poll.participation' @model={{poll}} class='nav-link'>
{{t 'poll.tab-title.participation'}}
</LinkTo>
</LinkTo>
<LinkTo
@route="poll.evaluation"
@route='poll.evaluation'
@model={{poll}}
@tagName="li"
@activeClass="active"
class="evaluation nav-item"
@tagName='li'
@activeClass='active'
class='evaluation nav-item'
>
<LinkTo @route="poll.evaluation" @model={{poll}} class="nav-link">
{{t "poll.tab-title.evaluation"}}
<LinkTo @route='poll.evaluation' @model={{poll}} class='nav-link'>
{{t 'poll.tab-title.evaluation'}}
</LinkTo>
</LinkTo>
</ul>
<div class="tab-content">
<div role="tabpanel" class="tab-pane active">
<div class='tab-content'>
<div role='tabpanel' class='tab-pane active'>
{{outlet}}
</div>
</div>
@ -125,32 +130,32 @@
{{/let}}
<BsModal
@title={{t "poll.modal.timezoneDiffers.title"}}
@title={{t 'poll.modal.timezoneDiffers.title'}}
@open={{this.mustChooseTimezone}}
@footer={{false}}
@closeButton={{false}}
@keyboard={{false}}
@autoClose={{false}}
data-test-modal="choose-timezone"
data-test-modal='choose-timezone'
as |modal|
>
<modal.body>
<p>
{{t "poll.modal.timezoneDiffers.body"}}
{{t 'poll.modal.timezoneDiffers.body'}}
</p>
</modal.body>
<modal.footer>
<BsButton
@onClick={{this.useLocalTimezone}}
data-test-button="use-local-timezone"
data-test-button='use-local-timezone'
>
{{t "poll.modal.timezoneDiffers.button.useLocalTimezone"}}
{{t 'poll.modal.timezoneDiffers.button.useLocalTimezone'}}
</BsButton>
<BsButton
@onClick={{action (mut this.timezoneChoosen) true}}
data-test-button="use-poll-timezone"
data-test-button='use-poll-timezone'
>
{{t "poll.modal.timezoneDiffers.button.usePollTimezone"}}
</BsButton>>
{{t 'poll.modal.timezoneDiffers.button.usePollTimezone'}}
</BsButton>
</modal.footer>
</BsModal>

View file

@ -1,19 +1,19 @@
import { assert } from '@ember/debug';
export default function(answerType) {
export default function (answerType) {
switch (answerType) {
case 'YesNo':
return [
{
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up'
icon: 'glyphicon glyphicon-thumbs-up',
},
{
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down'
}
icon: 'glyphicon glyphicon-thumbs-down',
},
];
case 'YesNoMaybe':
@ -21,18 +21,18 @@ export default function(answerType) {
{
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up'
icon: 'glyphicon glyphicon-thumbs-up',
},
{
type: 'maybe',
labelTranslation: 'answerTypes.maybe.label',
icon: 'glyphicon glyphicon-hand-right'
icon: 'glyphicon glyphicon-hand-right',
},
{
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down'
}
icon: 'glyphicon glyphicon-thumbs-down',
},
];
case 'FreeText':

View file

@ -7,9 +7,10 @@
* implementation by Aaron Toponce:
* https://pthree.org/2014/06/25/cryptographically-secure-passphrases-in-d-note/
*/
export default function(length) {
export default function (length) {
let passphrase = '';
const possible = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const possible =
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
const randomArray = new Uint32Array(length);
// Make some attempt at preferring a strong CSPRNG first
@ -22,12 +23,12 @@ export default function(length) {
window.msCrypto.getRandomValues(randomArray);
} else {
// Android browser, IE Mobile, Opera Mobile, older desktop browsers
for (let i = length; i--;) {
for (let i = length; i--; ) {
randomArray[i] = Math.floor(Math.random() * Math.pow(2, 32));
}
}
for (let j = length; j--;) {
for (let j = length; j--; ) {
passphrase += possible.charAt(Math.floor(randomArray[j] % possible.length));
}

View file

@ -5,12 +5,12 @@ module.exports = {
javascript: {
pattern: 'assets/*.js',
limit: '430KB',
compression: 'gzip'
compression: 'gzip',
},
css: {
pattern: 'assets/*.css',
limit: '16KB',
compression: 'gzip'
}
}
compression: 'gzip',
},
},
};

View file

@ -8,7 +8,7 @@ module.exports = function () {
'script-src': ["'self'"],
'font-src': ["'self'"],
'connect-src': ["'self'"],
'img-src': ["'self'", "data:"],
'img-src': ["'self'", 'data:'],
'style-src': ["'self'"],
'media-src': ["'none'"],
},

View file

@ -2,8 +2,8 @@
self.deprecationWorkflow = self.deprecationWorkflow || {};
self.deprecationWorkflow.config = {
workflow: [
{ handler: "silence", matchId: "ember-cli-page-object.old-collection-api" },
{ handler: "silence", matchId: "deprecate-fetch-ember-data-support" },
{ handler: "silence", matchId: "ember-runtime.deprecate-copy-copyable" }
]
{ handler: 'silence', matchId: 'ember-cli-page-object.old-collection-api' },
{ handler: 'silence', matchId: 'deprecate-fetch-ember-data-support' },
{ handler: 'silence', matchId: 'ember-runtime.deprecate-copy-copyable' },
],
};

View file

@ -3,7 +3,7 @@
"packages": [
{
"name": "ember-cli",
"version": "3.20.2",
"version": "3.28.6",
"blueprints": [
{
"name": "app",

View file

@ -1,6 +1,6 @@
/*jshint node:true*/
module.exports = function(/* environment */) {
module.exports = function (/* environment */) {
return {
/**
* The locales that the application needs to support.
@ -27,7 +27,7 @@ module.exports = function(/* environment */) {
* @type {String?}
* @default "null"
*/
fallbackLocale: "en",
fallbackLocale: 'en',
/**
* Path where translations are kept. This is relative to the project root.
@ -120,6 +120,6 @@ module.exports = function(/* environment */) {
*/
requiresTranslation(/* key, locale */) {
return true;
}
},
};
};

View file

@ -1,6 +1,6 @@
'use strict';
module.exports = function(environment) {
module.exports = function (environment) {
let ENV = {
modulePrefix: 'croodle',
environment,
@ -15,8 +15,8 @@ module.exports = function(environment) {
Array: true,
Date: false,
String: false,
Function: true
}
Function: true,
},
},
APP: {

View file

@ -2,15 +2,11 @@
const browsers = [
'last 2 Chrome versions',
// Last Edge versions are based on Chrome but not shipped to a significant
// number of users. Change to `last 2 Edge versions` as soon as Chrome-based
// Edge is shipped to all users.
'Edge >= 18',
'last 2 Firefox versions',
'Firefox ESR',
'last 2 Safari versions',
];
module.exports = {
browsers
browsers,
};

View file

@ -2,7 +2,7 @@
const EmberApp = require('ember-cli/lib/broccoli/ember-app');
module.exports = function(defaults) {
module.exports = function (defaults) {
let app = new EmberApp(defaults, {
autoImport: {
forbidEval: true,
@ -11,21 +11,27 @@ module.exports = function(defaults) {
// sjcl requires node's cryto library, which isn't needed
// in Browser but causes webpack to bundle a portable version
// which increases the build size by an inacceptable amount
crypto: "null",
crypto: 'null',
},
},
},
'buildInfoOptions': {
'metaTemplate': 'version={SEMVER}'
buildInfoOptions: {
metaTemplate: 'version={SEMVER}',
},
'ember-bootstrap': {
importBootstrapCSS: false,
'bootstrapVersion': 4,
'importBootstrapFont': false,
whitelist: ['bs-alert', 'bs-button', 'bs-button-group', 'bs-form', 'bs-modal'],
bootstrapVersion: 4,
importBootstrapFont: false,
whitelist: [
'bs-alert',
'bs-button',
'bs-button-group',
'bs-form',
'bs-modal',
],
},
'ember-cli-babel': {
includePolyfill: true
includePolyfill: true,
},
'ember-composable-helpers': {
only: ['array', 'object-at', 'pick'],
@ -36,7 +42,7 @@ module.exports = function(defaults) {
autoprefixer: {
browsers: ['last 2 ios version'],
cascade: false,
sourcemap: true
sourcemap: true,
},
sassOptions: {
sourceMapEmbed: true,

View file

@ -1,6 +1,6 @@
module.exports = {
env: {
node: true,
browser: false
}
browser: false,
},
};

View file

@ -41,15 +41,19 @@ module.exports = {
})
.then(() => {
return new Promise((resolve, reject) => {
exec('composer install --no-dev', {
cwd: outputPath
}, (err) => {
if (err) {
reject(err);
}
exec(
'composer install --no-dev',
{
cwd: outputPath,
},
(err) => {
if (err) {
reject(err);
}
resolve();
});
resolve();
}
);
});
})
.then(() => {
@ -58,5 +62,5 @@ module.exports = {
unlink(`${outputPath}/composer.lock`),
]);
});
}
},
};

View file

@ -1,6 +1,6 @@
export default function() {
this.namespace = '/api/index.php'; // make this `api`, for example, if your API is namespaced
this.timing = 400; // delay for each request, automatically set to 0 during testing
export default function () {
this.namespace = '/api/index.php'; // make this `api`, for example, if your API is namespaced
this.timing = 400; // delay for each request, automatically set to 0 during testing
this.get('/polls/:id');
this.post('/polls');

View file

@ -9,14 +9,14 @@ export default Factory.extend({
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Ja'
label: 'Ja',
},
{
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'Nein'
}
label: 'Nein',
},
],
answerType: 'YesNo',
creationDate: '2015-04-01T11:11:11.111Z',
@ -27,11 +27,11 @@ export default Factory.extend({
isDateTime: false,
options: [
{
title: '2017-12-24'
title: '2017-12-24',
},
{
title: '2018-01-01'
}
title: '2018-01-01',
},
],
pollType: 'FindADate',
title: 'default title',
@ -50,8 +50,8 @@ export default Factory.extend({
'options',
'pollType',
'timezone',
'title'
'title',
];
encrypt(propertiesToEncrypt, poll, server);
}
},
});

View file

@ -3,15 +3,11 @@ import { Factory } from 'ember-cli-mirage';
import encrypt from '../utils/encrypt';
export default Factory.extend({
creationDate: (new Date()).toISOString(),
creationDate: new Date().toISOString(),
name: 'John Doe',
selections: [],
afterCreate(user, server) {
let propertiesToEncrypt = [
'creationDate',
'name',
'selections'
];
let propertiesToEncrypt = ['creationDate', 'name', 'selections'];
encrypt(propertiesToEncrypt, user, server);
}
},
});

View file

@ -51,6 +51,6 @@ export default class {
* @public
*/
reset() {
this._ids = {};
this._ids = {};
}
}

View file

@ -1,5 +1,5 @@
import { Model, hasMany } from 'ember-cli-mirage';
export default Model.extend({
users: hasMany('user')
users: hasMany('user'),
});

View file

@ -1,5 +1,5 @@
import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({
poll: belongsTo('poll')
poll: belongsTo('poll'),
});

View file

@ -1,11 +1,9 @@
export default function(/* server */) {
export default function (/* server */) {
/*
Seed your development database using your factories.
This data will not be loaded in your tests.
Make sure to define a factory for each model you want to create.
*/
// server.createList('post', 10);
}

View file

@ -11,12 +11,13 @@ export default RestSerializer.extend({
normalize(payload) {
let [type] = Object.keys(payload);
let attrs = payload[type];
let { belongsToAssociations, hasManyAssociations } = this.registry.schema._registry[type].class.prototype;
let { belongsToAssociations, hasManyAssociations } =
this.registry.schema._registry[type].class.prototype;
let jsonApiPayload = {
data: {
type: pluralize(type)
}
type: pluralize(type),
},
};
Object.keys(attrs).forEach((key) => {
@ -32,8 +33,12 @@ export default RestSerializer.extend({
jsonApiPayload.data.relationships = {};
}
let association = belongsToAssociations.hasOwnProperty(key) ? belongsToAssociations[key] : hasManyAssociations[key];
let associationType = belongsToAssociations.hasOwnProperty(key) ? 'belongsTo' : 'hasMany';
let association = belongsToAssociations.hasOwnProperty(key)
? belongsToAssociations[key]
: hasManyAssociations[key];
let associationType = belongsToAssociations.hasOwnProperty(key)
? 'belongsTo'
: 'hasMany';
let associationModel = association.modelName;
let relationshipObject = {};
@ -41,7 +46,7 @@ export default RestSerializer.extend({
case 'belongsTo':
relationshipObject.data = {
type: associationModel,
id: attrs[key]
id: attrs[key],
};
break;
case 'hasMany':
@ -49,7 +54,7 @@ export default RestSerializer.extend({
attrs[key].forEach((value) => {
relationshipObject.data.push({
type: associationModel,
id: value
id: value,
});
});
break;
@ -68,5 +73,5 @@ export default RestSerializer.extend({
return jsonApiPayload;
},
serializeIds: 'always'
serializeIds: 'always',
});

View file

@ -3,5 +3,5 @@ import ApplicationSerializer from './application';
export default ApplicationSerializer.extend({
embed: true,
include: ['users']
include: ['users'],
});

View file

@ -9,19 +9,20 @@ import { isArray } from '@ember/array';
import { get } from '@ember/object';
import sjcl from 'sjcl';
export default function(propertiesToEncrypt, model) {
export default function (propertiesToEncrypt, model) {
assert('first argument must be an array', isArray(propertiesToEncrypt));
assert('model must have an encryptionKey property which isn\'t empty', isPresent(get(model, 'encryptionKey')));
assert(
"model must have an encryptionKey property which isn't empty",
isPresent(model.encryptionKey)
);
let passphrase = get(model, 'encryptionKey');
let passphrase = model.encryptionKey;
let data = {
encryptionKey: undefined
encryptionKey: undefined,
};
propertiesToEncrypt.forEach((propertyToEncrypt) => {
let value = JSON.stringify(
get(model, propertyToEncrypt)
);
let value = JSON.stringify(get(model, propertyToEncrypt));
data[propertyToEncrypt] = sjcl.encrypt(passphrase, value);
});

View file

@ -11,20 +11,24 @@
},
"scripts": {
"build": "ember build --environment=production",
"lint": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*",
"lint": "npm-run-all --aggregate-output --continue-on-error --parallel \"lint:!(fix)\"",
"lint:fix": "npm-run-all --aggregate-output --continue-on-error --parallel lint:*:fix",
"lint:hbs": "ember-template-lint .",
"lint:js": "eslint .",
"lint:hbs:fix": "ember-template-lint . --fix",
"lint:js": "eslint . --cache",
"release": "release-it",
"lint:js:fix": "eslint . --fix",
"start": "ember serve",
"test": "npm-run-all lint:* test:*",
"test": "npm-run-all lint test:*",
"test:ember": "ember test",
"test:bundlesize": "ember bundlesize:test",
"test:csp-header": "grep \"`ember csp-headers --environment production --silent 2>&1 | sed 's/ $//'`\" public/.htaccess || (echo \"CSP headers in public/.htaccess does not match configuration\" && exit 1)"
},
"devDependencies": {
"@ember/optional-features": "^2.0.0",
"@glimmer/component": "^1.0.1",
"@glimmer/tracking": "^1.0.0",
"@ember/test-helpers": "^2.6.0",
"@glimmer/component": "^1.0.4",
"@glimmer/tracking": "^1.0.4",
"babel-eslint": "^10.1.0",
"bootstrap": "^4.3.1",
"broccoli-asset-rev": "^3.0.0",
@ -32,10 +36,10 @@
"ember-awesome-macros": "^6.0.0",
"ember-bootstrap": "^5.0.0",
"ember-classic-decorator": "^3.0.0",
"ember-cli": "~3.28.0",
"ember-cli": "~3.28.6",
"ember-cli-acceptance-test-helpers": "^1.0.0",
"ember-cli-app-version": "^6.0.0",
"ember-cli-babel": "^7.21.0",
"ember-cli-babel": "^7.26.10",
"ember-cli-browser-navigation-button-test-helper": "^0.3.0",
"ember-cli-browserstack": "^2.0.0",
"ember-cli-bundlesize": "^0.3.0",
@ -44,43 +48,48 @@
"ember-cli-dependency-checker": "^3.2.0",
"ember-cli-deprecation-workflow": "^2.0.0",
"ember-cli-flash": "^2.0.0",
"ember-cli-htmlbars": "^5.2.0",
"ember-cli-inject-live-reload": "^2.0.2",
"ember-cli-htmlbars": "^5.7.2",
"ember-cli-inject-live-reload": "^2.1.0",
"ember-cli-mirage": "^2.0.0",
"ember-cli-page-object": "^1.11.0",
"ember-cli-sass": "^10.0.0",
"ember-cli-sri": "^2.1.1",
"ember-cli-uglify": "^3.0.0",
"ember-cli-terser": "^4.0.2",
"ember-composable-helpers": "^4.0.0",
"ember-data": "~3.28.0",
"ember-data": "~3.28.6",
"ember-data-model-fragments": "^6.0.0",
"ember-decorators": "^6.1.1",
"ember-export-application-global": "^2.0.1",
"ember-fetch": "^8.0.1",
"ember-fetch": "^8.1.1",
"ember-intl": "^4.2.2",
"ember-load-initializers": "^2.1.1",
"ember-load-initializers": "^2.1.2",
"ember-math-helpers": "^2.8.1",
"ember-maybe-import-regenerator": "^0.1.6",
"ember-modifier": "^3.2.7",
"ember-page-title": "^6.0.0",
"ember-page-title": "^6.2.2",
"ember-power-calendar": "^0.20.0",
"ember-power-calendar-luxon": "^0.5.0",
"ember-qunit": "^4.6.0",
"ember-resolver": "^8.0.0",
"ember-source": "~3.28.0",
"ember-template-lint": "^2.9.1",
"ember-qunit": "^5.1.5",
"ember-resolver": "^8.0.3",
"ember-source": "~3.28.8",
"ember-template-lint": "^3.15.0",
"ember-test-selectors": "^5.0.0",
"ember-transition-helper": "^1.0.0",
"ember-truth-helpers": "^3.0.0",
"eslint": "^7.5.0",
"eslint-plugin-ember": "^8.9.1",
"eslint": "^7.32.0",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-ember": "^10.5.8",
"eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-qunit": "^6.2.0",
"fs-extra": "^9.0.0",
"lerna-changelog": "^1.0.0",
"loader.js": "^4.7.0",
"npm-run-all": "^4.1.5",
"open-iconic": "^1.1.1",
"qunit-dom": "^1.2.0",
"prettier": "^2.5.1",
"qunit": "^2.17.2",
"qunit-dom": "^1.6.0",
"release-it": "^16.0.0",
"release-it-lerna-changelog": "^5.0.0",
"sass": "^1.19.0",

View file

@ -15,9 +15,7 @@ header.style.color = '#B33A3A';
var icon = document.createElement('span');
icon.className = 'oi oi-warning';
header.appendChild(icon);
header.appendChild(
document.createTextNode('Your Browser is not supported!')
);
header.appendChild(document.createTextNode('Your Browser is not supported!'));
header.appendChild(icon.cloneNode());
container.appendChild(header);

View file

@ -6,14 +6,9 @@ module.exports = {
timeout: 1200,
browser_start_timeout: 2000,
browser_disconnect_timeout: 300,
launch_in_ci: [
'BS_MS_Edge',
'BS_Safari_Current',
],
launch_in_ci: ['BS_MS_Edge', 'BS_Safari_Current'],
'launch_in_dev': [
'Chrome'
],
launch_in_dev: ['Chrome'],
launchers: {
BS_Chrome_Current: {
@ -106,5 +101,5 @@ module.exports = {
],
protocol: 'browser',
},
}
},
};

View file

@ -3,13 +3,8 @@
module.exports = {
test_page: 'tests/index.html?hidepassed',
disable_watching: true,
launch_in_ci: [
'Chrome',
'Firefox',
],
launch_in_dev: [
'Chrome'
],
launch_in_ci: ['Chrome'],
launch_in_dev: ['Chrome'],
browser_start_timeout: 120,
browser_args: {
Chrome: {
@ -21,23 +16,18 @@ module.exports = {
'--disable-software-rasterizer',
'--mute-audio',
'--remote-debugging-port=0',
'--window-size=1440,900'
].filter(Boolean)
'--window-size=1440,900',
].filter(Boolean),
},
Firefox: {
ci: [
'--headless',
'--window-size=1440,900',
]
ci: ['--headless', '--window-size=1440,900'],
},
},
proxies: {
'/': {
target: 'http://localhost:4200',
onlyContentTypes: [
'json'
]
}
onlyContentTypes: ['json'],
},
},
launchers: {
SL_chrome: {
@ -54,7 +44,7 @@ module.exports = {
'--no-ct',
'--u',
],
protocol: 'browser'
protocol: 'browser',
},
SL_firefox: {
exe: 'ember',
@ -70,7 +60,7 @@ module.exports = {
'--no-ct',
'--u',
],
protocol: 'browser'
protocol: 'browser',
},
SL_edge: {
exe: 'ember',
@ -84,7 +74,7 @@ module.exports = {
'--no-ct',
'--u',
],
protocol: 'browser'
protocol: 'browser',
},
SL_ie: {
exe: 'ember',
@ -102,7 +92,7 @@ module.exports = {
'--no-ct',
'--u',
],
protocol: 'browser'
protocol: 'browser',
},
SL_safari: {
exe: 'ember',
@ -118,7 +108,7 @@ module.exports = {
'--no-ct',
'--u',
],
protocol: 'browser'
}
}
protocol: 'browser',
},
},
};

View file

@ -2,15 +2,17 @@ import { visit } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
module('Acceptance | build', function(hooks) {
module('Acceptance | build', function (hooks) {
setupApplicationTest(hooks);
test('version is included as html meta tag', async function(assert) {
test('version is included as html meta tag', async function (assert) {
await visit('/');
// head is not available through `find()`, `assert.dom()` or `this.element.querySelector()`
// cause they are scoped to `#ember-testing-container`.
let buildInfoEl = document.head.querySelector('head meta[name="build-info"]');
let buildInfoEl = document.head.querySelector(
'head meta[name="build-info"]'
);
assert.ok(buildInfoEl, 'tag exists');
let content = buildInfoEl.content;
@ -20,18 +22,23 @@ module('Acceptance | build', function(hooks) {
);
});
test('CSP meta tag is present and before any dangerous element', async function(assert) {
test('CSP meta tag is present and before any dangerous element', async function (assert) {
await visit('/');
// `find()`, `assert.dom()` and `this.element.querySelector()` are all scoped to `#testing-container`
// and therefore don't have access to head
assert.ok(document.head.querySelector('meta[http-equiv="Content-Security-Policy"]'), 'CSP meta tag exists');
assert.ok(
document.head.querySelector('meta[http-equiv="Content-Security-Policy"]'),
'CSP meta tag exists'
);
// this only covers dynamically created elements not the ones defined in `app/index.html` cause
// that one is replaced by `tests/index.html` for testing.
['link', 'script', 'style'].forEach((type) => {
assert.notOk(
document.head.querySelector(`${type} meta[http-equiv="Content-Security-Policy"]`),
document.head.querySelector(
`${type} meta[http-equiv="Content-Security-Policy"]`
),
'CSP meta tag does not have a silbing of type ${type}'
);
});

File diff suppressed because it is too large Load diff

View file

@ -2,21 +2,26 @@ import { fillIn, find, visit } from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
module('Acceptance | i18n', function(hooks) {
hooks.beforeEach(function() {
module('Acceptance | i18n', function (hooks) {
hooks.beforeEach(function () {
window.localStorage.setItem('locale', 'de');
});
setupApplicationTest(hooks);
test('locale is saved in localStorage', async function(assert) {
test('locale is saved in localStorage', async function (assert) {
await visit('/');
assert.equal(find('.language-select').value, 'de', 'picks up locale in locale storage');
assert.equal(
find('.language-select').value,
'de',
'picks up locale in locale storage'
);
await fillIn('.language-select', 'en');
assert.equal(find('.language-select').value, 'en');
assert.equal(
window.localStorage.getItem('locale'), 'en',
window.localStorage.getItem('locale'),
'en',
'persisted in localeStorage'
);
});

View file

@ -8,12 +8,12 @@ import pollParticipate from 'croodle/tests/helpers/poll-participate';
import PollParticipationPage from 'croodle/tests/pages/poll/participation';
import PollEvaluationPage from 'croodle/tests/pages/poll/evaluation';
module('Acceptance | legacy support', function(hooks) {
module('Acceptance | legacy support', function (hooks) {
let yesLabel;
let maybeLabel;
let noLabel;
hooks.beforeEach(function() {
hooks.beforeEach(function () {
window.localStorage.setItem('locale', 'en');
});
@ -21,58 +21,115 @@ module('Acceptance | legacy support', function(hooks) {
setupIntl(hooks);
setupMirage(hooks);
hooks.beforeEach(function() {
hooks.beforeEach(function () {
yesLabel = t('answerTypes.yes.label').toString();
maybeLabel = t('answerTypes.maybe.label').toString();
noLabel = t('answerTypes.no.label').toString();
});
test('show a default poll created with v0.3.0', async function(assert) {
test('show a default poll created with v0.3.0', async function (assert) {
const encryptionKey = '5MKFuNTKILUXw6RuqkAw6ooZw4k3mWWx98ZQw8vH';
let poll = this.server.create('poll', {
encryptionKey,
// property 'id' of answers has been renamed to 'type' in v0.4.0
answers: [{ 'id': 'yes','labelTranslation': 'answerTypes.yes.label','icon': 'glyphicon glyphicon-thumbs-up','label': 'Ja' },{ 'id': 'maybe','labelTranslation': 'answerTypes.maybe.label','icon': 'glyphicon glyphicon-hand-right','label': 'Vielleicht' },{ 'id': 'no','labelTranslation': 'answerTypes.no.label','icon': 'glyphicon glyphicon-thumbs-down','label': 'Nein' }],
options: [{ 'title': '2015-12-24T17:00:00.000Z' },{ 'title': '2015-12-24T19:00:00.000Z' },{ 'title': '2015-12-31T22:59:00.000Z' }],
answers: [
{
id: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Ja',
},
{
id: 'maybe',
labelTranslation: 'answerTypes.maybe.label',
icon: 'glyphicon glyphicon-hand-right',
label: 'Vielleicht',
},
{
id: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'Nein',
},
],
options: [
{ title: '2015-12-24T17:00:00.000Z' },
{ title: '2015-12-24T19:00:00.000Z' },
{ title: '2015-12-31T22:59:00.000Z' },
],
users: [
this.server.create('user', {
encryptionKey,
name: 'Fritz Bauer',
// selections.value was renamed to selections.label
// selections.id was renamed to selections.type
selections: [{ 'value': { 'id': 'yes','labelTranslation': 'answerTypes.yes.label','icon': 'glyphicon glyphicon-thumbs-up','label': 'Ja' } },{ 'value': { 'id': 'no','labelTranslation': 'answerTypes.no.label','icon': 'glyphicon glyphicon-thumbs-down','label': 'Nein' } },{ 'value': { 'id': 'no','labelTranslation': 'answerTypes.no.label','icon': 'glyphicon glyphicon-thumbs-down','label': 'Nein' } }],
selections: [
{
value: {
id: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Ja',
},
},
{
value: {
id: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'Nein',
},
},
{
value: {
id: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'Nein',
},
},
],
// version tag had have wrong format
version: 'v0.3-0'
})
version: 'v0.3-0',
}),
],
// version tag had have wrong format
version: 'v0.3-0'
version: 'v0.3-0',
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.participation');
assert.deepEqual(
PollParticipationPage.options().labels,
[
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(
PollParticipationPage.options().answers,
[yesLabel, maybeLabel, noLabel]
);
assert.deepEqual(PollParticipationPage.options().labels, [
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(PollParticipationPage.options().answers, [
yesLabel,
maybeLabel,
noLabel,
]);
await switchTab('evaluation');
assert.equal(currentRouteName(), 'poll.evaluation');
let participant = PollEvaluationPage.participants.filterBy('name', 'Fritz Bauer')[0];
let participant = PollEvaluationPage.participants.filterBy(
'name',
'Fritz Bauer'
)[0];
assert.ok(participant, 'user exists in participants table');
assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, noLabel, noLabel],
participant.selections.map((_) => _.answer),
[yesLabel, noLabel, noLabel],
'participants table shows correct answers for new participant'
);
@ -82,21 +139,29 @@ module('Acceptance | legacy support', function(hooks) {
await pollParticipate('Hermann Langbein', ['yes', 'maybe', 'yes']);
assert.equal(currentRouteName(), 'poll.evaluation');
participant = PollEvaluationPage.participants.filterBy('name', 'Hermann Langbein')[0];
participant = PollEvaluationPage.participants.filterBy(
'name',
'Hermann Langbein'
)[0];
assert.ok(participant, 'user exists in participants table');
assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, maybeLabel, yesLabel],
participant.selections.map((_) => _.answer),
[yesLabel, maybeLabel, yesLabel],
'participants table shows correct answers for new participant'
);
});
test('show a poll using free text created with v0.3.0', async function(assert) {
test('show a poll using free text created with v0.3.0', async function (assert) {
let encryptionKey = 'Rre6dAGOYLW9gYKOP4LhX7Qwfhe5Th3je0uKDtyy';
let poll = this.server.create('poll', {
encryptionKey,
answerType: 'FreeText',
answers: [],
options: [{ 'title': 'apple pie' }, { 'title': 'pecan pie' }, { 'title': 'plum pie' }],
options: [
{ title: 'apple pie' },
{ title: 'pecan pie' },
{ title: 'plum pie' },
],
pollType: 'MakeAPoll',
users: [
this.server.create('user', {
@ -104,46 +169,59 @@ module('Acceptance | legacy support', function(hooks) {
name: 'Paul Levi',
// selections.value was renamed to selections.label
// selections.id was renamed to selections.type
selections: [{ 'value': 'would be great!' }, { 'value': 'no way' }, { 'value': 'if I had to' }],
selections: [
{ value: 'would be great!' },
{ value: 'no way' },
{ value: 'if I had to' },
],
// version tag had have wrong format
version: 'v0.3-0'
})
version: 'v0.3-0',
}),
],
// version tag had have wrong format
version: 'v0.3-0'
version: 'v0.3-0',
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.participation');
assert.deepEqual(
PollParticipationPage.options().labels,
[
'apple pie',
'pecan pie',
'plum pie'
]
);
assert.deepEqual(PollParticipationPage.options().labels, [
'apple pie',
'pecan pie',
'plum pie',
]);
await switchTab('evaluation');
assert.equal(currentRouteName(), 'poll.evaluation');
let participant = PollEvaluationPage.participants.filterBy('name', 'Paul Levi')[0];
let participant = PollEvaluationPage.participants.filterBy(
'name',
'Paul Levi'
)[0];
assert.ok(participant, 'user exists in participants table');
assert.deepEqual(
participant.selections.map((_) => _.answer), ['would be great!', 'no way', 'if I had to'],
participant.selections.map((_) => _.answer),
['would be great!', 'no way', 'if I had to'],
'participants table shows correct answers for new participant'
);
await switchTab('participation');
assert.equal(currentRouteName(), 'poll.participation');
await pollParticipate('Hermann Langbein', ["I don't care", 'would be awesome', "can't imagine anything better"]);
await pollParticipate('Hermann Langbein', [
"I don't care",
'would be awesome',
"can't imagine anything better",
]);
assert.equal(currentRouteName(), 'poll.evaluation');
participant = PollEvaluationPage.participants.filterBy('name', 'Hermann Langbein')[0];
participant = PollEvaluationPage.participants.filterBy(
'name',
'Hermann Langbein'
)[0];
assert.ok(participant, 'user exists in participants table');
assert.deepEqual(
participant.selections.map((_) => _.answer), ['I don\'t care', 'would be awesome', 'can\'t imagine anything better'],
participant.selections.map((_) => _.answer),
["I don't care", 'would be awesome', "can't imagine anything better"],
'participants table shows correct answers for new participant'
);
});

View file

@ -5,7 +5,7 @@ import {
currentURL,
currentRouteName,
waitFor,
visit
visit,
} from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
@ -14,11 +14,11 @@ import { setupMirage } from 'ember-cli-mirage/test-support';
import PollEvaluationPage from 'croodle/tests/pages/poll/evaluation';
import pollParticipate from 'croodle/tests/helpers/poll-participate';
module('Acceptance | participate in a poll', function(hooks) {
module('Acceptance | participate in a poll', function (hooks) {
let yesLabel;
let noLabel;
hooks.beforeEach(function() {
hooks.beforeEach(function () {
window.localStorage.setItem('locale', 'en');
});
@ -26,19 +26,23 @@ module('Acceptance | participate in a poll', function(hooks) {
setupIntl(hooks);
setupMirage(hooks);
hooks.beforeEach(function() {
hooks.beforeEach(function () {
yesLabel = t('answerTypes.yes.label').toString();
noLabel = t('answerTypes.no.label').toString();
});
test('participate in a default poll', async function(assert) {
test('participate in a default poll', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
encryptionKey
encryptionKey,
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.participation', 'poll is redirected to poll.participation');
assert.equal(
currentRouteName(),
'poll.participation',
'poll is redirected to poll.participation'
);
await pollParticipate('Max Meiner', ['yes', 'no']);
assert.equal(currentRouteName(), 'poll.evaluation');
@ -47,142 +51,202 @@ module('Acceptance | participate in a poll', function(hooks) {
`encryptionKey=${encryptionKey}`,
'encryption key is part of query params'
);
assert.equal(PollEvaluationPage.participants.length, 1, 'user is added to participants table');
let participant = PollEvaluationPage.participants.filterBy('name', 'Max Meiner')[0];
assert.equal(
PollEvaluationPage.participants.length,
1,
'user is added to participants table'
);
let participant = PollEvaluationPage.participants.filterBy(
'name',
'Max Meiner'
)[0];
assert.ok(participant, 'user exists in participants table');
assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, noLabel],
participant.selections.map((_) => _.answer),
[yesLabel, noLabel],
'participants table shows correct answers for new participant'
);
await click('.nav .participation');
assert.equal(currentRouteName(), 'poll.participation');
assert.equal(find('.name input').value, '', 'input for name is cleared');
assert.ok(
!findAll('input[type="radio"]').toArray().some((el) => el.checked),
assert.notOk(
findAll('input[type="radio"]')
.toArray()
.some((el) => el.checked),
'radios are cleared'
);
await pollParticipate('Peter Müller', ['yes', 'yes']);
assert.equal(currentRouteName(), 'poll.evaluation');
assert.equal(PollEvaluationPage.participants.length, 2, 'user is added to participants table');
participant = PollEvaluationPage.participants.filterBy('name', 'Peter Müller')[0];
assert.equal(
PollEvaluationPage.participants.length,
2,
'user is added to participants table'
);
participant = PollEvaluationPage.participants.filterBy(
'name',
'Peter Müller'
)[0];
assert.ok(participant, 'user exists in participants table');
assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, yesLabel],
participant.selections.map((_) => _.answer),
[yesLabel, yesLabel],
'participants table shows correct answers for new participant'
);
});
test('participate in a poll using freetext', async function(assert) {
test('participate in a poll using freetext', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
answerType: 'FreeText',
answers: [],
encryptionKey
encryptionKey,
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`)
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.participation');
await pollParticipate('Max Manus', ['answer 1', 'answer 2']);
assert.equal(currentRouteName(), 'poll.evaluation');
assert.equal(PollEvaluationPage.participants.length, 1, 'user is added to participants table');
assert.equal(
PollEvaluationPage.participants.length,
1,
'user is added to participants table'
);
let participant = PollEvaluationPage.participants.filterBy('name', 'Max Manus')[0];
let participant = PollEvaluationPage.participants.filterBy(
'name',
'Max Manus'
)[0];
assert.ok(participant, 'user exists in participants table');
assert.deepEqual(
participant.selections.map((_) => _.answer), ['answer 1', 'answer 2'],
participant.selections.map((_) => _.answer),
['answer 1', 'answer 2'],
'participants table shows correct answers for new participant'
);
});
test('participate in a poll which does not force an answer to all options', async function(assert) {
test('participate in a poll which does not force an answer to all options', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
encryptionKey,
forceAnswer: false
forceAnswer: false,
});
await visit(`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`);
await visit(
`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`
);
assert.equal(currentRouteName(), 'poll.participation');
await pollParticipate('Karl Käfer', ['yes', null]);
assert.equal(currentRouteName(), 'poll.evaluation');
assert.equal(PollEvaluationPage.participants.length, 1, 'user is added to participants table');
assert.equal(
PollEvaluationPage.participants.length,
1,
'user is added to participants table'
);
let participant = PollEvaluationPage.participants.filterBy('name', 'Karl Käfer')[0];
let participant = PollEvaluationPage.participants.filterBy(
'name',
'Karl Käfer'
)[0];
assert.ok(participant, 'user exists in participants table');
assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, ''],
participant.selections.map((_) => _.answer),
[yesLabel, ''],
'participants table shows correct answers for new participant'
);
});
test('participate in a poll which allows anonymous participation', async function(assert) {
test('participate in a poll which allows anonymous participation', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
anonymousUser: true,
encryptionKey
encryptionKey,
});
await visit(`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`);
await visit(
`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`
);
assert.equal(currentRouteName(), 'poll.participation');
await pollParticipate(null, ['yes', 'no']);
assert.equal(currentRouteName(), 'poll.evaluation');
assert.equal(PollEvaluationPage.participants.length, 1, 'user is added to participants table');
assert.equal(
PollEvaluationPage.participants.length,
1,
'user is added to participants table'
);
let participant = PollEvaluationPage.participants.filterBy('name', '')[0];
assert.ok(participant, 'user exists in participants table');
assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, noLabel],
participant.selections.map((_) => _.answer),
[yesLabel, noLabel],
'participants table shows correct answers for new participant'
);
});
test('network connectivity errors', async function(assert) {
test('network connectivity errors', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
encryptionKey
encryptionKey,
});
this.server.post('/users', undefined, 503);
await visit(`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`);
await visit(
`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`
);
assert.equal(currentRouteName(), 'poll.participation');
assert.dom('[data-test-modal="saving-failed"] .modal-content')
.isNotVisible('failed saving notification is not shown before attempt to save');
assert
.dom('[data-test-modal="saving-failed"] .modal-content')
.isNotVisible(
'failed saving notification is not shown before attempt to save'
);
await pollParticipate('John Doe', ['yes', 'no']);
assert.dom('[data-test-modal="saving-failed"] .modal-content')
assert
.dom('[data-test-modal="saving-failed"] .modal-content')
.isVisible('user gets notified that saving failed');
this.server.post('/users');
await click('[data-test-modal="saving-failed"] [data-test-button="retry"]');
assert.dom('[data-test-modal="saving-failed"] .modal-content')
.isNotVisible('Notification is hidden after another save attempt was successful');
assert
.dom('[data-test-modal="saving-failed"] .modal-content')
.isNotVisible(
'Notification is hidden after another save attempt was successful'
);
assert.equal(currentRouteName(), 'poll.evaluation');
assert.equal(PollEvaluationPage.participants.length, 1, 'user is added to participants table');
assert.equal(
PollEvaluationPage.participants.length,
1,
'user is added to participants table'
);
let participant = PollEvaluationPage.participants.filterBy('name', 'John Doe')[0];
let participant = PollEvaluationPage.participants.filterBy(
'name',
'John Doe'
)[0];
assert.ok(participant, 'user exists in participants table');
assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, noLabel],
participant.selections.map((_) => _.answer),
[yesLabel, noLabel],
'participants table shows correct answers for new participant'
);
});
test('shows loading spinner while submitting', async function(assert) {
test('shows loading spinner while submitting', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
encryptionKey
encryptionKey,
});
let resolveSubmission;
let resolveSubmissionWith;
this.server.post('/users', function(schema) {
this.server.post('/users', function (schema) {
return new Promise((resolve) => {
let attrs = this.normalizedRequestAttrs();
@ -191,90 +255,143 @@ module('Acceptance | participate in a poll', function(hooks) {
});
});
await visit(`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`);
await visit(
`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`
);
pollParticipate('John Doe', ['yes', 'no']);
await waitFor('[data-test-button="submit"] .spinner-border', {
timeoutMessage: 'timeout while waiting for loading spinner to appear',
});
assert.ok(true, 'loading spinner shown cause otherwise there would have been a timeout');
assert.ok(
true,
'loading spinner shown cause otherwise there would have been a timeout'
);
// resolve promise for test to finish
// need to resolve with a valid response cause otherwise Ember Data would throw
resolveSubmission(resolveSubmissionWith);
});
module('validation', function() {
test('shows validation errors for participation form on submit', async function(assert) {
module('validation', function () {
test('shows validation errors for participation form on submit', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
encryptionKey,
});
await visit(`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`);
await visit(
`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`
);
await click('button[type="submit"]');
assert.dom('[data-test-form-element="name"] input').hasClass('is-invalid');
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="yes"]').hasClass('is-invalid');
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="no"]').hasClass('is-invalid');
assert.dom('[data-test-form-element="option-2018-01-01"] input[id$="yes"]').hasClass('is-invalid');
assert.dom('[data-test-form-element="option-2018-01-01"] input[id$="no"]').hasClass('is-invalid');
assert
.dom('[data-test-form-element="name"] input')
.hasClass('is-invalid');
assert
.dom('[data-test-form-element="option-2017-12-24"] input[id$="yes"]')
.hasClass('is-invalid');
assert
.dom('[data-test-form-element="option-2017-12-24"] input[id$="no"]')
.hasClass('is-invalid');
assert
.dom('[data-test-form-element="option-2018-01-01"] input[id$="yes"]')
.hasClass('is-invalid');
assert
.dom('[data-test-form-element="option-2018-01-01"] input[id$="no"]')
.hasClass('is-invalid');
assert.dom('[data-test-form-element="name"] input').isFocused();
assert.equal(currentRouteName(), 'poll.participation', 'invalid form prevents a transition');
assert.equal(
currentRouteName(),
'poll.participation',
'invalid form prevents a transition'
);
});
test('does not show validation error for name if poll allows anonymous participation', async function(assert) {
test('does not show validation error for name if poll allows anonymous participation', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
anonymousUser: true,
encryptionKey,
});
await visit(`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`);
await visit(
`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`
);
await click('button[type="submit"]');
assert.dom('[data-test-form-element="name"] input').hasClass('is-valid');
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="yes"]').hasClass('is-invalid');
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="no"]').hasClass('is-invalid');
assert.dom('[data-test-form-element="option-2018-01-01"] input[id$="yes"]').hasClass('is-invalid');
assert.dom('[data-test-form-element="option-2018-01-01"] input[id$="no"]').hasClass('is-invalid');
assert
.dom('[data-test-form-element="option-2017-12-24"] input[id$="yes"]')
.hasClass('is-invalid');
assert
.dom('[data-test-form-element="option-2017-12-24"] input[id$="no"]')
.hasClass('is-invalid');
assert
.dom('[data-test-form-element="option-2018-01-01"] input[id$="yes"]')
.hasClass('is-invalid');
assert
.dom('[data-test-form-element="option-2018-01-01"] input[id$="no"]')
.hasClass('is-invalid');
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="yes"]').isFocused();
assert.equal(currentRouteName(), 'poll.participation', 'invalid form prevents a transition');
assert
.dom('[data-test-form-element="option-2017-12-24"] input[id$="yes"]')
.isFocused();
assert.equal(
currentRouteName(),
'poll.participation',
'invalid form prevents a transition'
);
});
test('does not show validation error for option inputs if poll does not force an answer to each option', async function(assert) {
test('does not show validation error for option inputs if poll does not force an answer to each option', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
encryptionKey,
forceAnswer: false
forceAnswer: false,
});
await visit(`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`);
await visit(
`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`
);
await click('button[type="submit"]');
assert.dom('[data-test-form-element="name"] input').hasClass('is-invalid');
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="yes"]').hasClass('is-valid');
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="no"]').hasClass('is-valid');
assert.dom('[data-test-form-element="option-2018-01-01"] input[id$="yes"]').hasClass('is-valid');
assert.dom('[data-test-form-element="option-2018-01-01"] input[id$="no"]').hasClass('is-valid');
assert
.dom('[data-test-form-element="name"] input')
.hasClass('is-invalid');
assert
.dom('[data-test-form-element="option-2017-12-24"] input[id$="yes"]')
.hasClass('is-valid');
assert
.dom('[data-test-form-element="option-2017-12-24"] input[id$="no"]')
.hasClass('is-valid');
assert
.dom('[data-test-form-element="option-2018-01-01"] input[id$="yes"]')
.hasClass('is-valid');
assert
.dom('[data-test-form-element="option-2018-01-01"] input[id$="no"]')
.hasClass('is-valid');
assert.dom('[data-test-form-element="name"] input').isFocused();
assert.equal(currentRouteName(), 'poll.participation', 'invalid form prevents a transition');
assert.equal(
currentRouteName(),
'poll.participation',
'invalid form prevents a transition'
);
});
test('does not show validation errors while saving participation', async function(assert) {
test('does not show validation errors while saving participation', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
encryptionKey
encryptionKey,
});
let resolveSubmission;
let resolveSubmissionWith;
this.server.post('/users', function(schema) {
this.server.post('/users', function (schema) {
return new Promise((resolve) => {
let attrs = this.normalizedRequestAttrs();
@ -283,13 +400,17 @@ module('Acceptance | participate in a poll', function(hooks) {
});
});
await visit(`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`);
await visit(
`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`
);
pollParticipate('John Doe', ['yes', 'no']);
await waitFor('[data-test-button="submit"] .spinner-border', {
timeoutMessage: 'timeout while waiting for loading spinner to appear',
});
assert.dom('.is-invalid').doesNotExist('does not show any validation error');
assert
.dom('.is-invalid')
.doesNotExist('does not show any validation error');
// resolve promise for test to finish
// need to resolve with a valid response cause otherwise Ember Data would throw

View file

@ -2,13 +2,14 @@ import { findAll, currentRouteName, find, visit } from '@ember/test-helpers';
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 { setupIntl, t } from 'ember-intl/test-support';
import switchTab from 'croodle/tests/helpers/switch-tab';
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() {
module('Acceptance | view evaluation', function (hooks) {
hooks.beforeEach(function () {
window.localStorage.setItem('locale', 'en');
});
@ -16,23 +17,27 @@ module('Acceptance | view evaluation', function(hooks) {
setupIntl(hooks);
setupMirage(hooks);
test('evaluation summary is not present for poll without participants', async function(assert) {
test('evaluation summary is not present for poll without participants', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
encryptionKey
encryptionKey,
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.participation');
await switchTab('evaluation');
assert.equal(findAll('.tab-content .tab-pane .evaluation-summary').length, 0, 'evaluation summary is not present');
assert.equal(
findAll('.tab-content .tab-pane .evaluation-summary').length,
0,
'evaluation summary is not present'
);
});
test('evaluation is correct for FindADate (date-only)', async function(assert) {
test('evaluation is correct for FindADate (date-only)', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let user1 = this.server.create('user', {
id: "1-1",
id: '1-1',
creationDate: DateTime.local().minus({ months: 8, weeks: 3 }).toISO(),
encryptionKey,
name: 'Maximilian',
@ -41,18 +46,18 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
}
]
label: 'Yes',
},
],
});
let user2 = this.server.create('user', {
id: "1-2",
id: '1-2',
creationDate: DateTime.local().minus({ months: 3, weeks: 2 }).toISO(),
encryptionKey,
name: 'Peter',
@ -61,15 +66,15 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'No'
label: 'No',
},
{
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
}
]
label: 'Yes',
},
],
});
let poll = this.server.create('poll', {
id: '1',
@ -78,26 +83,27 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'No'
}
label: 'No',
},
],
encryptionKey,
options: [
{ title: '2015-12-12' },
{ title: '2016-01-01' }
],
users: [user1, user2]
options: [{ title: '2015-12-12' }, { title: '2016-01-01' }],
users: [user1, user2],
});
await visit(`/poll/${poll.id}/evaluation?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.evaluation');
assert.equal(findAll('.tab-content .tab-pane .evaluation-summary').length, 1, 'evaluation summary is present');
assert.equal(
findAll('.tab-content .tab-pane .evaluation-summary').length,
1,
'evaluation summary is present'
);
assert.equal(
find('.participants').textContent.trim(),
t('poll.evaluation.participants', { count: 2 }).toString(),
@ -111,7 +117,7 @@ module('Acceptance | view evaluation', function(hooks) {
assert.equal(
find('.last-participation').textContent.trim(),
t('poll.evaluation.lastParticipation', {
ago: '3 months ago'
ago: '3 months ago',
}).toString(),
'shows last participation date'
);
@ -124,37 +130,56 @@ module('Acceptance | view evaluation', function(hooks) {
assert
.dom('[data-test-participant="1-1"] [data-test-value-for="name"]')
.hasText('Maximilian', 'shows expected name of first participant in participants table');
.hasText(
'Maximilian',
'shows expected name of first participant in participants table'
);
assert
.dom('[data-test-participant="1-2"] [data-test-value-for="name"]')
.hasText('Peter', 'shows expected name of second participant in participants table');
.hasText(
'Peter',
'shows expected name of second participant in participants table'
);
assert
.dom('[data-test-participant="1-1"] [data-test-value-for="2015-12-12"]')
.hasText('Yes', 'shows expected selection for first option of first participant');
.hasText(
'Yes',
'shows expected selection for first option of first participant'
);
assert
.dom('[data-test-participant="1-1"] [data-test-value-for="2016-01-01"]')
.hasText('Yes', 'shows expected selection for second option of first participant');
.hasText(
'Yes',
'shows expected selection for second option of first participant'
);
assert
.dom('[data-test-participant="1-2"] [data-test-value-for="2015-12-12"]')
.hasText('No', 'shows expected selection for first option of second participant');
.hasText(
'No',
'shows expected selection for first option of second participant'
);
assert
.dom('[data-test-participant="1-2"] [data-test-value-for="2016-01-01"]')
.hasText('Yes', 'shows expected selection for second option of second participant');
.hasText(
'Yes',
'shows expected selection for second option of second participant'
);
assert.deepEqual(
findAll('[data-test-participant] [data-test-value-for="name"]').map((el) => el.textContent.trim()),
findAll('[data-test-participant] [data-test-value-for="name"]').map(
(el) => el.textContent.trim()
),
['Maximilian', 'Peter'],
'Participants are ordered as correctly in participants table'
);
});
test('evaluation is correct for FindADate (datetime)', async function(assert) {
test('evaluation is correct for FindADate (datetime)', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let user1 = this.server.create('user', {
id: "1-1",
id: '1-1',
creationDate: DateTime.local().minus({ months: 8, weeks: 3 }).toISO(),
encryptionKey,
name: 'Maximilian',
@ -163,24 +188,24 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'No'
label: 'No',
},
]
],
});
let user2 = this.server.create('user', {
id: "1-2",
id: '1-2',
creationDate: DateTime.local().minus({ months: 3, weeks: 2 }).toISO(),
encryptionKey,
name: 'Peter',
@ -189,21 +214,21 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'No'
label: 'No',
},
{
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
}
]
label: 'Yes',
},
],
});
let poll = this.server.create('poll', {
id: '1',
@ -212,27 +237,31 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'No'
}
label: 'No',
},
],
encryptionKey,
options: [
{ title: DateTime.fromISO('2015-12-12T06:06').toISO() },
{ title: DateTime.fromISO('2015-12-12T12:12').toISO() },
{ title: DateTime.fromISO('2016-01-01T18:18').toISO() }
{ title: DateTime.fromISO('2016-01-01T18:18').toISO() },
],
users: [user1, user2]
users: [user1, user2],
});
await visit(`/poll/${poll.id}/evaluation?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.evaluation');
assert.equal(findAll('.tab-content .tab-pane .evaluation-summary').length, 1, 'evaluation summary is present');
assert.equal(
findAll('.tab-content .tab-pane .evaluation-summary').length,
1,
'evaluation summary is present'
);
assert.equal(
find('.participants').textContent.trim(),
t('poll.evaluation.participants', { count: 2 }).toString(),
@ -246,58 +275,111 @@ module('Acceptance | view evaluation', function(hooks) {
assert.equal(
find('.last-participation').textContent.trim(),
t('poll.evaluation.lastParticipation', {
ago: '3 months ago'
ago: '3 months ago',
}).toString(),
'shows last participation date'
);
assert.deepEqual(
findAll('table thead tr:first-child th').map((el) => el.textContent.trim()),
findAll('table thead tr:first-child th').map((el) =>
el.textContent.trim()
),
['', 'Saturday, December 12, 2015', 'Friday, January 1, 2016'],
'lists days as first row in table header of parcipants table'
);
assert.deepEqual(
findAll('table thead tr:last-child th').map((el) => el.textContent.trim()),
findAll('table thead tr:last-child th').map((el) =>
el.textContent.trim()
),
['', '6:06 AM', '12:12 PM', '6:18 PM'],
'lists times as second row in table header of parcipants table'
);
assert
.dom('[data-test-participant="1-1"] [data-test-value-for="name"]')
.hasText('Maximilian', 'shows expected name of first participant in participants table');
.hasText(
'Maximilian',
'shows expected name of first participant in participants table'
);
assert
.dom('[data-test-participant="1-2"] [data-test-value-for="name"]')
.hasText('Peter', 'shows expected name of second participant in participants table');
.hasText(
'Peter',
'shows expected name of second participant in participants table'
);
assert
.dom(`[data-test-participant="1-1"] [data-test-value-for="${DateTime.fromISO('2015-12-12T06:06').toISO()}"]`)
.hasText('Yes', 'shows expected selection for first option of first participant');
.dom(
`[data-test-participant="1-1"] [data-test-value-for="${DateTime.fromISO(
'2015-12-12T06:06'
).toISO()}"]`
)
.hasText(
'Yes',
'shows expected selection for first option of first participant'
);
assert
.dom(`[data-test-participant="1-1"] [data-test-value-for="${DateTime.fromISO('2015-12-12T12:12').toISO()}"]`)
.hasText('Yes', 'shows expected selection for second option of first participant');
.dom(
`[data-test-participant="1-1"] [data-test-value-for="${DateTime.fromISO(
'2015-12-12T12:12'
).toISO()}"]`
)
.hasText(
'Yes',
'shows expected selection for second option of first participant'
);
assert
.dom(`[data-test-participant="1-1"] [data-test-value-for="${DateTime.fromISO('2016-01-01T18:18').toISO()}"]`)
.hasText('No', 'shows expected selection for third option of first participant');
.dom(
`[data-test-participant="1-1"] [data-test-value-for="${DateTime.fromISO(
'2016-01-01T18:18'
).toISO()}"]`
)
.hasText(
'No',
'shows expected selection for third option of first participant'
);
assert
.dom(`[data-test-participant="1-2"] [data-test-value-for="${DateTime.fromISO('2015-12-12T06:06').toISO()}"]`)
.hasText('No', 'shows expected selection for first option of second participant');
.dom(
`[data-test-participant="1-2"] [data-test-value-for="${DateTime.fromISO(
'2015-12-12T06:06'
).toISO()}"]`
)
.hasText(
'No',
'shows expected selection for first option of second participant'
);
assert
.dom(`[data-test-participant="1-2"] [data-test-value-for="${DateTime.fromISO('2015-12-12T12:12').toISO()}"]`)
.hasText('Yes', 'shows expected selection for second option of second participant');
.dom(
`[data-test-participant="1-2"] [data-test-value-for="${DateTime.fromISO(
'2015-12-12T12:12'
).toISO()}"]`
)
.hasText(
'Yes',
'shows expected selection for second option of second participant'
);
assert
.dom(`[data-test-participant="1-2"] [data-test-value-for="${DateTime.fromISO('2016-01-01T18:18').toISO()}"]`)
.hasText('Yes', 'shows expected selection for third option of second participant');
.dom(
`[data-test-participant="1-2"] [data-test-value-for="${DateTime.fromISO(
'2016-01-01T18:18'
).toISO()}"]`
)
.hasText(
'Yes',
'shows expected selection for third option of second participant'
);
assert.deepEqual(
findAll('[data-test-participant] [data-test-value-for="name"]').map((el) => el.textContent.trim()),
findAll('[data-test-participant] [data-test-value-for="name"]').map(
(el) => el.textContent.trim()
),
['Maximilian', 'Peter'],
'Participants are ordered as correctly in participants table'
);
});
test('evaluation is correct for MakeAPoll', async function(assert) {
test('evaluation is correct for MakeAPoll', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let usersData = [
{
@ -309,15 +391,15 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
}
]
label: 'Yes',
},
],
},
{
creationDate: DateTime.local().minus({ days: 3 }).toISO(),
@ -328,15 +410,15 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'No'
label: 'No',
},
{
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
}
]
label: 'Yes',
},
],
},
];
let pollData = {
@ -345,27 +427,33 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'No'
}
label: 'No',
},
],
encryptionKey,
options: [
{ title: 'first option' },
{ title: 'second option' }
],
options: [{ title: 'first option' }, { title: 'second option' }],
pollType: 'MakeAPoll',
};
let poll = this.server.create('poll', assign(pollData, { users: usersData.map((_) => this.server.create('user', _)) }));
let poll = this.server.create(
'poll',
assign(pollData, {
users: usersData.map((_) => this.server.create('user', _)),
})
);
await visit(`/poll/${poll.id}/evaluation?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.evaluation');
assert.equal(findAll('.tab-content .tab-pane .evaluation-summary').length, 1, 'evaluation summary is present');
assert.equal(
findAll('.tab-content .tab-pane .evaluation-summary').length,
1,
'evaluation summary is present'
);
assert.equal(
find('.participants').textContent.trim(),
t('poll.evaluation.participants', { count: 2 }).toString(),
@ -383,11 +471,15 @@ module('Acceptance | view evaluation', function(hooks) {
'dates are used as table headers'
);
assert.deepEqual(
PollEvaluationPage.participants.map((_) => _.name), usersData.map((_) => _.name),
PollEvaluationPage.participants.map((_) => _.name),
usersData.map((_) => _.name),
'users are listed in participants table with their names'
);
usersData.forEach((user) => {
let participant = PollEvaluationPage.participants.filterBy('name', user.name)[0];
let participant = PollEvaluationPage.participants.filterBy(
'name',
user.name
)[0];
assert.deepEqual(
participant.selections.map((_) => _.answer),
user.selections.map((_) => t(_.labelTranslation).toString()),
@ -398,13 +490,13 @@ module('Acceptance | view evaluation', function(hooks) {
assert.equal(
find('.last-participation').textContent.trim(),
t('poll.evaluation.lastParticipation', {
ago: '3 days ago'
ago: '3 days ago',
}).toString(),
'last participation is evaluated correctly'
);
});
test('could open evaluation by tab from poll participation', async function(assert) {
test('could open evaluation by tab from poll participation', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
answers: [
@ -412,20 +504,17 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'No'
}
label: 'No',
},
],
encryptionKey,
options: [
{ title: '2015-12-12' },
{ title: '2016-01-01' }
],
options: [{ title: '2015-12-12' }, { title: '2016-01-01' }],
users: [
this.server.create('user', {
creationDate: '2015-01-01T00:00:00.000Z',
@ -436,15 +525,15 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
}
]
label: 'Yes',
},
],
}),
this.server.create('user', {
creationDate: '2015-08-01T00:00:00.000Z',
@ -455,17 +544,17 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
id: 'no',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
}
]
})
]
label: 'Yes',
},
],
}),
],
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);

View file

@ -1,4 +1,9 @@
import { click, currentURL, currentRouteName, visit } from '@ember/test-helpers';
import {
click,
currentURL,
currentRouteName,
visit,
} from '@ember/test-helpers';
import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support';
@ -8,15 +13,15 @@ import pageEvaluation from 'croodle/tests/pages/poll/evaluation';
import { DateTime } from 'luxon';
import { triggerCopySuccess } from 'ember-cli-clipboard/test-support';
module('Acceptance | view poll', function(hooks) {
hooks.beforeEach(function() {
module('Acceptance | view poll', function (hooks) {
hooks.beforeEach(function () {
window.localStorage.setItem('locale', 'en');
});
setupApplicationTest(hooks);
setupMirage(hooks);
test('poll url', async function(assert) {
test('poll url', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz012345789';
let poll = this.server.create('poll', { encryptionKey });
let pollUrl = `/poll/${poll.id}?encryptionKey=${encryptionKey}`;
@ -35,10 +40,10 @@ module('Acceptance | view poll', function(hooks) {
*
* Can't test if flash message is shown due to
* https://github.com/poteto/ember-cli-flash/issues/202
*/
*/
});
test('shows a warning if poll is about to be expired', async function(assert) {
test('shows a warning if poll is about to be expired', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
encryptionKey,
@ -46,34 +51,26 @@ module('Acceptance | view poll', function(hooks) {
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.ok(
pageParticipation.showsExpirationWarning
);
assert.ok(pageParticipation.showsExpirationWarning);
});
test('view a poll with dates', async function(assert) {
test('view a poll with dates', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', {
encryptionKey,
options: [
{ title: '2015-12-12' },
{ title: '2016-01-01' }
]
options: [{ title: '2015-12-12' }, { title: '2016-01-01' }],
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.deepEqual(
pageParticipation.options().labels,
[
'Saturday, December 12, 2015',
'Friday, January 1, 2016'
]
);
assert.deepEqual(pageParticipation.options().labels, [
'Saturday, December 12, 2015',
'Friday, January 1, 2016',
]);
});
test('view a poll with dates and times', async function(assert) {
test('view a poll with dates and times', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ;
let timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
let poll = this.server.create('poll', {
encryptionKey,
expirationDate: DateTime.local().plus({ years: 1 }).toISO(),
@ -84,39 +81,37 @@ module('Acceptance | view poll', function(hooks) {
// time zone as UTC rather than local time
{ 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() }
{ title: DateTime.fromISO('2016-01-01T11:11:00').toISO() },
],
timezone
timezone,
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.deepEqual(
pageParticipation.options().labels,
[
// full date
'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 at 11:11 AM',
]
);
assert.deepEqual(pageParticipation.options().labels, [
// full date
'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 at 11:11 AM',
]);
assert.notOk(
pageParticipation.showsExpirationWarning,
'does not show an expiration warning if poll will not expire in next weeks'
);
});
test('view a poll while timezone differs from the one poll got created in and choose local timezone', async function(assert) {
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 = Intl.DateTimeFormat().resolvedOptions().timeZone ;
let timezonePoll = timezoneUser !== 'America/Caracas' ? 'America/Caracas' : 'Europe/Moscow';
let timezoneUser = Intl.DateTimeFormat().resolvedOptions().timeZone;
let timezonePoll =
timezoneUser !== 'America/Caracas' ? 'America/Caracas' : 'Europe/Moscow';
let poll = this.server.create('poll', {
encryptionKey,
isDateTime: true,
options: [
{ title: '2015-12-12T11:11:00.000Z' },
{ title: '2016-01-01T11:11:00.000Z' }
{ title: '2016-01-01T11:11:00.000Z' },
],
timezone: timezonePoll,
users: [
@ -127,50 +122,61 @@ module('Acceptance | view poll', function(hooks) {
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'No'
}
]
})
]
label: 'No',
},
],
}),
],
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.dom('[data-test-modal="choose-timezone"]')
assert
.dom('[data-test-modal="choose-timezone"]')
.exists('user is asked which timezone should be used');
await click('[data-test-modal="choose-timezone"] [data-test-button="use-local-timezone"]');
assert.deepEqual(
pageParticipation.options().labels,
[
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')),
]
await click(
'[data-test-modal="choose-timezone"] [data-test-button="use-local-timezone"]'
);
assert.dom('[data-test-modal="choose-timezone"]').doesNotExist('modal is closed');
assert.deepEqual(pageParticipation.options().labels, [
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');
await switchTab('evaluation');
assert.deepEqual(
pageEvaluation.preferedOptions,
[Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-12T11:11:00.000Z'))]
);
assert.deepEqual(pageEvaluation.preferedOptions, [
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) {
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 = Intl.DateTimeFormat().resolvedOptions().timeZone ;
let timezonePoll = timezoneUser !== 'America/Caracas' ? 'America/Caracas' : 'Europe/Moscow';
let timezoneUser = Intl.DateTimeFormat().resolvedOptions().timeZone;
let timezonePoll =
timezoneUser !== 'America/Caracas' ? 'America/Caracas' : 'Europe/Moscow';
let poll = this.server.create('poll', {
encryptionKey,
isDateTime: true,
options: [
{ title: '2015-12-12T11:11:00.000Z' },
{ title: '2016-01-01T11:11:00.000Z' }
{ title: '2016-01-01T11:11:00.000Z' },
],
timezone: timezonePoll,
users: [
@ -181,61 +187,94 @@ module('Acceptance | view poll', function(hooks) {
type: 'yes',
labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes'
label: 'Yes',
},
{
type: 'no',
labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down',
label: 'No'
}
]
})
]
label: 'No',
},
],
}),
],
});
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.dom('[data-test-modal="choose-timezone"]')
assert
.dom('[data-test-modal="choose-timezone"]')
.exists('user is asked which timezone should be used');
await click('[data-test-modal="choose-timezone"] [data-test-button="use-poll-timezone"]');
assert.deepEqual(
pageParticipation.options().labels,
[
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')),
]
await click(
'[data-test-modal="choose-timezone"] [data-test-button="use-poll-timezone"]'
);
assert.dom('[data-test-modal="choose-timezone"]').doesNotExist('modal is closed');
assert.deepEqual(pageParticipation.options().labels, [
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');
await switchTab('evaluation');
assert.deepEqual(
pageEvaluation.preferedOptions,
[Intl.DateTimeFormat('en-US', { timeZone: timezonePoll, dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-12T11:11:00.000Z')),]
);
assert.deepEqual(pageEvaluation.preferedOptions, [
Intl.DateTimeFormat('en-US', {
timeZone: timezonePoll,
dateStyle: 'full',
timeStyle: 'short',
}).format(new Date('2015-12-12T11:11:00.000Z')),
]);
});
test('shows error page if poll does not exist', async function(assert) {
test('shows error page if poll does not exist', async function (assert) {
let pollId = 'not-existing';
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
await visit(`/poll/${pollId}?encryptionKey=${encryptionKey}`);
assert.equal(currentURL(), `/poll/${pollId}?encryptionKey=${encryptionKey}`, 'shows URL entered by user');
assert.equal(currentRouteName(), 'poll_error', 'shows error substate of poll route');
assert.dom('[data-test-error-type]').hasAttribute('data-test-error-type', 'not-found');
assert.equal(
currentURL(),
`/poll/${pollId}?encryptionKey=${encryptionKey}`,
'shows URL entered by user'
);
assert.equal(
currentRouteName(),
'poll_error',
'shows error substate of poll route'
);
assert
.dom('[data-test-error-type]')
.hasAttribute('data-test-error-type', 'not-found');
});
test('shows error page if encryption key is wrong', async function(assert) {
test('shows error page if encryption key is wrong', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { encryptionKey: 'anotherkey' });
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.equal(currentURL(), `/poll/${poll.id}?encryptionKey=${encryptionKey}`, 'shows URL entered by user');
assert.equal(currentRouteName(), 'poll_error', 'shows error substate of poll route');
assert.dom('[data-test-error-type]').hasAttribute('data-test-error-type', 'decryption-failed');
assert.equal(
currentURL(),
`/poll/${poll.id}?encryptionKey=${encryptionKey}`,
'shows URL entered by user'
);
assert.equal(
currentRouteName(),
'poll_error',
'shows error substate of poll route'
);
assert
.dom('[data-test-error-type]')
.hasAttribute('data-test-error-type', 'decryption-failed');
});
test('shows error page if server returns a 500', async function(assert) {
test('shows error page if server returns a 500', async function (assert) {
let pollId = 'not-existing';
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
@ -243,8 +282,18 @@ module('Acceptance | view poll', function(hooks) {
this.server.get('polls/:id', () => {}, 500);
await visit(`/poll/${pollId}?encryptionKey=${encryptionKey}`);
assert.equal(currentURL(), `/poll/${pollId}?encryptionKey=${encryptionKey}`, 'shows URL entered by user');
assert.equal(currentRouteName(), 'poll_error', 'shows error substate of poll route');
assert.dom('[data-test-error-type]').hasAttribute('data-test-error-type', 'unexpected');
})
assert.equal(
currentURL(),
`/poll/${pollId}?encryptionKey=${encryptionKey}`,
'shows URL entered by user'
);
assert.equal(
currentRouteName(),
'poll_error',
'shows error substate of poll route'
);
assert
.dom('[data-test-error-type]')
.hasAttribute('data-test-error-type', 'unexpected');
});
});

View file

@ -47,14 +47,14 @@ export default async function asyncThrows(f, expectedErrorMessage) {
result,
expected: expectedErrorMessage,
actual: errorText,
message: `Expected to see error '${expectedErrorMessage}'`
message: `Expected to see error '${expectedErrorMessage}'`,
});
} else {
this.pushResult({
result: false,
expected: '',
actual: errorText,
message: `You're using asyncThrows but you didn't add text to the assertion. Add some text as the second argument so the actual exception being thrown is what you expect it is.`
message: `You're using asyncThrows but you didn't add text to the assertion. Add some text as the second argument so the actual exception being thrown is what you expect it is.`,
});
}

View file

@ -22,11 +22,7 @@ function pollHasUsersCount(assert, count, message) {
if (isEmpty(message)) {
message = 'poll has expected count of users';
}
assert.equal(
findAll('.user').length,
count,
message
);
assert.equal(findAll('.user').length, count, message);
}
export default pollHasUser;

View file

@ -1,23 +1,31 @@
import { isEmpty } from '@ember/utils';
import { findAll, fillIn, click, settled } from '@ember/test-helpers';
import { findAll, fillIn, click } from '@ember/test-helpers';
export default async function(name, selections) {
export default async function (name, selections) {
if (!isEmpty(name)) {
await fillIn('.participation .name input', name);
}
const isFreeText = findAll('.participation .selections .radio').length > 0 ? false : true;
const isFreeText =
findAll('.participation .selections .radio').length > 0 ? false : true;
for (let [index, selection] of selections.entries()) {
if (!isEmpty(selection)) {
if (isFreeText) {
await fillIn(`.participation .selections .form-group:nth-child(${index + 1}) input`, selection);
await fillIn(
`.participation .selections .form-group:nth-child(${
index + 1
}) input`,
selection
);
} else {
await click(`.participation .selections .form-group:nth-child(${index + 1}) .${selection}.radio input`);
await click(
`.participation .selections .form-group:nth-child(${
index + 1
}) .${selection}.radio input`
);
}
}
}
await click('.participation button[type="submit"]');
await settled();
}

View file

@ -1,5 +1,5 @@
import { click } from '@ember/test-helpers';
export default function(tab) {
export default function (tab) {
return click(`.nav-tabs .${tab} a`);
}

View file

@ -21,7 +21,14 @@
{{content-for "body"}}
{{content-for "test-body"}}
<script src="/testem.js" integrity=""></script>
<div id="qunit"></div>
<div id="qunit-fixture">
<div id="ember-testing-container">
<div id="ember-testing"></div>
</div>
</div>
<script src="/testem.js" integrity="" data-embroider-ignore></script>
<script src="{{rootURL}}assets/vendor.js"></script>
<script src="{{rootURL}}assets/test-support.js"></script>
<script src="{{rootURL}}assets/croodle.js"></script>

View file

@ -3,10 +3,10 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | back-button', function(hooks) {
module('Integration | Component | back-button', function (hooks) {
setupRenderingTest(hooks);
test('it renders a button', async function(assert) {
test('it renders a button', async function (assert) {
await render(hbs`<BackButton />`);
assert.dom('button').exists();

View file

@ -1,14 +1,14 @@
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 { 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) {
module('Integration | Component | create options datetime', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function() {
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
});
@ -19,18 +19,19 @@ module('Integration | Component | create options datetime', function(hooks) {
* that ones could be identifed by class 'ws-inputreplace'
*/
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
// 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(
'poll',
this.store.createRecord('poll', {
pollType: 'FindADate',
options: [{ title: '2015-01-01' }],
})
);
});
await render(hbs`{{create-options-datetime dates=poll.options}}`);
@ -46,18 +47,19 @@ 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
// 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(
'poll',
this.store.createRecord('poll', {
pollType: 'FindADate',
options: [{ title: '2015-01-01T11:11:00.000Z' }],
})
);
});
await render(hbs`{{create-options-datetime dates=poll.options}}`);
@ -73,20 +75,23 @@ module('Integration | Component | create options datetime', function(hooks) {
);
});
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
// 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(
'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`{{create-options-datetime dates=poll.options}}`);
@ -101,31 +106,37 @@ module('Integration | Component | create options datetime', function(hooks) {
'there are two not hidden labels for two different dates'
);
assert.notOk(
findAll('.days .form-group')[0].querySelector('label').classList.contains('sr-only'),
findAll('.days .form-group')[0]
.querySelector('label')
.classList.contains('sr-only'),
'the first label is shown'
);
assert.ok(
findAll('.days .form-group')[1].querySelector('label').classList.contains('sr-only'),
findAll('.days .form-group')[1]
.querySelector('label')
.classList.contains('sr-only'),
'the repeated label on second form-group is hidden by sr-only class'
);
assert.notOk(
findAll('.days .form-group')[2].querySelector('label').classList.contains('sr-only'),
findAll('.days .form-group')[2]
.querySelector('label')
.classList.contains('sr-only'),
'the new label on third form-group is shown'
);
});
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
// 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(
'poll',
this.store.createRecord('poll', {
options: [{ title: '2015-01-01' }, { title: '2015-02-02' }],
})
);
});
await render(hbs`{{create-options-datetime dates=poll.options}}`);
@ -147,24 +158,29 @@ module('Integration | Component | create options datetime', function(hooks) {
'new input has correct label'
);
assert.ok(
findAll('.days .form-group')[1].querySelector('label').classList.contains('sr-only'),
'label ofnew input is hidden cause it\'s repeated'
findAll('.days .form-group')[1]
.querySelector('label')
.classList.contains('sr-only'),
"label ofnew input is hidden cause it's repeated"
);
});
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
// 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(
'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}} />`);

View file

@ -4,14 +4,14 @@ import { setupRenderingTest } from 'ember-qunit';
import { render, findAll, blur, fillIn, focus } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | create options', function(hooks) {
module('Integration | Component | create options', function (hooks) {
setupRenderingTest(hooks);
hooks.beforeEach(function() {
hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store');
});
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);
// validation is based on validation of every option fragment
@ -36,26 +36,34 @@ module('Integration | Component | create options', function(hooks) {
/>
`);
assert.dom('.form-group').exists({ count: 2 }, 'assumption: renders two form groups');
assert
.dom('.form-group')
.exists({ count: 2 }, 'assumption: renders two form groups');
await fillIn('.form-group:nth-child(1) input', 'foo');
await blur('.form-group:nth-child(1) input');
await fillIn('.form-group:nth-child(2) input', 'foo');
await blur('.form-group:nth-child(2) input');
assert.dom('.form-group:nth-child(2) input')
assert
.dom('.form-group:nth-child(2) input')
.hasClass('is-invalid', 'second input field has validation error');
assert.dom('.form-group:nth-child(2) .invalid-feedback')
assert
.dom('.form-group:nth-child(2) .invalid-feedback')
.exists('validation error is shown');
await fillIn(findAll('input')[0], 'bar');
await blur(findAll('input')[0]);
assert.dom('.form-group .invalid-feedback')
.doesNotExist('there is no validation error anymore after a unique value is entered');
assert.dom('.form-group .is-invalid')
assert
.dom('.form-group .invalid-feedback')
.doesNotExist(
'there is no validation error anymore after a unique value is entered'
);
assert
.dom('.form-group .is-invalid')
.doesNotExist('.is-invalid classes are removed');
});
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
// which validates according to poll model it belongs to
// therefore each option needs to be pushed to poll model to have it as
@ -78,9 +86,7 @@ module('Integration | Component | create options', function(hooks) {
/>
`);
assert.equal(
findAll('.form-group.has-error').length, 0
);
assert.equal(findAll('.form-group.has-error').length, 0);
await focus(findAll('input')[0]);
await blur(findAll('input')[0]);

View file

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

View file

@ -3,17 +3,17 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | next-button', function(hooks) {
module('Integration | Component | next-button', function (hooks) {
setupRenderingTest(hooks);
test('it renders a button', async function(assert) {
test('it renders a button', async function (assert) {
await render(hbs`<NextButton />`);
assert.dom('button').exists();
assert.dom('button').hasAttribute('type', 'submit');
});
test('it renders a loading spinner if `@isPending` is `true`', async function(assert) {
test('it renders a loading spinner if `@isPending` is `true`', async function (assert) {
await render(hbs`<NextButton @isPending={{true}} />`);
assert.dom('button .spinner-border').exists();

View file

@ -3,17 +3,17 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | save-button', function(hooks) {
module('Integration | Component | save-button', function (hooks) {
setupRenderingTest(hooks);
test('it renders a button', async function(assert) {
test('it renders a button', async function (assert) {
await render(hbs`<NextButton />`);
assert.dom('button').exists();
assert.dom('button').hasAttribute('type', 'submit');
});
test('it renders a loading spinner if `@isPending` is `true`', async function(assert) {
test('it renders a loading spinner if `@isPending` is `true`', async function (assert) {
await render(hbs`<NextButton @isPending={{true}} />`);
assert.dom('button .spinner-border').exists();

View file

@ -4,10 +4,10 @@ import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
import { DateTime } from 'luxon';
module('Integration | Helper | format-date-relative', function(hooks) {
module('Integration | Helper | format-date-relative', function (hooks) {
setupRenderingTest(hooks);
test('it formats an ISO date to relative duration from now', async function(assert) {
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}}`);
@ -35,7 +35,7 @@ module('Integration | Helper | format-date-relative', function(hooks) {
assert.dom(this.element).hasText('in 1 year');
});
test('it formats an ISO date to relative duration to now', async function(assert) {
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}}`);

View file

@ -3,11 +3,11 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Helper | mark-as-safe-html', function(hooks) {
module('Integration | Helper | mark-as-safe-html', function (hooks) {
setupRenderingTest(hooks);
// Replace this with your real tests.
test('it renders', async function(assert) {
test('it renders', async function (assert) {
this.set('inputValue', '1234');
await render(hbs`{{mark-as-safe-html "<p>foo</p>"}}`);

View file

@ -3,15 +3,18 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile';
module('Integration | Helper | scroll-first-invalid-element-into-view-port', function(hooks) {
setupRenderingTest(hooks);
module(
'Integration | Helper | scroll-first-invalid-element-into-view-port',
function (hooks) {
setupRenderingTest(hooks);
// Replace this with your real tests.
test('it renders', async function(assert) {
this.set('inputValue', '1234');
// Replace this with your real tests.
test('it renders', async function (assert) {
this.set('inputValue', '1234');
await render(hbs`{{scroll-element-into-view-port inputValue}}`);
await render(hbs`{{scroll-element-into-view-port inputValue}}`);
assert.equal(this.element.textContent.trim(), '1234');
});
});
assert.equal(this.element.textContent.trim(), '1234');
});
}
);

View file

@ -1,34 +1,36 @@
import { get } from '@ember/object';
import { module, test } from 'qunit';
import { startMirage } from 'croodle/initializers/ember-cli-mirage';
import sjcl from 'sjcl';
module('Integration | Mirage api mocking', function(hooks) {
hooks.beforeEach(function() {
module('Integration | Mirage api mocking', function (hooks) {
hooks.beforeEach(function () {
this.server = startMirage();
});
hooks.afterEach(function() {
hooks.afterEach(function () {
this.server.shutdown();
});
test('poll factory | encrypts properties', function(assert) {
test('poll factory | encrypts properties', function (assert) {
let encryptionKey = 'abc';
let poll = this.server.create('poll', {
description: 'bar',
encryptionKey,
title: 'foo'
title: 'foo',
});
assert.equal(JSON.parse(sjcl.decrypt(encryptionKey, get(poll, 'title'))), 'foo');
assert.equal(JSON.parse(sjcl.decrypt(encryptionKey, get(poll, 'description'))), 'bar');
assert.equal(JSON.parse(sjcl.decrypt(encryptionKey, poll.title)), 'foo');
assert.equal(
JSON.parse(sjcl.decrypt(encryptionKey, poll.description)),
'bar'
);
});
test('user factory | encrypts properties', function(assert) {
test('user factory | encrypts properties', function (assert) {
let encryptionKey = 'abc';
let user = this.server.create('user', {
encryptionKey,
name: 'foo'
name: 'foo',
});
assert.equal(JSON.parse(sjcl.decrypt(encryptionKey, get(user, 'name'))), 'foo');
assert.equal(JSON.parse(sjcl.decrypt(encryptionKey, user.name)), 'foo');
});
});

View file

@ -3,23 +3,23 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars';
module('Integration | Modifier | autofocus', function(hooks) {
module('Integration | Modifier | autofocus', function (hooks) {
setupRenderingTest(hooks);
test('it focuses the element', async function(assert) {
test('it focuses the element', async function (assert) {
await render(hbs`<input {{autofocus}} />`);
assert.dom('input').isFocused();
});
test('it focuses the element if `enabled` is `true`', async function(assert) {
test('it focuses the element if `enabled` is `true`', async function (assert) {
this.set('enabled', true);
await render(hbs`<input {{autofocus enabled=this.enabled}} />`);
assert.dom('input').isFocused();
});
test('it does not focus the element if `enabled` is `false`', async function(assert) {
test('it does not focus the element if `enabled` is `false`', async function (assert) {
this.set('enabled', false);
await render(hbs`<input {{autofocus enabled=this.enabled}} />`);

View file

@ -2,14 +2,17 @@ import { module, test } from 'qunit';
import { setupTest } from 'ember-qunit';
import localesMeta from 'croodle/locales/meta';
module('Integration | translations', function(hooks) {
module('Integration | translations', function (hooks) {
setupTest(hooks);
test('all locales have an entry in locales/meta', function(assert) {
test('all locales have an entry in locales/meta', function (assert) {
let intl = this.owner.lookup('service:intl');
intl.locales.forEach((locale) => {
assert.ok(Object.keys(localesMeta).includes(locale), `locales meta data is present for ${locale}`);
assert.ok(
Object.keys(localesMeta).includes(locale),
`locales meta data is present for ${locale}`
);
});
});
});

View file

@ -2,17 +2,14 @@ import PageObject from 'ember-cli-page-object';
import { defaultsForCreate } from 'croodle/tests/pages/defaults';
import { hasFocus } from 'croodle/tests/pages/helpers';
const {
assign
} = Object;
const { assign } = Object;
const {
fillable,
visitable
} = PageObject;
const { fillable, visitable } = PageObject;
export default PageObject.create(assign({}, defaultsForCreate, {
pollType: fillable('.poll-type select'),
pollTypeHasFocus: hasFocus('.poll-type select'),
visit: visitable('/create')
}));
export default PageObject.create(
assign({}, defaultsForCreate, {
pollType: fillable('.poll-type select'),
pollTypeHasFocus: hasFocus('.poll-type select'),
visit: visitable('/create'),
})
);

View file

@ -2,16 +2,14 @@ import PageObject from 'ember-cli-page-object';
import { defaultsForCreate } from 'croodle/tests/pages/defaults';
import { hasFocus } from 'croodle/tests/pages/helpers';
const {
assign
} = Object;
const { assign } = Object;
let {
fillable
} = PageObject;
let { fillable } = PageObject;
export default PageObject.create(assign({}, defaultsForCreate, {
description: fillable('.description textarea'),
title: fillable('.title input'),
titleHasFocus: hasFocus('.title input')
}));
export default PageObject.create(
assign({}, defaultsForCreate, {
description: fillable('.description textarea'),
title: fillable('.title input'),
titleHasFocus: hasFocus('.title input'),
})
);

View file

@ -2,36 +2,30 @@ import PageObject from 'ember-cli-page-object';
import { defaultsForCreate } from 'croodle/tests/pages/defaults';
import { hasFocus } from 'croodle/tests/pages/helpers';
const {
assign
} = Object;
const { assign } = Object;
let {
clickable,
collection,
fillable,
hasClass,
text
} = PageObject;
let { clickable, collection, fillable, hasClass, text } = PageObject;
export default PageObject.create(assign({}, defaultsForCreate, {
days: collection({
itemScope: '.form-group',
labels: text('label:not(.sr-only)', { multiple: true })
}),
times: collection({
itemScope: '.form-group',
item: {
add: clickable('button.add'),
delete: clickable('button.delete'),
label: text('label'),
labelIsHidden: hasClass('label', 'sr-only'),
time: fillable('input')
}
}),
firstTime: {
scope: '.form-group:first',
export default PageObject.create(
assign({}, defaultsForCreate, {
days: collection({
itemScope: '.form-group',
labels: text('label:not(.sr-only)', { multiple: true }),
}),
times: collection({
itemScope: '.form-group',
item: {
add: clickable('button.add'),
delete: clickable('button.delete'),
label: text('label'),
labelIsHidden: hasClass('label', 'sr-only'),
time: fillable('input'),
},
}),
firstTime: {
scope: '.form-group:first',
inputHasFocus: hasFocus('input')
}
}));
inputHasFocus: hasFocus('input'),
},
})
);

View file

@ -5,7 +5,7 @@ import {
fillable,
hasClass,
isVisible,
text
text,
} from 'ember-cli-page-object';
import { defaultsForCreate } from 'croodle/tests/pages/defaults';
import { hasFocus } from 'croodle/tests/pages/helpers';
@ -21,35 +21,44 @@ function selectDates(selector) {
async value(dateOrDateTimes) {
assert(
'selectDates expects an array of date or DateTime (luxon) objects as frist argument',
isArray(dateOrDateTimes) && dateOrDateTimes.every((dateOrDateTime) => dateOrDateTime instanceof Date || DateTime.isDateTime(dateOrDateTime))
)
isArray(dateOrDateTimes) &&
dateOrDateTimes.every(
(dateOrDateTime) =>
dateOrDateTime instanceof Date ||
DateTime.isDateTime(dateOrDateTime)
)
);
for (let i = 0; i < dateOrDateTimes.length; i++) {
let dateOrDateTime = dateOrDateTimes[i];
let date = DateTime.isDateTime(dateOrDateTime) ? dateOrDateTime.toJSDate() : dateOrDateTime;
let date = DateTime.isDateTime(dateOrDateTime)
? dateOrDateTime.toJSDate()
: dateOrDateTime;
await calendarSelect(selector, date);
}
}
},
};
}
export default create(assign({}, defaultsForCreate, {
selectDates: selectDates('[data-test-form-element-for="days"]'),
dateHasError: isVisible('.days.has-error'),
dateError: text('.days .help-block'),
export default create(
assign({}, defaultsForCreate, {
selectDates: selectDates('[data-test-form-element-for="days"]'),
dateHasError: isVisible('.days.has-error'),
dateError: text('.days .help-block'),
textOptions: collection({
itemScope: '.form-group.option',
item: {
add: clickable('button.add'),
delete: clickable('button.delete'),
hasError: hasClass('is-invalid', 'input'),
title: fillable('input')
}
}),
firstTextOption: {
scope: '.form-group.option:first',
textOptions: collection({
itemScope: '.form-group.option',
item: {
add: clickable('button.add'),
delete: clickable('button.delete'),
hasError: hasClass('is-invalid', 'input'),
title: fillable('input'),
},
}),
firstTextOption: {
scope: '.form-group.option:first',
inputHasFocus: hasFocus('input')
}
}));
inputHasFocus: hasFocus('input'),
},
})
);

View file

@ -2,18 +2,15 @@ import PageObject from 'ember-cli-page-object';
import { defaultsForCreate } from 'croodle/tests/pages/defaults';
import { hasFocus } from 'croodle/tests/pages/helpers';
const {
assign
} = Object;
const { assign } = Object;
const {
fillable,
visitable
} = PageObject;
const { fillable, visitable } = PageObject;
export default PageObject.create(assign({}, defaultsForCreate, {
availableAnswers: fillable('.answer-type select'),
availableAnswersHasFocus: hasFocus('.answer-type select'),
save: defaultsForCreate.next,
visit: visitable('/create/settings')
}));
export default PageObject.create(
assign({}, defaultsForCreate, {
availableAnswers: fillable('.answer-type select'),
availableAnswersHasFocus: hasFocus('.answer-type select'),
save: defaultsForCreate.next,
visit: visitable('/create/settings'),
})
);

Some files were not shown because too many files have changed in this diff Show more