Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Refactoring: Event Subscribing gets its own class.

NRC is now just a constructor/central hub for accessing all other
objects.

There's more refactoring to do. And a version bump needed.
  • Loading branch information...
commit cbe08b1123b3653d51a7544739810919fb3b067b 1 parent 9899c12
@Havvy Havvy authored
View
1  .gitignore
@@ -3,3 +3,4 @@ node_modules/*
.project
.buildpath
npm-debug.log
+*sublime*
View
16 lib/bisubscriber.js
@@ -48,7 +48,9 @@ var BiEventSubscriber = function (primary, secondary, ctx) {
};
BiEventSubscriber.prototype = {
- _wrap : function (ctx, fn) {
+ _wrap : function (fn) {
+ var ctx = this._ctx;
+
return function () {
try {
fn.apply(ctx, arguments);
@@ -65,11 +67,9 @@ BiEventSubscriber.prototype = {
var event = events[ix];
if (event[0] === '!') {
- this._secondary.on(event.substr(1),
- this._wrap(this._secondary, listener));
+ this._secondary.on(event.substr(1), this._wrap(listener));
} else {
- this._primary.on(event,
- this._wrap(this._primary, listener));
+ this._primary.on(event, this._wrap(listener));
}
}
},
@@ -83,13 +83,13 @@ BiEventSubscriber.prototype = {
_onceString : function (string, listener) {
var events = string.split(" ");
- for (var ix = 0; ix < string.split; ix++) {
+ for (var ix = 0; ix < events.length; ix++) {
var event = events[ix];
if (event[0] === '!') {
- this._secondary.once(event.substr(1), this._wrap(secondary, listener));
+ this._secondary.once(event.substr(1), this._wrap(listener));
} else {
- this._primary.once(event, this._wrap(primary, listener));
+ this._primary.once(event, this._wrap(listener));
}
}
},
View
35 lib/commander.js
@@ -1,6 +1,12 @@
+/**
+ *
+ * Command Handler
+ *
+ */
+
var events = require('events');
var util = require('util');
-var Log = require('./protocols/log');
+
var Command = require('./structures/command');
/**
@@ -8,25 +14,12 @@ var Command = require('./structures/command');
* @param {NRC} nrc Context to which the commander listens upon. Must be an
* event emitter that emits the 'privmsg' event.
* @param {Object} config Configuration object. See Readme.md for options.
- * @param {Object} opt Optional parameters. List: log
*/
-var Commander = function (nrc, config, opt) {
+var Commander = function (ctx, config) {
events.EventEmitter.call(this);
var that = this;
- this.ctx = nrc;
+ this.ctx = ctx;
this.trigger = config.trigger || "!";
- this.log = (opt && opt.log) || {};
-
- nrc.on('privmsg', function (msg) {
- // Deterimine if the event is a command.
- var command = that.getCommandString(msg);
- if (command) {
- command = Object.freeze(new Command(msg.actor, command,
- msg.channel, msg.isQuery));
- Log.event(that.log, "Command: " + command.name);
- that.emit(command.name, command);
- }
- });
};
Commander.prototype = new events.EventEmitter();
@@ -43,6 +36,16 @@ Commander.prototype.on = function (message, callback) {
callback.bind(this.ctx));
};
+Commander.prototype.parseMessage = function (msg) {
+ var command = this.getCommandString(msg);
+
+ if (command) {
+ command = Object.freeze(new Command(msg.actor, command,
+ msg. channel, msg.isQuery));
+ this.emit(command.name, command);
+ }
+};
+
Commander.prototype.getCommandString = function (privmsg) {
if (privmsg.message[0] === this.trigger[0]) {
return privmsg.message.substr(1);
View
2  lib/events.js
@@ -2,7 +2,7 @@
* EventEmitter wrapper which wraps on, once, and emit so that they may
* * on/once can take a map of events to listeners.
* * listen/emit multiple events by seperating events with a space.
- */
+ */
var events = require('events');
View
2  lib/index.js
@@ -4,4 +4,4 @@ module.exports = {
Command : require('./structures/command'),
Socket : require('./socket'),
Log : require('./protocols/log')
-}
+};
View
83 lib/modules.js
@@ -0,0 +1,83 @@
+/* This class is tightly coupled with the main NRC class.
+ * The reason it is located in its own class and not the NRC class is to
+ * keep the modules related code in one place, easier to understand without
+ * having all the non-module related code
+ */
+
+var Modules = function (bisubscriber) {
+ this._subscriber = bisubscriber; // {BiEventSubscriber}
+ this._exports = {}; //
+ this._loadedNames = []; // {List<String>}
+
+ this.require(require("../modules/help"));
+ this.require(require("../modules/user"));
+ this.require(require("../modules/server"));
+};
+
+Modules.prototype.require = function (module) {
+ var nrc = this._nrc;
+ var that = this;
+
+ function listen (event, listener) {
+ that._subscriber.on(event, listener);
+ }
+
+ // DEPENDENCIES
+ if (module.dependencies) {
+ module.dependencies.forEach(function (dependency) {
+ if (!that.isModule(dependency)) {
+ throw new Exception("Module dependency," + dependency +
+ ", not loaded for module" + module.name);
+ }
+ });
+ }
+
+ // NAME
+ if (this.isModule(module.name)) {
+ throw new Exception("Tried to load same module, " + module.name +
+ ", twice.");
+ } else if (typeof module.name === "string" && module.name.length === 0) {
+ throw new Exception("Module's name property must be a non-empty " +
+ "string.");
+ }
+
+ this._loadedNames.push(module.name);
+
+ // HANDLERS
+ for (var handler in module.handlers) {
+ var events = handler.split(" ");
+ var listener = module.handlers[handler];
+
+ for (var ix = 0; ix < events.length; ix++) {
+ listen(events[ix], listener);
+ }
+ }
+
+ // EXPORTS
+ this._exports[module.name] = module.exports || {};
+ this._exports[module.name].name = module.name;
+};
+
+Modules.prototype.use = function (name) {
+ return this._exports[name];
+};
+
+Modules.prototype.isModule = function (name) {
+ return this._loadedNames.indexOf(name) !== -1;
+};
+
+Modules.prototype.getAllModuleNames = function () {
+ // Return a copy of the names array.
+ return this._loadedNames.slice();
+};
+
+Modules.prototype.getAllModuleExports = function () {
+ var that = this;
+ var exports = {};
+ this.getAllModuleNames().forEach(function (name) {
+ exports[name] = that._exports[name];
+ });
+ return exports;
+};
+
+module.exports = Modules;
View
278 lib/nrc.js
@@ -1,135 +1,124 @@
+/**
+ *
+ * This is mostly documented by readme.md.
+ *
+ */
+
var events = require('events');
+var util = require('util');
+
+// TODO: Remove protocol stuff
var Log = require('./protocols/log');
+
var IrcSocket = require('./socket');
var Message = require('./structures/message');
var Commander = require('./commander');
-var util = require('util');
-
+var Modules = require('./modules');
+var Subscriber = require('./bisubscriber');
+
+var defaultNRCConfiguration = {
+ channels: [],
+ nickserv: "nickserv",
+ password: undefined,
+ nick: "nrcbot",
+ user: "nrc",
+ port: 6667,
+ modules: [], // unimplemented
+ realname: "nrc beta 0.3",
+ trigger: '!'
+};
+
+var defaults = function (o, d) {
+ var n = {};
+ for (var key in d) {
+ if (d.hasOwnProperty(key)) {
+ if (o[key] === undefined) {
+ n[key] = d[key];
+ } else {
+ n[key] = o[key];
+ }
+ }
+ }
+ return n;
+};
+
+/** Fields
+ *
+ * _nrcEmitter
+ * _config
+ * _socket
+ * _commander
+ * _nick
+ * _subscriber
+ * _modules
+ *
+ ** Callbacks
+ *
+ * _onData
+ * _onReady
+ */
var NRC = function (config, opt) {
var that = this;
- events.EventEmitter.call(this);
+ this._config = config = defaults(config, defaultNRCConfiguration);
+
+ this._nrcEmitter = new events.EventEmitter();
- this._config = config;
this._socket = new IrcSocket(config, opt);
this._commander = new Commander(this, config, opt);
this._nick = config.nick;
- this._loadedModuleNames = [];
- this._modules = {};
this.log = (opt && opt.log) || {};
- this._socket.on('data', function (raw) {
- var event = Object.freeze(new Message(raw, that));
-
- // Emit the event.
- // Log Format: NAME [SENDER] PARAMETERS
- Log.event(that.log,
- [event.name,
- "[" + event.sender + "]",
- event.params.join(" ")].join(" "));
-
- that.emit(event.name, event);
- });
-
- this._socket.on('ready', function () {
- that.emit("load");
+ this._subscriber = new Subscriber(this._nrcEmitter, this._commander);
- var channels = config.channels;
+ // TODO: Get rid of context!
+ this._subscriber.setContext(this._commander);
+ this._subscriber.on("privmsg", this._commander.parseMessage);
+ this._subscriber.setContext(this);
- that.say(config.nickserv, "identify " + config.password);
+ this._modules = new Modules(this._subscriber);
- for (var chan in channels) {
- if (channels.hasOwnProperty(chan)) {
- that.join(channels[chan]);
- }
- }
+ this._socket.on('data', this._onData.bind(this));
+ this._socket.on('ready', this._onReady.bind(this));
- that.emit("ready");
+ // Standard event for IRC quitting.
+ this._subscriber.on("error", function (event) {
+ this._socket.end();
});
-
- this.on("error", function (event) {
- this._socket.disconnect();
- });
-
- this.require(require("../modules/help"));
- this.require(require("../modules/user"));
- this.require(require("../modules/server"));
};
-NRC.prototype = new events.EventEmitter();
-NRC.prototype.constructor = NRC;
-
-NRC.prototype.config = function (param) {
- return this._config[param];
-};
+// IMPL STUFF
-// EVENTS
-
-NRC.prototype._onString = function (string, listener) {
- var that = this;
-
- var listenerWrapper = function () {
- try {
- listener.apply(this, arguments);
- } catch (err) {
- console.log(err.stack);
- }
- };
-
- string.split(" ").forEach(function (event) {
- if (event[0] === '!') {
- that._commander.on(event.substr(1), listenerWrapper);
- } else {
- events.EventEmitter.prototype.on.call(that, event, listenerWrapper);
- }
- });
-};
+NRC.prototype._onReady = function () {
+ this._nrcEmitter.emit("load");
-NRC.prototype._onMap = function (map) {
- for (var event in map) {
- this._onString(event, map[event]);
+ if (this._config.password) {
+ this.say(this._config.nickserv, "identify " + this._config.password);
}
-};
-NRC.prototype.on = function () {
- switch (arguments.length) {
- case 1: this._onMap(arguments[0]); break;
- case 2: this._onString(arguments[0], arguments[1]); break;
- default: throw new Exception("on takes one or two arguments.");
+ for (var ix = 0; ix < this._config.channels.length; ix++) {
+ this.join(this._config.channels[ix]);
}
+
+ this._nrcEmitter.emit("ready");
};
-NRC.prototype._onceString = function (string, listener) {
- var that = this;
+NRC.prototype._onData = function (raw) {
+ var event = Object.freeze(new Message(raw, this));
- var listenerWrapper = function () {
- try {
- listener.apply(this, arguments);
- } catch (err) {
- console.log(err.stack);
- }
- };
+ // Emit the event.
+ // Log Format: NAME [SENDER] PARAMETERS
+ Log.event(this.log,
+ [event.name,
+ "[" + event.sender + "]",
+ event.params.join(" ")].join(" "));
- string.split(" ").forEach(function (event) {
- if (event[0] === '!') {
- that._commander.on(string, listenerWrapper);
- } else {
- events.EventEmitter.prototype.once.call(that, event, listenerWrapper);
- }
- });
+ this._nrcEmitter.emit(event.name, event);
};
-NRC.prototype._onceMap = function (ee, map) {
- for (var event in map) {
- this._onceString(event, map[event]);
- }
-};
+// GETTER
-NRC.prototype.once = function () {
- switch (arguments.length) {
- case 1: return this._onceMap(arguments[0]);
- case 2: return this._onceString(arguments[0], arguments[1]);
- default: throw new Exception("on takes one or two arguments.");
- }
+NRC.prototype.config = function (param) {
+ return this._config[param];
};
// ACTIONS
@@ -140,7 +129,7 @@ NRC.prototype.connect = function () {
};
NRC.prototype.disconnect = function () {
- this._socket.disconnect();
+ this._socket.end();
return this;
};
@@ -176,17 +165,21 @@ NRC.prototype.act = function(location, message) {
NRC.prototype.join = function (channel) {
Log.output(this.log, "join " + channel);
- this._socket.raw("JOIN " + channel);
+ this._socket.raw(["JOIN", channel]);
return this;
};
NRC.prototype.part = function (channel, reason) {
Log.output(this.log, "part " + channel + " " + reason);
- this._socket.raw([
- "PART",
- channel,
- reason ? ":" + reason : ""
- ].join(" ").trim());
+
+ this._socket.raw(function () {
+ var part = ["PART", channel];
+ if (reason) {
+ part.push(":" + reason);
+ }
+ return part;
+ }());
+
return this;
};
@@ -207,78 +200,37 @@ NRC.prototype.nick = function (newNick) {
}
};
-// MODULES
-
-NRC.prototype.require = function (module) {
- var that = this;
-
- function listen (event, listener) {
- if (event[0] === '!') {
- that._commander.on(event.substr(1), listener);
- } else {
- that.on(event, listener);
- }
- }
-
- // DEPENDENCIES
- if (module.dependencies) {
- module.dependencies.forEach(function (dependency) {
- if (!that.isModule(dependency)) {
- throw new Error("Module dependency," + dependency +
- ", not loaded for module" + module.name);
- }
- });
- }
-
- // NAME
- if (this.isModule(module.name)) {
- throw new Error("Tried to load same module, " + module.name + ", twice.");
- } else if (module.name.length === 0) {
- throw new Error("Module must be named.");
- }
-
- this._loadedModuleNames.push(module.name);
+// EVENTS
- // HANDLERS
- for (var handler in module.handlers) {
- var events = handler.split(" ");
- var listener = module.handlers[handler];
+NRC.prototype.on = function (a1, a2) {
+ this._subscriber.on(a1, a2);
+};
- for (var ix = 0; ix < events.length; ix++) {
- listen(events[ix], listener);
- }
- }
+NRC.prototype.once = function (a1, a2) {
+ this._subscriber.once(a1, a2);
+};
- // EXPORTS
- this._modules[module.name] = module.exports || {};
- this._modules[module.name].name = module.name;
+// MODULES
- return this;
+NRC.prototype.require = function (module) {
+ return this._modules.require(module);
};
NRC.prototype.use = function (name) {
- return this._modules[name];
+ return this._modules.use(name);
};
NRC.prototype.isModule = function (name) {
- return this._loadedModuleNames.indexOf(name) !== -1;
+ return this._modules.isModule(name);
};
NRC.prototype.getAllModuleNames = function () {
- var names = [];
- for (var ix = 0; ix < this._loadedModuleNames.length; ix++) {
- names[ix] = this._loadedModuleNames[ix];
- }
- return names;
+ return this._modules.getAllModuleNames();
};
NRC.prototype.getAllModuleExports = function () {
- var that = this;
- var exports = {};
- this.getAllModuleNames().forEach(function (name) {
- exports[name] = that.modules[name];
- });
- return exports;
+ return this._modules.getAllModuleExports();
};
+// Export the class.
module.exports = NRC;
View
95 lib/socket.js
@@ -2,25 +2,63 @@
* @author havvy
* IRC Socket - handles communication between an IRC server and a Node.js script.
*
- * Wraps the Node.js net.Socket socket for IRC communications. Sometimes sends
- * unfinished messages.
+ * This socket exposes the connect and end methods of the socket Interface.
+ *
+ * A network configuration object needs the following properties:
+ * server - IRC server to connect to. _Example:_ _irc.mibbit.net_
+ * nick - Nickname the bot will use.
+ * user - Username the bot will use.
+ * realname - Realname for the bot.
+ * port - Port to connect to. Defaults to 6667.
+ *
+ * Note that the only default value from the perspective of the socket is
+ * the port. This class is reusable outside of the framework, should you want
+ * to create your own framework.
+ *
+ * To send messages to the server, use socket.raw(). It accepts either a
+ * string or an array which it will convert into a string by joining with
+ * a space. The message must follow the IRC protocol. For example, to send a
+ * message to the channel #evilbiscuits, you would send one of the following:
+ *
+ * socket.raw("PRIVMSG #evilbiscuits :The message!");
+ * socket.raw(["PRIVMSG", "#evilbiscuits", ":The message!"]);
+ *
+ * <Documentation TODO: 'data event' & handled nuisances>
+ *
+ * Public methods
+ * - new(network: Object[, opt: Object]): IRCSocket
+ * - connect(): undefined
+ * - end(): undefined
+ * - raw(message: List[String]): undefined
+ * - raw(message: String): undefined
+ * - isConnected(): boolean
+ * - getRealName(): String
+ * - onConnect(callback: function): undefined
+ * - onDisconnect(callback: function): undefined
+ * + (from events.EventEmitter)
+ * - on
+ * - once
+ *
+ * Events
+ * - ready(): Once the first 001 message has been acknowledged.
+ * - data(message: String): Every message (including the 001) from the
+ * sender (inclusive) the the newline (exclusive).
*/
var net = require('net');
var events = require('events');
+var util = require('util');
var Log = require('./protocols/log');
/**
* @constructor
- * @param {Object} network Network information. See Readme.md for info.
+ * @param {Object} network Network information. See above for info.
* @param {Object} opt Optional parameters: socket
*/
var Socket = function (network, opt) {
that = this;
events.EventEmitter.call(this);
- this.nick = network.nick;
- this.user = network.user;
this.port = network.port || 6667;
this.netname = network.server;
@@ -36,50 +74,54 @@ var Socket = function (network, opt) {
data = data.split('\r\n');
// Filter-Filter-Map pattern.
for (var ix = 0; ix < data.length; ix++) {
+ var line = data[ix];
+
// Filter empty lines.
- if (data[ix] === '') {
+ if (line === '') {
continue;
}
// Filter pings.
- if (data[ix].indexOf('PING') === 0) {
- var reply = data[ix].slice(data[ix].indexOf(':'));
- that.raw("PONG " + reply);
+ if (line.indexOf('PING') === 0) {
+ var reply = line.slice(line.indexOf(':'));
+ that.raw(["PONG", reply]);
continue;
}
// Map event emitting.
- Log.input(that.log, data[ix]);
- that.emit('data', data[ix]);
+ Log.input(that.log, line);
+ that.emit('data', line);
}
};
var determineWhenReady = function (data) {
- var lines = data.split('\r\n');
+ data = data.split('\r\n');
// Filter-Filter-Map pattern.
- for (var ix = 0; ix < lines.length; ix++) {
+ for (var ix = 0; ix < data.length; ix++) {
+ var line = data[ix];
+
// Filter empty lines.
- if (lines[ix] === '') {
+ if (line.length === 0) {
continue;
}
- lines[ix] = lines[ix].split(" ");
- if (lines[ix][1] === "001") {
- that.emit("ready");
- //that.server.removeListener('data', determineWhenReady);
- break;
+ line = line.split(" ");
+ if (line[1] === "001") {
+ that.emit("ready");
+ that.server.removeListener('data', determineWhenReady);
+ break;
}
}
};
this.server.once('connect', function () {
that.connected = true;
- that.raw("NICK " + network.nick);
- that.raw("USER " + (that.user || "shadow") + " 8 * :" + that._realname);
+ that.raw(["NICK", network.nick]);
+ that.raw(["USER", network.user || "user", "8 * :" + network.realname]);
});
this.once('ready', function () {
// RAWR! I'm a <s>monster</s> Bot!
- that.raw("MODE " + network.nick + " :+B");
+ that.raw(["MODE", network.nick, ":+B"]);
});
this.server.once('close', function () {
@@ -108,7 +150,7 @@ Socket.prototype.connect = function () {
/**
* Disconnect from the server without a reason.
*/
-Socket.prototype.disconnect = function () {
+Socket.prototype.end = function () {
if (!this.isConnected()) {
return;
}
@@ -122,8 +164,11 @@ Socket.prototype.disconnect = function () {
* @param message Message to send to network.
*/
Socket.prototype.raw = function (message) {
+ if (util.isArray(message)) {
+ message = message.join(" ");
+ }
+
this.server.write(message + '\n', 'ascii');
- Log.output(this.log, message);
};
/**
@@ -154,6 +199,4 @@ Socket.prototype.onDisconnect = function (handler) {
this.server.on('close', handler.bind(this));
};
-Socket.prototype._realname = "nrc beta";
-
module.exports = Socket;
View
4 lib/structures/message.js
@@ -44,7 +44,6 @@ var Message = function (message, receiver) {
break;
case "privmsg":
this.actor = this.sender.nick;
- //console.log(require('util').inspect(receiver));
if (this.params[0] === receiver.nick()) {
this.isQuery = true;
this.channel = this.actor;
@@ -54,6 +53,9 @@ var Message = function (message, receiver) {
}
this.message = this.params[1].trim().replace(mircColors, "");
break;
+ case "notice":
+ this.actor = this.sender.nick;
+ break;
case "quit":
this.actor = this.sender.nick;
this.reason = this.params[0].toLowerCase();
View
3  modules/server.js
@@ -1,3 +1,5 @@
+var util = require('util');
+
var capabilities = {};
var _005Handler = function (e) {
@@ -5,7 +7,6 @@ var _005Handler = function (e) {
// Last parameter is plain text.
for (var ix = 1; ix < e.parameters.length - 1; ix++) {
var param = e.parameters[ix].split("=");
-
switch (param.length) {
case 1:
capabilities[param[0]] = true;
View
31 modules/user.js
@@ -1,6 +1,21 @@
+// TODO: XXX: Make this work with multiple NRC instances!
+
+var util = require('util');
+
var SSet = require("simplesets").Set;
var server;
+var filterStatusSymbol = function (nickname) {
+ // We have bigger problems if server isn't there...
+ if (server && server.capabilities && server.capabilities.STATUSMSG) {
+ if (server.capabilities.STATUSMSG.indexOf(nickname[0]) !== -1) {
+ return nickname.substring(1);
+ }
+ }
+
+ return nickname;
+};
+
var onLoad = function () {
server = this.use("server");
};
@@ -14,7 +29,6 @@ var User = function (name, channel) {
};
var addUserChannel = function (user, channel) {
-
if (users[user]) {
users[user].channels.add(channel);
} else {
@@ -31,9 +45,11 @@ var isSelf = function (nrc, nick) {
};
var selfPart = function (channel) {
- users.forEach(function (user) {
- removeUserChannel(user, channel);
- });
+ for (var user in users) {
+ if (Object.hasOwnProperty(users, user)) {
+ removeUserChannel(user, channel);
+ }
+ }
};
var userQuit = function (user) {
@@ -49,11 +65,8 @@ var userQuit = function (user) {
*/
var namesHandler = function (msg) {
msg.users.forEach(function (user) {
- // The numeric will add status messages (~, @, ect.) to nicks,
- // so they need to pruned.
- if (server.capabilities.STATUSMSG.indexOf(user[0]) !== -1) {
- user = user.substring(1);
- }
+ // The numeric will add status messages (~, @, ect.) to nicks.
+ user = filterStatusSymbol(user);
addUserChannel(user, msg.channel);
});
View
12 readme.md
@@ -45,14 +45,16 @@ It is suggested that your static network configuration objects go in _/config/%N
A network configuration object has the following properties:
* server - IRC server to connect to. _Example:_ _irc.mibbit.net_
-* nick - Nickname the bot will use. *Required*
-* user - Username the bot will use. *Required*
+* nick - Nickname the bot will use. Defaults to "nrcbot"
+* user - Username the bot will use. Defaults to "user"
+* realname - Realname for the bot. Defaults to "nrc v0.3"
* port - Port to connect to. Defaults to 6667.
-* password - Password for identifying to services. Useless without nickserv option.
-* nickserv - Nickname for nickserv service. Useless without password option.
+* password - Password for identifying to services.
+* nickserv - Nickname for nickserv service. Defaults to "nickserv".
* trigger - Command character to trigger commands with. By default, '!'.
* channels - Array of channels to autojoin. _Example:_ ["#help", "#nrc"]
-Other modules may require more options.
+
+Other modules may require or use more options.
-------------
View
135 spec/bisubscriber.spec.js
@@ -26,7 +26,7 @@ describe("bisubscribers subscribe events to two event emitters", function () {
primary.emit("event");
});
- waitsFor(function() {
+ waitsFor(function() {
return spy1.wasCalled;
}, "spy1 was called", 200);
@@ -88,6 +88,137 @@ describe("bisubscribers subscribe events to two event emitters", function () {
runs(function () {
expect(spy3).not.toHaveBeenCalled();
expect(spy4).toHaveBeenCalled(); // redundant
- });
+ });
+ });
+});
+
+describe("dealing with context", function () {
+ var ctx, primary, secondary, capture, done;
+
+ beforeEach(function () {
+ ctx = {};
+ primary = new events.EventEmitter();
+ secondary = new events.EventEmitter();
+ capture = undefined;
+ done = false;
+ });
+
+ it("can take an optional context in the constructor", function () {
+ var subscriber = new BiSubscriber(primary, secondary, ctx);
+
+ runs(function() {
+ subscriber.on("data", function () {
+ capture = this;
+ done = true;
+ });
+
+ primary.emit("data");
+ });
+
+ waitsFor(function () { return done; }, "done", 200);
+
+ runs(function () {
+ expect(capture).toBe(ctx);
+ });
+ });
+
+ it("can change the context after creation", function () {
+ var subscriber = new BiSubscriber(primary, secondary);
+ subscriber.setContext(ctx);
+
+ runs(function() {
+ subscriber.on("data", function () {
+ capture = this;
+ done = true;
+ });
+
+ primary.emit("data");
+ });
+
+ waitsFor(function () { return done; }, "done", 200);
+
+ runs(function () {
+ expect(capture).toBe(ctx);
+ });
+ });
+});
+
+describe("quantification (on vs. once)", function () {
+ var subscriber, primary, secondary, spy, eventCount, isDone;
+
+ isDone = function () {
+ return eventCount == 2;
+ };
+
+ beforeEach(function () {
+ primary = new events.EventEmitter();
+ secondary = new events.EventEmitter();
+ subscriber = new BiSubscriber(primary, secondary, null);
+ spy = jasmine.createSpy();
+ eventCount = 0;
+
+ subscriber.on("event !event", function () {
+ eventCount += 1;
+ });
+ });
+
+ it("handles once one time (primary)", function () {
+ runs(function () {
+ subscriber.once("event", spy);
+
+ primary.emit("event");
+ primary.emit("event");
+ });
+
+ waitsFor(isDone, "event fired twice", 200);
+
+ runs(function () {
+ expect(spy.calls.length).toEqual(1);
+ });
+ });
+
+ it("handles once one time (secondary)", function () {
+ runs(function () {
+ subscriber.once("!event", spy);
+
+ secondary.emit("event");
+ secondary.emit("event");
+ });
+
+ waitsFor(isDone, "event fired twice", 200);
+
+ runs(function () {
+ expect(spy.calls.length).toEqual(1);
+ });
+ });
+
+ it("handles on multiple times", function () {
+ runs(function () {
+ subscriber.on("event", spy);
+
+ primary.emit("event");
+ primary.emit("event");
+ });
+
+ waitsFor(isDone, "event fired twice", 200);
+
+ runs(function () {
+ expect(spy.calls.length).toEqual(2);
+ });
+ });
+
+ it("handles once one time", function () {
+ runs(function () {
+ subscriber.on("!event", spy);
+
+ secondary.emit("event");
+ secondary.emit("event");
+ });
+
+ waitsFor(isDone, "event fired twice", 200);
+
+ runs(function () {
+ expect(spy.calls.length).toEqual(2);
+ });
});
});
View
6 spec/commander.spec.js
@@ -1,3 +1,5 @@
+var util = require('util');
+
var Commander = require('../lib/commander');
var EE = require('events').EventEmitter;
var mockMessage = Object.freeze({
@@ -7,7 +9,9 @@ var mockMessage = Object.freeze({
message : 'event'
});
-describe("binding", function () {
+// Does not work this way anymore. Commander doesn't listen to the ctx
+// anymore. Instead, it's up to the ctx to set this up.
+xdescribe("binding", function () {
it('binds callbacks to its context', function () {
var ctx = new EE();
var commander = new Commander(ctx, {}, {});
View
61 spec/ircsocket.spec.js
@@ -1,8 +1,7 @@
/**
* @author havvy
* This document does not respect the 80 char length limit.
- * Waits total: 1.1 seconds. :(
- * Wanted: Less than 0.2 seconds.
+ * Waits total max: 1.1 seconds.
*/
/*
@@ -12,14 +11,17 @@
*/
var MockSocket = require('./mocksocket');
var IRCSocket = require('../lib/socket.js');
-var socket, mocksocket;
+
var network = Object.freeze({
nick : 'testbot',
user : 'testuser',
server : 'irc.test.net',
+ realname: 'realbot'
});
describe("connecting to a network", function connectingToANetwork () {
+ var mocksocket, socket;
+
it('knows whether or not it is connected.', function () {
mocksocket = new MockSocket();
socket = new IRCSocket(network, {socket : mocksocket});
@@ -27,25 +29,24 @@ describe("connecting to a network", function connectingToANetwork () {
expect(socket.isConnected()).toBeFalsy();
});
- it('can connect to a network', function () {
+ it('can connect to a network', function () {
socket.connect();
expect(socket.isConnected()).toBeTruthy();
});
- it('can then disconnect', function () {
- socket.disconnect();
+ it('can then disconnect', function () {
+ socket.end();
expect(socket.isConnected()).toBeFalsy();
});
it('declares NICK and USER to the server on connection', function () {
mocksocket = new MockSocket();
- spyOn(mocksocket, 'write');
socket = new IRCSocket(network, {socket : mocksocket});
socket.connect();
- socket.disconnect();
+ socket.end();
expect(mocksocket.write).toHaveBeenCalledWith('NICK testbot\n', 'ascii');
expect(mocksocket.write).toHaveBeenCalledWith('USER testuser 8 * :' + //-
- socket.getRealName() + '\n', 'ascii');
+ 'realbot\n', 'ascii');
});
it('declares when ready to send commands', function () {
@@ -59,46 +60,56 @@ describe("connecting to a network", function connectingToANetwork () {
socket.connect();
});
- waits(300);
+ waitsFor(function () {
+ return readyIsCalled;
+ }, "ready is emitted", 300);
runs(function () {
- socket.disconnect();
+ socket.end();
expect(readyIsCalled).toBeTruthy();
});
});
});
describe('maintaining connection to a server', function () {
+ var mocksocket, socket;
+
+ beforeEach(function () {
+ mocksocket = new MockSocket();
+ socket = new IRCSocket(network, {socket : mocksocket});
+ });
+
+ afterEach(function () {
+ socket.end();
+ });
+
it('responds to pings', function () {
runs(function () {
- mocksocket = new MockSocket();
- spyOn(mocksocket, 'write');
- socket = new IRCSocket(network, {socket : mocksocket});
socket.connect();
});
- waits(400);
+ waitsFor(function () {
+ return mocksocket.isConnected;
+ }, "socket to connect", 400);
- runs(function () {
+ runs(function () {
expect(mocksocket.write).toHaveBeenCalledWith('PONG :PINGMESSAGE\n', 'ascii');
});
});
it("emits each IRC line in a 'data' event", function () {
- var handler = {handler : function () {}};
+ var spy = jasmine.createSpy();
runs(function () {
- mocksocket = new MockSocket();
- socket = new IRCSocket(network, {socket : mocksocket});
- spyOn(handler, 'handler');
- socket.on('data', handler.handler)
+ socket.on('data', spy);
socket.connect();
});
- waits(400);
+ waitsFor(function () {
+ return mocksocket.isConnected;
+ }, "socket to connect", 400);
runs(function () {
- socket.disconnect();
- expect(handler.handler).toHaveBeenCalledWith(':irc.test.net 001 testbot :Welcome to the Test IRC Network testbot!testuser@localhost');
+ expect(spy).toHaveBeenCalledWith(':irc.test.net 001 testbot :Welcome to the Test IRC Network testbot!testuser@localhost');
});
- })
+ });
});
View
4 spec/ircsocket.test.js
@@ -1,6 +1,6 @@
var Socket = require('../lib/socket');
var winston = require('winston');
-var Log = require('../lib/protocols/log')
+var Log = require('../lib/protocols/log');
var ioed = {
levels: {
@@ -35,7 +35,7 @@ Log(winston.Logger, {
}
});
-var logger = new (winston.Logger)({
+var logger = new (winston.Logger)({
levels: ioed.levels,
transports : [new (winston.transports.Console)({colorize : true, level: 'debug'})]
});
View
42 spec/mocksocket.js
@@ -6,36 +6,26 @@ var EE = require('events').EventEmitter;
var Socket = function () {
EE.call(this);
-};
-
-Socket.prototype = new EE;
-Socket.prototype.connect = function () {
- this.emit("connect");
- setTimeout((function () {
- this.emit("data", 'PING :PINGMESSAGE\r\n');
- this.emit("data", ":irc.test.net 001 testbot :Welcome to the Test IRC Network testbot!testuser@localhost\r\n");
- }).bind(this), 200);
-};
-
-Socket.prototype.end = function () {
+ this.connect = jasmine.createSpy("connect").andCallFake(function () {
+ this.emit("connect");
+ setTimeout((function () {
+ this.emit("data", 'PING :PINGMESSAGE\r\n');
+ this.emit("data", ":irc.test.net 001 testbot :Welcome to the Test IRC Network testbot!testuser@localhost\r\n");
+ this.emit("data", ":irc.test.net 005 testbot STATUSMSG=@&~ :are supported by this server");
+ this.isConnected = true;
+ }).bind(this), 0);
+ });
+
+ this.end = function () {
this.emit("close");
-};
-
-Socket.prototype.write = function (message) {
- void 0;
-};
-
-Socket.prototype.setNoDelay = function () {
- void 0;
-};
+ };
-Socket.prototype.setEncoding = function () {
- void 0;
+ this.write = jasmine.createSpy();
+ this.setNoDelay = jasmine.createSpy();
+ this.setEncoding = jasmine.createSpy();
};
-Socket.prototype.sendMessage = function (message) {
- this.emit("data", message + "\r\n");
-}
+Socket.prototype = new EE();
module.exports = Socket;
View
228 spec/nrc.spec.js
@@ -3,9 +3,10 @@
* Waits Total: 1.1 seconds. :(
*/
+var util = require('util');
+
var NRC = require('../lib/nrc');
var MockSocket = require('./mocksocket');
-var util = require('util');
var network = Object.freeze({
nick : 'testbot',
@@ -13,70 +14,94 @@ var network = Object.freeze({
server : 'irc.test.net'
});
-autonetwork = {
+var autonetwork = {
nickserv : "nickserv",
password : "testpass",
- channels : ["#test"]
+ channels : ["#test"],
+ prototype: network
};
-autonetwork.prototype = network;
-
-var mocksocket;
-var nrc;
+var fakeWrite = function (message) {
+ switch (message) {
+ case "JOIN #test\n":
+ this.emit('data', [
+ ":testbot!testuser@localhost JOIN :#test",
+ ":irc.localhost.net 353 testbot = #test :@testbot",
+ ":irc.localhost.net 366 testbot #test :End of /NAMES list.\r\n"].join('\r\n'));
+ break;
+ case "QUIT\n":
+ this.emit('data', "ERROR :Closing Link: testbot[localhost] (Quit: testbot)\r\n");
+ break;
+ case "NICK newNick\n":
+ this.emit('data', ":testbot!testuser@localhost NICK :newNick\r\n");
+ break;
+ case "PART #test\n":
+ this.emit('data', ":testbot!testuser@localhost PART #test\r\n");
+ break;
+ case "PRIVMSG nickserv :identify testpass\n":
+ this.emit('data', ":nickserv!services@test.net NOTICE :testbot Password accepted - you are now recognized.\rn\n");
+ break;
+ default:
+ void 0;
+ }
+};
describe('basics', function () {
+ var mocksocket, nrc;
+
beforeEach(function () {
mocksocket = new MockSocket();
nrc = new NRC(network, {socket : mocksocket});
});
+ afterEach(function () {
+ nrc.disconnect();
+ });
+
it('wraps an IRC socket', function () {
nrc.connect();
- nrc.disconnect();
});
it('says when it is ready', function () {
- var handler;
+ var handler = jasmine.createSpy("handler");
+
runs(function () {
- handler = {handler : function () {}};
- spyOn(handler, 'handler');
- nrc.on('ready', handler.handler);
+ nrc.on('ready', handler);
nrc.connect();
});
- waits(500);
+ waitsFor(function () {
+ return handler.wasCalled;
+ }, "handler was called", 500);
runs(function () {
- nrc.disconnect();
- expect(handler.handler).toHaveBeenCalled();
+ expect(handler).toHaveBeenCalled();
});
});
});
+
describe('the nrc api', function () {
- mocksocket = new MockSocket();
- nrc = new NRC(network, {socket : mocksocket});
- nrc.connect();
+ var mocksocket, nrc;
beforeEach(function () {
- spyOn(mocksocket, "write").andCallFake(function (message) {
- switch (message) {
- case "JOIN #test\n":
- this.emit('data', ":testbot!testuser@localhost JOIN :#test\r\n:irc.localhost.net 353 testbot = #test :@testbot\r\n:irc.localhost.net 366 testbot #test :End of /NAMES list.");
- break;
- case "QUIT\n":
- this.emit('data', "ERROR :Closing Link: testbot[localhost] (Quit: testbot)\r\n");
- break;
- case "NICK newNick\n":
- this.emit('data', ":testbot!testuser@localhost NICK :newNick\r\n");
- break;
- default:
- void 0;
- }
+ var ready = false;
+
+ mocksocket = new MockSocket();
+ mocksocket.write.andCallFake(fakeWrite);
+
+ nrc = new NRC(network, {socket : mocksocket});
+
+ nrc.on("ready", function () {
+ ready = true;
});
- });
- waits(500);
+ nrc.connect();
+
+ waitsFor(function () {
+ return ready;
+ }, "nrc is ready", 100);
+ });
it('can join channels', function () {
nrc.join("#test");
@@ -94,14 +119,25 @@ describe('the nrc api', function () {
});
it('can part channels without a reason', function () {
- nrc.once('join', function onJoin (msg) {
- nrc.part(msg.channel);
+ var done = false;
+
+ runs(function () {
+ nrc.once('join', function onJoin (msg) {
+ this.part(msg.channel);
+ });
+
+ nrc.once('part', function (msg) {
+ done = true;
+ });
+
+ nrc.join("#test");
});
- nrc.join("#test");
- waits(100);
+ waitsFor(function () { return done; }, "nrc parted", 1000);
- expect(mocksocket.write).toHaveBeenCalledWith("PART #test\n", 'ascii');
+ runs(function () {
+ expect(mocksocket.write).toHaveBeenCalledWith("PART #test\n", 'ascii');
+ });
});
it('can quit', function () {
@@ -112,16 +148,20 @@ describe('the nrc api', function () {
});
describe('state-tracking', function () {
+ var nrc = new NRC(network, {socket: new MockSocket()});
+
it('knows when its nick changes', function () {
- nrc.connect();
+ runs(function () {
+ nrc.connect();
- expect(nrc.nick()).toBe('testbot');
+ expect(nrc.nick()).toBe('testbot');
- runs(function () {
nrc.nick('newNick');
});
- waits(100);
+ waitsFor(function() {
+ return nrc.nick() !== 'testbot';
+ }, "nick changed", 100);
runs(function () {
expect(nrc.nick()).toBe('newNick');
@@ -129,58 +169,102 @@ describe('state-tracking', function () {
});
});
+// This is more an integration test...
+// Should have this test for the Commander spec too.
describe("listening to user commands", function () {
+ var nrc, mocksocket, called;
+
beforeEach(function () {
+ var done = false;
+
mocksocket = new MockSocket();
- nrc = new NRC(network, {socket : mocksocket}).connect().join("#test");
+ mocksocket.write.andCallFake(fakeWrite);
+ nrc = new NRC(network, {socket : mocksocket});
+
+ nrc.on("!testcommand", function () {
+ called = true;
+ });
+
+ nrc.on("join", function () {
+ done = true;
+ });
+
+ nrc.connect().join("#test");
+
+ waitsFor(function () { return done; }, "#test is joined.", 200);
});
- it('emits user commands', function () {
- var on = {testcommand : function (event) {} };
- spyOn(on, 'testcommand');
+ afterEach(function () {
+ nrc.disconnect();
+ });
- nrc.on("!testcommand", on.testcommand);
+ it('listens to commands starting with the trigger letter', function () {
+ runs(function() {
+ mocksocket.emit("data", ":sender!user@localhost PRIVMSG #test :!testcommand\r\n");
+ });
- mocksocket.sendMessage(":sender!user@localhost PRIVMSG #test :!testcommand");
- expect(on.testcommand.callCount).toBe(1);
+ waitsFor(function () { return called; }, "spy was called", 1000);
+ //expect(spy).toHaveBeenCalled();
+ });
- mocksocket.sendMessage(":sender!user@localhost PRIVMSG #test :!testcommand");
- expect(on.testcommand.callCount).toBe(2);
+ it('listens to commands directed to it', function () {
+ runs(function() {
+ mocksocket.emit("data", ":sender!user@localhost PRIVMSG #test :testbot: testcommand\r\n");
+ });
- mocksocket.sendMessage(":sender!user@localhost PRIVMSG #test :testbot: testcommand");
- expect(on.testcommand.callCount).toBe(3);
+ waitsFor(function () { return called; }, "spy was called", 100);
+ //expect(spy).toHaveBeenCalled();
+ });
+
+ it('listens to commands via private messages', function () {
+ runs(function() {
+ mocksocket.emit("data", ":sender!user@localhost PRIVMSG testbot :testcommand\r\n");
+ });
+
+ waitsFor(function () { return called; }, "spy was called", 100);
+ //expect(spy).toHaveBeenCalled();
+ });
- mocksocket.sendMessage(":sender!user@localhost PRIVMSG testbot :testcommand");
- expect(on.testcommand.callCount).toBe(4);
+ it('event ignores the trigger charcter in private messages', function () {
+ runs(function() {
+ mocksocket.emit("data", ":sender!user@localhost PRIVMSG testbot :!testcommand\r\n");
+ });
- mocksocket.sendMessage(":sender!user@localhost PRIVMSG testbot :!testcommand");
- expect(on.testcommand.callCount).toBe(5);
+ waitsFor(function () { return called; }, "spy was called", 100);
+ //expect(spy).toHaveBeenCalled();
});
});
describe("autojoin and autoidentify", function () {
+ var mocksocket, nrc, hasJoined, hasIdentified;
+
beforeEach(function () {
+ hasJoined = false;
+ hasIdentified = false;
+
mocksocket = new MockSocket();
+ mocksocket.write.andCallFake(fakeWrite);
nrc = new NRC(autonetwork, {socket : mocksocket});
- spyOn(mocksocket, "write").andCallFake(function (message) {
- switch (message) {
- case "JOIN #test\n":
- this.emit('data', [":testbot!testuser@localhost JOIN :#test",
- ":irc.localhost.net 353 testbot = #test :@testbot",
- ":irc.localhost.net 366 testbot #test :End of /NAMES list."].join("\r\n"));
- break;
- case "PRIVMSG nickserv :identify testpass\n":
- this.emit('data', ":nickserv!services@test.net NOTICE :heartless Password accepted - you are now recognized.");
- break;
- default:
- void 0;
+ nrc.on("join", function () {
+ hasJoined = true;
+ });
+
+ nrc.on("notice", function(e) {
+ if (e.actor === "nickserv") {
+ hasIdentified = true;
}
});
+
+ nrc.connect();
+ });
+
+ afterEach(function () {
+ nrc.disconnect();
});
it('automatically joins specified channels.', function () {
- waits(100);
+ waitsFor(function () { return hasJoined; }, "has joined", 100);
runs(function () {
expect(mocksocket.write).toHaveBeenCalledWith("JOIN #test\n", 'ascii');
@@ -188,7 +272,7 @@ describe("autojoin and autoidentify", function () {
});
it('automatically identifies to services.', function () {
- waits(100);
+ waitsFor(function () { return hasIdentified; }, "has identified", 100);
runs(function () {
expect(mocksocket.write).toHaveBeenCalledWith("PRIVMSG nickserv :identify testpass\n", 'ascii');
Please sign in to comment.
Something went wrong with that request. Please try again.