Files
hope-events/backend/tests/kids.test.js
T

152 lines
6.4 KiB
JavaScript

const kids = require('../src/utils/kids');
describe('pickup codes', () => {
test('are 4 characters from the alphabet without 0/O/1/I', () => {
for (let i = 0; i < 500; i++) {
const c = kids.randomCode();
expect(c).toHaveLength(4);
expect(c).toMatch(/^[A-HJ-NP-Z2-9]{4}$/);
}
});
test('typed or scanned codes are cleaned up', () => {
expect(kids.normaliseCode('k7p2')).toBe('K7P2');
expect(kids.normaliseCode(' K7 P2 ')).toBe('K7P2');
expect(kids.normaliseCode('KIDS-K7P2')).toBe('K7P2');
expect(kids.normaliseCode('k7-p2')).toBe('K7P2');
});
test('anything that cannot be a code is null', () => {
expect(kids.normaliseCode('')).toBeNull();
expect(kids.normaliseCode('K7P')).toBeNull();
expect(kids.normaliseCode('K7P22')).toBeNull();
expect(kids.normaliseCode('K0P2')).toBeNull(); // no zero
expect(kids.normaliseCode('KOP2')).toBeNull(); // no O
expect(kids.normaliseCode('K1P2')).toBeNull();
});
});
describe('ages and rooms', () => {
const on = new Date('2026-11-01T08:00:00Z');
test('age in whole years, birthday counted on the day', () => {
expect(kids.ageOn('2021-11-01', on)).toBe(5);
expect(kids.ageOn('2021-11-02', on)).toBe(4);
expect(kids.ageOn(null, on)).toBeNull();
});
const rooms = [
{ id: 'all', name: 'Everyone', minAge: null, maxAge: null, order: 0 },
{ id: 'tod', name: 'Toddlers', minAge: 1, maxAge: 3, order: 1, capacity: 2 },
{ id: 'jun', name: 'Juniors', minAge: 4, maxAge: 7, order: 2 },
];
test('fitsRoom respects the range; no age fits anywhere', () => {
expect(kids.fitsRoom(rooms[1], 2)).toBe(true);
expect(kids.fitsRoom(rooms[1], 4)).toBe(false);
expect(kids.fitsRoom(rooms[1], null)).toBe(true);
});
test('suggests the narrowest room that fits', () => {
expect(kids.suggestRoom(rooms, 2).id).toBe('tod');
expect(kids.suggestRoom(rooms, 5).id).toBe('jun');
expect(kids.suggestRoom(rooms, 11).id).toBe('all');
});
test('skips full rooms', () => {
expect(kids.suggestRoom(rooms, 2, { tod: 2 }).id).toBe('all');
});
test('without an age, the first room in order', () => {
expect(kids.suggestRoom(rooms, null).id).toBe('all');
});
test('nothing fits → null', () => {
expect(kids.suggestRoom([rooms[1]], 9)).toBeNull();
});
});
describe('room status', () => {
test('ratio: leaders needed and over ratio', () => {
expect(kids.roomStatus({ ratio: 5 }, 6, 1)).toMatchObject({ leadersNeeded: 2, overRatio: true });
expect(kids.roomStatus({ ratio: 5 }, 5, 1)).toMatchObject({ leadersNeeded: 1, overRatio: false });
expect(kids.roomStatus({ ratio: 5 }, 0, 0)).toMatchObject({ leadersNeeded: 0, overRatio: false });
});
test('no ratio is never over ratio; capacity makes it full', () => {
expect(kids.roomStatus({ capacity: 3 }, 3, 0)).toMatchObject({ full: true, overRatio: false });
expect(kids.roomStatus({}, 30, 0)).toMatchObject({ full: false, overRatio: false });
});
});
describe('sticker text', () => {
test('allergy and medical flags, kept short', () => {
expect(kids.stickerAlert('Peanuts', '')).toBe('ALLERGY: PEANUTS');
expect(kids.stickerAlert('', 'Asthma pump in bag')).toBe('MEDICAL');
expect(kids.stickerAlert('nuts', 'epipen')).toBe('ALLERGY: NUTS · MEDICAL');
expect(kids.stickerAlert('', '')).toBe('');
expect(kids.stickerAlert('a'.repeat(40), '')).toMatch(/^ALLERGY: A{24}…$/);
});
});
describe('occasions', () => {
const single = { scheduleType: 'single', startDate: '2026-11-01T07:00:00Z', endDate: '2026-11-01T10:00:00Z' };
test('a single-date event: its day, only while it is on', () => {
expect(kids.occasionFor(single, [], null, new Date('2026-11-01T06:00:00Z'))).toEqual({ occasion: '2026-11-01', sessionId: null });
expect(() => kids.occasionFor(single, [], null, new Date('2026-11-02T08:00:00Z'))).toThrow(/open on 2026-11-01/);
});
test('a multi-session event: today\'s session', () => {
const ev = { scheduleType: 'sessions', checkInWindow: 'whole_day' };
const sessions = [
{ id: 's1', startsAt: '2026-11-01T07:00:00Z', endsAt: '2026-11-01T09:00:00Z' },
{ id: 's2', startsAt: '2026-11-08T07:00:00Z', endsAt: '2026-11-08T09:00:00Z' },
];
expect(kids.occasionFor(ev, sessions, null, new Date('2026-11-08T06:30:00Z'))).toEqual({ occasion: 's2', sessionId: 's2' });
expect(() => kids.occasionFor(ev, sessions, null, new Date('2026-11-04T08:00:00Z'))).toThrow(/next one/);
});
});
describe('messages', () => {
test('check-in message has the code and rooms, nothing medical', () => {
const text = kids.checkInMessage({ eventTitle: 'Sunday Kids', code: 'K7P2', children: [{ name: 'Mia', room: 'Toddlers' }, { name: 'Ben', room: 'Juniors' }] });
expect(text).toContain('Mia and Ben are checked in at Sunday Kids (Toddlers, Juniors)');
expect(text).toContain('Pickup code: K7P2');
expect(text).toContain('/dashboard/user/kids');
});
test('for an authorised adult, no link to an account', () => {
const text = kids.checkInMessage({ eventTitle: 'Sunday Kids', code: 'K7P2', children: [{ name: 'Mia' }], forParent: false });
expect(text).toContain('Mia is checked in');
expect(text).not.toContain('My account');
});
test('listNames', () => {
expect(kids.listNames(['A'])).toBe('A');
expect(kids.listNames(['A', 'B', 'C'])).toBe('A, B and C');
});
});
describe('children', () => {
test('cleanChild needs a first name and seals the notes', () => {
expect(() => kids.cleanChild({})).toThrow(/first name/);
const data = kids.cleanChild({ firstName: ' Mia ', allergies: 'Peanuts', custodyNotes: 'Not to Mr X' });
expect(data.firstName).toBe('Mia');
expect(data.allergies).toMatch(/^enc:/);
expect(data.custodyNotes).toMatch(/^enc:/);
expect(kids.childView({ id: 'c', ...data }).allergies).toBe('Peanuts');
});
test('a future date of birth is refused', () => {
expect(() => kids.cleanChild({ firstName: 'A', dateOfBirth: '2999-01-01' })).toThrow(/date of birth/);
});
test('custodyChanged only when the text changes', () => {
const existing = kids.cleanChild({ firstName: 'A', custodyNotes: 'x' });
expect(kids.custodyChanged(existing, kids.cleanChild({ custodyNotes: 'x' }, existing))).toBe(false);
expect(kids.custodyChanged(existing, kids.cleanChild({ custodyNotes: 'y' }, existing))).toBe(true);
expect(kids.custodyChanged(existing, kids.cleanChild({ allergies: 'y' }, existing))).toBe(false);
});
});