A Feedback step in the event wizard builds a survey that is sent at a chosen time to everyone who checked in (or everyone registered), with personal links and one reminder after 3 days. Answers can be anonymous. Adds rating (1-5 stars) and multiple-choice field types, also usable on attendee forms, and a Feedback summary report with response rate, average ratings, choice breakdowns, comments and event comparison. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
69 lines
3.1 KiB
JavaScript
69 lines
3.1 KiB
JavaScript
const { cleanFields, cleanAnswer, summarise } = require('../src/utils/feedback');
|
|
|
|
describe('cleanFields', () => {
|
|
test('keeps labelled questions in order, tidying choices', () => {
|
|
const out = cleanFields([
|
|
{ type: 'rating', label: ' Overall ', isRequired: true },
|
|
{ type: 'text', label: '' },
|
|
{ type: 'multiple_choice', label: 'Best part', options: ['Worship', ' Talks ', 'Worship', ''] },
|
|
{ type: 'statement', label: 'Thanks!', isRequired: true },
|
|
]);
|
|
expect(out.map(f => [f.type, f.label, f.order])).toEqual([['rating', 'Overall', 0], ['multiple_choice', 'Best part', 1], ['statement', 'Thanks!', 2]]);
|
|
expect(out[1].options).toEqual(['Worship', 'Talks']);
|
|
expect(out[2].isRequired).toBe(false);
|
|
});
|
|
|
|
test('rejects bad forms', () => {
|
|
expect(() => cleanFields([{ type: 'stars', label: 'X' }])).toThrow(/unknown type/);
|
|
expect(() => cleanFields([{ type: 'multiple_choice', label: 'X', options: ['only one'] }])).toThrow(/two choices/);
|
|
expect(() => cleanFields([{ type: 'statement', label: 'Hello' }])).toThrow(/at least one question/);
|
|
});
|
|
});
|
|
|
|
describe('cleanAnswer', () => {
|
|
test('ratings are whole stars from 1 to 5', () => {
|
|
expect(cleanAnswer({ type: 'rating', label: 'R' }, '4')).toBe('4');
|
|
expect(() => cleanAnswer({ type: 'rating', label: 'R' }, '6')).toThrow();
|
|
expect(() => cleanAnswer({ type: 'rating', label: 'R' }, '3.5')).toThrow();
|
|
});
|
|
|
|
test('multiple choice must be one of the options', () => {
|
|
const f = { type: 'multiple_choice', label: 'M', options: ['A', 'B'] };
|
|
expect(cleanAnswer(f, 'B')).toBe('B');
|
|
expect(() => cleanAnswer(f, 'C')).toThrow();
|
|
});
|
|
|
|
test('required questions need an answer; optional ones may be blank', () => {
|
|
expect(() => cleanAnswer({ type: 'text', label: 'Why?', isRequired: true }, ' ')).toThrow(/Please answer/);
|
|
expect(cleanAnswer({ type: 'text', label: 'Why?' }, '')).toBeNull();
|
|
expect(cleanAnswer({ type: 'yes_no', label: 'Again?' }, 'YES')).toBe('yes');
|
|
});
|
|
});
|
|
|
|
describe('summarise', () => {
|
|
const fields = [
|
|
{ id: 'r', type: 'rating', label: 'Overall' },
|
|
{ id: 'm', type: 'multiple_choice', label: 'Best part', options: ['Worship', 'Talks'] },
|
|
{ id: 't', type: 'text', label: 'Comments' },
|
|
{ id: 's', type: 'statement', label: 'Thanks' },
|
|
];
|
|
const answers = [
|
|
{ fieldId: 'r', value: '5' }, { fieldId: 'r', value: '4' }, { fieldId: 'r', value: '4' },
|
|
{ fieldId: 'm', value: 'Talks' }, { fieldId: 'm', value: 'Talks' }, { fieldId: 'm', value: 'Worship' },
|
|
{ fieldId: 't', value: 'Loved it', name: 'Ann' },
|
|
];
|
|
|
|
test('averages ratings with a star distribution', () => {
|
|
const [r] = summarise(fields, answers);
|
|
expect(r.average).toBe(4.3);
|
|
expect(r.distribution.map(d => d.value)).toEqual([0, 0, 0, 2, 1]);
|
|
});
|
|
|
|
test('counts choices and lists text answers; skips statements', () => {
|
|
const out = summarise(fields, answers);
|
|
expect(out.length).toBe(3);
|
|
expect(out[1].counts).toEqual([{ label: 'Worship', value: 1 }, { label: 'Talks', value: 2 }]);
|
|
expect(out[2].texts).toEqual([{ value: 'Loved it', name: 'Ann' }]);
|
|
});
|
|
});
|