const { validateAttendees, planLineTickets, lineKey } = require('../src/utils/attendees'); const EVENT = { allowNamedAttendees: true, eventOptions: [ { id: 'kid', name: 'Camper', isMainTicket: true }, { id: 'shirt', name: 'T-shirt', isMainTicket: false }, ], }; const places = (n) => ({ [lineKey('kid', null)]: n }); describe('validateAttendees', () => { test('cleans up names and contact details', () => { const out = validateAttendees(EVENT, places(2), [ { eventOptionId: 'kid', name: ' Sam ', email: ' ', phone: '0821234567' }, { eventOptionId: 'kid', name: 'Alex', email: 'alex@example.com' }, ]); expect(out).toMatchObject([ { eventOptionId: 'kid', variantId: null, name: 'Sam', email: null, phone: '0821234567' }, { eventOptionId: 'kid', variantId: null, name: 'Alex', email: 'alex@example.com', phone: null }, ]); }); test('nothing to check when no attendees are given', () => { expect(validateAttendees({ ...EVENT, allowNamedAttendees: false }, places(1), [])).toEqual([]); }); test('refuses names on events that do not take them', () => { expect(() => validateAttendees({ ...EVENT, allowNamedAttendees: false }, places(1), [{ eventOptionId: 'kid', name: 'Sam' }])).toThrow(/doesn't take attendee names/); }); test('only main tickets can be named', () => { expect(() => validateAttendees(EVENT, { [lineKey('shirt', null)]: 1 }, [{ eventOptionId: 'shirt', name: 'Sam' }])).toThrow(/main tickets/); }); test('needs a name and a valid email', () => { expect(() => validateAttendees(EVENT, places(1), [{ eventOptionId: 'kid', name: ' ' }])).toThrow(/enter a name/); expect(() => validateAttendees(EVENT, places(1), [{ eventOptionId: 'kid', name: 'Sam', email: 'nope' }])).toThrow(/valid email/); }); test('no more names than places, counting names already given', () => { expect(() => validateAttendees(EVENT, places(1), [{ eventOptionId: 'kid', name: 'A' }, { eventOptionId: 'kid', name: 'B' }])).toThrow(/More names/); expect(() => validateAttendees(EVENT, places(3), [{ eventOptionId: 'kid', name: 'C' }], places(2))).not.toThrow(); expect(() => validateAttendees(EVENT, places(2), [{ eventOptionId: 'kid', name: 'C' }], places(2))).toThrow(/More names/); }); }); describe('planLineTickets', () => { const scan = (q = 1) => [{ quantityRedeemed: q }]; test('no attendees: one shared ticket for the whole quantity', () => { expect(planLineTickets({ quantity: 3 }, [], [])).toEqual({ create: [{ attendeeId: null, holderUserId: null, quantity: 3 }], update: [], remove: [] }); }); test('every attendee gets their own ticket; unnamed places share one', () => { const plan = planLineTickets({ quantity: 3 }, [{ id: 'a1', userId: 'u9' }, { id: 'a2', userId: null }], []); expect(plan.create).toEqual([ { attendeeId: 'a1', holderUserId: 'u9', quantity: 1 }, { attendeeId: 'a2', holderUserId: null, quantity: 1 }, { attendeeId: null, holderUserId: null, quantity: 1 }, ]); }); test('naming every place replaces the shared ticket', () => { const plan = planLineTickets({ quantity: 2 }, [{ id: 'a1' }, { id: 'a2' }], [{ id: 't0', attendeeId: null, quantity: 2, usages: [] }]); expect(plan.remove).toEqual(['t0']); expect(plan.create).toHaveLength(2); }); test('a scanned shared ticket is kept and never shrunk below what was used', () => { const plan = planLineTickets({ quantity: 2 }, [{ id: 'a1' }, { id: 'a2' }], [{ id: 't0', attendeeId: null, quantity: 2, usages: scan(1) }]); expect(plan.remove).toEqual([]); expect(plan.update).toEqual([{ id: 't0', data: { quantity: 1 } }]); }); test('removing an attendee removes their unscanned ticket', () => { const plan = planLineTickets({ quantity: 1 }, [{ id: 'a1' }], [ { id: 't1', attendeeId: 'a1', quantity: 1, usages: [] }, { id: 't2', attendeeId: 'gone', quantity: 1, usages: [] }, ]); expect(plan).toEqual({ create: [], update: [], remove: ['t2'] }); }); test("keeps a scanned ticket whose attendee was removed, and counts it as a used place", () => { const plan = planLineTickets({ quantity: 2 }, [{ id: 'a1' }], [ { id: 't1', attendeeId: 'a1', quantity: 1, usages: [] }, { id: 't2', attendeeId: 'gone', quantity: 1, usages: scan() }, ]); expect(plan).toEqual({ create: [], update: [], remove: [] }); }); test('follows the attendee to a newly linked account', () => { const plan = planLineTickets({ quantity: 1 }, [{ id: 'a1', userId: 'u2' }], [{ id: 't1', attendeeId: 'a1', quantity: 1, holderUserId: null, usages: [] }]); expect(plan.update).toEqual([{ id: 't1', data: { holderUserId: 'u2', quantity: 1 } }]); }); test('existing single shared ticket is resized, duplicates dropped', () => { const plan = planLineTickets({ quantity: 4 }, [], [ { id: 't1', attendeeId: null, quantity: 2, usages: [] }, { id: 't2', attendeeId: null, quantity: 2, usages: [] }, ]); expect(plan).toEqual({ create: [], update: [{ id: 't1', data: { quantity: 4 } }], remove: ['t2'] }); }); });