refactor participants table (#164)
- Drops floatthead and additional scrollbar - Makes header and first column sticky - Refactors code for readability Sticky header is only working in Firefox. Chrome and Edge does not support `position: sticky` for `<thead>`. Haven't tested Safari.
This commit is contained in:
parent
6c122148c3
commit
bb160cc503
29 changed files with 697 additions and 723 deletions
|
@ -1,6 +1,7 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import Component from '@ember/component';
|
||||
import { get, computed } from '@ember/object';
|
||||
import { readOnly } from '@ember/object/computed';
|
||||
import { isArray } from '@ember/array';
|
||||
import { isPresent } from '@ember/utils';
|
||||
import moment from 'moment';
|
||||
|
@ -25,60 +26,7 @@ const addArrays = function() {
|
|||
|
||||
export default Component.extend({
|
||||
i18n: service(),
|
||||
type: 'bar',
|
||||
data: computed('users.[]', 'options.{[],each.title}', 'currentLocale', function() {
|
||||
let labels = this.options.map((option) => {
|
||||
let value = get(option, 'title');
|
||||
if (!this.isFindADate) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let hasTime = value.length > 10; // 'YYYY-MM-DD'.length === 10
|
||||
let momentFormat = hasTime ? 'LLLL' : this.momentLongDayFormat;
|
||||
let timezone = this.timezone;
|
||||
let date = hasTime && isPresent(timezone) ? moment.tz(value, timezone) : moment(value);
|
||||
date.locale(this.currentLocale);
|
||||
return date.format(momentFormat);
|
||||
});
|
||||
|
||||
let datasets = [];
|
||||
let participants = this.get('users.length');
|
||||
|
||||
let yes = this.users.map((user) => {
|
||||
return user.get('selections').map((selection) => {
|
||||
return selection.get('type') === 'yes' ? 1 : 0;
|
||||
});
|
||||
});
|
||||
datasets.push({
|
||||
label: this.i18n.t('answerTypes.yes.label').toString(),
|
||||
backgroundColor: 'rgba(151,187,205,0.5)',
|
||||
borderColor: 'rgba(151,187,205,0.8)',
|
||||
hoverBackgroundColor: 'rgba(151,187,205,0.75)',
|
||||
hoverBorderColor: 'rgba(151,187,205,1)',
|
||||
data: addArrays.apply(this, yes).map((value) => Math.round(value / participants * 100))
|
||||
});
|
||||
|
||||
if (this.answerType === 'YesNoMaybe') {
|
||||
let maybe = this.users.map((user) => {
|
||||
return user.get('selections').map((selection) => {
|
||||
return selection.get('type') === 'maybe' ? 1 : 0;
|
||||
});
|
||||
});
|
||||
datasets.push({
|
||||
label: this.i18n.t('answerTypes.maybe.label').toString(),
|
||||
backgroundColor: 'rgba(220,220,220,0.5)',
|
||||
borderColor: 'rgba(220,220,220,0.8)',
|
||||
hoverBackgroundColor: 'rgba(220,220,220,0.75)',
|
||||
hoverBorderColor: 'rgba(220,220,220,1)',
|
||||
data: addArrays.apply(this, maybe).map((value) => Math.round(value / participants * 100))
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
datasets,
|
||||
labels
|
||||
};
|
||||
}),
|
||||
chartOptions: computed(function () {
|
||||
return {
|
||||
legend: {
|
||||
|
@ -113,4 +61,60 @@ export default Component.extend({
|
|||
}
|
||||
}
|
||||
}),
|
||||
|
||||
data: computed('users.[]', 'options.{[],each.title}', 'currentLocale', function() {
|
||||
let labels = this.options.map((option) => {
|
||||
let value = get(option, 'title');
|
||||
if (!this.isFindADate) {
|
||||
return value;
|
||||
}
|
||||
|
||||
let hasTime = value.length > 10; // 'YYYY-MM-DD'.length === 10
|
||||
let momentFormat = hasTime ? 'LLLL' : this.momentLongDayFormat;
|
||||
let timezone = this.timezone;
|
||||
let date = hasTime && isPresent(timezone) ? moment.tz(value, timezone) : moment(value);
|
||||
date.locale(this.currentLocale);
|
||||
return date.format(momentFormat);
|
||||
});
|
||||
|
||||
let datasets = [];
|
||||
let participants = this.users.length;
|
||||
|
||||
let yes = this.users.map(({ selections }) => {
|
||||
return selections.map(({ type }) => type === 'yes' ? 1 : 0);
|
||||
});
|
||||
datasets.push({
|
||||
label: this.i18n.t('answerTypes.yes.label').toString(),
|
||||
backgroundColor: 'rgba(151,187,205,0.5)',
|
||||
borderColor: 'rgba(151,187,205,0.8)',
|
||||
hoverBackgroundColor: 'rgba(151,187,205,0.75)',
|
||||
hoverBorderColor: 'rgba(151,187,205,1)',
|
||||
data: addArrays.apply(this, yes).map((value) => Math.round(value / participants * 100))
|
||||
});
|
||||
|
||||
if (this.answerType === 'YesNoMaybe') {
|
||||
let maybe = this.users.map(({ selections }) => {
|
||||
return selections.map(({ type }) => type === 'maybe' ? 1 : 0);
|
||||
});
|
||||
datasets.push({
|
||||
label: this.i18n.t('answerTypes.maybe.label').toString(),
|
||||
backgroundColor: 'rgba(220,220,220,0.5)',
|
||||
borderColor: 'rgba(220,220,220,0.8)',
|
||||
hoverBackgroundColor: 'rgba(220,220,220,0.75)',
|
||||
hoverBorderColor: 'rgba(220,220,220,1)',
|
||||
data: addArrays.apply(this, maybe).map((value) => Math.round(value / participants * 100))
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
datasets,
|
||||
labels
|
||||
};
|
||||
}),
|
||||
|
||||
answerType: readOnly('poll.answerType'),
|
||||
currentLocale: readOnly('i18n.locale'),
|
||||
isFindADate: readOnly('poll.isFindADate'),
|
||||
options: readOnly('poll.options'),
|
||||
users: readOnly('poll.users'),
|
||||
});
|
||||
|
|
|
@ -1,173 +1,17 @@
|
|||
import { observer } from '@ember/object';
|
||||
import $ from 'jquery';
|
||||
import { scheduleOnce, next } from '@ember/runloop';
|
||||
import Component from '@ember/component';
|
||||
import moment from 'moment';
|
||||
import { groupBy } from 'ember-awesome-macros/array';
|
||||
import { readOnly } from '@ember/object/computed';
|
||||
import { raw } from 'ember-awesome-macros';
|
||||
import { groupBy, sort } from 'ember-awesome-macros/array';
|
||||
|
||||
export default Component.extend({
|
||||
didInsertElement() {
|
||||
this._super();
|
||||
scheduleOnce('afterRender', this, function() {
|
||||
/*
|
||||
* adding floatThead jQuery plugin to poll table
|
||||
* https://mkoryak.github.io/floatThead/
|
||||
*
|
||||
* top:
|
||||
* Offset from the top of the `window` where the floating header will
|
||||
* 'stick' when scrolling down
|
||||
* Since we are adding a browser horizontal scrollbar on top, scrollingTop
|
||||
* has to be set to height of horizontal scrollbar which depends on
|
||||
* used browser
|
||||
*/
|
||||
$('.user-selections-table').floatThead({
|
||||
position: 'absolute',
|
||||
top: this.getScrollbarHeight
|
||||
});
|
||||
hasTimes: readOnly('poll.hasTimes'),
|
||||
|
||||
/*
|
||||
* fix width calculation error caused by bootstrap glyphicon on webkit
|
||||
*/
|
||||
$('.glyphicon').css('width', '14px');
|
||||
isFindADate: readOnly('poll.isFindADate'),
|
||||
isFreeText: readOnly('poll.isFreeText'),
|
||||
|
||||
/*
|
||||
* scrollbar on top of table
|
||||
*/
|
||||
const topScrollbarInner = $('<div></div>')
|
||||
.css('width', $('.user-selections-table').width())
|
||||
.css('height', '1px');
|
||||
const topScrollbarOuter = $('<div></div>')
|
||||
.addClass('top-scrollbar')
|
||||
.css('width', '100%')
|
||||
.css('overflow-x', 'scroll')
|
||||
.css('overflow-y', 'hidden')
|
||||
.css('position', 'relative')
|
||||
.css('z-index', '1002');
|
||||
$('.table-scroll').before(
|
||||
topScrollbarOuter.append(topScrollbarInner)
|
||||
);
|
||||
options: readOnly('poll.options'),
|
||||
optionsGroupedByDays: groupBy('options', raw('day')),
|
||||
|
||||
/*
|
||||
* scrollbar on top of table for thead
|
||||
*/
|
||||
const topScrollbarInnerThead = $('<div></div>')
|
||||
.css('width', $('.user-selections-table').width())
|
||||
.css('height', '1px');
|
||||
const topScrollbarOuterThead = $('<div></div>')
|
||||
.addClass('top-scrollbar-floatThead')
|
||||
.css('width', $('.table-scroll').outerWidth())
|
||||
.css('overflow-x', 'scroll')
|
||||
.css('overflow-y', 'hidden')
|
||||
.css('position', 'fixed')
|
||||
.css('top', '-1px')
|
||||
.css('z-index', '1002')
|
||||
.css('margin-left', `${($('.table-scroll').outerWidth() - $('.table-scroll').width()) / 2 * (-1)}px`)
|
||||
.css('margin-right', `${($('.table-scroll').outerWidth() - $('.table-scroll').width()) / 2 * (-1)}px`);
|
||||
$('.table-scroll').prepend(
|
||||
topScrollbarOuterThead.append(topScrollbarInnerThead).hide()
|
||||
);
|
||||
|
||||
// add listener to resize scrollbars if window get resized
|
||||
$(window).resize(this.resizeScrollbars);
|
||||
|
||||
/*
|
||||
* bind scroll event on all scrollbars
|
||||
*/
|
||||
$('.table-scroll').scroll(function() {
|
||||
$('.top-scrollbar').scrollLeft($('.table-scroll').scrollLeft());
|
||||
$('.top-scrollbar-floatThead').scrollLeft($('.table-scroll').scrollLeft());
|
||||
});
|
||||
$('.top-scrollbar').scroll(function() {
|
||||
$('.table-scroll').scrollLeft($('.top-scrollbar').scrollLeft());
|
||||
$('.top-scrollbar-floatThead').scrollLeft($('.top-scrollbar').scrollLeft());
|
||||
});
|
||||
$('.top-scrollbar-floatThead').scroll(function() {
|
||||
$('.table-scroll').scrollLeft($('.top-scrollbar-floatThead').scrollLeft());
|
||||
$('.top-scrollbar').scrollLeft($('.top-scrollbar-floatThead').scrollLeft());
|
||||
});
|
||||
|
||||
/*
|
||||
* show inner scrollbar only, if header is fixed
|
||||
*/
|
||||
$(window).scroll($.proxy(this.updateScrollbarTopVisibility, this));
|
||||
});
|
||||
},
|
||||
|
||||
/*
|
||||
* calculates horizontal scrollbar height depending on current browser
|
||||
*/
|
||||
getScrollbarHeight() {
|
||||
const wideScrollWtml = $('<div>').attr('id', 'wide_scroll_div_one').css({
|
||||
'width': 50,
|
||||
'height': 50,
|
||||
'overflow-y': 'scroll',
|
||||
'position': 'absolute',
|
||||
'top': -200,
|
||||
'left': -200
|
||||
}).append(
|
||||
$('<div>').attr('id', 'wide_scroll_div_two').css({
|
||||
'height': '100%',
|
||||
'width': 100
|
||||
})
|
||||
);
|
||||
$('body').append(wideScrollWtml); // Append our div and add the hmtl to your document for calculations
|
||||
const scrollW1 = $('#wide_scroll_div_one').height(); // Getting the width of the surrounding(parent) div - we already know it is 50px since we styled it but just to make sure.
|
||||
const scrollW2 = $('#wide_scroll_div_two').innerHeight(); // Find the inner width of the inner(child) div.
|
||||
const scrollBarWidth = scrollW1 - scrollW2; // subtract the difference
|
||||
$('#wide_scroll_div_one').remove(); // remove the html from your document
|
||||
return scrollBarWidth;
|
||||
},
|
||||
|
||||
optionsGroupedByDates: groupBy('options', 'optionsGroupedBy', function(groupValue, currentValue) {
|
||||
// have to parse the date cause due to timezone it may start with another day string but be at same day due to timezone
|
||||
// e.g. '2015-01-01T23:00:00.000Z' and '2015-01-02T00:00:00.000Z' both are at '2015-01-02' for timezone offset '+01:00'
|
||||
return moment(groupValue).format('YYYY-MM-DD') === moment(currentValue).format('YYYY-MM-DD');
|
||||
}),
|
||||
optionsGroupedBy: 'title',
|
||||
|
||||
/*
|
||||
* resize scrollbars
|
||||
* used as event callback when window is resized
|
||||
*/
|
||||
resizeScrollbars() {
|
||||
$('.top-scrollbar div').css('width', $('.user-selections-table').width());
|
||||
$('.top-scrollbar-floatThead').css('width', $('.table-scroll').outerWidth());
|
||||
$('.top-scrollbar-floatThead div').css('width', $('.user-selections-table').width());
|
||||
},
|
||||
|
||||
/*
|
||||
* resize scrollbars if document height might be changed
|
||||
* and therefore scrollbars might be added
|
||||
*/
|
||||
triggerResizeScrollbars: observer('controller.isEvaluable', 'controller.model.users.[]', function() {
|
||||
next(() => {
|
||||
this.resizeScrollbars();
|
||||
});
|
||||
}),
|
||||
|
||||
/*
|
||||
* show / hide top scrollbar depending on window position
|
||||
* used as event callback when window is scrolled
|
||||
*/
|
||||
updateScrollbarTopVisibility() {
|
||||
const windowTop = $(window).scrollTop();
|
||||
const tableTop = $('.table-scroll table').offset().top;
|
||||
if (windowTop >= tableTop - this.getScrollbarHeight()) {
|
||||
$('.top-scrollbar-floatThead').show();
|
||||
|
||||
// update scroll position
|
||||
$('.top-scrollbar-floatThead').scrollLeft($('.table-scroll').scrollLeft());
|
||||
} else {
|
||||
$('.top-scrollbar-floatThead').hide();
|
||||
}
|
||||
},
|
||||
|
||||
/*
|
||||
* clean up
|
||||
* especially remove event listeners
|
||||
*/
|
||||
willDestroyElement() {
|
||||
$(window).off('resize', this.resizeScrollbars);
|
||||
$(window).off('scroll', this.updateScrollbarTopVisibility);
|
||||
}
|
||||
users: readOnly('poll.users'),
|
||||
usersSorted: sort('users', ['creationDate']),
|
||||
});
|
||||
|
|
|
@ -1,27 +1,31 @@
|
|||
import Component from '@ember/component';
|
||||
import { computed } from '@ember/object';
|
||||
import { gt, mapBy, max, readOnly } from '@ember/object/computed';
|
||||
import { copy } from '@ember/object/internals';
|
||||
import { isEmpty } from '@ember/utils';
|
||||
import { inject as service } from '@ember/service';
|
||||
|
||||
export default Component.extend({
|
||||
i18n: service(),
|
||||
|
||||
classNames: ['evaluation-summary'],
|
||||
|
||||
evaluationBestOptions: computed('poll.users.[]', function() {
|
||||
bestOptions: computed('users.[]', function() {
|
||||
// can not evaluate answer type free text
|
||||
if (this.get('poll.isFreeText')) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
// can not evaluate a poll without users
|
||||
if (isEmpty(this.get('poll.users'))) {
|
||||
if (isEmpty(this.users)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
let answers = this.get('poll.answers').reduce((answers, answer) => {
|
||||
let answers = this.poll.answers.reduce((answers, answer) => {
|
||||
answers[answer.get('type')] = 0;
|
||||
return answers;
|
||||
}, {});
|
||||
let evaluation = this.get('poll.options').map((option) => {
|
||||
let evaluation = this.poll.options.map((option) => {
|
||||
return {
|
||||
answers: copy(answers),
|
||||
option,
|
||||
|
@ -30,11 +34,11 @@ export default Component.extend({
|
|||
});
|
||||
let bestOptions = [];
|
||||
|
||||
this.get('poll.users').forEach(function(user) {
|
||||
user.get('selections').forEach(function(selection, i) {
|
||||
evaluation[i].answers[selection.get('type')]++;
|
||||
this.users.forEach((user) => {
|
||||
user.selections.forEach(({ type }, i) => {
|
||||
evaluation[i].answers[type]++;
|
||||
|
||||
switch (selection.get('type')) {
|
||||
switch (type) {
|
||||
case 'yes':
|
||||
evaluation[i].score += 2;
|
||||
break;
|
||||
|
@ -50,9 +54,7 @@ export default Component.extend({
|
|||
});
|
||||
});
|
||||
|
||||
evaluation.sort(function(a, b) {
|
||||
return b.score - a.score;
|
||||
});
|
||||
evaluation.sort((a, b) => b.score - a.score);
|
||||
|
||||
let bestScore = evaluation[0].score;
|
||||
for (let i = 0; i < evaluation.length; i++) {
|
||||
|
@ -70,19 +72,14 @@ export default Component.extend({
|
|||
return bestOptions;
|
||||
}),
|
||||
|
||||
evaluationBestOptionsMultiple: computed('evaluationBestOptions', function() {
|
||||
if (this.get('evaluationBestOptions.length') > 1) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
currentLocale: readOnly('i18n.locale'),
|
||||
|
||||
evaluationLastParticipation: computed('sortedUsers.[]', function() {
|
||||
return this.get('sortedUsers.lastObject.creationDate');
|
||||
}),
|
||||
multipleBestOptions: gt('bestOptions.length', 1),
|
||||
|
||||
evaluationParticipants: computed('poll.users.[]', function() {
|
||||
return this.get('poll.users.length');
|
||||
})
|
||||
lastParticipationAt: max('participationDates'),
|
||||
participationDates: mapBy('users', 'creationDate'),
|
||||
|
||||
participantsCount: readOnly('users.length'),
|
||||
|
||||
users: readOnly('poll.users'),
|
||||
});
|
||||
|
|
|
@ -6,6 +6,11 @@ import { observer, computed } from '@ember/object';
|
|||
import moment from 'moment';
|
||||
|
||||
export default Controller.extend({
|
||||
encryption: service(),
|
||||
flashMessages: service(),
|
||||
i18n: service(),
|
||||
router: service(),
|
||||
|
||||
actions: {
|
||||
linkAction(type) {
|
||||
let flashMessages = this.flashMessages;
|
||||
|
@ -27,25 +32,9 @@ export default Controller.extend({
|
|||
|
||||
currentLocale: readOnly('i18n.locale'),
|
||||
|
||||
encryption: service(),
|
||||
encryptionKey: '',
|
||||
queryParams: ['encryptionKey'],
|
||||
|
||||
flashMessages: service(),
|
||||
|
||||
hasTimes: computed('model.options.[]', function() {
|
||||
if (this.get('model.isMakeAPoll')) {
|
||||
return false;
|
||||
} else {
|
||||
return this.get('model.options').any((option) => {
|
||||
let dayStringLength = 10; // 'YYYY-MM-DD'.length
|
||||
return option.get('title').length > dayStringLength;
|
||||
});
|
||||
}
|
||||
}),
|
||||
|
||||
i18n: service(),
|
||||
|
||||
momentLongDayFormat: computed('currentLocale', function() {
|
||||
let currentLocale = this.currentLocale;
|
||||
return moment.localeData(currentLocale)
|
||||
|
@ -55,24 +44,26 @@ export default Controller.extend({
|
|||
.trim();
|
||||
}),
|
||||
|
||||
pollUrl: computed('currentPath', 'encryptionKey', function() {
|
||||
poll: readOnly('model'),
|
||||
pollUrl: computed('router.currentURL', 'encryptionKey', function() {
|
||||
return window.location.href;
|
||||
}),
|
||||
|
||||
// TODO: Remove this code. It's spooky.
|
||||
preventEncryptionKeyChanges: observer('encryptionKey', function() {
|
||||
if (
|
||||
!isEmpty(this.get('encryption.key')) &&
|
||||
this.encryptionKey !== this.get('encryption.key')
|
||||
!isEmpty(this.encryption.key) &&
|
||||
this.encryptionKey !== this.encryption.key
|
||||
) {
|
||||
// work-a-round for url not being updated
|
||||
window.location.hash = window.location.hash.replace(this.encryptionKey, this.get('encryption.key'));
|
||||
window.location.hash = window.location.hash.replace(this.encryptionKey, this.encryption.key);
|
||||
|
||||
this.set('encryptionKey', this.get('encryption.key'));
|
||||
this.set('encryptionKey', this.encryption.key);
|
||||
}
|
||||
}),
|
||||
|
||||
showExpirationWarning: computed('model.expirationDate', function() {
|
||||
let expirationDate = this.get('model.expirationDate');
|
||||
showExpirationWarning: computed('poll.expirationDate', function() {
|
||||
let expirationDate = this.poll.expirationDate;
|
||||
if (isEmpty(expirationDate)) {
|
||||
return false;
|
||||
}
|
||||
|
@ -84,8 +75,8 @@ export default Controller.extend({
|
|||
/*
|
||||
* return true if current timezone differs from timezone poll got created with
|
||||
*/
|
||||
timezoneDiffers: computed('model.timezone', function() {
|
||||
const modelTimezone = this.get('model.timezone');
|
||||
timezoneDiffers: computed('poll.timezone', function() {
|
||||
let modelTimezone = this.poll.timezone;
|
||||
return isPresent(modelTimezone) && moment.tz.guess() !== modelTimezone;
|
||||
}),
|
||||
|
||||
|
@ -96,6 +87,6 @@ export default Controller.extend({
|
|||
}),
|
||||
|
||||
timezone: computed('useLocalTimezone', function() {
|
||||
return this.useLocalTimezone ? undefined : this.get('model.timezone');
|
||||
return this.useLocalTimezone ? undefined : this.poll.timezone;
|
||||
})
|
||||
});
|
||||
|
|
|
@ -1,32 +1,31 @@
|
|||
import { inject as service } from '@ember/service';
|
||||
import { reads, readOnly, sort } from '@ember/object/computed';
|
||||
import { and, gt, not, readOnly } from '@ember/object/computed';
|
||||
import $ from 'jquery';
|
||||
import { computed } from '@ember/object';
|
||||
import Controller, { inject as controller } from '@ember/controller';
|
||||
|
||||
export default Controller.extend({
|
||||
currentLocale: reads('i18n.locale'),
|
||||
currentLocale: readOnly('i18n.locale'),
|
||||
|
||||
hasTimes: reads('pollController.hasTimes'),
|
||||
hasTimes: readOnly('poll.hasTimes'),
|
||||
|
||||
i18n: service(),
|
||||
|
||||
momentLongDayFormat: readOnly('pollController.momentLongDayFormat'),
|
||||
|
||||
poll: readOnly('model'),
|
||||
pollController: controller('poll'),
|
||||
|
||||
sortedUsers: sort('pollController.model.users', 'usersSorting'),
|
||||
usersSorting: computed(() => ['creationDate']),
|
||||
timezone: readOnly('pollController.timezone'),
|
||||
|
||||
timezone: reads('pollController.timezone'),
|
||||
users: readOnly('poll.users'),
|
||||
|
||||
/*
|
||||
* evaluates poll data
|
||||
* if free text answers are allowed evaluation is disabled
|
||||
*/
|
||||
evaluation: computed('model.users.[]', function() {
|
||||
// disable evaluation if answer type is free text
|
||||
if (this.get('model.answerType') === 'FreeText') {
|
||||
evaluation: computed('users.[]', function() {
|
||||
if (!this.isEvaluable) {
|
||||
return [];
|
||||
}
|
||||
|
||||
|
@ -35,13 +34,13 @@ export default Controller.extend({
|
|||
let lookup = [];
|
||||
|
||||
// init options array
|
||||
this.get('model.options').forEach(function(option, index) {
|
||||
this.poll.options.forEach((option, index) => {
|
||||
options[index] = 0;
|
||||
});
|
||||
|
||||
// init array of evalutation objects
|
||||
// create object for every possible answer
|
||||
this.get('model.answers').forEach(function(answer) {
|
||||
this.poll.answers.forEach((answer) => {
|
||||
evaluation.push({
|
||||
id: answer.label,
|
||||
label: answer.label,
|
||||
|
@ -49,7 +48,7 @@ export default Controller.extend({
|
|||
});
|
||||
});
|
||||
// create object for no answer if answers are not forced
|
||||
if (!this.get('model.forceAnswer')) {
|
||||
if (!this.poll.forceAnswer) {
|
||||
evaluation.push({
|
||||
id: null,
|
||||
label: 'no answer',
|
||||
|
@ -63,21 +62,21 @@ export default Controller.extend({
|
|||
});
|
||||
|
||||
// loop over all users
|
||||
this.get('model.users').forEach(function(user) {
|
||||
this.poll.users.forEach((user) => {
|
||||
// loop over all selections of the user
|
||||
user.get('selections').forEach(function(selection, optionindex) {
|
||||
let answerindex;
|
||||
user.selections.forEach(function(selection, optionIndex) {
|
||||
let answerIndex;
|
||||
|
||||
// get answer index by lookup array
|
||||
if (typeof lookup[selection.get('value.label')] === 'undefined') {
|
||||
answerindex = lookup[null];
|
||||
if (typeof lookup[selection.value.label] === 'undefined') {
|
||||
answerIndex = lookup[null];
|
||||
} else {
|
||||
answerindex = lookup[selection.get('value.label')];
|
||||
answerIndex = lookup[selection.get('value.label')];
|
||||
}
|
||||
|
||||
// increment counter
|
||||
try {
|
||||
evaluation[answerindex].options[optionindex] = evaluation[answerindex].options[optionindex] + 1;
|
||||
evaluation[answerIndex].options[optionIndex]++;
|
||||
} catch (e) {
|
||||
// ToDo: Throw an error
|
||||
}
|
||||
|
@ -87,26 +86,7 @@ export default Controller.extend({
|
|||
return evaluation;
|
||||
}),
|
||||
|
||||
/*
|
||||
* calculate colspan for a row which should use all columns in table
|
||||
* used by evaluation row
|
||||
*/
|
||||
fullRowColspan: computed('model.options.[]', function() {
|
||||
return this.get('model.options.length') + 2;
|
||||
}),
|
||||
|
||||
isEvaluable: computed('model.{users.[],isFreeText}', function() {
|
||||
if (
|
||||
!this.get('model.isFreeText') &&
|
||||
this.get('model.users.length') > 0
|
||||
) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}),
|
||||
|
||||
optionCount: computed('model.options', function() {
|
||||
return this.get('model.options.length');
|
||||
})
|
||||
hasUsers: gt('poll.users.length', 0),
|
||||
isNotFreeText: not('poll.isFreeText'),
|
||||
isEvaluable: and('hasUsers', 'isNotFreeText'),
|
||||
});
|
||||
|
|
|
@ -25,7 +25,7 @@ const Validations = buildValidations({
|
|||
dependentKeys: ['model.i18n.locale']
|
||||
}),
|
||||
validator('unique', {
|
||||
parent: 'pollController.model',
|
||||
parent: 'poll',
|
||||
attributeInParent: 'users',
|
||||
dependentKeys: ['model.poll.users.[]', 'model.poll.users.@each.name', 'model.i18n.locale'],
|
||||
disable: readOnly('model.anonymousUser'),
|
||||
|
@ -58,67 +58,74 @@ const SelectionValidations = buildValidations({
|
|||
export default Controller.extend(Validations, {
|
||||
actions: {
|
||||
submit() {
|
||||
if (this.get('validations.isValid')) {
|
||||
const user = this.store.createRecord('user', {
|
||||
creationDate: new Date(),
|
||||
poll: this.get('pollController.model'),
|
||||
version: config.APP.version,
|
||||
});
|
||||
|
||||
user.set('name', this.name);
|
||||
|
||||
const selections = user.get('selections');
|
||||
const possibleAnswers = this.get('pollController.model.answers');
|
||||
|
||||
this.selections.forEach((selection) => {
|
||||
if (selection.get('value') !== null) {
|
||||
if (this.isFreeText) {
|
||||
selections.createFragment({
|
||||
label: selection.get('value')
|
||||
});
|
||||
} else {
|
||||
const answer = possibleAnswers.findBy('type', selection.get('value'));
|
||||
selections.createFragment({
|
||||
icon: answer.get('icon'),
|
||||
label: answer.get('label'),
|
||||
labelTranslation: answer.get('labelTranslation'),
|
||||
type: answer.get('type')
|
||||
});
|
||||
}
|
||||
} else {
|
||||
selections.createFragment();
|
||||
}
|
||||
});
|
||||
|
||||
this.set('newUserRecord', user);
|
||||
this.send('save');
|
||||
if (!this.get('validations.isValid')) {
|
||||
return;
|
||||
}
|
||||
|
||||
let poll = this.poll;
|
||||
let selections = this.selections.map(({ value }) => {
|
||||
if (value === null) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (this.isFreeText) {
|
||||
return {
|
||||
label: value,
|
||||
};
|
||||
}
|
||||
|
||||
// map selection to answer if it's not freetext
|
||||
let answer = poll.answers.findBy('type', value);
|
||||
let { icon, label, labelTranslation, type } = answer;
|
||||
|
||||
return {
|
||||
icon,
|
||||
label,
|
||||
labelTranslation,
|
||||
type,
|
||||
};
|
||||
});
|
||||
let user = this.store.createRecord('user', {
|
||||
creationDate: new Date(),
|
||||
name: this.name,
|
||||
poll,
|
||||
selections,
|
||||
version: config.APP.version,
|
||||
});
|
||||
|
||||
this.set('newUserRecord', user);
|
||||
this.send('save');
|
||||
},
|
||||
save() {
|
||||
const user = this.newUserRecord;
|
||||
user.save()
|
||||
.then(() => {
|
||||
async save() {
|
||||
let user = this.newUserRecord;
|
||||
|
||||
try {
|
||||
await user.save();
|
||||
|
||||
this.set('savingFailed', false);
|
||||
|
||||
// reset form
|
||||
this.set('name', '');
|
||||
this.selections.forEach((selection) => {
|
||||
selection.set('value', null);
|
||||
});
|
||||
|
||||
this.transitionToRoute('poll.evaluation', this.model, {
|
||||
queryParams: { encryptionKey: this.get('encryption.key') }
|
||||
});
|
||||
}, () => {
|
||||
} catch (error) {
|
||||
// couldn't save user model
|
||||
this.set('savingFailed', true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// reset form
|
||||
this.set('name', '');
|
||||
this.selections.forEach((selection) => {
|
||||
selection.set('value', null);
|
||||
});
|
||||
|
||||
this.transitionToRoute('poll.evaluation', this.model, {
|
||||
queryParams: { encryptionKey: this.encryption.key }
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
anonymousUser: readOnly('pollController.model.anonymousUser'),
|
||||
anonymousUser: readOnly('poll.anonymousUser'),
|
||||
currentLocale: readOnly('i18n.locale'),
|
||||
encryption: service(),
|
||||
forceAnswer: readOnly('pollController.model.forceAnswer'),
|
||||
forceAnswer: readOnly('poll.forceAnswer'),
|
||||
i18n: service(),
|
||||
|
||||
init() {
|
||||
|
@ -127,19 +134,20 @@ export default Controller.extend(Validations, {
|
|||
this.get('i18n.locale');
|
||||
},
|
||||
|
||||
isFreeText: readOnly('pollController.model.isFreeText'),
|
||||
isFindADate: readOnly('pollController.model.isFindADate'),
|
||||
isFreeText: readOnly('poll.isFreeText'),
|
||||
isFindADate: readOnly('poll.isFindADate'),
|
||||
|
||||
momentLongDayFormat: readOnly('pollController.momentLongDayFormat'),
|
||||
|
||||
name: '',
|
||||
|
||||
options: readOnly('pollController.model.options'),
|
||||
options: readOnly('poll.options'),
|
||||
|
||||
poll: readOnly('model'),
|
||||
pollController: controller('poll'),
|
||||
|
||||
possibleAnswers: computed('pollController.model.answers', function() {
|
||||
return this.get('pollController.model.answers').map((answer) => {
|
||||
possibleAnswers: computed('poll.answers', function() {
|
||||
return this.get('poll.answers').map((answer) => {
|
||||
const owner = getOwner(this);
|
||||
|
||||
const AnswerObject = EmberObject.extend({
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import { computed } from '@ember/object';
|
||||
import DS from 'ember-data';
|
||||
import {
|
||||
fragmentArray
|
||||
} from 'ember-data-model-fragments/attributes';
|
||||
import { fragmentArray } from 'ember-data-model-fragments/attributes';
|
||||
import { computed } from '@ember/object';
|
||||
import { equal } from '@ember/object/computed';
|
||||
|
||||
const {
|
||||
attr,
|
||||
|
@ -64,15 +63,18 @@ export default Model.extend({
|
|||
/*
|
||||
* computed properties
|
||||
*/
|
||||
isFindADate: computed('pollType', function() {
|
||||
return this.pollType === 'FindADate';
|
||||
hasTimes: computed('options.[]', function() {
|
||||
if (this.isMakeAPoll) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.options.any((option) => {
|
||||
let dayStringLength = 10; // 'YYYY-MM-DD'.length
|
||||
return option.title.length > dayStringLength;
|
||||
});
|
||||
}),
|
||||
|
||||
isFreeText: computed('answerType', function() {
|
||||
return this.answerType === 'FreeText';
|
||||
}),
|
||||
|
||||
isMakeAPoll: computed('pollType', function() {
|
||||
return this.pollType === 'MakeAPoll';
|
||||
})
|
||||
isFindADate: equal('pollType', 'FindADate'),
|
||||
isFreeText: equal('answerType', 'FreeText'),
|
||||
isMakeAPoll: equal('pollType', 'MakeAPoll'),
|
||||
});
|
||||
|
|
7
app/routes/poll/evaluation.js
Normal file
7
app/routes/poll/evaluation.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend({
|
||||
model() {
|
||||
return this.modelFor('poll');
|
||||
}
|
||||
});
|
7
app/routes/poll/participation.js
Normal file
7
app/routes/poll/participation.js
Normal file
|
@ -0,0 +1,7 @@
|
|||
import Route from '@ember/routing/route';
|
||||
|
||||
export default Route.extend({
|
||||
model() {
|
||||
return this.modelFor('poll');
|
||||
}
|
||||
});
|
47
app/styles/_participants-table.scss
Normal file
47
app/styles/_participants-table.scss
Normal file
|
@ -0,0 +1,47 @@
|
|||
#poll {
|
||||
.participants-table {
|
||||
max-height: 95vh;
|
||||
width: 100%;
|
||||
overflow: scroll;
|
||||
|
||||
table {
|
||||
overflow: scroll;
|
||||
|
||||
th, td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
thead {
|
||||
// position sticky on thead is not supported by Chrome and Edge
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 100;
|
||||
background-color: #fff;
|
||||
|
||||
tr {
|
||||
&:not(:last-child) th:not([rowspan="2"]) {
|
||||
// do not show a border between rows of a multi-row header
|
||||
border-bottom: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
thead,
|
||||
tbody {
|
||||
tr {
|
||||
th,
|
||||
td {
|
||||
&:first-child {
|
||||
position: -webkit-sticky;
|
||||
position: sticky;
|
||||
left: 0;
|
||||
z-index: 200;
|
||||
background-color: #fff;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
@import "ember-bootstrap/bootstrap";
|
||||
@import "calendar";
|
||||
@import "participants-table";
|
||||
|
||||
table tr td .form-group {
|
||||
margin-bottom:0;
|
||||
|
@ -168,25 +169,7 @@ body {
|
|||
left:100%;
|
||||
animation-delay:1.43s;
|
||||
}
|
||||
/*
|
||||
* using floatThead with Bootstrap 3 fix
|
||||
* https://mkoryak.github.io/floatThead/examples/bootstrap3/
|
||||
*/
|
||||
table.floatThead-table {
|
||||
border-top:none;
|
||||
border-bottom:none;
|
||||
background-color:#ffffff;
|
||||
}
|
||||
#poll table tr th,
|
||||
#poll table tr td {
|
||||
white-space: nowrap;
|
||||
}
|
||||
#poll table thead tr.dateGroups th {
|
||||
border-bottom: none;
|
||||
}
|
||||
#poll table thead tr.dateGroups ~ tr th {
|
||||
border-top: none;
|
||||
}
|
||||
|
||||
ul.nav-tabs {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{{ember-chart
|
||||
type=type
|
||||
type="bar"
|
||||
data=data
|
||||
options=chartOptions
|
||||
}}
|
||||
|
|
|
@ -1,44 +1,34 @@
|
|||
<div class="table-scroll">
|
||||
<table class="user-selections-table table table-striped table-condensed">
|
||||
<div class="participants-table">
|
||||
<table
|
||||
class="table"
|
||||
data-test-table-of="participants"
|
||||
>
|
||||
<thead>
|
||||
{{#if hasTimes}}
|
||||
<tr class="dateGroups">
|
||||
<th> </th>
|
||||
{{#each optionsGroupedByDates as |optionGroup|}}
|
||||
{{#if this.hasTimes}}
|
||||
<tr>
|
||||
<th>
|
||||
{{!-- column for name --}}
|
||||
</th>
|
||||
{{#each this.optionsGroupedByDays as |optionGroup|}}
|
||||
<th colspan={{optionGroup.items.length}}>
|
||||
{{moment-format
|
||||
optionGroup.value
|
||||
momentLongDayFormat
|
||||
locale=currentLocale
|
||||
timeZone=timezone
|
||||
}}
|
||||
{{moment-format optionGroup.value this.momentLongDayFormat}}
|
||||
</th>
|
||||
{{/each}}
|
||||
<th> </th>
|
||||
</tr>
|
||||
{{/if}}
|
||||
|
||||
<tr>
|
||||
<th> </th>
|
||||
{{#each options as |option|}}
|
||||
<th>
|
||||
{{!-- column for name --}}
|
||||
</th>
|
||||
{{#each this.options as |option|}}
|
||||
<th>
|
||||
{{#if isFindADate}}
|
||||
{{#if hasTimes}}
|
||||
{{#if option.hasTime}}
|
||||
{{moment-format
|
||||
option.title
|
||||
"LT"
|
||||
locale=currentLocale
|
||||
timeZone=timezone
|
||||
}}
|
||||
{{/if}}
|
||||
{{else}}
|
||||
{{moment-format
|
||||
option.title
|
||||
momentLongDayFormat
|
||||
locale=currentLocale
|
||||
timeZone=timezone
|
||||
}}
|
||||
{{#if (and this.isFindADate this.hasTimes)}}
|
||||
{{#if option.hasTime}}
|
||||
{{moment-format option.date "LT"}}
|
||||
{{/if}}
|
||||
{{else if this.isFindADate}}
|
||||
{{moment-format option.date this.momentLongDayFormat}}
|
||||
{{else}}
|
||||
{{option.title}}
|
||||
{{/if}}
|
||||
|
@ -48,28 +38,34 @@
|
|||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#each sortedUsers as |user|}}
|
||||
<tr class="user">
|
||||
<td>{{user.name}}</td>
|
||||
{{#each user.selections as |selection|}}
|
||||
<td>
|
||||
{{#if selection.label}}
|
||||
{{#if isFreeText}}
|
||||
{{#each this.usersSorted as |user|}}
|
||||
<tr data-test-participant={{user.id}}>
|
||||
<td
|
||||
data-test-value-for="name"
|
||||
>
|
||||
{{user.name}}
|
||||
</td>
|
||||
{{#each this.options as |option index|}}
|
||||
<td
|
||||
data-test-is-selection-cell
|
||||
data-test-value-for={{option.value}}
|
||||
>
|
||||
{{#let (object-at index user.selections) as |selection|}}
|
||||
{{#if this.isFreeText}}
|
||||
{{selection.label}}
|
||||
{{else}}
|
||||
{{#if selection.type}}
|
||||
<span class={{selection.type}}>
|
||||
<span class={{selection.icon}}></span>
|
||||
{{t selection.labelTranslation}}
|
||||
</span>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{#if selection.labelTranslation}}
|
||||
{{#unless isFreeText}}
|
||||
<span class={{selection.type}}>
|
||||
<span class={{selection.icon}}></span>
|
||||
{{t selection.labelTranslation}}
|
||||
</span>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
</td>
|
||||
{{/each}}
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
|
@ -3,25 +3,25 @@
|
|||
</h2>
|
||||
|
||||
<p class="participants">
|
||||
{{t "poll.evaluation.participants" count=evaluationParticipants}}
|
||||
{{t "poll.evaluation.participants" count=participantsCount}}
|
||||
</p>
|
||||
|
||||
<p class="best-options">
|
||||
{{#if poll.isFindADate}}
|
||||
{{t
|
||||
"poll.evaluation.bestOption.label.findADate"
|
||||
count=evaluationBestOptions.length
|
||||
count=bestOptions.length
|
||||
}}
|
||||
{{else}}
|
||||
{{t
|
||||
"poll.evaluation.bestOption.label.makeAPoll"
|
||||
count=evaluationBestOptions.length
|
||||
count=bestOptions.length
|
||||
}}
|
||||
{{/if}}
|
||||
|
||||
{{#if evaluationBestOptionsMultiple}}
|
||||
{{#if multipleBestOptions}}
|
||||
<ul>
|
||||
{{#each evaluationBestOptions as |evaluationBestOption|}}
|
||||
{{#each bestOptions as |evaluationBestOption|}}
|
||||
{{poll-evaluation-summary-option
|
||||
currentLocale=currentLocale
|
||||
evaluationBestOption=evaluationBestOption
|
||||
|
@ -35,7 +35,7 @@
|
|||
{{else}}
|
||||
{{poll-evaluation-summary-option
|
||||
currentLocale=currentLocale
|
||||
evaluationBestOption=evaluationBestOptions.firstObject
|
||||
evaluationBestOption=bestOptions.firstObject
|
||||
isFindADate=poll.isFindADate
|
||||
momentLongDayFormat=momentLongDayFormat
|
||||
tagName="span"
|
||||
|
@ -47,6 +47,6 @@
|
|||
<p class="last-participation">
|
||||
{{t
|
||||
"poll.evaluation.lastParticipation"
|
||||
ago=(moment-from-now evaluationLastParticipation locale=currentLocale timezone=timezone)
|
||||
ago=(moment-from-now lastParticipationAt locale=currentLocale timezone=timezone)
|
||||
}}
|
||||
</p>
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
}}
|
||||
{{#autofocusable-element
|
||||
tagName="select"
|
||||
onchange=(action "updateAnswerType" value="target.value")
|
||||
change=(action "updateAnswerType" value="target.value")
|
||||
id=el.id
|
||||
class="form-control"
|
||||
}}
|
||||
|
@ -52,14 +52,14 @@
|
|||
controlType="checkbox"
|
||||
label=(t "create.settings.anonymousUser.label")
|
||||
showValidationOn="change"
|
||||
value=anonymousUser
|
||||
property="anonymousUser"
|
||||
}}
|
||||
{{form.element
|
||||
classNames="force-answer"
|
||||
controlType="checkbox"
|
||||
label=(t "create.settings.forceAnswer.label")
|
||||
showValidationOn="change"
|
||||
value=forceAnswer
|
||||
property="forceAnswer"
|
||||
}}
|
||||
{{form-navigation-buttons
|
||||
nextButtonText=(t "action.save")
|
||||
|
|
|
@ -1,32 +1,21 @@
|
|||
{{#if isEvaluable}}
|
||||
{{poll-evaluation-summary
|
||||
currentLocale=currentLocale
|
||||
momentLongDayFormat=momentLongDayFormat
|
||||
poll=model
|
||||
sortedUsers=sortedUsers
|
||||
poll=poll
|
||||
timezone=timezone
|
||||
}}
|
||||
|
||||
<h3>{{t "poll.evaluation.overview"}}</h3>
|
||||
{{poll-evaluation-chart
|
||||
answerType=model.answerType
|
||||
currentLocale=currentLocale
|
||||
isFindADate=model.isFindADate
|
||||
momentLongDayFormat=momentLongDayFormat
|
||||
options=model.options
|
||||
users=model.users
|
||||
poll=poll
|
||||
timezone=timezone
|
||||
}}
|
||||
{{/if}}
|
||||
|
||||
<h3>{{t "poll.evaluation.participantTable"}}</h3>
|
||||
{{poll-evaluation-participants-table
|
||||
currentLocale=currentLocale
|
||||
hasTimes=hasTimes
|
||||
isFindADate=model.isFindADate
|
||||
isFreeText=model.isFreeText
|
||||
momentLongDayFormat=momentLongDayFormat
|
||||
options=model.options
|
||||
sortedUsers=sortedUsers
|
||||
poll=poll
|
||||
timezone=timezone
|
||||
}}
|
||||
|
|
|
@ -23,7 +23,7 @@ module.exports = function(defaults) {
|
|||
only: ['array', 'object-at'],
|
||||
},
|
||||
'ember-math-helpers': {
|
||||
only: ['gt', 'lte', 'sub'],
|
||||
only: ['lte', 'sub'],
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -40,11 +40,6 @@ module.exports = function(defaults) {
|
|||
// please specify an object with the list of modules as keys
|
||||
// along with the exports of each module as its value.
|
||||
|
||||
app.import({
|
||||
development: 'node_modules/floatthead/dist/jquery.floatThead.js',
|
||||
production: 'node_modules/floatthead/dist/jquery.floatThead.min.js'
|
||||
});
|
||||
|
||||
app.import('node_modules/sjcl/sjcl.js', {
|
||||
using: [
|
||||
{ transformation: 'amd', as: 'sjcl' }
|
||||
|
|
|
@ -69,7 +69,6 @@
|
|||
"ember-transition-helper": "^1.0.0",
|
||||
"ember-truth-helpers": "^2.1.0",
|
||||
"eslint-plugin-ember": "^5.2.0",
|
||||
"floatthead": "^2.1.2",
|
||||
"fs-extra": "^7.0.1",
|
||||
"loader.js": "^4.7.0",
|
||||
"qunit-dom": "^0.7.1",
|
||||
|
|
|
@ -4,12 +4,16 @@ import { setupApplicationTest } from 'ember-qunit';
|
|||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { t } from 'ember-i18n/test-support';
|
||||
import switchTab from 'croodle/tests/helpers/switch-tab';
|
||||
import pollHasUser from 'croodle/tests/helpers/poll-has-user';
|
||||
import pollParticipate from 'croodle/tests/helpers/poll-participate';
|
||||
import moment from 'moment';
|
||||
import pagePollParticipation from 'croodle/tests/pages/poll/participation';
|
||||
import PollParticipationPage from 'croodle/tests/pages/poll/participation';
|
||||
import PollEvaluationPage from 'croodle/tests/pages/poll/evaluation';
|
||||
|
||||
module('Acceptance | legacy support', function(hooks) {
|
||||
let yesLabel;
|
||||
let maybeLabel;
|
||||
let noLabel;
|
||||
|
||||
hooks.beforeEach(function() {
|
||||
window.localStorage.setItem('locale', 'en');
|
||||
});
|
||||
|
@ -17,6 +21,13 @@ module('Acceptance | legacy support', function(hooks) {
|
|||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function() {
|
||||
yesLabel = t('answerTypes.yes.label').toString();
|
||||
maybeLabel = t('answerTypes.maybe.label').toString();
|
||||
noLabel = t('answerTypes.no.label').toString();
|
||||
});
|
||||
|
||||
|
||||
test('show a default poll created with v0.3.0', async function(assert) {
|
||||
const encryptionKey = '5MKFuNTKILUXw6RuqkAw6ooZw4k3mWWx98ZQw8vH';
|
||||
|
||||
|
@ -43,7 +54,7 @@ module('Acceptance | legacy support', function(hooks) {
|
|||
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
|
||||
assert.equal(currentRouteName(), 'poll.participation');
|
||||
assert.deepEqual(
|
||||
pagePollParticipation.options().labels,
|
||||
PollParticipationPage.options().labels,
|
||||
[
|
||||
moment('2015-12-24T17:00:00.000Z').format('LLLL'),
|
||||
moment('2015-12-24T19:00:00.000Z').format('LT'),
|
||||
|
@ -51,23 +62,18 @@ module('Acceptance | legacy support', function(hooks) {
|
|||
]
|
||||
);
|
||||
assert.deepEqual(
|
||||
pagePollParticipation.options().answers,
|
||||
[
|
||||
t('answerTypes.yes.label').toString(),
|
||||
t('answerTypes.maybe.label').toString(),
|
||||
t('answerTypes.no.label').toString()
|
||||
]
|
||||
PollParticipationPage.options().answers,
|
||||
[yesLabel, maybeLabel, noLabel]
|
||||
);
|
||||
|
||||
await switchTab('evaluation');
|
||||
assert.equal(currentRouteName(), 'poll.evaluation');
|
||||
pollHasUser(assert,
|
||||
'Fritz Bauer',
|
||||
[
|
||||
t('answerTypes.yes.label'),
|
||||
t('answerTypes.no.label'),
|
||||
t('answerTypes.no.label')
|
||||
]
|
||||
|
||||
let participant = PollEvaluationPage.participants.filterBy('name', 'Fritz Bauer')[0];
|
||||
assert.ok(participant, 'user exists in participants table');
|
||||
assert.deepEqual(
|
||||
participant.selections.map((_) => _.answer), [yesLabel, noLabel, noLabel],
|
||||
'participants table shows correct answers for new participant'
|
||||
);
|
||||
|
||||
await switchTab('participation');
|
||||
|
@ -75,13 +81,12 @@ module('Acceptance | legacy support', function(hooks) {
|
|||
|
||||
await pollParticipate('Hermann Langbein', ['yes', 'maybe', 'yes']);
|
||||
assert.equal(currentRouteName(), 'poll.evaluation');
|
||||
pollHasUser(assert,
|
||||
'Hermann Langbein',
|
||||
[
|
||||
t('answerTypes.yes.label'),
|
||||
t('answerTypes.maybe.label'),
|
||||
t('answerTypes.yes.label')
|
||||
]
|
||||
|
||||
participant = PollEvaluationPage.participants.filterBy('name', 'Hermann Langbein')[0];
|
||||
assert.ok(participant, 'user exists in participants table');
|
||||
assert.deepEqual(
|
||||
participant.selections.map((_) => _.answer), [yesLabel, maybeLabel, yesLabel],
|
||||
'participants table shows correct answers for new participant'
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -111,7 +116,7 @@ module('Acceptance | legacy support', function(hooks) {
|
|||
await visit(`/poll/${poll.id}?encryptionKey=${encryptionKey}`);
|
||||
assert.equal(currentRouteName(), 'poll.participation');
|
||||
assert.deepEqual(
|
||||
pagePollParticipation.options().labels,
|
||||
PollParticipationPage.options().labels,
|
||||
[
|
||||
'apple pie',
|
||||
'pecan pie',
|
||||
|
@ -121,13 +126,12 @@ module('Acceptance | legacy support', function(hooks) {
|
|||
|
||||
await switchTab('evaluation');
|
||||
assert.equal(currentRouteName(), 'poll.evaluation');
|
||||
pollHasUser(assert,
|
||||
'Paul Levi',
|
||||
[
|
||||
'would be great!',
|
||||
'no way',
|
||||
'if I had to'
|
||||
]
|
||||
|
||||
let participant = PollEvaluationPage.participants.filterBy('name', 'Paul Levi')[0];
|
||||
assert.ok(participant, 'user exists in participants table');
|
||||
assert.deepEqual(
|
||||
participant.selections.map((_) => _.answer), ['would be great!', 'no way', 'if I had to'],
|
||||
'participants table shows correct answers for new participant'
|
||||
);
|
||||
|
||||
await switchTab('participation');
|
||||
|
@ -135,13 +139,12 @@ module('Acceptance | legacy support', function(hooks) {
|
|||
|
||||
await pollParticipate('Hermann Langbein', ["I don't care", 'would be awesome', "can't imagine anything better"]);
|
||||
assert.equal(currentRouteName(), 'poll.evaluation');
|
||||
pollHasUser(assert,
|
||||
'Hermann Langbein',
|
||||
[
|
||||
"I don't care",
|
||||
'would be awesome',
|
||||
"can't imagine anything better"
|
||||
]
|
||||
|
||||
participant = PollEvaluationPage.participants.filterBy('name', 'Hermann Langbein')[0];
|
||||
assert.ok(participant, 'user exists in participants table');
|
||||
assert.deepEqual(
|
||||
participant.selections.map((_) => _.answer), ['I don\'t care', 'would be awesome', 'can\'t imagine anything better'],
|
||||
'participants table shows correct answers for new participant'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -8,12 +8,15 @@ import {
|
|||
} from '@ember/test-helpers';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupApplicationTest } from 'ember-qunit';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import { t } from 'ember-i18n/test-support';
|
||||
import pollHasUser, { pollHasUsersCount } from 'croodle/tests/helpers/poll-has-user';
|
||||
import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
||||
import PollEvaluationPage from 'croodle/tests/pages/poll/evaluation';
|
||||
import pollParticipate from 'croodle/tests/helpers/poll-participate';
|
||||
|
||||
module('Acceptance | participate in a poll', function(hooks) {
|
||||
let yesLabel;
|
||||
let noLabel;
|
||||
|
||||
hooks.beforeEach(function() {
|
||||
window.localStorage.setItem('locale', 'en');
|
||||
});
|
||||
|
@ -21,6 +24,11 @@ module('Acceptance | participate in a poll', function(hooks) {
|
|||
setupApplicationTest(hooks);
|
||||
setupMirage(hooks);
|
||||
|
||||
hooks.beforeEach(function() {
|
||||
yesLabel = t('answerTypes.yes.label').toString();
|
||||
noLabel = t('answerTypes.no.label').toString();
|
||||
});
|
||||
|
||||
test('participate in a default poll', async function(assert) {
|
||||
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let poll = this.server.create('poll', {
|
||||
|
@ -37,8 +45,13 @@ module('Acceptance | participate in a poll', function(hooks) {
|
|||
`encryptionKey=${encryptionKey}`,
|
||||
'encryption key is part of query params'
|
||||
);
|
||||
pollHasUsersCount(assert, 1, 'user is added to user selections table');
|
||||
pollHasUser(assert, 'Max Meiner', [t('answerTypes.yes.label'), t('answerTypes.no.label')]);
|
||||
assert.equal(PollEvaluationPage.participants.length, 1, 'user is added to participants table');
|
||||
let participant = PollEvaluationPage.participants.filterBy('name', 'Max Meiner')[0];
|
||||
assert.ok(participant, 'user exists in participants table');
|
||||
assert.deepEqual(
|
||||
participant.selections.map((_) => _.answer), [yesLabel, noLabel],
|
||||
'participants table shows correct answers for new participant'
|
||||
);
|
||||
|
||||
await click('.nav .participation');
|
||||
assert.equal(currentRouteName(), 'poll.participation');
|
||||
|
@ -50,8 +63,13 @@ module('Acceptance | participate in a poll', function(hooks) {
|
|||
|
||||
await pollParticipate('Peter Müller', ['yes', 'yes']);
|
||||
assert.equal(currentRouteName(), 'poll.evaluation');
|
||||
pollHasUsersCount(assert, 2, 'user is added to user selections table');
|
||||
pollHasUser(assert, 'Peter Müller', [t('answerTypes.yes.label'), t('answerTypes.yes.label')]);
|
||||
assert.equal(PollEvaluationPage.participants.length, 2, 'user is added to participants table');
|
||||
participant = PollEvaluationPage.participants.filterBy('name', 'Peter Müller')[0];
|
||||
assert.ok(participant, 'user exists in participants table');
|
||||
assert.deepEqual(
|
||||
participant.selections.map((_) => _.answer), [yesLabel, yesLabel],
|
||||
'participants table shows correct answers for new participant'
|
||||
);
|
||||
});
|
||||
|
||||
test('participate in a poll using freetext', async function(assert) {
|
||||
|
@ -67,8 +85,14 @@ module('Acceptance | participate in a poll', function(hooks) {
|
|||
|
||||
await pollParticipate('Max Manus', ['answer 1', 'answer 2']);
|
||||
assert.equal(currentRouteName(), 'poll.evaluation');
|
||||
pollHasUsersCount(assert, 1, 'user is added to user selections table');
|
||||
pollHasUser(assert, 'Max Manus', ['answer 1', 'answer 2']);
|
||||
assert.equal(PollEvaluationPage.participants.length, 1, 'user is added to participants table');
|
||||
|
||||
let participant = PollEvaluationPage.participants.filterBy('name', 'Max Manus')[0];
|
||||
assert.ok(participant, 'user exists in participants table');
|
||||
assert.deepEqual(
|
||||
participant.selections.map((_) => _.answer), ['answer 1', 'answer 2'],
|
||||
'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) {
|
||||
|
@ -83,8 +107,14 @@ module('Acceptance | participate in a poll', function(hooks) {
|
|||
|
||||
await pollParticipate('Karl Käfer', ['yes', null]);
|
||||
assert.equal(currentRouteName(), 'poll.evaluation');
|
||||
pollHasUsersCount(assert, 1, 'user is added to user selections table');
|
||||
pollHasUser(assert, 'Karl Käfer', [t('answerTypes.yes.label'), '']);
|
||||
assert.equal(PollEvaluationPage.participants.length, 1, 'user is added to participants table');
|
||||
|
||||
let participant = PollEvaluationPage.participants.filterBy('name', 'Karl Käfer')[0];
|
||||
assert.ok(participant, 'user exists in participants table');
|
||||
assert.deepEqual(
|
||||
participant.selections.map((_) => _.answer), [yesLabel, ''],
|
||||
'participants table shows correct answers for new participant'
|
||||
);
|
||||
});
|
||||
|
||||
test('participate in a poll which allows anonymous participation', async function(assert) {
|
||||
|
@ -99,8 +129,14 @@ module('Acceptance | participate in a poll', function(hooks) {
|
|||
|
||||
await pollParticipate(null, ['yes', 'no']);
|
||||
assert.equal(currentRouteName(), 'poll.evaluation');
|
||||
pollHasUsersCount(assert, 1, 'user is added to user selections table');
|
||||
pollHasUser(assert, '', [t('answerTypes.yes.label'), t('answerTypes.no.label')]);
|
||||
assert.equal(PollEvaluationPage.participants.length, 1, 'user is added to participants table');
|
||||
|
||||
let participant = PollEvaluationPage.participants.filterBy('name', '')[0];
|
||||
assert.ok(participant, 'user exists in participants table');
|
||||
assert.deepEqual(
|
||||
participant.selections.map((_) => _.answer), [yesLabel, noLabel],
|
||||
'participants table shows correct answers for new participant'
|
||||
);
|
||||
});
|
||||
|
||||
test('network connectivity errors', async function(assert) {
|
||||
|
@ -116,7 +152,7 @@ module('Acceptance | participate in a poll', function(hooks) {
|
|||
assert.dom('modal-saving-failed-modal')
|
||||
.doesNotExist('failed saving notification is not shown before attempt to save');
|
||||
|
||||
await pollParticipate('foo bar', ['yes', 'no']);
|
||||
await pollParticipate('John Doe', ['yes', 'no']);
|
||||
assert.dom('#modal-saving-failed-modal')
|
||||
.exists('user gets notified that saving failed');
|
||||
|
||||
|
@ -126,7 +162,13 @@ module('Acceptance | participate in a poll', function(hooks) {
|
|||
assert.dom('#modal-saving-failed-modal')
|
||||
.doesNotExist('Notification is hidden after another save attempt was successful');
|
||||
assert.equal(currentRouteName(), 'poll.evaluation');
|
||||
pollHasUsersCount(assert, 1, 'user is added to user selections table');
|
||||
pollHasUser(assert, 'foo bar', [t('answerTypes.yes.label'), t('answerTypes.no.label')]);
|
||||
assert.equal(PollEvaluationPage.participants.length, 1, 'user is added to participants table');
|
||||
|
||||
let participant = PollEvaluationPage.participants.filterBy('name', 'John Doe')[0];
|
||||
assert.ok(participant, 'user exists in participants table');
|
||||
assert.deepEqual(
|
||||
participant.selections.map((_) => _.answer), [yesLabel, noLabel],
|
||||
'participants table shows correct answers for new participant'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
@ -5,6 +5,8 @@ import setupMirage from 'ember-cli-mirage/test-support/setup-mirage';
|
|||
import { t } from 'ember-i18n/test-support';
|
||||
import switchTab from 'croodle/tests/helpers/switch-tab';
|
||||
import moment from 'moment';
|
||||
import PollEvaluationPage from 'croodle/tests/pages/poll/evaluation';
|
||||
import { assign } from '@ember/polyfills';
|
||||
|
||||
module('Acceptance | view evaluation', function(hooks) {
|
||||
hooks.beforeEach(function() {
|
||||
|
@ -114,45 +116,47 @@ module('Acceptance | view evaluation', function(hooks) {
|
|||
|
||||
test('evaluation is correct for MakeAPoll', async function(assert) {
|
||||
let encryptionKey = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
||||
let user1 = this.server.create('user', {
|
||||
creationDate: '2015-01-01T00:00:00.000Z',
|
||||
encryptionKey,
|
||||
name: 'Maximilian',
|
||||
selections: [
|
||||
{
|
||||
type: 'yes',
|
||||
labelTranslation: 'answerTypes.yes.label',
|
||||
icon: 'glyphicon glyphicon-thumbs-up',
|
||||
label: 'Yes'
|
||||
},
|
||||
{
|
||||
type: 'yes',
|
||||
labelTranslation: 'answerTypes.yes.label',
|
||||
icon: 'glyphicon glyphicon-thumbs-up',
|
||||
label: 'Yes'
|
||||
}
|
||||
]
|
||||
});
|
||||
let user2 = this.server.create('user', {
|
||||
creationDate: '2015-08-01T00:00:00.000Z',
|
||||
encryptionKey,
|
||||
name: 'Peter',
|
||||
selections: [
|
||||
{
|
||||
type: 'no',
|
||||
labelTranslation: 'answerTypes.no.label',
|
||||
icon: 'glyphicon glyphicon-thumbs-down',
|
||||
label: 'No'
|
||||
},
|
||||
{
|
||||
type: 'yes',
|
||||
labelTranslation: 'answerTypes.yes.label',
|
||||
icon: 'glyphicon glyphicon-thumbs-up',
|
||||
label: 'Yes'
|
||||
}
|
||||
]
|
||||
});
|
||||
let poll = this.server.create('poll', {
|
||||
let usersData = [
|
||||
{
|
||||
creationDate: '2015-01-01T00:00:00.000Z',
|
||||
encryptionKey,
|
||||
name: 'Maximilian',
|
||||
selections: [
|
||||
{
|
||||
type: 'yes',
|
||||
labelTranslation: 'answerTypes.yes.label',
|
||||
icon: 'glyphicon glyphicon-thumbs-up',
|
||||
label: 'Yes'
|
||||
},
|
||||
{
|
||||
type: 'yes',
|
||||
labelTranslation: 'answerTypes.yes.label',
|
||||
icon: 'glyphicon glyphicon-thumbs-up',
|
||||
label: 'Yes'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
creationDate: '2015-08-01T00:00:00.000Z',
|
||||
encryptionKey,
|
||||
name: 'Peter',
|
||||
selections: [
|
||||
{
|
||||
type: 'no',
|
||||
labelTranslation: 'answerTypes.no.label',
|
||||
icon: 'glyphicon glyphicon-thumbs-down',
|
||||
label: 'No'
|
||||
},
|
||||
{
|
||||
type: 'yes',
|
||||
labelTranslation: 'answerTypes.yes.label',
|
||||
icon: 'glyphicon glyphicon-thumbs-up',
|
||||
label: 'Yes'
|
||||
}
|
||||
]
|
||||
},
|
||||
];
|
||||
let pollData = {
|
||||
answers: [
|
||||
{
|
||||
type: 'yes',
|
||||
|
@ -173,8 +177,8 @@ module('Acceptance | view evaluation', function(hooks) {
|
|||
{ title: 'second option' }
|
||||
],
|
||||
pollType: 'MakeAPoll',
|
||||
users: [user1, user2]
|
||||
});
|
||||
};
|
||||
let poll = this.server.create('poll', assign(pollData, { users: usersData.map((_) => this.server.create('user', _)) }));
|
||||
|
||||
await visit(`/poll/${poll.id}/evaluation?encryptionKey=${encryptionKey}`);
|
||||
assert.equal(currentRouteName(), 'poll.evaluation');
|
||||
|
@ -189,25 +193,25 @@ module('Acceptance | view evaluation', function(hooks) {
|
|||
'second option',
|
||||
'options are evaluated correctly'
|
||||
);
|
||||
assert.ok(
|
||||
findAll('.user-selections-table').length,
|
||||
'has a table showing user selections'
|
||||
);
|
||||
|
||||
assert.deepEqual(
|
||||
findAll('.user-selections-table thead th').toArray().map((el) => el.textContent.trim()),
|
||||
['', 'first option', 'second option'],
|
||||
PollEvaluationPage.options.map((_) => _.label),
|
||||
['first option', 'second option'],
|
||||
'dates are used as table headers'
|
||||
);
|
||||
assert.deepEqual(
|
||||
findAll('.user-selections-table tbody tr:nth-child(1) td').toArray().map((el) => el.textContent.trim()),
|
||||
['Maximilian', 'Yes', 'Yes'],
|
||||
'answers shown in table are correct for first user'
|
||||
);
|
||||
assert.deepEqual(
|
||||
findAll('.user-selections-table tbody tr:nth-child(2) td').toArray().map((el) => el.textContent.trim()),
|
||||
['Peter', 'No', 'Yes'],
|
||||
'answers shown in table are correct for second user'
|
||||
PollEvaluationPage.participants.map((_) => _.name), usersData.map((_) => _.name),
|
||||
'users are listed in participants table with their names'
|
||||
);
|
||||
usersData.forEach((user) => {
|
||||
let participant = PollEvaluationPage.participants.filterBy('name', user.name)[0];
|
||||
assert.deepEqual(
|
||||
participant.selections.map((_) => _.answer),
|
||||
user.selections.map((_) => t(_.labelTranslation).toString()),
|
||||
`answers are shown for user ${user.name} in participants table`
|
||||
);
|
||||
});
|
||||
|
||||
assert.equal(
|
||||
find('.last-participation').textContent.trim(),
|
||||
t('poll.evaluation.lastParticipation', {
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { isEmpty } from '@ember/utils';
|
||||
import { findAll, fillIn, click } from '@ember/test-helpers';
|
||||
import { findAll, fillIn, click, settled } from '@ember/test-helpers';
|
||||
|
||||
export default async function(name, selections) {
|
||||
if (!isEmpty(name)) {
|
||||
|
@ -18,4 +18,6 @@ export default async function(name, selections) {
|
|||
}
|
||||
|
||||
await click('.participation button[type="submit"]');
|
||||
|
||||
await settled();
|
||||
}
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import EmberObject from '@ember/object';
|
||||
import { module, test } from 'qunit';
|
||||
import { setupRenderingTest } from 'ember-qunit';
|
||||
import { render } from '@ember/test-helpers';
|
||||
|
@ -13,66 +12,17 @@ module('Integration | Component | poll evaluation chart', function(hooks) {
|
|||
});
|
||||
|
||||
test('it renders', async function(assert) {
|
||||
this.set('options', [
|
||||
EmberObject.create({
|
||||
formatted: 'Thursday, January 1, 2015',
|
||||
title: moment('2015-01-01'),
|
||||
hasTime: false
|
||||
}),
|
||||
EmberObject.create({
|
||||
formatted: 'Monday, February 2, 2015',
|
||||
title: moment('2015-02-02'),
|
||||
hasTime: false
|
||||
}),
|
||||
EmberObject.create({
|
||||
formatted: 'Tuesday, March 3, 2015 1:00 AM',
|
||||
title: moment('2015-03-03T01:00'),
|
||||
hasTime: true
|
||||
}),
|
||||
EmberObject.create({
|
||||
formatted: 'Tuesday, March 3, 2015 11:00 AM',
|
||||
title: moment('2015-03-03T11:00'),
|
||||
hasTime: true
|
||||
})
|
||||
]);
|
||||
this.set('answerType', 'YesNoMaybe');
|
||||
this.set('users', [
|
||||
EmberObject.create({
|
||||
id: 1,
|
||||
selections: [
|
||||
EmberObject.create({
|
||||
type: 'yes'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'yes'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'maybe'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'no'
|
||||
})
|
||||
]
|
||||
}),
|
||||
EmberObject.create({
|
||||
id: 2,
|
||||
selections: [
|
||||
EmberObject.create({
|
||||
type: 'yes'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'maybe'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'no'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'no'
|
||||
})
|
||||
]
|
||||
})
|
||||
]);
|
||||
await render(hbs`{{poll-evaluation-chart options=options answerType=answerType users=users}}`);
|
||||
this.set('poll', {
|
||||
answerType: 'YesNoMaybe',
|
||||
options: [
|
||||
{ title: '2015-01-01' },
|
||||
],
|
||||
users: [
|
||||
{ selections: [{ type: 'yes' }]},
|
||||
],
|
||||
});
|
||||
await render(hbs`{{poll-evaluation-chart poll=poll}}`);
|
||||
|
||||
assert.dom('canvas').exists('it renders a canvas element');
|
||||
});
|
||||
});
|
||||
|
|
|
@ -1,12 +1,23 @@
|
|||
import PageObject from 'ember-cli-page-object';
|
||||
import {
|
||||
attribute,
|
||||
collection,
|
||||
create,
|
||||
text,
|
||||
} from 'ember-cli-page-object';
|
||||
import { definition as Poll } from 'croodle/tests/pages/poll';
|
||||
import { defaultsForApplication } from 'croodle/tests/pages/defaults';
|
||||
import { assign } from '@ember/polyfills';
|
||||
|
||||
const { assign } = Object;
|
||||
const {
|
||||
text
|
||||
} = PageObject;
|
||||
|
||||
export default PageObject.create(assign({}, defaultsForApplication, Poll, {
|
||||
preferedOptions: text('.best-options .best-option-value', { multiple: true })
|
||||
export default create(assign({}, defaultsForApplication, Poll, {
|
||||
options: collection('[data-test-table-of="participants"] thead tr:last-child th:not(:first-child)', {
|
||||
label: text(''),
|
||||
}),
|
||||
preferedOptions: text('.best-options .best-option-value', { multiple: true }),
|
||||
participants: collection('[data-test-table-of="participants"] [data-test-participant]', {
|
||||
name: text('[data-test-value-for="name"]'),
|
||||
selections: collection('[data-test-is-selection-cell]', {
|
||||
answer: text(''),
|
||||
option: attribute('data-test-value-for', ''),
|
||||
}),
|
||||
}),
|
||||
}));
|
||||
|
|
|
@ -67,21 +67,22 @@ module('Unit | Component | poll evaluation chart', function(hooks) {
|
|||
]
|
||||
})
|
||||
];
|
||||
let currentLocale = 'en';
|
||||
let momentLongDayFormat = moment.localeData(currentLocale)
|
||||
let momentLongDayFormat = moment.localeData('en')
|
||||
.longDateFormat('LLLL')
|
||||
.replace(
|
||||
moment.localeData(currentLocale).longDateFormat('LT'), '')
|
||||
moment.localeData('en').longDateFormat('LT'), '')
|
||||
.trim();
|
||||
let component = this.owner.factoryFor('component:poll-evaluation-chart').create({
|
||||
answerType: 'YesNoMaybe',
|
||||
currentLocale,
|
||||
isFindADate: true,
|
||||
momentLongDayFormat,
|
||||
options,
|
||||
poll: {
|
||||
answerType: 'YesNoMaybe',
|
||||
isFindADate: true,
|
||||
options,
|
||||
users,
|
||||
},
|
||||
timezone: 'Asia/Hong_Kong',
|
||||
users
|
||||
});
|
||||
|
||||
const data = component.get('data');
|
||||
assert.deepEqual(
|
||||
data.labels,
|
||||
|
@ -126,45 +127,48 @@ module('Unit | Component | poll evaluation chart', function(hooks) {
|
|||
})
|
||||
];
|
||||
let component = this.owner.factoryFor('component:poll-evaluation-chart').create({
|
||||
answerType: 'YesNoMaybe',
|
||||
options,
|
||||
users: [
|
||||
EmberObject.create({
|
||||
id: 1,
|
||||
selections: [
|
||||
EmberObject.create({
|
||||
type: 'yes'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'yes'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'maybe'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'no'
|
||||
})
|
||||
]
|
||||
}),
|
||||
EmberObject.create({
|
||||
id: 2,
|
||||
selections: [
|
||||
EmberObject.create({
|
||||
type: 'yes'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'maybe'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'no'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'no'
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
poll: {
|
||||
answerType: 'YesNoMaybe',
|
||||
options,
|
||||
users: [
|
||||
EmberObject.create({
|
||||
id: 1,
|
||||
selections: [
|
||||
EmberObject.create({
|
||||
type: 'yes'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'yes'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'maybe'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'no'
|
||||
})
|
||||
]
|
||||
}),
|
||||
EmberObject.create({
|
||||
id: 2,
|
||||
selections: [
|
||||
EmberObject.create({
|
||||
type: 'yes'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'maybe'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'no'
|
||||
}),
|
||||
EmberObject.create({
|
||||
type: 'no'
|
||||
})
|
||||
]
|
||||
})
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
const data = component.get('data');
|
||||
assert.deepEqual(
|
||||
data.labels,
|
||||
|
@ -244,21 +248,22 @@ module('Unit | Component | poll evaluation chart', function(hooks) {
|
|||
]
|
||||
})
|
||||
];
|
||||
let currentLocale = 'en';
|
||||
let momentLongDayFormat = moment.localeData(currentLocale)
|
||||
let momentLongDayFormat = moment.localeData('en')
|
||||
.longDateFormat('LLLL')
|
||||
.replace(
|
||||
moment.localeData(currentLocale).longDateFormat('LT'), '')
|
||||
moment.localeData('en').longDateFormat('LT'), '')
|
||||
.trim();
|
||||
let component = this.owner.factoryFor('component:poll-evaluation-chart').create({
|
||||
answerType: 'YesNoMaybe',
|
||||
currentLocale,
|
||||
isFindADate: true,
|
||||
momentLongDayFormat,
|
||||
options,
|
||||
poll: {
|
||||
answerType: 'YesNoMaybe',
|
||||
isFindADate: true,
|
||||
options,
|
||||
users,
|
||||
},
|
||||
timezone: 'Asia/Hong_Kong',
|
||||
users
|
||||
});
|
||||
|
||||
const data = component.get('data');
|
||||
assert.deepEqual(
|
||||
data.labels,
|
||||
|
@ -294,14 +299,15 @@ module('Unit | Component | poll evaluation chart', function(hooks) {
|
|||
})
|
||||
];
|
||||
let component = this.owner.factoryFor('component:poll-evaluation-chart').create({
|
||||
answerType: 'YesNoMaybe',
|
||||
currentLocale: 'en',
|
||||
isFindADate: true,
|
||||
momentLongDayFormat: '',
|
||||
options,
|
||||
timezone: undefined,
|
||||
users: []
|
||||
poll: {
|
||||
answerType: 'YesNoMaybe',
|
||||
isFindADate: true,
|
||||
options,
|
||||
users: [],
|
||||
},
|
||||
});
|
||||
|
||||
const data = component.get('data');
|
||||
assert.deepEqual(
|
||||
data.labels,
|
||||
|
|
90
tests/unit/models/poll-test.js
Normal file
90
tests/unit/models/poll-test.js
Normal file
|
@ -0,0 +1,90 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Model | poll', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('#hasTimes: true if all options have times', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let poll = store.createRecord('poll', {
|
||||
pollType: 'FindADate',
|
||||
options: [
|
||||
{ title: '2019-01-01T00:00:00.000Z' },
|
||||
{ title: '2019-01-01T10:00:00.000Z' },
|
||||
],
|
||||
});
|
||||
|
||||
assert.ok(poll.hasTimes);
|
||||
});
|
||||
|
||||
test('#hasTimes: true if at least one option has times', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let poll = store.createRecord('poll', {
|
||||
options: [
|
||||
{ title: '2019-01-01T00:00:00.000Z' },
|
||||
{ title: '2019-01-02' },
|
||||
],
|
||||
pollType: 'FindADate',
|
||||
});
|
||||
assert.ok(poll.hasTimes);
|
||||
});
|
||||
|
||||
test('#hasTimes: false if no option has times', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let poll = store.createRecord('poll', {
|
||||
options: [
|
||||
{ title: '2019-01-01' },
|
||||
{ title: '2019-01-02' },
|
||||
],
|
||||
pollType: 'FindADate',
|
||||
});
|
||||
assert.notOk(poll.hasTimes);
|
||||
});
|
||||
|
||||
test('#hasTimes: false if poll is not FindADate', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let poll = store.createRecord('poll', {
|
||||
options: [
|
||||
{ title: 'abc' },
|
||||
{ title: 'def' },
|
||||
],
|
||||
pollType: 'MakeAPoll',
|
||||
});
|
||||
assert.notOk(poll.hasTimes);
|
||||
});
|
||||
|
||||
test('#isFindADate', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let poll = store.createRecord('poll', {
|
||||
pollType: 'FindADate',
|
||||
});
|
||||
|
||||
assert.ok(poll.isFindADate);
|
||||
assert.notOk(poll.isMakeAPoll);
|
||||
});
|
||||
|
||||
test('#isFreeText', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let poll = store.createRecord('poll', {
|
||||
answerType: 'FreeText',
|
||||
});
|
||||
|
||||
assert.ok(poll.isFreeText);
|
||||
|
||||
poll.set('answerType', 'YesNo');
|
||||
assert.notOk(poll.isFreeText);
|
||||
|
||||
poll.set('answerType', 'YesNoMaybe');
|
||||
assert.notOk(poll.isFreeText);
|
||||
});
|
||||
|
||||
test('#isMakeAPoll', function(assert) {
|
||||
let store = this.owner.lookup('service:store');
|
||||
let poll = store.createRecord('poll', {
|
||||
pollType: 'MakeAPoll',
|
||||
});
|
||||
|
||||
assert.ok(poll.isMakeAPoll);
|
||||
assert.notOk(poll.isFindADate);
|
||||
});
|
||||
});
|
11
tests/unit/routes/poll/evaluation-test.js
Normal file
11
tests/unit/routes/poll/evaluation-test.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | poll/evaluation', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:poll/evaluation');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
11
tests/unit/routes/poll/participation-test.js
Normal file
11
tests/unit/routes/poll/participation-test.js
Normal file
|
@ -0,0 +1,11 @@
|
|||
import { module, test } from 'qunit';
|
||||
import { setupTest } from 'ember-qunit';
|
||||
|
||||
module('Unit | Route | poll/participation', function(hooks) {
|
||||
setupTest(hooks);
|
||||
|
||||
test('it exists', function(assert) {
|
||||
let route = this.owner.lookup('route:poll/participation');
|
||||
assert.ok(route);
|
||||
});
|
||||
});
|
|
@ -5336,11 +5336,6 @@ flat-cache@^1.2.1:
|
|||
graceful-fs "^4.1.2"
|
||||
write "^0.2.1"
|
||||
|
||||
floatthead@^2.1.2:
|
||||
version "2.1.3"
|
||||
resolved "https://registry.yarnpkg.com/floatthead/-/floatthead-2.1.3.tgz#3e959c51de19672bc75af41be35d538a27e9b7eb"
|
||||
integrity sha512-PglU8Oy6XHyjjZ2g1mu2iZIPIkcf+IkKpB6g12hVY6bXn+2lQj9+BOGMpaMgDKPTfawTAUQKtp7/UMcVs8q/eg==
|
||||
|
||||
follow-redirects@^1.0.0:
|
||||
version "1.6.0"
|
||||
resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.6.0.tgz#d12452c031e8c67eb6637d861bfc7a8090167933"
|
||||
|
|
Loading…
Reference in a new issue