Skip to content

Commit

Permalink
feat(Commands): Commands can now be pinned to a particular this con…
Browse files Browse the repository at this point in the history
…text

By providing a context when registering the command SockBot will ensure that that command will

always be called with that context, allowing use of the keyword `this` if needed or desired by

programming practices within the plugin providing the command.

Any command that relies on the previously undocumented value of `this` within a command handler will

be broken by this update.
  • Loading branch information
AccaliaDeElementia committed Dec 14, 2016
1 parent c06ef05 commit 0e5d698
Show file tree
Hide file tree
Showing 2 changed files with 71 additions and 11 deletions.
16 changes: 12 additions & 4 deletions lib/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -531,9 +531,10 @@ exports.bindCommands = function bindCommands(forum) {
* @param {string} command Command to be added
* @param {string} helpText Short help text for command
* @param {CommandHandler} handler Function to handle the command
* @param {object} [context] `this` context for the command. If not provided context will be a bare Object.
* @returns {Promise} Resolves when command has been added
*/
static add(command, helpText, handler) {
static add(command, helpText, handler, context) {
return new Promise((resolve, reject) => {
debug(`Registering command: ${command}`);
const cmd = command.toLowerCase(),
Expand All @@ -547,10 +548,13 @@ exports.bindCommands = function bindCommands(forum) {
if (!checkAvailable(forbiddenCmds, cmd, 'error', forbiddenMsg)) {
return reject(new Error('E_FORBIDDEN_COMMAND'));
}
if (context === undefined) {
context = new Object();
}
handlers[cmd] = {
commandText: command,
help: helpText,
handler: handler
handler: handler.bind(context)
};
return resolve(this);
});
Expand All @@ -564,9 +568,10 @@ exports.bindCommands = function bindCommands(forum) {
*
* @param {string} command Command alias to be added
* @param {CommandHandler} handler Function to handle the command
* @param {object} [context] `this` context for the command. If not provided context will be a bare Object.
* @returns {Promise} Resolves when command has been added
*/
static addAlias(command, handler) {
static addAlias(command, handler, context) {
return new Promise((resolve, reject) => {
debug(`Registering command alias: ${command}`);
const cmd = command.toLowerCase(),
Expand All @@ -580,7 +585,10 @@ exports.bindCommands = function bindCommands(forum) {
if (!checkAvailable(forbiddenCmds, cmd, 'error', forbiddenMsg)) {
return reject(new Error('E_FORBIDDEN_COMMAND'));
}
shadowHandlers[cmd] = handler;
if (context === undefined) {
context = new Object();
}
shadowHandlers[cmd] = handler.bind(context);
return resolve(this);
});
}
Expand Down
66 changes: 59 additions & 7 deletions test/lib/commandsTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -445,7 +445,7 @@ describe('lib/config', () => {
spy.firstCall.args[0].should.include('help: print command help listing');
spy.firstCall.args[0].should.not.include('help: print command help listing *');
});

it('should indicate extended help without short help available', () => {
commands.internals.helpTopics.shutup = 'foobar';
const spy = sinon.spy();
Expand Down Expand Up @@ -1114,11 +1114,37 @@ describe('lib/config', () => {
handler = sinon.spy();
expect(commands.internals.handlers[cmd]).to.be.not.ok;
return commands.internals.Commands.add(cmd, text, handler).then(() => {
commands.internals.handlers[cmd].should.eql({
commandText: cmd,
handler: handler,
help: text
});
const obj = commands.internals.handlers[cmd];
obj.commandText.should.equal(cmd);
obj.handler.should.be.a('function');
obj.help.should.equal(text);
handler.callCount.should.equal(0);
obj.handler();
handler.callCount.should.equal(1);
});
});
it('should use default `this` when context not provided', () => {
const cmd = `a${Math.random()}a`,
text = `b${Math.random()}b`,
handler = sinon.spy();
expect(commands.internals.handlers[cmd]).to.be.not.ok;
return commands.internals.Commands.add(cmd, text, handler).then(() => {
commands.internals.handlers[cmd].handler();
handler.firstCall.thisValue.should.deep.equal({});
});
});
it('should use provided `this` context', () => {
const cmd = `a${Math.random()}a`,
text = `b${Math.random()}b`,
handler = sinon.spy(),
context = {
foo: Math.random(),
bar: Math.random()
};
expect(commands.internals.handlers[cmd]).to.be.not.ok;
return commands.internals.Commands.add(cmd, text, handler, context).then(() => {
commands.internals.handlers[cmd].handler();
handler.firstCall.thisValue.should.equal(context);
});
});
it('should normalize command case for matching', () => {
Expand Down Expand Up @@ -1180,7 +1206,33 @@ describe('lib/config', () => {
handler = sinon.spy();
expect(commands.internals.shadowHandlers[cmd]).to.be.not.ok;
return commands.internals.Commands.addAlias(cmd, handler).then(() => {
commands.internals.shadowHandlers[cmd].should.equal(handler);
const func = commands.internals.shadowHandlers[cmd];
func.should.be.a('function');
handler.callCount.should.equal(0);
func();
handler.callCount.should.equal(1);
});
});
it('should use default `this` when context not provided', () => {
const cmd = `a${Math.random()}a`,
handler = sinon.spy();
expect(commands.internals.shadowHandlers[cmd]).to.be.not.ok;
return commands.internals.Commands.addAlias(cmd, handler).then(() => {
commands.internals.shadowHandlers[cmd]();
handler.firstCall.thisValue.should.deep.equal({});
});
});
it('should use provided `this` context', () => {
const cmd = `a${Math.random()}a`,
handler = sinon.spy(),
context = {
foo: Math.random(),
bar: Math.random()
};
expect(commands.internals.shadowHandlers[cmd]).to.be.not.ok;
return commands.internals.Commands.addAlias(cmd, handler, context).then(() => {
commands.internals.shadowHandlers[cmd]();
handler.firstCall.thisValue.should.equal(context);
});
});
it('should normalize command case for matching', () => {
Expand Down

0 comments on commit 0e5d698

Please sign in to comment.