334 lines
8.7 KiB
JavaScript
334 lines
8.7 KiB
JavaScript
![]() |
import { create, free, recall } from '~/lib/utils/recurrence';
|
||
|
|
||
|
const HEX = /[a-f0-9]/i;
|
||
|
const HEX_RE = HEX.source;
|
||
|
const UUIDV4 = new RegExp(
|
||
|
`${HEX_RE}{8}-${HEX_RE}{4}-4${HEX_RE}{3}-[89ab]${HEX_RE}{3}-${HEX_RE}{12}`,
|
||
|
'i',
|
||
|
);
|
||
|
|
||
|
describe('recurrence', () => {
|
||
|
let recurInstance;
|
||
|
let id;
|
||
|
|
||
|
beforeEach(() => {
|
||
|
recurInstance = create();
|
||
|
id = recurInstance.id;
|
||
|
});
|
||
|
|
||
|
afterEach(() => {
|
||
|
id = null;
|
||
|
recurInstance.free();
|
||
|
});
|
||
|
|
||
|
describe('create', () => {
|
||
|
it('returns an object with the correct external api', () => {
|
||
|
expect(recurInstance).toMatchObject(
|
||
|
expect.objectContaining({
|
||
|
id: expect.stringMatching(UUIDV4),
|
||
|
count: 0,
|
||
|
handlers: {},
|
||
|
free: expect.any(Function),
|
||
|
handle: expect.any(Function),
|
||
|
eject: expect.any(Function),
|
||
|
occur: expect.any(Function),
|
||
|
reset: expect.any(Function),
|
||
|
}),
|
||
|
);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('recall', () => {
|
||
|
it('returns a previously created RecurInstance', () => {
|
||
|
expect(recall(id).id).toBe(id);
|
||
|
});
|
||
|
|
||
|
it("returns undefined if the provided UUID doesn't refer to a stored RecurInstance", () => {
|
||
|
expect(recall('1234')).toBeUndefined();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('free', () => {
|
||
|
it('returns true when the RecurInstance exists', () => {
|
||
|
expect(free(id)).toBe(true);
|
||
|
});
|
||
|
|
||
|
it("returns false when the ID doesn't refer to a known RecurInstance", () => {
|
||
|
expect(free('1234')).toBe(false);
|
||
|
});
|
||
|
|
||
|
it('removes the correct RecurInstance from the list of references', () => {
|
||
|
const anotherInstance = create();
|
||
|
|
||
|
expect(recall(id)).toEqual(recurInstance);
|
||
|
expect(recall(anotherInstance.id)).toEqual(anotherInstance);
|
||
|
|
||
|
free(id);
|
||
|
|
||
|
expect(recall(id)).toBeUndefined();
|
||
|
expect(recall(anotherInstance.id)).toEqual(anotherInstance);
|
||
|
|
||
|
anotherInstance.free();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('RecurInstance (`create()` return value)', () => {
|
||
|
it.each`
|
||
|
property | value | alias
|
||
|
${'id'} | ${expect.stringMatching(UUIDV4)} | ${'[a string matching the UUIDv4 specification]'}
|
||
|
${'count'} | ${0} | ${0}
|
||
|
${'handlers'} | ${{}} | ${{}}
|
||
|
`(
|
||
|
'has the correct primitive value $alias for the member `$property` to start',
|
||
|
({ property, value }) => {
|
||
|
expect(recurInstance[property]).toEqual(value);
|
||
|
},
|
||
|
);
|
||
|
|
||
|
describe('id', () => {
|
||
|
it('cannot be changed manually', () => {
|
||
|
expect(() => {
|
||
|
recurInstance.id = 'new-id';
|
||
|
}).toThrow(TypeError);
|
||
|
|
||
|
expect(recurInstance.id).toBe(id);
|
||
|
});
|
||
|
|
||
|
it.each`
|
||
|
method
|
||
|
${'free'}
|
||
|
${'handle'}
|
||
|
${'eject'}
|
||
|
${'occur'}
|
||
|
${'reset'}
|
||
|
`('does not change across any method call - like after `$method`', ({ method }) => {
|
||
|
recurInstance[method]();
|
||
|
|
||
|
expect(recurInstance.id).toBe(id);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('count', () => {
|
||
|
it('cannot be changed manually', () => {
|
||
|
expect(() => {
|
||
|
recurInstance.count = 9999;
|
||
|
}).toThrow(TypeError);
|
||
|
|
||
|
expect(recurInstance.count).toBe(0);
|
||
|
});
|
||
|
|
||
|
it.each`
|
||
|
method
|
||
|
${'free'}
|
||
|
${'handle'}
|
||
|
${'eject'}
|
||
|
${'reset'}
|
||
|
`("doesn't change in unexpected scenarios - like after a call to `$method`", ({ method }) => {
|
||
|
recurInstance[method]();
|
||
|
|
||
|
expect(recurInstance.count).toBe(0);
|
||
|
});
|
||
|
|
||
|
it('increments by one each time `.occur()` is called', () => {
|
||
|
expect(recurInstance.count).toBe(0);
|
||
|
recurInstance.occur();
|
||
|
expect(recurInstance.count).toBe(1);
|
||
|
recurInstance.occur();
|
||
|
expect(recurInstance.count).toBe(2);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('handlers', () => {
|
||
|
it('cannot be changed manually', () => {
|
||
|
const fn = jest.fn();
|
||
|
|
||
|
recurInstance.handle(1, fn);
|
||
|
expect(() => {
|
||
|
recurInstance.handlers = {};
|
||
|
}).toThrow(TypeError);
|
||
|
|
||
|
expect(recurInstance.handlers).toStrictEqual({
|
||
|
1: fn,
|
||
|
});
|
||
|
});
|
||
|
|
||
|
it.each`
|
||
|
method
|
||
|
${'free'}
|
||
|
${'occur'}
|
||
|
${'eject'}
|
||
|
${'reset'}
|
||
|
`("doesn't change in unexpected scenarios - like after a call to `$method`", ({ method }) => {
|
||
|
recurInstance[method]();
|
||
|
|
||
|
expect(recurInstance.handlers).toEqual({});
|
||
|
});
|
||
|
|
||
|
it('adds handlers to the correct slots', () => {
|
||
|
const fn1 = jest.fn();
|
||
|
const fn2 = jest.fn();
|
||
|
|
||
|
recurInstance.handle(100, fn1);
|
||
|
recurInstance.handle(1000, fn2);
|
||
|
|
||
|
expect(recurInstance.handlers).toMatchObject({
|
||
|
100: fn1,
|
||
|
1000: fn2,
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('free', () => {
|
||
|
it('removes itself from recallable memory', () => {
|
||
|
expect(recall(id)).toEqual(recurInstance);
|
||
|
|
||
|
recurInstance.free();
|
||
|
|
||
|
expect(recall(id)).toBeUndefined();
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('handle', () => {
|
||
|
it('adds a handler for the provided count', () => {
|
||
|
const fn = jest.fn();
|
||
|
|
||
|
recurInstance.handle(5, fn);
|
||
|
|
||
|
expect(recurInstance.handlers[5]).toEqual(fn);
|
||
|
});
|
||
|
|
||
|
it("doesn't add any handlers if either the count or behavior aren't provided", () => {
|
||
|
const fn = jest.fn();
|
||
|
|
||
|
recurInstance.handle(null, fn);
|
||
|
// Note that it's not possible to react to something not happening (without timers)
|
||
|
recurInstance.handle(0, fn);
|
||
|
recurInstance.handle(5, null);
|
||
|
|
||
|
expect(recurInstance.handlers).toEqual({});
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('eject', () => {
|
||
|
it('removes the handler assigned to the particular count slot', () => {
|
||
|
recurInstance.handle(1, jest.fn());
|
||
|
|
||
|
expect(recurInstance.handlers[1]).toBeTruthy();
|
||
|
|
||
|
recurInstance.eject(1);
|
||
|
|
||
|
expect(recurInstance.handlers).toEqual({});
|
||
|
});
|
||
|
|
||
|
it("succeeds (or fails gracefully) when the count provided doesn't have a handler assigned", () => {
|
||
|
recurInstance.eject('abc');
|
||
|
recurInstance.eject(1);
|
||
|
|
||
|
expect(recurInstance.handlers).toEqual({});
|
||
|
});
|
||
|
|
||
|
it('makes no changes if no count is provided', () => {
|
||
|
const fn = jest.fn();
|
||
|
|
||
|
recurInstance.handle(1, fn);
|
||
|
|
||
|
recurInstance.eject();
|
||
|
|
||
|
expect(recurInstance.handlers[1]).toStrictEqual(fn);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('occur', () => {
|
||
|
it('increments the .count property by 1', () => {
|
||
|
expect(recurInstance.count).toBe(0);
|
||
|
|
||
|
recurInstance.occur();
|
||
|
|
||
|
expect(recurInstance.count).toBe(1);
|
||
|
});
|
||
|
|
||
|
it('calls the appropriate handlers', () => {
|
||
|
const fn1 = jest.fn();
|
||
|
const fn5 = jest.fn();
|
||
|
const fn10 = jest.fn();
|
||
|
|
||
|
recurInstance.handle(1, fn1);
|
||
|
recurInstance.handle(5, fn5);
|
||
|
recurInstance.handle(10, fn10);
|
||
|
|
||
|
expect(fn1).not.toHaveBeenCalled();
|
||
|
expect(fn5).not.toHaveBeenCalled();
|
||
|
expect(fn10).not.toHaveBeenCalled();
|
||
|
|
||
|
recurInstance.occur();
|
||
|
|
||
|
expect(fn1).toHaveBeenCalledTimes(1);
|
||
|
expect(fn5).not.toHaveBeenCalled();
|
||
|
expect(fn10).not.toHaveBeenCalled();
|
||
|
|
||
|
recurInstance.occur();
|
||
|
recurInstance.occur();
|
||
|
recurInstance.occur();
|
||
|
recurInstance.occur();
|
||
|
|
||
|
expect(fn1).toHaveBeenCalledTimes(1);
|
||
|
expect(fn5).toHaveBeenCalledTimes(1);
|
||
|
expect(fn10).not.toHaveBeenCalled();
|
||
|
|
||
|
recurInstance.occur();
|
||
|
recurInstance.occur();
|
||
|
recurInstance.occur();
|
||
|
recurInstance.occur();
|
||
|
recurInstance.occur();
|
||
|
|
||
|
expect(fn1).toHaveBeenCalledTimes(1);
|
||
|
expect(fn5).toHaveBeenCalledTimes(1);
|
||
|
expect(fn10).toHaveBeenCalledTimes(1);
|
||
|
});
|
||
|
});
|
||
|
|
||
|
describe('reset', () => {
|
||
|
it('resets the count only, by default', () => {
|
||
|
const fn = jest.fn();
|
||
|
|
||
|
recurInstance.handle(3, fn);
|
||
|
recurInstance.occur();
|
||
|
recurInstance.occur();
|
||
|
|
||
|
expect(recurInstance.count).toBe(2);
|
||
|
|
||
|
recurInstance.reset();
|
||
|
|
||
|
expect(recurInstance.count).toBe(0);
|
||
|
expect(recurInstance.handlers).toEqual({ 3: fn });
|
||
|
});
|
||
|
|
||
|
it('also resets the handlers, by specific request', () => {
|
||
|
const fn = jest.fn();
|
||
|
|
||
|
recurInstance.handle(3, fn);
|
||
|
recurInstance.occur();
|
||
|
recurInstance.occur();
|
||
|
|
||
|
expect(recurInstance.count).toBe(2);
|
||
|
|
||
|
recurInstance.reset({ handlersList: true });
|
||
|
|
||
|
expect(recurInstance.count).toBe(0);
|
||
|
expect(recurInstance.handlers).toEqual({});
|
||
|
});
|
||
|
|
||
|
it('leaves the count in place, by request', () => {
|
||
|
recurInstance.occur();
|
||
|
recurInstance.occur();
|
||
|
|
||
|
expect(recurInstance.count).toBe(2);
|
||
|
|
||
|
recurInstance.reset({ currentCount: false });
|
||
|
|
||
|
expect(recurInstance.count).toBe(2);
|
||
|
});
|
||
|
});
|
||
|
});
|
||
|
});
|