const { joinBlocker, autoAllocate, cleanGender } = require('../src/utils/allocation'); const person = (id, registrationId, gender = null) => ({ id, name: id, registrationId, gender }); const groupsOf = (result) => { const out = {}; for (const p of result.placements) (out[p.groupId] = out[p.groupId] || []).push(p.attendeeId); return out; }; describe('cleanGender', () => { test('accepts male/female in any case, blank as null', () => { expect(cleanGender('Male')).toBe('male'); expect(cleanGender('')).toBeNull(); expect(() => cleanGender('other')).toThrow(); }); }); describe('joinBlocker', () => { test('capacity', () => { expect(joinBlocker({ name: 'Cabin 1', capacity: 2 }, 1, [person('a', 'r')])).toBeNull(); expect(joinBlocker({ name: 'Cabin 1', capacity: 2 }, 2, [person('a', 'r')])).toMatch(/full/); }); test('gender', () => { expect(joinBlocker({ name: 'Boys', gender: 'male' }, 0, [person('a', 'r', 'male')])).toBeNull(); expect(joinBlocker({ name: 'Boys', gender: 'male' }, 0, [person('a', 'r', 'female')])).toMatch(/males only/); expect(joinBlocker({ name: 'Boys', gender: 'male' }, 0, [person('a', 'r')])).toMatch(/no gender recorded/); }); }); describe('autoAllocate', () => { test('fills groups evenly', () => { const groups = [{ id: 'g1', name: 'A' }, { id: 'g2', name: 'B' }]; const attendees = ['a', 'b', 'c', 'd'].map(id => person(id, id)); const r = autoAllocate({ groups, attendees }); const g = groupsOf(r); expect(g.g1.length).toBe(2); expect(g.g2.length).toBe(2); expect(r.unplaced).toEqual([]); }); test('respects capacity and reports who could not be placed', () => { const groups = [{ id: 'g1', name: 'A', capacity: 1 }]; const r = autoAllocate({ groups, attendees: [person('a', 'r1'), person('b', 'r2')] }); expect(r.placements.length).toBe(1); expect(r.unplaced.length).toBe(1); }); test('respects gender', () => { const groups = [{ id: 'boys', name: 'Boys', gender: 'male' }, { id: 'girls', name: 'Girls', gender: 'female' }]; const r = autoAllocate({ groups, attendees: [person('a', 'r1', 'male'), person('b', 'r2', 'female'), person('c', 'r3')] }); expect(groupsOf(r)).toEqual({ boys: ['a'], girls: ['b'] }); expect(r.unplaced).toEqual(['c']); }); test('keeps a registration together when a group can take them all', () => { const groups = [{ id: 'g1', name: 'A', capacity: 3 }, { id: 'g2', name: 'B', capacity: 3 }]; const attendees = [person('s1', 'fam'), person('s2', 'fam'), person('x', 'solo')]; const g = groupsOf(autoAllocate({ groups, attendees, mode: 'together' })); const famGroup = Object.keys(g).find(k => g[k].includes('s1')); expect(g[famGroup]).toContain('s2'); }); test('places siblings one by one when no group fits them all', () => { const groups = [{ id: 'boys', name: 'Boys', gender: 'male' }, { id: 'girls', name: 'Girls', gender: 'female' }]; const r = autoAllocate({ groups, attendees: [person('bro', 'fam', 'male'), person('sis', 'fam', 'female')], mode: 'together' }); expect(groupsOf(r)).toEqual({ boys: ['bro'], girls: ['sis'] }); }); test('split mode spreads a registration across groups', () => { const groups = [{ id: 'g1', name: 'A' }, { id: 'g2', name: 'B' }]; const attendees = [person('f1', 'friends'), person('f2', 'friends')]; const g = groupsOf(autoAllocate({ groups, attendees, mode: 'split' })); expect(g.g1.length).toBe(1); expect(g.g2.length).toBe(1); }); test('counts people already placed and leaves them where they are', () => { const groups = [{ id: 'g1', name: 'A', capacity: 2 }, { id: 'g2', name: 'B', capacity: 2 }]; const attendees = [person('a', 'r1'), person('b', 'r2'), person('c', 'r3')]; const r = autoAllocate({ groups, attendees, assigned: new Map([['a', 'g1'], ['b', 'g1']]) }); expect(r.placements).toEqual([{ attendeeId: 'c', groupId: 'g2' }]); }); });