group-cache.test.ts3.3 KBView on GitHub import {
patchCachedGroup,
reorderCachedGroups,
} from '@/modules/userTasks/utils/group-cache';
/**
* Every group surface renders from one cached `listGroups` payload, so these helpers are what make
* a rename or a reorder land everywhere at once. They take `unknown` because the payload mixes real
* group rows with the synthesized virtual-Misc row (`id: null`) — the cases below pin down that
* Misc survives both operations untouched, which is the thing a careless `.map` would break.
*/
const payload = () => ({
groups: [
{ id: 'a', name: 'Responses needed', color: '#3b82f6', icon: 'Reply', position: 0 },
{ id: 'b', name: 'Follow-ups', color: '#f97316', icon: 'Send', position: 1 },
{ id: 'c', name: 'CRM updates', color: '#14b8a6', icon: 'Database', position: 2 },
{ id: null, name: 'Misc', color: null, icon: null, position: Number.MAX_SAFE_INTEGER },
],
});
describe('reorderCachedGroups', () => {
it('rewrites both array order and position, so List and Board agree', () => {
const next = reorderCachedGroups(payload(), ['c', 'a', 'b']) as ReturnType<typeof payload>;
expect(next.groups.map((g) => g.id)).toEqual(['c', 'a', 'b', null]);
expect(next.groups.map((g) => g.position)).toEqual([0, 1, 2, Number.MAX_SAFE_INTEGER]);
});
it('leaves Misc last and untouched', () => {
const next = reorderCachedGroups(payload(), ['c', 'b', 'a']) as ReturnType<typeof payload>;
const misc = next.groups[next.groups.length - 1];
expect(misc.id).toBeNull();
expect(misc.position).toBe(Number.MAX_SAFE_INTEGER);
});
it('passes a payload it does not understand straight through', () => {
expect(reorderCachedGroups(undefined, ['a'])).toBeUndefined();
expect(reorderCachedGroups({ nope: 1 }, ['a'])).toEqual({ nope: 1 });
});
});
describe('patchCachedGroup', () => {
it('renames only the target group', () => {
const next = patchCachedGroup(payload(), 'b', { name: 'Nudges' }) as ReturnType<typeof payload>;
expect(next.groups.map((g) => g.name)).toEqual([
'Responses needed',
'Nudges',
'CRM updates',
'Misc',
]);
});
it('leaves unlisted fields alone', () => {
const next = patchCachedGroup(payload(), 'b', { name: 'Nudges' }) as ReturnType<typeof payload>;
expect(next.groups[1]).toMatchObject({ color: '#f97316', icon: 'Send', position: 1 });
});
it('clears a colour when explicitly passed null, but not when omitted', () => {
const cleared = patchCachedGroup(payload(), 'a', { color: null }) as ReturnType<typeof payload>;
expect(cleared.groups[0].color).toBeNull();
const untouched = patchCachedGroup(payload(), 'a', { name: 'x' }) as ReturnType<typeof payload>;
expect(untouched.groups[0].color).toBe('#3b82f6');
});
it('cannot match Misc, whose id is null', () => {
// A caller can only ever pass a real group id, but the virtual row is the one thing in the
// payload with no server row behind it — an edit landing on it would be unsaveable.
const next = patchCachedGroup(payload(), 'a', { name: 'x' }) as ReturnType<typeof payload>;
expect(next.groups[3]).toEqual(payload().groups[3]);
});
it('passes a payload it does not understand straight through', () => {
expect(patchCachedGroup(undefined, 'a', { name: 'x' })).toBeUndefined();
});
});