Home Reference Source Test Repository

test/ReopeningWebSocket.spec.js

/* global ReopeningWebSocket, sinon */
/* eslint-env mocha */

const TestUrl = 'ws://localhost:9000';

/** @test {ReopeningWebSocket} */
describe('ReopeningWebSocket', function() {
  after(function(done) {
    const ws = new WebSocket(TestUrl);
    ws.addEventListener('open', function() {
      ws.send('shutdown');
      done();
    });
  });

  it('must be used as class', function() {
    expect(function() {
      ReopeningWebSocket();
    }, 'to throw', TypeError);
  });

  describe('extends Standard WebSocket API', function() {
    context('should inherit', function() {
      let ws = new ReopeningWebSocket(TestUrl);

      it('methods', function() {
        expect(ws.close, 'to be defined');
        expect(ws.send, 'to be defined');
      });

      it('attributes', function() {
        expect(ws.binaryType, 'to be defined');
        expect(ws.bufferedAmount, 'to be defined');
        expect(ws.extensions, 'to be defined');
        expect(ws.onerror, 'to be defined');
        expect(ws.onmessage, 'to be defined');
        expect(ws.onopen, 'to be defined');
        expect(ws.protocol, 'to be defined');
        expect(ws.readyState, 'to be defined');
        expect(ws.url, 'to be defined');
      });

      it('constants', function() {
        expect(ReopeningWebSocket.CONNECTING, 'to be defined');
        expect(ReopeningWebSocket.OPEN, 'to be defined');
        expect(ReopeningWebSocket.CLOSING, 'to be defined');
        expect(ReopeningWebSocket.CLOSED, 'to be defined');
      });
    });

    context('should forward native events', function() {
      let ws;

      beforeEach(function() {
        ws = new ReopeningWebSocket(TestUrl);
      });

      afterEach(function() {
        ws.close();
      });

      it('open', function(done) {
        ws.addEventListener('open', function() {
          done();
        });
      });

      it('close', function(done) {
        let closed = false;
        ws.addEventListener('close', function() {
          if (!closed) {
            done();
            closed = true;
          }
        });
        ws.addEventListener('open', function() {
          ws._webSocket.close();
        });
      });

      it('message', function(done) {
        const message = 'Test';
        ws.addEventListener('message', function(e) {
          expect(e.data, 'to equal', message);
          done();
        });
        ws.addEventListener('open', function() {
          ws.send(message);
        });
      });
    });

    /** @test {ReopeningWebSocket#close} */
    describe('#close', function() {
      it('should pass arguments', function(done) {
        const ws = new ReopeningWebSocket(TestUrl);
        const listener = sinon.spy(ws._webSocket, 'close');
        const code = 4000;
        const reason = 'Unit testing';

        ws.addEventListener('close', function() {
          expect(listener.callCount, 'to equal', 1);
          expect(listener.calledWith(code, reason), 'to be true');
          done();
        });

        ws.close(code, reason);
      });
    });
  });

  describe('automatic reopening', function() {
    let ws;
    beforeEach(function(done) {
      ws = new ReopeningWebSocket(TestUrl);
      ws._webSocket.addEventListener('open', function() { done(); });
    });

    afterEach(function() {
      ws.close();
    });

    it('should dispatch reopenattempt event before trying to reopen', function(done) {
      ws.addEventListener('reopenattempt', function(e) {
        expect(e.detail, 'to be a number');
        expect(e.detail, 'to equal', ws.reopenAttempt);

        done();
      });
      ws.reopen();
    });

    it('should dispatch reopen event on reopen', function(done) {
      ws.addEventListener('reopen', function(e) {
        expect(e.detail, 'to be a number');
        expect(e.detail, 'to equal', ws.reopenAttempt);
        done();
      });

      ws._webSocket.close();
    });

    it('should try again if reopen is not possible', function(done) {
      ws.send('close');

      ws.addEventListener('reopen', function(e) {
        expect(e.detail, 'to be greater than', 1);
        expect(ws.reopenAttempt, 'to be greater than', 1);
        done();
      });
    });

    it('should not be enabled if #close was called before', function(done) {
      ws.addEventListener('reopenattempt', function() {
        throw new Error('fail');
      });
      ws.close();

      setTimeout(done, 50);
    });

    it('should be re-enabled when calling #reopen', function(done) {
      let closed = true;
      ws.addEventListener('reopenattempt', function() {
        expect(closed, 'to be false');
        expect(ws._reopeningEnabled, 'to be true');
        done();
      });

      ws.close();
      setTimeout(function() {
        closed = false;
        ws.reopen();
      }, 50);
    });
  });

  describe('EventTarget', function() {
    /** @test {ReopeningWebSocket#addEventListener} */
    describe('#addEventListener', function() {
      it('should accept option "once"', function(done) {
        const ws = new ReopeningWebSocket(TestUrl);
        const reopenListener = sinon.spy();
        const attemptListener = sinon.spy();

        ws.addEventListener('reopen', reopenListener, { once: true });
        ws.addEventListener('reopenattempt', attemptListener, { once: true });

        // Force multiple reopens
        ws.addEventListener('open', function() {
          ws._webSocket.close();
        });

        setTimeout(function() {
          expect(reopenListener.callCount, 'to equal', 1);
          expect(attemptListener.callCount, 'to equal', 1);
          ws.close();
          done();
        }, 200);
      });
    });

    /** @test {ReopeningWebSocket#removeEventListener} */
    describe('#removeEventListener', function() {
      it('should work with WebSocket listeners', function(done) {
        const listener = sinon.spy();
        const ws = new ReopeningWebSocket(TestUrl);
        ws.addEventListener('open', listener);
        ws.removeEventListener('open', listener);

        ws.addEventListener('open', function() {
          expect(listener.callCount, 'to equal', 0);
          ws.close();
          done();
        });
      });

      context('should ignore', function() {
        const ws = new ReopeningWebSocket(TestUrl);

        // Clone listeners
        const old = { reopen: [], reopenattempt: [] };
        Object.assign(old.reopen, ws._listeners.reopen.map(l => Object.assign({}, l)));
        Object.assign(old.reopenattempt, ws._listeners.reopenattempt.map(l => Object.assign({}, l)));

        it('unknown event types', function() {
          ws.removeEventListener('unknown', function() {});
          expect(old, 'to equal', ws._listeners);
        });

        it('unknown listeners', function() {
          ws.removeEventListener('reopen', function() {});
          expect(old, 'to equal', ws._listeners);
        });
      });
    });

    /** @test {ReopeningWebSocket#dispatchEvent} */
    describe('#dispatchEvent', function() {
      it('should ignore unknown events', function() {
        const ws = new ReopeningWebSocket(TestUrl);
        function shallNotThrow() {
          ws.dispatchEvent(new CustomEvent('unknown'));
        }

        expect(shallNotThrow, 'not to throw');
      });
    });

    /** @test {ReopeningWebSocket#onreopen} */
    describe('on[event] properties', function() {
      it('should be called', function(done) {
        const ws = new ReopeningWebSocket(TestUrl);
        const openListener = sinon.spy();
        const attemptListener = sinon.spy();

        ws.onreopen = openListener;
        ws.onreopenattempt = attemptListener;

        ws.addEventListener('reopen', function() {
          expect(openListener.callCount, 'to equal', 1);
          expect(attemptListener.callCount, 'to equal', 1);
          ws.close();
          done();
        });

        ws._webSocket.addEventListener('open', function() {
          ws.reopen();
        });
      });

      context('of WebSocket API', function() {
        it('should be forwarded on reopen', function(done) {
          const ws = new ReopeningWebSocket(TestUrl);
          const oldWs = Object.assign({}, ws._webSocket);
          const listener = sinon.spy();
          ws.onopen = listener;

          ws.addEventListener('reopen', function() {
            expect(oldWs, 'not to equal', ws._webSocket);
            expect(ws.onopen, 'to equal', listener);
            ws.close();
            done();
          });

          ws._webSocket.addEventListener('open', function() {
            ws.reopen();
          });
        });
      });
    });
  });
});