JezK
Edit File: webSocket-spec.js
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var chai_1 = require("chai"); var sinon = require("sinon"); var webSocket_1 = require("rxjs/webSocket"); var operators_1 = require("rxjs/operators"); var root = (typeof globalThis !== 'undefined' && globalThis) || (typeof self !== 'undefined' && self) || global; var WebSocketState; (function (WebSocketState) { WebSocketState[WebSocketState["CONNECTING"] = 0] = "CONNECTING"; WebSocketState[WebSocketState["OPEN"] = 1] = "OPEN"; WebSocketState[WebSocketState["CLOSING"] = 2] = "CLOSING"; WebSocketState[WebSocketState["CLOSED"] = 3] = "CLOSED"; })(WebSocketState || (WebSocketState = {})); describe('webSocket', function () { var __ws; function setupMockWebSocket() { __ws = root.WebSocket; root.WebSocket = MockWebSocket; } function teardownMockWebSocket() { root.WebSocket = __ws; MockWebSocket.clearSockets(); } describe('basic behavior', function () { beforeEach(function () { setupMockWebSocket(); }); afterEach(function () { teardownMockWebSocket(); }); it('should send and receive messages', function () { var messageReceived = false; var subject = (0, webSocket_1.webSocket)('ws://mysocket'); subject.next('ping'); subject.subscribe(function (x) { (0, chai_1.expect)(x).to.equal('pong'); messageReceived = true; }); var socket = MockWebSocket.lastSocket; (0, chai_1.expect)(socket.url).to.equal('ws://mysocket'); socket.open(); (0, chai_1.expect)(socket.lastMessageSent).to.equal(JSON.stringify('ping')); socket.triggerMessage(JSON.stringify('pong')); (0, chai_1.expect)(messageReceived).to.be.true; subject.unsubscribe(); }); it('should allow use of operators and subscribe', function () { var subject = (0, webSocket_1.webSocket)('ws://mysocket'); var results = []; subject.pipe((0, operators_1.map)(function (x) { return x + '!'; })) .subscribe(function (x) { return results.push(x); }); MockWebSocket.lastSocket.triggerMessage(JSON.stringify('ngconf 2018 bug')); (0, chai_1.expect)(results).to.deep.equal(['ngconf 2018 bug!']); }); it('receive multiple messages', function () { var expected = ['what', 'do', 'you', 'do', 'with', 'a', 'drunken', 'sailor?']; var results = []; var subject = (0, webSocket_1.webSocket)('ws://mysocket'); subject.subscribe(function (x) { results.push(x); }); var socket = MockWebSocket.lastSocket; socket.open(); expected.forEach(function (x) { socket.triggerMessage(JSON.stringify(x)); }); (0, chai_1.expect)(results).to.deep.equal(expected); subject.unsubscribe(); }); it('should queue messages prior to subscription', function () { var expected = ['make', 'him', 'walk', 'the', 'plank']; var subject = (0, webSocket_1.webSocket)('ws://mysocket'); expected.forEach(function (x) { subject.next(x); }); var socket = MockWebSocket.lastSocket; (0, chai_1.expect)(socket).not.exist; subject.subscribe(); socket = MockWebSocket.lastSocket; (0, chai_1.expect)(socket.sent.length).to.equal(0); socket.open(); (0, chai_1.expect)(socket.sent.length).to.equal(expected.length); subject.unsubscribe(); }); it('should send messages immediately if already open', function () { var subject = (0, webSocket_1.webSocket)('ws://mysocket'); subject.subscribe(); var socket = MockWebSocket.lastSocket; socket.open(); subject.next('avast!'); (0, chai_1.expect)(socket.lastMessageSent).to.equal(JSON.stringify('avast!')); subject.next('ye swab!'); (0, chai_1.expect)(socket.lastMessageSent).to.equal(JSON.stringify('ye swab!')); subject.unsubscribe(); }); it('should close the socket when completed', function () { var subject = (0, webSocket_1.webSocket)('ws://mysocket'); subject.subscribe(); var socket = MockWebSocket.lastSocket; socket.open(); (0, chai_1.expect)(socket.readyState).to.equal(WebSocketState.OPEN); sinon.spy(socket, 'close'); (0, chai_1.expect)(socket.close).not.have.been.called; subject.complete(); (0, chai_1.expect)(socket.close).have.been.called; (0, chai_1.expect)(socket.readyState).to.equal(WebSocketState.CLOSING); socket.triggerClose({ wasClean: true }); (0, chai_1.expect)(socket.readyState).to.equal(WebSocketState.CLOSED); subject.unsubscribe(); socket.close.restore(); }); it('should close the socket when unsubscribed before socket open', function () { var subject = (0, webSocket_1.webSocket)('ws://mysocket'); subject.subscribe(); subject.unsubscribe(); var socket = MockWebSocket.lastSocket; sinon.spy(socket, 'close'); socket.open(); (0, chai_1.expect)(socket.close).have.been.called; (0, chai_1.expect)(socket.readyState).to.equal(WebSocketState.CLOSING); socket.close.restore(); }); it('should close the socket when subscription is cancelled before socket open', function () { var subject = (0, webSocket_1.webSocket)('ws://mysocket'); var subscription = subject.subscribe(); subscription.unsubscribe(); var socket = MockWebSocket.lastSocket; sinon.spy(socket, 'close'); socket.open(); (0, chai_1.expect)(socket.close).have.been.called; (0, chai_1.expect)(socket.readyState).to.equal(WebSocketState.CLOSING); socket.close.restore(); }); it('should close the socket when unsubscribed while connecting', function () { var subject = (0, webSocket_1.webSocket)('ws://mysocket'); subject.subscribe(); var socket = MockWebSocket.lastSocket; sinon.spy(socket, 'close'); subject.unsubscribe(); (0, chai_1.expect)(socket.close).have.been.called; (0, chai_1.expect)(socket.readyState).to.equal(WebSocketState.CLOSING); socket.close.restore(); }); it('should close the socket when subscription is cancelled while connecting', function () { var subject = (0, webSocket_1.webSocket)('ws://mysocket'); var subscription = subject.subscribe(); var socket = MockWebSocket.lastSocket; sinon.spy(socket, 'close'); subscription.unsubscribe(); (0, chai_1.expect)(socket.close).have.been.called; (0, chai_1.expect)(socket.readyState).to.equal(WebSocketState.CLOSING); socket.close.restore(); }); it('should close a socket that opens before the previous socket has closed', function () { var subject = (0, webSocket_1.webSocket)('ws://mysocket'); var subscription = subject.subscribe(); var socket = MockWebSocket.lastSocket; sinon.spy(socket, 'close'); subscription.unsubscribe(); (0, chai_1.expect)(socket.close).have.been.called; (0, chai_1.expect)(socket.readyState).to.equal(WebSocketState.CLOSING); var subscription2 = subject.subscribe(); var socket2 = MockWebSocket.lastSocket; sinon.spy(socket2, 'close'); socket2.open(); (0, chai_1.expect)(socket2.readyState).to.equal(WebSocketState.OPEN); socket.triggerClose({ wasClean: true }); (0, chai_1.expect)(socket.readyState).to.equal(WebSocketState.CLOSED); (0, chai_1.expect)(socket2.close).have.not.been.called; subscription2.unsubscribe(); (0, chai_1.expect)(socket2.close).have.been.called; (0, chai_1.expect)(socket2.readyState).to.equal(WebSocketState.CLOSING); socket.close.restore(); }); it('should close the socket with a code and a reason when errored', function () { var subject = (0, webSocket_1.webSocket)('ws://mysocket'); subject.subscribe(); var socket = MockWebSocket.lastSocket; socket.open(); sinon.spy(socket, 'close'); (0, chai_1.expect)(socket.close).not.have.been.called; subject.error({ code: 1337, reason: 'Too bad, so sad :(' }); (0, chai_1.expect)(socket.close).have.been.calledWith(1337, 'Too bad, so sad :('); subject.unsubscribe(); socket.close.restore(); }); it('should allow resubscription after closure via complete', function () { var subject = (0, webSocket_1.webSocket)('ws://mysocket'); subject.subscribe(); var socket1 = MockWebSocket.lastSocket; socket1.open(); subject.complete(); subject.next('a mariner yer not. yarrr.'); subject.subscribe(); var socket2 = MockWebSocket.lastSocket; socket2.open(); (0, chai_1.expect)(socket2).not.to.equal(socket1); (0, chai_1.expect)(socket2.lastMessageSent).to.equal(JSON.stringify('a mariner yer not. yarrr.')); subject.unsubscribe(); }); it('should allow resubscription after closure via error', function () { var subject = (0, webSocket_1.webSocket)('ws://mysocket'); subject.subscribe(); var socket1 = MockWebSocket.lastSocket; socket1.open(); subject.error({ code: 1337 }); subject.next('yo-ho! yo-ho!'); subject.subscribe(); var socket2 = MockWebSocket.lastSocket; socket2.open(); (0, chai_1.expect)(socket2).not.to.equal(socket1); (0, chai_1.expect)(socket2.lastMessageSent).to.equal(JSON.stringify('yo-ho! yo-ho!')); subject.unsubscribe(); }); it('should have a default resultSelector that parses message data as JSON', function () { var result; var expected = { mork: 'shazbot!' }; var subject = (0, webSocket_1.webSocket)('ws://mysocket'); subject.subscribe(function (x) { result = x; }); var socket = MockWebSocket.lastSocket; socket.open(); socket.triggerMessage(JSON.stringify(expected)); (0, chai_1.expect)(result).to.deep.equal(expected); subject.unsubscribe(); }); }); describe('with a config object', function () { beforeEach(function () { setupMockWebSocket(); }); afterEach(function () { teardownMockWebSocket(); }); it('should send and receive messages', function () { var messageReceived = false; var subject = (0, webSocket_1.webSocket)({ url: 'ws://mysocket' }); subject.next('ping'); subject.subscribe(function (x) { (0, chai_1.expect)(x).to.equal('pong'); messageReceived = true; }); var socket = MockWebSocket.lastSocket; (0, chai_1.expect)(socket.url).to.equal('ws://mysocket'); socket.open(); (0, chai_1.expect)(socket.lastMessageSent).to.equal(JSON.stringify('ping')); socket.triggerMessage(JSON.stringify('pong')); (0, chai_1.expect)(messageReceived).to.be.true; subject.unsubscribe(); }); it('should take a protocol and set it properly on the web socket', function () { var subject = (0, webSocket_1.webSocket)({ url: 'ws://mysocket', protocol: 'someprotocol' }); subject.subscribe(); var socket = MockWebSocket.lastSocket; (0, chai_1.expect)(socket.protocol).to.equal('someprotocol'); subject.unsubscribe(); }); it('should take a binaryType and set it properly on the web socket', function () { var subject = (0, webSocket_1.webSocket)({ url: 'ws://mysocket', binaryType: 'blob' }); subject.subscribe(); var socket = MockWebSocket.lastSocket; (0, chai_1.expect)(socket.binaryType).to.equal('blob'); subject.unsubscribe(); }); it('should take a deserializer', function () { var results = []; var subject = (0, webSocket_1.webSocket)({ url: 'ws://mysocket', deserializer: function (e) { return e.data + '!'; } }); subject.subscribe(function (x) { results.push(x); }); var socket = MockWebSocket.lastSocket; socket.open(); ['ahoy', 'yarr', 'shove off'].forEach(function (x) { socket.triggerMessage(x); }); (0, chai_1.expect)(results).to.deep.equal(['ahoy!', 'yarr!', 'shove off!']); subject.unsubscribe(); }); it('if the deserializer fails it should go down the error path', function () { var subject = (0, webSocket_1.webSocket)({ url: 'ws://mysocket', deserializer: function (e) { throw new Error('I am a bad error'); } }); subject.subscribe({ next: function (x) { (0, chai_1.expect)(x).to.equal('this should not happen'); }, error: function (err) { (0, chai_1.expect)(err).to.be.an('error', 'I am a bad error'); } }); var socket = MockWebSocket.lastSocket; socket.open(); socket.triggerMessage('weee!'); subject.unsubscribe(); }); it('should accept a closingObserver', function () { var calls = 0; var subject = (0, webSocket_1.webSocket)({ url: 'ws://mysocket', closingObserver: { next: function (x) { calls++; (0, chai_1.expect)(x).to.be.an('undefined'); } } }); subject.subscribe(); var socket = MockWebSocket.lastSocket; socket.open(); (0, chai_1.expect)(calls).to.equal(0); subject.complete(); (0, chai_1.expect)(calls).to.equal(1); subject.subscribe(); socket = MockWebSocket.lastSocket; socket.open(); subject.error({ code: 1337 }); (0, chai_1.expect)(calls).to.equal(2); subject.unsubscribe(); }); it('should accept a closeObserver', function () { var expected = [{ wasClean: true }, { wasClean: false }]; var closes = []; var subject = (0, webSocket_1.webSocket)({ url: 'ws://mysocket', closeObserver: { next: function (e) { closes.push(e); } } }); subject.subscribe(); var socket = MockWebSocket.lastSocket; socket.open(); (0, chai_1.expect)(closes.length).to.equal(0); socket.triggerClose(expected[0]); (0, chai_1.expect)(closes.length).to.equal(1); subject.subscribe({ error: function (err) { (0, chai_1.expect)(err).to.equal(expected[1]); } }); socket = MockWebSocket.lastSocket; socket.open(); socket.triggerClose(expected[1]); (0, chai_1.expect)(closes.length).to.equal(2); (0, chai_1.expect)(closes[0]).to.equal(expected[0]); (0, chai_1.expect)(closes[1]).to.equal(expected[1]); subject.unsubscribe(); }); it('should handle constructor errors', function () { var subject = (0, webSocket_1.webSocket)({ url: 'bad_url', WebSocketCtor: function (url, protocol) { throw new Error("connection refused"); } }); subject.subscribe({ next: function (x) { (0, chai_1.expect)(x).to.equal('this should not happen'); }, error: function (err) { (0, chai_1.expect)(err).to.be.an('error', 'connection refused'); } }); subject.unsubscribe(); }); }); describe('multiplex', function () { beforeEach(function () { setupMockWebSocket(); }); afterEach(function () { teardownMockWebSocket(); }); it('should be retryable', function () { var results = []; var subject = (0, webSocket_1.webSocket)('ws://websocket'); var source = subject.multiplex(function () { return ({ sub: 'foo' }); }, function () { return ({ unsub: 'foo' }); }, function (value) { return value.name === 'foo'; }); source.pipe((0, operators_1.retry)(1), (0, operators_1.map)(function (x) { return x.value; }), (0, operators_1.take)(2)).subscribe(function (x) { results.push(x); }); var socket = MockWebSocket.lastSocket; socket.open(); (0, chai_1.expect)(socket.lastMessageSent).to.deep.equal(JSON.stringify({ sub: 'foo' })); socket.triggerClose({ wasClean: false }); var socket2 = MockWebSocket.lastSocket; (0, chai_1.expect)(socket2).not.to.equal(socket); socket2.open(); (0, chai_1.expect)(socket2.lastMessageSent).to.deep.equal(JSON.stringify({ sub: 'foo' })); socket2.triggerMessage(JSON.stringify({ name: 'foo', value: 'test' })); socket2.triggerMessage(JSON.stringify({ name: 'foo', value: 'this' })); (0, chai_1.expect)(results).to.deep.equal(['test', 'this']); }); it('should be repeatable', function () { var results = []; var subject = (0, webSocket_1.webSocket)('ws://websocket'); var source = subject.multiplex(function () { return ({ sub: 'foo' }); }, function () { return ({ unsub: 'foo' }); }, function (value) { return value.name === 'foo'; }); source .pipe((0, operators_1.repeat)(2), (0, operators_1.map)(function (x) { return x.value; })) .subscribe(function (x) { results.push(x); }); var socket = MockWebSocket.lastSocket; socket.open(); (0, chai_1.expect)(socket.lastMessageSent).to.deep.equal(JSON.stringify({ sub: 'foo' }), 'first multiplexed sub'); socket.triggerMessage(JSON.stringify({ name: 'foo', value: 'test' })); socket.triggerMessage(JSON.stringify({ name: 'foo', value: 'this' })); socket.triggerClose({ wasClean: true }); var socket2 = MockWebSocket.lastSocket; (0, chai_1.expect)(socket2).not.to.equal(socket, 'a new socket was not created'); socket2.open(); (0, chai_1.expect)(socket2.lastMessageSent).to.deep.equal(JSON.stringify({ sub: 'foo' }), 'second multiplexed sub'); socket2.triggerMessage(JSON.stringify({ name: 'foo', value: 'test' })); socket2.triggerMessage(JSON.stringify({ name: 'foo', value: 'this' })); socket2.triggerClose({ wasClean: true }); (0, chai_1.expect)(results).to.deep.equal(['test', 'this', 'test', 'this'], 'results were not equal'); }); it('should multiplex over the webSocket', function () { var results = []; var subject = (0, webSocket_1.webSocket)('ws://websocket'); var source = subject.multiplex(function () { return ({ sub: 'foo' }); }, function () { return ({ unsub: 'foo' }); }, function (value) { return value.name === 'foo'; }); var sub = source.subscribe(function (x) { results.push(x.value); }); var socket = MockWebSocket.lastSocket; socket.open(); (0, chai_1.expect)(socket.lastMessageSent).to.deep.equal(JSON.stringify({ sub: 'foo' })); [1, 2, 3, 4, 5].map(function (x) { return { name: x % 3 === 0 ? 'bar' : 'foo', value: x }; }).forEach(function (x) { socket.triggerMessage(JSON.stringify(x)); }); (0, chai_1.expect)(results).to.deep.equal([1, 2, 4, 5]); sinon.spy(socket, 'close'); sub.unsubscribe(); (0, chai_1.expect)(socket.lastMessageSent).to.deep.equal(JSON.stringify({ unsub: 'foo' })); (0, chai_1.expect)(socket.close).have.been.called; socket.close.restore(); }); it('should keep the same socket for multiple multiplex subscriptions', function () { var socketSubject = (0, webSocket_1.webSocket)({ url: 'ws://mysocket' }); var results = []; var socketMessages = [ { id: 'A' }, { id: 'B' }, { id: 'A' }, { id: 'B' }, { id: 'B' }, ]; var sub1 = socketSubject.multiplex(function () { return 'no-op'; }, function () { return results.push('A unsub'); }, function (req) { return req.id === 'A'; }).pipe((0, operators_1.takeWhile)(function (req) { return !req.complete; })) .subscribe({ next: function () { return results.push('A next'); }, error: function (e) { return results.push('A error ' + e); }, complete: function () { return results.push('A complete'); } }); socketSubject.multiplex(function () { return 'no-op'; }, function () { return results.push('B unsub'); }, function (req) { return req.id === 'B'; }) .subscribe({ next: function () { return results.push('B next'); }, error: function (e) { return results.push('B error ' + e); }, complete: function () { return results.push('B complete'); } }); var socket = MockWebSocket.lastSocket; socket.open(); socketMessages.forEach(function (msg, i) { if (i === 1) { sub1.unsubscribe(); (0, chai_1.expect)(socketSubject._socket).to.equal(socket); } socket.triggerMessage(JSON.stringify(msg)); }); socket.triggerClose({ wasClean: true }); (0, chai_1.expect)(results).to.deep.equal([ 'A next', 'A unsub', 'B next', 'B next', 'B next', 'B complete', 'B unsub', ]); }); it('should not close the socket until all subscriptions complete', function () { var socketSubject = (0, webSocket_1.webSocket)({ url: 'ws://mysocket' }); var results = []; var socketMessages = [ { id: 'A' }, { id: 'B' }, { id: 'A', complete: true }, { id: 'B' }, { id: 'B', complete: true }, ]; socketSubject.multiplex(function () { return 'no-op'; }, function () { return results.push('A unsub'); }, function (req) { return req.id === 'A'; }).pipe((0, operators_1.takeWhile)(function (req) { return !req.complete; })).subscribe({ next: function () { return results.push('A next'); }, error: function (e) { return results.push('A error ' + e); }, complete: function () { return results.push('A complete'); } }); socketSubject.multiplex(function () { return 'no-op'; }, function () { return results.push('B unsub'); }, function (req) { return req.id === 'B'; }).pipe((0, operators_1.takeWhile)(function (req) { return !req.complete; })).subscribe({ next: function () { return results.push('B next'); }, error: function (e) { return results.push('B error ' + e); }, complete: function () { return results.push('B complete'); } }); var socket = MockWebSocket.lastSocket; socket.open(); socketMessages.forEach(function (msg) { socket.triggerMessage(JSON.stringify(msg)); }); (0, chai_1.expect)(results).to.deep.equal([ 'A next', 'B next', 'A complete', 'A unsub', 'B next', 'B complete', 'B unsub', ]); }); }); describe('node constructor', function () { it('should send and receive messages', function () { var messageReceived = false; var subject = (0, webSocket_1.webSocket)({ url: 'ws://mysocket', WebSocketCtor: MockWebSocket }); subject.next('ping'); subject.subscribe(function (x) { (0, chai_1.expect)(x).to.equal('pong'); messageReceived = true; }); var socket = MockWebSocket.lastSocket; (0, chai_1.expect)(socket.url).to.equal('ws://mysocket'); socket.open(); (0, chai_1.expect)(socket.lastMessageSent).to.equal(JSON.stringify('ping')); socket.triggerMessage(JSON.stringify('pong')); (0, chai_1.expect)(messageReceived).to.be.true; subject.unsubscribe(); }); it('should handle constructor errors if no WebSocketCtor', function () { (0, chai_1.expect)(function () { var subject = (0, webSocket_1.webSocket)({ url: 'ws://mysocket' }); }).to.throw('no WebSocket constructor can be found'); }); }); }); var MockWebSocket = (function () { function MockWebSocket(url, protocol) { this.url = url; this.protocol = protocol; this.sent = []; this.handlers = {}; this.readyState = WebSocketState.CONNECTING; MockWebSocket.sockets.push(this); } Object.defineProperty(MockWebSocket, "lastSocket", { get: function () { var socket = MockWebSocket.sockets; var length = socket.length; return length > 0 ? socket[length - 1] : undefined; }, enumerable: false, configurable: true }); MockWebSocket.clearSockets = function () { MockWebSocket.sockets.length = 0; }; MockWebSocket.prototype.send = function (data) { this.sent.push(data); }; Object.defineProperty(MockWebSocket.prototype, "lastMessageSent", { get: function () { var sent = this.sent; var length = sent.length; return length > 0 ? sent[length - 1] : undefined; }, enumerable: false, configurable: true }); MockWebSocket.prototype.triggerClose = function (e) { this.readyState = WebSocketState.CLOSED; this.trigger('close', e); }; MockWebSocket.prototype.triggerMessage = function (data) { var messageEvent = { data: data, origin: 'mockorigin', ports: undefined, source: root, }; this.trigger('message', messageEvent); }; MockWebSocket.prototype.open = function () { this.readyState = WebSocketState.OPEN; this.trigger('open', {}); }; MockWebSocket.prototype.close = function (code, reason) { if (this.readyState < WebSocketState.CLOSING) { this.readyState = WebSocketState.CLOSING; this.closeCode = code; this.closeReason = reason; } }; MockWebSocket.prototype.trigger = function (name, e) { if (this['on' + name]) { this['on' + name](e); } var lookup = this.handlers[name]; if (lookup) { for (var i = 0; i < lookup.length; i++) { lookup[i](e); } } }; MockWebSocket.sockets = []; return MockWebSocket; }()); //# sourceMappingURL=webSocket-spec.js.map