spec/lib/sphero.spec.js
"use strict";
var Sphero = lib("sphero");
var Packet = lib("packet"),
SerialPort = lib("adaptors/serialport");
describe("Sphero", function() {
var sphero;
beforeEach(function() {
sphero = new Sphero("/dev/rfcomm0");
});
describe("#constructor", function() {
it("is not ready until #connect is called", function() {
expect(sphero.ready).to.be.false;
});
it("sets @packet to be an instance of Packet", function() {
expect(sphero.packet).to.be.an.instanceOf(Packet);
});
it("sets @connection to be an instance of Loader", function() {
expect(sphero.connection).to.be.an.instanceOf(SerialPort);
});
it("sets @sop2Bitfield to SOP2.both by default", function() {
expect(sphero.sop2Bitfield).to.be.eql(0xFF);
});
it("sets @seqCounter to 0x00", function() {
expect(sphero.seqCounter).to.be.eql(0x00);
});
it("sets responseQueue array to an empty array", function() {
expect(sphero.responseQueue).to.be.eql([]);
});
it("adds core device methods", function() {
expect(sphero.ping).to.be.a("function");
expect(sphero.setDeviceName).to.be.a("function");
});
it("adds sphero device methods", function() {
expect(sphero.setHeading).to.be.a("function");
expect(sphero.setRgbLed).to.be.a("function");
});
it("adds custom device methods", function() {
expect(sphero.color).to.be.a("function");
});
context("when calling Sphero class as a function", function() {
it("throws because Sphero is a constructor", function() {
var spheroClass = Sphero;
expect(spheroClass).to.throw();
});
});
});
describe("connect", function() {
var callback, buffer, packet;
beforeEach(function() {
callback = spy();
buffer = new Buffer([0xFF, 0xFF, 0x00, 0x00, 0x01, 0xFE]);
packet = {
sop1: 0xFF,
sop2: 0xFF,
mrsp: 0x00,
seq: 0x00,
dlen: 0x01,
checksum: 0xFE
};
stub(sphero, "emit");
stub(sphero, "_responseCmd");
stub(sphero, "_execCallback");
stub(sphero.packet, "on");
stub(sphero.packet, "parse");
stub(sphero.packet, "parseResponseData");
spy(sphero.packet, "parseAsyncData");
stub(sphero.connection, "open");
stub(sphero.connection, "onRead");
stub(sphero.connection, "on");
sphero.packet.on.yields();
sphero.packet.parse.returns(packet);
sphero.packet.parseResponseData.returns(packet);
sphero.connection.open.yields();
sphero.connection.on.yields();
sphero.connection.onRead.yields(buffer);
sphero.connect(callback);
});
afterEach(function() {
sphero.emit.restore();
sphero._execCallback.restore();
sphero.packet.on.restore();
sphero.packet.parse.restore();
sphero.packet.parseResponseData.restore();
sphero.connection.open.restore();
sphero.connection.on.restore();
});
it("sets a listener for @packet 'error' event", function() {
expect(sphero.packet.on).to.be.calledOnce;
expect(sphero.packet.on).to.be.calledWith("error");
});
it("emits error on @packet 'error' event", function() {
expect(sphero.packet.on).to.be.calledOnce;
expect(sphero.emit).to.be.calledWith("error");
});
it("calls @connection#open once", function() {
expect(sphero.connection.open).to.be.calledOnce;
});
it("sets @ready to true", function() {
expect(sphero.ready).to.be.true;
});
it("adds listener to @connection onRead", function() {
expect(sphero.connection.onRead).to.be.calledOnce;
});
it("emits data on @connection onRead", function() {
expect(sphero.emit).to.be.calledWith("data", buffer);
});
it("calls @packet.parse passing the buffer", function() {
expect(sphero.packet.parse).to.be.calledOnce;
expect(sphero.packet.parse).to.be.calledWith(buffer);
});
context("with sync responses", function() {
it("emits a response event", function() {
expect(sphero.emit).to.be.calledWith("response", packet);
});
it("executes the corresponding callback", function() {
expect(sphero._execCallback).to.be.calledOnce;
expect(sphero._execCallback).to.be.calledWith(0x00, packet);
});
});
context("with an async response", function() {
beforeEach(function() {
packet.sop2 = 0xFE;
packet.idCode = 0x07;
packet.data = new Buffer([
0x00, 0xFF,
0x00, 0xFE,
0x00, 0xFD,
0x01,
0x00, 0x01,
0x00, 0x02,
0x10,
0x01, 0x02, 0X03, 0x04
]);
sphero.connect(callback);
});
it("emits an async event", function() {
expect(sphero.emit).to.be.calledWith("async", {
axis: 1,
cid: 18,
did: 2,
idCode: 7,
desc: "Collision detected",
event: "collision",
packet: packet,
x: 255,
y: 254,
z: 253,
speed: 16,
xMagnitude: 1,
yMagnitude: 2,
timestamp: 16909060
});
});
it("emits a collision event", function() {
expect(sphero.emit).to.be.calledWith("collision", {
axis: 1,
cid: 18,
did: 2,
idCode: 7,
desc: "Collision detected",
event: "collision",
packet: packet,
x: 255,
y: 254,
z: 253,
speed: 16,
xMagnitude: 1,
yMagnitude: 2,
timestamp: 16909060
});
});
});
it("adds listener on @connection close", function() {
expect(sphero.connection.on).to.be.calledWith("open");
});
it("adds listener on @connection close", function() {
expect(sphero.connection.on).to.be.calledWith("close");
});
it("emits close on @connection close event", function() {
expect(sphero.emit).to.be.calledWith("close");
});
it("emits error on @connection error event", function() {
expect(sphero.emit).to.be.calledWith("error");
});
it("emits ready event when connection setup is completed", function() {
expect(sphero.emit).to.be.calledWith("ready");
});
it("calls the callback once");
it("returns void when callback is not a function", function() {
callback.reset();
sphero.connect();
expect(sphero.connection.open).to.not.return;
expect(callback).to.not.be.called;
});
context("with invalid SOP2", function() {
beforeEach(function() {
sphero.packet.parseResponseData.reset();
sphero.packet.parseAsyncData.reset();
sphero._execCallback.reset();
packet.sop2 = 0xF0;
sphero.packet.parse.returns(packet);
sphero.connect(callback);
});
it("does not call _execCallback", function() {
expect(sphero._execCallback).to.not.be.called;
});
it("does not call @packet.parseResponseData", function() {
expect(sphero.packet.parseResponseData).to.not.be.called;
});
it("does not call @packet.parseAsyncData", function() {
expect(sphero.packet.parseAsyncData).to.not.be.called;
});
});
context("with invalid parsedPayload", function() {
beforeEach(function() {
sphero.packet.parse.returns(null);
sphero.packet.parseResponseData.reset();
sphero.packet.parseAsyncData.reset();
sphero.connect(callback);
sphero.packet.parse.returns(packet);
});
it("does not call _execCallback", function() {
expect(sphero._execCallback).to.be.calledOnce;
});
it("does not call @packet.parseResponseData", function() {
expect(sphero.packet.parseResponseData).to.not.be.called;
});
it("does not call @packet.parseAsyncData", function() {
expect(sphero.packet.parseAsyncData).to.not.be.called;
});
});
});
describe("#disconnect", function() {
var callback;
beforeEach(function() {
callback = spy();
sphero.connection.close = stub();
sphero.disconnect(callback);
});
it("tells the Sphero adaptor to disconnect", function() {
expect(sphero.connection.close).to.be.calledWith(callback);
});
});
describe("#command", function() {
var opts, cmdByteArray, callback;
beforeEach(function() {
opts = {
sop2: 0xFF,
did: 0x00,
cid: 0x01,
seq: 0x01,
data: null,
emitPacketErrors: false
};
cmdByteArray = [0xFF, 0xFF, 0x00, 0x01, 0x01, 0xFE];
callback = spy();
sphero.sop2Bitfield = 0xFF;
stub(sphero.packet, "create");
stub(sphero, "_queuePromise");
stub(sphero.connection, "write");
stub(sphero, "_incSeq").returns(0x01);
sphero.packet.create.returns(cmdByteArray);
sphero.command(0x00, 0x01, null, callback);
});
it("calls @packet#create with params", function() {
expect(sphero.packet.create).to.be.calledOnce;
expect(sphero.packet.create).to.be.calledWith(opts);
});
it("calls #_queuePromise with params (cmdPacket, promise)", function() {
expect(sphero._queuePromise).to.be.calledOnce;
//expect(sphero._queuePromise).to.be.calledWith(cmdByteArray, callback);
});
it("calls @connection#write with param commandPacket", function() {
expect(sphero.connection.write).to.be.calledOnce;
expect(sphero.connection.write).to.be.calledWith(cmdByteArray);
});
});
describe("#_queueCommand", function() {
var resolve, reject, packet;
beforeEach(function() {
resolve = spy();
reject = spy();
sphero.commandQueue = [];
packet = [0xFF, 0xFF, 0x00, 0x01, 0x00, 0x01, 0xFE];
sphero._queueCommand(packet, resolve, reject);
});
it("Adds a command and callback to the @commandQueue", function() {
expect(sphero.commandQueue.length).to.be.eql(1);
expect(sphero.commandQueue)
.to.be.eql([
{ packet: packet, resolver: resolve, rejecter: reject}
]);
});
context("@commandQueue is full del next command in the queue", function() {
var packet2, packet3;
beforeEach(function() {
packet2 = [0xFF, 0xFF, 0x00, 0x04, 0x00, 0x01, 0xFE];
packet2 = [0xFF, 0xFF, 0x00, 0x08, 0x00, 0x01, 0xFE];
for (var i = 1; i < 256; i++) {
sphero._queueCommand(packet2, resolve, reject);
}
sphero._queueCommand(packet3, resolve, reject);
});
it("discards next cmd and ads new cmd at the end", function() {
expect(sphero.commandQueue.length).to.be.eql(256);
expect(sphero.commandQueue[0])
.to.be.eql({ packet: packet2, resolver: resolve, rejecter: reject});
expect(sphero.commandQueue[255])
.to.be.eql({ packet: packet3, resolver: resolve, rejecter: reject});
});
});
});
describe("#_incSeq", function() {
var seq;
beforeEach(function() {
sphero.seqCounter = 10;
seq = sphero._incSeq();
});
it("returns the current @seqCounter value", function() {
expect(seq).to.be.eql(10);
});
it("increments @seqCounter after returning current value", function() {
expect(sphero.seqCounter).to.be.eql(11);
});
context("when @seqCounter is 256", function() {
beforeEach(function() {
sphero.seqCounter = 256;
seq = sphero._incSeq();
});
it("returns 0", function() {
expect(seq).to.be.eql(0);
});
it("resets @seqCounter to 1", function() {
expect(sphero.seqCounter).to.be.eql(1);
});
});
});
describe("#_queuePromise", function() {
var resolver, rejecter, fakeTimers, cmdPacket;
beforeEach(function() {
cmdPacket = new Buffer([0xFF, 0xFD, 0x00, 0x01, 0x04, 0x01, 0xFD]);
sphero.seqCounter = 0;
fakeTimers = sinon.useFakeTimers();
resolver = spy();
rejecter = spy();
stub(sphero, "_execCommand");
sphero._queuePromise(cmdPacket, resolver, rejecter);
});
afterEach(function() {
sphero._execCommand.restore();
fakeTimers.restore();
});
it("triggers the callback with the packet", function() {
var packet = {
sop1: 0xFF,
sop2: 0xFF,
mrsp: 0x00,
seq: 0x00,
dlen: 0x01,
checksum: 0xFE
};
sphero._execCallback(4, packet);
expect(resolver).to.be.calledWith(packet);
});
it("adds the callback to the @responseQueue queue", function() {
expect(sphero.responseQueue[4]).to.not.be.null;
});
it("removes the callback from @responseQueue after 500ms", function() {
fakeTimers.tick(500);
expect(sphero.responseQueue[4]).to.be.null;
});
it("triggers the callback passed", function() {
var error = new Error("Command sync response was lost.");
fakeTimers.tick(500);
expect(rejecter).to.be.calledWith(error);
});
it("calls #_execCommand once", function() {
fakeTimers.tick(500);
expect(sphero._execCommand).to.be.calledOnce;
});
it("triggers #_execCommand if promise is null", function() {
fakeTimers.tick(500);
sphero._queuePromise(cmdPacket);
fakeTimers.tick(500);
expect(sphero._execCommand).to.be.calledTwice;
});
});
describe("#_execCallback", function() {
var fakeTimers, resolved, rejected, packet, cmdPacket;
beforeEach(function() {
cmdPacket = new Buffer([0xFF, 0xFD, 0x00, 0x01, 0x04, 0x01, 0xFD]);
packet = {
sop1: 0xFF,
sop2: 0xFF,
mrsp: 0x00,
seq: 0x00,
dlen: 0x01,
checksum: 0xFE
};
fakeTimers = sinon.useFakeTimers();
resolved = spy();
rejected = spy();
sphero._queuePromise(cmdPacket, resolved, rejected);
sphero._execCallback(0x04, packet);
});
afterEach(function() {
fakeTimers.restore();
});
it("triggers callback with args", function() {
expect(resolved).to.be.calledWith(packet);
});
it("removes the promise from the queue", function() {
expect(sphero.responseQueue[0x04]).to.be.null;
});
context("when queued promise has already been removed", function() {
it("does not exist and does not try to trigger it", function() {
sphero._queuePromise(cmdPacket, resolved, rejected);
expect(sphero.responseQueue[0x04]).to.not.be.null;
sphero._execCallback(0x04, packet);
expect(sphero.responseQueue[0x04]).to.be.null;
sphero._execCallback(0x04, packet);
});
});
});
describe("#responseCmd", function() {
beforeEach(function() {
sphero.responseQueue[1] = {
did: 0x00,
cid: 0x01,
callback: spy(),
timeoutId: 1
};
});
it("returns did and cid stored in seq pos of responseQueue", function() {
expect(sphero._responseCmd(1)).to.be.eql({ did: 0x00, cid: 0x01 });
});
it("returns null if the responseQueue position is not found", function() {
expect(sphero._responseCmd(100)).to.be.null;
});
});
});