Manual Reference Source Test

src/modules/observable.test.js

import Observable from './observable.js';

// eslint-disable-next-line no-console
const consoleLogFunction = console.log;

let sut;

describe('Observable', () => {
  beforeEach(() => {
    sut = new Observable();
    // eslint-disable-next-line no-console
    console.log = function() {};
  });

  afterEach(() => {
    // eslint-disable-next-line no-console
    console.log = consoleLogFunction;
  });

  it('on must register callback upon invocation', () =>{
    const callback = jest.fn();
    sut.on('test', callback);
    expect(sut._observers['test']).toContain(callback);
  });

  it('on must fail when passing undefined callback', () =>{
    expect(() => sut.on('test', null)).toThrow();
  });

  it('on must fail when passing undefined type', () =>{
    expect(() => sut.on(null, new Function())).toThrow();
  });

  it('on must silently ignore registering a callback more than once', () =>{
    const callback = new Function();
    sut.on('test', callback);
    sut.on('test', callback);

    expect(sut._observers['test'].length).toBe(1);
  });

  it('notify must make sure callback exists', () =>{
    sut.notify('test', {});

    expect(sut._observers).not.toContain('test');
  });

  it('notify must invoke all registered callbacks', () =>{
    const callback1 = jest.fn();
    const callback2 = jest.fn();

    sut.on('test', callback1);
    sut.on('test', callback2);

    sut.notify('test', {});

    expect(callback1.mock.calls.length).toBe(1);
    expect(callback2.mock.calls.length).toBe(1);
  });

  it('notify must pass data to callback', () =>{
    const callback = jest.fn();
    const data = {value: 'data'};

    sut.on('test', callback);

    sut.notify('test', data);

    expect(callback.mock.calls[0][0]).toBe(data);
  });

  it('notify must complete notification dispatch despite exceptions in callbacks', () =>{
    const callback = jest.fn();

    sut.on('test', () => {
      throw new Error('Forced exception');
    });
    sut.on('test', callback);

    sut.notify('test', {});

    expect(callback.mock.calls.length).toBe(1);
  });

  it('invoking unregister method must remove callback', () =>{
    sut.on('test', jest.fn())();

    expect(sut._observers['test'].length).toBe(0);
  });

  it('invoking unregister method twice must be silently ignored', () =>{
    const remove = sut.on('test', jest.fn());

    remove();
    remove();

    expect(sut._observers['test'].length).toBe(0);
  });
});