gitlab-org--gitlab-foss/spec/frontend/lib/utils/recurrence_spec.js

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);
});
});
});
});