spec-js/observables/dom/webSocket-spec.js
"use strict";
var chai_1 = require('chai');
var sinon = require('sinon');
var Rx = require('../../../dist/cjs/Rx');
var Observable = Rx.Observable;
/** @test {webSocket} */
describe('Observable.webSocket', function () {
var __ws;
function setupMockWebSocket() {
MockWebSocket.clearSockets();
__ws = __root__.WebSocket;
__root__.WebSocket = MockWebSocket;
}
function teardownMockWebSocket() {
__root__.WebSocket = __ws;
MockWebSocket.clearSockets();
}
beforeEach(function () {
setupMockWebSocket();
});
afterEach(function () {
teardownMockWebSocket();
});
it('should send and receive messages', function () {
var messageReceived = false;
var subject = Observable.webSocket('ws://mysocket');
subject.next('ping');
subject.subscribe(function (x) {
chai_1.expect(x).to.equal('pong');
messageReceived = true;
});
var socket = MockWebSocket.lastSocket;
chai_1.expect(socket.url).to.equal('ws://mysocket');
socket.open();
chai_1.expect(socket.lastMessageSent).to.equal('ping');
socket.triggerMessage(JSON.stringify('pong'));
chai_1.expect(messageReceived).to.be.true;
subject.unsubscribe();
});
it('should allow the user to chain operators', function () {
var messageReceived = false;
var subject = Observable.webSocket('ws://mysocket');
subject
.map(function (x) { return x + '?'; })
.map(function (x) { return x + '!'; })
.map(function (x) { return x + '!'; })
.subscribe(function (x) {
chai_1.expect(x).to.equal('pong?!!');
messageReceived = true;
});
var socket = MockWebSocket.lastSocket;
socket.open();
socket.triggerMessage(JSON.stringify('pong'));
chai_1.expect(messageReceived).to.be.true;
subject.unsubscribe();
});
it('receive multiple messages', function () {
var expected = ['what', 'do', 'you', 'do', 'with', 'a', 'drunken', 'sailor?'];
var results = [];
var subject = Observable.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));
});
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 = Observable.webSocket('ws://mysocket');
expected.forEach(function (x) {
subject.next(x);
});
var socket = MockWebSocket.lastSocket;
chai_1.expect(socket).not.exist;
subject.subscribe();
socket = MockWebSocket.lastSocket;
chai_1.expect(socket.sent.length).to.equal(0);
socket.open();
chai_1.expect(socket.sent.length).to.equal(expected.length);
subject.unsubscribe();
});
it('should send messages immediately if already open', function () {
var subject = Observable.webSocket('ws://mysocket');
subject.subscribe();
var socket = MockWebSocket.lastSocket;
socket.open();
subject.next('avast!');
chai_1.expect(socket.lastMessageSent).to.equal('avast!');
subject.next('ye swab!');
chai_1.expect(socket.lastMessageSent).to.equal('ye swab!');
subject.unsubscribe();
});
it('should close the socket when completed', function () {
var subject = Observable.webSocket('ws://mysocket');
subject.subscribe();
var socket = MockWebSocket.lastSocket;
socket.open();
chai_1.expect(socket.readyState).to.equal(1); // open
sinon.spy(socket, 'close');
chai_1.expect(socket.close).not.have.been.called;
subject.complete();
chai_1.expect(socket.close).have.been.called;
chai_1.expect(socket.readyState).to.equal(3); // closed
subject.unsubscribe();
socket.close.restore();
});
it('should close the socket with a code and a reason when errored', function () {
var subject = Observable.webSocket('ws://mysocket');
subject.subscribe();
var socket = MockWebSocket.lastSocket;
socket.open();
sinon.spy(socket, 'close');
chai_1.expect(socket.close).not.have.been.called;
subject.error({ code: 1337, reason: 'Too bad, so sad :(' });
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 = Observable.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();
chai_1.expect(socket2).not.to.equal(socket1);
chai_1.expect(socket2.lastMessageSent).to.equal('a mariner yer not. yarrr.');
subject.unsubscribe();
});
it('should allow resubscription after closure via error', function () {
var subject = Observable.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();
chai_1.expect(socket2).not.to.equal(socket1);
chai_1.expect(socket2.lastMessageSent).to.equal('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 = Observable.webSocket('ws://mysocket');
subject.subscribe(function (x) {
result = x;
});
var socket = MockWebSocket.lastSocket;
socket.open();
socket.triggerMessage(JSON.stringify(expected));
chai_1.expect(result).to.deep.equal(expected);
subject.unsubscribe();
});
describe('with a config object', function () {
it('should send and receive messages', function () {
var messageReceived = false;
var subject = Observable.webSocket({ url: 'ws://mysocket' });
subject.next('ping');
subject.subscribe(function (x) {
chai_1.expect(x).to.equal('pong');
messageReceived = true;
});
var socket = MockWebSocket.lastSocket;
chai_1.expect(socket.url).to.equal('ws://mysocket');
socket.open();
chai_1.expect(socket.lastMessageSent).to.equal('ping');
socket.triggerMessage(JSON.stringify('pong'));
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 = Observable.webSocket({
url: 'ws://mysocket',
protocol: 'someprotocol'
});
subject.subscribe();
var socket = MockWebSocket.lastSocket;
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 = Observable.webSocket({
url: 'ws://mysocket',
binaryType: 'blob'
});
subject.subscribe();
var socket = MockWebSocket.lastSocket;
chai_1.expect(socket.binaryType).to.equal('blob');
subject.unsubscribe();
});
it('should take a resultSelector', function () {
var results = [];
var subject = Observable.webSocket({
url: 'ws://mysocket',
resultSelector: 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);
});
chai_1.expect(results).to.deep.equal(['ahoy!', 'yarr!', 'shove off!']);
subject.unsubscribe();
});
it('if the resultSelector fails it should go down the error path', function () {
var subject = Observable.webSocket({
url: 'ws://mysocket',
resultSelector: function (e) {
throw new Error('I am a bad error');
}
});
subject.subscribe(function (x) {
chai_1.expect(x).to.equal('this should not happen');
}, function (err) {
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 = Observable.webSocket({
url: 'ws://mysocket',
closingObserver: {
next: function (x) {
calls++;
chai_1.expect(x).to.be.an('undefined');
}
}
});
subject.subscribe();
var socket = MockWebSocket.lastSocket;
socket.open();
chai_1.expect(calls).to.equal(0);
subject.complete();
chai_1.expect(calls).to.equal(1);
subject.subscribe();
socket = MockWebSocket.lastSocket;
socket.open();
subject.error({ code: 1337 });
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 = Observable.webSocket({
url: 'ws://mysocket',
closeObserver: {
next: function (e) {
closes.push(e);
}
}
});
subject.subscribe();
var socket = MockWebSocket.lastSocket;
socket.open();
chai_1.expect(closes.length).to.equal(0);
socket.triggerClose(expected[0]);
chai_1.expect(closes.length).to.equal(1);
subject.subscribe(null, function (err) {
chai_1.expect(err).to.equal(expected[1]);
});
socket = MockWebSocket.lastSocket;
socket.open();
socket.triggerClose(expected[1]);
chai_1.expect(closes.length).to.equal(2);
chai_1.expect(closes[0]).to.equal(expected[0]);
chai_1.expect(closes[1]).to.equal(expected[1]);
subject.unsubscribe();
});
it('should handle constructor errors', function () {
var subject = Observable.webSocket({
url: 'bad_url',
WebSocketCtor: function (url, protocol) {
throw new Error("connection refused");
}
});
subject.subscribe(function (x) {
chai_1.expect(x).to.equal('this should not happen');
}, function (err) {
chai_1.expect(err).to.be.an('error', 'connection refused');
});
subject.unsubscribe();
});
});
describe('multiplex', function () {
it('should be retryable', function () {
var results = [];
var subject = Observable.webSocket('ws://websocket');
var source = subject.multiplex(function () {
return { sub: 'foo' };
}, function () {
return { unsub: 'foo' };
}, function (value) {
return value.name === 'foo';
});
source
.retry(1)
.map(function (x) { return x.value; })
.take(2)
.subscribe(function (x) {
results.push(x);
});
var socket = MockWebSocket.lastSocket;
socket.open();
chai_1.expect(socket.lastMessageSent).to.deep.equal({ sub: 'foo' });
socket.triggerClose({ wasClean: false }); // Bad connection
var socket2 = MockWebSocket.lastSocket;
chai_1.expect(socket2).not.to.equal(socket);
socket2.open();
chai_1.expect(socket2.lastMessageSent).to.deep.equal({ sub: 'foo' });
socket2.triggerMessage(JSON.stringify({ name: 'foo', value: 'test' }));
socket2.triggerMessage(JSON.stringify({ name: 'foo', value: 'this' }));
chai_1.expect(results).to.deep.equal(['test', 'this']);
});
it('should be repeatable', function () {
var results = [];
var subject = Observable.webSocket('ws://websocket');
var source = subject.multiplex(function () {
return { sub: 'foo' };
}, function () {
return { unsub: 'foo' };
}, function (value) {
return value.name === 'foo';
});
source
.repeat(2)
.map(function (x) { return x.value; })
.subscribe(function (x) {
results.push(x);
});
var socket = MockWebSocket.lastSocket;
socket.open();
chai_1.expect(socket.lastMessageSent).to.deep.equal({ 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;
chai_1.expect(socket2).not.to.equal(socket, 'a new socket was not created');
socket2.open();
chai_1.expect(socket2.lastMessageSent).to.deep.equal({ 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 });
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 = Observable.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();
chai_1.expect(socket.lastMessageSent).to.deep.equal({ 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));
});
chai_1.expect(results).to.deep.equal([1, 2, 4, 5]);
sinon.spy(socket, 'close');
sub.unsubscribe();
chai_1.expect(socket.lastMessageSent).to.deep.equal({ unsub: 'foo' });
chai_1.expect(socket.close).have.been.called;
socket.close.restore();
});
it('should keep the same socket for multiple multiplex subscriptions', function () {
var socketSubject = Rx.Observable.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'; })
.takeWhile(function (req) { return !req.complete; })
.subscribe(function () { return results.push('A next'); }, function (e) { return results.push('A error ' + e); }, 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(function () { return results.push('B next'); }, function (e) { return results.push('B error ' + e); }, function () { return results.push('B complete'); });
// Setup socket and send messages
var socket = MockWebSocket.lastSocket;
socket.open();
socketMessages.forEach(function (msg, i) {
if (i === 1) {
sub1.unsubscribe();
chai_1.expect(socketSubject.socket).to.equal(socket);
}
socket.triggerMessage(JSON.stringify(msg));
});
socket.triggerClose({ wasClean: true });
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 = Rx.Observable.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'; })
.takeWhile(function (req) { return !req.complete; })
.subscribe(function () { return results.push('A next'); }, function (e) { return results.push('A error ' + e); }, function () { return results.push('A complete'); });
socketSubject.multiplex(function () { return 'no-op'; }, function () { return results.push('B unsub'); }, function (req) { return req.id === 'B'; })
.takeWhile(function (req) { return !req.complete; })
.subscribe(function () { return results.push('B next'); }, function (e) { return results.push('B error ' + e); }, function () { return results.push('B complete'); });
// Setup socket and send messages
var socket = MockWebSocket.lastSocket;
socket.open();
socketMessages.forEach(function (msg) {
socket.triggerMessage(JSON.stringify(msg));
});
chai_1.expect(results).to.deep.equal([
'A next',
'B next',
'A complete',
'A unsub',
'B next',
'B complete',
'B unsub',
]);
});
});
});
var MockWebSocket = (function () {
function MockWebSocket(url, protocol) {
this.url = url;
this.protocol = protocol;
this.sent = [];
this.handlers = {};
this.readyState = 0;
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: true,
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: true,
configurable: true
});
MockWebSocket.prototype.triggerClose = function (e) {
this.readyState = 3;
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 = 1;
this.trigger('open', {});
};
MockWebSocket.prototype.close = function (code, reason) {
if (this.readyState < 2) {
this.readyState = 2;
this.closeCode = code;
this.closeReason = reason;
this.triggerClose({ wasClean: true });
}
};
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