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