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 /api
/coverage/ /coverage/
!.* !.*
.*/
.eslintcache
# ember-try # ember-try
/.node_modules.ember-try/ /.node_modules.ember-try/

View file

@ -7,15 +7,14 @@ module.exports = {
ecmaVersion: 2018, ecmaVersion: 2018,
sourceType: 'module', sourceType: 'module',
ecmaFeatures: { ecmaFeatures: {
legacyDecorators: true legacyDecorators: true,
} },
}, },
plugins: [ plugins: ['ember'],
'ember'
],
extends: [ extends: [
'eslint:recommended', 'eslint:recommended',
'plugin:ember/recommended' 'plugin:ember/recommended',
'plugin:prettier/recommended',
], ],
env: { env: {
browser: true, browser: true,
@ -24,6 +23,8 @@ module.exports = {
// Croodle is not compliant with some of the recommended rules yet. // Croodle is not compliant with some of the recommended rules yet.
// We should refactor the code step by step and enable them as soon // We should refactor the code step by step and enable them as soon
// as the code is compliant. // as the code is compliant.
'ember/classic-decorator-no-classic-methods': 'warn',
'ember/no-controller-access-in-routes': 'warn',
'ember/no-observers': 'warn', 'ember/no-observers': 'warn',
'no-prototype-builtins': 'warn', 'no-prototype-builtins': 'warn',
}, },
@ -31,29 +32,40 @@ module.exports = {
// node files // node files
{ {
files: [ files: [
'.eslintrc.js', './.eslintrc.js',
'.template-lintrc.js', './.prettierrc.js',
'ember-cli-build.js', './.template-lintrc.js',
'testem.js', './ember-cli-build.js',
'blueprints/*/index.js', './testem.js',
'config/**/*.js', './blueprints/*/index.js',
'lib/*/index.js', './config/**/*.js',
'server/**/*.js' './lib/*/index.js',
'./server/**/*.js',
], ],
parserOptions: { parserOptions: {
sourceType: 'script' sourceType: 'script',
}, },
env: { env: {
browser: false, browser: false,
node: true node: true,
}, },
plugins: ['node'], plugins: ['node'],
extends: ['plugin:node/recommended'], extends: ['plugin:node/recommended'],
rules: { rules: {
// this can be removed once the following is fixed // this can be removed once the following is fixed
// https://github.com/mysticatea/eslint-plugin-node/issues/77 // 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* /.env*
/.pnp* /.pnp*
/.sass-cache /.sass-cache
/.eslintcache
/connect.lock /connect.lock
/coverage/ /coverage/
/libpeerconnection.log /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 = { module.exports = {
extends: 'recommended', extends: 'recommended',
rules: { rules: {
'no-action': false,
'no-implicit-this': { 'no-implicit-this': {
allow: ['scroll-first-invalid-element-into-view-port'], allow: ['scroll-first-invalid-element-into-view-port'],
}, },

View file

@ -6,8 +6,7 @@ export default class ApplicationAdapter extends RESTAdapter {
encryption; encryption;
// set namespace to api.php in same subdirectory // set namespace to api.php in same subdirectory
namespace = namespace = window.location.pathname
window.location.pathname
// remove index.html if it's there // remove index.html if it's there
.replace(/index.html$/, '') .replace(/index.html$/, '')
// remove tests prefix which is added by testem (starting with a number) // 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 // add api.php
.concat('/api/index.php') .concat('/api/index.php')
// remove leading slash // 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 BaseBsForm from 'ember-bootstrap/components/bs-form';
import IntlMessage from "../utils/intl-message"; import IntlMessage from '../utils/intl-message';
export default class BsForm extends BaseBsForm { export default class BsForm extends BaseBsForm {
"__ember-bootstrap_subclass" = true; '__ember-bootstrap_subclass' = true;
get hasValidator() { get hasValidator() {
return true; return true;

View file

@ -1,8 +1,8 @@
import Component from "@glimmer/component"; import Component from '@glimmer/component';
import { action } from "@ember/object"; import { action } from '@ember/object';
import { isArray } from "@ember/array"; import { isArray } from '@ember/array';
import { DateTime } from "luxon"; import { DateTime } from 'luxon';
import { tracked } from "@glimmer/tracking"; import { tracked } from '@glimmer/tracking';
export default class CreateOptionsDates extends Component { export default class CreateOptionsDates extends Component {
@tracked calendarCenter = @tracked calendarCenter =
@ -64,21 +64,24 @@ export default class CreateOptionsDates extends Component {
}); });
if (dateRemoved) { if (dateRemoved) {
this.args.updateOptions(this.args.options.filter( this.args.updateOptions(
({ value }) => this.args.options
DateTime.fromISO(value).toISODate() !== dateRemoved.toISODate() .filter(
).map(({ value }) => value), ({ value }) =>
DateTime.fromISO(value).toISODate() !== dateRemoved.toISODate()
)
.map(({ value }) => value)
); );
return; return;
} }
throw new Error( 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 @action
updateCalenderCenter(diff) { 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 Component from '@glimmer/component';
import { inject as service } from "@ember/service"; import { inject as service } from '@ember/service';
import { action } from "@ember/object"; import { action } from '@ember/object';
import { tracked } from "@glimmer/tracking"; import { tracked } from '@glimmer/tracking';
import { TrackedArray } from "tracked-built-ins"; import { TrackedArray } from 'tracked-built-ins';
import { DateTime } from "luxon"; import { DateTime } from 'luxon';
import IntlMessage from "../utils/intl-message"; import IntlMessage from '../utils/intl-message';
class FormDataOption { class FormDataOption {
formData; formData;
@ -23,7 +23,7 @@ class FormDataOption {
const { isPartiallyFilled } = this; const { isPartiallyFilled } = this;
if (isPartiallyFilled) { if (isPartiallyFilled) {
return new IntlMessage( return new IntlMessage(
"create.options-datetime.error.partiallyFilledTime" 'create.options-datetime.error.partiallyFilledTime'
); );
} }
@ -37,7 +37,7 @@ class FormDataOption {
.slice(0, optionsForThisDay.indexOf(this)) .slice(0, optionsForThisDay.indexOf(this))
.any((option) => option.time == this.time); .any((option) => option.time == this.time);
if (isDuplicate) { if (isDuplicate) {
return new IntlMessage("create.options-datetime.error.duplicatedDate"); return new IntlMessage('create.options-datetime.error.duplicatedDate');
} }
return null; return null;
@ -73,7 +73,7 @@ class FormData {
const { options } = this; const { options } = this;
const allOptionsAreValid = options.every((option) => option.isValid); const allOptionsAreValid = options.every((option) => option.isValid);
if (!allOptionsAreValid) { if (!allOptionsAreValid) {
return IntlMessage("create.options-datetime.error.invalidTime"); return IntlMessage('create.options-datetime.error.invalidTime');
} }
return null; return null;
@ -172,7 +172,7 @@ export default class CreateOptionsDatetime extends Component {
if (!successful) { if (!successful) {
this.errorMesage = 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 @action
handleTransition(transition) { handleTransition(transition) {
if (transition.from?.name === "create.options-datetime") { if (transition.from?.name === 'create.options-datetime') {
this.args.updateOptions(this.formData.options); this.args.updateOptions(this.formData.options);
this.router.off('routeWillChange', this.handleTransition); this.router.off('routeWillChange', this.handleTransition);
} }
@ -216,6 +216,6 @@ export default class CreateOptionsDatetime extends Component {
constructor() { constructor() {
super(...arguments); 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 Component from '@glimmer/component';
import { inject as service } from "@ember/service"; import { inject as service } from '@ember/service';
import { action } from "@ember/object"; import { action } from '@ember/object';
import { TrackedArray } from "tracked-built-ins"; import { TrackedArray } from 'tracked-built-ins';
import IntlMessage from "../utils/intl-message"; import IntlMessage from '../utils/intl-message';
import { tracked } from "@glimmer/tracking"; import { tracked } from '@glimmer/tracking';
class FormDataOption { class FormDataOption {
@tracked value; @tracked value;
@ -14,7 +14,7 @@ class FormDataOption {
// Every option must have a label // Every option must have a label
if (!value) { 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 // 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)) .slice(0, this.formData.options.indexOf(this))
.some((option) => option.value === this.value); .some((option) => option.value === this.value);
if (!isUnique) { if (!isUnique) {
return new IntlMessage("create.options.error.duplicatedOption"); return new IntlMessage('create.options.error.duplicatedOption');
} }
return null; return null;
@ -48,11 +48,11 @@ class FormData {
if (options.length < 1) { if (options.length < 1) {
// UI enforces that there is at least one option if poll type is `MakeAPoll`. // 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`. // 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)) { if (options.any((option) => !option.isValid)) {
return new IntlMessage("create.options.error.invalidOption"); return new IntlMessage('create.options.error.invalidOption');
} }
return null; return null;
@ -84,7 +84,7 @@ class FormData {
// enforce minimal options amount for poll of type MakeAPoll // enforce minimal options amount for poll of type MakeAPoll
if (this.options.length === 0 && defaultOptionCount > 0) { if (this.options.length === 0 && defaultOptionCount > 0) {
this.updateOptions(["", ""]); this.updateOptions(['', '']);
} }
} }
} }
@ -109,15 +109,15 @@ export default class CreateOptionsComponent extends Component {
@action @action
handleTransition(transition) { handleTransition(transition) {
if (transition.from?.name === "create.options") { if (transition.from?.name === 'create.options') {
this.args.updateOptions(this.formData.options); this.args.updateOptions(this.formData.options);
this.router.off("routeWillChange", this.handleTransition); this.router.off('routeWillChange', this.handleTransition);
} }
} }
constructor() { constructor() {
super(...arguments); 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|}} {{#each-in this.locales as |localeKey localeName|}}
<option value={{localeKey}} selected={{eq localeKey this.currentLocale}}> <option value={{localeKey}} selected={{eq localeKey this.currentLocale}}>
{{localeName}} {{localeName}}

View file

@ -19,7 +19,9 @@ export default class LanguageSelect extends Component {
handleChange(event) { handleChange(event) {
const locale = event.target.value; 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; this.powerCalendar.locale = locale;
if (window.localStorage) { if (window.localStorage) {

View file

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

View file

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

View file

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

View file

@ -1,18 +1,18 @@
import Controller from "@ember/controller"; import Controller from '@ember/controller';
import { inject as service } from "@ember/service"; import { inject as service } from '@ember/service';
import { action } from "@ember/object"; import { action } from '@ember/object';
export default class CreateIndex extends Controller { export default class CreateIndex extends Controller {
@service router; @service router;
@action @action
submit() { submit() {
this.router.transitionTo("create.meta"); this.router.transitionTo('create.meta');
} }
@action @action
handleTransition(transition) { handleTransition(transition) {
if (transition.from?.name === "create.index") { if (transition.from?.name === 'create.index') {
const { poll, formData } = this.model; const { poll, formData } = this.model;
poll.pollType = formData.pollType; poll.pollType = formData.pollType;
@ -22,6 +22,6 @@ export default class CreateIndex extends Controller {
constructor() { constructor() {
super(...arguments); 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 { inject as service } from '@ember/service';
import { action } from "@ember/object"; import { action } from '@ember/object';
import Controller from "@ember/controller"; import Controller from '@ember/controller';
export default class CreateMetaController extends Controller { export default class CreateMetaController extends Controller {
@service router; @service router;
@action @action
previousPage() { previousPage() {
this.router.transitionTo("create.index"); this.router.transitionTo('create.index');
} }
@action @action
submit() { submit() {
this.router.transitionTo("create.options"); this.router.transitionTo('create.options');
} }
@action @action
handleTransition(transition) { handleTransition(transition) {
if (transition.from?.name === "create.meta") { if (transition.from?.name === 'create.meta') {
const { poll, formData } = this.model; const { poll, formData } = this.model;
poll.title = formData.title; poll.title = formData.title;
@ -28,6 +28,6 @@ export default class CreateMetaController extends Controller {
constructor() { constructor() {
super(...arguments); 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 Controller from '@ember/controller';
import { action } from "@ember/object"; import { action } from '@ember/object';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { inject as service } from '@ember/service'; import { inject as service } from '@ember/service';
@ -8,21 +8,23 @@ export default class CreateOptionsDatetimeController extends Controller {
@action @action
nextPage() { nextPage() {
this.router.transitionTo("create.settings"); this.router.transitionTo('create.settings');
} }
@action @action
previousPage() { previousPage() {
this.router.transitionTo("create.options"); this.router.transitionTo('create.options');
} }
@action @action
updateOptions(options) { updateOptions(options) {
this.model.options = 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() .sort()
.map((isoString) => { .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 Controller from '@ember/controller';
import { inject as service } from "@ember/service"; import { inject as service } from '@ember/service';
import { action } from "@ember/object"; import { action } from '@ember/object';
export default class CreateOptionsController extends Controller { export default class CreateOptionsController extends Controller {
@service router; @service router;
@ -10,21 +10,21 @@ export default class CreateOptionsController extends Controller {
const { isFindADate } = this.model; const { isFindADate } = this.model;
if (isFindADate) { if (isFindADate) {
this.router.transitionTo("create.options-datetime"); this.router.transitionTo('create.options-datetime');
} else { } else {
this.router.transitionTo("create.settings"); this.router.transitionTo('create.settings');
} }
} }
@action @action
previousPage() { previousPage() {
this.router.transitionTo("create.meta"); this.router.transitionTo('create.meta');
} }
@action @action
updateOptions(newOptions) { updateOptions(newOptions) {
this.model.options = newOptions.map(({ value }) => 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) { set expirationDuration(value) {
this.model.expirationDate = isPresent(value) this.model.expirationDate = isPresent(value)
? DateTime.local().plus(Duration.fromISO(value)).toISO() ? DateTime.local().plus(Duration.fromISO(value)).toISO()
: ""; : '';
} }
get expirationDurations() { get expirationDurations() {
return [ return [
{ id: 'P7D', labelTranslation: 'create.settings.expirationDurations.P7D' }, {
{ id: 'P1M', labelTranslation: 'create.settings.expirationDurations.P1M' }, id: 'P7D',
{ id: 'P3M', labelTranslation: 'create.settings.expirationDurations.P3M' }, labelTranslation: 'create.settings.expirationDurations.P7D',
{ id: 'P6M', labelTranslation: 'create.settings.expirationDurations.P6M' }, },
{ id: 'P1Y', labelTranslation: 'create.settings.expirationDurations.P1Y' }, {
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' }, { id: '', labelTranslation: 'create.settings.expirationDurations.never' },
]; ];
} }
@ -82,13 +97,16 @@ export default class CreateSettings extends Controller {
return option.hasTime; return option.hasTime;
}) })
) { ) {
this.set('model.timezone', Intl.DateTimeFormat().resolvedOptions().timeZone); this.set(
'model.timezone',
Intl.DateTimeFormat().resolvedOptions().timeZone
);
} }
// save poll // save poll
try { try {
await poll.save(); await poll.save();
} catch(err) { } catch (err) {
this.flashMessages.danger('error.poll.savingFailed'); this.flashMessages.danger('error.poll.savingFailed');
throw err; throw err;

View file

@ -34,7 +34,9 @@ export default class PollController extends Controller {
if (isEmpty(expirationDate)) { if (isEmpty(expirationDate)) {
return false; 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 { model: poll } = this;
const { timezone: pollTimezone } = poll; const { timezone: pollTimezone } = poll;
return isPresent(pollTimezone) && Intl.DateTimeFormat().resolvedOptions().timeZone !== pollTimezone; return (
isPresent(pollTimezone) &&
Intl.DateTimeFormat().resolvedOptions().timeZone !== pollTimezone
);
} }
get mustChooseTimezone() { get mustChooseTimezone() {
@ -85,7 +90,10 @@ export default class PollController extends Controller {
this.encryptionKey !== this.encryption.key this.encryptionKey !== this.encryption.key
) { ) {
// work-a-round for url not being updated // 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); this.set('encryptionKey', this.encryption.key);
} }

View file

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

View file

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

View file

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

View file

@ -10,16 +10,16 @@ export default {
let availableLocales = Object.keys(localesMeta); let availableLocales = Object.keys(localesMeta);
let locale = getLocale(availableLocales); 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); powerCalendar.set('local', locale);
} },
}; };
function getLocale(availableLocales) { function getLocale(availableLocales) {
let methods = [ let methods = [getSavedLocale, getLocaleByBrowser];
getSavedLocale,
getLocaleByBrowser
];
let locale; let locale;
methods.any((method) => { methods.any((method) => {

View file

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

View file

@ -40,8 +40,7 @@ export default class Option extends Fragment {
} }
get hasTime() { get hasTime() {
return this.isDate && return this.isDate && this.title.length >= 'YYYY-MM-DDTHH:mm'.length;
this.title.length >= 'YYYY-MM-DDTHH:mm'.length;
} }
get time() { get time() {
@ -67,6 +66,11 @@ export default class Option extends Fragment {
if (!datetime.isValid) { if (!datetime.isValid) {
return; 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 // polls description
@attr('string', { @attr('string', {
defaultValue: '' defaultValue: '',
}) })
description; description;
// ISO 8601 date + time string in UTC // ISO 8601 date + time string in UTC
@attr('string', { @attr('string', {
includePlainOnCreate: 'serverExpirationDate' includePlainOnCreate: 'serverExpirationDate',
}) })
expirationDate; expirationDate;
@ -61,7 +61,7 @@ export default class Poll extends Model {
// Croodle version poll got created with // Croodle version poll got created with
@attr('string', { @attr('string', {
encrypted: false encrypted: false,
}) })
version; version;

View file

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

View file

@ -1,6 +1,10 @@
import { modifier } from 'ember-modifier'; 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) { if (!enabled) {
return; return;
} }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -27,13 +27,13 @@ export default class ApplicationSerializer extends RESTSerializer {
* implement decryption * implement decryption
*/ */
normalize(modelClass, resourceHash, prop) { normalize(modelClass, resourceHash, prop) {
// run before serialization of attribute hash // run before serialization of attribute hash
modelClass.eachAttribute(function(key, attributes) { modelClass.eachAttribute(function (key, attributes) {
if ( if (attributes.options.encrypted !== false) {
attributes.options.encrypted !== false if (
) { typeof resourceHash[key] !== 'undefined' &&
if (typeof resourceHash[key] !== 'undefined' && resourceHash[key] !== null) { resourceHash[key] !== null
) {
resourceHash[key] = this.encryption.decrypt(resourceHash[key]); resourceHash[key] = this.encryption.decrypt(resourceHash[key]);
} }
} }
@ -63,9 +63,7 @@ export default class ApplicationSerializer extends RESTSerializer {
} }
// encrypt after serialization of attribute hash // encrypt after serialization of attribute hash
if ( if (attribute.options.encrypted !== false) {
attribute.options.encrypted !== false
) {
json[key] = this.encryption.encrypt(json[key]); 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 { isEmpty } from '@ember/utils';
import ApplicationSerializer from './application'; import ApplicationSerializer from './application';
export default class PollSerializer extends ApplicationSerializer.extend(EmbeddedRecordsMixin) { export default class PollSerializer extends ApplicationSerializer.extend(
EmbeddedRecordsMixin
) {
attrs = { attrs = {
users: { users: {
deserialize: 'records' deserialize: 'records',
} },
}; };
legacySupport(resourceHash) { legacySupport(resourceHash) {

View file

@ -14,17 +14,17 @@ export default class UserSerializer extends ApplicationSerializer {
* and selection property "type" where named "id" * and selection property "type" where named "id"
*/ */
if (!isEmpty(resourceHash.selections[0].value)) { if (!isEmpty(resourceHash.selections[0].value)) {
resourceHash.selections.forEach(function(selection, index) { resourceHash.selections.forEach(function (selection, index) {
if (typeof selection.value === 'string') { if (typeof selection.value === 'string') {
resourceHash.selections[index] = { resourceHash.selections[index] = {
label: selection.value label: selection.value,
}; };
} else { } else {
resourceHash.selections[index] = { resourceHash.selections[index] = {
icon: selection.value.icon, icon: selection.value.icon,
label: selection.value.label, label: selection.value.label,
labelTranslation: selection.value.labelTranslation, 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; key = null;
decrypt(value) { decrypt(value) {
return JSON.parse( return JSON.parse(sjcl.decrypt(this.key, value));
sjcl.decrypt(
this.key,
value
)
);
} }
encrypt(value) { encrypt(value) {
return sjcl.encrypt( return sjcl.encrypt(this.key, JSON.stringify(value));
this.key,
JSON.stringify(value)
);
} }
generateKey() { generateKey() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -2,15 +2,11 @@
const browsers = [ const browsers = [
'last 2 Chrome versions', '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', 'last 2 Firefox versions',
'Firefox ESR', 'Firefox ESR',
'last 2 Safari versions', 'last 2 Safari versions',
]; ];
module.exports = { module.exports = {
browsers browsers,
}; };

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,5 +1,5 @@
import { Model, belongsTo } from 'ember-cli-mirage'; import { Model, belongsTo } from 'ember-cli-mirage';
export default Model.extend({ 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. Seed your development database using your factories.
This data will not be loaded in your tests. This data will not be loaded in your tests.
Make sure to define a factory for each model you want to create. Make sure to define a factory for each model you want to create.
*/ */
// server.createList('post', 10); // server.createList('post', 10);
} }

View file

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

View file

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

View file

@ -9,19 +9,20 @@ import { isArray } from '@ember/array';
import { get } from '@ember/object'; import { get } from '@ember/object';
import sjcl from 'sjcl'; import sjcl from 'sjcl';
export default function(propertiesToEncrypt, model) { export default function (propertiesToEncrypt, model) {
assert('first argument must be an array', isArray(propertiesToEncrypt)); 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 = { let data = {
encryptionKey: undefined encryptionKey: undefined,
}; };
propertiesToEncrypt.forEach((propertyToEncrypt) => { propertiesToEncrypt.forEach((propertyToEncrypt) => {
let value = JSON.stringify( let value = JSON.stringify(get(model, propertyToEncrypt));
get(model, propertyToEncrypt)
);
data[propertyToEncrypt] = sjcl.encrypt(passphrase, value); data[propertyToEncrypt] = sjcl.encrypt(passphrase, value);
}); });

View file

@ -11,20 +11,24 @@
}, },
"scripts": { "scripts": {
"build": "ember build --environment=production", "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:hbs": "ember-template-lint .",
"lint:js": "eslint .", "lint:hbs:fix": "ember-template-lint . --fix",
"lint:js": "eslint . --cache",
"release": "release-it", "release": "release-it",
"lint:js:fix": "eslint . --fix",
"start": "ember serve", "start": "ember serve",
"test": "npm-run-all lint:* test:*", "test": "npm-run-all lint test:*",
"test:ember": "ember test", "test:ember": "ember test",
"test:bundlesize": "ember bundlesize: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)" "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": { "devDependencies": {
"@ember/optional-features": "^2.0.0", "@ember/optional-features": "^2.0.0",
"@glimmer/component": "^1.0.1", "@ember/test-helpers": "^2.6.0",
"@glimmer/tracking": "^1.0.0", "@glimmer/component": "^1.0.4",
"@glimmer/tracking": "^1.0.4",
"babel-eslint": "^10.1.0", "babel-eslint": "^10.1.0",
"bootstrap": "^4.3.1", "bootstrap": "^4.3.1",
"broccoli-asset-rev": "^3.0.0", "broccoli-asset-rev": "^3.0.0",
@ -32,10 +36,10 @@
"ember-awesome-macros": "^6.0.0", "ember-awesome-macros": "^6.0.0",
"ember-bootstrap": "^5.0.0", "ember-bootstrap": "^5.0.0",
"ember-classic-decorator": "^3.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-acceptance-test-helpers": "^1.0.0",
"ember-cli-app-version": "^6.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-browser-navigation-button-test-helper": "^0.3.0",
"ember-cli-browserstack": "^2.0.0", "ember-cli-browserstack": "^2.0.0",
"ember-cli-bundlesize": "^0.3.0", "ember-cli-bundlesize": "^0.3.0",
@ -44,43 +48,48 @@
"ember-cli-dependency-checker": "^3.2.0", "ember-cli-dependency-checker": "^3.2.0",
"ember-cli-deprecation-workflow": "^2.0.0", "ember-cli-deprecation-workflow": "^2.0.0",
"ember-cli-flash": "^2.0.0", "ember-cli-flash": "^2.0.0",
"ember-cli-htmlbars": "^5.2.0", "ember-cli-htmlbars": "^5.7.2",
"ember-cli-inject-live-reload": "^2.0.2", "ember-cli-inject-live-reload": "^2.1.0",
"ember-cli-mirage": "^2.0.0", "ember-cli-mirage": "^2.0.0",
"ember-cli-page-object": "^1.11.0", "ember-cli-page-object": "^1.11.0",
"ember-cli-sass": "^10.0.0", "ember-cli-sass": "^10.0.0",
"ember-cli-sri": "^2.1.1", "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-composable-helpers": "^4.0.0",
"ember-data": "~3.28.0", "ember-data": "~3.28.6",
"ember-data-model-fragments": "^6.0.0", "ember-data-model-fragments": "^6.0.0",
"ember-decorators": "^6.1.1", "ember-decorators": "^6.1.1",
"ember-export-application-global": "^2.0.1", "ember-export-application-global": "^2.0.1",
"ember-fetch": "^8.0.1", "ember-fetch": "^8.1.1",
"ember-intl": "^4.2.2", "ember-intl": "^4.2.2",
"ember-load-initializers": "^2.1.1", "ember-load-initializers": "^2.1.2",
"ember-math-helpers": "^2.8.1", "ember-math-helpers": "^2.8.1",
"ember-maybe-import-regenerator": "^0.1.6", "ember-maybe-import-regenerator": "^0.1.6",
"ember-modifier": "^3.2.7", "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": "^0.20.0",
"ember-power-calendar-luxon": "^0.5.0", "ember-power-calendar-luxon": "^0.5.0",
"ember-qunit": "^4.6.0", "ember-qunit": "^5.1.5",
"ember-resolver": "^8.0.0", "ember-resolver": "^8.0.3",
"ember-source": "~3.28.0", "ember-source": "~3.28.8",
"ember-template-lint": "^2.9.1", "ember-template-lint": "^3.15.0",
"ember-test-selectors": "^5.0.0", "ember-test-selectors": "^5.0.0",
"ember-transition-helper": "^1.0.0", "ember-transition-helper": "^1.0.0",
"ember-truth-helpers": "^3.0.0", "ember-truth-helpers": "^3.0.0",
"eslint": "^7.5.0", "eslint": "^7.32.0",
"eslint-plugin-ember": "^8.9.1", "eslint-config-prettier": "^8.3.0",
"eslint-plugin-ember": "^10.5.8",
"eslint-plugin-node": "^11.1.0", "eslint-plugin-node": "^11.1.0",
"eslint-plugin-prettier": "^3.4.1",
"eslint-plugin-qunit": "^6.2.0",
"fs-extra": "^9.0.0", "fs-extra": "^9.0.0",
"lerna-changelog": "^1.0.0", "lerna-changelog": "^1.0.0",
"loader.js": "^4.7.0", "loader.js": "^4.7.0",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"open-iconic": "^1.1.1", "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": "^16.0.0",
"release-it-lerna-changelog": "^5.0.0", "release-it-lerna-changelog": "^5.0.0",
"sass": "^1.19.0", "sass": "^1.19.0",

View file

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

View file

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

View file

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

View file

@ -2,15 +2,17 @@ import { visit } from '@ember/test-helpers';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
module('Acceptance | build', function(hooks) { module('Acceptance | build', function (hooks) {
setupApplicationTest(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('/'); await visit('/');
// head is not available through `find()`, `assert.dom()` or `this.element.querySelector()` // head is not available through `find()`, `assert.dom()` or `this.element.querySelector()`
// cause they are scoped to `#ember-testing-container`. // 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'); assert.ok(buildInfoEl, 'tag exists');
let content = buildInfoEl.content; 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('/'); await visit('/');
// `find()`, `assert.dom()` and `this.element.querySelector()` are all scoped to `#testing-container` // `find()`, `assert.dom()` and `this.element.querySelector()` are all scoped to `#testing-container`
// and therefore don't have access to head // 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 // 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. // that one is replaced by `tests/index.html` for testing.
['link', 'script', 'style'].forEach((type) => { ['link', 'script', 'style'].forEach((type) => {
assert.notOk( 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}' '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 { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
module('Acceptance | i18n', function(hooks) { module('Acceptance | i18n', function (hooks) {
hooks.beforeEach(function() { hooks.beforeEach(function () {
window.localStorage.setItem('locale', 'de'); window.localStorage.setItem('locale', 'de');
}); });
setupApplicationTest(hooks); setupApplicationTest(hooks);
test('locale is saved in localStorage', async function(assert) { test('locale is saved in localStorage', async function (assert) {
await visit('/'); 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'); await fillIn('.language-select', 'en');
assert.equal(find('.language-select').value, 'en'); assert.equal(find('.language-select').value, 'en');
assert.equal( assert.equal(
window.localStorage.getItem('locale'), 'en', window.localStorage.getItem('locale'),
'en',
'persisted in localeStorage' '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 PollParticipationPage from 'croodle/tests/pages/poll/participation';
import PollEvaluationPage from 'croodle/tests/pages/poll/evaluation'; import PollEvaluationPage from 'croodle/tests/pages/poll/evaluation';
module('Acceptance | legacy support', function(hooks) { module('Acceptance | legacy support', function (hooks) {
let yesLabel; let yesLabel;
let maybeLabel; let maybeLabel;
let noLabel; let noLabel;
hooks.beforeEach(function() { hooks.beforeEach(function () {
window.localStorage.setItem('locale', 'en'); window.localStorage.setItem('locale', 'en');
}); });
@ -21,58 +21,115 @@ module('Acceptance | legacy support', function(hooks) {
setupIntl(hooks); setupIntl(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(function() { hooks.beforeEach(function () {
yesLabel = t('answerTypes.yes.label').toString(); yesLabel = t('answerTypes.yes.label').toString();
maybeLabel = t('answerTypes.maybe.label').toString(); maybeLabel = t('answerTypes.maybe.label').toString();
noLabel = t('answerTypes.no.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'; const encryptionKey = '5MKFuNTKILUXw6RuqkAw6ooZw4k3mWWx98ZQw8vH';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey, encryptionKey,
// property 'id' of answers has been renamed to 'type' in v0.4.0 // 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' }], answers: [
options: [{ 'title': '2015-12-24T17:00:00.000Z' },{ 'title': '2015-12-24T19:00:00.000Z' },{ 'title': '2015-12-31T22:59:00.000Z' }], {
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: [ users: [
this.server.create('user', { this.server.create('user', {
encryptionKey, encryptionKey,
name: 'Fritz Bauer', name: 'Fritz Bauer',
// selections.value was renamed to selections.label // selections.value was renamed to selections.label
// selections.id was renamed to selections.type // 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 tag had have wrong format
version: 'v0.3-0' version: 'v0.3-0',
}) }),
], ],
// version tag had have wrong format // version tag had have wrong format
version: 'v0.3-0' version: 'v0.3-0',
}); });
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`); await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.participation'); assert.equal(currentRouteName(), 'poll.participation');
assert.deepEqual( assert.deepEqual(PollParticipationPage.options().labels, [
PollParticipationPage.options().labels, Intl.DateTimeFormat('en-US', {
[ dateStyle: 'full',
Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-24T17:00:00.000Z')), timeStyle: 'short',
Intl.DateTimeFormat('en-US', { timeStyle: "short" }).format(new Date('2015-12-24T19:00:00.000Z')), }).format(new Date('2015-12-24T17:00:00.000Z')),
Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-31T22:59:00.000Z')), Intl.DateTimeFormat('en-US', { timeStyle: 'short' }).format(
] new Date('2015-12-24T19:00:00.000Z')
); ),
assert.deepEqual( Intl.DateTimeFormat('en-US', {
PollParticipationPage.options().answers, dateStyle: 'full',
[yesLabel, maybeLabel, noLabel] timeStyle: 'short',
); }).format(new Date('2015-12-31T22:59:00.000Z')),
]);
assert.deepEqual(PollParticipationPage.options().answers, [
yesLabel,
maybeLabel,
noLabel,
]);
await switchTab('evaluation'); await switchTab('evaluation');
assert.equal(currentRouteName(), 'poll.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.ok(participant, 'user exists in participants table');
assert.deepEqual( assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, noLabel, noLabel], participant.selections.map((_) => _.answer),
[yesLabel, noLabel, noLabel],
'participants table shows correct answers for new participant' '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']); await pollParticipate('Hermann Langbein', ['yes', 'maybe', 'yes']);
assert.equal(currentRouteName(), 'poll.evaluation'); 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.ok(participant, 'user exists in participants table');
assert.deepEqual( assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, maybeLabel, yesLabel], participant.selections.map((_) => _.answer),
[yesLabel, maybeLabel, yesLabel],
'participants table shows correct answers for new participant' '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 encryptionKey = 'Rre6dAGOYLW9gYKOP4LhX7Qwfhe5Th3je0uKDtyy';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey, encryptionKey,
answerType: 'FreeText', answerType: 'FreeText',
answers: [], answers: [],
options: [{ 'title': 'apple pie' }, { 'title': 'pecan pie' }, { 'title': 'plum pie' }], options: [
{ title: 'apple pie' },
{ title: 'pecan pie' },
{ title: 'plum pie' },
],
pollType: 'MakeAPoll', pollType: 'MakeAPoll',
users: [ users: [
this.server.create('user', { this.server.create('user', {
@ -104,46 +169,59 @@ module('Acceptance | legacy support', function(hooks) {
name: 'Paul Levi', name: 'Paul Levi',
// selections.value was renamed to selections.label // selections.value was renamed to selections.label
// selections.id was renamed to selections.type // 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 tag had have wrong format
version: 'v0.3-0' version: 'v0.3-0',
}) }),
], ],
// version tag had have wrong format // version tag had have wrong format
version: 'v0.3-0' version: 'v0.3-0',
}); });
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`); await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.participation'); assert.equal(currentRouteName(), 'poll.participation');
assert.deepEqual( assert.deepEqual(PollParticipationPage.options().labels, [
PollParticipationPage.options().labels, 'apple pie',
[ 'pecan pie',
'apple pie', 'plum pie',
'pecan pie', ]);
'plum pie'
]
);
await switchTab('evaluation'); await switchTab('evaluation');
assert.equal(currentRouteName(), 'poll.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.ok(participant, 'user exists in participants table');
assert.deepEqual( 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' 'participants table shows correct answers for new participant'
); );
await switchTab('participation'); await switchTab('participation');
assert.equal(currentRouteName(), 'poll.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'); 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.ok(participant, 'user exists in participants table');
assert.deepEqual( 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' 'participants table shows correct answers for new participant'
); );
}); });

View file

@ -5,7 +5,7 @@ import {
currentURL, currentURL,
currentRouteName, currentRouteName,
waitFor, waitFor,
visit visit,
} from '@ember/test-helpers'; } from '@ember/test-helpers';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-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 PollEvaluationPage from 'croodle/tests/pages/poll/evaluation';
import pollParticipate from 'croodle/tests/helpers/poll-participate'; 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 yesLabel;
let noLabel; let noLabel;
hooks.beforeEach(function() { hooks.beforeEach(function () {
window.localStorage.setItem('locale', 'en'); window.localStorage.setItem('locale', 'en');
}); });
@ -26,19 +26,23 @@ module('Acceptance | participate in a poll', function(hooks) {
setupIntl(hooks); setupIntl(hooks);
setupMirage(hooks); setupMirage(hooks);
hooks.beforeEach(function() { hooks.beforeEach(function () {
yesLabel = t('answerTypes.yes.label').toString(); yesLabel = t('answerTypes.yes.label').toString();
noLabel = t('answerTypes.no.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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey encryptionKey,
}); });
await visit(`/poll/${poll.id}?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']); await pollParticipate('Max Meiner', ['yes', 'no']);
assert.equal(currentRouteName(), 'poll.evaluation'); assert.equal(currentRouteName(), 'poll.evaluation');
@ -47,142 +51,202 @@ module('Acceptance | participate in a poll', function(hooks) {
`encryptionKey=${encryptionKey}`, `encryptionKey=${encryptionKey}`,
'encryption key is part of query params' 'encryption key is part of query params'
); );
assert.equal(PollEvaluationPage.participants.length, 1, 'user is added to participants table'); assert.equal(
let participant = PollEvaluationPage.participants.filterBy('name', 'Max Meiner')[0]; 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.ok(participant, 'user exists in participants table');
assert.deepEqual( assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, noLabel], participant.selections.map((_) => _.answer),
[yesLabel, noLabel],
'participants table shows correct answers for new participant' 'participants table shows correct answers for new participant'
); );
await click('.nav .participation'); await click('.nav .participation');
assert.equal(currentRouteName(), 'poll.participation'); assert.equal(currentRouteName(), 'poll.participation');
assert.equal(find('.name input').value, '', 'input for name is cleared'); assert.equal(find('.name input').value, '', 'input for name is cleared');
assert.ok( assert.notOk(
!findAll('input[type="radio"]').toArray().some((el) => el.checked), findAll('input[type="radio"]')
.toArray()
.some((el) => el.checked),
'radios are cleared' 'radios are cleared'
); );
await pollParticipate('Peter Müller', ['yes', 'yes']); await pollParticipate('Peter Müller', ['yes', 'yes']);
assert.equal(currentRouteName(), 'poll.evaluation'); assert.equal(currentRouteName(), 'poll.evaluation');
assert.equal(PollEvaluationPage.participants.length, 2, 'user is added to participants table'); assert.equal(
participant = PollEvaluationPage.participants.filterBy('name', 'Peter Müller')[0]; 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.ok(participant, 'user exists in participants table');
assert.deepEqual( assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, yesLabel], participant.selections.map((_) => _.answer),
[yesLabel, yesLabel],
'participants table shows correct answers for new participant' '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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
answerType: 'FreeText', answerType: 'FreeText',
answers: [], answers: [],
encryptionKey encryptionKey,
}); });
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`) await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.participation'); assert.equal(currentRouteName(), 'poll.participation');
await pollParticipate('Max Manus', ['answer 1', 'answer 2']); await pollParticipate('Max Manus', ['answer 1', 'answer 2']);
assert.equal(currentRouteName(), 'poll.evaluation'); 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.ok(participant, 'user exists in participants table');
assert.deepEqual( 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' '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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey, 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'); assert.equal(currentRouteName(), 'poll.participation');
await pollParticipate('Karl Käfer', ['yes', null]); await pollParticipate('Karl Käfer', ['yes', null]);
assert.equal(currentRouteName(), 'poll.evaluation'); 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.ok(participant, 'user exists in participants table');
assert.deepEqual( assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, ''], participant.selections.map((_) => _.answer),
[yesLabel, ''],
'participants table shows correct answers for new participant' '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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
anonymousUser: true, 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'); assert.equal(currentRouteName(), 'poll.participation');
await pollParticipate(null, ['yes', 'no']); await pollParticipate(null, ['yes', 'no']);
assert.equal(currentRouteName(), 'poll.evaluation'); 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]; let participant = PollEvaluationPage.participants.filterBy('name', '')[0];
assert.ok(participant, 'user exists in participants table'); assert.ok(participant, 'user exists in participants table');
assert.deepEqual( assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, noLabel], participant.selections.map((_) => _.answer),
[yesLabel, noLabel],
'participants table shows correct answers for new participant' '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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey encryptionKey,
}); });
this.server.post('/users', undefined, 503); 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.equal(currentRouteName(), 'poll.participation');
assert.dom('[data-test-modal="saving-failed"] .modal-content') assert
.isNotVisible('failed saving notification is not shown before attempt to save'); .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']); 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'); .isVisible('user gets notified that saving failed');
this.server.post('/users'); this.server.post('/users');
await click('[data-test-modal="saving-failed"] [data-test-button="retry"]'); await click('[data-test-modal="saving-failed"] [data-test-button="retry"]');
assert.dom('[data-test-modal="saving-failed"] .modal-content') assert
.isNotVisible('Notification is hidden after another save attempt was successful'); .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(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.ok(participant, 'user exists in participants table');
assert.deepEqual( assert.deepEqual(
participant.selections.map((_) => _.answer), [yesLabel, noLabel], participant.selections.map((_) => _.answer),
[yesLabel, noLabel],
'participants table shows correct answers for new participant' '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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey encryptionKey,
}); });
let resolveSubmission; let resolveSubmission;
let resolveSubmissionWith; let resolveSubmissionWith;
this.server.post('/users', function(schema) { this.server.post('/users', function (schema) {
return new Promise((resolve) => { return new Promise((resolve) => {
let attrs = this.normalizedRequestAttrs(); 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']); pollParticipate('John Doe', ['yes', 'no']);
await waitFor('[data-test-button="submit"] .spinner-border', { await waitFor('[data-test-button="submit"] .spinner-border', {
timeoutMessage: 'timeout while waiting for loading spinner to appear', 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 // resolve promise for test to finish
// need to resolve with a valid response cause otherwise Ember Data would throw // need to resolve with a valid response cause otherwise Ember Data would throw
resolveSubmission(resolveSubmissionWith); resolveSubmission(resolveSubmissionWith);
}); });
module('validation', function() { module('validation', function () {
test('shows validation errors for participation form on submit', async function(assert) { test('shows validation errors for participation form on submit', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789'; let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey, encryptionKey,
}); });
await visit(`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`); await visit(
`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`
);
await click('button[type="submit"]'); await click('button[type="submit"]');
assert.dom('[data-test-form-element="name"] input').hasClass('is-invalid'); assert
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="yes"]').hasClass('is-invalid'); .dom('[data-test-form-element="name"] input')
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="no"]').hasClass('is-invalid'); .hasClass('is-invalid');
assert.dom('[data-test-form-element="option-2018-01-01"] input[id$="yes"]').hasClass('is-invalid'); assert
assert.dom('[data-test-form-element="option-2018-01-01"] input[id$="no"]').hasClass('is-invalid'); .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.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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
anonymousUser: true, anonymousUser: true,
encryptionKey, encryptionKey,
}); });
await visit(`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`); await visit(
`/poll/${poll.id}/participation?encryptionKey=${encryptionKey}`
);
await click('button[type="submit"]'); await click('button[type="submit"]');
assert.dom('[data-test-form-element="name"] input').hasClass('is-valid'); 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
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="no"]').hasClass('is-invalid'); .dom('[data-test-form-element="option-2017-12-24"] input[id$="yes"]')
assert.dom('[data-test-form-element="option-2018-01-01"] input[id$="yes"]').hasClass('is-invalid'); .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$="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
assert.equal(currentRouteName(), 'poll.participation', 'invalid form prevents a transition'); .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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey, 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"]'); await click('button[type="submit"]');
assert.dom('[data-test-form-element="name"] input').hasClass('is-invalid'); assert
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="yes"]').hasClass('is-valid'); .dom('[data-test-form-element="name"] input')
assert.dom('[data-test-form-element="option-2017-12-24"] input[id$="no"]').hasClass('is-valid'); .hasClass('is-invalid');
assert.dom('[data-test-form-element="option-2018-01-01"] input[id$="yes"]').hasClass('is-valid'); assert
assert.dom('[data-test-form-element="option-2018-01-01"] input[id$="no"]').hasClass('is-valid'); .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.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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey encryptionKey,
}); });
let resolveSubmission; let resolveSubmission;
let resolveSubmissionWith; let resolveSubmissionWith;
this.server.post('/users', function(schema) { this.server.post('/users', function (schema) {
return new Promise((resolve) => { return new Promise((resolve) => {
let attrs = this.normalizedRequestAttrs(); 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']); pollParticipate('John Doe', ['yes', 'no']);
await waitFor('[data-test-button="submit"] .spinner-border', { await waitFor('[data-test-button="submit"] .spinner-border', {
timeoutMessage: 'timeout while waiting for loading spinner to appear', 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 // resolve promise for test to finish
// need to resolve with a valid response cause otherwise Ember Data would throw // 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 { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support'; 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 PollEvaluationPage from 'croodle/tests/pages/poll/evaluation';
import { assign } from '@ember/polyfills'; import { assign } from '@ember/polyfills';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
module('Acceptance | view evaluation', function(hooks) { module('Acceptance | view evaluation', function (hooks) {
hooks.beforeEach(function() { hooks.beforeEach(function () {
window.localStorage.setItem('locale', 'en'); window.localStorage.setItem('locale', 'en');
}); });
@ -16,23 +17,27 @@ module('Acceptance | view evaluation', function(hooks) {
setupIntl(hooks); setupIntl(hooks);
setupMirage(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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey encryptionKey,
}); });
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`); await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.participation'); assert.equal(currentRouteName(), 'poll.participation');
await switchTab('evaluation'); 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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let user1 = this.server.create('user', { let user1 = this.server.create('user', {
id: "1-1", id: '1-1',
creationDate: DateTime.local().minus({ months: 8, weeks: 3 }).toISO(), creationDate: DateTime.local().minus({ months: 8, weeks: 3 }).toISO(),
encryptionKey, encryptionKey,
name: 'Maximilian', name: 'Maximilian',
@ -41,18 +46,18 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
} },
] ],
}); });
let user2 = this.server.create('user', { let user2 = this.server.create('user', {
id: "1-2", id: '1-2',
creationDate: DateTime.local().minus({ months: 3, weeks: 2 }).toISO(), creationDate: DateTime.local().minus({ months: 3, weeks: 2 }).toISO(),
encryptionKey, encryptionKey,
name: 'Peter', name: 'Peter',
@ -61,15 +66,15 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'no', type: 'no',
labelTranslation: 'answerTypes.no.label', labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down', icon: 'glyphicon glyphicon-thumbs-down',
label: 'No' label: 'No',
}, },
{ {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
} },
] ],
}); });
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
id: '1', id: '1',
@ -78,26 +83,27 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'no', type: 'no',
labelTranslation: 'answerTypes.no.label', labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down', icon: 'glyphicon glyphicon-thumbs-down',
label: 'No' label: 'No',
} },
], ],
encryptionKey, encryptionKey,
options: [ options: [{ title: '2015-12-12' }, { title: '2016-01-01' }],
{ title: '2015-12-12' }, users: [user1, user2],
{ title: '2016-01-01' }
],
users: [user1, user2]
}); });
await visit(`/poll/${poll.id}/evaluation?encryptionKey=${encryptionKey}`); await visit(`/poll/${poll.id}/evaluation?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.evaluation'); 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( assert.equal(
find('.participants').textContent.trim(), find('.participants').textContent.trim(),
t('poll.evaluation.participants', { count: 2 }).toString(), t('poll.evaluation.participants', { count: 2 }).toString(),
@ -111,7 +117,7 @@ module('Acceptance | view evaluation', function(hooks) {
assert.equal( assert.equal(
find('.last-participation').textContent.trim(), find('.last-participation').textContent.trim(),
t('poll.evaluation.lastParticipation', { t('poll.evaluation.lastParticipation', {
ago: '3 months ago' ago: '3 months ago',
}).toString(), }).toString(),
'shows last participation date' 'shows last participation date'
); );
@ -124,37 +130,56 @@ module('Acceptance | view evaluation', function(hooks) {
assert assert
.dom('[data-test-participant="1-1"] [data-test-value-for="name"]') .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 assert
.dom('[data-test-participant="1-2"] [data-test-value-for="name"]') .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 assert
.dom('[data-test-participant="1-1"] [data-test-value-for="2015-12-12"]') .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 assert
.dom('[data-test-participant="1-1"] [data-test-value-for="2016-01-01"]') .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 assert
.dom('[data-test-participant="1-2"] [data-test-value-for="2015-12-12"]') .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 assert
.dom('[data-test-participant="1-2"] [data-test-value-for="2016-01-01"]') .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( 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'], ['Maximilian', 'Peter'],
'Participants are ordered as correctly in participants table' '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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let user1 = this.server.create('user', { let user1 = this.server.create('user', {
id: "1-1", id: '1-1',
creationDate: DateTime.local().minus({ months: 8, weeks: 3 }).toISO(), creationDate: DateTime.local().minus({ months: 8, weeks: 3 }).toISO(),
encryptionKey, encryptionKey,
name: 'Maximilian', name: 'Maximilian',
@ -163,24 +188,24 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'no', type: 'no',
labelTranslation: 'answerTypes.no.label', labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down', icon: 'glyphicon glyphicon-thumbs-down',
label: 'No' label: 'No',
}, },
] ],
}); });
let user2 = this.server.create('user', { let user2 = this.server.create('user', {
id: "1-2", id: '1-2',
creationDate: DateTime.local().minus({ months: 3, weeks: 2 }).toISO(), creationDate: DateTime.local().minus({ months: 3, weeks: 2 }).toISO(),
encryptionKey, encryptionKey,
name: 'Peter', name: 'Peter',
@ -189,21 +214,21 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'no', type: 'no',
labelTranslation: 'answerTypes.no.label', labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down', icon: 'glyphicon glyphicon-thumbs-down',
label: 'No' label: 'No',
}, },
{ {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
} },
] ],
}); });
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
id: '1', id: '1',
@ -212,27 +237,31 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'no', type: 'no',
labelTranslation: 'answerTypes.no.label', labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down', icon: 'glyphicon glyphicon-thumbs-down',
label: 'No' label: 'No',
} },
], ],
encryptionKey, encryptionKey,
options: [ options: [
{ title: DateTime.fromISO('2015-12-12T06:06').toISO() }, { title: DateTime.fromISO('2015-12-12T06:06').toISO() },
{ title: DateTime.fromISO('2015-12-12T12:12').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}`); await visit(`/poll/${poll.id}/evaluation?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.evaluation'); 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( assert.equal(
find('.participants').textContent.trim(), find('.participants').textContent.trim(),
t('poll.evaluation.participants', { count: 2 }).toString(), t('poll.evaluation.participants', { count: 2 }).toString(),
@ -246,58 +275,111 @@ module('Acceptance | view evaluation', function(hooks) {
assert.equal( assert.equal(
find('.last-participation').textContent.trim(), find('.last-participation').textContent.trim(),
t('poll.evaluation.lastParticipation', { t('poll.evaluation.lastParticipation', {
ago: '3 months ago' ago: '3 months ago',
}).toString(), }).toString(),
'shows last participation date' 'shows last participation date'
); );
assert.deepEqual( 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'], ['', 'Saturday, December 12, 2015', 'Friday, January 1, 2016'],
'lists days as first row in table header of parcipants table' 'lists days as first row in table header of parcipants table'
); );
assert.deepEqual( 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'], ['', '6:06 AM', '12:12 PM', '6:18 PM'],
'lists times as second row in table header of parcipants table' 'lists times as second row in table header of parcipants table'
); );
assert assert
.dom('[data-test-participant="1-1"] [data-test-value-for="name"]') .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 assert
.dom('[data-test-participant="1-2"] [data-test-value-for="name"]') .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 assert
.dom(`[data-test-participant="1-1"] [data-test-value-for="${DateTime.fromISO('2015-12-12T06:06').toISO()}"]`) .dom(
.hasText('Yes', 'shows expected selection for first option of first participant'); `[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 assert
.dom(`[data-test-participant="1-1"] [data-test-value-for="${DateTime.fromISO('2015-12-12T12:12').toISO()}"]`) .dom(
.hasText('Yes', 'shows expected selection for second option of first participant'); `[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 assert
.dom(`[data-test-participant="1-1"] [data-test-value-for="${DateTime.fromISO('2016-01-01T18:18').toISO()}"]`) .dom(
.hasText('No', 'shows expected selection for third option of first participant'); `[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 assert
.dom(`[data-test-participant="1-2"] [data-test-value-for="${DateTime.fromISO('2015-12-12T06:06').toISO()}"]`) .dom(
.hasText('No', 'shows expected selection for first option of second participant'); `[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 assert
.dom(`[data-test-participant="1-2"] [data-test-value-for="${DateTime.fromISO('2015-12-12T12:12').toISO()}"]`) .dom(
.hasText('Yes', 'shows expected selection for second option of second participant'); `[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 assert
.dom(`[data-test-participant="1-2"] [data-test-value-for="${DateTime.fromISO('2016-01-01T18:18').toISO()}"]`) .dom(
.hasText('Yes', 'shows expected selection for third option of second participant'); `[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( 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'], ['Maximilian', 'Peter'],
'Participants are ordered as correctly in participants table' '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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let usersData = [ let usersData = [
{ {
@ -309,15 +391,15 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
} },
] ],
}, },
{ {
creationDate: DateTime.local().minus({ days: 3 }).toISO(), creationDate: DateTime.local().minus({ days: 3 }).toISO(),
@ -328,15 +410,15 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'no', type: 'no',
labelTranslation: 'answerTypes.no.label', labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down', icon: 'glyphicon glyphicon-thumbs-down',
label: 'No' label: 'No',
}, },
{ {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
} },
] ],
}, },
]; ];
let pollData = { let pollData = {
@ -345,27 +427,33 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'no', type: 'no',
labelTranslation: 'answerTypes.no.label', labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down', icon: 'glyphicon glyphicon-thumbs-down',
label: 'No' label: 'No',
} },
], ],
encryptionKey, encryptionKey,
options: [ options: [{ title: 'first option' }, { title: 'second option' }],
{ title: 'first option' },
{ title: 'second option' }
],
pollType: 'MakeAPoll', 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}`); await visit(`/poll/${poll.id}/evaluation?encryptionKey=${encryptionKey}`);
assert.equal(currentRouteName(), 'poll.evaluation'); 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( assert.equal(
find('.participants').textContent.trim(), find('.participants').textContent.trim(),
t('poll.evaluation.participants', { count: 2 }).toString(), t('poll.evaluation.participants', { count: 2 }).toString(),
@ -383,11 +471,15 @@ module('Acceptance | view evaluation', function(hooks) {
'dates are used as table headers' 'dates are used as table headers'
); );
assert.deepEqual( 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' 'users are listed in participants table with their names'
); );
usersData.forEach((user) => { usersData.forEach((user) => {
let participant = PollEvaluationPage.participants.filterBy('name', user.name)[0]; let participant = PollEvaluationPage.participants.filterBy(
'name',
user.name
)[0];
assert.deepEqual( assert.deepEqual(
participant.selections.map((_) => _.answer), participant.selections.map((_) => _.answer),
user.selections.map((_) => t(_.labelTranslation).toString()), user.selections.map((_) => t(_.labelTranslation).toString()),
@ -398,13 +490,13 @@ module('Acceptance | view evaluation', function(hooks) {
assert.equal( assert.equal(
find('.last-participation').textContent.trim(), find('.last-participation').textContent.trim(),
t('poll.evaluation.lastParticipation', { t('poll.evaluation.lastParticipation', {
ago: '3 days ago' ago: '3 days ago',
}).toString(), }).toString(),
'last participation is evaluated correctly' '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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
answers: [ answers: [
@ -412,20 +504,17 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'no', type: 'no',
labelTranslation: 'answerTypes.no.label', labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down', icon: 'glyphicon glyphicon-thumbs-down',
label: 'No' label: 'No',
} },
], ],
encryptionKey, encryptionKey,
options: [ options: [{ title: '2015-12-12' }, { title: '2016-01-01' }],
{ title: '2015-12-12' },
{ title: '2016-01-01' }
],
users: [ users: [
this.server.create('user', { this.server.create('user', {
creationDate: '2015-01-01T00:00:00.000Z', creationDate: '2015-01-01T00:00:00.000Z',
@ -436,15 +525,15 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
} },
] ],
}), }),
this.server.create('user', { this.server.create('user', {
creationDate: '2015-08-01T00:00:00.000Z', creationDate: '2015-08-01T00:00:00.000Z',
@ -455,17 +544,17 @@ module('Acceptance | view evaluation', function(hooks) {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
id: 'no', id: 'no',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
} },
] ],
}) }),
] ],
}); });
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`); 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 { module, test } from 'qunit';
import { setupApplicationTest } from 'ember-qunit'; import { setupApplicationTest } from 'ember-qunit';
import { setupMirage } from 'ember-cli-mirage/test-support'; 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 { DateTime } from 'luxon';
import { triggerCopySuccess } from 'ember-cli-clipboard/test-support'; import { triggerCopySuccess } from 'ember-cli-clipboard/test-support';
module('Acceptance | view poll', function(hooks) { module('Acceptance | view poll', function (hooks) {
hooks.beforeEach(function() { hooks.beforeEach(function () {
window.localStorage.setItem('locale', 'en'); window.localStorage.setItem('locale', 'en');
}); });
setupApplicationTest(hooks); setupApplicationTest(hooks);
setupMirage(hooks); setupMirage(hooks);
test('poll url', async function(assert) { test('poll url', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz012345789'; let encryptionKey = 'abcdefghijklmnopqrstuvwxyz012345789';
let poll = this.server.create('poll', { encryptionKey }); let poll = this.server.create('poll', { encryptionKey });
let pollUrl = `/poll/${poll.id}?encryptionKey=${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 * Can't test if flash message is shown due to
* https://github.com/poteto/ember-cli-flash/issues/202 * 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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey, encryptionKey,
@ -46,34 +51,26 @@ module('Acceptance | view poll', function(hooks) {
}); });
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`); await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.ok( assert.ok(pageParticipation.showsExpirationWarning);
pageParticipation.showsExpirationWarning
);
}); });
test('view a poll with dates', async function(assert) { test('view a poll with dates', async function (assert) {
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789'; let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey, encryptionKey,
options: [ options: [{ title: '2015-12-12' }, { title: '2016-01-01' }],
{ title: '2015-12-12' },
{ title: '2016-01-01' }
]
}); });
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`); await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.deepEqual( assert.deepEqual(pageParticipation.options().labels, [
pageParticipation.options().labels, 'Saturday, December 12, 2015',
[ 'Friday, January 1, 2016',
'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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let timezone = Intl.DateTimeFormat().resolvedOptions().timeZone ; let timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey, encryptionKey,
expirationDate: DateTime.local().plus({ years: 1 }).toISO(), 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 // time zone as UTC rather than local time
{ title: DateTime.fromISO('2015-12-12T11:11:00').toISO() }, { title: DateTime.fromISO('2015-12-12T11:11:00').toISO() },
{ title: DateTime.fromISO('2015-12-12T13:13: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}`); await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.deepEqual( assert.deepEqual(pageParticipation.options().labels, [
pageParticipation.options().labels, // full date
[ 'Saturday, December 12, 2015 at 11:11 AM',
// full date // only time cause day is repeated
'Saturday, December 12, 2015 at 11:11 AM', '1:13 PM',
// only time cause day is repeated // full date cause day changed
'1:13 PM', 'Friday, January 1, 2016 at 11:11 AM',
// full date cause day changed ]);
'Friday, January 1, 2016 at 11:11 AM',
]
);
assert.notOk( assert.notOk(
pageParticipation.showsExpirationWarning, pageParticipation.showsExpirationWarning,
'does not show an expiration warning if poll will not expire in next weeks' '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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let timezoneUser = Intl.DateTimeFormat().resolvedOptions().timeZone ; let timezoneUser = Intl.DateTimeFormat().resolvedOptions().timeZone;
let timezonePoll = timezoneUser !== 'America/Caracas' ? 'America/Caracas' : 'Europe/Moscow'; let timezonePoll =
timezoneUser !== 'America/Caracas' ? 'America/Caracas' : 'Europe/Moscow';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey, encryptionKey,
isDateTime: true, isDateTime: true,
options: [ options: [
{ title: '2015-12-12T11:11:00.000Z' }, { title: '2015-12-12T11:11:00.000Z' },
{ title: '2016-01-01T11:11:00.000Z' } { title: '2016-01-01T11:11:00.000Z' },
], ],
timezone: timezonePoll, timezone: timezonePoll,
users: [ users: [
@ -127,50 +122,61 @@ module('Acceptance | view poll', function(hooks) {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'no', type: 'no',
labelTranslation: 'answerTypes.no.label', labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down', icon: 'glyphicon glyphicon-thumbs-down',
label: 'No' label: 'No',
} },
] ],
}) }),
] ],
}); });
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`); 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'); .exists('user is asked which timezone should be used');
await click('[data-test-modal="choose-timezone"] [data-test-button="use-local-timezone"]'); await click(
assert.deepEqual( '[data-test-modal="choose-timezone"] [data-test-button="use-local-timezone"]'
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'); 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'); await switchTab('evaluation');
assert.deepEqual( assert.deepEqual(pageEvaluation.preferedOptions, [
pageEvaluation.preferedOptions, Intl.DateTimeFormat('en-US', {
[Intl.DateTimeFormat('en-US', { dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-12T11:11:00.000Z'))] 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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let timezoneUser = Intl.DateTimeFormat().resolvedOptions().timeZone ; let timezoneUser = Intl.DateTimeFormat().resolvedOptions().timeZone;
let timezonePoll = timezoneUser !== 'America/Caracas' ? 'America/Caracas' : 'Europe/Moscow'; let timezonePoll =
timezoneUser !== 'America/Caracas' ? 'America/Caracas' : 'Europe/Moscow';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
encryptionKey, encryptionKey,
isDateTime: true, isDateTime: true,
options: [ options: [
{ title: '2015-12-12T11:11:00.000Z' }, { title: '2015-12-12T11:11:00.000Z' },
{ title: '2016-01-01T11:11:00.000Z' } { title: '2016-01-01T11:11:00.000Z' },
], ],
timezone: timezonePoll, timezone: timezonePoll,
users: [ users: [
@ -181,61 +187,94 @@ module('Acceptance | view poll', function(hooks) {
type: 'yes', type: 'yes',
labelTranslation: 'answerTypes.yes.label', labelTranslation: 'answerTypes.yes.label',
icon: 'glyphicon glyphicon-thumbs-up', icon: 'glyphicon glyphicon-thumbs-up',
label: 'Yes' label: 'Yes',
}, },
{ {
type: 'no', type: 'no',
labelTranslation: 'answerTypes.no.label', labelTranslation: 'answerTypes.no.label',
icon: 'glyphicon glyphicon-thumbs-down', icon: 'glyphicon glyphicon-thumbs-down',
label: 'No' label: 'No',
} },
] ],
}) }),
] ],
}); });
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`); 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'); .exists('user is asked which timezone should be used');
await click('[data-test-modal="choose-timezone"] [data-test-button="use-poll-timezone"]'); await click(
assert.deepEqual( '[data-test-modal="choose-timezone"] [data-test-button="use-poll-timezone"]'
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'); 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'); await switchTab('evaluation');
assert.deepEqual( assert.deepEqual(pageEvaluation.preferedOptions, [
pageEvaluation.preferedOptions, Intl.DateTimeFormat('en-US', {
[Intl.DateTimeFormat('en-US', { timeZone: timezonePoll, dateStyle: "full", timeStyle: "short" }).format(new Date('2015-12-12T11:11:00.000Z')),] 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 pollId = 'not-existing';
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789'; let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
await visit(`/poll/${pollId}?encryptionKey=${encryptionKey}`); await visit(`/poll/${pollId}?encryptionKey=${encryptionKey}`);
assert.equal(currentURL(), `/poll/${pollId}?encryptionKey=${encryptionKey}`, 'shows URL entered by user'); assert.equal(
assert.equal(currentRouteName(), 'poll_error', 'shows error substate of poll route'); currentURL(),
assert.dom('[data-test-error-type]').hasAttribute('data-test-error-type', 'not-found'); `/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 encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
let poll = this.server.create('poll', { encryptionKey: 'anotherkey' }); let poll = this.server.create('poll', { encryptionKey: 'anotherkey' });
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`); await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
assert.equal(currentURL(), `/poll/${poll.id}?encryptionKey=${encryptionKey}`, 'shows URL entered by user'); assert.equal(
assert.equal(currentRouteName(), 'poll_error', 'shows error substate of poll route'); currentURL(),
assert.dom('[data-test-error-type]').hasAttribute('data-test-error-type', 'decryption-failed'); `/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 pollId = 'not-existing';
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789'; let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
@ -243,8 +282,18 @@ module('Acceptance | view poll', function(hooks) {
this.server.get('polls/:id', () => {}, 500); this.server.get('polls/:id', () => {}, 500);
await visit(`/poll/${pollId}?encryptionKey=${encryptionKey}`); await visit(`/poll/${pollId}?encryptionKey=${encryptionKey}`);
assert.equal(currentURL(), `/poll/${pollId}?encryptionKey=${encryptionKey}`, 'shows URL entered by user'); assert.equal(
assert.equal(currentRouteName(), 'poll_error', 'shows error substate of poll route'); currentURL(),
assert.dom('[data-test-error-type]').hasAttribute('data-test-error-type', 'unexpected'); `/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, result,
expected: expectedErrorMessage, expected: expectedErrorMessage,
actual: errorText, actual: errorText,
message: `Expected to see error '${expectedErrorMessage}'` message: `Expected to see error '${expectedErrorMessage}'`,
}); });
} else { } else {
this.pushResult({ this.pushResult({
result: false, result: false,
expected: '', expected: '',
actual: errorText, 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)) { if (isEmpty(message)) {
message = 'poll has expected count of users'; message = 'poll has expected count of users';
} }
assert.equal( assert.equal(findAll('.user').length, count, message);
findAll('.user').length,
count,
message
);
} }
export default pollHasUser; export default pollHasUser;

View file

@ -1,23 +1,31 @@
import { isEmpty } from '@ember/utils'; 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)) { if (!isEmpty(name)) {
await fillIn('.participation .name input', 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()) { for (let [index, selection] of selections.entries()) {
if (!isEmpty(selection)) { if (!isEmpty(selection)) {
if (isFreeText) { 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 { } 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 click('.participation button[type="submit"]');
await settled();
} }

View file

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

View file

@ -21,7 +21,14 @@
{{content-for "body"}} {{content-for "body"}}
{{content-for "test-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/vendor.js"></script>
<script src="{{rootURL}}assets/test-support.js"></script> <script src="{{rootURL}}assets/test-support.js"></script>
<script src="{{rootURL}}assets/croodle.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 { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | back-button', function(hooks) { module('Integration | Component | back-button', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
test('it renders a button', async function(assert) { test('it renders a button', async function (assert) {
await render(hbs`<BackButton />`); await render(hbs`<BackButton />`);
assert.dom('button').exists(); assert.dom('button').exists();

View file

@ -1,14 +1,14 @@
import { run } from '@ember/runloop'; import { run } from '@ember/runloop';
import { module, test } from 'qunit'; import { module, test } from 'qunit';
import { setupRenderingTest } from 'ember-qunit'; import { setupRenderingTest } from 'ember-qunit';
import { render, click, find, findAll } from "@ember/test-helpers"; import { render, click, find, findAll } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
module('Integration | Component | create options datetime', function(hooks) { module('Integration | Component | create options datetime', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
hooks.beforeEach(function() { hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store'); 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' * 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 // validation is based on validation of every option fragment
// which validates according to poll model it belongs to // which validates according to poll model it belongs to
// therefore each option needs to be pushed to poll model to have it as // therefore each option needs to be pushed to poll model to have it as
// it's owner // it's owner
run(() => { run(() => {
this.set('poll', this.store.createRecord('poll', { this.set(
pollType: 'FindADate', 'poll',
options: [ this.store.createRecord('poll', {
{ title: '2015-01-01' } pollType: 'FindADate',
] options: [{ title: '2015-01-01' }],
})); })
);
}); });
await render(hbs`{{create-options-datetime dates=poll.options}}`); 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 // validation is based on validation of every option fragment
// which validates according to poll model it belongs to // which validates according to poll model it belongs to
// therefore each option needs to be pushed to poll model to have it as // therefore each option needs to be pushed to poll model to have it as
// it's owner // it's owner
run(() => { run(() => {
this.set('poll', this.store.createRecord('poll', { this.set(
pollType: 'FindADate', 'poll',
options: [ this.store.createRecord('poll', {
{ title: '2015-01-01T11:11:00.000Z' } pollType: 'FindADate',
] options: [{ title: '2015-01-01T11:11:00.000Z' }],
})); })
);
}); });
await render(hbs`{{create-options-datetime dates=poll.options}}`); 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 // validation is based on validation of every option fragment
// which validates according to poll model it belongs to // which validates according to poll model it belongs to
// therefore each option needs to be pushed to poll model to have it as // therefore each option needs to be pushed to poll model to have it as
// it's owner // it's owner
run(() => { run(() => {
this.set('poll', this.store.createRecord('poll', { this.set(
pollType: 'FindADate', 'poll',
options: [ this.store.createRecord('poll', {
{ title: DateTime.fromISO('2015-01-01T10:11').toISO() }, pollType: 'FindADate',
{ title: DateTime.fromISO('2015-01-01T22:22').toISO() }, options: [
{ title: '2015-02-02' } { 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}}`); 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' 'there are two not hidden labels for two different dates'
); );
assert.notOk( 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' 'the first label is shown'
); );
assert.ok( 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' 'the repeated label on second form-group is hidden by sr-only class'
); );
assert.notOk( 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' '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 // validation is based on validation of every option fragment
// which validates according to poll model it belongs to // which validates according to poll model it belongs to
// therefore each option needs to be pushed to poll model to have it as // therefore each option needs to be pushed to poll model to have it as
// it's owner // it's owner
run(() => { run(() => {
this.set('poll', this.store.createRecord('poll', { this.set(
options: [ 'poll',
{ title: '2015-01-01' }, this.store.createRecord('poll', {
{ title: '2015-02-02' } options: [{ title: '2015-01-01' }, { title: '2015-02-02' }],
] })
})); );
}); });
await render(hbs`{{create-options-datetime dates=poll.options}}`); 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' 'new input has correct label'
); );
assert.ok( assert.ok(
findAll('.days .form-group')[1].querySelector('label').classList.contains('sr-only'), findAll('.days .form-group')[1]
'label ofnew input is hidden cause it\'s repeated' .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 // validation is based on validation of every option fragment
// which validates according to poll model it belongs to // which validates according to poll model it belongs to
// therefore each option needs to be pushed to poll model to have it as // therefore each option needs to be pushed to poll model to have it as
// it's owner // it's owner
run(() => { run(() => {
this.set('poll', this.store.createRecord('poll', { this.set(
pollType: 'FindADate', 'poll',
options: [ this.store.createRecord('poll', {
{ title: DateTime.fromISO('2015-01-01T11:11').toISO() }, pollType: 'FindADate',
{ title: DateTime.fromISO('2015-01-01T22:22').toISO() } 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}} />`); 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 { render, findAll, blur, fillIn, focus } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | create options', function(hooks) { module('Integration | Component | create options', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
hooks.beforeEach(function() { hooks.beforeEach(function () {
this.store = this.owner.lookup('service:store'); 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); assert.expect(5);
// validation is based on validation of every option fragment // 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 fillIn('.form-group:nth-child(1) input', 'foo');
await blur('.form-group:nth-child(1) input'); await blur('.form-group:nth-child(1) input');
await fillIn('.form-group:nth-child(2) input', 'foo'); await fillIn('.form-group:nth-child(2) input', 'foo');
await blur('.form-group:nth-child(2) input'); 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'); .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'); .exists('validation error is shown');
await fillIn(findAll('input')[0], 'bar'); await fillIn(findAll('input')[0], 'bar');
await blur(findAll('input')[0]); await blur(findAll('input')[0]);
assert.dom('.form-group .invalid-feedback') assert
.doesNotExist('there is no validation error anymore after a unique value is entered'); .dom('.form-group .invalid-feedback')
assert.dom('.form-group .is-invalid') .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'); .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 // validation is based on validation of every option fragment
// which validates according to poll model it belongs to // which validates according to poll model it belongs to
// therefore each option needs to be pushed to poll model to have it as // 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( assert.equal(findAll('.form-group.has-error').length, 0);
findAll('.form-group.has-error').length, 0
);
await focus(findAll('input')[0]); await focus(findAll('input')[0]);
await blur(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 { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | inline-datepicker', function(hooks) { module('Integration | Component | inline-datepicker', function (hooks) {
setupRenderingTest(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', () => {}); this.set('noop', () => {});
await render(hbs`{{inline-datepicker onCenterChange=noop onSelect=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 { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | next-button', function(hooks) { module('Integration | Component | next-button', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
test('it renders a button', async function(assert) { test('it renders a button', async function (assert) {
await render(hbs`<NextButton />`); await render(hbs`<NextButton />`);
assert.dom('button').exists(); assert.dom('button').exists();
assert.dom('button').hasAttribute('type', 'submit'); 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}} />`); await render(hbs`<NextButton @isPending={{true}} />`);
assert.dom('button .spinner-border').exists(); assert.dom('button .spinner-border').exists();

View file

@ -3,17 +3,17 @@ import { setupRenderingTest } from 'ember-qunit';
import { render } from '@ember/test-helpers'; import { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
module('Integration | Component | save-button', function(hooks) { module('Integration | Component | save-button', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
test('it renders a button', async function(assert) { test('it renders a button', async function (assert) {
await render(hbs`<NextButton />`); await render(hbs`<NextButton />`);
assert.dom('button').exists(); assert.dom('button').exists();
assert.dom('button').hasAttribute('type', 'submit'); 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}} />`); await render(hbs`<NextButton @isPending={{true}} />`);
assert.dom('button .spinner-border').exists(); 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 { hbs } from 'ember-cli-htmlbars';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
module('Integration | Helper | format-date-relative', function(hooks) { module('Integration | Helper | format-date-relative', function (hooks) {
setupRenderingTest(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 })); this.set('date', DateTime.local().plus({ hours: 6 }));
await render(hbs`{{format-date-relative this.date}}`); 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'); 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 })); this.set('date', DateTime.local().minus({ hours: 6 }));
await render(hbs`{{format-date-relative this.date}}`); 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 { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile'; 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); setupRenderingTest(hooks);
// Replace this with your real tests. // Replace this with your real tests.
test('it renders', async function(assert) { test('it renders', async function (assert) {
this.set('inputValue', '1234'); this.set('inputValue', '1234');
await render(hbs`{{mark-as-safe-html "<p>foo</p>"}}`); 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 { render } from '@ember/test-helpers';
import hbs from 'htmlbars-inline-precompile'; import hbs from 'htmlbars-inline-precompile';
module('Integration | Helper | scroll-first-invalid-element-into-view-port', function(hooks) { module(
setupRenderingTest(hooks); 'Integration | Helper | scroll-first-invalid-element-into-view-port',
function (hooks) {
setupRenderingTest(hooks);
// Replace this with your real tests. // Replace this with your real tests.
test('it renders', async function(assert) { test('it renders', async function (assert) {
this.set('inputValue', '1234'); 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 { module, test } from 'qunit';
import { startMirage } from 'croodle/initializers/ember-cli-mirage'; import { startMirage } from 'croodle/initializers/ember-cli-mirage';
import sjcl from 'sjcl'; import sjcl from 'sjcl';
module('Integration | Mirage api mocking', function(hooks) { module('Integration | Mirage api mocking', function (hooks) {
hooks.beforeEach(function() { hooks.beforeEach(function () {
this.server = startMirage(); this.server = startMirage();
}); });
hooks.afterEach(function() { hooks.afterEach(function () {
this.server.shutdown(); this.server.shutdown();
}); });
test('poll factory | encrypts properties', function(assert) { test('poll factory | encrypts properties', function (assert) {
let encryptionKey = 'abc'; let encryptionKey = 'abc';
let poll = this.server.create('poll', { let poll = this.server.create('poll', {
description: 'bar', description: 'bar',
encryptionKey, encryptionKey,
title: 'foo' title: 'foo',
}); });
assert.equal(JSON.parse(sjcl.decrypt(encryptionKey, get(poll, 'title'))), 'foo'); assert.equal(JSON.parse(sjcl.decrypt(encryptionKey, poll.title)), 'foo');
assert.equal(JSON.parse(sjcl.decrypt(encryptionKey, get(poll, 'description'))), 'bar'); 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 encryptionKey = 'abc';
let user = this.server.create('user', { let user = this.server.create('user', {
encryptionKey, 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 { render } from '@ember/test-helpers';
import { hbs } from 'ember-cli-htmlbars'; import { hbs } from 'ember-cli-htmlbars';
module('Integration | Modifier | autofocus', function(hooks) { module('Integration | Modifier | autofocus', function (hooks) {
setupRenderingTest(hooks); setupRenderingTest(hooks);
test('it focuses the element', async function(assert) { test('it focuses the element', async function (assert) {
await render(hbs`<input {{autofocus}} />`); await render(hbs`<input {{autofocus}} />`);
assert.dom('input').isFocused(); 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); this.set('enabled', true);
await render(hbs`<input {{autofocus enabled=this.enabled}} />`); await render(hbs`<input {{autofocus enabled=this.enabled}} />`);
assert.dom('input').isFocused(); 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); this.set('enabled', false);
await render(hbs`<input {{autofocus enabled=this.enabled}} />`); 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 { setupTest } from 'ember-qunit';
import localesMeta from 'croodle/locales/meta'; import localesMeta from 'croodle/locales/meta';
module('Integration | translations', function(hooks) { module('Integration | translations', function (hooks) {
setupTest(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'); let intl = this.owner.lookup('service:intl');
intl.locales.forEach((locale) => { 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 { defaultsForCreate } from 'croodle/tests/pages/defaults';
import { hasFocus } from 'croodle/tests/pages/helpers'; import { hasFocus } from 'croodle/tests/pages/helpers';
const { const { assign } = Object;
assign
} = Object;
const { const { fillable, visitable } = PageObject;
fillable,
visitable
} = PageObject;
export default PageObject.create(assign({}, defaultsForCreate, { export default PageObject.create(
pollType: fillable('.poll-type select'), assign({}, defaultsForCreate, {
pollTypeHasFocus: hasFocus('.poll-type select'), pollType: fillable('.poll-type select'),
visit: visitable('/create') 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 { defaultsForCreate } from 'croodle/tests/pages/defaults';
import { hasFocus } from 'croodle/tests/pages/helpers'; import { hasFocus } from 'croodle/tests/pages/helpers';
const { const { assign } = Object;
assign
} = Object;
let { let { fillable } = PageObject;
fillable
} = PageObject;
export default PageObject.create(assign({}, defaultsForCreate, { export default PageObject.create(
description: fillable('.description textarea'), assign({}, defaultsForCreate, {
title: fillable('.title input'), description: fillable('.description textarea'),
titleHasFocus: hasFocus('.title input') 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 { defaultsForCreate } from 'croodle/tests/pages/defaults';
import { hasFocus } from 'croodle/tests/pages/helpers'; import { hasFocus } from 'croodle/tests/pages/helpers';
const { const { assign } = Object;
assign
} = Object;
let { let { clickable, collection, fillable, hasClass, text } = PageObject;
clickable,
collection,
fillable,
hasClass,
text
} = PageObject;
export default PageObject.create(assign({}, defaultsForCreate, { export default PageObject.create(
days: collection({ assign({}, defaultsForCreate, {
itemScope: '.form-group', days: collection({
labels: text('label:not(.sr-only)', { multiple: true }) itemScope: '.form-group',
}), labels: text('label:not(.sr-only)', { multiple: true }),
times: collection({ }),
itemScope: '.form-group', times: collection({
item: { itemScope: '.form-group',
add: clickable('button.add'), item: {
delete: clickable('button.delete'), add: clickable('button.add'),
label: text('label'), delete: clickable('button.delete'),
labelIsHidden: hasClass('label', 'sr-only'), label: text('label'),
time: fillable('input') labelIsHidden: hasClass('label', 'sr-only'),
} time: fillable('input'),
}), },
firstTime: { }),
scope: '.form-group:first', firstTime: {
scope: '.form-group:first',
inputHasFocus: hasFocus('input') inputHasFocus: hasFocus('input'),
} },
})); })
);

View file

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

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