upgrade to Ember 3.28 blueprints (#647)
This commit is contained in:
parent
c48d7d1441
commit
02058ab756
116 changed files with 2128 additions and 1609 deletions
|
@ -14,6 +14,8 @@
|
||||||
/api
|
/api
|
||||||
/coverage/
|
/coverage/
|
||||||
!.*
|
!.*
|
||||||
|
.*/
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
# ember-try
|
# ember-try
|
||||||
/.node_modules.ember-try/
|
/.node_modules.ember-try/
|
||||||
|
|
52
.eslintrc.js
52
.eslintrc.js
|
@ -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
1
.gitignore
vendored
|
@ -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
21
.prettierignore
Normal 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
5
.prettierrc.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
'use strict';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
singleQuote: true,
|
||||||
|
};
|
|
@ -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'],
|
||||||
},
|
},
|
||||||
|
|
|
@ -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, '');
|
||||||
}
|
}
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
{{yield}}
|
|
|
@ -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;
|
||||||
|
|
|
@ -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');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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}}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 });
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 })
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,2 +1 @@
|
||||||
export default {
|
export default {};
|
||||||
};
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
});
|
});
|
||||||
|
|
|
@ -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) => {
|
||||||
|
|
|
@ -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',
|
||||||
};
|
};
|
||||||
|
|
|
@ -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()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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}}>
|
||||||
|
|
|
@ -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"}}
|
{{t 'poll.link.copy-label'}}
|
||||||
<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>
|
|
@ -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':
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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'"],
|
||||||
},
|
},
|
||||||
|
|
|
@ -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' },
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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;
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
|
@ -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: {
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
module.exports = {
|
module.exports = {
|
||||||
env: {
|
env: {
|
||||||
node: true,
|
node: true,
|
||||||
browser: false
|
browser: false,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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`),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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');
|
||||||
|
|
|
@ -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);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -51,6 +51,6 @@ export default class {
|
||||||
* @public
|
* @public
|
||||||
*/
|
*/
|
||||||
reset() {
|
reset() {
|
||||||
this._ids = {};
|
this._ids = {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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'),
|
||||||
});
|
});
|
||||||
|
|
|
@ -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'),
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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',
|
||||||
});
|
});
|
||||||
|
|
|
@ -3,5 +3,5 @@ import ApplicationSerializer from './application';
|
||||||
|
|
||||||
export default ApplicationSerializer.extend({
|
export default ApplicationSerializer.extend({
|
||||||
embed: true,
|
embed: true,
|
||||||
include: ['users']
|
include: ['users'],
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
51
package.json
51
package.json
|
@ -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",
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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',
|
||||||
},
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
38
testem.js
38
testem.js
|
@ -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',
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
|
@ -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
|
@ -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'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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'
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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}`);
|
||||||
|
|
|
@ -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');
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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.`,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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();
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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`);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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}} />`);
|
||||||
|
|
||||||
|
|
|
@ -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]);
|
||||||
|
|
|
@ -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}}`);
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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}}`);
|
||||||
|
|
|
@ -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>"}}`);
|
||||||
|
|
|
@ -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');
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
);
|
||||||
|
|
|
@ -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');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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}} />`);
|
||||||
|
|
||||||
|
|
|
@ -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}`
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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'),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
|
@ -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'),
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
|
@ -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'),
|
||||||
}
|
},
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
|
|
|
@ -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'),
|
||||||
}
|
},
|
||||||
}));
|
})
|
||||||
|
);
|
||||||
|
|
|
@ -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
Loading…
Reference in a new issue