summaryrefslogtreecommitdiff
path: root/spec/frontend/lib/utils/recurrence_spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'spec/frontend/lib/utils/recurrence_spec.js')
-rw-r--r--spec/frontend/lib/utils/recurrence_spec.js333
1 files changed, 333 insertions, 0 deletions
diff --git a/spec/frontend/lib/utils/recurrence_spec.js b/spec/frontend/lib/utils/recurrence_spec.js
new file mode 100644
index 00000000000..fc22529dffc
--- /dev/null
+++ b/spec/frontend/lib/utils/recurrence_spec.js
@@ -0,0 +1,333 @@
+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);
+ });
+ });
+ });
+});